diff --git a/releasenotes/notes/update_endpoint-f87c1f42d0c0d1ef.yaml b/releasenotes/notes/update_endpoint-f87c1f42d0c0d1ef.yaml new file mode 100644 index 000000000..a7b6a458b --- /dev/null +++ b/releasenotes/notes/update_endpoint-f87c1f42d0c0d1ef.yaml @@ -0,0 +1,8 @@ +--- +features: + - Added update_endpoint as a new function that allows + the user to update a created endpoint with new values + rather than deleting and recreating that endpoint. + This feature only works with keystone v3, with v2 it + will raise an exception stating the feature is not + available. diff --git a/shade/_tasks.py b/shade/_tasks.py index 47ffd9538..238eafe20 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -724,6 +724,11 @@ class EndpointCreate(task_manager.Task): return client.keystone_client.endpoints.create(**self.args) +class EndpointUpdate(task_manager.Task): + def main(self, client): + return client.keystone_client.endpoints.update(**self.args) + + class EndpointList(task_manager.Task): def main(self, client): return client.keystone_client.endpoints.list() diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index 77c63c38a..c93cabab8 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -980,6 +980,26 @@ class OperatorCloud(openstackcloud.OpenStackCloud): endpoints.append(endpoint) return endpoints + @_utils.valid_kwargs('enabled', 'service_name_or_id', 'url', 'interface', + 'region') + def update_endpoint(self, endpoint_id, **kwargs): + # NOTE(SamYaple): Endpoint updates are only available on v3 api + if self.cloud_config.get_api_version('identity').startswith('2'): + raise OpenStackCloudUnavailableFeature( + 'Unavailable Feature: Endpoint update' + ) + + service_name_or_id = kwargs.pop('service_name_or_id', None) + if service_name_or_id is not None: + kwargs['service'] = service_name_or_id + + with _utils.shade_exceptions( + "Failed to update endpoint {}".format(endpoint_id) + ): + return self.manager.submitTask(_tasks.EndpointUpdate( + endpoint=endpoint_id, **kwargs + )) + def list_endpoints(self): """List Keystone endpoints. @@ -988,7 +1008,10 @@ class OperatorCloud(openstackcloud.OpenStackCloud): :raises: ``OpenStackCloudException``: if something goes wrong during the openstack API call. """ - # ToDo: support v3 api (dguerri) + # NOTE(SamYaple): With keystone v3 we can filter directly via the + # the keystone api, but since the return of all the endpoints even in + # large environments is small, we can continue to filter in shade just + # like the v2 api. with _utils.shade_exceptions("Failed to list endpoints"): endpoints = self.manager.submitTask(_tasks.EndpointList()) @@ -1042,7 +1065,6 @@ class OperatorCloud(openstackcloud.OpenStackCloud): :raises: ``OpenStackCloudException`` if something goes wrong during the openstack API call. """ - # ToDo: support v3 api (dguerri) endpoint = self.get_endpoint(id=id) if endpoint is None: self.log.debug("Endpoint %s not found for deleting" % id) diff --git a/shade/tests/functional/test_endpoints.py b/shade/tests/functional/test_endpoints.py index ca79f855f..e7346fe64 100644 --- a/shade/tests/functional/test_endpoints.py +++ b/shade/tests/functional/test_endpoints.py @@ -25,6 +25,7 @@ import string import random from shade.exc import OpenStackCloudException +from shade.exc import OpenStackCloudUnavailableFeature from shade.tests.functional import base @@ -101,6 +102,39 @@ class TestEndpoints(base.BaseFunctionalTestCase): self.assertNotEqual([], endpoints) self.assertIsNotNone(endpoints[0].get('id')) + def test_update_endpoint(self): + if self.operator_cloud.cloud_config.get_api_version( + 'identity').startswith('2'): + # NOTE(SamYaple): Update endpoint only works with v3 api + self.assertRaises(OpenStackCloudUnavailableFeature, + self.operator_cloud.update_endpoint, + 'endpoint_id1') + else: + service = self.operator_cloud.create_service( + name='service1', type='test_type') + endpoint = self.operator_cloud.create_endpoint( + service_name_or_id=service['id'], + url='http://admin.url/', + interface='admin', + region='orig_region', + enabled=False)[0] + + new_service = self.operator_cloud.create_service( + name='service2', type='test_type') + new_endpoint = self.operator_cloud.update_endpoint( + endpoint.id, + service_name_or_id=new_service.id, + url='http://public.url/', + interface='public', + region='update_region', + enabled=True) + + self.assertEqual(new_endpoint.url, 'http://public.url/') + self.assertEqual(new_endpoint.interface, 'public') + self.assertEqual(new_endpoint.region, 'update_region') + self.assertEqual(new_endpoint.service_id, new_service.id) + self.assertTrue(new_endpoint.enabled) + def test_list_endpoints(self): service_name = self.new_item_name + '_list' diff --git a/shade/tests/unit/test_endpoints.py b/shade/tests/unit/test_endpoints.py index a0c596903..1d85eebfa 100644 --- a/shade/tests/unit/test_endpoints.py +++ b/shade/tests/unit/test_endpoints.py @@ -23,6 +23,7 @@ from mock import patch import os_client_config from shade import OperatorCloud from shade.exc import OpenStackCloudException +from shade.exc import OpenStackCloudUnavailableFeature from shade.tests.fakes import FakeEndpoint from shade.tests.fakes import FakeEndpointv3 from shade.tests.unit import base @@ -170,6 +171,41 @@ class TestCloudEndpoints(base.TestCase): for k, v in self.mock_endpoints_v3[count].items(): self.assertEquals(v, endpoints_2on3[count].get(k)) + @patch.object(os_client_config.cloud_config.CloudConfig, 'get_api_version') + def test_update_endpoint_v2(self, mock_api_version): + mock_api_version.return_value = '2.0' + # NOTE(SamYaple): Update endpoint only works with v3 api + self.assertRaises(OpenStackCloudUnavailableFeature, + self.client.update_endpoint, 'endpoint_id') + + @patch.object(OperatorCloud, 'keystone_client') + @patch.object(os_client_config.cloud_config.CloudConfig, 'get_api_version') + def test_update_endpoint_v3(self, mock_api_version, mock_keystone_client): + mock_api_version.return_value = '3' + mock_keystone_client.endpoints.update.return_value = \ + self.mock_ks_endpoints_v3[0] + + endpoint = self.client.update_endpoint( + 'id1', + service_name_or_id='service_id1', + region='mock_region', + url='mock_url', + interface='mock_interface', + enabled=False + ) + mock_keystone_client.endpoints.update.assert_called_with( + endpoint='id1', + service='service_id1', + region='mock_region', + url='mock_url', + interface='mock_interface', + enabled=False + ) + + # test keys and values are correct + for k, v in self.mock_endpoints_v3[0].items(): + self.assertEquals(v, endpoint.get(k)) + @patch.object(OperatorCloud, 'keystone_client') def test_list_endpoints(self, mock_keystone_client): mock_keystone_client.endpoints.list.return_value = \