# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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. # Common function for Cloudstack's XenAPI plugins import ConfigParser import logging import os import subprocess import simplejson as json import copy from time import localtime, asctime DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" DEFAULT_LOG_FILE = "/var/log/cloudstack_plugins.log" PLUGIN_CONFIG_PATH = "/etc/xensource/cloudstack_plugins.conf" OVSDB_PID_PATH = "/var/run/openvswitch/ovsdb-server.pid" OVSDB_DAEMON_PATH = "ovsdb-server" OVS_PID_PATH = "/var/run/openvswitch/ovs-vswitchd.pid" OVS_DAEMON_PATH = "ovs-vswitchd" VSCTL_PATH = "/usr/bin/ovs-vsctl" OFCTL_PATH = "/usr/bin/ovs-ofctl" XE_PATH = "/opt/xensource/bin/xe" # OpenFlow tables set in a pipeline processing fashion for the bridge created for a VPC's that are enabled for # distributed routing. # L2 path (intra-tier traffic) CLASSIFIER-> L2 lookup -> L2 flooding tables # L3 path (inter-tier traffic) CLASSIFIER-> EGRESS ACL -> L3 lookup -> INGRESS ACL-> L2 lookup -> L2 flooding tables # Classifier table has the rules to separate broadcast/multi-cast traffic, inter-tier traffic, intra-tier traffic CLASSIFIER_TABLE=0 # Lookup table to determine the output port (vif/tunnel port) based on the MAC address L2_LOOKUP_TABLE=1 # flooding table has the rules to flood on ports (both VIF, tunnel ports) except on the port on which packet arrived L2_FLOOD_TABLE=2 # table has flow rules derived from egress ACL's EGRESS_ACL_TABLE=3 # Lookup table to determine the output port (vif/tunnel port) based on the IP address L3_LOOKUP_TABLE=4 # table has flow rules derived from egress ACL's INGRESS_ACL_TABLE=5 class PluginError(Exception): """Base Exception class for all plugin errors.""" def __init__(self, *args): Exception.__init__(self, *args) def setup_logging(log_file=None): debug = False verbose = False log_format = DEFAULT_LOG_FORMAT log_date_format = DEFAULT_LOG_DATE_FORMAT # try to read plugin configuration file if os.path.exists(PLUGIN_CONFIG_PATH): config = ConfigParser.ConfigParser() config.read(PLUGIN_CONFIG_PATH) try: options = config.options('LOGGING') if 'debug' in options: debug = config.getboolean('LOGGING', 'debug') if 'verbose' in options: verbose = config.getboolean('LOGGING', 'verbose') if 'format' in options: log_format = config.get('LOGGING', 'format') if 'date_format' in options: log_date_format = config.get('LOGGING', 'date_format') if 'file' in options: log_file_2 = config.get('LOGGING', 'file') except ValueError: # configuration file contained invalid attributes # ignore them pass except ConfigParser.NoSectionError: # Missing 'Logging' section in configuration file pass root_logger = logging.root if debug: root_logger.setLevel(logging.DEBUG) elif verbose: root_logger.setLevel(logging.INFO) else: root_logger.setLevel(logging.WARNING) formatter = logging.Formatter(log_format, log_date_format) log_filename = log_file or log_file_2 or DEFAULT_LOG_FILE logfile_handler = logging.FileHandler(log_filename) logfile_handler.setFormatter(formatter) root_logger.addHandler(logfile_handler) def do_cmd(cmd): """Abstracts out the basics of issuing system commands. If the command returns anything in stderr, a PluginError is raised with that information. Otherwise, the output from stdout is returned. """ pipe = subprocess.PIPE logging.debug("Executing:%s", cmd) proc = subprocess.Popen(cmd, shell=False, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) ret_code = proc.wait() err = proc.stderr.read() if ret_code: logging.debug("The command exited with the error code: " + "%s (stderr output:%s)" % (ret_code, err)) raise PluginError(err) output = proc.stdout.read() if output.endswith('\n'): output = output[:-1] return output def _is_process_run(pidFile, name): try: fpid = open(pidFile, "r") pid = fpid.readline() fpid.close() except IOError, e: return -1 pid = pid[:-1] ps = os.popen("ps -ae") for l in ps: if pid in l and name in l: ps.close() return 0 ps.close() return -2 def _is_tool_exist(name): if os.path.exists(name): return 0 return -1 def check_switch(): global result ret = _is_process_run(OVSDB_PID_PATH, OVSDB_DAEMON_PATH) if ret < 0: if ret == -1: return "NO_DB_PID_FILE" if ret == -2: return "DB_NOT_RUN" ret = _is_process_run(OVS_PID_PATH, OVS_DAEMON_PATH) if ret < 0: if ret == -1: return "NO_SWITCH_PID_FILE" if ret == -2: return "SWITCH_NOT_RUN" if _is_tool_exist(VSCTL_PATH) < 0: return "NO_VSCTL" if _is_tool_exist(OFCTL_PATH) < 0: return "NO_OFCTL" return "SUCCESS" def _build_flow_expr(**kwargs): is_delete_expr = kwargs.get('delete', False) flow = "" if not is_delete_expr: flow = "hard_timeout=%s,idle_timeout=%s,priority=%s"\ % (kwargs.get('hard_timeout', '0'), kwargs.get('idle_timeout', '0'), kwargs.get('priority', '1')) in_port = 'in_port' in kwargs and ",in_port=%s" % kwargs['in_port'] or '' dl_type = 'dl_type' in kwargs and ",dl_type=%s" % kwargs['dl_type'] or '' dl_src = 'dl_src' in kwargs and ",dl_src=%s" % kwargs['dl_src'] or '' dl_dst = 'dl_dst' in kwargs and ",dl_dst=%s" % kwargs['dl_dst'] or '' nw_src = 'nw_src' in kwargs and ",nw_src=%s" % kwargs['nw_src'] or '' nw_dst = 'nw_dst' in kwargs and ",nw_dst=%s" % kwargs['nw_dst'] or '' table = 'table' in kwargs and ",table=%s" % kwargs['table'] or '' cookie = 'cookie' in kwargs and ",cookie=%s" % kwargs['cookie'] or '' proto = 'proto' in kwargs and ",%s" % kwargs['proto'] or '' ip = ('nw_src' in kwargs or 'nw_dst' in kwargs) and ',ip' or '' flow = (flow + cookie+ in_port + dl_type + dl_src + dl_dst + (ip or proto) + nw_src + nw_dst + table) return flow def add_flow(bridge, **kwargs): """ Builds a flow expression for **kwargs and adds the flow entry to an Open vSwitch instance """ flow = _build_flow_expr(**kwargs) actions = 'actions' in kwargs and ",actions=%s" % kwargs['actions'] or '' flow = flow + actions addflow = [OFCTL_PATH, "add-flow", bridge, flow] do_cmd(addflow) def del_flows(bridge, **kwargs): """ Removes flows according to criteria passed as keyword. """ flow = _build_flow_expr(delete=True, **kwargs) # out_port condition does not exist for all flow commands out_port = ("out_port" in kwargs and ",out_port=%s" % kwargs['out_port'] or '') flow = flow + out_port delFlow = [OFCTL_PATH, 'del-flows', bridge, flow] do_cmd(delFlow) def del_all_flows(bridge): delFlow = [OFCTL_PATH, "del-flows", bridge] do_cmd(delFlow) normalFlow = "priority=0 idle_timeout=0 hard_timeout=0 actions=normal" add_flow(bridge, normalFlow) def del_port(bridge, port): delPort = [VSCTL_PATH, "del-port", bridge, port] do_cmd(delPort) def get_network_id_for_vif(vif_name): domain_id, device_id = vif_name[3:len(vif_name)].split(".") dom_uuid = do_cmd([XE_PATH, "vm-list", "dom-id=%s" % domain_id, "--minimal"]) vif_uuid = do_cmd([XE_PATH, "vif-list", "vm-uuid=%s" % dom_uuid, "device=%s" % device_id, "--minimal"]) vnet = do_cmd([XE_PATH, "vif-param-get", "uuid=%s" % vif_uuid, "param-name=other-config", "param-key=cloudstack-network-id"]) return vnet def get_network_id_for_tunnel_port(tunnelif_name): vnet = do_cmd([VSCTL_PATH, "get", "interface", tunnelif_name, "options:cloudstack-network-id"]) return vnet def clear_flooding_rules_for_port(bridge, ofport): del_flows(bridge, in_port=ofport, table=L2_FLOOD_TABLE) def clear_flooding_rules_for_all_ports(bridge): del_flows(bridge, cookie=111, table=L2_FLOOD_TABLE) def add_flooding_rules_for_port(bridge, in_ofport, out_ofports): action = "".join("output:%s," %ofport for ofport in out_ofports)[:-1] add_flow(bridge, cookie=111, priority=1100, in_port=in_ofport, table=L2_FLOOD_TABLE, actions=action) def get_ofport_for_vif(vif_name): return do_cmd([VSCTL_PATH, "get", "interface", vif_name, "ofport"]) def get_macaddress_of_vif(vif_name): domain_id, device_id = vif_name[3:len(vif_name)].split(".") dom_uuid = do_cmd([XE_PATH, "vm-list", "dom-id=%s" % domain_id, "--minimal"]) vif_uuid = do_cmd([XE_PATH, "vif-list", "vm-uuid=%s" % dom_uuid, "device=%s" % device_id, "--minimal"]) mac = do_cmd([XE_PATH, "vif-param-get", "uuid=%s" % vif_uuid, "param-name=MAC"]) return mac def get_vif_name_from_macaddress(macaddress): vif_uuid = do_cmd([XE_PATH, "vif-list", "MAC=%s" % macaddress, "--minimal"]) vif_device_id = do_cmd([XE_PATH, "vif-param-get", "uuid=%s" % vif_uuid, "param-name=device"]) vm_uuid = do_cmd([XE_PATH, "vif-param-get", "uuid=%s" % vif_uuid, "param-name=vm-uuid"]) vm_domain_id = do_cmd([XE_PATH, "vm-param-get", "uuid=%s" % vm_uuid, "param-name=dom-id"]) return "vif"+vm_domain_id+"."+vif_device_id def add_mac_lookup_table_entry(bridge, mac_address, out_of_port): action = "output=%s" %out_of_port add_flow(bridge, priority=1100, dl_dst=mac_address, table=L2_LOOKUP_TABLE, actions=action) def delete_mac_lookup_table_entry(bridge, mac_address): del_flows(bridge, dl_dst=mac_address, table=L2_LOOKUP_TABLE) def add_ip_lookup_table_entry(bridge, ip, dst_tier_gateway_mac, dst_vm_mac): action_str = "mod_dl_src:%s" % dst_tier_gateway_mac + ",mod_dl_dst:%s" % dst_vm_mac + ",resubmit(,%s)"%INGRESS_ACL_TABLE action_str = "table=%s"%L3_LOOKUP_TABLE + ", ip, nw_dst=%s" % ip + ", actions=%s" %action_str addflow = [OFCTL_PATH, "add-flow", bridge, action_str] do_cmd(addflow) def get_vpc_vms_on_host(vpc, host_id): all_vms = vpc.vms vms_on_host = [] for vm in all_vms: if str(vm.hostid) == (host_id): vms_on_host.append(vm) return vms_on_host def get_network_details(vpc, network_uuid): tiers = vpc.tiers for tier in tiers: if str(tier.networkuuid) == (network_uuid): return tier return None class jsonLoader(object): def __init__(self, obj): for k in obj: v = obj[k] if isinstance(v, dict): setattr(self, k, jsonLoader(v)) elif isinstance(v, (list, tuple)): if len(v) > 0 and isinstance(v[0], dict): setattr(self, k, [jsonLoader(elem) for elem in v]) else: setattr(self, k, v) else: setattr(self, k, v) def __getattr__(self, val): if val in self.__dict__: return self.__dict__[val] else: return None def __repr__(self): return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict__.iteritems())) def __str__(self): return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict__.iteritems())) def get_acl(vpcconfig, required_acl_id): acls = vpcconfig.acls for acl in acls: if acl.id == required_acl_id: return acl return None # Configures the bridge created for a VPC enabled for distributed routing. Management server sends VPC physical topology # details. Based on the VPC physical topology L2 lookup table and L3 lookup tables are updated by this function. def configure_bridge_for_network_topology(bridge, this_host_id, json_config, sequence_no): vpconfig = jsonLoader(json.loads(json_config)).vpc if vpconfig is None: logging.debug("WARNING:Can't find VPC topology information in the json configuration file") return "FAILURE:IMPROPER_JSON_CONFG_FILE" try: if not os.path.exists('/var/run/cloud'): os.makedirs('/var/run/cloud') # create a temporary file to store OpenFlow rules corresponding to L2 and L3 lookup table updates ofspec_filename = "/var/run/cloud/" + bridge + sequence_no + ".ofspec" ofspec = open(ofspec_filename, 'w') # get the list of VM's in all the tiers of VPC running in this host from the JSON config this_host_vms = get_vpc_vms_on_host(vpconfig, this_host_id) for vm in this_host_vms: for nic in vm.nics: mac_addr = nic.macaddress ip = nic.ipaddress vif_name = get_vif_name_from_macaddress(mac_addr) of_port = get_ofport_for_vif(vif_name) network = get_network_details(vpconfig, nic.networkuuid) # Add OF rule in L2 look up table, if packet's destination mac matches MAC of the VM's nic # then send packet on the found OFPORT ofspec.write(" table=%s" %L2_LOOKUP_TABLE + " priority=1100 dl_dst=%s " %mac_addr + " actions=output:%s" %of_port + "\n") # Add OF rule in L3 look up table: if packet's destination IP matches VM's IP then modify the packet # to set DST MAC = VM's MAC, SRC MAC= destination tier gateway MAC and send to egress table. This step # emulates steps VPC virtual router would have done on the current host itself. action_str = " mod_dl_src:%s"%network.gatewaymac + ",mod_dl_dst:%s" % mac_addr \ + ",resubmit(,%s)"%INGRESS_ACL_TABLE action_str = " table=%s"%L3_LOOKUP_TABLE + " ip nw_dst=%s"%ip + " actions=%s" %action_str ofspec.write(action_str + "\n") # Add OF rule to send intra-tier traffic from this nic of the VM to L2 lookup path (L2 switching) action_str = " table=%s" %CLASSIFIER_TABLE + " priority=1200 in_port=%s " %of_port + \ " ip nw_dst=%s " %network.cidr + " actions=resubmit(,%s)" %L2_LOOKUP_TABLE ofspec.write(action_str + "\n") # Add OF rule to send inter-tier traffic from this nic of the VM to egress ACL table(L3 lookup path) action_str = " table=%s "%CLASSIFIER_TABLE + " priority=1100 in_port=%s " %of_port +\ " ip dl_dst=%s " %network.gatewaymac + " nw_dst=%s " %vpconfig.cidr + \ " actions=resubmit(,%s)" %EGRESS_ACL_TABLE ofspec.write(action_str + "\n") # get the list of hosts on which VPC spans from the JSON config vpc_spanning_hosts = vpconfig.hosts for host in vpc_spanning_hosts: if str(this_host_id) == str(host.hostid): continue other_host_vms = get_vpc_vms_on_host(vpconfig, str(host.hostid)) for vm in other_host_vms: for nic in vm.nics: mac_addr = nic.macaddress ip = nic.ipaddress network = get_network_details(vpconfig, nic.networkuuid) gre_key = network.grekey # generate tunnel name as per the tunnel naming convention and get the OF port tunnel_name = "t%s-%s-%s" % (gre_key, this_host_id, host.hostid) of_port = get_ofport_for_vif(tunnel_name) # Add flow rule in L2 look up table, if packet's destination mac matches MAC of the VM's nic # on the remote host then send packet on the found OFPORT corresponding to the tunnel ofspec.write("table=%s" %L2_LOOKUP_TABLE + " priority=1100 dl_dst=%s " %mac_addr + " actions=output:%s" %of_port + "\n") # Add flow rule in L3 look up table. if packet's destination IP matches VM's IP then modify the # packet to set DST MAC = VM's MAC, SRC MAC=tier gateway MAC and send to ingress table. This step # emulates steps VPC virtual router would have done on the current host itself. action_str = "mod_dl_src:%s"%network.gatewaymac + ",mod_dl_dst:%s" % mac_addr + \ ",resubmit(,%s)"%INGRESS_ACL_TABLE action_str = "table=%s"%L3_LOOKUP_TABLE + " ip nw_dst=%s"%ip + " actions=%s" %action_str ofspec.write(action_str + "\n") action_str = "table=%s "%L2_LOOKUP_TABLE + " priority=0 " + " actions=resubmit(,%s)"%L2_FLOOD_TABLE ofspec.write(action_str + "\n") action_str = "table=%s "%L3_LOOKUP_TABLE + " priority=0 " + " actions=resubmit(,%s)"%L2_LOOKUP_TABLE ofspec.write(action_str + "\n") del_flows(bridge, table=L2_LOOKUP_TABLE) del_flows(bridge, table=L3_LOOKUP_TABLE) # update bridge with the flow-rules added in the file in one attempt do_cmd([OFCTL_PATH, 'add-flows', bridge, ofspec_filename]) ofspec.close() return "SUCCESS: successfully configured bridge as per the VPC topology update with sequence no: %s"%sequence_no except: logging.debug("An unexpected error occurred while configuring bridge as per VPC topology " "update with sequence no: %s"%sequence_no) # now that we updated the bridge with flow rules delete the file. os.remove(ofspec_filename) raise else: # now that we updated the bridge with flow rules delete the file. os.remove(ofspec_filename) # Configures the bridge created for a VPC enabled for distributed firewall. Management server sends VPC routing policies # details. Based on the VPC routing policies ingress ACL table and egress ACL tables are updated by this function. def configure_ovs_bridge_for_routing_policies(bridge, json_config, sequence_no): vpconfig = jsonLoader(json.loads(json_config)).vpc if vpconfig is None: logging.debug("WARNING: Can't find VPC routing policies info in json config file") return "FAILURE:IMPROPER_JSON_CONFG_FILE" try: if not os.path.exists('/var/run/cloud'): os.makedirs('/var/run/cloud') # create a temporary file to store OpenFlow rules corresponding to ingress and egress ACL table updates ofspec_filename = "/var/run/cloud/" + bridge + sequence_no + ".ofspec" ofspec = open(ofspec_filename, 'w') egress_rules_added = False ingress_rules_added = False tiers = vpconfig.tiers for tier in tiers: tier_cidr = tier.cidr acl = get_acl(vpconfig, tier.aclid) acl_items = acl.aclitems for acl_item in acl_items: number = acl_item.number action = acl_item.action direction = acl_item.direction source_port_start = acl_item.sourceportstart source_port_end = acl_item.sourceportend protocol = acl_item.protocol source_cidrs = acl_item.sourcecidrs acl_priority = 1000 + number if direction == "ingress": matching_table = INGRESS_ACL_TABLE resubmit_table = L2_LOOKUP_TABLE elif direction == "egress": matching_table = EGRESS_ACL_TABLE resubmit_table = L3_LOOKUP_TABLE for source_cidr in source_cidrs: ingress_rules_added = True if source_port_start is None and source_port_end is None: if source_cidr.startswith('0.0.0.0'): if action == "deny": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='drop'" + "\n") if action == "allow": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='resubmit(,%s)'"%resubmit_table + "\n") else: if action == "deny": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " nw_src=%s " %source_cidr + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='drop'" + "\n") if action == "allow": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " nw_src=%s "%source_cidr + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='resubmit(,%s)'"%resubmit_table + "\n") continue # add flow rule to do action (allow/deny) for flows where source IP of the packet is in # source_cidr and destination ip is in tier_cidr port = source_port_start while (port < source_port_end): if source_cidr.startswith('0.0.0.0'): if action == "deny": ofspec.write(" table=%s " %matching_table + " priority=%s " %acl_priority + " tp_dst=%s " %port + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='drop'" + "\n") if action == "allow": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " tp_dst=%s " %port + " nw_dst=%s " %tier_cidr + " nw_proto=%s " %protocol + " actions='resubmit(,%s)'"%resubmit_table + "\n") else: if action == "deny": ofspec.write(" table=%s " %matching_table + " priority=%s " %acl_priority + " tp_dst=%s " %port + " nw_src=%s "%source_cidr + " nw_dst=%s "%tier_cidr + " nw_proto=%s " %protocol + " actions='drop'" + "\n") if action == "allow": ofspec.write(" table=%s "%matching_table + " priority=%s " %acl_priority + " tp_dst=%s " %port + " nw_src=%s "%source_cidr + " nw_dst=%s "%tier_cidr + " nw_proto=%s " %protocol + " actions='resubmit(,%s)'"%resubmit_table + "\n") port = port + 1 # add a default rule in egress table to allow packets (so forward packet to L3 lookup table) ofspec.write(" table=%s " %EGRESS_ACL_TABLE + " priority=0 actions='resubmit(,%s)')"%L3_LOOKUP_TABLE + "\n") # add a default rule in ingress table drop packets ofspec.write(" table=%s " %INGRESS_ACL_TABLE + " priority=0 actions='drop'" + "\n") # First flush current ingress and egress ACL's before re-applying the ACL's del_flows(bridge, table=EGRESS_ACL_TABLE) del_flows(bridge, table=INGRESS_ACL_TABLE) # update bridge with the flow-rules for ingress and egress ACL's added in the file in one attempt do_cmd([OFCTL_PATH, 'add-flows', bridge, ofspec_filename]) ofspec.close() return "SUCCESS: successfully configured bridge as per the latest routing policies of the VPC" except: logging.debug("An unexpected error occurred while configuring bridge as per VPC's routing policies.") # now that we updated the bridge with flow rules delete the file. ofspec.close() os.remove(ofspec_filename) raise else: # now that we updated the bridge with flow rules delete the file. ofspec.close() os.remove(ofspec_filename) def update_flooding_rules_on_port_plug_unplug(bridge, interface, command, if_network_id): vnet_vif_ofports = [] vnet_tunnelif_ofports = [] vnet_all_ofports = [] logging.debug("Updating the flooding rules as interface %s" %interface + " is %s"%command + " now.") try: vsctl_output = do_cmd([VSCTL_PATH, 'list-ports', bridge]) ports = vsctl_output.split('\n') for port in ports: if_ofport = do_cmd([VSCTL_PATH, 'get', 'Interface', port, 'ofport']) if port.startswith('vif'): # check VIF is in same network as that of plugged vif if if_network_id != get_network_id_for_vif(port): continue vnet_vif_ofports.append(if_ofport) vnet_all_ofports.append(if_ofport) if port.startswith('t'): # check tunnel port is in same network as that of plugged vif if if_network_id != get_network_id_for_tunnel_port(port)[1:-1]: continue vnet_tunnelif_ofports.append(if_ofport) vnet_all_ofports.append(if_ofport) if command == 'online': if len(vnet_all_ofports) == 1 : return for port in vnet_all_ofports: clear_flooding_rules_for_port(bridge, port) # for a packet arrived from tunnel port, flood only on VIF ports for port in vnet_tunnelif_ofports: add_flooding_rules_for_port(bridge, port, vnet_vif_ofports) # for a packet arrived from VIF port send on all VIF and tunnel port excluding the port # on which packet arrived for port in vnet_vif_ofports: vnet_all_ofports_copy = copy.copy(vnet_all_ofports) vnet_all_ofports_copy.remove(port) add_flooding_rules_for_port(bridge, port, vnet_all_ofports_copy) this_if_ofport = do_cmd([VSCTL_PATH, 'get', 'Interface', interface, 'ofport']) #learn that MAC is reachable through the VIF port if interface.startswith('vif'): mac = get_macaddress_of_vif(interface) add_mac_lookup_table_entry(bridge, mac, this_if_ofport) if command == 'offline': for port in vnet_all_ofports: clear_flooding_rules_for_port(bridge, port) vnet_all_ofports.remove(this_if_ofport) vnet_vif_ofports.remove(this_if_ofport) # for a packet arrived from tunnel port, flood only on VIF ports for port in vnet_tunnelif_ofports: add_flooding_rules_for_port(bridge, port, vnet_vif_ofports) # for a packet from VIF port send on all VIF's and tunnel ports excluding the port on which packet arrived for port in vnet_vif_ofports: vnet_all_ofports_copy = copy.copy(vnet_all_ofports) vnet_all_ofports_copy.remove(port) add_flooding_rules_for_port(bridge, port, vnet_all_ofports_copy) # un-learn that MAC is reachable through the VIF port if interface.startswith('vif'): mac = get_macaddress_of_vif(interface) delete_mac_lookup_table_entry(bridge, mac) except: logging.debug("An unexpected error occurred while updating the flooding rules when interface " + " %s" %interface + " is %s"%command) raise