diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..2cbda291 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +--- +language: python +python: "2.7" + +# Run jobs in VMs - sudo is required by ansible tests. +sudo: required + +# Install ansible +addons: + apt: + packages: + - gcc + - python-apt + - python-virtualenv + - realpath + +# Create a build matrix for the different test jobs. +env: + matrix: + # Run python style checks. + - TOX_ENV=pep8 + # Build documentation. + - TOX_ENV=docs + # Run python2.7 unit tests. + - TOX_ENV=py27 + +install: + # Install tox in a virtualenv to ensure we have an up to date version. + - pip install -U pip + - pip install tox + +script: + # Run the tox environment. + - tox -e ${TOX_ENV} diff --git a/networking_generic_switch/devices/__init__.py b/networking_generic_switch/devices/__init__.py index 31c52f63..9e986273 100644 --- a/networking_generic_switch/devices/__init__.py +++ b/networking_generic_switch/devices/__init__.py @@ -33,6 +33,8 @@ {'name': 'ngs_ssh_connect_timeout', 'default': 60}, {'name': 'ngs_ssh_connect_interval', 'default': 10}, {'name': 'ngs_max_connections', 'default': 1}, + # If True, disable switch ports that are not in use. + {'name': 'ngs_disable_inactive_ports', 'default': False}, ] @@ -91,6 +93,15 @@ def _get_physical_networks(self): return [] return physnets.split(',') + @staticmethod + def _str_to_bool(value): + truthy = ('true', 'yes', '1') + return str(value).lower() in truthy + + def _disable_inactive_ports(self): + """Return whether inactive ports should be disabled.""" + return self._str_to_bool(self.ngs_config['ngs_disable_inactive_ports']) + @abc.abstractmethod def add_network(self, segmentation_id, network_id): pass diff --git a/networking_generic_switch/devices/netmiko_devices/__init__.py b/networking_generic_switch/devices/netmiko_devices/__init__.py index c1e246b7..2ae915a3 100644 --- a/networking_generic_switch/devices/netmiko_devices/__init__.py +++ b/networking_generic_switch/devices/netmiko_devices/__init__.py @@ -42,6 +42,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice): DELETE_PORT = None + ENABLE_PORT = None + + DISABLE_PORT = None + PLUG_TRUNK_PORT_TO_NETWORK = None UNPLUG_TRUNK_PORT_FROM_NETWORK = None @@ -179,16 +183,21 @@ def del_network(self, segmentation_id, network_id): self.send_commands_to_device(cmds) def plug_port_to_network(self, port, segmentation_id): - self.send_commands_to_device( - self._format_commands(self.PLUG_PORT_TO_NETWORK, - port=port, - segmentation_id=segmentation_id)) + cmds = [] + if self._disable_inactive_ports() and self.ENABLE_PORT: + cmds += self._format_commands(self.ENABLE_PORT, port=port) + cmds += self._format_commands(self.PLUG_PORT_TO_NETWORK, + port=port, + segmentation_id=segmentation_id) + self.send_commands_to_device(cmds) def delete_port(self, port, segmentation_id): - self.send_commands_to_device( - self._format_commands(self.DELETE_PORT, - port=port, - segmentation_id=segmentation_id)) + cmds = self._format_commands(self.DELETE_PORT, + port=port, + segmentation_id=segmentation_id) + if self._disable_inactive_ports() and self.DISABLE_PORT: + cmds += self._format_commands(self.DISABLE_PORT, port=port) + self.send_commands_to_device(cmds) def send_config_set(self, net_connect, cmd_set): """Send a set of configuration lines to the device. diff --git a/networking_generic_switch/devices/netmiko_devices/juniper.py b/networking_generic_switch/devices/netmiko_devices/juniper.py index 9c3dde14..5ccacde5 100644 --- a/networking_generic_switch/devices/netmiko_devices/juniper.py +++ b/networking_generic_switch/devices/netmiko_devices/juniper.py @@ -55,6 +55,14 @@ class Juniper(netmiko_devices.NetmikoSwitch): 'vlan members', ) + ENABLE_PORT = ( + 'delete interface {port} disable', + ) + + DISABLE_PORT = ( + 'set interface {port} disable', + ) + PLUG_TRUNK_PORT_TO_NETWORK = ( 'set interface {port} unit 0 family ethernet-switching ' 'vlan members {segmentation_id}', diff --git a/networking_generic_switch/tests/unit/netmiko/test_juniper.py b/networking_generic_switch/tests/unit/netmiko/test_juniper.py index 2d599861..56ee092f 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_juniper.py +++ b/networking_generic_switch/tests/unit/netmiko/test_juniper.py @@ -80,6 +80,19 @@ def test_plug_port_to_network(self, mock_exec): 'set interface 3333 unit 0 family ethernet-switching ' 'vlan members 33']) + @mock.patch('networking_generic_switch.devices.netmiko_devices.' + 'NetmikoSwitch.send_commands_to_device') + def test_plug_port_to_network_disable_inactive(self, m_sctd): + switch = self._make_switch_device( + {'ngs_disable_inactive_ports': 'true'}) + switch.plug_port_to_network(3333, 33) + m_sctd.assert_called_with( + ['delete interface 3333 disable', + 'delete interface 3333 unit 0 family ethernet-switching ' + 'vlan members', + 'set interface 3333 unit 0 family ethernet-switching ' + 'vlan members 33']) + @mock.patch('networking_generic_switch.devices.netmiko_devices.' 'NetmikoSwitch.send_commands_to_device') def test_delete_port(self, mock_exec): @@ -88,6 +101,17 @@ def test_delete_port(self, mock_exec): ['delete interface 3333 unit 0 family ethernet-switching ' 'vlan members']) + @mock.patch('networking_generic_switch.devices.netmiko_devices.' + 'NetmikoSwitch.send_commands_to_device') + def test_delete_port_disable_inactive(self, m_sctd): + switch = self._make_switch_device( + {'ngs_disable_inactive_ports': 'true'}) + switch.delete_port(3333, 33) + m_sctd.assert_called_with( + ['delete interface 3333 unit 0 family ethernet-switching ' + 'vlan members', + 'set interface 3333 disable']) + def test_send_config_set(self): connect_mock = mock.MagicMock(netmiko.base_connection.BaseConnection) connect_mock.send_config_set.return_value = 'fake output' diff --git a/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py b/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py index d8356f6c..31b08d0b 100644 --- a/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py +++ b/networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py @@ -76,11 +76,27 @@ def test_plug_port_to_network(self, m_sctd): self.switch.plug_port_to_network(2222, 22) m_sctd.assert_called_with([]) + @mock.patch('networking_generic_switch.devices.netmiko_devices.' + 'NetmikoSwitch.send_commands_to_device') + def test_plug_port_to_network_disable_inactive(self, m_sctd): + switch = self._make_switch_device( + {'ngs_disable_inactive_ports': 'true'}) + switch.plug_port_to_network(2222, 22) + m_sctd.assert_called_with([]) + @mock.patch('networking_generic_switch.devices.netmiko_devices.' 'NetmikoSwitch.send_commands_to_device') def test_delete_port(self, m_sctd): self.switch.delete_port(2222, 22) - m_sctd.assert_called_with(None) + m_sctd.assert_called_with([]) + + @mock.patch('networking_generic_switch.devices.netmiko_devices.' + 'NetmikoSwitch.send_commands_to_device') + def test_delete_port_disable_inactive(self, m_sctd): + switch = self._make_switch_device( + {'ngs_disable_inactive_ports': 'true'}) + switch.delete_port(2222, 22) + m_sctd.assert_called_with([]) def test__format_commands(self): self.switch._format_commands( diff --git a/tools/tox_install.sh b/tools/tox_install.sh index 30b6a805..48ece0b4 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -5,7 +5,7 @@ set -ex ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner -BRANCH_NAME=master +BRANCH_NAME=stable/pike CONSTRAINTS_FILE=$1 shift diff --git a/tox.ini b/tox.ini index fa92b965..948da021 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = py35,py27,pep8 [testenv] usedevelop = True install_command = - {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} + {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/pike} {opts} {packages} setenv = VIRTUAL_ENV={envdir} PYTHONDONTWRITEBYTECODE = 1 PYTHONWARNINGS=default::DeprecationWarning