diff --git a/etc/neutron/plugins/ml2/ml2_conf_cisco.ini b/etc/neutron/plugins/ml2/ml2_conf_cisco.ini index 82b687e..06b89cd 100644 --- a/etc/neutron/plugins/ml2/ml2_conf_cisco.ini +++ b/etc/neutron/plugins/ml2/ml2_conf_cisco.ini @@ -16,6 +16,15 @@ # Nexus switches. Defaults to False (No hostkey checks) # host_key_checks = False +# (ListOp) A choice of drivers to configure Nexus devices. The +# default choice is 'ncclient' which was the original driver +# until rest api driver was developed. To choose the +# rest api driver, set nexus_driver to 'restapi' which +# has better performance and no Nexus session limits. +# This option should only be selected if you have a +# Nexus 9K image version 7.0(3)I5(2) or greater. +# nexus_driver = ncclient + # # (StrOpt) The name of the physical_network managed via the Cisco Nexus Switch. # This string value must be present in the ml2_conf.ini network_vlan_ranges diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/config.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/config.py index 0aa565f..609b54c 100644 --- a/networking_cisco/plugins/ml2/drivers/cisco/nexus/config.py +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/config.py @@ -45,6 +45,11 @@ ml2_cisco_opts = [ cfg.BoolOpt('host_key_checks', default=False, help=_("Enable strict host key checks when " "connecting to Nexus switches")), + cfg.StrOpt('nexus_driver', + default='ncclient', + help=_("Choice of Nexus Config Driver to be loaded from " + "the networking_cisco.ml2.nexus_driver namespace.")), + ] diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/exceptions.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/exceptions.py index 3976ecc..15a503d 100644 --- a/networking_cisco/plugins/ml2/drivers/cisco/nexus/exceptions.py +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/exceptions.py @@ -44,7 +44,7 @@ class NexusConnectFailed(exceptions.NeutronException): class NexusConfigFailed(exceptions.NeutronException): """Failed to configure Nexus switch.""" message = _("Failed to configure Nexus switch: %(nexus_host)s " - "XML: %(config)s. Reason: %(exc)s.") + "Config: %(config)s. Reason: %(exc)s.") class NexusPortBindingNotFound(exceptions.NeutronException): diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py index 15e8fbb..6c2c95c 100644 --- a/networking_cisco/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/mech_cisco_nexus.py @@ -30,6 +30,7 @@ from oslo_utils import excutils from networking_cisco import backwards_compatibility as bc +from neutron.common import utils as neutron_utils from neutron.extensions import portbindings from neutron.plugins.common import constants as p_const @@ -49,8 +50,6 @@ from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( exceptions as excep) from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( nexus_db_v2 as nxos_db) -from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( - nexus_network_driver) LOG = logging.getLogger(__name__) @@ -157,6 +156,16 @@ class CiscoNexusCfgMonitor(object): nve_bindings = nxos_db.get_nve_switch_bindings(switch_ip) + # If configured to set global VXLAN values and + # there exists VXLAN data base entries, then configure + # the "interface nve" entry on the switch. + if (len(nve_bindings) > 0 and + cfg.CONF.ml2_cisco.vxlan_global_config): + LOG.debug("Nexus: Replay NVE Interface") + loopback = self._mdriver.get_nve_loopback(switch_ip) + self._driver.enable_vxlan_feature(switch_ip, + const.NVE_INT_NUM, loopback) + for x in nve_bindings: try: self._driver.create_nve_member(switch_ip, @@ -271,6 +280,21 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): """Cisco Nexus ML2 Mechanism Driver.""" + def _load_nexus_cfg_driver(self): + """Load Nexus Config driver. + :raises SystemExit of 1 if driver cannot be loaded + """ + + try: + loaded_class = neutron_utils.load_class_by_alias_or_classname( + 'networking_cisco.ml2.nexus_driver', + conf.cfg.CONF.ml2_cisco.nexus_driver) + return loaded_class() + except ImportError: + LOG.error(_LE("Error loading Nexus Config driver '%s'"), + conf.cfg.CONF.ml2_cisco.nexus_driver) + raise SystemExit(1) + def initialize(self): # Create ML2 device dictionary from ml2_conf.ini entries. conf.ML2MechCiscoConfig() @@ -278,10 +302,11 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): # Extract configuration parameters from the configuration file. self._nexus_switches = conf.ML2MechCiscoConfig.nexus_dict LOG.debug("nexus_switches found = %s", self._nexus_switches) + # Save dynamic switch information self._switch_state = {} - self.driver = nexus_network_driver.CiscoNexusDriver() + self.driver = self._load_nexus_cfg_driver() # This method is only called once regardless of number of # api/rpc workers defined. @@ -1059,6 +1084,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): nxos_db.add_nexusnve_binding(vni, switch_ip, device_id, mcast_group) + def get_nve_loopback(self, switch_ip): + return self._nexus_switches.get( + (switch_ip, 'nve_src_intf'), '0') + def _configure_nve_member(self, vni, device_id, mcast_group, host_id): """Add "member vni" configuration to the NVE interface. @@ -1075,8 +1104,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): nve_bindings = nxos_db.get_nve_switch_bindings(switch_ip) if len(nve_bindings) == 1: LOG.debug("Nexus: create NVE interface") - loopback = self._nexus_switches.get( - (switch_ip, 'nve_src_intf'), '0') + loopback = self.get_nve_loopback(switch_ip) self.driver.enable_vxlan_feature(switch_ip, const.NVE_INT_NUM, loopback) @@ -1177,7 +1205,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): nexus_port, vni, is_native) elif auto_create: LOG.debug("Nexus: create vlan %s", vlan_name) - self.driver.create_vlan_segment(switch_ip, vlan_id, + self.driver.create_vlan(switch_ip, vlan_id, vlan_name, vni) elif auto_trunk: LOG.debug("Nexus: trunk vlan %s", vlan_name) @@ -1234,11 +1262,21 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): def _restore_port_binding(self, switch_ip, pvlan_ids, - port, is_native): + port, native_vlan): """Restores a set of vlans for a given port.""" intf_type, nexus_port = self.split_interface_name(port) + # If native_vlan is configured, this is isolated since + # two configs (native + trunk) must be sent for this vlan only. + if native_vlan != 0: + self.driver.send_enable_vlan_on_trunk_int( + switch_ip, native_vlan, + intf_type, nexus_port, True) + # If this is the only vlan + if len(pvlan_ids) == 1: + return + concat_vlans = '' compressed_vlans = self._get_compressed_vlan_list(pvlan_ids) for pvlan in compressed_vlans: @@ -1252,14 +1290,14 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): if len(concat_vlans) >= const.CREATE_PORT_VLAN_LENGTH: self.driver.send_enable_vlan_on_trunk_int( switch_ip, concat_vlans, - intf_type, nexus_port, is_native) + intf_type, nexus_port, False) concat_vlans = '' # Send remaining vlans if any if len(concat_vlans): self.driver.send_enable_vlan_on_trunk_int( switch_ip, concat_vlans, - intf_type, nexus_port, is_native) + intf_type, nexus_port, False) def _restore_vxlan_entries(self, switch_ip, vlans): """Restore vxlan entries on a Nexus switch.""" @@ -1267,24 +1305,30 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): count = 1 conf_str = '' vnsegment_sent = 0 + path_str, conf_str = self.driver.start_create_vlan() # At this time, this will only configure vni information when needed while vnsegment_sent < const.CREATE_VLAN_BATCH and vlans: vlan_id, vni, vlan_name = vlans.pop(0) # Add it to the batch - conf_str += self.driver.get_create_vlan( - switch_ip, vlan_id, vni) + conf_str = self.driver.get_create_vlan( + switch_ip, vlan_id, vni, conf_str) + # batch size has been met if (count == const.CREATE_VLAN_SEND_SIZE): - self.driver.send_edit_string(switch_ip, conf_str) + conf_str = self.driver.end_create_vlan(conf_str) + self.driver.send_edit_string(switch_ip, path_str, conf_str) vnsegment_sent += count conf_str = '' count = 1 else: count += 1 + # batch size was not met if conf_str: vnsegment_sent += count - self.driver.send_edit_string(switch_ip, conf_str) + conf_str = self.driver.end_create_vlan(conf_str) + self.driver.send_edit_string(switch_ip, path_str, conf_str) conf_str = '' + LOG.debug("Switch %s VLAN vn-segment replay summary: %d", switch_ip, vnsegment_sent) @@ -1391,7 +1435,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): prev_vlan = -1 prev_vni = -1 prev_port = None - prev_is_native = False + prev_native_vlan = 0 starttime = time.time() port_bindings.sort(key=lambda x: (x.port_id, x.vlan_id, x.vni)) @@ -1422,6 +1466,8 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): vlans.add((port.vlan_id, port.vni, vlan_name)) if auto_trunk: pvlans.add(port.vlan_id) + if port.is_native: + prev_native_vlan = port.vlan_id else: # Different port - write out interface trunk on previous port if prev_port: @@ -1433,22 +1479,24 @@ class CiscoNexusMechanismDriver(api.MechanismDriver): vlan_count = 0 if pvlans: self._restore_port_binding( - switch_ip, pvlans, prev_port, prev_is_native) + switch_ip, pvlans, prev_port, prev_native_vlan) pvlans.clear() + prev_native_vlan = 0 # Start tracking new port if auto_create: vlans.add((port.vlan_id, port.vni, vlan_name)) if auto_trunk: pvlans.add(port.vlan_id) prev_port = port.port_id - prev_is_native = port.is_native + if port.is_native: + prev_native_vlan = port.vlan_id if pvlans: LOG.debug("Switch %s port %s replay summary: unique vlan " "count %d, duplicate port entries %d", switch_ip, port.port_id, vlan_count, duplicate_port) self._restore_port_binding( - switch_ip, pvlans, prev_port, prev_is_native) + switch_ip, pvlans, prev_port, prev_native_vlan) LOG.debug("Replayed total %d ports for Switch %s", interface_count + 1, switch_ip) diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_base_network_driver.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_base_network_driver.py new file mode 100644 index 0000000..af3069e --- /dev/null +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_base_network_driver.py @@ -0,0 +1,184 @@ +# Copyright (c) 2013-2016 Cisco Systems, Inc. +# 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. + +""" +Base class driver for SSH and RESTAPI Client. Some APIs are +called by upper layer code but only apply to either RESTAPIs drivers +or SSH drivers. Having base class which simply performs 'pass' +is necessary so these two drivers get required actions without +breaking the other driver. Drivers defined in this base are +mostly those called externally. +""" + +import os +import threading +import time + +from oslo_log import log as logging + +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + config as conf) + +LOG = logging.getLogger(__name__) + + +class CiscoNexusBaseDriver(object): + """Nexus Driver Base Class.""" + def __init__(self): + self.nexus_switches = conf.ML2MechCiscoConfig.nexus_dict + self.time_stats = {} + + def keep_ssh_caching(self): + pass + + def init_ssh_caching(self): + pass + + def capture_and_print_timeshot(self, start_time, which, + other=99, switch="0.0.0.0"): + """Determine delta, keep track, and print results.""" + + curr_timeout = time.time() - start_time + if which in self.time_stats: + self.time_stats[which]["total_time"] += curr_timeout + self.time_stats[which]["total_count"] += 1 + if (curr_timeout < self.time_stats[which]["min"]): + self.time_stats[which]["min"] = curr_timeout + if (curr_timeout > self.time_stats[which]["max"]): + self.time_stats[which]["max"] = curr_timeout + else: + self.time_stats[which] = { + "total_time": curr_timeout, + "total_count": 1, + "min": curr_timeout, + "max": curr_timeout} + LOG.debug("NEXUS_TIME_STATS %(switch)s, pid %(pid)d, tid %(tid)d: " + "%(which)s_timeout %(curr)f count %(count)d " + "average %(ave)f other %(other)d min %(min)f max %(max)f", + {'switch': switch, + 'pid': os.getpid(), + 'tid': threading.current_thread().ident, + 'which': which, + 'curr': curr_timeout, + 'count': self.time_stats[which]["total_count"], + 'ave': (self.time_stats[which]["total_time"] / + self.time_stats[which]["total_count"]), + 'other': other, + 'min': self.time_stats[which]["min"], + 'max': self.time_stats[which]["max"]}) + + def close_session(self, nexus_host): + pass + + def get_interface_switch(self, nexus_host, + intf_type, interface): + """Get the interface data from host. + + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns response: + """ + return None + + def initialize_all_switch_interfaces(self, interfaces): + """Configure Nexus interface and get port channel number. + + Receive a list of interfaces containing: + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns interface: Appends port channel to each entry + channel number is 0 if none + """ + pass + + def get_nexus_type(self, nexus_host): + """Given the nexus host, get the type of Nexus switch. + + :param nexus_host: IP address of Nexus switch + :returns Nexus type + """ + return None + + def set_all_vlan_states(self, nexus_host, vlanid_range): + """Set the VLAN states to active.""" + pass + + def start_create_vlan(self): + """Returns an XML snippet for start of create VLAN.""" + return None, '' + + def get_create_vlan(self, nexus_host, vlanid, vni, conf_str): + """Returns an XML snippet for create VLAN.""" + return conf_str + + def end_create_vlan(self, conf_str): + """Returns an XML snippet for terminate of create VLAN.""" + return conf_str + + def create_vlan(self, nexus_host, + vlanid, vlanname, vni): + """Create a VLAN on a Nexus Switch. + + Creates a VLAN given the VLAN ID, name and possible VxLAN ID. + """ + pass + + def delete_vlan(self, nexus_host, vlanid): + """Delete a VLAN on Nexus Switch given the VLAN ID.""" + pass + + def disable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface, is_native): + """Disable a VLAN on a trunk interface.""" + pass + + def send_edit_string(self, nexus_host, path, confstr, + check_to_close_session=True): + """Sends any XML snippet to Nexus switch.""" + pass + + def send_enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface, is_native): + """Gathers and sends an interface trunk XML snippet.""" + pass + + def create_and_trunk_vlan(self, nexus_host, vlan_id, + vlan_name, intf_type, + nexus_port, vni, + is_native): + """Create VLAN and trunk it on the specified ports.""" + pass + + def enable_vxlan_feature(self, nexus_host, nve_int_num, src_intf): + """Enable VXLAN on the switch.""" + pass + + def disable_vxlan_feature(self, nexus_host): + """Disable VXLAN on the switch.""" + pass + + def create_nve_member(self, nexus_host, nve_int_num, vni, mcast_group): + """Add a member configuration to the NVE interface.""" + pass + + def delete_nve_member(self, nexus_host, nve_int_num, vni): + """Delete a member configuration on the NVE interface.""" + pass diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py index 6ac685f..3cb1156 100644 --- a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_network_driver.py @@ -17,10 +17,8 @@ Implements a Nexus-OS NETCONF over SSHv2 API Client """ -import os import re import six -import threading import time from oslo_concurrency import lockutils @@ -36,20 +34,23 @@ from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( constants as const) from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( exceptions as cexc) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_base_network_driver as basedrvr) from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( nexus_snippets as snipp) LOG = logging.getLogger(__name__) -class CiscoNexusDriver(object): +class CiscoNexusSshDriver(basedrvr.CiscoNexusBaseDriver): """Nexus Driver Main Class.""" def __init__(self): self.ncclient = None self.nexus_switches = conf.ML2MechCiscoConfig.nexus_dict self.connections = {} - self.time_stats = {} self.init_ssh_caching() + super(CiscoNexusSshDriver, self).__init__() + LOG.debug("ML2 Nexus SSH Drivers initialized.") # Driver lock introduced to prevent replay thread and # transaction thread from closing each others @@ -68,7 +69,7 @@ class CiscoNexusDriver(object): (cfg.CONF.rpc_workers + cfg.CONF.api_workers) >= const.MAX_NEXUS_SSH_SESSIONS) else False - def _import_ncclient(self): + def _import_client(self): """Import the NETCONF client (ncclient) module. The ncclient module is not installed as part of the normal Neutron @@ -79,39 +80,6 @@ class CiscoNexusDriver(object): """ return importutils.import_module('ncclient.manager') - def capture_and_print_timeshot(self, start_time, which, - other=99, switch="0.0.0.0"): - """Determine delta, keep track, and print results.""" - - curr_timeout = time.time() - start_time - if which in self.time_stats: - self.time_stats[which]["total_time"] += curr_timeout - self.time_stats[which]["total_count"] += 1 - if (curr_timeout < self.time_stats[which]["min"]): - self.time_stats[which]["min"] = curr_timeout - if (curr_timeout > self.time_stats[which]["max"]): - self.time_stats[which]["max"] = curr_timeout - else: - self.time_stats[which] = { - "total_time": curr_timeout, - "total_count": 1, - "min": curr_timeout, - "max": curr_timeout} - LOG.debug("NEXUS_TIME_STATS %(switch)s, pid %(pid)d, tid %(tid)d: " - "%(which)s_timeout %(curr)f count %(count)d " - "average %(ave)f other %(other)d min %(min)f max %(max)f", - {'switch': switch, - 'pid': os.getpid(), - 'tid': threading.current_thread().ident, - 'which': which, - 'curr': curr_timeout, - 'count': self.time_stats[which]["total_count"], - 'ave': (self.time_stats[which]["total_time"] / - self.time_stats[which]["total_count"]), - 'other': other, - 'min': self.time_stats[which]["min"], - 'max': self.time_stats[which]["max"]}) - def _get_close_ssh_session(self): return self._close_ssh_session @@ -134,7 +102,7 @@ class CiscoNexusDriver(object): # session before complete. @lockutils.synchronized('cisco-nexus-drvrlock') def close_session(self, nexus_host): - mgr = self.nxos_connect(nexus_host) + mgr = self._nxos_connect(nexus_host) self._close_session(mgr, nexus_host) # Driver lock introduced to prevent replay thread and @@ -157,7 +125,7 @@ class CiscoNexusDriver(object): # try again # then quit for retry_count in (1, 2): - mgr = self.nxos_connect(nexus_host) + mgr = self._nxos_connect(nexus_host) starttime = time.time() try: data_xml = mgr.get(filter=('subtree', filter)).data_xml @@ -224,7 +192,7 @@ class CiscoNexusDriver(object): # try again # then quit for retry_count in (1, 2): - mgr = self.nxos_connect(nexus_host) + mgr = self._nxos_connect(nexus_host) LOG.debug("NexusDriver edit config for host %s: %s", nexus_host, config) starttime = time.time() @@ -259,7 +227,7 @@ class CiscoNexusDriver(object): if check_to_close_session and self._get_close_ssh_session(): self._close_session(mgr, nexus_host) - def nxos_connect(self, nexus_host): + def _nxos_connect(self, nexus_host): """Make SSH connection to the Nexus Switch.""" starttime = time.time() @@ -270,7 +238,7 @@ class CiscoNexusDriver(object): return self.connections[nexus_host] if not self.ncclient: - self.ncclient = self._import_ncclient() + self.ncclient = self._import_client() nexus_ssh_port = int(self.nexus_switches[nexus_host, 'ssh_port']) nexus_user = self.nexus_switches[nexus_host, const.USERNAME] nexus_password = self.nexus_switches[nexus_host, const.PASSWORD] @@ -374,7 +342,7 @@ class CiscoNexusDriver(object): "switchport trunk allowed vlan" in response): pass else: - ifs.append(self.build_intf_confstr( + ifs.append(self._build_intf_confstr( snipp.CMD_INT_VLAN_SNIPPET, intf_type, nexus_port, 'None')) if ifs: @@ -385,22 +353,6 @@ class CiscoNexusDriver(object): starttime, "get_allif", switch=nexus_host) - def get_version(self, nexus_host): - """Given the nexus host, get the version data. - - :param nexus_host: IP address of Nexus switch - :returns version number - """ - - confstr = snipp.EXEC_GET_VERSION_SNIPPET - response = self._get_config(nexus_host, confstr) - LOG.debug("GET call returned version") - version = None - if response: - version = re.findall( - "\([\x20-\x7e]+)\<\/sys_ver_str\>", response) - return version - def get_nexus_type(self, nexus_host): """Given the nexus host, get the type of Nexus switch. @@ -443,9 +395,9 @@ class CiscoNexusDriver(object): self.capture_and_print_timeshot( starttime, "set_all_vlan_states", switch=nexus_host) - self.send_edit_string(nexus_host, snippet) + self.send_edit_string(nexus_host, None, snippet) - def get_create_vlan(self, nexus_host, vlanid, vni): + def get_create_vlan(self, nexus_host, vlanid, vni, conf_str): """Returns an XML snippet for create VLAN on a Nexus Switch.""" LOG.debug("NexusDriver: ") @@ -460,7 +412,8 @@ class CiscoNexusDriver(object): starttime, "get_create_vlan", switch=nexus_host) - return snippet + conf_str += snippet + return conf_str def create_vlan(self, nexus_host, vlanid, vlanname, vni): @@ -472,9 +425,10 @@ class CiscoNexusDriver(object): LOG.debug("NexusDriver: ") starttime = time.time() - confstr = self.get_create_vlan(nexus_host, vlanid, vni) + confstr = '' + confstr = self.get_create_vlan(nexus_host, vlanid, vni, confstr) - self.send_edit_string(nexus_host, confstr, + self.send_edit_string(nexus_host, None, confstr, check_to_close_session=False) self.capture_and_print_timeshot( @@ -492,12 +446,12 @@ class CiscoNexusDriver(object): starttime, "del_vlan", switch=nexus_host) - def build_intf_confstr(self, snippet, intf_type, interface, vlanid): + def _build_intf_confstr(self, snippet, intf_type, interface, vlanid): """Build the VLAN config string xml snippet to be used.""" confstr = snippet % (intf_type, interface, vlanid, intf_type) return confstr - def get_enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + def _get_enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, interface, is_native, confstr=''): """Prepares an XML snippet for VLAN on a trunk interface. @@ -519,7 +473,7 @@ class CiscoNexusDriver(object): snippets.append(snipp.CMD_INT_VLAN_ADD_SNIPPET) for snip in snippets: - confstr += self.build_intf_confstr( + confstr += self._build_intf_confstr( snippet=snip, intf_type=intf_type, interface=interface, @@ -551,7 +505,7 @@ class CiscoNexusDriver(object): starttime, "delif", switch=nexus_host) - def send_edit_string(self, nexus_host, confstr, + def send_edit_string(self, nexus_host, path, confstr, check_to_close_session=True): """Sends any XML snippet to Nexus switch.""" @@ -571,10 +525,10 @@ class CiscoNexusDriver(object): def send_enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, interface, is_native): """Gathers and sends an interface trunk XML snippet.""" - confstr = self.get_enable_vlan_on_trunk_int( + confstr = self._get_enable_vlan_on_trunk_int( nexus_host, vlanid, intf_type, interface, is_native) - self.send_edit_string(nexus_host, confstr) + self.send_edit_string(nexus_host, None, confstr) def create_and_trunk_vlan(self, nexus_host, vlan_id, vlan_name, intf_type, @@ -593,23 +547,11 @@ class CiscoNexusDriver(object): starttime, "create_all", switch=nexus_host) - def create_vlan_svi(self, nexus_host, vlan_id, gateway_ip): - """Create VLAN vn_segment.""" - confstr = snipp.CMD_VLAN_SVI_SNIPPET % (vlan_id, gateway_ip) - confstr = self.create_xml_snippet(confstr) - LOG.debug("NexusDriver: ") - self._edit_config(nexus_host, target='running', config=confstr) - - def delete_vlan_svi(self, nexus_host, vlan_id): - """Delete VLAN vn_segment.""" - confstr = snipp.CMD_NO_VLAN_SVI_SNIPPET % vlan_id - confstr = self.create_xml_snippet(confstr) - LOG.debug("NexusDriver: ") - self._edit_config(nexus_host, target='running', config=confstr) - def enable_vxlan_feature(self, nexus_host, nve_int_num, src_intf): """Enable VXLAN on the switch.""" + starttime = time.time() + # Configure the "feature" commands and NVE interface # (without "member" subcommand configuration). # The Nexus 9K will not allow the "interface nve" configuration @@ -624,25 +566,47 @@ class CiscoNexusDriver(object): (nve_int_num, src_intf))) LOG.debug("NexusDriver: ") self._edit_config(nexus_host, config=confstr) + self.capture_and_print_timeshot( + starttime, "enable_vxlan", + switch=nexus_host) def disable_vxlan_feature(self, nexus_host): """Disable VXLAN on the switch.""" + starttime = time.time() + # Removing the "feature" commands also removes the NVE interface. confstr = self.create_xml_snippet(snipp.CMD_NO_FEATURE_VXLAN_SNIPPET) LOG.debug("NexusDriver: ") self._edit_config(nexus_host, config=confstr) + self.capture_and_print_timeshot( + starttime, "disable_vxlan", + switch=nexus_host) + def create_nve_member(self, nexus_host, nve_int_num, vni, mcast_group): """Add a member configuration to the NVE interface.""" + + starttime = time.time() confstr = self.create_xml_snippet((snipp.CMD_INT_NVE_MEMBER_SNIPPET % (nve_int_num, vni, mcast_group))) LOG.debug("NexusDriver: ") self._edit_config(nexus_host, config=confstr) + self.capture_and_print_timeshot( + starttime, "create_nve", + switch=nexus_host) + def delete_nve_member(self, nexus_host, nve_int_num, vni): """Delete a member configuration on the NVE interface.""" + + starttime = time.time() + confstr = self.create_xml_snippet((snipp.CMD_INT_NVE_NO_MEMBER_SNIPPET % (nve_int_num, vni))) LOG.debug("NexusDriver: ") self._edit_config(nexus_host, config=confstr) + + self.capture_and_print_timeshot( + starttime, "delete_nve", + switch=nexus_host) diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_client.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_client.py new file mode 100644 index 0000000..6a82052 --- /dev/null +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_client.py @@ -0,0 +1,178 @@ +# Copyright (c) 2017-2017 Cisco Systems, Inc. +# 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. + +""" +Implements REST API Client For Nexus +""" + +import netaddr +import requests + +from networking_cisco._i18n import _LE +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + exceptions as cexc) +from oslo_log import log as logging +from oslo_serialization import jsonutils + +DEFAULT_HEADER = {"Content-type": "application/json", "Accept": "text/plain"} +COOKIE_HEADER = """ +{"Cookie": %s, "Content-type": "application/json", "Accept": "text/plain"}""" +DEFAULT_SCHEME = "http" +ACCEPTED_CODES = [200, 201, 204] + +LOG = logging.getLogger(__name__) + + +class CiscoNexusRestapiClient(object): + + def __init__(self, credentials, + accepted_codes=ACCEPTED_CODES, + scheme=DEFAULT_SCHEME, + timeout=30, + max_retries=2): + """Initialize the rest api client for Nexus.""" + self.format = 'json' + self.accepted_codes = accepted_codes + self.action_prefix = 'http://%s/' + self.scheme = scheme + self.status = requests.codes.OK + self.time_stats = {} + self.timeout = timeout + self.max_retries = max_retries + self.session = requests.Session() + self.credentials = credentials + + def _get_cookie(self, mgmt_ip, config): + """Performs authentication and retries cookie.""" + + if mgmt_ip not in self.credentials: + return None + + security_data = self.credentials[mgmt_ip] + payload = {"aaaUser": {"attributes": {"name": security_data[0], + "pwd": security_data[1]}}} + headers = {"Content-type": "application/json", "Accept": "text/plain"} + + url = "http://{0}/api/aaaLogin.json".format(mgmt_ip) + + try: + response = self.session.request('POST', + url, + data=jsonutils.dumps(payload), + headers=headers, + timeout=self.timeout * 2) + except Exception as e: + raise cexc.NexusConnectFailed(nexus_host=mgmt_ip, + exc=e) + + self.status = response.status_code + if response.status_code == requests.codes.OK: + return response.headers.get('Set-Cookie') + else: + e = "REST API connect returned Error code: " + e += str(self.status) + raise cexc.NexusConnectFailed(nexus_host=mgmt_ip, + exc=e) + + def send_request(self, method, action, body=None, + headers=None, ipaddr=None): + """Perform the HTTP request. + + The response is in either JSON format or plain text. A GET method will + invoke a JSON response while a PUT/POST/DELETE returns message from the + the server in plain text format. + Exception is raised when server replies with an INTERNAL SERVER ERROR + status code (500) i.e. an error has occurred on the server or SERVICE + UNAVAILABLE (404) i.e. server is not reachable. + + :param method: type of the HTTP request. POST, GET, PUT or DELETE + :param action: path to which the client makes request + :param body: dict of arguments which are sent as part of the request + :param headers: header for the HTTP request + :param server_ip: server_ip for the HTTP request. + :returns: JSON or plain text in HTTP response + """ + + action = ''.join([self.scheme, '://%s/', action]) + if netaddr.valid_ipv6(ipaddr): + # Enclose IPv6 address in [] in the URL + action = action % ("[%s]" % ipaddr) + else: + # IPv4 address + action = action % ipaddr + + config = action + " : " + body if body else action + + cookie = self._get_cookie(ipaddr, config) + if not cookie or self.status != requests.codes.OK: + return {} + headers = {"Content-type": "application/json", + "Accept": "text/plain", "Cookie": cookie} + + for attempt in range(self.max_retries + 1): + try: + LOG.debug("[Nexus %(ipaddr)s attempt %(id)s]: Connecting.." % + {"ipaddr": ipaddr, "id": attempt}) + response = self.session.request( + method, + action, + data=body, + headers=headers, + timeout=self.timeout) + except Exception as e: + LOG.error(_LE( + "Exception raised %(err)s for Rest API %(cfg)s"), + {'err': str(e), 'cfg': config}) + raise cexc.NexusConfigFailed(nexus_host=ipaddr, + config=config, + exc=e) + else: + break + + status_string = requests.status_codes._codes[response.status_code][0] + if response.status_code in self.accepted_codes: + LOG.debug( + "Good status %(status)s(%(code)d) returned for %(url)s", + {'status': status_string, + 'code': response.status_code, + 'url': action}) + if 'application/json' in response.headers['content-type']: + try: + return response.json() + except ValueError: + return {} + else: + LOG.error(_LE( + "Bad status %(status)s(%(code)d) returned for %(url)s"), + {'status': status_string, + 'code': response.status_code, + 'url': action}) + LOG.error(_LE("Response text: %(txt)s"), + {'txt': response.text}) + raise cexc.NexusConfigFailed(nexus_host=ipaddr, + config=action, + exc=e) + + def rest_delete(self, action, ipaddr=None, body=None, headers=None): + return self.send_request("DELETE", action, body=body, + headers=headers, ipaddr=ipaddr) + + def rest_get(self, action, ipaddr, body=None, headers=None): + return self.send_request("GET", action, body=body, + headers=headers, ipaddr=ipaddr) + + def rest_post(self, action, ipaddr=None, body=None, headers=None): + return self.send_request("POST", action, body=body, + headers=headers, ipaddr=ipaddr) diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_network_driver.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_network_driver.py new file mode 100644 index 0000000..e85ba7d --- /dev/null +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_network_driver.py @@ -0,0 +1,533 @@ +# Copyright (c) 2017-2017 Cisco Systems, Inc. +# 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. + +""" +Implements a Nexus-OS NETCONF over SSHv2 API Client +""" + +import re +import time + +from oslo_log import log as logging + +from networking_cisco._i18n import _LW + +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + config as conf) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + constants as const) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_base_network_driver as basedrvr) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_client as client) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_snippets as snipp) + +LOG = logging.getLogger(__name__) + + +class CiscoNexusRestapiDriver(basedrvr.CiscoNexusBaseDriver): + """Nexus Driver Restapi Class.""" + def __init__(self): + conf.ML2MechCiscoConfig() + credentials = self._build_credentials( + conf.ML2MechCiscoConfig.nexus_dict) + self.client = self._import_client(credentials) + super(CiscoNexusRestapiDriver, self).__init__() + LOG.debug("ML2 Nexus RESTAPI Drivers initialized.") + + def _import_client(self, credentials): + """Import the local RESTAPI client module. + + This method was created to mirror original ssh driver so + test code was in sync. + + """ + + return client.CiscoNexusRestapiClient(credentials) + + def _build_credentials(self, nexus_switches): + """Build credential table for Rest API Client. + + :param nexus_switches: switch config + :returns credentials: switch credentials list + """ + + credential_attr = [const.USERNAME, const.PASSWORD] + + credentials = {} + for switch_ip, option in nexus_switches: + if option in credential_attr: + value = nexus_switches[switch_ip, option] + if switch_ip in credentials: + credential_tuple = credentials[switch_ip] + else: + credential_tuple = (None, None) + if option == const.USERNAME: + credential_tuple = (value, + credential_tuple[1]) + else: + credential_tuple = ( + credential_tuple[0], + value) + credentials[switch_ip] = credential_tuple + return credentials + + def get_interface_switch(self, nexus_host, + intf_type, interface): + """Get the interface data from host. + + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns response: Returns interface data + """ + + if intf_type == "ethernet": + path_interface = "phys-[eth" + interface + "]" + else: + path_interface = "aggr-[po" + interface + "]" + + action = snipp.PATH_IF % path_interface + + starttime = time.time() + response = self.client.rest_get(action, nexus_host) + self.capture_and_print_timeshot(starttime, "getif", + switch=nexus_host) + LOG.debug("GET call returned interface %(if_type)s %(interface)s " + "config", {'if_type': intf_type, 'interface': interface}) + return response + + def _get_interface_switch_trunk_present( + self, nexus_host, intf_type, interface): + """Check if 'switchport trunk allowed vlan' present. + + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns found: True if config already present + """ + result = self.get_interface_switch(nexus_host, intf_type, interface) + try: + if_type = 'l1PhysIf' if intf_type == "ethernet" else 'pcAggrIf' + vlan_list = result['imdata'][0][if_type] + vlan_list = vlan_list['attributes']['trunkVlans'] + if vlan_list != "1-4094": + found = True + else: + found = False + except Exception: + found = False + + return found + + def _get_port_channel_group( + self, nexus_host, intf_type, interface): + """Look for 'channel-group x' config and return x. + + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns pc_group: Returns port channel group if + present else 0 + """ + + ch_grp = 0 + + # channel-group only applied to ethernet, + # otherwise, return 0 + if intf_type != 'ethernet': + return ch_grp + + match_key = "eth" + interface + + action = snipp.PATH_GET_PC_MEMBERS + + starttime = time.time() + result = self.client.rest_get(action, nexus_host) + self.capture_and_print_timeshot(starttime, "getpc", + switch=nexus_host) + try: + for pcmbr in result['imdata']: + mbr_data = pcmbr['pcRsMbrIfs']['attributes'] + if mbr_data['tSKey'] == match_key: + _, nbr = mbr_data['parentSKey'].split("po") + ch_grp = int(nbr) + break + except Exception: + pass + + LOG.debug("GET interface %(key)s port channel is %(pc)", + {'key': match_key, 'pc': ch_grp}) + + return ch_grp + + def initialize_all_switch_interfaces(self, interfaces): + """Configure Nexus interface and get port channel number. + + Receive a list of interfaces containing: + :param nexus_host: IP address of Nexus switch + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :returns interface: Appends port channel to each entry + channel number is 0 if none + """ + if not interfaces: + return + + starttime = time.time() + for i in range(len(interfaces)): + nexus_host, intf_type, nexus_port, is_native = interfaces[i] + ch_grp = self._get_port_channel_group( + nexus_host, intf_type, nexus_port) + if ch_grp is not 0: + # if channel-group returned, init port-channel + # instead of the provided ethernet interface + intf_type = 'port-channel' + nexus_port = str(ch_grp) + interfaces[i] += (ch_grp,) + present = self._get_interface_switch_trunk_present( + nexus_host, intf_type, nexus_port) + if not present: + self.send_enable_vlan_on_trunk_int( + nexus_host, "", intf_type, nexus_port, False) + self.capture_and_print_timeshot( + starttime, "get_allif", + switch=nexus_host) + + def get_nexus_type(self, nexus_host): + """Given the nexus host, get the type of Nexus switch. + + :param nexus_host: IP address of Nexus switch + :returns Nexus type + """ + + starttime = time.time() + response = self.client.rest_get( + snipp.PATH_GET_NEXUS_TYPE, nexus_host) + self.capture_and_print_timeshot( + starttime, "gettype", + switch=nexus_host) + + if response: + try: + result = response['imdata'][0]["eqptCh"]['attributes']['descr'] + except Exception: + result = '' + nexus_type = re.findall( + "Nexus\s*(\d)\d+\s*[0-9A-Z]+\s*" + "[cC]hassis", + result) + if len(nexus_type) > 0: + LOG.debug("GET call returned Nexus type %d", + int(nexus_type[0])) + return int(nexus_type[0]) + + LOG.warning(_LW("GET call failed to return Nexus type")) + return -1 + + def start_create_vlan(self): + """Returns REST API path and config start.""" + + return snipp.PATH_VLAN_ALL, snipp.BODY_VLAN_ALL_BEG + + def end_create_vlan(self, conf_str): + """Returns current config + end of config.""" + + conf_str += snipp.BODY_VLAN_ALL_END + return conf_str + + def get_create_vlan(self, nexus_host, vlanid, vni, conf_str): + """Returns an XML snippet for create VLAN on a Nexus Switch.""" + + starttime = time.time() + + if vni: + body_snip = snipp.BODY_VXLAN_ALL_INCR % (vlanid, vni) + else: + body_snip = snipp.BODY_VLAN_ALL_INCR % vlanid + conf_str += body_snip + snipp.BODY_VLAN_ALL_CONT + + self.capture_and_print_timeshot( + starttime, "get_create_vlan", + switch=nexus_host) + + return conf_str + + def set_all_vlan_states(self, nexus_host, vlanid_range): + """Set the VLAN states to active.""" + + starttime = time.time() + + if not vlanid_range: + LOG.warning("Exiting set_all_vlan_states: No vlans to configure") + return + + # Eliminate possible whitespace and separate vlans by commas + vlan_id_list = re.sub(r'\s', '', vlanid_range).split(',') + if not vlan_id_list or not vlan_id_list[0]: + LOG.warning("Exiting set_all_vlan_states: No vlans to configure") + return + + path_str, body_vlan_all = self.start_create_vlan() + while vlan_id_list: + rangev = vlan_id_list.pop(0) + if '-' in rangev: + fr, to = rangev.split('-') + max = int(to) + 1 + for vlan_id in range(int(fr), max): + body_vlan_all = self.get_create_vlan( + nexus_host, vlan_id, 0, body_vlan_all) + else: + body_vlan_all = self.get_create_vlan( + nexus_host, rangev, 0, body_vlan_all) + + body_vlan_all = self.end_create_vlan(body_vlan_all) + self.send_edit_string( + nexus_host, path_str, body_vlan_all) + + self.capture_and_print_timeshot( + starttime, "set_all_vlan_states", + switch=nexus_host) + + def create_vlan(self, nexus_host, + vlanid, vlanname, vni): + """Given switch, vlanid, vni, Create a VLAN on Switch.""" + + starttime = time.time() + path_snip, body_snip = self.start_create_vlan() + body_snip = self.get_create_vlan(nexus_host, vlanid, vni, body_snip) + body_snip = self.end_create_vlan(body_snip) + + self.send_edit_string(nexus_host, path_snip, body_snip) + + self.capture_and_print_timeshot( + starttime, "create_vlan_seg", + switch=nexus_host) + + def delete_vlan(self, nexus_host, vlanid): + """Delete a VLAN on Nexus Switch given the VLAN ID.""" + starttime = time.time() + + path_snip = snipp.PATH_VLAN % vlanid + self.client.rest_delete(path_snip, nexus_host) + + self.capture_and_print_timeshot( + starttime, "del_vlan", + switch=nexus_host) + + def _get_vlan_body_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface, is_native, is_delete): + """Prepares an XML snippet for VLAN on a trunk interface. + + :param nexus_host: IP address of Nexus switch + :param vlanid: Vlanid(s) to add to interface + :param intf_type: String which specifies interface type. + example: ethernet + :param interface: String indicating which interface. + example: 1/19 + :param is_native: Is native vlan config desired? + :param is_delete: Is this a delete operation? + :returns path_snippet, body_snippet + """ + + starttime = time.time() + + LOG.debug("NexusDriver get if body config for host %s: " + "if_type %s port %s", + nexus_host, intf_type, interface) + if intf_type == "ethernet": + body_if_type = "l1PhysIf" + path_interface = "phys-[eth" + interface + "]" + else: + body_if_type = "pcAggrIf" + path_interface = "aggr-[po" + interface + "]" + + path_snip = (snipp.PATH_IF % (path_interface)) + + if is_delete: + increment_it = "-" + debug_desc = "delif" + native_vlan = "" + else: + native_vlan = 'vlan-' + str(vlanid) + debug_desc = "createif" + if vlanid is "": + increment_it = "" + else: + increment_it = "+" + + if is_native: + body_snip = (snipp.BODY_NATIVE_TRUNKVLAN % + (body_if_type, increment_it + str(vlanid), + str(native_vlan))) + else: + body_snip = (snipp.BODY_TRUNKVLAN % + (body_if_type, increment_it + str(vlanid))) + + self.capture_and_print_timeshot( + starttime, debug_desc, + switch=nexus_host) + + return path_snip, body_snip + + def disable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface, is_native): + """Disable a VLAN on a trunk interface.""" + + starttime = time.time() + + path_snip, body_snip = self._get_vlan_body_on_trunk_int( + nexus_host, vlanid, intf_type, interface, + is_native, True) + self.send_edit_string(nexus_host, path_snip, body_snip) + self.capture_and_print_timeshot( + starttime, "delif", + switch=nexus_host) + + def send_edit_string(self, nexus_host, path_snip, body_snip, + check_to_close_session=True): + """Sends rest Post request to Nexus switch.""" + + starttime = time.time() + LOG.debug("NexusDriver edit config for host %s: path: %s body: %s", + nexus_host, path_snip, body_snip) + self.client.rest_post(path_snip, nexus_host, body_snip) + self.capture_and_print_timeshot( + starttime, "send_edit", + switch=nexus_host) + + def send_enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type, + interface, is_native): + """Gathers and sends an interface trunk XML snippet.""" + + path_snip, body_snip = self._get_vlan_body_on_trunk_int( + nexus_host, vlanid, intf_type, interface, + is_native, False) + self.send_edit_string(nexus_host, path_snip, body_snip) + + def create_and_trunk_vlan(self, nexus_host, vlan_id, + vlan_name, intf_type, + nexus_port, vni, + is_native): + """Create VLAN and trunk it on the specified ports.""" + + starttime = time.time() + + self.create_vlan(nexus_host, vlan_id, vlan_name, vni) + LOG.debug("NexusDriver created VLAN: %s", vlan_id) + + if nexus_port: + self.send_enable_vlan_on_trunk_int( + nexus_host, vlan_id, + intf_type, nexus_port, + is_native) + + self.capture_and_print_timeshot( + starttime, "create_all", + switch=nexus_host) + + def enable_vxlan_feature(self, nexus_host, nve_int_num, src_intf): + """Enable VXLAN on the switch.""" + + # Configure the "feature" commands and NVE interface + # (without "member" subcommand configuration). + # The Nexus 9K will not allow the "interface nve" configuration + # until the "feature nv overlay" command is issued and installed. + # To get around the N9K failing on the "interface nve" command + # send the two XML snippets down separately. + + starttime = time.time() + + ## Do CLI 'feature nv overlay' + self.send_edit_string(nexus_host, snipp.PATH_VXLAN_STATE, + (snipp.BODY_VXLAN_STATE % "enabled")) + + # Do CLI 'feature vn-segment-vlan-based' + self.send_edit_string(nexus_host, snipp.PATH_VNSEG_STATE, + (snipp.BODY_VNSEG_STATE % "enabled")) + + # Do CLI 'int nve1' to Create nve1 + self.send_edit_string( + nexus_host, + (snipp.PATH_NVE_CREATE % nve_int_num), + (snipp.BODY_NVE_CREATE % nve_int_num)) + + # Do CLI 'no shut + # source-interface loopback %s' + # beneath int nve1 + self.send_edit_string( + nexus_host, + (snipp.PATH_NVE_CREATE % nve_int_num), + (snipp.BODY_NVE_ADD_LOOPBACK % ("enabled", src_intf))) + self.capture_and_print_timeshot( + starttime, "enable_vxlan", + switch=nexus_host) + + def disable_vxlan_feature(self, nexus_host): + """Disable VXLAN on the switch.""" + + # Removing the "feature nv overlay" configuration also + # removes the "interface nve" configuration. + + starttime = time.time() + + # Do CLI 'no feature nv overlay' + self.send_edit_string(nexus_host, snipp.PATH_VXLAN_STATE, + (snipp.BODY_VXLAN_STATE % "disabled")) + + # Do CLI 'no feature vn-segment-vlan-based' + self.send_edit_string(nexus_host, snipp.PATH_VNSEG_STATE, + (snipp.BODY_VNSEG_STATE % "disabled")) + + self.capture_and_print_timeshot( + starttime, "disable_vxlan", + switch=nexus_host) + + def create_nve_member(self, nexus_host, nve_int_num, vni, mcast_group): + """Add a member configuration to the NVE interface.""" + + # Do CLI [no] member vni %s mcast-group %s + # beneath int nve1 + + starttime = time.time() + + path = snipp.PATH_VNI_UPDATE % (nve_int_num, vni) + body = snipp.BODY_VNI_UPDATE % (vni, vni, vni, mcast_group) + self.send_edit_string(nexus_host, path, body) + + self.capture_and_print_timeshot( + starttime, "create_nve", + switch=nexus_host) + + def delete_nve_member(self, nexus_host, nve_int_num, vni): + """Delete a member configuration on the NVE interface.""" + + starttime = time.time() + + path_snip = snipp.PATH_VNI_UPDATE % (nve_int_num, vni) + self.client.rest_delete(path_snip, nexus_host) + + self.capture_and_print_timeshot( + starttime, "delete_nve", + switch=nexus_host) diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_snippets.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_snippets.py new file mode 100644 index 0000000..58fdab2 --- /dev/null +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_restapi_snippets.py @@ -0,0 +1,110 @@ +# Copyright (c) 2017-2017 Cisco Systems, Inc. +# 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. + +# This section is organized by N9K CLI commands + +# show inventory +PATH_GET_NEXUS_TYPE = 'api/mo/sys/ch.json' + +# conf t +# vlan +# state active +PATH_VLAN_ALL = 'api/mo.json' +BODY_VLAN_ALL_BEG = '{"topSystem": { "children": [ {"bdEntity":' +BODY_VLAN_ALL_BEG += ' { children": [' +BODY_VLAN_ALL_INCR = ' {"l2BD": {"attributes": {"fabEncap": "vlan-%s",' +BODY_VLAN_ALL_INCR += ' "pcTag": "1", "adminSt": "active"}}}' +BODY_VXLAN_ALL_INCR = ' {"l2BD": {"attributes": {"fabEncap": "vlan-%s",' +BODY_VXLAN_ALL_INCR += ' "pcTag": "1", "adminSt": "active",' +BODY_VXLAN_ALL_INCR += ' "accEncap": "vxlan-%s"}}}' +BODY_VLAN_ALL_CONT = ',' +BODY_VLAN_ALL_END = ' ]}}]}}' + +# The following was added to make simple Test case results more readible. +BODY_VLAN_ADD_START = (BODY_VLAN_ALL_BEG + BODY_VLAN_ALL_INCR + +BODY_VLAN_ALL_CONT) + +BODY_VLAN_ADD_NEXT = BODY_VLAN_ALL_INCR + BODY_VLAN_ALL_CONT + +BODY_VLAN_ADD = (BODY_VLAN_ALL_BEG + BODY_VLAN_ALL_INCR + +BODY_VLAN_ALL_CONT + BODY_VLAN_ALL_END) + +BODY_VXLAN_ADD = (BODY_VLAN_ALL_BEG + BODY_VXLAN_ALL_INCR + +BODY_VLAN_ALL_CONT + BODY_VLAN_ALL_END) + +# conf t +# vlan +# state active +PATH_VLAN = 'api/mo/sys/bd/bd-[vlan-%s].json' +BODY_VLAN_ACTIVE = '{"l2BD": {"attributes": {"adminSt": "active"}}}' + +# conf t +# vlan +# state active +# vn-segment +BODY_VXLAN_ACTIVE = '{"l2BD": {"attributes": {"adminSt": "active",' +BODY_VXLAN_ACTIVE += ' "accEncap": "vxlan-%s"}}}' + +# conf t +# int ethernet x/x OR int port-channel n +# where %s is "phys-[eth1/19]" OR "aggr-[po50]" +PATH_IF = 'api/mo/sys/intf/%s.json' +# THEN +# switchport trunk native vlan +# switchport trunk allowed vlan none | add | remove +# first %s is "l1PhysIf" | "pcAggrIf", 2nd trunkvlan string, 3rd one +# native vlan +BODY_TRUNKVLAN = '{"%s": {"attributes": {"trunkVlans": "%s"}}}' +BODY_NATIVE_TRUNKVLAN = '{"%s": {"attributes": {"trunkVlans": "%s",' +BODY_NATIVE_TRUNKVLAN += ' "nativeVlan": "%s"}}}' + +# conf t +# feature nv overlay +PATH_VXLAN_STATE = 'api/mo/sys/fm/nvo.json' +# where %s is "enable" | "disable" +BODY_VXLAN_STATE = '{"fmNvo": {"attributes": {"adminSt": "%s"}}}' + +# conf t +# feature vn-segment-vlan-based +PATH_VNSEG_STATE = 'api/mo/sys/fm/vnsegment.json' +BODY_VNSEG_STATE = '{"fmVnSegment": {"attributes": {"adminSt": "%s"}}}' + +# conf t +# int nve%s +# no shut +# source-interface loopback %s +PATH_NVE_CREATE = 'api/mo/sys/epId-%s.json' +BODY_NVE_CREATE = '{"nvoEp": {"attributes": {"epId": "%s"}}}' +BODY_NVE_ADD_LOOPBACK = '{"nvoEp": {"attributes": {"adminSt": "%s",' +BODY_NVE_ADD_LOOPBACK += ' "sourceInterface": "lo%s"}}}' + +# conf t +# int nve%s +# no shut +# source-interface loopback %s + +# conf t +# int nve%s +# [no] member vni %s mcast-group %s +PATH_VNI_UPDATE = 'api/mo/sys/epId-%s/nws/vni-%s.json' +BODY_VNI_UPDATE = '{"nvoNw": {"attributes": {"vni": "%s", "vniRangeMin": "%s",' +BODY_VNI_UPDATE += ' "vniRangeMax": "%s", "mcastGroup": "%s", "isMcastRange":' +BODY_VNI_UPDATE += ' "yes", "suppressARP": "no", "associateVrfFlag": "no"}}}' + +# channel-group x mode active is not immediately available beneath the +# ethernet interface data. Instead one needs to gather pc channel members +# and search for ethernet interface. +PATH_GET_PC_MEMBERS = 'api/mo/sys/intf.json?query-target=subtree&' +PATH_GET_PC_MEMBERS += 'target-subtree-class=pcRsMbrIfs' diff --git a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py index b5e1ce2..3cc1a41 100644 --- a/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py +++ b/networking_cisco/plugins/ml2/drivers/cisco/nexus/nexus_snippets.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2016 Cisco Systems, Inc. +# Copyright (c) 2013-2017 Cisco Systems, Inc. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -33,10 +33,6 @@ EXEC_GET_INTF_SNIPPET = """ show running-config interface %s %s """ -EXEC_GET_VERSION_SNIPPET = """ - show version -""" - EXEC_GET_INVENTORY_SNIPPET = """ show inventory """ diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_base.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_base.py index dab5d28..d73bf10 100644 --- a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_base.py +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016 Cisco Systems, Inc. +# Copyright (c) 2015-2017 Cisco Systems, Inc. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,13 +15,20 @@ """ Basic test Class and elements for testing Cisco Nexus platforms. + +Most classes in this file do not contain test cases but instead +provide common methods for other classes to utilize. This class +provides the basic methods needed to drive a create or delete +port request thru to the ssh or restapi driver. It verifies the +final content of the data base and verifies what data the +Drivers sent out. There also exists another 'base' class +specifically for Replay testing. """ import collections import mock import os from oslo_config import cfg -from oslo_utils import importutils import re import six import testtools @@ -31,6 +38,10 @@ from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( constants as const) from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( nexus_network_driver) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_network_driver) +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_snippets as rest_snipp) from networking_cisco.plugins.ml2.drivers.cisco.nexus import constants from networking_cisco.plugins.ml2.drivers.cisco.nexus import exceptions from networking_cisco.plugins.ml2.drivers.cisco.nexus import mech_cisco_nexus @@ -106,6 +117,55 @@ BAREMETAL_VNIC = u'baremetal' CONNECT_ERROR = 'Unable to connect to Nexus' +GET_NEXUS_TYPE_RESPONSE = { + "totalCount": "1", + "imdata": [ + { + "eqptCh": { + "attributes": { + "descr": "Nexus9000 C9396PX Chassis", + } + } + } + ] +} + +GET_INTERFACE_RESPONSE = { + "totalCount": "1", + "imdata": [ + { + "l1PhysIf": { + "attributes": { + "trunkVlans": "" + } + } + } + ] +} + +GET_INTERFACE_PCHAN_RESPONSE = { + "totalCount": "1", + "imdata": [ + { + "pcAggrIf": { + "attributes": { + "trunkVlans": "" + } + } + } + ] +} + + +GET_NO_PORT_CH_RESPONSE = { + "totalCount": "3", + "imdata": [ + ] +} + +POST = 0 +DELETE = 1 + ## Test snippets used to verify nexus command output RESULT_ADD_VLAN = """configure\>\s+\\s+\ \s+\<__XML__PARAM_value\>{0}""" @@ -147,9 +207,6 @@ RESULT_DEL_NATIVE_INTERFACE = """\ RESULT_DEL_NVE_INTERFACE = """\\s+\ \nve{0}\<\/nve\>\s+[\x20-\x7e]+\s+\no member vni {1}""" -NEXUS_DRIVER = ('networking_cisco.plugins.ml2.drivers.cisco.nexus.' - 'nexus_network_driver.CiscoNexusDriver') - class FakeNetworkContext(object): """Network context for testing purposes only.""" @@ -291,6 +348,19 @@ class FakeUnbindPortContext(FakePortContext): return self._bottom_segment +class TestCiscoNexusBaseResults(object): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = {} + + def get_test_results(self, name): + if name in self.test_results: + return self.test_results[name] + else: + return None + + class TestCiscoNexusBase(testlib_api.SqlTestCase): """Feature Base Test Class for Cisco ML2 Nexus driver.""" @@ -299,19 +369,45 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase): 'nexus_ip_addr host_name nexus_port instance_id vlan_id vxlan_id ' 'mcast_group device_owner profile vnic_type') - def _mock_init(self): - # this is to prevent interface initialization from occurring - # which adds unnecessary noise to the results. + def mock_init(self): + + # This initializes interface responses to prevent + # unnecessary noise to the results. data_xml = {'connect.return_value.get.return_value.data_xml': 'switchport trunk allowed vlan none'} self.mock_ncclient.configure_mock(**data_xml) + def restapi_mock_init(self): + + # This initializes RESTAPI responses to prevent + # unnecessary noise to the results. + + data_json = {'rest_get.side_effect': + self.get_side_effect} + self.mock_ncclient.configure_mock(**data_json) + + def get_side_effect(self, action, ipaddr=None, body=None, headers=None): + + eth_path = 'api/mo/sys/intf/phys-' + port_chan_path = 'api/mo/sys/intf/aggr-' + + if action == rest_snipp.PATH_GET_NEXUS_TYPE: + return GET_NEXUS_TYPE_RESPONSE + elif action in rest_snipp.PATH_GET_PC_MEMBERS: + return GET_NO_PORT_CH_RESPONSE + elif eth_path in action: + return GET_INTERFACE_RESPONSE + elif port_chan_path in action: + return GET_INTERFACE_PCHAN_RESPONSE + + return {} + def _clear_port_dbs(self): nexus_db_v2.remove_all_nexusport_bindings() def setUp(self): - """Sets up mock ncclient, and switch and credentials dictionaries.""" + """Sets up mock client, switch, and credentials dictionaries.""" super(TestCiscoNexusBase, self).setUp() @@ -320,20 +416,32 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase): cfg.CONF.import_opt('rpc_workers', 'neutron.service') cfg.CONF.set_default('rpc_workers', 0) - # Use a mock netconf client + # Use a mock netconf or REST API client self.mock_ncclient = mock.Mock() - mock.patch.object(nexus_network_driver.CiscoNexusDriver, - '_import_ncclient', - return_value=self.mock_ncclient).start() + if cfg.CONF.ml2_cisco.nexus_driver == 'restapi': + mock.patch.object( + nexus_restapi_network_driver.CiscoNexusRestapiDriver, + '_import_client', + return_value=self.mock_ncclient).start() + self._verify_results = self._verify_restapi_results + else: + mock.patch.object( + nexus_network_driver.CiscoNexusSshDriver, + '_import_client', + return_value=self.mock_ncclient).start() + self._verify_results = self._verify_ssh_results self.mock_continue_binding = mock.patch.object( FakePortContext, 'continue_binding').start() - self._mock_init() + if cfg.CONF.ml2_cisco.nexus_driver == 'restapi': + self.restapi_mock_init() + else: + self.mock_init() def new_nexus_init(mech_instance): - mech_instance.driver = importutils.import_object(NEXUS_DRIVER) + mech_instance.driver = mech_instance._load_nexus_cfg_driver() mech_instance.monitor_timeout = ( cfg.CONF.ml2_cisco.switch_heartbeat_time) mech_instance._ppid = os.getpid() @@ -471,6 +579,57 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase): port_config.instance_id) self.assertEqual(1, len(bindings)) + def _verify_restapi_results(self, driver_result): + """Verifies correct entries sent to Nexus.""" + + posts = 0 + deletes = 0 + for idx in range(0, len(driver_result)): + if driver_result[idx][3] == POST: + posts += 1 + else: + deletes += 1 + self.assertEqual( + posts, + len(self.mock_ncclient.rest_post.mock_calls), + "Unexpected driver post calls") + self.assertEqual( + deletes, + len(self.mock_ncclient.rest_delete.mock_calls), + "Unexpected driver delete calls") + + post_calls = self.mock_ncclient.rest_post.mock_calls + del_calls = self.mock_ncclient.rest_delete.mock_calls + posts = 0 + deletes = 0 + for idx in range(0, len(driver_result)): + # assigned None to skip testing this one. + if not driver_result[idx]: + continue + if driver_result[idx][3] == POST: + test_it = post_calls[posts][1] + else: + test_it = del_calls[deletes][1] + self.assertTrue( + (driver_result[idx][0] == + test_it[0]), + "Expected Rest URI does not match") + + if driver_result[idx][1] is not None: + self.assertTrue( + (driver_result[idx][1] == + test_it[1]), + "Expected Nexus Switch ip does not match") + + if driver_result[idx][3] == POST: + self.assertTrue( + (driver_result[idx][2] == + test_it[2]), + "Expected Rest Body does not match") + posts += 1 + else: + deletes += 1 + def _delete_port(self, port_config): """Tests deletion of a virtual port.""" port_context = self._generate_port_context(port_config) @@ -498,7 +657,7 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase): port_config.nexus_ip_addr, port_config.instance_id) - def _verify_results(self, driver_result): + def _verify_ssh_results(self, driver_result): """Verifies correct entries sent to Nexus.""" self.assertEqual( diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events.py index c133545..7a4023a 100644 --- a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events.py +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events.py @@ -34,7 +34,7 @@ CONNECT_ERROR = 'Unable to connect to Nexus' class TestCiscoNexusDeviceConfig(object): - """Unit tests for Cisco ML2 Nexus device driver.""" + """Unit tests Config for Cisco ML2 Nexus device driver.""" test_configs = { 'test_config1': @@ -185,20 +185,105 @@ class TestCiscoNexusDeviceConfig(object): test_configs = collections.OrderedDict(sorted(test_configs.items())) - # The following contains desired Nexus output for some basic config above. - duplicate_add_port_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267)]) - duplicate_delete_port_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) +class TestCiscoNexusDeviceResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + 'duplicate_add_port_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'duplicate_del_port_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'add_port2_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 265)]), + + 'delete_port2_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/20', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'add_port2_driver_result2': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'delete_port2_driver_result2': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'add_port2_driver_result3': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('portchannel', '2', 268), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('portchannel', '2', 268)]), + + 'delete_port2_driver_result3': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('portchannel', '2', 268), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(268), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('portchannel', '2', 268), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(268)]), + + 'add_port_channel_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('portchannel', '2', 268)]), + + 'delete_port_channel_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('portchannel', '2', 268), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(268)]), + + 'dual_add_port_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/3', 269), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('portchannel', '2', 269)]), + + 'dual_delete_port_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/3', 269), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(269), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('portchannel', '2', 269)]), + + 'migrate_add_host2_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'add_port_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'del_port_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + } class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, - TestCiscoNexusDeviceConfig): + TestCiscoNexusDeviceConfig, + TestCiscoNexusDeviceResults): """Unit tests for Cisco ML2 Nexus device driver.""" @@ -208,13 +293,15 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') super(TestCiscoNexusDevice, self).setUp() self.mock_ncclient.reset_mock() + self.results = TestCiscoNexusDeviceResults() def test_create_delete_duplicate_ports(self): """Tests creation and deletion of two new virtual Ports.""" self._basic_create_verify_port_vlan( 'test_config1', - self.duplicate_add_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result') + ) self._create_port( self.test_configs['test_config3']) @@ -245,14 +332,15 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_config3', - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_del_port_driver_result')) def test_create_delete_duplicate_port_transaction(self): """Tests creation and deletion same port transaction.""" self._basic_create_verify_port_vlan( 'test_config1', - self.duplicate_add_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result') + ) self.assertEqual( 1, len(nexus_db_v2.get_nexusport_switch_bindings( @@ -260,7 +348,9 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._create_port( self.test_configs['test_config1']) - self._verify_results(self.duplicate_add_port_driver_result) + self._verify_results( + self.results.get_test_results('duplicate_add_port_driver_result') + ) self.assertEqual( 1, len(nexus_db_v2.get_nexusport_switch_bindings( @@ -272,32 +362,24 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_config1', - self.duplicate_delete_port_driver_result, + self.results.get_test_results('duplicate_del_port_driver_result'), nbr_of_bindings=0) self._basic_delete_verify_port_vlan( 'test_config1', - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_del_port_driver_result')) def test_create_delete_same_switch_diff_hosts_diff_vlan(self): """Test create/delete two Ports, same switch/diff host & vlan.""" - add_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 265)]) - delete_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/20', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - self._basic_create_verify_port_vlan( 'test_config1', - self.duplicate_add_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result')) self._create_port( self.test_configs['test_config2']) - self._verify_results(add_port2_driver_result) + self._verify_results( + self.results.get_test_results('add_port2_driver_result')) # Verify there are 2 port configs bindings = nexus_db_v2.get_nexusport_switch_bindings( @@ -310,31 +392,24 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_config2', - delete_port2_driver_result, + self.results.get_test_results('delete_port2_driver_result'), nbr_of_bindings=1) self._basic_delete_verify_port_vlan( 'test_config1', - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_del_port_driver_result')) def test_create_delete_same_switch_diff_hosts_same_vlan(self): """Test create/delete two Ports, same switch & vlan/diff host.""" - add_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267)]) - delete_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/20', 267)]) - self._basic_create_verify_port_vlan( 'test_config4', - self.duplicate_add_port_driver_result) + self.results.get_test_results('add_port_driver_result')) self._create_port( self.test_configs['test_config5']) - self._verify_results(add_port2_driver_result) + self._verify_results( + self.results.get_test_results('add_port2_driver_result2')) # Verify there are 2 port configs bindings = nexus_db_v2.get_nexusport_switch_bindings( @@ -347,34 +422,19 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_config5', - delete_port2_driver_result, + self.results.get_test_results('delete_port2_driver_result2'), nbr_of_bindings=1) self._basic_delete_verify_port_vlan( 'test_config4', - self.duplicate_delete_port_driver_result) + self.results.get_test_results('del_port_driver_result')) def test_create_delete_diff_switch_same_host(self): """Test create/delete of two Ports, diff switch/same host.""" - add_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('portchannel', '2', 268), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('portchannel', '2', 268)]) - delete_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('portchannel', '2', 268), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(268), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('portchannel', '2', 268), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(268)]) - self._basic_create_verify_port_vlan( 'test_config_portchannel2', - add_port2_driver_result) + self.results.get_test_results('add_port2_driver_result3')) # Verify there are 2 port configs. One per switch. bindings = nexus_db_v2.get_nexusport_switch_bindings( @@ -392,60 +452,35 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, # first port removal. self._basic_delete_verify_port_vlan( 'test_config_portchannel2', - delete_port2_driver_result) + self.results.get_test_results('delete_port2_driver_result3')) def test_create_delete_portchannel(self): """Tests creation of a port over a portchannel.""" - duplicate_add_port_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(268), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('portchannel', '2', 268)]) - - duplicate_delete_port_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('portchannel', '2', 268), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(268)]) - self._create_delete_port( 'test_config_portchannel', - duplicate_add_port_driver_result, - duplicate_delete_port_driver_result) + self.results.get_test_results('add_port_channel_driver_result'), + self.results.get_test_results('delete_port_channel_driver_result')) def test_create_delete_dual(self): """Tests creation and deletion of dual ports for single server""" - duplicate_add_port_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/3', 269), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('portchannel', '2', 269)]) - - duplicate_delete_port_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/3', 269), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(269), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('portchannel', '2', 269)]) - self._basic_create_verify_port_vlan( 'test_config_dual', - duplicate_add_port_driver_result, + self.results.get_test_results('dual_add_port_driver_result'), nbr_of_bindings=2) self._basic_delete_verify_port_vlan( 'test_config_dual', - duplicate_delete_port_driver_result) + self.results.get_test_results('dual_delete_port_driver_result')) def test_create_delete_dhcp(self): """Tests creation and deletion of ports with device_owner of dhcp.""" self._create_delete_port( 'test_config_dhcp', - self.duplicate_add_port_driver_result, - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result'), + self.results.get_test_results('duplicate_del_port_driver_result')) def test_create_delete_router_ha_intf(self): """Tests creation and deletion of ports with device_owner @@ -454,8 +489,95 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._create_delete_port( 'test_config_router_ha_intf', - self.duplicate_add_port_driver_result, - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result'), + self.results.get_test_results('duplicate_del_port_driver_result')) + + def test_nexus_vm_migration(self): + """Verify VM (live) migration. + + Simulate the following: + Nova informs neutron of live-migration with port-update(new host). + This should trigger two update_port_pre/postcommit() calls. + + The first one should only change the current host_id and remove the + binding resulting in the mechanism drivers receiving: + PortContext.original['binding:host_id']: previous value + PortContext.original_top_bound_segment: previous value + PortContext.current['binding:host_id']: current (new) value + PortContext.top_bound_segment: None + + The second one binds the new host resulting in the mechanism + drivers receiving: + PortContext.original['binding:host_id']: previous value + PortContext.original_top_bound_segment: None + PortContext.current['binding:host_id']: previous value + PortContext.top_bound_segment: new value + """ + + self._basic_create_verify_port_vlan( + 'test_config1', + self.results.get_test_results('duplicate_add_port_driver_result')) + binding = nexus_db_v2.get_nexusvm_bindings( + test_cisco_nexus_base.VLAN_ID_1, + test_cisco_nexus_base.INSTANCE_1)[0] + self.assertEqual( + test_cisco_nexus_base.NEXUS_PORT_1, + binding.port_id) + + port_context = self._generate_port_context( + self.test_configs['test_config_migrate'], + unbind_port=True) + port_cfg = self.test_configs['test_config1'] + port_context.set_orig_port( + port_cfg.instance_id, + port_cfg.host_name, + port_cfg.device_owner, + port_cfg.profile, + port_cfg.vnic_type, + test_cisco_nexus_base.NETID) + + self._cisco_mech_driver.create_port_postcommit(port_context) + self._cisco_mech_driver.update_port_precommit(port_context) + self._cisco_mech_driver.update_port_postcommit(port_context) + + # Verify that port entry has been deleted. + self.assertRaises( + exceptions.NexusPortBindingNotFound, + nexus_db_v2.get_nexusvm_bindings, + test_cisco_nexus_base.VLAN_ID_1, + test_cisco_nexus_base.INSTANCE_1) + + # Clean all the ncclient mock_calls to clear exception + # and other mock_call history. + self.mock_ncclient.reset_mock() + + self._basic_create_verify_port_vlan( + 'test_config_migrate', + self.results.get_test_results('migrate_add_host2_driver_result')) + + # Verify that port entry has been added using new host name. + # Use port_id to verify that 2nd host name was used. + binding = nexus_db_v2.get_nexusvm_bindings( + test_cisco_nexus_base.VLAN_ID_1, + test_cisco_nexus_base.INSTANCE_1)[0] + self.assertEqual( + test_cisco_nexus_base.NEXUS_PORT_2, + binding.port_id) + + +class TestCiscoNexusDeviceFailure(test_cisco_nexus_base.TestCiscoNexusBase, + TestCiscoNexusDeviceConfig, + TestCiscoNexusDeviceResults): + + """Negative Unit tests for Cisco ML2 Nexus device driver.""" + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusDeviceFailure, self).setUp() + self.mock_ncclient.reset_mock() + self.results = TestCiscoNexusDeviceResults() def test_connect_failure(self): """Verifies exception handling during ncclient connect. """ @@ -743,7 +865,7 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, # Mock to keep track of number of close_session calls. ncclient_close = mock.patch.object( - nexus_network_driver.CiscoNexusDriver, + nexus_network_driver.CiscoNexusSshDriver, '_close_session').start() # Clear out call_count changes during initialization activity @@ -752,15 +874,15 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, # Verify that ncclient is not closed by default. self._basic_create_verify_port_vlan( 'test_config1', - self.duplicate_add_port_driver_result) + self.results.get_test_results('duplicate_add_port_driver_result')) assert not ncclient_close.called self._basic_delete_verify_port_vlan( 'test_config1', - self.duplicate_delete_port_driver_result) + self.results.get_test_results('duplicate_del_port_driver_result')) # Patch to close ncclient session. - mock.patch.object(nexus_network_driver.CiscoNexusDriver, + mock.patch.object(nexus_network_driver.CiscoNexusSshDriver, '_get_close_ssh_session', return_value=True).start() @@ -770,7 +892,8 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, # after trunk interface calls. self._basic_create_verify_port_vlan( 'test_config1', - self.duplicate_add_port_driver_result) + self.results.get_test_results( + 'duplicate_add_port_driver_result')) self.assertEqual(2, ncclient_close.call_count) def test_nexus_extended_vlan_range_failure(self): @@ -801,88 +924,53 @@ class TestCiscoNexusDevice(test_cisco_nexus_base.TestCiscoNexusBase, # is called in _create_port_valid_exception and caching enabled self.assertEqual(0, self.mock_ncclient.connect.call_count) - def test_nexus_vm_migration(self): - """Verify VM (live) migration. - Simulate the following: - Nova informs neutron of live-migration with port-update(new host). - This should trigger two update_port_pre/postcommit() calls. +class TestCiscoNexusInitResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): - The first one should only change the current host_id and remove the - binding resulting in the mechanism drivers receiving: - PortContext.original['binding:host_id']: previous value - PortContext.original_top_bound_segment: previous value - PortContext.current['binding:host_id']: current (new) value - PortContext.top_bound_segment: None + """Unit tests driver results for Cisco ML2 Nexus.""" - The second one binds the new host resulting in the mechanism - drivers receiving: - PortContext.original['binding:host_id']: previous value - PortContext.original_top_bound_segment: None - PortContext.current['binding:host_id']: previous value - PortContext.top_bound_segment: new value - """ - migrate_add_host2_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267)]) + test_results = { + # set 1 - switch 1.1.1.1 sets eth 1/10 & 1/20 to None + # set 2 - switch 8.8.8.8 sets eth 1/10 & 1/20 to None + # set 3 - switch 4.4.4.4 sets eth 1/3 & portchannel 2 to None + # set 4 - switch 2.2.2.2 sets portchannel 2 to None + # set 5 - switch 6.6.6.6 sets portchannel 2 to None + # set 6 - switch 7.7.7.7 sets portchannel 2 to None + 'duplicate_init_port_driver_result1': ( + [test_cisco_nexus_base.RESULT_INTERFACE. + format('ethernet', '1\/10', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('ethernet', '1\/10', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('ethernet', '1\/3', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('portchannel', '2', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('portchannel', '2', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('portchannel', '2', 'None')]), - self._basic_create_verify_port_vlan( - 'test_config1', - self.duplicate_add_port_driver_result) - binding = nexus_db_v2.get_nexusvm_bindings( - test_cisco_nexus_base.VLAN_ID_1, - test_cisco_nexus_base.INSTANCE_1)[0] - self.assertEqual( - test_cisco_nexus_base.NEXUS_PORT_1, - binding.port_id) - - port_context = self._generate_port_context( - self.test_configs['test_config_migrate'], - unbind_port=True) - port_cfg = self.test_configs['test_config1'] - port_context.set_orig_port( - port_cfg.instance_id, - port_cfg.host_name, - port_cfg.device_owner, - port_cfg.profile, - port_cfg.vnic_type, - test_cisco_nexus_base.NETID) - - self._cisco_mech_driver.create_port_postcommit(port_context) - self._cisco_mech_driver.update_port_precommit(port_context) - self._cisco_mech_driver.update_port_postcommit(port_context) - - # Verify that port entry has been deleted. - self.assertRaises( - exceptions.NexusPortBindingNotFound, - nexus_db_v2.get_nexusvm_bindings, - test_cisco_nexus_base.VLAN_ID_1, - test_cisco_nexus_base.INSTANCE_1) - - # Clean all the ncclient mock_calls to clear exception - # and other mock_call history. - self.mock_ncclient.reset_mock() - - self._basic_create_verify_port_vlan( - 'test_config_migrate', - migrate_add_host2_driver_result) - - # Verify that port entry has been added using new host name. - # Use port_id to verify that 2nd host name was used. - binding = nexus_db_v2.get_nexusvm_bindings( - test_cisco_nexus_base.VLAN_ID_1, - test_cisco_nexus_base.INSTANCE_1)[0] - self.assertEqual( - test_cisco_nexus_base.NEXUS_PORT_2, - binding.port_id) + # Only one entry to match for last 3 so make None + # so count matches in _verify_results + 'duplicate_init_port_driver_result2': ( + [test_cisco_nexus_base.RESULT_INTERFACE. + format('ethernet', '1\/20', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('ethernet', '1\/20', 'None'), + test_cisco_nexus_base.RESULT_INTERFACE. + format('portchannel', '2', 'None'), + None, + None, + None]) + } class TestCiscoNexusDeviceInit(test_cisco_nexus_base.TestCiscoNexusBase, TestCiscoNexusDeviceConfig): """Verifies interface vlan allowed none is set when missing.""" - def _mock_init(self): + def mock_init(self): # Prevent default which returns # 'switchport trunk allowed vlan none' # in get interface calls so Nexus driver @@ -896,44 +984,60 @@ class TestCiscoNexusDeviceInit(test_cisco_nexus_base.TestCiscoNexusBase, cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') super(TestCiscoNexusDeviceInit, self).setUp() + self.results = TestCiscoNexusInitResults() def test_verify_initialization(self): - # set 1 - switch 1.1.1.1 sets eth 1/10 & 1/20 to None - # set 2 - switch 8.8.8.8 sets eth 1/10 & 1/20 to None - # set 3 - switch 4.4.4.4 sets eth 1/3 & portchannel 2 to None - # set 4 - switch 2.2.2.2 sets portchannel 2 to None - # set 5 - switch 6.6.6.6 sets portchannel 2 to None - # set 6 - switch 7.7.7.7 sets portchannel 2 to None - duplicate_init_port_driver_result1 = ( - [test_cisco_nexus_base.RESULT_INTERFACE. - format('ethernet', '1\/10', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('ethernet', '1\/10', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('ethernet', '1\/3', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('portchannel', '2', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('portchannel', '2', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('portchannel', '2', 'None')]) + self._verify_results( + self.results.get_test_results( + 'duplicate_init_port_driver_result1')) + self._verify_results( + self.results.get_test_results( + 'duplicate_init_port_driver_result2')) - # Only one entry to match for last 3 so make None - # so count matches in _verify_results - duplicate_init_port_driver_result2 = ( - [test_cisco_nexus_base.RESULT_INTERFACE. - format('ethernet', '1\/20', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('ethernet', '1\/20', 'None'), - test_cisco_nexus_base.RESULT_INTERFACE. - format('portchannel', '2', 'None'), - None, - None, - None]) - self._verify_results(duplicate_init_port_driver_result1) - self._verify_results(duplicate_init_port_driver_result2) +class TestCiscoNexusBaremetalResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + 'add_port_ethernet_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'delete_port_ethernet_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'add_port_channel_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '469', 267)]), + + 'delete_port_channel_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('port-channel', '469', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'add_port_ethernet_native_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + (test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. + format('ethernet', '1\/10', 265) + + '[\x00-\x7f]+' + + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 265))]), + + 'delete_port_ethernet_native_driver_result': ( + [(test_cisco_nexus_base.RESULT_DEL_NATIVE_INTERFACE. + format('ethernet', '1\/10') + + '[\x00-\x7f]+' + + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 265)), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) + } class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): @@ -995,69 +1099,14 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): test_cisco_nexus_base.BAREMETAL_VNIC), } - simple_add_port_ethernet_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267)]) - - simple_delete_port_ethernet_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - - simple_add_port_channel_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '469', 267)]) - - simple_delete_port_channel_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('port-channel', '469', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - - simple_add_port_ethernet_native_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - (test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. - format('ethernet', '1\/10', 265) + - '[\x00-\x7f]+' + - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 265))]) - - simple_delete_port_ethernet_native_driver_result = ( - [(test_cisco_nexus_base.RESULT_DEL_NATIVE_INTERFACE. - format('ethernet', '1\/10') + - '[\x00-\x7f]+' + - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 265)), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - def setUp(self): """Sets up mock ncclient, and switch and credentials dictionaries.""" cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') super(TestCiscoNexusBaremetalDevice, self).setUp() + self.results = TestCiscoNexusBaremetalResults() - def test_create_delete_basic_ethernet_port(self): - """Basic creation and deletion test of 1 ethernet port.""" - - # nbr_of_bindings includes reserved port binding - self._basic_create_verify_port_vlan( - 'test_config1', - self.simple_add_port_ethernet_driver_result, 2) - - # Clean all the ncclient mock_calls so we can evaluate - # results of delete operations. - self.mock_ncclient.reset_mock() - - # nbr_of_bindings includes reserved port binding - self._basic_delete_verify_port_vlan( - 'test_config1', - self.simple_delete_port_ethernet_driver_result, - nbr_of_bindings=1) - - def test_create_delete_basic_port_channel(self): - """Basic creation and deletion test of 1 port-channel.""" - + def _init_port_channel(self): # this is to prevent interface initialization from occurring # which adds unnecessary noise to the results. data_xml = {'connect.return_value.get.return_value.data_xml': @@ -1065,10 +1114,15 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): 'channel-group 469 mode active'} self.mock_ncclient.configure_mock(**data_xml) + def test_create_delete_basic_ethernet_port(self): + """Basic creation and deletion test of 1 ethernet port.""" + # nbr_of_bindings includes reserved port binding self._basic_create_verify_port_vlan( 'test_config1', - self.simple_add_port_channel_driver_result, 2) + self.results.get_test_results( + 'add_port_ethernet_driver_result'), + 2) # Clean all the ncclient mock_calls so we can evaluate # results of delete operations. @@ -1077,7 +1131,30 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): # nbr_of_bindings includes reserved port binding self._basic_delete_verify_port_vlan( 'test_config1', - self.simple_delete_port_channel_driver_result, + self.results.get_test_results( + 'delete_port_ethernet_driver_result'), + nbr_of_bindings=1) + + def test_create_delete_basic_port_channel(self): + """Basic creation and deletion test of 1 port-channel.""" + + self._init_port_channel() + # nbr_of_bindings includes reserved port binding + self._basic_create_verify_port_vlan( + 'test_config1', + self.results.get_test_results( + 'add_port_channel_driver_result'), + 2) + + # Clean all the ncclient mock_calls so we can evaluate + # results of delete operations. + self.mock_ncclient.reset_mock() + + # nbr_of_bindings includes reserved port binding + self._basic_delete_verify_port_vlan( + 'test_config1', + self.results.get_test_results( + 'delete_port_channel_driver_result'), nbr_of_bindings=1) def test_create_delete_basic_eth_port_is_native(self): @@ -1086,7 +1163,9 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): # nbr_of_bindings includes reserved port binding self._basic_create_verify_port_vlan( 'test_config_native', - self.simple_add_port_ethernet_native_driver_result, 2) + self.results.get_test_results( + 'add_port_ethernet_native_driver_result'), + 2) # Clean all the ncclient mock_calls so we can evaluate # results of delete operations. @@ -1095,7 +1174,8 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): # nbr_of_bindings includes reserved port binding self._basic_delete_verify_port_vlan( 'test_config_native', - self.simple_delete_port_ethernet_native_driver_result, + self.results.get_test_results( + 'delete_port_ethernet_native_driver_result'), nbr_of_bindings=1) def test_create_delete_switch_ip_not_defined(self): @@ -1140,7 +1220,8 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): # nbr_of_bindings includes reserved port binding self._basic_create_verify_port_vlan( '', - self.simple_add_port_ethernet_driver_result, 2, + self.results.get_test_results( + 'add_port_ethernet_driver_result'), 2, other_test=local_test_configs['test_config1']) # Clean all the ncclient mock_calls so we can evaluate @@ -1150,7 +1231,8 @@ class TestCiscoNexusBaremetalDevice(test_cisco_nexus_base.TestCiscoNexusBase): # nbr_of_bindings includes reserved port binding self._basic_delete_verify_port_vlan( '', - self.simple_delete_port_ethernet_driver_result, + self.results.get_test_results( + 'delete_port_ethernet_driver_result'), nbr_of_bindings=1, other_test=local_test_configs['test_config1']) diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events_vxlan.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events_vxlan.py index 354e38f..83884e9 100644 --- a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events_vxlan.py +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_events_vxlan.py @@ -115,21 +115,89 @@ class TestCiscoNexusVxlanDeviceConfig(object): test_configs = collections.OrderedDict(sorted(test_configs.items())) - # The following contains desired Nexus output for some basic config above. - add_port_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. - format(1, 70000, '255.1.1.1'), - test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(267, 70000), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267)]) - delete_port_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. - format(1, 70000, 267), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) +class TestCiscoNexusVxlanResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + # The following contains desired Nexus output for + # some basic config above. + 'add_port_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. + format(1, 70000, '255.1.1.1'), + test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(267, 70000), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'delete_port_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. + format(1, 70000, 267), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'add_port2_driver_result': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(267, 70000), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'delete_port2_driver_result': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'add_port_driver_result3': ( + [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. + format(1, 70000, '255.1.1.1'), + test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. + format(1, 70000, '255.1.1.1'), + test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(267, 70000), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(267, 70000), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/2', 267), + test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(267, 70000), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/3', 267)]), + + 'delete_port_driver_result3': ( + [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. + format(1, 70000, 267), + test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. + format(1, 70000, 267), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/2', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/3', 267)]), + + 'add_port_driver_result2': ( + [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. + format(1, 70001, '255.1.1.1'), + test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. + format(265, 70001), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 265)]), + + 'delete_port_driver_result2': ( + [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. + format(1, 70001, 265), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/20', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) + + } class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, @@ -144,6 +212,7 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, super(TestCiscoNexusVxlanDevice, self).setUp() self.mock_ncclient.reset_mock() self.addCleanup(self._clear_nve_db) + self.results = TestCiscoNexusVxlanResults() def _clear_nve_db(self): nexus_db_v2.remove_all_nexusnve_bindings() @@ -193,23 +262,16 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, def test_nexus_vxlan_one_network_two_hosts(self): """Tests creation and deletion of two new virtual Ports.""" - add_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(267, 70000), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267)]) - - delete_port2_driver_result = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/20', 267)]) - self._basic_create_verify_port_vlan( 'test_vxlan_config1', - self.add_port_driver_result) + self.results.get_test_results( + 'add_port_driver_result')) self._create_port( self.test_configs['test_vxlan_config2']) - self._verify_results(add_port2_driver_result) + self._verify_results( + self.results.get_test_results( + 'add_port2_driver_result')) bindings = nexus_db_v2.get_nexusvlan_binding( test_cisco_nexus_base.VLAN_ID_1, @@ -222,11 +284,14 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_vxlan_config2', - delete_port2_driver_result, nbr_of_bindings=1) + self.results.get_test_results( + 'delete_port2_driver_result'), + nbr_of_bindings=1) self._basic_delete_verify_port_vlan( 'test_vxlan_config1', - self.delete_port_driver_result) + self.results.get_test_results( + 'delete_port_driver_result')) def test_nexus_missing_vxlan_fields(self): """Test handling of a VXLAN NexusMissingRequiredFields exception. @@ -324,44 +389,13 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, def test_nexus_vxlan_one_network(self): """Test processing for creating one VXLAN segment.""" - add_port_driver_result3 = ( - [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. - format(1, 70000, '255.1.1.1'), - test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. - format(1, 70000, '255.1.1.1'), - test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(267, 70000), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(267, 70000), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/2', 267), - test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(267, 70000), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/3', 267)]) - - delete_port_driver_result3 = ( - [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. - format(1, 70000, 267), - test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. - format(1, 70000, 267), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/2', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/3', 267)]) - # Since test_vxlan_config3 & test_vxlan_config4 share # the same host name they both get processed in the # next call. self._basic_create_verify_port_vlan( 'test_vxlan_config3', - add_port_driver_result3) + self.results.get_test_results( + 'add_port_driver_result3')) for switch_ip, nbr_bind in [ (test_cisco_nexus_base.NEXUS_IP_ADDRESS_1, 1), @@ -378,7 +412,8 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, # next call. self._basic_delete_verify_port_vlan( 'test_vxlan_config3', - delete_port_driver_result3) + self.results.get_test_results( + 'delete_port_driver_result3')) for switch_ip in [ test_cisco_nexus_base.NEXUS_IP_ADDRESS_1, @@ -399,29 +434,17 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, def test_nexus_vxlan_two_network(self): """Test processing for creating one VXLAN segment.""" - add_port_driver_result2 = ( - [test_cisco_nexus_base.RESULT_ADD_NVE_INTERFACE. - format(1, 70001, '255.1.1.1'), - test_cisco_nexus_base.RESULT_ADD_VLAN_VNI. - format(265, 70001), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 265)]) - - delete_port_driver_result2 = ( - [test_cisco_nexus_base.RESULT_DEL_NVE_INTERFACE. - format(1, 70001, 265), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/20', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - self._basic_create_verify_port_vlan( 'test_vxlan_config5', - self.add_port_driver_result) + self.results.get_test_results( + 'add_port_driver_result')) self._create_port( self.test_configs['test_vxlan_config6'], override_netid=888) - self._verify_results(add_port_driver_result2) + self._verify_results( + self.results.get_test_results( + 'add_port_driver_result2')) binding = nexus_db_v2.get_nve_switch_bindings( test_cisco_nexus_base.NEXUS_IP_ADDRESS_1) @@ -433,11 +456,14 @@ class TestCiscoNexusVxlanDevice(test_cisco_nexus_base.TestCiscoNexusBase, self._basic_delete_verify_port_vlan( 'test_vxlan_config6', - delete_port_driver_result2, nbr_of_bindings=1) + self.results.get_test_results( + 'delete_port_driver_result2'), + nbr_of_bindings=1) self._basic_delete_verify_port_vlan( 'test_vxlan_config5', - self.delete_port_driver_result) + self.results.get_test_results( + 'delete_port_driver_result')) try: binding = nexus_db_v2.get_nve_switch_bindings( diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_replay.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_replay.py index 3f93a9e..2ccb0b6 100644 --- a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_replay.py +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_replay.py @@ -31,6 +31,106 @@ RP_HOST_NAME_DUAL = 'testdualhost' MAX_REPLAY_COUNT = 4 +class TestCiscoNexusReplayResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + 'driver_result_unique_init': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 'None')]), + + 'driver_result_unique_add1': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'driver_result_unique_add2': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 265)]), + + 'driver_result_unique_del1': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'driver_result_unique_del2': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'driver_result_unique_2vlan_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', '265,267'), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]), + + 'dupl_vlan_result1_add': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'dupl_vlan_result2_add': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'dupl_vlan_result2_del': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/20', 267)]), + + 'dupl_vlan_result_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/20', 267), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(267)]), + + 'dupl_port_result_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(267)]), + + 'switch_up_result_add': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/3', 269)]), + + 'switch_up_result_del': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/3', 269), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(269)]), + + 'switch_restore_result_add': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/3', 269)]), + + 'switch_restore_result_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/2', 269), + test_cisco_nexus_base.RESULT_ADD_VLAN.format(269)]), + + 'switch_restore_result_del': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/3', 269), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(269), + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/2', 269), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(269)]), + + } + + class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): """Unit tests for Replay of Cisco ML2 Nexus data.""" test_configs = { @@ -143,30 +243,6 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): {}, test_cisco_nexus_base.NORMAL_VNIC), } - driver_result_unique_init = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 'None')]) - - driver_result_unique_add1 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267)]) - - driver_result_unique_add2 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 265)]) - - driver_result_unique_del1 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - - driver_result_unique_del2 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - test_configs = collections.OrderedDict(sorted(test_configs.items())) def setUp(self): @@ -174,117 +250,103 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') super(TestCiscoNexusReplay, self).setUp() + self.results = TestCiscoNexusReplayResults() def test_replay_unique_ports(self): """Provides replay data and result data for unique ports. """ - first_add = {'driver_results': self. - driver_result_unique_add1, - 'nbr_db_entries': 1} - second_add = {'driver_results': self. - driver_result_unique_add2, - 'nbr_db_entries': 2} - first_del = {'driver_results': self. - driver_result_unique_del1, - 'nbr_db_entries': 1} - second_del = {'driver_results': self. - driver_result_unique_del2, - 'nbr_db_entries': 0} - driver_result_unique_2vlan_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', '265,267'), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_add1'), + 'nbr_db_entries': 1} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_add2'), + 'nbr_db_entries': 2} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_del1'), + 'nbr_db_entries': 1} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_del2'), + 'nbr_db_entries': 0} self._process_replay( 'test_replay_unique1', 'test_replay_unique2', - self.driver_result_unique_init, + self.results.get_test_results( + 'driver_result_unique_init'), first_add, second_add, - driver_result_unique_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_2vlan_replay'), first_del, second_del) def test_replay_duplicate_vlan(self): """Provides replay data and result data for duplicate vlans. """ - result1_add = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267)]) - first_add = {'driver_results': result1_add, - 'nbr_db_entries': 2} + first_add = { + 'driver_results': self.results.get_test_results( + 'dupl_vlan_result1_add'), + 'nbr_db_entries': 2} - result2_add = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267)]) # TODO(caboucha) # 'driver_result': [], until the correct fix for # the following issue is resolved. # https://review.openstack.org/#/c/241216/ - second_add = {'driver_results': result2_add, - 'nbr_db_entries': 4} + second_add = { + 'driver_results': self.results.get_test_results( + 'dupl_vlan_result2_add'), + 'nbr_db_entries': 4} first_del = {'driver_results': [], 'nbr_db_entries': 2} - result2_del = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/20', 267)]) - second_del = {'driver_results': result2_del, - 'nbr_db_entries': 0} - - result_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/20', 267), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(267)]) + second_del = { + 'driver_results': self.results.get_test_results( + 'dupl_vlan_result2_del'), + 'nbr_db_entries': 0} self._process_replay('test_replay_duplvlan1', 'test_replay_duplvlan2', [], first_add, second_add, - result_replay, + self.results.get_test_results( + 'dupl_vlan_result_replay'), first_del, second_del) def test_replay_duplicate_ports(self): """Provides replay data and result data for duplicate ports. """ - first_add = {'driver_results': self.driver_result_unique_add1, - 'nbr_db_entries': 1} + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_add1'), + 'nbr_db_entries': 1} # TODO(caboucha) # 'driver_result': [], until the correct fix for # the following issue is resolved. # https://review.openstack.org/#/c/241216/ - second_add = {'driver_results': self.driver_result_unique_add1, - 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_add1'), + 'nbr_db_entries': 2} first_del = {'driver_results': [], 'nbr_db_entries': 1} - second_del = {'driver_results': self.driver_result_unique_del2, - 'nbr_db_entries': 0} - - result_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(267)]) + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_del2'), + 'nbr_db_entries': 0} self._process_replay('test_replay_duplport1', 'test_replay_duplport2', [], first_add, second_add, - result_replay, + self.results.get_test_results( + 'dupl_port_result_replay'), first_del, second_del) def test_replay_enable_vxlan_feature_failure(self): @@ -378,12 +440,9 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): port_cfg2.nexus_ip_addr, const.SWITCH_INACTIVE) # Set-up successful creation of port vlan config - result_add = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/3', 269)]) self._basic_create_verify_port_vlan('test_replay_dual', - result_add, + self.results.get_test_results( + 'switch_up_result_add'), nbr_of_bindings=1) # Even though 2nd entry is inactive, there should be @@ -394,12 +453,9 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): port_cfg2.nexus_ip_addr))) # Clean-up the port entry - result_del = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/3', 269), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(269)]) self._basic_delete_verify_port_vlan('test_replay_dual', - result_del, + self.results.get_test_results( + 'switch_up_result_del'), nbr_of_bindings=0) def test_replay_port_success_if_one_switch_restored(self): @@ -417,12 +473,9 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): port_cfg2.nexus_ip_addr, const.SWITCH_INACTIVE) # Set-up successful creation of port vlan config - result_add = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(269), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/3', 269)]) self._basic_create_verify_port_vlan('test_replay_dual', - result_add, + self.results.get_test_results( + 'switch_restore_result_add'), nbr_of_bindings=1) # Even though 2nd entry is inactive, there should be @@ -434,25 +487,17 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): # Restore port data for that switch self._cfg_monitor.check_connections() - result_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/2', 269), - test_cisco_nexus_base.RESULT_ADD_VLAN.format(269)]) - self._verify_results(result_replay) + self._verify_results( + self.results.get_test_results( + 'switch_restore_result_replay')) # Clear mock_call history. self.mock_ncclient.reset_mock() # Clean-up the port entries - result_del = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/3', 269), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(269), - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/2', 269), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(269)]) self._basic_delete_verify_port_vlan('test_replay_dual', - result_del, + self.results.get_test_results( + 'switch_restore_result_del'), nbr_of_bindings=0) def test_replay_create_fails_if_single_switch_down(self): @@ -495,7 +540,8 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): port_cfg.nexus_ip_addr, const.SWITCH_ACTIVE) self._basic_create_verify_port_vlan('test_replay_unique1', - self.driver_result_unique_add1) + self.results.get_test_results( + 'driver_result_unique_add1')) # Make switch inactive before delete self._cisco_mech_driver.set_switch_ip_and_active_state( @@ -544,7 +590,8 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): # Set-up successful creation of port vlan config self._basic_create_verify_port_vlan('test_replay_unique1', - self.driver_result_unique_add1) + self.results.get_test_results( + 'driver_result_unique_add1')) # Set-up so get_nexus_type driver fails config = {'connect.return_value.get.side_effect': @@ -581,7 +628,8 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): # Set-up successful creation of port vlan config self._basic_create_verify_port_vlan('test_replay_unique1', - self.driver_result_unique_add1) + self.results.get_test_results( + 'driver_result_unique_add1')) # Set-up exception during create_vlan config = {'connect.return_value.edit_config.side_effect': @@ -608,6 +656,7 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): result_replay = ( [test_cisco_nexus_base.RESULT_ADD_INTERFACE. format('ethernet', '1\/10', 267)] + driver_result1) + self._verify_results(result_replay) # Clear the edit driver exception for next test. @@ -630,7 +679,8 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): # Clean-up the port entry self._basic_delete_verify_port_vlan('test_replay_unique1', - self.driver_result_unique_del2) + self.results.get_test_results( + 'driver_result_unique_del2')) def test_replay_vlan_batch_failure_during_replay(self): """Verifies handling of batch vlan during replay.""" @@ -737,8 +787,10 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): port_cfg.nexus_ip_addr, const.SWITCH_ACTIVE) # Set-up successful creation of port vlan config - self._basic_create_verify_port_vlan('test_replay_unique1', - self.driver_result_unique_add1) + self._basic_create_verify_port_vlan( + 'test_replay_unique1', + self.results.get_test_results( + 'driver_result_unique_add1')) # Test 1: # Set the edit create vlan driver exception @@ -855,9 +907,123 @@ class TestCiscoNexusReplay(test_cisco_nexus_base.TestCiscoNexusReplayBase): test_cisco_nexus_base.NEXUS_IP_ADDRESS_1)) +class TestCiscoNexusBaremetalReplayResults( + test_cisco_nexus_base.TestCiscoNexusBaseResults): + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + 'driver_result_unique_eth_init': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 'None')]), + + 'driver_result_unique_eth_add1': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 267)]), + + 'driver_result_unique_eth_add2': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 265)]), + + 'driver_result_unique_eth_del1': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'driver_result_unique_eth_del2': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'driver_result_unique_vPC_init': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '469', 'None')]), + + 'driver_result_unique_vPC_add1': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '469', 267)]), + + 'driver_result_unique_vPC_add2': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '469', 265)]), + + 'driver_result_unique_vPC_del1': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('port-channel', '469', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'driver_result_unique_vPC_del2': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('port-channel', '469', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'driver_result_unique_native_port_ethernet_add': ( + [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), + (test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. + format('ethernet', '1\/10', 265) + + '[\x00-\x7f]+' + + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', 265))]), + + 'driver_result_unique_native_port_ethernet_del': ( + [(test_cisco_nexus_base.RESULT_DEL_NATIVE_INTERFACE. + format('ethernet', '1\/10') + + '[\x00-\x7f]+' + + test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('ethernet', '1\/10', 265)), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'driver_result_unique_2vlan_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', '265,267'), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]), + + 'driver_result_unique_2vlan_vpc_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '469', '265,267'), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]), + + 'driver_result_unique_vPC470_del1': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('port-channel', '470', 265), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]), + + 'driver_result_unique_vPC470_del2': ( + [test_cisco_nexus_base.RESULT_DEL_INTERFACE. + format('port-channel', '470', 267), + test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]), + + 'driver_result_unique_vPC470_2vlan_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('port-channel', '470', '265,267'), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]), + + 'driver_result_unique_2vlan_vpc_enchg_replay': ( + [test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', '265,267'), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]), + + 'driver_result_unique_native_2vlan_replay': ( + [(test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. + format('ethernet', '1\/10', 265) + + '[\x00-\x7f]+' + + test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', '265')), + (test_cisco_nexus_base.RESULT_ADD_INTERFACE. + format('ethernet', '1\/10', '265,267')), + test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + } + + class TestCiscoNexusBaremetalReplay( test_cisco_nexus_base.TestCiscoNexusReplayBase): + """Unit tests for Replay of Cisco ML2 Nexus data.""" + baremetal_profile = { "local_link_information": [ { @@ -921,70 +1087,6 @@ class TestCiscoNexusBaremetalReplay( test_cisco_nexus_base.BAREMETAL_VNIC), } - driver_result_unique_eth_init = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 'None')]) - - driver_result_unique_eth_add1 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 267)]) - - driver_result_unique_eth_add2 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 265)]) - - driver_result_unique_eth_del1 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - - driver_result_unique_eth_del2 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - - driver_result_unique_vPC_init = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '469', 'None')]) - - driver_result_unique_vPC_add1 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(267), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '469', 267)]) - - driver_result_unique_vPC_add2 = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '469', 265)]) - - driver_result_unique_vPC_del1 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('port-channel', '469', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - - driver_result_unique_vPC_del2 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('port-channel', '469', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - - driver_result_unique_native_port_ethernet_add = ( - [test_cisco_nexus_base.RESULT_ADD_VLAN.format(265), - (test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. - format('ethernet', '1\/10', 265) + - '[\x00-\x7f]+' + - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', 265))]) - - driver_result_unique_native_port_ethernet_del = ( - [(test_cisco_nexus_base.RESULT_DEL_NATIVE_INTERFACE. - format('ethernet', '1\/10') + - '[\x00-\x7f]+' + - test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('ethernet', '1\/10', 265)), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - test_configs = collections.OrderedDict(sorted(test_configs.items())) def setUp(self): @@ -992,71 +1094,81 @@ class TestCiscoNexusBaremetalReplay( cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') super(TestCiscoNexusBaremetalReplay, self).setUp() + self.results = TestCiscoNexusBaremetalReplayResults() + + def _init_port_channel(self, ch_grp): + + # with Baremetal config when enet interface associated to port-channel, + # the port-channel interface is configured instead. This config + # causes this to happen. + data_xml = {'connect.return_value.get.return_value.data_xml': + 'switchport trunk allowed vlan none\n' + 'channel-group ' + str(ch_grp) + ' mode active'} + self.mock_ncclient.configure_mock(**data_xml) def test_replay_unique_ethernet_ports(self): """Provides replay data and result data for unique ports. """ - first_add = {'driver_results': self. - driver_result_unique_eth_add1, - 'nbr_db_entries': 2} - second_add = {'driver_results': self. - driver_result_unique_eth_add2, - 'nbr_db_entries': 3} - first_del = {'driver_results': self. - driver_result_unique_eth_del1, - 'nbr_db_entries': 2} - second_del = {'driver_results': self. - driver_result_unique_eth_del2, - 'nbr_db_entries': 1} - driver_result_unique_2vlan_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', '265,267'), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_add1'), + 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_add2'), + 'nbr_db_entries': 3} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_del1'), + 'nbr_db_entries': 2} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_del2'), + 'nbr_db_entries': 1} self._process_replay( 'test_replay_unique1', 'test_replay_unique2', - self.driver_result_unique_eth_init, + self.results.get_test_results( + 'driver_result_unique_eth_init'), first_add, second_add, - driver_result_unique_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_2vlan_replay'), first_del, second_del) def test_replay_unique_vPC_ports(self): """Provides replay data and result data for unique ports. """ - first_add = {'driver_results': self. - driver_result_unique_vPC_add1, - 'nbr_db_entries': 2} - second_add = {'driver_results': self. - driver_result_unique_vPC_add2, - 'nbr_db_entries': 3} - first_del = {'driver_results': self. - driver_result_unique_vPC_del1, - 'nbr_db_entries': 2} - second_del = {'driver_results': self. - driver_result_unique_vPC_del2, - 'nbr_db_entries': 1} - driver_result_unique_2vlan_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '469', '265,267'), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add1'), + 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add2'), + 'nbr_db_entries': 3} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_del1'), + 'nbr_db_entries': 2} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_del2'), + 'nbr_db_entries': 1} - # this is to prevent interface initialization from occurring - # which adds unnecessary noise to the results. - data_xml = {'connect.return_value.get.return_value.data_xml': - 'switchport trunk allowed vlan none\n' - 'channel-group 469 mode active'} - self.mock_ncclient.configure_mock(**data_xml) + self._init_port_channel(469) self._process_replay( 'test_replay_unique1', 'test_replay_unique2', - self.driver_result_unique_vPC_init, + self.results.get_test_results( + 'driver_result_unique_vPC_init'), first_add, second_add, - driver_result_unique_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_2vlan_vpc_replay'), first_del, second_del) @@ -1064,53 +1176,39 @@ class TestCiscoNexusBaremetalReplay( """Provides replay data and result data for unique ports. """ def replay_init(): - # This is to cause port-channel to get configured to 470 - data_xml = {'connect.return_value.get.return_value.data_xml': - 'switchport trunk allowed vlan none\n' - 'channel-group 470 mode active'} - self.mock_ncclient.configure_mock(**data_xml) + # This causes port-channel 470 to get configured instead. + self._init_port_channel(470) - driver_result_unique_vPC470_del1 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('port-channel', '470', 265), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(265)]) - - driver_result_unique_vPC470_del2 = ( - [test_cisco_nexus_base.RESULT_DEL_INTERFACE. - format('port-channel', '470', 267), - test_cisco_nexus_base.RESULT_DEL_VLAN.format(267)]) - - first_add = {'driver_results': self. - driver_result_unique_vPC_add1, - 'nbr_db_entries': 2} - second_add = {'driver_results': self. - driver_result_unique_vPC_add2, - 'nbr_db_entries': 3} - first_del = {'driver_results': - driver_result_unique_vPC470_del1, - 'nbr_db_entries': 2} - second_del = {'driver_results': - driver_result_unique_vPC470_del2, - 'nbr_db_entries': 1} - driver_result_unique_vPC470_2vlan_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('port-channel', '470', '265,267'), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add1'), + 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add2'), + 'nbr_db_entries': 3} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC470_del1'), + 'nbr_db_entries': 2} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC470_del2'), + 'nbr_db_entries': 1} # This is to cause port-channel 469 to get configured - data_xml = {'connect.return_value.get.return_value.data_xml': - 'switchport trunk allowed vlan none\n' - 'channel-group 469 mode active'} - self.mock_ncclient.configure_mock(**data_xml) + self._init_port_channel(469) # Providing replay_init to change channel-group self._process_replay( 'test_replay_unique1', 'test_replay_unique2', - self.driver_result_unique_vPC_init, + self.results.get_test_results( + 'driver_result_unique_vPC_init'), first_add, second_add, - driver_result_unique_vPC470_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_vPC470_2vlan_replay'), first_del, second_del, replay_init) @@ -1119,43 +1217,42 @@ class TestCiscoNexusBaremetalReplay( """Provides replay data and result data for unique ports. """ def replay_init(): - # This is to cause port-channel to get replaced with enet - data_xml = {'connect.return_value.get.return_value.data_xml': - 'switchport trunk allowed vlan none\n'} - self.mock_ncclient.configure_mock(**data_xml) + # This causes port-channel to get replaced with enet + # by eliminating channel-group config from enet config. + if cfg.CONF.ml2_cisco.nexus_driver == 'restapi': + self.restapi_mock_init() + else: + self.mock_init() - first_add = {'driver_results': self. - driver_result_unique_vPC_add1, - 'nbr_db_entries': 2} - second_add = {'driver_results': self. - driver_result_unique_vPC_add2, - 'nbr_db_entries': 3} - first_del = {'driver_results': self. - driver_result_unique_eth_del1, - 'nbr_db_entries': 2} - second_del = {'driver_results': self. - driver_result_unique_eth_del2, - 'nbr_db_entries': 1} - driver_result_unique_2vlan_replay = ( - [test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', '265,267'), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add1'), + 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_vPC_add2'), + 'nbr_db_entries': 3} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_del1'), + 'nbr_db_entries': 2} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_del2'), + 'nbr_db_entries': 1} - # this is to prevent interface initialization from occurring - # which adds unnecessary noise to the results. - data_xml = {'connect.return_value.get.return_value.data_xml': - 'switchport trunk allowed vlan none\n' - 'channel-group 469 mode active'} - self.mock_ncclient.configure_mock(**data_xml) + self._init_port_channel(469) # Providing replay_init to remove port-channel self._process_replay( 'test_replay_unique1', 'test_replay_unique2', - self.driver_result_unique_vPC_init, + self.results.get_test_results( + 'driver_result_unique_vPC_init'), first_add, second_add, - driver_result_unique_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_2vlan_vpc_enchg_replay'), first_del, second_del, replay_init) @@ -1163,33 +1260,32 @@ class TestCiscoNexusBaremetalReplay( def test_replay_unique_native_nonnative_ethernet_ports(self): """Test replay with native and nonnative ethernet ports. """ - first_add = {'driver_results': self. - driver_result_unique_native_port_ethernet_add, - 'nbr_db_entries': 2} - second_add = {'driver_results': self. - driver_result_unique_eth_add1, - 'nbr_db_entries': 3} - first_del = {'driver_results': self. - driver_result_unique_eth_del2, - 'nbr_db_entries': 2} - second_del = {'driver_results': self. - driver_result_unique_native_port_ethernet_del, - 'nbr_db_entries': 1} - driver_result_unique_native_2vlan_replay = ( - [(test_cisco_nexus_base.RESULT_ADD_NATIVE_INTERFACE. - format('ethernet', '1\/10', 265) + - '[\x00-\x7f]+' + - test_cisco_nexus_base.RESULT_ADD_INTERFACE. - format('ethernet', '1\/10', '265,267')), - test_cisco_nexus_base.RESULT_ADD_VLAN.format('265,267')]) + first_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_native_port_ethernet_add'), + 'nbr_db_entries': 2} + second_add = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_add1'), + 'nbr_db_entries': 3} + first_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_eth_del2'), + 'nbr_db_entries': 2} + second_del = { + 'driver_results': self.results.get_test_results( + 'driver_result_unique_native_port_ethernet_del'), + 'nbr_db_entries': 1} self._process_replay( 'test_replay_unique_native1', 'test_replay_unique1', - self.driver_result_unique_eth_init, + self.results.get_test_results( + 'driver_result_unique_eth_init'), first_add, second_add, - driver_result_unique_native_2vlan_replay, + self.results.get_test_results( + 'driver_result_unique_native_2vlan_replay'), first_del, second_del) diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events.py new file mode 100644 index 0000000..ae2323c --- /dev/null +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events.py @@ -0,0 +1,571 @@ +# Copyright (c) 2017 Cisco Systems, Inc. +# 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. + +""" +Basic Test Classes using RESTAPI Driver to test Cisco Nexus platforms. + +These Classes are based on the original ssh event driver so same +tests occur with same configuration. What's different between +the tests is the resulting driver output which is what +the tests in this class presents to its parent class. + +You will notice in this file there are test methods which +are skipped by using 'pass'. This is because these tests +apply to ssh only OR because rerunning the test would be +redundant. +""" + +from oslo_config import cfg + +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_snippets as snipp) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_base as base) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_events) + + +class TestCiscoNexusRestDeviceResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + 'duplicate_add_port_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + 'duplicate_del_port_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + 'add_port2_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265')), + base.POST] + ], + 'delete_port2_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + 'add_port2_driver_result2': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + 'delete_port2_driver_result2': [ + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST] + ], + 'add_port2_driver_result3': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_6, + (snipp.BODY_VLAN_ADD % 268), + base.POST], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_6, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+268')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_7, + (snipp.BODY_VLAN_ADD % 268), + base.POST], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_7, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+268')), + base.POST] + ], + 'delete_port2_driver_result3': [ + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_6, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-268')), + base.POST], + [(snipp.PATH_VLAN % '268'), + base.NEXUS_IP_ADDRESS_6, + '', + base.DELETE], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_7, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-268')), + base.POST], + [(snipp.PATH_VLAN % '268'), + base.NEXUS_IP_ADDRESS_7, + '', + base.DELETE] + ], + 'add_port_channel_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VLAN_ADD % 268), + base.POST], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+268')), + base.POST] + ], + 'delete_port_channel_driver_result': [ + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-268')), + base.POST], + [(snipp.PATH_VLAN % '268'), + base.NEXUS_IP_ADDRESS_2, + '', + base.DELETE] + ], + 'dual_add_port_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_VLAN_ADD % 269), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+269')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_VLAN_ADD % 269), + base.POST], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+269')), + base.POST] + ], + 'dual_delete_port_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-269')), + base.POST], + [(snipp.PATH_VLAN % '269'), + base.NEXUS_IP_ADDRESS_DUAL, + '', + base.DELETE], + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-269')), + base.POST], + ], + 'add_port_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + 'del_port_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_8, + '', + base.DELETE] + ], + 'migrate_add_host2_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + } + + +class TestCiscoNexusRestDevice(test_cisco_nexus_events.TestCiscoNexusDevice): + + """Unit tests for Cisco ML2 Nexus restapi device driver""" + + def setUp(self): + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + super(TestCiscoNexusRestDevice, self).setUp() + self.results = TestCiscoNexusRestDeviceResults() + + def test_create_delete_duplicate_ports(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_duplicate_ports()) + + def test_create_delete_duplicate_port_transaction(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_duplicate_port_transaction()) + + def test_create_delete_same_switch_diff_hosts_diff_vlan(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_same_switch_diff_hosts_diff_vlan()) + + def test_create_delete_same_switch_diff_hosts_same_vlan(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_same_switch_diff_hosts_same_vlan()) + + def test_create_delete_diff_switch_same_host(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_diff_switch_same_host()) + + def test_create_delete_portchannel(self): + super(TestCiscoNexusRestDevice, self).test_create_delete_portchannel() + + def test_create_delete_dual(self): + super(TestCiscoNexusRestDevice, self).test_create_delete_dual() + + def test_create_delete_dhcp(self): + super(TestCiscoNexusRestDevice, self).test_create_delete_dhcp() + + def test_create_delete_router_ha_intf(self): + (super(TestCiscoNexusRestDevice, self). + test_create_delete_router_ha_intf()) + + def test_nexus_vm_migration(self): + super(TestCiscoNexusRestDevice, self).test_nexus_vm_migration() + + +class TestCiscoNexusRestInitResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + # set 1 - switch 1.1.1.1 sets eth 1/10 & 1/20 to None + # set 2 - switch 8.8.8.8 sets eth 1/10 & 1/20 to None + # set 3 - switch 4.4.4.4 sets eth 1/3 & portchannel 2 to None + # set 4 - switch 2.2.2.2 sets portchannel 2 to None + # set 5 - switch 6.6.6.6 sets portchannel 2 to None + # set 6 - switch 7.7.7.7 sets portchannel 2 to None + 'duplicate_init_port_driver_result1': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_8, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '')), + base.POST], + + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '')), + base.POST], + + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_6, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '')), + base.POST], + + [(snipp.PATH_IF % 'aggr-[po2]'), + base.NEXUS_IP_ADDRESS_7, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '')), + base.POST], + + ], + + } + +GET_INTERFACE_NO_TRUNK_RESPONSE = { + "totalCount": "1", + "imdata": [ + { + "l1PhysIf": { + "attributes": { + "trunkVlans": "1-4094" + } + } + } + ] +} + +GET_INTERFACE_PCHAN_NO_TRUNK_RESPONSE = { + "totalCount": "1", + "imdata": [ + { + "pcAggrIf": { + "attributes": { + "trunkVlans": "1-4094" + } + } + } + ] +} + + +# Skipped inheriting event class TestCiscoNexusDeviceFailure +# since some tests are generic and need not be executed twice +# and some apply only to SSH driver. + +class TestCiscoNexusRestDeviceInit( + test_cisco_nexus_events.TestCiscoNexusDeviceInit): + """Verifies interface vlan allowed none is set when missing.""" + + def get_init_side_effect( + self, action, ipaddr=None, body=None, headers=None): + + eth_path = 'api/mo/sys/intf/phys-' + port_chan_path = 'api/mo/sys/intf/aggr-' + + if action == snipp.PATH_GET_NEXUS_TYPE: + return base.GET_NEXUS_TYPE_RESPONSE + elif action in snipp.PATH_GET_PC_MEMBERS: + return base.GET_NO_PORT_CH_RESPONSE + elif eth_path in action: + return GET_INTERFACE_NO_TRUNK_RESPONSE + elif port_chan_path in action: + return GET_INTERFACE_PCHAN_NO_TRUNK_RESPONSE + + return {} + + def restapi_mock_init(self): + + # this is to prevent interface initialization from occurring + # which adds unnecessary noise to the results. + + data_json = {'rest_get.side_effect': + self.get_init_side_effect} + self.mock_ncclient.configure_mock(**data_json) + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusRestDeviceInit, self).setUp() + self.results = TestCiscoNexusRestInitResults() + + def test_verify_initialization(self): + self._verify_results( + self.results.get_test_results( + 'duplicate_init_port_driver_result1')) + + +class TestCiscoNexusRestBaremetalResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + 'add_port_ethernet_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'delete_port_ethernet_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'add_port_channel_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+267')), + base.POST] + ], + + 'delete_port_channel_driver_result': [ + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'add_port_ethernet_native_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_NATIVE_TRUNKVLAN % ('l1PhysIf', '+265', 'vlan-265')), + base.POST] + ], + + 'delete_port_ethernet_native_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_NATIVE_TRUNKVLAN % ('l1PhysIf', '-265', '')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + } + +GET_PORT_CH_RESPONSE = { + "totalCount": "3", + "imdata": [ + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po1", + "tSKey": "eth1/11", + } + } + }, + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po469", + "tSKey": "eth1/10", + } + } + }, + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po2", + "tSKey": "eth1/12", + } + } + } + ] +} + + +class TestCiscoNexusRestBaremetalDevice( + test_cisco_nexus_events.TestCiscoNexusBaremetalDevice): + + """Tests for Cisco ML2 Nexus baremetal RESTAPI device driver.""" + + def get_init_side_effect( + self, action, ipaddr=None, body=None, headers=None): + + eth_path = 'api/mo/sys/intf/phys-' + port_chan_path = 'api/mo/sys/intf/aggr-' + + if action == snipp.PATH_GET_NEXUS_TYPE: + return base.GET_NEXUS_TYPE_RESPONSE + elif action in snipp.PATH_GET_PC_MEMBERS: + return GET_PORT_CH_RESPONSE + elif eth_path in action: + return base.GET_INTERFACE_RESPONSE + elif port_chan_path in action: + return base.GET_INTERFACE_PCHAN_RESPONSE + + return {} + + def _init_port_channel(self): + + # this is to prevent interface initialization from occurring + # which adds unnecessary noise to the results. + + data_json = {'rest_get.side_effect': + self.get_init_side_effect} + self.mock_ncclient.configure_mock(**data_json) + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusRestBaremetalDevice, self).setUp() + self.results = TestCiscoNexusRestBaremetalResults() + + def test_create_delete_basic_ethernet_port(self): + (super(TestCiscoNexusRestBaremetalDevice, self). + test_create_delete_basic_ethernet_port()) + + def test_create_delete_basic_port_channel(self): + (super(TestCiscoNexusRestBaremetalDevice, self). + test_create_delete_basic_port_channel()) + + def test_create_delete_basic_eth_port_is_native(self): + (super(TestCiscoNexusRestBaremetalDevice, self). + test_create_delete_basic_eth_port_is_native()) + + def test_create_delete_switch_ip_not_defined(self): + (super(TestCiscoNexusRestBaremetalDevice, self). + test_create_delete_switch_ip_not_defined()) + + +# Skipped inheriting event class TestCiscoNexusNonCacheSshDevice +# since it does not apply to REST API diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events_vxlan.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events_vxlan.py new file mode 100644 index 0000000..ece3523 --- /dev/null +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_events_vxlan.py @@ -0,0 +1,255 @@ +#Copyright (c) 2017 Cisco Systems, Inc. +# 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. + +""" +VXLAN Test Class using RESTAPI Driver to test Cisco Nexus platforms. + +These Classes are based on the original ssh VXLAN event driver +so same tests occur with same configuration. What's different +between the tests is the resulting driver output which is what +the tests in this class presents to its parent class. + +You will notice in this file there are test methods which +are skipped by using 'pass'. This is because these tests +apply to ssh only OR because rerunning the test would be +redundant. +""" + +from oslo_config import cfg + +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_snippets as snipp) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_base as base) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_events_vxlan) + + +class TestCiscoNexusRestVxlanResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + # The following contains desired Nexus output for + # some basic vxlan config. + 'add_port_driver_result': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VNI_UPDATE % ( + '70000', '70000', '70000', + base.MCAST_GROUP)), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VXLAN_ADD % (267, 70000)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'delete_port_driver_result': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + + 'add_port2_driver_result': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VXLAN_ADD % (267, 70000)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'delete_port2_driver_result': [ + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + ], + + 'add_port_driver_result3': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VNI_UPDATE % ( + '70000', '70000', '70000', + base.MCAST_GROUP)), + base.POST], + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VNI_UPDATE % ( + '70000', '70000', '70000', + base.MCAST_GROUP)), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VXLAN_ADD % (267, 70000)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VXLAN_ADD % (267, 70000)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/2]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VXLAN_ADD % (267, 70000)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'delete_port_driver_result3': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE], + [(snipp.PATH_VNI_UPDATE % ('1', '70000')), + base.NEXUS_IP_ADDRESS_2, + '', + base.DELETE], + + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/2]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_2, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST] + ], + + 'add_port_driver_result2': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70001')), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VNI_UPDATE % ( + '70001', '70001', '70001', + base.MCAST_GROUP)), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VXLAN_ADD % (265, 70001)), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265')), + base.POST] + ], + + 'delete_port_driver_result2': [ + [(snipp.PATH_VNI_UPDATE % ('1', '70001')), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + } + + +class TestCiscoNexusRestVxlanDevice( + test_cisco_nexus_events_vxlan.TestCiscoNexusVxlanDevice): + + """Unit tests for Cisco ML2 VXLAN Nexus device driver.""" + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusRestVxlanDevice, self).setUp() + self.mock_ncclient.reset_mock() + self.addCleanup(self._clear_nve_db) + self.results = TestCiscoNexusRestVxlanResults() + + def test_enable_vxlan_feature_failure(self): + pass + + def test_disable_vxlan_feature_failure(self): + pass + + def test_create_nve_member_failure(self): + pass + + def test_delete_nve_member_failure(self): + pass + + def test_nexus_vxlan_one_network_two_hosts(self): + (super(TestCiscoNexusRestVxlanDevice, self). + test_nexus_vxlan_one_network_two_hosts()) + + def test_nexus_missing_vxlan_fields(self): + pass + + def test_nexus_vxlan_bind_port(self): + pass + + def test_nexus_vxlan_bind_port_no_physnet(self): + pass + + def test_nexus_vxlan_bind_port_no_dynamic_segment(self): + pass + + def test_nexus_vxlan_one_network(self): + (super(TestCiscoNexusRestVxlanDevice, self). + test_nexus_vxlan_one_network()) + + def test_nexus_vxlan_two_network(self): + (super(TestCiscoNexusRestVxlanDevice, self). + test_nexus_vxlan_two_network()) diff --git a/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_replay.py b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_replay.py new file mode 100644 index 0000000..b2f6051 --- /dev/null +++ b/networking_cisco/tests/unit/ml2/drivers/cisco/nexus/test_cisco_nexus_restapi_replay.py @@ -0,0 +1,644 @@ +# Copyright (c) 2017 Cisco Systems, Inc. +# 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. + +""" +Replay Test Classes using RESTAPI Driver to test Cisco Nexus platforms. + +These Classes are based on the original ssh event driver so same +tests occur with same configuration. What's different between +the tests is the resulting driver output which is what +the tests in this class presents to its parent class. + +You will notice in this file there are test methods which +are skipped by using 'pass'. This is because these tests +apply to ssh only OR because rerunning the test would be +redundant. +""" + + +from oslo_config import cfg + +from networking_cisco.plugins.ml2.drivers.cisco.nexus import ( + nexus_restapi_snippets as snipp) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_base as base) +from networking_cisco.tests.unit.ml2.drivers.cisco.nexus import ( + test_cisco_nexus_replay) + + +class TestCiscoNexusRestReplayResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + 'driver_result_unique_init': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + ], + + 'driver_result_unique_add1': [ + [snipp.PATH_VLAN_ALL, + None, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + None, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'driver_result_unique_add2': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265')), + base.POST] + ], + + 'driver_result_unique_del1': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_del2': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + None, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + None, + '', + base.DELETE] + ], + + 'driver_result_unique_2vlan_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ], + + 'dupl_vlan_result1_add': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'dupl_vlan_result2_add': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'dupl_vlan_result2_del': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_2, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST] + ], + + 'dupl_vlan_result_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/20]'), + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_2, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + ], + + 'dupl_port_result_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_3, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_3, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + ], + + 'switch_up_result_add': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_VLAN_ADD % 269), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+269')), + base.POST] + ], + + 'switch_up_result_del': [ + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-269')), + base.POST], + [(snipp.PATH_VLAN % '269'), + base.NEXUS_IP_ADDRESS_DUAL, + '', + base.DELETE] + ], + + 'switch_restore_result_add': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_VLAN_ADD % 269), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+269')), + base.POST] + ], + + 'switch_restore_result_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/2]'), + base.NEXUS_IP_ADDRESS_DUAL2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+269')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_DUAL2, + (snipp.BODY_VLAN_ADD % 269), + base.POST] + ], + + 'switch_restore_result_del': [ + [(snipp.PATH_IF % 'phys-[eth1/3]'), + base.NEXUS_IP_ADDRESS_DUAL, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-269')), + base.POST], + [(snipp.PATH_VLAN % '269'), + base.NEXUS_IP_ADDRESS_DUAL, + '', + base.DELETE], + [(snipp.PATH_IF % 'phys-[eth1/2]'), + base.NEXUS_IP_ADDRESS_DUAL2, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-269')), + base.POST], + [(snipp.PATH_VLAN % '269'), + base.NEXUS_IP_ADDRESS_DUAL2, + '', + base.DELETE] + ], + + } + + +class TestCiscoNexusRestReplay(test_cisco_nexus_replay.TestCiscoNexusReplay): + """Unit tests for Replay of Cisco ML2 Nexus data.""" + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusRestReplay, self).setUp() + self.results = TestCiscoNexusRestReplayResults() + + def test_replay_unique_ports(self): + super(TestCiscoNexusRestReplay, self).test_replay_unique_ports() + + def test_replay_duplicate_vlan(self): + super(TestCiscoNexusRestReplay, self).test_replay_duplicate_vlan() + + def test_replay_duplicate_ports(self): + super(TestCiscoNexusRestReplay, self).test_replay_duplicate_ports() + + def test_replay_enable_vxlan_feature_failure(self): + pass + + def test_replay_disable_vxlan_feature_failure(self): + pass + + def test_replay_create_nve_member_failure(self): + pass + + def test_replay_delete_nve_member_failure(self): + pass + + def test_replay_create_vlan_failure(self): + pass + + def test_replay_delete_vlan_failure(self): + pass + + def test_replay_create_trunk_failure(self): + pass + + def test_replay_delete_trunk_failure(self): + pass + + def test_replay_new_port_success_if_one_switch_up(self): + (super(TestCiscoNexusRestReplay, self). + test_replay_new_port_success_if_one_switch_up()) + + def test_replay_port_success_if_one_switch_restored(self): + (super(TestCiscoNexusRestReplay, self). + test_replay_port_success_if_one_switch_restored()) + + def test_replay_create_fails_if_single_switch_down(self): + (super(TestCiscoNexusRestReplay, self). + test_replay_create_fails_if_single_switch_down()) + + def test_replay_update_fails_if_single_switch_down(self): + (super(TestCiscoNexusRestReplay, self). + test_replay_update_fails_if_single_switch_down()) + + def test_replay_delete_success_if_switch_down(self): + (super(TestCiscoNexusRestReplay, self). + test_replay_delete_success_if_switch_down()) + + def test_replay_get_nexus_type_failure_two_switches(self): + pass + + def test_replay_get_nexus_type_failure(self): + pass + + def test_replay_create_vlan_failure_during_replay(self): + pass + + def test_replay_vlan_batch_failure_during_replay(self): + pass + + def test_replay_no_retry_failure_handling(self): + pass + + +class TestCiscoNexusRestBaremetalReplayResults(base.TestCiscoNexusBaseResults): + + """Unit tests driver results for Cisco ML2 Nexus.""" + + test_results = { + + 'driver_result_unique_init': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '')), + base.POST], + ], + + 'driver_result_unique_eth_add1': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+267')), + base.POST] + ], + + 'driver_result_unique_eth_add2': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265')), + base.POST] + ], + + 'driver_result_unique_eth_del1': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_eth_del2': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_vPC_init': [ + [(snipp.PATH_IF % 'aggr--[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '')), + base.POST], + ], + + 'driver_result_unique_vPC_add1': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 267), + base.POST], + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+267')), + base.POST] + ], + + 'driver_result_unique_vPC_add2': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+265')), + base.POST] + ], + + 'driver_result_unique_vPC_del1': [ + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_vPC_del2': [ + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_native_port_ethernet_add': [ + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD % 265), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_NATIVE_TRUNKVLAN % ('l1PhysIf', '+265', 'vlan-265')), + base.POST] + ], + + 'driver_result_unique_native_port_ethernet_del': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_NATIVE_TRUNKVLAN % ('l1PhysIf', '-265', '')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_2vlan_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ], + + 'driver_result_unique_2vlan_vpc_replay': [ + [(snipp.PATH_IF % 'aggr-[po469]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ], + + 'driver_result_unique_vPC470_del1': [ + [(snipp.PATH_IF % 'aggr-[po470]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-265')), + base.POST], + [(snipp.PATH_VLAN % '265'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_vPC470_del2': [ + [(snipp.PATH_IF % 'aggr-[po470]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '-267')), + base.POST], + [(snipp.PATH_VLAN % '267'), + base.NEXUS_IP_ADDRESS_1, + '', + base.DELETE] + ], + + 'driver_result_unique_vPC470_2vlan_replay': [ + [(snipp.PATH_IF % 'aggr-[po470]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('pcAggrIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ], + + 'driver_result_unique_2vlan_vpc_enchg_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ], + + 'driver_result_unique_native_2vlan_replay': [ + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_NATIVE_TRUNKVLAN % ('l1PhysIf', '+265', 'vlan-265')), + base.POST], + [(snipp.PATH_IF % 'phys-[eth1/10]'), + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_TRUNKVLAN % ('l1PhysIf', '+265,267')), + base.POST], + [snipp.PATH_VLAN_ALL, + base.NEXUS_IP_ADDRESS_1, + (snipp.BODY_VLAN_ADD_START % 265) + ( + snipp.BODY_VLAN_ADD_NEXT % 267) + snipp.BODY_VLAN_ALL_END, + base.POST] + ] + + } + + +GET_PORT_CH_RESPONSE = { + "totalCount": "3", + "imdata": [ + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po1", + "tSKey": "eth1/11", + } + } + }, + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po469", + "tSKey": "eth1/10", + } + } + }, + { + "pcRsMbrIfs": { + "attributes": { + "parentSKey": "po2", + "tSKey": "eth1/12", + } + } + } + ] +} + + +class TestCiscoNexusRestBaremetalReplay( + test_cisco_nexus_replay.TestCiscoNexusBaremetalReplay): + + def get_init_side_effect( + self, action, ipaddr=None, body=None, headers=None): + + eth_path = 'api/mo/sys/intf/phys-' + port_chan_path = 'api/mo/sys/intf/aggr-' + + if action == snipp.PATH_GET_NEXUS_TYPE: + return base.GET_NEXUS_TYPE_RESPONSE + elif action in snipp.PATH_GET_PC_MEMBERS: + return GET_PORT_CH_RESPONSE + elif eth_path in action: + return base.GET_INTERFACE_RESPONSE + elif port_chan_path in action: + return base.GET_INTERFACE_PCHAN_RESPONSE + + return {} + + def _init_port_channel(self, ch_grp): + + # this is to prevent interface initialization from occurring + # which adds unnecessary noise to the results. + + GET_PORT_CH_RESPONSE['imdata'][1]['pcRsMbrIfs'][ + 'attributes']['parentSKey'] = ('po' + str(ch_grp)) + data_json = {'rest_get.side_effect': + self.get_init_side_effect} + self.mock_ncclient.configure_mock(**data_json) + + def setUp(self): + """Sets up mock ncclient, and switch and credentials dictionaries.""" + + cfg.CONF.set_override('nexus_driver', 'restapi', 'ml2_cisco') + cfg.CONF.set_override('never_cache_ssh_connection', False, 'ml2_cisco') + super(TestCiscoNexusRestBaremetalReplay, self).setUp() + self.results = TestCiscoNexusRestBaremetalReplayResults() + + def test_replay_unique_ethernet_ports(self): + (super(TestCiscoNexusRestBaremetalReplay, self). + test_replay_unique_ethernet_ports()) + + def test_replay_unique_vPC_ports(self): + (super(TestCiscoNexusRestBaremetalReplay, self). + test_replay_unique_vPC_ports()) + + def test_replay_unique_vPC_ports_chg_vPC_nbr(self): + (super(TestCiscoNexusRestBaremetalReplay, self). + test_replay_unique_vPC_ports_chg_vPC_nbr()) + + def test_replay_unique_vPC_ports_chg_to_enet(self): + (super(TestCiscoNexusRestBaremetalReplay, self). + test_replay_unique_vPC_ports_chg_to_enet()) + + def test_replay_unique_native_nonnative_ethernet_ports(self): + (super(TestCiscoNexusRestBaremetalReplay, self). + test_replay_unique_native_nonnative_ethernet_ports()) + + +#The tests in class below is reproduced this is does not apply to restapis. +#class TestCiscoNexusNonCachedSshReplay( +# test_cisco_nexus_base.TestCiscoNexusReplayBase): diff --git a/setup.cfg b/setup.cfg index 52087d5..67447d8 100755 --- a/setup.cfg +++ b/setup.cfg @@ -101,6 +101,10 @@ neutronclient.extension = policy_profile = networking_cisco.neutronclient.policyprofile network_profile = networking_cisco.neutronclient.networkprofile +networking_cisco.ml2.nexus_driver = + ncclient = networking_cisco.plugins.ml2.drivers.cisco.nexus.nexus_network_driver:CiscoNexusSshDriver + restapi = networking_cisco.plugins.ml2.drivers.cisco.nexus.nexus_restapi_network_driver:CiscoNexusRestapiDriver + # Extension Firewall drivers for SAF services.firewall.native.drivers = native = networking_cisco.apps.saf.server.services.firewall.native.drivers.native:NativeFirewall