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