| #!/usr/bin/env python |
| # -*- coding: utf-8 -*- |
| # PYTHON_ARGCOMPLETE_OK |
| |
| ## |
| # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| # This file is part of openmano |
| # All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| # |
| # For those usages not covered by the Apache License, Version 2.0 please |
| # contact with: nfvlabs@tid.es |
| ## |
| |
| ''' |
| This program is useful to interact directly with Openflow Controllers |
| to clear rules, add and delete rules, list rules, etc. |
| ''' |
| |
| __author__="Gerardo Garcia, Alfonso Tierno, Pablo Montes" |
| __date__ ="$09-oct-2014 09:09:48$" |
| |
| #import time |
| import os |
| import sys |
| import argparse |
| import argcomplete |
| import imp |
| import yaml |
| import requests |
| import logging |
| from openflow_thread import change_db2of, FlowBadFormat |
| |
| def of_switches(args): |
| r,c = ofconnector.get_of_switches() |
| if r<0: |
| print c |
| return r |
| else: |
| for s in c: |
| print " %s %s" % (s[0], s[1]) |
| return 0 |
| |
| def of_list(args): |
| r,c = ofconnector.get_of_rules(not args.no_translate) |
| if r<0: |
| print c |
| return r |
| if args.verbose > 0: |
| print yaml.safe_dump(c, indent=4, default_flow_style=False) |
| return 0 |
| |
| print " switch priority name ingress_port dst_mac vlan_id actions" |
| for name,rule in c.iteritems(): |
| action_list=[] |
| for action in rule["actions"]: |
| action_list.append(action[0]+"="+str(action[1])) |
| if "vlan_id" in rule: |
| vlan=str(rule["vlan_id"]) |
| else: |
| vlan="any" |
| print "%s %s %s %s %s %s %s" % \ |
| (rule["switch"], str(rule["priority"]).ljust(6), name.ljust(40), rule["ingress_port"].ljust(8), \ |
| rule.get("dst_mac","any").ljust(18), vlan.ljust(4), ",".join(action_list) ) |
| return 0 |
| |
| def of_clear(args): |
| if not args.force: |
| r = raw_input("Clear all Openflow rules (y/N)? ") |
| if not (len(r)>0 and r[0].lower()=="y"): |
| return 0 |
| r,c = ofconnector.clear_all_flows() |
| if r<0: |
| print c |
| return r |
| return 0 |
| |
| def of_port_list(args): |
| r,c = ofconnector.obtain_port_correspondence() |
| if r<0: |
| print c |
| return r |
| yaml.safe_dump({"ports": c}, sys.stdout, indent=2, default_flow_style=False) |
| |
| #def of_dump(args): |
| # args.verbose = 3 |
| # args.no_translate=False |
| # of_list(args) |
| return 0 |
| |
| def of_reinstall(args): |
| try: |
| URLrequest = "http://%s:%s/openvim/networks/all/openflow" %(vim_host, vim_admin_port) |
| print URLrequest |
| openvim_response = requests.put(URLrequest) |
| print openvim_response.text |
| return 0 |
| except requests.exceptions.RequestException as e: |
| print " Exception GET at '"+URLrequest+"' " + str(e) |
| return -1 |
| |
| def of_install(args): |
| line_number=1 |
| try: |
| f = open(args.file, "r") |
| text = f.read() |
| f.close() |
| lines=text.split("\n") |
| heads=lines[0].split() |
| |
| for line in lines[1:]: |
| line_number += 1 |
| rule={} |
| items= line.split() |
| if len(items)==0 or items[0][0]=="#": #empty line or commented |
| continue |
| for i in range(0,len(items)): |
| rule[ heads[i] ] = items[i] |
| if rule["vlan_id"] == "any": |
| del rule["vlan_id"] |
| if rule["dst_mac"] == "any": |
| del rule["dst_mac"] |
| if 'priority' in rule and (rule['priority']==None or rule['priority']=="None" ): |
| del rule['priority'] |
| try: |
| change_db2of(rule) |
| except FlowBadFormat as e: |
| print "Format error at line %d: %s" % (line_number, str(e)) |
| continue |
| r,c = ofconnector.new_flow(rule) |
| if r<0: |
| error="ERROR: "+c |
| else: |
| error="OK" |
| print "%s %s %s input=%s dst_mac=%s vlan_id=%s %s" % \ |
| (rule["switch"], str(rule.get("priority")).ljust(6), rule["name"].ljust(20), rule["ingress_port"].ljust(3), \ |
| rule.get("dst_mac","any").ljust(18), rule.get("vlan_id","any").ljust(4), error ) |
| return 0 |
| except IOError as e: |
| print " Error opening file '" + args.file + "': " + e.args[1] |
| return -1 |
| except yaml.YAMLError as exc: |
| error_pos = "" |
| if hasattr(exc, 'problem_mark'): |
| mark = exc.problem_mark |
| error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1) |
| print " Error yaml/json format error at " + error_pos |
| return -1 |
| |
| def of_add(args): |
| if args.act==None and args.actions==None: |
| print "openflow add: error: one of the arguments --actions or [--setvlan,--stripvlan],--out is required" |
| return -1 |
| elif args.act!=None and args.actions!=None: |
| print "openflow add: error: Use either --actions option or [--setvlan,--stripvlan],--out options; but not both" |
| return -1 |
| |
| rule={"name":args.name, "priority":args.priority, |
| "ingress_port": args.inport |
| } |
| if args.matchvlan: |
| rule["vlan_id"] = args.matchvlan |
| if args.matchmac: |
| rule["dst_mac"] = args.matchmac |
| |
| if args.actions: |
| rule["actions"] = args.actions |
| try: |
| change_db2of(rule) |
| except FlowBadFormat as e: |
| print "Format error at --actions: '%s' Expected 'vlan=<None/vlan_id>,out=<egress_port>,...'" % str(e) |
| return -1 |
| elif args.act: |
| rule["actions"]=[] |
| error_msj = "openflow add: error: --setvlan,--stripvlan options must be followed by an --out option" |
| 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 |
| for action in args.act: |
| if action==None or type(action)==int: |
| if previous_option_vlan: #consecutive vlan options |
| print error_msj |
| return -1 |
| previous_option_vlan=True |
| rule["actions"].append( ("vlan", action) ) |
| else: |
| previous_option_vlan=False |
| rule["actions"].append( ("out", action) ) |
| if previous_option_vlan: |
| print error_msj |
| return -1 |
| #print rule |
| #return |
| |
| r,c = ofconnector.new_flow(rule) |
| if r<0: |
| print c |
| return -1 |
| if args.print_id: |
| print rule["name"] |
| return 0 |
| |
| def of_delete(args): |
| if not args.force: |
| r = raw_input("Clear rule %s (y/N)? " %(args.name)) |
| if not (len(r)>0 and r[0].lower()=="y"): |
| return 0 |
| r,c = ofconnector.del_flow(args.name) |
| if r<0: |
| print c |
| return -1 |
| return 0 |
| |
| def config(args): |
| print "OPENVIM_HOST: %s" %(vim_host) |
| print "OPENVIM_ADMIN_PORT: %s" %(vim_admin_port) |
| print "OF_CONTROLLER_TYPE: %s" %(of_controller_type) |
| if of_controller_module or (of_controller_type!="floodlight" and of_controller_type!="opendaylight"): |
| print "OF_CONTROLLER_MODULE: %s" %(of_controller_module) |
| print "OF_CONTROLLER_USER: %s" %(of_controller_user) |
| print "OF_CONTROLLER_PASSWORD: %s" %(of_controller_password) |
| #print "OF_CONTROLLER_VERSION: %s" %(of_controller_version) |
| print "OF_CONTROLLER_IP: %s" %(of_controller_ip) |
| print "OF_CONTROLLER_PORT: %s" %(of_controller_port) |
| print "OF_CONTROLLER_DPID: %s" %(of_controller_dpid) |
| return |
| |
| version="0.8" |
| global vim_host |
| global vim_admin_port |
| global of_controller_type |
| global of_controller_user |
| global of_controller_password |
| global of_controller_ip |
| global of_controller_port |
| global of_controller_dpid |
| global of_controller_module |
| global ofconnector |
| |
| if __name__=="__main__": |
| #print "test_ofconnector version", version, "Jul 2015" |
| #print "(c) Copyright Telefonica" |
| |
| vim_host = os.getenv('OPENVIM_HOST',"localhost") |
| vim_admin_port = os.getenv('OPENVIM_ADMIN_PORT',"8085") |
| of_controller_type = os.getenv('OF_CONTROLLER_TYPE',"floodlight") |
| of_controller_user = os.getenv('OF_CONTROLLER_USER',None) |
| of_controller_password = os.getenv('OF_CONTROLLER_PASSWORD',None) |
| #of_controller_version = os.getenv('OF_CONTROLLER_VERSION',"0.90") |
| of_controller_ip = os.getenv('OF_CONTROLLER_IP',"localhost") |
| of_controller_port = os.getenv('OF_CONTROLLER_PORT',"7070") |
| of_controller_dpid = os.getenv('OF_CONTROLLER_DPID','00:01:02:03:e4:05:e6:07') |
| of_controller_module = os.getenv('OF_CONTROLLER_MODULE',None) |
| |
| main_parser = argparse.ArgumentParser(description='User program to interact with Openflow controller') |
| main_parser.add_argument('--version', action='version', version='%(prog)s ' + version ) |
| |
| #main_parser = argparse.ArgumentParser() |
| subparsers = main_parser.add_subparsers(help='commands') |
| |
| config_parser = subparsers.add_parser('config', help="prints configuration values") |
| config_parser.set_defaults(func=config) |
| |
| add_parser = subparsers.add_parser('add', help="adds an openflow rule") |
| add_parser.add_argument('--verbose', '-v', action='count') |
| add_parser.add_argument("name", action="store", help="name of the rule") |
| add_parser.add_argument("--inport", required=True, action="store", type=str, help="match rule: ingress-port") |
| add_parser.add_argument("--actions", action="store", type=str, help="action with the format: vlan=<None/vlan-id>,out=<egress-port>,...") |
| add_parser.add_argument("--priority", action="store", type=int, help="rule priority") |
| add_parser.add_argument("--matchmac", action="store", help="match rule: mac address") |
| add_parser.add_argument("--matchvlan", action="store", type=int, help="match rule: vlan id") |
| add_parser.add_argument("--stripvlan", action="append_const", dest="act", const=None, help="alternative to --actions. Use before --out to strip vlan") |
| add_parser.add_argument("--setvlan", action="append", dest="act", type=int, help="alternative to --actions. Use before --out to set vlan") |
| add_parser.add_argument("--out", action="append", dest="act", type=str, help="alternative to --actions. out=<egress-port> can be used several times") |
| add_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| add_parser.add_argument('--print-id', action='store_true', help="print the flow id after added") |
| add_parser.set_defaults(func=of_add) |
| |
| delete_parser = subparsers.add_parser('delete', help="delete an openflow rule") |
| delete_parser.add_argument('--verbose', '-v', action='count') |
| delete_parser.add_argument("-f", "--force", action="store_true", help="force deletion without asking") |
| delete_parser.add_argument("name", action="store", help="name of the rule to be deleted") |
| delete_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| delete_parser.set_defaults(func=of_delete) |
| |
| switches_parser = subparsers.add_parser('switches', help="list all switches controlled by the OFC") |
| switches_parser.add_argument('--verbose', '-v', action='count') |
| switches_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| switches_parser.set_defaults(func=of_switches) |
| |
| list_parser = subparsers.add_parser('list', help="list openflow rules") |
| list_parser.add_argument('--verbose', '-v', action='count') |
| list_parser.add_argument("--no-translate", "-n", action="store_true", help="Skip translation from openflow index to switch port name") |
| list_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| list_parser.set_defaults(func=of_list) |
| |
| #dump_parser = subparsers.add_parser('dump', help="dump openflow rules") |
| #dump_parser.set_defaults(func=of_dump) |
| |
| clear_parser = subparsers.add_parser('clear', help="clear all openflow rules") |
| clear_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking") |
| clear_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| clear_parser.set_defaults(func=of_clear) |
| |
| install_parser = subparsers.add_parser('install', help="install openflow rules from file") |
| install_parser.add_argument("file", action="store", help="file with rules generated using 'openflow list > rules.txt'") |
| install_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| install_parser.set_defaults(func=of_install) |
| |
| reinstall_parser = subparsers.add_parser('reinstall', help="reinstall openflow rules from VIM rules") |
| reinstall_parser.set_defaults(func=of_reinstall) |
| reinstall_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| |
| portlist_parser = subparsers.add_parser('port-list', help="list the physical to openflow port correspondence") |
| portlist_parser.set_defaults(func=of_port_list) |
| portlist_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") |
| |
| argcomplete.autocomplete(main_parser) |
| |
| args = main_parser.parse_args() |
| module_info=None |
| try: |
| if args.func is not config: |
| params={ "of_ip": of_controller_ip, |
| "of_port": of_controller_port, |
| "of_dpid": of_controller_dpid, |
| "of_user": of_controller_user, |
| "of_password": of_controller_password, |
| } |
| if "debug" in args and args.debug: |
| streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s" |
| logging.basicConfig(format=streamformat, level= logging.DEBUG) |
| logger = logging.getLogger('vim') |
| logger.setLevel(logging.DEBUG) |
| params["of_debug"]="DEBUG" |
| else: |
| #logger = logging.getLogger('vim').addHandler(logging.NullHandler()) |
| #logger.setLevel(logging.CRITICAL) |
| params["of_debug"]="CRITICAL" |
| |
| if of_controller_type=='opendaylight': |
| module = "ODL" |
| elif of_controller_module != None: |
| module = of_controller_module |
| else: |
| module = of_controller_type |
| module_info = imp.find_module(module) |
| |
| of_conn = imp.load_module("of_conn", *module_info) |
| try: |
| ofconnector = of_conn.OF_conn(params) |
| except Exception as e: |
| print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e)) |
| result = -1 |
| exit() |
| result = args.func(args) |
| if result == None: |
| result = 0 |
| |
| #for some reason it fails if call exit inside try instance. Need to call exit at the end !? |
| except (IOError, ImportError) as e: |
| print "Cannot open openflow controller module '%s'; %s: %s" % (module, type(e).__name__, str(e)) |
| result = -1 |
| #except Exception as e: |
| # print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e)) |
| # result = -1 |
| except requests.exceptions.ConnectionError as e: |
| print "Cannot connect to server; %s: %s" % (type(e).__name__, str(e)) |
| result = -2 |
| except (KeyboardInterrupt): |
| print 'Exiting openVIM' |
| result = -3 |
| except (SystemExit): |
| result = -4 |
| |
| #close open file |
| if module_info and module_info[0]: |
| file.close(module_info[0]) |
| exit(result) |
| |
| |
| |