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
This commit is contained in:
Sławek Kapłoński 2018-03-24 23:36:20 +01:00
parent abe16d3dd4
commit ff4a261ae4
5 changed files with 331 additions and 0 deletions

View File

@ -134,6 +134,7 @@
o-hm: true
o-hk: true
neutron-qos: true
neutron-trunk: true
tox_environment:
OPENSTACKSDK_HAS_OCTAVIA: 1

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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)