From d10b92745b2bb7c623d550e9c77f91730e403e54 Mon Sep 17 00:00:00 2001 From: Luis Tomas Bolivar Date: Mon, 28 Aug 2017 18:24:04 +0200 Subject: [PATCH] Add methods to populate/free subport pools This patch adds the needed methods to be able to populate/free specific pools for the nested-vlan pool driver. By using this methods X subports can be created and attached to a given set of pools (in this case of trunk ports), so that pools can be pre-populated with the required amount of port in advance, without the need of allocating any pod. Additionaly, it also allows to remove all unused subports from the given pool(s). Change-Id: I1f06427f095429f1149ac2a7cb9dd5341a84674f --- .../controller/drivers/nested_vlan_vif.py | 10 ++- .../controller/drivers/vif_pool.py | 88 +++++++++++++++++-- .../drivers/test_nested_vlan_vif.py | 5 +- .../unit/controller/drivers/test_vif_pool.py | 57 +++++++++--- 4 files changed, 137 insertions(+), 23 deletions(-) diff --git a/kuryr_kubernetes/controller/drivers/nested_vlan_vif.py b/kuryr_kubernetes/controller/drivers/nested_vlan_vif.py index f09379b45..01ad74cc5 100644 --- a/kuryr_kubernetes/controller/drivers/nested_vlan_vif.py +++ b/kuryr_kubernetes/controller/drivers/nested_vlan_vif.py @@ -46,7 +46,7 @@ class NestedVlanPodVIFDriver(nested_vif.NestedPodVIFDriver): return ovu.neutron_to_osvif_vif_nested_vlan(port, subnets, vlan_id) def request_vifs(self, pod, project_id, subnets, security_groups, - num_ports): + num_ports, trunk_ip=None): """This method creates subports and returns a list with their vifs. It creates up to num_ports subports and attaches them to the trunk @@ -61,7 +61,10 @@ class NestedVlanPodVIFDriver(nested_vif.NestedPodVIFDriver): exception is raised. """ neutron = clients.get_neutron_client() - parent_port = self._get_parent_port(neutron, pod) + if trunk_ip: + parent_port = self._get_parent_port_by_host_ip(neutron, trunk_ip) + else: + parent_port = self._get_parent_port(neutron, pod) trunk_id = self._get_trunk_id(parent_port) port_rq, subports_info = self._create_subports_info( @@ -116,7 +119,6 @@ class NestedVlanPodVIFDriver(nested_vif.NestedPodVIFDriver): def _get_port_request(self, pod, project_id, subnets, security_groups, unbound=False): port_req_body = {'project_id': project_id, - 'name': self._get_port_name(pod), 'network_id': self._get_network_id(subnets), 'fixed_ips': ovu.osvif_to_neutron_fixed_ips(subnets), 'device_owner': kl_const.DEVICE_OWNER, @@ -124,6 +126,8 @@ class NestedVlanPodVIFDriver(nested_vif.NestedPodVIFDriver): if unbound: port_req_body['name'] = 'available-port' + else: + port_req_body['name'] = self._get_port_name(pod) if security_groups: port_req_body['security_groups'] = security_groups diff --git a/kuryr_kubernetes/controller/drivers/vif_pool.py b/kuryr_kubernetes/controller/drivers/vif_pool.py index 0ed5680aa..6a186a4d2 100644 --- a/kuryr_kubernetes/controller/drivers/vif_pool.py +++ b/kuryr_kubernetes/controller/drivers/vif_pool.py @@ -348,6 +348,21 @@ class NestedVIFPool(BaseVIFPool): return parent_port['fixed_ips'][0]['ip_address'] def _recover_precreated_ports(self): + self._precreated_ports(action='recover') + + def _remove_precreated_ports(self, trunk_ips=None): + self._precreated_ports(action='free', trunk_ips=trunk_ips) + + def _precreated_ports(self, action, trunk_ips=None): + """Removes or recovers pre-created subports at given pools + + This function handles the pre-created ports based on the given action: + - If action is `free` it will remove all the subport from the given + trunk ports, or from all the trunk ports if no trunk_ips are passed. + - If action is `recover` it will discover the existing subports in the + given trunk ports (or in all of them if none are passed) and will add + them (and the needed information) to the respective pools. + """ neutron = clients.get_neutron_client() # Note(ltomasbo): ML2/OVS changes the device_owner to trunk:subport # when a port is attached to a trunk. However, that is not the case @@ -369,6 +384,9 @@ class NestedVIFPool(BaseVIFPool): trunk['port_id']) continue + if trunk_ips and host_addr not in trunk_ips: + continue + for subport in trunk.get('sub_ports'): kuryr_subport = None for port in available_ports: @@ -379,11 +397,67 @@ class NestedVIFPool(BaseVIFPool): if kuryr_subport: pool_key = (host_addr, kuryr_subport['project_id'], tuple(kuryr_subport['security_groups'])) - subnet_id = kuryr_subport['fixed_ips'][0]['subnet_id'] - subnet = {subnet_id: default_subnet._get_subnet(subnet_id)} - vif = ovu.neutron_to_osvif_vif_nested_vlan( - kuryr_subport, subnet, subport['segmentation_id']) - self._existing_vifs[subport['port_id']] = vif - self._available_ports_pools.setdefault( - pool_key, []).append(subport['port_id']) + if action == 'recover': + subnet_id = kuryr_subport['fixed_ips'][0]['subnet_id'] + subnet = { + subnet_id: default_subnet._get_subnet(subnet_id)} + vif = ovu.neutron_to_osvif_vif_nested_vlan( + kuryr_subport, subnet, subport['segmentation_id']) + + self._existing_vifs[subport['port_id']] = vif + self._available_ports_pools.setdefault( + pool_key, []).append(subport['port_id']) + elif action == 'free': + try: + self._drv_vif._remove_subport(neutron, trunk['id'], + subport['port_id']) + neutron.delete_port(subport['port_id']) + self._drv_vif._release_vlan_id( + subport['segmentation_id']) + del self._existing_vifs[subport['port_id']] + self._available_ports_pools[pool_key].remove( + subport['port_id']) + except n_exc.PortNotFoundClient: + LOG.debug('Unable to release port %s as it no ' + 'longer exists.', subport['port_id']) + except KeyError: + LOG.debug('Port %s is not in the ports list.', + subport['port_id']) + except n_exc.NeutronClientException: + LOG.warning('Error removing the subport %s', + subport['port_id']) + except ValueError: + LOG.debug('Port %s is not in the available ports ' + 'pool.', subport['port_id']) + + def force_populate_pool(self, trunk_ip, project_id, subnets, + security_groups, num_ports): + """Create a given amount of subports at a given trunk port. + + This function creates a given amount of subports and attaches them to + the specified trunk, adding them to the related subports pool + regardless of the amount of subports already available in the pool. + """ + vifs = self._drv_vif.request_vifs( + pod=[], + project_id=project_id, + subnets=subnets, + security_groups=security_groups, + num_ports=num_ports, + trunk_ip=trunk_ip) + + pool_key = (trunk_ip, project_id, tuple(sorted(security_groups))) + for vif in vifs: + self._existing_vifs[vif.id] = vif + self._available_ports_pools.setdefault(pool_key, + []).append(vif.id) + + def free_pool(self, trunk_ips=None): + """Removes subports from the pool and deletes neutron port resource. + + This function empties the pool of available subports and removes the + neutron port resources of the specified trunk port (or all of them if + no trunk is specified). + """ + self._remove_precreated_ports(trunk_ips) diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_nested_vlan_vif.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_nested_vlan_vif.py index 2a7d8550f..2398040f8 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_nested_vlan_vif.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_nested_vlan_vif.py @@ -356,7 +356,10 @@ class TestNestedVlanPodVIFDriver(test_base.TestCase): security_groups, unbound) self.assertEqual(expected, ret) - m_driver._get_port_name.assert_called_once_with(pod) + if unbound: + m_driver._get_port_name.assert_not_called() + else: + m_driver._get_port_name.assert_called_once_with(pod) m_driver._get_network_id.assert_called_once_with(subnets) m_to_fips.assert_called_once_with(subnets) diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_vif_pool.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_vif_pool.py index 8f5b8f9d8..fd6257a26 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_vif_pool.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_vif_pool.py @@ -957,7 +957,7 @@ class NestedVIFPool(test_base.TestCase): 'neutron_to_osvif_vif_nested_vlan') @mock.patch('kuryr_kubernetes.controller.drivers.default_subnet.' '_get_subnet') - def test__recover_precreated_ports(self, m_get_subnet, m_to_osvif): + def test__precreated_ports_recover(self, m_get_subnet, m_to_osvif): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) neutron = self.useFixture(k_fix.MockNeutronClient()).client @@ -975,15 +975,45 @@ class NestedVIFPool(test_base.TestCase): m_get_subnet.return_value = mock.sentinel.subnet m_to_osvif.return_value = mock.sentinel.vif - cls._recover_precreated_ports(m_driver) + cls._precreated_ports(m_driver, 'recover') neutron.list_trunks.assert_called_once() m_driver._get_parent_port_ip.assert_called_with(trunk_id) + def test__precreated_ports_free(self): + cls = vif_pool.NestedVIFPool + m_driver = mock.MagicMock(spec=cls) + neutron = self.useFixture(k_fix.MockNeutronClient()).client + cls_vif_driver = nested_vlan_vif.NestedVlanPodVIFDriver + vif_driver = mock.MagicMock(spec=cls_vif_driver) + m_driver._drv_vif = vif_driver + + port_id = mock.sentinel.port_id + host_addr = mock.sentinel.host_addr + + subport_obj = get_port_obj(port_id=port_id, + device_owner='trunk:subport') + m_driver._get_ports_by_attrs.side_effect = [[subport_obj], []] + trunk_id = mock.sentinel.trunk_id + trunk_obj = self._get_trunk_obj(port_id=trunk_id, subport_id=port_id) + pool_key = (host_addr, subport_obj['id'], + tuple(subport_obj['security_groups'])) + m_driver._available_ports_pools = {pool_key: port_id} + + neutron.list_trunks.return_value = {'trunks': [trunk_obj]} + m_driver._get_parent_port_ip.return_value = host_addr + + cls._precreated_ports(m_driver, 'free') + neutron.list_trunks.assert_called_once() + m_driver._get_parent_port_ip.assert_called_with(trunk_id) + m_driver._drv_vif._remove_subport.assert_called_once() + neutron.delete_port.assert_called_once() + m_driver._drv_vif._release_vlan_id.assert_called_once() + @mock.patch('kuryr_kubernetes.os_vif_util.' 'neutron_to_osvif_vif_nested_vlan') @mock.patch('kuryr_kubernetes.controller.drivers.default_subnet.' '_get_subnet') - def test__recover_precreated_ports_several_trunks(self, m_get_subnet, + def test__precreated_ports_recover_several_trunks(self, m_get_subnet, m_to_osvif): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) @@ -1013,7 +1043,7 @@ class NestedVIFPool(test_base.TestCase): m_get_subnet.return_value = subnet m_to_osvif.return_value = mock.sentinel.vif - cls._recover_precreated_ports(m_driver) + cls._precreated_ports(m_driver, 'recover') neutron.list_trunks.asser_called_once() m_driver._get_parent_port_ip.assert_has_calls([mock.call(trunk_id1), mock.call(trunk_id2)]) @@ -1027,7 +1057,7 @@ class NestedVIFPool(test_base.TestCase): 'neutron_to_osvif_vif_nested_vlan') @mock.patch('kuryr_kubernetes.controller.drivers.default_subnet.' '_get_subnet') - def test__recover_precreated_ports_several_subports(self, m_get_subnet, + def test__precreated_ports_recover_several_subports(self, m_get_subnet, m_to_osvif): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) @@ -1055,7 +1085,7 @@ class NestedVIFPool(test_base.TestCase): m_get_subnet.return_value = subnet m_to_osvif.return_value = mock.sentinel.vif - cls._recover_precreated_ports(m_driver) + cls._precreated_ports(m_driver, 'recover') neutron.list_trunks.asser_called_once() m_driver._get_parent_port_ip.assert_called_once_with(trunk_id) calls = [mock.call(port1, {port1['fixed_ips'][0]['subnet_id']: subnet}, @@ -1064,17 +1094,19 @@ class NestedVIFPool(test_base.TestCase): trunk_obj['sub_ports'][1]['segmentation_id'])] m_to_osvif.assert_has_calls(calls) - def test__recover_precreated_ports_no_ports_to_recover(self): + @ddt.data(('recover'), ('free')) + def test__precreated_ports_no_ports(self, m_action): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) neutron = self.useFixture(k_fix.MockNeutronClient()).client m_driver._get_ports_by_attrs.return_value = [] - cls._recover_precreated_ports(m_driver) + cls._precreated_ports(m_driver, m_action) neutron.list_trunks.assert_not_called() - def test__recover_precreated_ports_no_trunks(self): + @ddt.data(('recover'), ('free')) + def test__precreated_ports_no_trunks(self, m_action): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) neutron = self.useFixture(k_fix.MockNeutronClient()).client @@ -1083,11 +1115,12 @@ class NestedVIFPool(test_base.TestCase): device_owner='trunk:subport')], []] neutron.list_trunks.return_value = {'trunks': []} - cls._recover_precreated_ports(m_driver) + cls._precreated_ports(m_driver, m_action) neutron.list_trunks.assert_called() m_driver._get_parent_port_ip.assert_not_called() - def test__recover_precreated_ports_exception(self): + @ddt.data(('recover'), ('free')) + def test__precreated_ports_exception(self, m_action): cls = vif_pool.NestedVIFPool m_driver = mock.MagicMock(spec=cls) neutron = self.useFixture(k_fix.MockNeutronClient()).client @@ -1100,6 +1133,6 @@ class NestedVIFPool(test_base.TestCase): neutron.list_trunks.return_value = {'trunks': [trunk_obj]} m_driver._get_parent_port_ip.side_effect = n_exc.PortNotFoundClient - self.assertIsNone(cls._recover_precreated_ports(m_driver)) + self.assertIsNone(cls._precreated_ports(m_driver, m_action)) neutron.list_trunks.assert_called() m_driver._get_parent_port_ip.assert_called_with(trunk_id)