From ff4a261ae468dee12545d907c496cd43dd9bf0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Sat, 24 Mar 2018 23:36:20 +0100 Subject: [PATCH] Add support for trunk ports and subports This patch adds support for Neutron's trunks CRUD. It also adds subport for create/get/delete trunk's sub_ports. Change-Id: I835591ad517b84078a8ca87ef4d0bb93258c6e1c --- .zuul.yaml | 1 + openstack/network/v2/_proxy.py | 117 ++++++++++++++++++ openstack/network/v2/trunk.py | 72 +++++++++++ .../tests/functional/network/v2/test_trunk.py | 84 +++++++++++++ openstack/tests/unit/network/v2/test_trunk.py | 57 +++++++++ 5 files changed, 331 insertions(+) create mode 100644 openstack/network/v2/trunk.py create mode 100644 openstack/tests/functional/network/v2/test_trunk.py create mode 100644 openstack/tests/unit/network/v2/test_trunk.py diff --git a/.zuul.yaml b/.zuul.yaml index 5f3e1293e..a6e909948 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -134,6 +134,7 @@ o-hm: true o-hk: true neutron-qos: true + neutron-trunk: true tox_environment: OPENSTACKSDK_HAS_OCTAVIA: 1 diff --git a/openstack/network/v2/_proxy.py b/openstack/network/v2/_proxy.py index a05f472f0..f61335af7 100644 --- a/openstack/network/v2/_proxy.py +++ b/openstack/network/v2/_proxy.py @@ -47,6 +47,7 @@ from openstack.network.v2 import service_profile as _service_profile from openstack.network.v2 import service_provider as _service_provider from openstack.network.v2 import subnet as _subnet from openstack.network.v2 import subnet_pool as _subnet_pool +from openstack.network.v2 import trunk as _trunk from openstack.network.v2 import vpn_service as _vpn_service from openstack import proxy from openstack import utils @@ -3046,6 +3047,122 @@ class Proxy(proxy.Proxy): self._check_tag_support(resource) return resource.set_tags(self, tags) + def create_trunk(self, **attrs): + """Create a new trunk from attributes + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~openstack.network.v2.trunk.Trunk, + comprised of the properties on the Trunk class. + + :returns: The results of trunk creation + :rtype: :class:`~openstack.network.v2.trunk.Trunk` + """ + return self._create(_trunk.Trunk, **attrs) + + def delete_trunk(self, trunk, ignore_missing=True): + """Delete a trunk + + :param trunk: The value can be either the ID of trunk or a + :class:`openstack.network.v2.trunk.Trunk` instance + + :returns: ``None`` + """ + self._delete(_trunk.Trunk, trunk, ignore_missing=ignore_missing) + + def find_trunk(self, name_or_id, ignore_missing=True, **args): + """Find a single trunk + + :param name_or_id: The name or ID of a trunk. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the resource does not exist. + When set to ``True``, None will be returned when + attempting to find a nonexistent resource. + :param dict args: Any additional parameters to be passed into + underlying methods. such as query filters. + :returns: One :class:`~openstack.network.v2.trunk.Trunk` + or None + """ + return self._find(_trunk.Trunk, name_or_id, + ignore_missing=ignore_missing, **args) + + def get_trunk(self, trunk): + """Get a single trunk + + :param trunk: The value can be the ID of a trunk or a + :class:`~openstack.network.v2.trunk.Trunk` instance. + + :returns: One + :class:`~openstack.network.v2.trunk.Trunk` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._get(_trunk.Trunk, trunk) + + def trunks(self, **query): + """Return a generator of trunks + + :param dict query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of trunk objects + :rtype: :class:`~openstack.network.v2.trunk.trunk` + """ + return self._list(_trunk.Trunk, paginated=False, **query) + + def update_trunk(self, trunk, **attrs): + """Update a trunk + + :param trunk: Either the id of a trunk or a + :class:`~openstack.network.v2.trunk.Trunk` instance. + :param dict attrs: The attributes to update on the trunk + represented by ``trunk``. + + :returns: The updated trunk + :rtype: :class:`~openstack.network.v2.trunk.Trunk` + """ + return self._update(_trunk.Trunk, trunk, **attrs) + + def add_trunk_subports(self, trunk, subports): + """Set sub_ports on trunk + + :param trunk: The value can be the ID of a trunk or a + :class:`~openstack.network.v2.trunk.Trunk` instance. + :param subports: New subports to be set. + :type subports: "list" + + :returns: The updated trunk + :rtype: :class:`~openstack.network.v2.trunk.Trunk` + """ + trunk = self._get_resource(_trunk.Trunk, trunk) + return trunk.add_subports(self, subports) + + def delete_trunk_subports(self, trunk, subports): + """Remove sub_ports from trunk + + :param trunk: The value can be the ID of a trunk or a + :class:`~openstack.network.v2.trunk.Trunk` instance. + :param subports: Subports to be removed. + :type subports: "list" + + :returns: The updated trunk + :rtype: :class:`~openstack.network.v2.trunk.Trunk` + """ + trunk = self._get_resource(_trunk.Trunk, trunk) + return trunk.delete_subports(self, subports) + + def get_trunk_subports(self, trunk): + """Get sub_ports configured on trunk + + :param trunk: The value can be the ID of a trunk or a + :class:`~openstack.network.v2.trunk.Trunk` instance. + + :returns: Trunk sub_ports + :rtype: "list" + """ + trunk = self._get_resource(_trunk.Trunk, trunk) + return trunk.get_subports(self) + def create_vpn_service(self, **attrs): """Create a new vpn service from attributes diff --git a/openstack/network/v2/trunk.py b/openstack/network/v2/trunk.py new file mode 100644 index 000000000..875a08790 --- /dev/null +++ b/openstack/network/v2/trunk.py @@ -0,0 +1,72 @@ +# 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. + +from openstack.network import network_service +from openstack import resource +from openstack import utils + + +class Trunk(resource.Resource): + resource_key = 'trunk' + resources_key = 'trunks' + base_path = '/trunks' + service = network_service.NetworkService() + + # capabilities + allow_create = True + allow_get = True + allow_update = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'name', 'description', 'port_id', 'status', 'sub_ports', + project_id='tenant_id', + is_admin_state_up='admin_state_up', + ) + + # Properties + #: Trunk name. + name = resource.Body('name') + #: The ID of the project who owns the trunk. Only administrative + #: users can specify a project ID other than their own. + project_id = resource.Body('tenant_id') + #: The trunk description. + description = resource.Body('description') + #: The administrative state of the port, which is up ``True`` or + #: down ``False``. *Type: bool* + is_admin_state_up = resource.Body('admin_state_up', type=bool) + #: The ID of the trunk's parent port + port_id = resource.Body('port_id') + #: The status for the trunk. Possible values are ACTIVE, DOWN, BUILD, + #: DEGRADED, and ERROR. + status = resource.Body('status') + #: A list of ports associated with the trunk. + sub_ports = resource.Body('sub_ports', type=list) + + def add_subports(self, session, subports): + url = utils.urljoin('/trunks', self.id, 'add_subports') + session.put(url, json={'sub_ports': subports}) + self._body.attributes.update({'sub_ports': subports}) + return self + + def delete_subports(self, session, subports): + url = utils.urljoin('/trunks', self.id, 'remove_subports') + session.put(url, json={'sub_ports': subports}) + self._body.attributes.update({'sub_ports': subports}) + return self + + def get_subports(self, session): + url = utils.urljoin('/trunks', self.id, 'get_subports') + resp = session.get(url) + self._body.attributes.update(resp.json()) + return resp.json() diff --git a/openstack/tests/functional/network/v2/test_trunk.py b/openstack/tests/functional/network/v2/test_trunk.py new file mode 100644 index 000000000..62b625998 --- /dev/null +++ b/openstack/tests/functional/network/v2/test_trunk.py @@ -0,0 +1,84 @@ +# 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. + + +from openstack.network.v2 import network +from openstack.network.v2 import port +from openstack.network.v2 import trunk as _trunk +from openstack.tests.functional import base + + +class TestTrunk(base.BaseFunctionalTest): + + def setUp(self): + super(TestTrunk, self).setUp() + self.TRUNK_NAME = self.getUniqueString() + self.TRUNK_NAME_UPDATED = self.getUniqueString() + net = self.conn.network.create_network() + assert isinstance(net, network.Network) + self.NET_ID = net.id + prt = self.conn.network.create_port(network_id=self.NET_ID) + assert isinstance(prt, port.Port) + self.PORT_ID = prt.id + self.ports_to_clean = [self.PORT_ID] + trunk = self.conn.network.create_trunk( + name=self.TRUNK_NAME, + port_id=self.PORT_ID) + assert isinstance(trunk, _trunk.Trunk) + self.TRUNK_ID = trunk.id + + def tearDown(self): + self.conn.network.delete_trunk(self.TRUNK_ID, ignore_missing=False) + for port_id in self.ports_to_clean: + self.conn.network.delete_port(port_id, ignore_missing=False) + self.conn.network.delete_network(self.NET_ID, ignore_missing=False) + super(TestTrunk, self).tearDown() + + def test_find(self): + sot = self.conn.network.find_trunk(self.TRUNK_NAME) + self.assertEqual(self.TRUNK_ID, sot.id) + + def test_get(self): + sot = self.conn.network.get_trunk(self.TRUNK_ID) + self.assertEqual(self.TRUNK_ID, sot.id) + self.assertEqual(self.TRUNK_NAME, sot.name) + + def test_list(self): + ids = [o.id for o in self.conn.network.trunks()] + self.assertIn(self.TRUNK_ID, ids) + + def test_update(self): + sot = self.conn.network.update_trunk(self.TRUNK_ID, + name=self.TRUNK_NAME_UPDATED) + self.assertEqual(self.TRUNK_NAME_UPDATED, sot.name) + + def test_subports(self): + port_for_subport = self.conn.network.create_port( + network_id=self.NET_ID) + self.ports_to_clean.append(port_for_subport.id) + subports = [{ + 'port_id': port_for_subport.id, + 'segmentation_type': 'vlan', + 'segmentation_id': 111 + }] + + sot = self.conn.network.get_trunk_subports(self.TRUNK_ID) + self.assertEqual({'sub_ports': []}, sot) + + self.conn.network.add_trunk_subports(self.TRUNK_ID, subports) + sot = self.conn.network.get_trunk_subports(self.TRUNK_ID) + self.assertEqual({'sub_ports': subports}, sot) + + self.conn.network.delete_trunk_subports( + self.TRUNK_ID, [{'port_id': port_for_subport.id}]) + sot = self.conn.network.get_trunk_subports(self.TRUNK_ID) + self.assertEqual({'sub_ports': []}, sot) diff --git a/openstack/tests/unit/network/v2/test_trunk.py b/openstack/tests/unit/network/v2/test_trunk.py new file mode 100644 index 000000000..47abc14a9 --- /dev/null +++ b/openstack/tests/unit/network/v2/test_trunk.py @@ -0,0 +1,57 @@ +# 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. + +from openstack.tests.unit import base + +from openstack.network.v2 import trunk + +EXAMPLE = { + 'id': 'IDENTIFIER', + 'description': 'Trunk description', + 'name': 'trunk-name', + 'tenant_id': '2', + 'admin_state_up': True, + 'port_id': 'fake_port_id', + 'status': 'ACTIVE', + 'sub_ports': [{ + 'port_id': 'subport_port_id', + 'segmentation_id': 1234, + 'segmentation_type': 'vlan' + }] + +} + + +class TestQoSPolicy(base.TestCase): + + def test_basic(self): + sot = trunk.Trunk() + self.assertEqual('trunk', sot.resource_key) + self.assertEqual('trunks', sot.resources_key) + self.assertEqual('/trunks', sot.base_path) + self.assertEqual('network', sot.service.service_type) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_get) + self.assertTrue(sot.allow_update) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = trunk.Trunk(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['description'], sot.description) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['tenant_id'], sot.project_id) + self.assertEqual(EXAMPLE['admin_state_up'], sot.is_admin_state_up) + self.assertEqual(EXAMPLE['port_id'], sot.port_id) + self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['sub_ports'], sot.sub_ports)