Add neutron address group CRUD
Add support for neutron address groups CRUD operations. Subsequent patches will be added to use address groups in security group rules. Change-Id: I9252b37d252a46c6708947142705006a56a390c7 Implements: blueprint address-groups-in-sg-rules Depends-On: https://review.opendev.org/738274
This commit is contained in:
@@ -4,6 +4,7 @@ Network Resources
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
v2/address_group
|
||||||
v2/address_scope
|
v2/address_scope
|
||||||
v2/agent
|
v2/agent
|
||||||
v2/auto_allocated_topology
|
v2/auto_allocated_topology
|
||||||
|
12
doc/source/user/resources/network/v2/address_group.rst
Normal file
12
doc/source/user/resources/network/v2/address_group.rst
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
openstack.network.v2.address_group
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: openstack.network.v2.address_group
|
||||||
|
|
||||||
|
The AddressGroup Class
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The ``AddressGroup`` class inherits from :class:`~openstack.resource.Resource`.
|
||||||
|
|
||||||
|
.. autoclass:: openstack.network.v2.address_group.AddressGroup
|
||||||
|
:members:
|
@@ -11,6 +11,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from openstack import exceptions
|
from openstack import exceptions
|
||||||
|
from openstack.network.v2 import address_group as _address_group
|
||||||
from openstack.network.v2 import address_scope as _address_scope
|
from openstack.network.v2 import address_scope as _address_scope
|
||||||
from openstack.network.v2 import agent as _agent
|
from openstack.network.v2 import agent as _agent
|
||||||
from openstack.network.v2 import auto_allocated_topology as \
|
from openstack.network.v2 import auto_allocated_topology as \
|
||||||
@@ -80,6 +81,118 @@ class Proxy(proxy.Proxy):
|
|||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def create_address_group(self, **attrs):
|
||||||
|
"""Create a new address group from attributes
|
||||||
|
|
||||||
|
:param dict attrs: Keyword arguments which will be used to create
|
||||||
|
a :class:`~openstack.network.v2.address_group.AddressGroup`,
|
||||||
|
comprised of the properties on the AddressGroup class.
|
||||||
|
|
||||||
|
:returns: The results of address group creation
|
||||||
|
:rtype: :class:`~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
"""
|
||||||
|
return self._create(_address_group.AddressGroup, **attrs)
|
||||||
|
|
||||||
|
def delete_address_group(self, address_group, ignore_missing=True):
|
||||||
|
"""Delete an address group
|
||||||
|
|
||||||
|
:param address_group: The value can be either the ID of an
|
||||||
|
address group or
|
||||||
|
a :class:`~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
instance.
|
||||||
|
:param bool ignore_missing: When set to ``False``
|
||||||
|
:class:`~openstack.exceptions.ResourceNotFound` will
|
||||||
|
be raised when the address group does not exist.
|
||||||
|
When set to ``True``, no exception will be set when
|
||||||
|
attempting to delete a nonexistent address group.
|
||||||
|
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
|
self._delete(_address_group.AddressGroup, address_group,
|
||||||
|
ignore_missing=ignore_missing)
|
||||||
|
|
||||||
|
def find_address_group(self, name_or_id, ignore_missing=True, **args):
|
||||||
|
"""Find a single address group
|
||||||
|
|
||||||
|
:param name_or_id: The name or ID of an address group.
|
||||||
|
: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.address_group.AddressGroup`
|
||||||
|
or None
|
||||||
|
"""
|
||||||
|
return self._find(_address_group.AddressGroup, name_or_id,
|
||||||
|
ignore_missing=ignore_missing, **args)
|
||||||
|
|
||||||
|
def get_address_group(self, address_group):
|
||||||
|
"""Get a single address group
|
||||||
|
|
||||||
|
:param address_group: The value can be the ID of an address group or a
|
||||||
|
:class:`~openstack.network.v2.address_group.AddressGroup` instance.
|
||||||
|
|
||||||
|
:returns: One :class:`~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||||
|
when no resource can be found.
|
||||||
|
"""
|
||||||
|
return self._get(_address_group.AddressGroup, address_group)
|
||||||
|
|
||||||
|
def address_groups(self, **query):
|
||||||
|
"""Return a generator of address groups
|
||||||
|
|
||||||
|
:param dict query: Optional query parameters to be sent to limit
|
||||||
|
the resources being returned.
|
||||||
|
|
||||||
|
* ``name``: Address group name
|
||||||
|
* ``description``: Address group description
|
||||||
|
* ``project_id``: Owner project ID
|
||||||
|
|
||||||
|
:returns: A generator of address group objects
|
||||||
|
:rtype: :class:`~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
"""
|
||||||
|
return self._list(_address_group.AddressGroup, **query)
|
||||||
|
|
||||||
|
def update_address_group(self, address_group, **attrs):
|
||||||
|
"""Update an address group
|
||||||
|
|
||||||
|
:param address_group: Either the ID of an address group or a
|
||||||
|
:class:`~openstack.network.v2.address_group.AddressGroup` instance.
|
||||||
|
:param dict attrs: The attributes to update on the address group
|
||||||
|
represented by ``value``.
|
||||||
|
|
||||||
|
:returns: The updated address group
|
||||||
|
:rtype: :class:`~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
"""
|
||||||
|
return self._update(_address_group.AddressGroup, address_group,
|
||||||
|
**attrs)
|
||||||
|
|
||||||
|
def add_addresses_to_address_group(self, address_group, addresses):
|
||||||
|
"""Add addresses to a address group
|
||||||
|
|
||||||
|
:param address_group: Either the ID of an address group or a
|
||||||
|
:class:`~openstack.network.v2.address_group.AddressGroup` instance.
|
||||||
|
:param list addresses: List of address strings.
|
||||||
|
:returns: AddressGroup with updated addresses
|
||||||
|
:rtype: :class: `~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
"""
|
||||||
|
ag = self._get_resource(_address_group.AddressGroup, address_group)
|
||||||
|
return ag.add_addresses(self, addresses)
|
||||||
|
|
||||||
|
def remove_addresses_from_address_group(self, address_group, addresses):
|
||||||
|
"""Remove addresses from a address group
|
||||||
|
|
||||||
|
:param address_group: Either the ID of an address group or a
|
||||||
|
:class:`~openstack.network.v2.address_group.AddressGroup` instance.
|
||||||
|
:param list addresses: List of address strings.
|
||||||
|
:returns: AddressGroup with updated addresses
|
||||||
|
:rtype: :class: `~openstack.network.v2.address_group.AddressGroup`
|
||||||
|
"""
|
||||||
|
ag = self._get_resource(_address_group.AddressGroup, address_group)
|
||||||
|
return ag.remove_addresses(self, addresses)
|
||||||
|
|
||||||
def create_address_scope(self, **attrs):
|
def create_address_scope(self, **attrs):
|
||||||
"""Create a new address scope from attributes
|
"""Create a new address scope from attributes
|
||||||
|
|
||||||
|
84
openstack/network/v2/address_group.py
Normal file
84
openstack/network/v2/address_group.py
Normal 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 import exceptions
|
||||||
|
from openstack import resource
|
||||||
|
from openstack import utils
|
||||||
|
|
||||||
|
|
||||||
|
class AddressGroup(resource.Resource):
|
||||||
|
"""Address group extension."""
|
||||||
|
resource_key = 'address_group'
|
||||||
|
resources_key = 'address_groups'
|
||||||
|
base_path = '/address-groups'
|
||||||
|
|
||||||
|
# capabilities
|
||||||
|
allow_create = True
|
||||||
|
allow_fetch = True
|
||||||
|
allow_commit = True
|
||||||
|
allow_delete = True
|
||||||
|
allow_list = True
|
||||||
|
|
||||||
|
_query_mapping = resource.QueryParameters(
|
||||||
|
"sort_key", "sort_dir",
|
||||||
|
'name', 'description',
|
||||||
|
project_id='tenant_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
#: The ID of the address group.
|
||||||
|
id = resource.Body('id')
|
||||||
|
#: The address group name.
|
||||||
|
name = resource.Body('name')
|
||||||
|
#: The address group name.
|
||||||
|
description = resource.Body('description')
|
||||||
|
#: The ID of the project that owns the address group.
|
||||||
|
project_id = resource.Body('tenant_id')
|
||||||
|
#: The IP addresses of the address group.
|
||||||
|
addresses = resource.Body('addresses', type=list)
|
||||||
|
|
||||||
|
def _put(self, session, url, body):
|
||||||
|
resp = session.put(url, json=body)
|
||||||
|
exceptions.raise_from_response(resp)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def add_addresses(self, session, addresses):
|
||||||
|
"""Add addresses into the address group.
|
||||||
|
|
||||||
|
:param session: The session to communicate through.
|
||||||
|
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||||
|
:param list addresses: The list of address strings.
|
||||||
|
|
||||||
|
:returns: The response as a AddressGroup object with updated addresses
|
||||||
|
|
||||||
|
:raises: :class:`~openstack.exceptions.SDKException` on error.
|
||||||
|
"""
|
||||||
|
url = utils.urljoin(self.base_path, self.id, 'add_addresses')
|
||||||
|
resp = self._put(session, url, {'addresses': addresses})
|
||||||
|
self._translate_response(resp)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_addresses(self, session, addresses):
|
||||||
|
"""Remove addresses from the address group.
|
||||||
|
|
||||||
|
:param session: The session to communicate through.
|
||||||
|
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||||
|
:param list addresses: The list of address strings.
|
||||||
|
|
||||||
|
:returns: The response as a AddressGroup object with updated addresses
|
||||||
|
|
||||||
|
:raises: :class:`~openstack.exceptions.SDKException` on error.
|
||||||
|
"""
|
||||||
|
url = utils.urljoin(self.base_path, self.id, 'remove_addresses')
|
||||||
|
resp = self._put(session, url, {'addresses': addresses})
|
||||||
|
self._translate_response(resp)
|
||||||
|
return self
|
76
openstack/tests/functional/network/v2/test_address_group.py
Normal file
76
openstack/tests/functional/network/v2/test_address_group.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# 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 address_group as _address_group
|
||||||
|
from openstack.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddressGroup(base.BaseFunctionalTest):
|
||||||
|
|
||||||
|
ADDRESS_GROUP_ID = None
|
||||||
|
ADDRESSES = ['10.0.0.1/32', '2001:db8::/32']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAddressGroup, self).setUp()
|
||||||
|
self.ADDRESS_GROUP_NAME = self.getUniqueString()
|
||||||
|
self.ADDRESS_GROUP_DESCRIPTION = self.getUniqueString()
|
||||||
|
self.ADDRESS_GROUP_NAME_UPDATED = self.getUniqueString()
|
||||||
|
self.ADDRESS_GROUP_DESCRIPTION_UPDATED = self.getUniqueString()
|
||||||
|
address_group = self.conn.network.create_address_group(
|
||||||
|
name=self.ADDRESS_GROUP_NAME,
|
||||||
|
description=self.ADDRESS_GROUP_DESCRIPTION,
|
||||||
|
addresses=self.ADDRESSES
|
||||||
|
)
|
||||||
|
assert isinstance(address_group, _address_group.AddressGroup)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_NAME, address_group.name)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_DESCRIPTION,
|
||||||
|
address_group.description)
|
||||||
|
self.assertItemsEqual(self.ADDRESSES, address_group.addresses)
|
||||||
|
self.ADDRESS_GROUP_ID = address_group.id
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
sot = self.conn.network.delete_address_group(self.ADDRESS_GROUP_ID)
|
||||||
|
self.assertIsNone(sot)
|
||||||
|
super(TestAddressGroup, self).tearDown()
|
||||||
|
|
||||||
|
def test_find(self):
|
||||||
|
sot = self.conn.network.find_address_group(self.ADDRESS_GROUP_NAME)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_ID, sot.id)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
sot = self.conn.network.get_address_group(self.ADDRESS_GROUP_ID)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_NAME, sot.name)
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
names = [ag.name for ag in self.conn.network.address_groups()]
|
||||||
|
self.assertIn(self.ADDRESS_GROUP_NAME, names)
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
sot = self.conn.network.update_address_group(
|
||||||
|
self.ADDRESS_GROUP_ID,
|
||||||
|
name=self.ADDRESS_GROUP_NAME_UPDATED,
|
||||||
|
description=self.ADDRESS_GROUP_DESCRIPTION_UPDATED)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_NAME_UPDATED, sot.name)
|
||||||
|
self.assertEqual(self.ADDRESS_GROUP_DESCRIPTION_UPDATED,
|
||||||
|
sot.description)
|
||||||
|
|
||||||
|
def test_add_remove_addresses(self):
|
||||||
|
addrs = ['127.0.0.1/32', 'fe80::/10']
|
||||||
|
sot = self.conn.network.add_addresses_to_address_group(
|
||||||
|
self.ADDRESS_GROUP_ID, addrs)
|
||||||
|
updated_addrs = self.ADDRESSES.copy()
|
||||||
|
updated_addrs.extend(addrs)
|
||||||
|
self.assertItemsEqual(updated_addrs, sot.addresses)
|
||||||
|
sot = self.conn.network.remove_addresses_from_address_group(
|
||||||
|
self.ADDRESS_GROUP_ID, addrs)
|
||||||
|
self.assertItemsEqual(self.ADDRESSES, sot.addresses)
|
55
openstack/tests/unit/network/v2/test_address_group.py
Normal file
55
openstack/tests/unit/network/v2/test_address_group.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# 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 address_group
|
||||||
|
|
||||||
|
IDENTIFIER = 'IDENTIFIER'
|
||||||
|
EXAMPLE = {
|
||||||
|
'id': IDENTIFIER,
|
||||||
|
'name': '1',
|
||||||
|
'description': '2',
|
||||||
|
'tenant_id': '3',
|
||||||
|
'addresses': ['10.0.0.1/32']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddressGroup(base.TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
sot = address_group.AddressGroup()
|
||||||
|
self.assertEqual('address_group', sot.resource_key)
|
||||||
|
self.assertEqual('address_groups', sot.resources_key)
|
||||||
|
self.assertEqual('/address-groups', sot.base_path)
|
||||||
|
self.assertTrue(sot.allow_create)
|
||||||
|
self.assertTrue(sot.allow_fetch)
|
||||||
|
self.assertTrue(sot.allow_commit)
|
||||||
|
self.assertTrue(sot.allow_delete)
|
||||||
|
self.assertTrue(sot.allow_list)
|
||||||
|
|
||||||
|
self.assertDictEqual({"name": "name",
|
||||||
|
"description": "description",
|
||||||
|
"project_id": "tenant_id",
|
||||||
|
"sort_key": "sort_key",
|
||||||
|
"sort_dir": "sort_dir",
|
||||||
|
"limit": "limit",
|
||||||
|
"marker": "marker"},
|
||||||
|
sot._query_mapping._mapping)
|
||||||
|
|
||||||
|
def test_make_it(self):
|
||||||
|
sot = address_group.AddressGroup(**EXAMPLE)
|
||||||
|
self.assertEqual(EXAMPLE['id'], sot.id)
|
||||||
|
self.assertEqual(EXAMPLE['name'], sot.name)
|
||||||
|
self.assertEqual(EXAMPLE['description'], sot.description)
|
||||||
|
self.assertEqual(EXAMPLE['tenant_id'], sot.project_id)
|
||||||
|
self.assertItemsEqual(EXAMPLE['addresses'], sot.addresses)
|
Reference in New Issue
Block a user