Initial openvim v0.4.6 upload
[osm/openvim.git] / openflow
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # PYTHON_ARGCOMPLETE_OK
4
5 ##
6 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
7 # This file is part of openmano
8 # All Rights Reserved.
9 #
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
13 #
14 #         http://www.apache.org/licenses/LICENSE-2.0
15 #
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
20 # under the License.
21 #
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: nfvlabs@tid.es
24 ##
25
26 '''
27 This program is useful to interact directly with Openflow Controllers
28 to clear rules, add and delete rules, list rules, etc.
29 '''
30
31 __author__="Gerardo Garcia, Alfonso Tierno, Pablo Montes"
32 __date__ ="$09-oct-2014 09:09:48$"
33
34 #import time
35 import os
36 import sys
37 import argparse
38 import argcomplete
39 import imp
40 import yaml
41 import requests
42 import logging
43 from openflow_thread import change_db2of, FlowBadFormat
44
45 def of_switches(args):
46     r,c = ofconnector.get_of_switches()
47     if r<0:
48         print c
49         return r
50     else: 
51         for s in c:
52             print " %s %s" % (s[0], s[1])
53     return 0
54
55 def of_list(args):
56     r,c = ofconnector.get_of_rules(not args.no_translate)
57     if r<0:
58         print c
59         return r
60     if args.verbose > 0:
61         print yaml.safe_dump(c, indent=4, default_flow_style=False)
62         return 0
63
64     print "       switch           priority        name                             ingress_port    dst_mac       vlan_id  actions"
65     for name,rule in c.iteritems():
66         action_list=[]
67         for action in rule["actions"]:
68             action_list.append(action[0]+"="+str(action[1]))
69         if "vlan_id" in rule:
70             vlan=str(rule["vlan_id"])
71         else:
72             vlan="any"
73         print "%s  %s  %s  %s  %s  %s  %s" % \
74             (rule["switch"], str(rule["priority"]).ljust(6), name.ljust(40), rule["ingress_port"].ljust(8), \
75             rule.get("dst_mac","any").ljust(18), vlan.ljust(4), ",".join(action_list) )
76     return 0
77
78 def of_clear(args):
79     if not args.force:
80         r = raw_input("Clear all Openflow rules (y/N)? ")
81         if  not (len(r)>0  and r[0].lower()=="y"):
82             return 0
83     r,c = ofconnector.clear_all_flows()
84     if r<0:
85         print c
86         return r
87     return 0
88
89 def of_port_list(args):
90     r,c = ofconnector.obtain_port_correspondence()
91     if r<0:
92         print c
93         return r
94     yaml.safe_dump({"ports": c}, sys.stdout, indent=2, default_flow_style=False)
95
96 #def of_dump(args):
97 #    args.verbose = 3
98 #    args.no_translate=False
99 #    of_list(args)
100     return 0
101
102 def of_reinstall(args):
103     try:
104         URLrequest = "http://%s:%s/openvim/networks/all/openflow" %(vim_host, vim_admin_port)
105         print URLrequest
106         openvim_response = requests.put(URLrequest)
107         print openvim_response.text
108         return 0
109     except requests.exceptions.RequestException as e:
110         print " Exception GET at '"+URLrequest+"' " + str(e)
111         return -1
112
113 def of_install(args):
114     line_number=1
115     try:
116         f = open(args.file, "r")
117         text = f.read()
118         f.close()
119         lines=text.split("\n")
120         heads=lines[0].split()
121
122         for line in lines[1:]:
123             line_number += 1
124             rule={}
125             items= line.split()
126             if len(items)==0 or items[0][0]=="#": #empty line or commented
127                 continue
128             for i in range(0,len(items)):
129                 rule[ heads[i] ] = items[i]
130             if rule["vlan_id"] == "any":
131                 del rule["vlan_id"]
132             if rule["dst_mac"] == "any":
133                 del rule["dst_mac"]
134             if 'priority' in rule and (rule['priority']==None or rule['priority']=="None" ):
135                 del rule['priority']
136             try:
137                 change_db2of(rule)
138             except FlowBadFormat as e:
139                 print "Format error at line %d:  %s" % (line_number, str(e))
140                 continue
141             r,c = ofconnector.new_flow(rule)
142             if r<0:
143                 error="ERROR: "+c
144             else:
145                 error="OK"
146             print "%s  %s  %s  input=%s  dst_mac=%s  vlan_id=%s  %s" % \
147                     (rule["switch"], str(rule.get("priority")).ljust(6), rule["name"].ljust(20), rule["ingress_port"].ljust(3), \
148                      rule.get("dst_mac","any").ljust(18), rule.get("vlan_id","any").ljust(4), error )
149         return 0
150     except IOError as e:
151         print " Error opening file '" + args.file + "': " + e.args[1]
152         return -1
153     except yaml.YAMLError as exc:
154         error_pos = ""
155         if hasattr(exc, 'problem_mark'):
156             mark = exc.problem_mark
157             error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
158         print " Error yaml/json format error at " + error_pos
159         return -1
160
161 def of_add(args):
162     if args.act==None and args.actions==None:
163         print "openflow add: error: one of the arguments --actions or [--setvlan,--stripvlan],--out is required"
164         return -1
165     elif args.act!=None and args.actions!=None:
166         print "openflow add: error: Use either --actions option or [--setvlan,--stripvlan],--out options; but not both"
167         return -1
168     
169     rule={"name":args.name, "priority":args.priority,
170           "ingress_port": args.inport
171         }
172     if args.matchvlan:
173         rule["vlan_id"] = args.matchvlan
174     if args.matchmac:
175         rule["dst_mac"] = args.matchmac
176
177     if args.actions:
178         rule["actions"] = args.actions
179         try:
180             change_db2of(rule)
181         except FlowBadFormat as e:
182             print "Format error at --actions: '%s' Expected 'vlan=<None/vlan_id>,out=<egress_port>,...'" % str(e)
183             return -1
184     elif args.act:
185         rule["actions"]=[]
186         error_msj = "openflow add: error: --setvlan,--stripvlan options must be followed by an --out option"
187         previous_option_vlan=False # indicates if the previous option was a set or strip vlan to avoid consecutive ones and to force an out options afterwards
188         for action in args.act:
189             if action==None or type(action)==int:
190                 if previous_option_vlan: #consecutive vlan options
191                     print error_msj
192                     return -1
193                 previous_option_vlan=True
194                 rule["actions"].append( ("vlan", action) )
195             else:
196                 previous_option_vlan=False
197                 rule["actions"].append( ("out", action) ) 
198         if previous_option_vlan:
199             print error_msj
200             return -1
201     #print rule
202     #return
203
204     r,c = ofconnector.new_flow(rule)
205     if r<0:
206         print c
207         return -1
208     return 0
209
210 def of_delete(args):
211     if not args.force:
212         r = raw_input("Clear rule %s (y/N)? " %(args.name))
213         if  not (len(r)>0  and r[0].lower()=="y"):
214             return 0
215     r,c = ofconnector.del_flow(args.name)
216     if r<0:
217         print c
218         return -1
219     return 0
220
221 def config(args):
222     print "OPENVIM_HOST: %s" %(vim_host)
223     print "OPENVIM_ADMIN_PORT: %s" %(vim_admin_port)
224     print "OF_CONTROLLER_TYPE: %s" %(of_controller_type)
225     if of_controller_module or (of_controller_type!="floodlight" and of_controller_type!="opendaylight"):
226         print "OF_CONTROLLER_MODULE: %s" %(of_controller_module)
227     print "OF_CONTROLLER_USER: %s" %(of_controller_user)
228     print "OF_CONTROLLER_PASSWORD: %s" %(of_controller_password)
229     #print "OF_CONTROLLER_VERSION: %s" %(of_controller_version)
230     print "OF_CONTROLLER_IP: %s" %(of_controller_ip)
231     print "OF_CONTROLLER_PORT: %s" %(of_controller_port)
232     print "OF_CONTROLLER_DPID: %s" %(of_controller_dpid)
233     return
234
235 version="0.8"
236 global vim_host
237 global vim_admin_port
238 global of_controller_type
239 global of_controller_user
240 global of_controller_password
241 global of_controller_ip
242 global of_controller_port
243 global of_controller_dpid
244 global of_controller_module
245 global ofconnector
246    
247 if __name__=="__main__":
248     #print "test_ofconnector version", version, "Jul 2015"
249     #print "(c) Copyright Telefonica"
250     
251     vim_host = os.getenv('OPENVIM_HOST',"localhost")
252     vim_admin_port = os.getenv('OPENVIM_ADMIN_PORT',"8085")
253     of_controller_type = os.getenv('OF_CONTROLLER_TYPE',"floodlight")
254     of_controller_user = os.getenv('OF_CONTROLLER_USER',None)
255     of_controller_password = os.getenv('OF_CONTROLLER_PASSWORD',None)
256     #of_controller_version = os.getenv('OF_CONTROLLER_VERSION',"0.90")
257     of_controller_ip = os.getenv('OF_CONTROLLER_IP',"localhost")
258     of_controller_port = os.getenv('OF_CONTROLLER_PORT',"7070")
259     of_controller_dpid = os.getenv('OF_CONTROLLER_DPID','00:01:02:03:e4:05:e6:07')
260     of_controller_module = os.getenv('OF_CONTROLLER_MODULE',None)
261     
262     main_parser = argparse.ArgumentParser(description='User program to interact with Openflow controller')
263     main_parser.add_argument('--version', action='version', version='%(prog)s ' + version )
264     
265     #main_parser = argparse.ArgumentParser()
266     subparsers = main_parser.add_subparsers(help='commands')
267     
268     config_parser = subparsers.add_parser('config', help="prints configuration values")
269     config_parser.set_defaults(func=config)
270     
271     add_parser = subparsers.add_parser('add', help="adds an openflow rule")
272     add_parser.add_argument('--verbose', '-v', action='count')
273     add_parser.add_argument("name", action="store", help="name of the rule")
274     add_parser.add_argument("--inport", required=True, action="store", type=str, help="match rule: ingress-port")
275     add_parser.add_argument("--actions", action="store", type=str, help="action with the format: vlan=<None/vlan-id>,out=<egress-port>,...")
276     add_parser.add_argument("--priority", action="store", type=int, help="rule priority")
277     add_parser.add_argument("--matchmac", action="store", help="match rule: mac address")
278     add_parser.add_argument("--matchvlan", action="store", type=int, help="match rule: vlan id")
279     add_parser.add_argument("--stripvlan", action="append_const", dest="act", const=None, help="alternative to --actions. Use before --out to strip vlan")
280     add_parser.add_argument("--setvlan", action="append", dest="act", type=int, help="alternative to --actions. Use before --out to set vlan")
281     add_parser.add_argument("--out", action="append", dest="act", type=str, help="alternative to --actions. out=<egress-port> can be used several times")
282     add_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
283     add_parser.set_defaults(func=of_add)
284     
285     delete_parser = subparsers.add_parser('delete', help="delete an openflow rule")
286     delete_parser.add_argument('--verbose', '-v', action='count')
287     delete_parser.add_argument("-f", "--force", action="store_true", help="force deletion without asking")
288     delete_parser.add_argument("name", action="store", help="name of the rule to be deleted")
289     delete_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
290     delete_parser.set_defaults(func=of_delete)
291     
292     switches_parser = subparsers.add_parser('switches', help="list all switches controlled by the OFC")
293     switches_parser.add_argument('--verbose', '-v', action='count')
294     switches_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
295     switches_parser.set_defaults(func=of_switches)
296
297     list_parser = subparsers.add_parser('list', help="list openflow rules")
298     list_parser.add_argument('--verbose', '-v', action='count')
299     list_parser.add_argument("--no-translate", "-n", action="store_true", help="Skip translation from openflow index to switch port name")
300     list_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
301     list_parser.set_defaults(func=of_list)
302
303     #dump_parser = subparsers.add_parser('dump', help="dump openflow rules")
304     #dump_parser.set_defaults(func=of_dump)
305     
306     clear_parser = subparsers.add_parser('clear', help="clear all openflow rules")
307     clear_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking")
308     clear_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
309     clear_parser.set_defaults(func=of_clear)
310
311     install_parser = subparsers.add_parser('install', help="install openflow rules from file")
312     install_parser.add_argument("file", action="store", help="file with rules generated using 'openflow list > rules.txt'")
313     install_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
314     install_parser.set_defaults(func=of_install)
315
316     reinstall_parser = subparsers.add_parser('reinstall', help="reinstall openflow rules from VIM rules")
317     reinstall_parser.set_defaults(func=of_reinstall)
318     reinstall_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
319
320     portlist_parser = subparsers.add_parser('port-list', help="list the physical to openflow port correspondence")
321     portlist_parser.set_defaults(func=of_port_list)
322     portlist_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
323
324     argcomplete.autocomplete(main_parser)
325     
326     args = main_parser.parse_args()
327     module_info=None
328     try:
329         if args.func is not config:
330             params={ "of_ip":   of_controller_ip,
331                         "of_port": of_controller_port, 
332                         "of_dpid": of_controller_dpid,
333                         "of_user": of_controller_user,
334                         "of_password": of_controller_password,
335                 }
336             if "debug" in args and args.debug:
337                 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
338                 logging.basicConfig(format=streamformat, level= logging.DEBUG)
339                 logger = logging.getLogger('vim')
340                 logger.setLevel(logging.DEBUG)
341                 params["of_debug"]="DEBUG"
342             else:
343                 #logger = logging.getLogger('vim').addHandler(logging.NullHandler())
344                 #logger.setLevel(logging.CRITICAL)
345                 params["of_debug"]="CRITICAL"
346             
347             if of_controller_type=='opendaylight':
348                 module = "ODL"
349             elif of_controller_module != None:
350                 module = of_controller_module
351             else:
352                 module = of_controller_type
353             module_info = imp.find_module(module)
354             
355             of_conn = imp.load_module("of_conn", *module_info)
356             try:
357                 ofconnector = of_conn.OF_conn(params)
358             except Exception as e: 
359                 print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
360                 result = -1
361                 exit()
362         result = args.func(args)
363         if result == None:
364             result = 0
365             
366         #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
367     except (IOError, ImportError) as e:
368         print "Cannot open openflow controller module '%s'; %s: %s" % (module, type(e).__name__, str(e))
369         result = -1
370     #except Exception as e:
371     #    print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
372     #    result = -1
373     except requests.exceptions.ConnectionError as e:
374         print "Cannot connect to server; %s: %s" % (type(e).__name__, str(e))
375         result = -2
376     except (KeyboardInterrupt):
377         print 'Exiting openVIM'
378         result = -3
379     except (SystemExit):
380         result = -4
381     
382     #close open file
383     if module_info and module_info[0]:
384         file.close(module_info[0])
385     exit(result)
386     
387
388