diff --git a/doc/source/users/proxies/network.rst b/doc/source/users/proxies/network.rst index 690e8755..290e2010 100644 --- a/doc/source/users/proxies/network.rst +++ b/doc/source/users/proxies/network.rst @@ -332,6 +332,13 @@ Service Profile Operations .. automethod:: openstack.network.v2._proxy.Proxy.associate_flavor_with_service_profile .. automethod:: openstack.network.v2._proxy.Proxy.disassociate_flavor_from_service_profile +Tag Operations +^^^^^^^^^^^^^^ + +.. autoclass:: openstack.network.v2._proxy.Proxy + + .. automethod:: openstack.network.v2._proxy.Proxy.set_tags + VPN Operations ^^^^^^^^^^^^^^ diff --git a/openstack/network/v2/_proxy.py b/openstack/network/v2/_proxy.py index d0d3a936..4ee8559a 100644 --- a/openstack/network/v2/_proxy.py +++ b/openstack/network/v2/_proxy.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack import exceptions from openstack.network.v2 import address_scope as _address_scope from openstack.network.v2 import agent as _agent from openstack.network.v2 import auto_allocated_topology as \ @@ -2927,6 +2928,30 @@ class Proxy(proxy2.BaseProxy): """ return self._update(_subnet_pool.SubnetPool, subnet_pool, **attrs) + @staticmethod + def _check_tag_support(resource): + try: + # Check 'tags' attribute exists + resource.tags + except AttributeError: + raise exceptions.InvalidRequest( + '%s resource does not support tag' % + resource.__class__.__name__) + + def set_tags(self, resource, tags): + """Replace tags of a specified resource with specified tags + + :param resource: + :class:`~openstack.resource2.Resource` instance. + :param tags: New tags to be set. + :type tags: "list" + + :returns: The updated resource + :rtype: :class:`~openstack.resource2.Resource` + """ + self._check_tag_support(resource) + return resource.set_tags(self._session, tags) + def create_vpn_service(self, **attrs): """Create a new vpn service from attributes diff --git a/openstack/network/v2/network.py b/openstack/network/v2/network.py index 33a86e9a..cbdd4518 100644 --- a/openstack/network/v2/network.py +++ b/openstack/network/v2/network.py @@ -11,10 +11,11 @@ # under the License. from openstack.network import network_service +from openstack.network.v2 import tag from openstack import resource2 as resource -class Network(resource.Resource): +class Network(resource.Resource, tag.TagMixin): resource_key = 'network' resources_key = 'networks' base_path = '/networks' @@ -39,6 +40,7 @@ class Network(resource.Resource): provider_network_type='provider:network_type', provider_physical_network='provider:physical_network', provider_segmentation_id='provider:segmentation_id', + **tag.TagMixin._tag_query_parameters ) # Properties @@ -111,6 +113,9 @@ class Network(resource.Resource): updated_at = resource.Body('updated_at') #: Indicates the VLAN transparency mode of the network is_vlan_transparent = resource.Body('vlan_transparent', type=bool) + #: A list of assocaited tags + #: *Type: list of tag strings* + tags = resource.Body('tags', type=list) class DHCPAgentHostingNetwork(Network): diff --git a/openstack/network/v2/port.py b/openstack/network/v2/port.py index e98de374..b6f94665 100644 --- a/openstack/network/v2/port.py +++ b/openstack/network/v2/port.py @@ -11,10 +11,11 @@ # under the License. from openstack.network import network_service +from openstack.network.v2 import tag from openstack import resource2 as resource -class Port(resource.Resource): +class Port(resource.Resource, tag.TagMixin): resource_key = 'port' resources_key = 'ports' base_path = '/ports' @@ -34,6 +35,7 @@ class Port(resource.Resource): is_admin_state_up='admin_state_up', is_port_security_enabled='port_security_enabled', project_id='tenant_id', + **tag.TagMixin._tag_query_parameters ) # Properties @@ -127,3 +129,6 @@ class Port(resource.Resource): trunk_details = resource.Body('trunk_details', type=dict) #: Timestamp when the port was last updated. updated_at = resource.Body('updated_at') + #: A list of assocaited tags + #: *Type: list of tag strings* + tags = resource.Body('tags', type=list) diff --git a/openstack/network/v2/router.py b/openstack/network/v2/router.py index 3c21b113..7fa58dba 100644 --- a/openstack/network/v2/router.py +++ b/openstack/network/v2/router.py @@ -11,11 +11,12 @@ # under the License. from openstack.network import network_service +from openstack.network.v2 import tag from openstack import resource2 as resource from openstack import utils -class Router(resource.Resource): +class Router(resource.Resource, tag.TagMixin): resource_key = 'router' resources_key = 'routers' base_path = '/routers' @@ -35,6 +36,7 @@ class Router(resource.Resource): is_distributed='distributed', is_ha='ha', project_id='tenant_id', + **tag.TagMixin._tag_query_parameters ) # Properties @@ -74,6 +76,9 @@ class Router(resource.Resource): status = resource.Body('status') #: Timestamp when the router was created. updated_at = resource.Body('updated_at') + #: A list of assocaited tags + #: *Type: list of tag strings* + tags = resource.Body('tags', type=list) def add_interface(self, session, **body): """Add an internal interface to a logical router. diff --git a/openstack/network/v2/subnet.py b/openstack/network/v2/subnet.py index 1466017b..a9e95ad5 100644 --- a/openstack/network/v2/subnet.py +++ b/openstack/network/v2/subnet.py @@ -11,10 +11,11 @@ # under the License. from openstack.network import network_service +from openstack.network.v2 import tag from openstack import resource2 as resource -class Subnet(resource.Resource): +class Subnet(resource.Resource, tag.TagMixin): resource_key = 'subnet' resources_key = 'subnets' base_path = '/subnets' @@ -36,6 +37,7 @@ class Subnet(resource.Resource): project_id='tenant_id', subnet_pool_id='subnetpool_id', use_default_subnet_pool='use_default_subnetpool', + **tag.TagMixin._tag_query_parameters ) # Properties @@ -87,3 +89,6 @@ class Subnet(resource.Resource): 'use_default_subnetpool', type=bool ) + #: A list of assocaited tags + #: *Type: list of tag strings* + tags = resource.Body('tags', type=list) diff --git a/openstack/network/v2/subnet_pool.py b/openstack/network/v2/subnet_pool.py index 22b9f60a..86a43590 100644 --- a/openstack/network/v2/subnet_pool.py +++ b/openstack/network/v2/subnet_pool.py @@ -11,10 +11,11 @@ # under the License. from openstack.network import network_service +from openstack.network.v2 import tag from openstack import resource2 as resource -class SubnetPool(resource.Resource): +class SubnetPool(resource.Resource, tag.TagMixin): resource_key = 'subnetpool' resources_key = 'subnetpools' base_path = '/subnetpools' @@ -32,6 +33,7 @@ class SubnetPool(resource.Resource): 'name', is_shared='shared', project_id='tenant_id', + **tag.TagMixin._tag_query_parameters ) # Properties @@ -77,3 +79,6 @@ class SubnetPool(resource.Resource): revision_number = resource.Body('revision_number', type=int) #: Timestamp when the subnet pool was last updated. updated_at = resource.Body('updated_at') + #: A list of assocaited tags + #: *Type: list of tag strings* + tags = resource.Body('tags', type=list) diff --git a/openstack/network/v2/tag.py b/openstack/network/v2/tag.py new file mode 100644 index 00000000..b216e2eb --- /dev/null +++ b/openstack/network/v2/tag.py @@ -0,0 +1,30 @@ +# 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 utils + + +class TagMixin(object): + + _tag_query_parameters = { + 'tags': 'tags', + 'any_tags': 'tags-any', + 'not_tags': 'not-tags', + 'not_any_tags': 'not-tags-any', + } + + def set_tags(self, session, tags): + url = utils.urljoin(self.base_path, self.id, 'tags') + session.put(url, endpoint_filter=self.service, + json={'tags': tags}) + self._body.attributes.update({'tags': tags}) + return self diff --git a/openstack/tests/unit/network/v2/test_network.py b/openstack/tests/unit/network/v2/test_network.py index fe90b056..f8fc88b6 100644 --- a/openstack/tests/unit/network/v2/test_network.py +++ b/openstack/tests/unit/network/v2/test_network.py @@ -110,7 +110,11 @@ class TestNetwork(testtools.TestCase): 'is_shared': 'shared', 'provider_network_type': 'provider:network_type', 'provider_physical_network': 'provider:physical_network', - 'provider_segmentation_id': 'provider:segmentation_id' + 'provider_segmentation_id': 'provider:segmentation_id', + 'tags': 'tags', + 'any_tags': 'tags-any', + 'not_tags': 'not-tags', + 'not_any_tags': 'not-tags-any', }, sot._query_mapping._mapping) diff --git a/openstack/tests/unit/network/v2/test_proxy.py b/openstack/tests/unit/network/v2/test_proxy.py index ff11c8ca..06232cd2 100644 --- a/openstack/tests/unit/network/v2/test_proxy.py +++ b/openstack/tests/unit/network/v2/test_proxy.py @@ -14,6 +14,7 @@ import deprecation import mock import uuid +from openstack import exceptions from openstack.network.v2 import _proxy from openstack.network.v2 import address_scope from openstack.network.v2 import agent @@ -1064,3 +1065,19 @@ class TestNetworkProxy(test_proxy_base2.TestProxyBase): auto_allocated_topology.ValidateTopology], expected_kwargs={"project": mock.sentinel.project_id, "requires_id": False}) + + def test_set_tags(self): + x_network = network.Network.new(id='NETWORK_ID') + self._verify('openstack.network.v2.network.Network.set_tags', + self.proxy.set_tags, + method_args=[x_network, ['TAG1', 'TAG2']], + expected_args=[['TAG1', 'TAG2']], + expected_result=mock.sentinel.result_set_tags) + + @mock.patch('openstack.network.v2.network.Network.set_tags') + def test_set_tags_resource_without_tag_suport(self, mock_set_tags): + no_tag_resource = object() + self.assertRaises(exceptions.InvalidRequest, + self.proxy.set_tags, + no_tag_resource, ['TAG1', 'TAG2']) + self.assertEqual(0, mock_set_tags.call_count) diff --git a/openstack/tests/unit/network/v2/test_tag.py b/openstack/tests/unit/network/v2/test_tag.py new file mode 100644 index 00000000..b22ae87d --- /dev/null +++ b/openstack/tests/unit/network/v2/test_tag.py @@ -0,0 +1,44 @@ +# 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. + +import mock +import testtools + +from openstack.network.v2 import network + + +ID = 'IDENTIFIER' + + +class TestTag(testtools.TestCase): + + @staticmethod + def _create_resource(tags=None): + tags = tags or [] + return network.Network(id=ID, name='test-net', tags=tags) + + def test_tags_attribute(self): + net = self._create_resource() + self.assertTrue(hasattr(net, 'tags')) + self.assertIsInstance(net.tags, list) + + def test_set_tags(self): + net = self._create_resource() + sess = mock.Mock() + result = net.set_tags(sess, ['blue', 'green']) + # Check tags attribute is updated + self.assertEqual(['blue', 'green'], net.tags) + # Check the passed resource is returned + self.assertEqual(net, result) + url = 'networks/' + ID + '/tags' + sess.put.assert_called_once_with(url, endpoint_filter=net.service, + json={'tags': ['blue', 'green']})