Merge branch 'v1.1'
[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     if args.print_id:
209         print rule["name"]
210     return 0
211
212 def of_delete(args):
213     if not args.force:
214         r = raw_input("Clear rule %s (y/N)? " %(args.name))
215         if  not (len(r)>0  and r[0].lower()=="y"):
216             return 0
217     r,c = ofconnector.del_flow(args.name)
218     if r<0:
219         print c
220         return -1
221     return 0
222
223 def config(args):
224     print "OPENVIM_HOST: %s" %(vim_host)
225     print "OPENVIM_ADMIN_PORT: %s" %(vim_admin_port)
226     print "OF_CONTROLLER_TYPE: %s" %(of_controller_type)
227     if of_controller_module or (of_controller_type!="floodlight" and of_controller_type!="opendaylight"):
228         print "OF_CONTROLLER_MODULE: %s" %(of_controller_module)
229     print "OF_CONTROLLER_USER: %s" %(of_controller_user)
230     print "OF_CONTROLLER_PASSWORD: %s" %(of_controller_password)
231     #print "OF_CONTROLLER_VERSION: %s" %(of_controller_version)
232     print "OF_CONTROLLER_IP: %s" %(of_controller_ip)
233     print "OF_CONTROLLER_PORT: %s" %(of_controller_port)
234     print "OF_CONTROLLER_DPID: %s" %(of_controller_dpid)
235     return
236
237 version="0.8"
238 global vim_host
239 global vim_admin_port
240 global of_controller_type
241 global of_controller_user
242 global of_controller_password
243 global of_controller_ip
244 global of_controller_port
245 global of_controller_dpid
246 global of_controller_module
247 global ofconnector
248    
249 if __name__=="__main__":
250     #print "test_ofconnector version", version, "Jul 2015"
251     #print "(c) Copyright Telefonica"
252     
253     vim_host = os.getenv('OPENVIM_HOST',"localhost")
254     vim_admin_port = os.getenv('OPENVIM_ADMIN_PORT',"8085")
255     of_controller_type = os.getenv('OF_CONTROLLER_TYPE',"floodlight")
256     of_controller_user = os.getenv('OF_CONTROLLER_USER',None)
257     of_controller_password = os.getenv('OF_CONTROLLER_PASSWORD',None)
258     #of_controller_version = os.getenv('OF_CONTROLLER_VERSION',"0.90")
259     of_controller_ip = os.getenv('OF_CONTROLLER_IP',"localhost")
260     of_controller_port = os.getenv('OF_CONTROLLER_PORT',"7070")
261     of_controller_dpid = os.getenv('OF_CONTROLLER_DPID','00:01:02:03:e4:05:e6:07')
262     of_controller_module = os.getenv('OF_CONTROLLER_MODULE',None)
263     
264     main_parser = argparse.ArgumentParser(description='User program to interact with Openflow controller')
265     main_parser.add_argument('--version', action='version', version='%(prog)s ' + version )
266     
267     #main_parser = argparse.ArgumentParser()
268     subparsers = main_parser.add_subparsers(help='commands')
269     
270     config_parser = subparsers.add_parser('config', help="prints configuration values")
271     config_parser.set_defaults(func=config)
272     
273     add_parser = subparsers.add_parser('add', help="adds an openflow rule")
274     add_parser.add_argument('--verbose', '-v', action='count')
275     add_parser.add_argument("name", action="store", help="name of the rule")
276     add_parser.add_argument("--inport", required=True, action="store", type=str, help="match rule: ingress-port")
277     add_parser.add_argument("--actions", action="store", type=str, help="action with the format: vlan=<None/vlan-id>,out=<egress-port>,...")
278     add_parser.add_argument("--priority", action="store", type=int, help="rule priority")
279     add_parser.add_argument("--matchmac", action="store", help="match rule: mac address")
280     add_parser.add_argument("--matchvlan", action="store", type=int, help="match rule: vlan id")
281     add_parser.add_argument("--stripvlan", action="append_const", dest="act", const=None, help="alternative to --actions. Use before --out to strip vlan")
282     add_parser.add_argument("--setvlan", action="append", dest="act", type=int, help="alternative to --actions. Use before --out to set vlan")
283     add_parser.add_argument("--out", action="append", dest="act", type=str, help="alternative to --actions. out=<egress-port> can be used several times")
284     add_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
285     add_parser.add_argument('--print-id', action='store_true', help="print the flow id after added")
286     add_parser.set_defaults(func=of_add)
287     
288     delete_parser = subparsers.add_parser('delete', help="delete an openflow rule")
289     delete_parser.add_argument('--verbose', '-v', action='count')
290     delete_parser.add_argument("-f", "--force", action="store_true", help="force deletion without asking")
291     delete_parser.add_argument("name", action="store", help="name of the rule to be deleted")
292     delete_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
293     delete_parser.set_defaults(func=of_delete)
294     
295     switches_parser = subparsers.add_parser('switches', help="list all switches controlled by the OFC")
296     switches_parser.add_argument('--verbose', '-v', action='count')
297     switches_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
298     switches_parser.set_defaults(func=of_switches)
299
300     list_parser = subparsers.add_parser('list', help="list openflow rules")
301     list_parser.add_argument('--verbose', '-v', action='count')
302     list_parser.add_argument("--no-translate", "-n", action="store_true", help="Skip translation from openflow index to switch port name")
303     list_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
304     list_parser.set_defaults(func=of_list)
305
306     #dump_parser = subparsers.add_parser('dump', help="dump openflow rules")
307     #dump_parser.set_defaults(func=of_dump)
308     
309     clear_parser = subparsers.add_parser('clear', help="clear all openflow rules")
310     clear_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking")
311     clear_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
312     clear_parser.set_defaults(func=of_clear)
313
314     install_parser = subparsers.add_parser('install', help="install openflow rules from file")
315     install_parser.add_argument("file", action="store", help="file with rules generated using 'openflow list > rules.txt'")
316     install_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
317     install_parser.set_defaults(func=of_install)
318
319     reinstall_parser = subparsers.add_parser('reinstall', help="reinstall openflow rules from VIM rules")
320     reinstall_parser.set_defaults(func=of_reinstall)
321     reinstall_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
322
323     portlist_parser = subparsers.add_parser('port-list', help="list the physical to openflow port correspondence")
324     portlist_parser.set_defaults(func=of_port_list)
325     portlist_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
326
327     argcomplete.autocomplete(main_parser)
328     
329     args = main_parser.parse_args()
330     module_info=None
331     try:
332         if args.func is not config:
333             params={ "of_ip":   of_controller_ip,
334                         "of_port": of_controller_port, 
335                         "of_dpid": of_controller_dpid,
336                         "of_user": of_controller_user,
337                         "of_password": of_controller_password,
338                 }
339             if "debug" in args and args.debug:
340                 streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
341                 logging.basicConfig(format=streamformat, level= logging.DEBUG)
342                 logger = logging.getLogger('vim')
343                 logger.setLevel(logging.DEBUG)
344                 params["of_debug"]="DEBUG"
345             else:
346                 #logger = logging.getLogger('vim').addHandler(logging.NullHandler())
347                 #logger.setLevel(logging.CRITICAL)
348                 params["of_debug"]="CRITICAL"
349             
350             if of_controller_type=='opendaylight':
351                 module = "ODL"
352             elif of_controller_module != None:
353                 module = of_controller_module
354             else:
355                 module = of_controller_type
356             module_info = imp.find_module(module)
357             
358             of_conn = imp.load_module("of_conn", *module_info)
359             try:
360                 ofconnector = of_conn.OF_conn(params)
361             except Exception as e: 
362                 print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
363                 result = -1
364                 exit()
365         result = args.func(args)
366         if result == None:
367             result = 0
368             
369         #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
370     except (IOError, ImportError) as e:
371         print "Cannot open openflow controller module '%s'; %s: %s" % (module, type(e).__name__, str(e))
372         result = -1
373     #except Exception as e:
374     #    print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
375     #    result = -1
376     except requests.exceptions.ConnectionError as e:
377         print "Cannot connect to server; %s: %s" % (type(e).__name__, str(e))
378         result = -2
379     except (KeyboardInterrupt):
380         print 'Exiting openVIM'
381         result = -3
382     except (SystemExit):
383         result = -4
384     
385     #close open file
386     if module_info and module_info[0]:
387         file.close(module_info[0])
388     exit(result)
389     
390
391