from neutron.plugins.ml2.drivers.cisco.nexus import config as conf
from neutron.plugins.ml2.drivers.cisco.nexus import constants as const
from neutron.plugins.ml2.drivers.cisco.nexus import exceptions as cexc
-from neutron.plugins.ml2.drivers.cisco.nexus import nexus_db_v2
from neutron.plugins.ml2.drivers.cisco.nexus import nexus_snippets as snipp
LOG = logging.getLogger(__name__)
as a subset of their exception message
(str(exception)) can be ignored
- :raises: NexusConfigFailed
+ :returns None: if config was edited successfully
+ Exception object: if _edit_config() encountered an exception
+ containing one of allowed_exc_strs
+
+ :raises: NexusConfigFailed: if _edit_config() encountered an exception
+ not containing one of allowed_exc_strs
"""
if not allowed_exc_strs:
allowed_exc_strs = []
mgr = self.nxos_connect(nexus_host)
try:
+ LOG.debug("NexusDriver config: %s", config)
mgr.edit_config(target=target, config=config)
except Exception as e:
for exc_str in allowed_exc_strs:
if exc_str in str(e):
- break
- else:
+ return e
# Raise a Neutron exception. Include a description of
# the original ncclient exception.
- raise cexc.NexusConfigFailed(config=config, exc=e)
+ raise cexc.NexusConfigFailed(config=config, exc=e)
def nxos_connect(self, nexus_host):
"""Make SSH connection to the Nexus Switch."""
"""Create a VLAN on Nexus Switch given the VLAN ID and Name."""
confstr = self.create_xml_snippet(
snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname))
- LOG.debug(_("NexusDriver: %s"), confstr)
self._edit_config(nexus_host, target='running', config=confstr)
# Enable VLAN active and no-shutdown states. Some versions of
confstr = self.create_xml_snippet(confstr)
self._edit_config(nexus_host, target='running', config=confstr)
- def enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type,
- interface):
- """Enable a VLAN on a trunk interface."""
- # If more than one VLAN is configured on this interface then
- # include the 'add' keyword.
- if len(nexus_db_v2.get_port_switch_bindings(
- '%s:%s' % (intf_type, interface), nexus_host)) == 1:
- snippet = snipp.CMD_INT_VLAN_SNIPPET
- else:
- snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET
+ 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)
confstr = self.create_xml_snippet(confstr)
LOG.debug(_("NexusDriver: %s"), confstr)
- self._edit_config(nexus_host, target='running', config=confstr)
+ return confstr
+
+ def enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type,
+ interface):
+ """Enable a VLAN on a trunk interface."""
+ # Configure a new VLAN into the interface using 'ADD' keyword
+ confstr = self.build_intf_confstr(
+ snippet=snipp.CMD_INT_VLAN_ADD_SNIPPET,
+ intf_type=intf_type,
+ interface=interface,
+ vlanid=vlanid
+ )
+ exc_str = ["switchport trunk allowed vlan list is empty"]
+ ret_exc = self._edit_config(nexus_host, target='running',
+ config=confstr,
+ allowed_exc_strs=exc_str)
+ if ret_exc:
+ # If no switchports have been configured on the switch
+ # before the new 'ADD', configure the VLAN into the
+ # interface without the keyword so as to create a vlan list
+ confstr = self.build_intf_confstr(
+ snippet=snipp.CMD_INT_VLAN_SNIPPET,
+ intf_type=intf_type,
+ interface=interface,
+ vlanid=vlanid
+ )
+ self._edit_config(nexus_host, target='running',
+ config=confstr)
def disable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type,
interface):
confstr = (snipp.CMD_NO_VLAN_INT_SNIPPET %
(intf_type, interface, vlanid, intf_type))
confstr = self.create_xml_snippet(confstr)
- LOG.debug(_("NexusDriver: %s"), confstr)
self._edit_config(nexus_host, target='running', config=confstr)
def create_and_trunk_vlan(self, nexus_host, vlan_id, vlan_name,
return all(word in last_cfg for word in words)
def _is_vlan_configured(self, vlan_creation_expected=True,
- add_keyword_expected=False):
+ first_vlan_addition=False):
+ """Confirm if VLAN was configured or not."""
vlan_created = self._is_in_nexus_cfg(['vlan', 'vlan-name'])
add_appears = self._is_in_last_nexus_cfg(['add'])
+ # The first VLAN being configured should be done without the
+ # ADD keyword. Thereafter additional VLANs to be configured
+ # should be done with the ADD keyword.
+ add_keyword_expected = not first_vlan_addition
return (self._is_in_last_nexus_cfg(['allowed', 'vlan']) and
vlan_created == vlan_creation_expected and
add_appears == add_keyword_expected)
expected_http = wexc.HTTPInternalServerError.code
self.assertEqual(status, expected_http)
+ def _mock_config_first_trunk(self):
+ """Mock the behavior for the first VLAN addition.
+
+ When the first VLAN is being added to the interface the usage of
+ the ADD keyword should raise an exception specifying that the ADD
+ keyword cannot be used till a VLAN list is created and to create
+ a VLAN list the configuration should not contain the ADD keyword.
+
+ """
+ config = "switchport trunk allowed vlan add"
+ exc = Exception("switchport trunk allowed vlan list is empty, "
+ "please specify a list using "
+ "'switchport trunk allowed vlan X' "
+ "before using the add option")
+ return (self._patch_ncclient(
+ 'connect.return_value.edit_config.side_effect',
+ self._config_dependent_side_effect(config, exc)))
+
def test_create_ports_bulk_emulated_plugin_failure(self):
real_has_attr = hasattr
'ports',
wexc.HTTPInternalServerError.code)
- def test_nexus_enable_vlan_cmd(self):
+ def test_nexus_enable_vlan_cmd_on_same_host(self):
"""Verify the syntax of the command to enable a vlan on an intf.
Test of the following ml2_conf_cisco_ini config:
[ml2_mech_cisco_nexus:1.1.1.1]
- hostA=1/1
- hostB=1/2 (second pass only)
- where vlan_id = 100
+ Resource A on host=COMP_HOST_NAME with vlan_id = 1000
+ Resource B on host=COMP_HOST_NAME with vlan_id = 1001
- Confirm that for the first VLAN configured on a Nexus interface,
- the command string sent to the switch does not contain the
- keyword 'add'.
+ Confirm that when configuring the first VLAN on a Nexus interface,
+ the final command string sent to the switch does not contain the
+ keyword 'add'. The initial attempt will contain 'add' but when
+ the switch rejects it, the re-attempt shouldn't contain the 'add'.
Confirm that for the second VLAN configured on a Nexus interface,
the command string sent to the switch contains the keyword 'add'
- if it is on the same host. Confirm it does not contain the
- keyword 'add' when on different hosts.
+ since it is on the same host.
+
+ """
+ # First vlan should be configured without 'add' keyword and an
+ # exception should be raised when it is done with the 'add'
+ # thereby triggering a re-attempt without the 'add'.
+ with self._mock_config_first_trunk():
+ with self._create_resources():
+ self.assertTrue(self._is_vlan_configured(
+ vlan_creation_expected=True,
+ first_vlan_addition=True))
+ self.mock_ncclient.reset_mock()
+ self.mock_bound_segment.return_value = BOUND_SEGMENT2
+
+ # Second vlan should be configured with the 'add' keyword
+ # when on first host.
+ with(self._patch_ncclient(
+ 'connect.return_value.edit_config.side_effect',
+ None)):
+ with self._create_resources(name=NETWORK_NAME_2,
+ device_id=DEVICE_ID_2,
+ cidr=CIDR_2,
+ host_id=COMP_HOST_NAME):
+ self.assertTrue(self._is_vlan_configured(
+ vlan_creation_expected=True,
+ first_vlan_addition=False
+ ))
+
+ # Return to first segment for delete port calls.
+ self.mock_bound_segment.return_value = BOUND_SEGMENT1
+
+ def test_nexus_enable_vlan_cmd_on_different_hosts(self):
+ """Verify the syntax of the command to enable a vlan on an intf.
+
+ Test of the following ml2_conf_cisco_ini config:
+ [ml2_mech_cisco_nexus:1.1.1.1]
+ Resource A on host=COMP_HOST_NAME with vlan_id = 1000
+ Resource B on host=COMP_HOST_NAME_2 with vlan_id = 1001
+
+ Confirm that when configuring the first VLAN on a Nexus interface,
+ the final command string sent to the switch does not contain the
+ keyword 'add'. The initial attempt will contain 'add' but when
+ the switch rejects it, the re-attempt shouldn't contain the 'add'.
+
+ Confirm that for the second VLAN configured on a Nexus interface,
+ the command string sent to the switch does not contain the
+ keyword 'add' since it is on a different host.
"""
- hosts = [COMP_HOST_NAME, COMP_HOST_NAME_2]
- for host in hosts:
- # First vlan should be configured without 'add' keyword
+ # First vlan should be configured without 'add' keyword and an
+ # exception should be raised when it is done with the 'add'
+ # thereby triggering a re-attempt without the 'add'.
+ with self._mock_config_first_trunk():
with self._create_resources():
self.assertTrue(self._is_vlan_configured(
- vlan_creation_expected=True,
- add_keyword_expected=False))
+ vlan_creation_expected=True,
+ first_vlan_addition=True))
self.mock_ncclient.reset_mock()
self.mock_bound_segment.return_value = BOUND_SEGMENT2
- # Second vlan should be configured with 'add' keyword
- # when on a single host.
+ # Second vlan should be configured without the 'add' keyword
+ # when on second host.
with self._create_resources(name=NETWORK_NAME_2,
- device_id=DEVICE_ID_2,
- cidr=CIDR_2,
- host_id=host):
+ device_id=DEVICE_ID_2,
+ cidr=CIDR_2,
+ host_id=COMP_HOST_NAME_2):
self.assertTrue(self._is_vlan_configured(
- vlan_creation_expected=True,
- add_keyword_expected=(host == COMP_HOST_NAME)
+ vlan_creation_expected=True,
+ first_vlan_addition=True
))
# Return to first segment for delete port calls.
req.get_response(self.api)
self.assertTrue(self._is_vlan_configured(
vlan_creation_expected=vlan_creation_expected,
- add_keyword_expected=False))
+ first_vlan_addition=True))
self.mock_ncclient.reset_mock()
yield
self._delete('ports', port['port']['id'])
# Create network and subnet
- with self.network(name=NETWORK_NAME) as network:
- with self.subnet(network=network, cidr=CIDR_1) as subnet:
-
- # Create an instance on first compute host
- with _create_port_check_vlan(COMP_HOST_NAME, DEVICE_ID_1,
- vlan_creation_expected=True):
- # Create an instance on second compute host
- with _create_port_check_vlan(COMP_HOST_NAME_2, DEVICE_ID_2,
- vlan_creation_expected=False):
- pass
-
- # Instance on second host is now terminated.
- # Vlan should be untrunked from port, but vlan should
- # still exist on the switch.
+ with self._mock_config_first_trunk():
+ with self.network(name=NETWORK_NAME) as network:
+ with self.subnet(network=network, cidr=CIDR_1) as subnet:
+
+ # Create an instance on first compute host
+ with _create_port_check_vlan(COMP_HOST_NAME, DEVICE_ID_1,
+ vlan_creation_expected=True):
+ # Create an instance on second compute host
+ with _create_port_check_vlan(
+ COMP_HOST_NAME_2,
+ DEVICE_ID_2,
+ vlan_creation_expected=False):
+ pass
+
+ # Instance on second host is now terminated.
+ # Vlan should be untrunked from port, but vlan should
+ # still exist on the switch.
+ self.assertTrue(self._is_vlan_unconfigured(
+ vlan_deletion_expected=False))
+ self.mock_ncclient.reset_mock()
+
+ # Instance on first host is now terminated.
+ # Vlan should be untrunked from port and vlan should have
+ # been deleted from the switch.
self.assertTrue(self._is_vlan_unconfigured(
- vlan_deletion_expected=False))
- self.mock_ncclient.reset_mock()
-
- # Instance on first host is now terminated.
- # Vlan should be untrunked from port and vlan should have
- # been deleted from the switch.
- self.assertTrue(self._is_vlan_unconfigured(
- vlan_deletion_expected=True))
+ vlan_deletion_expected=True))
def test_nexus_vm_migration(self):
"""Verify VM (live) migration.