From b3bbd09131e127e7540f4ccdb1376c10bace8b7a Mon Sep 17 00:00:00 2001 From: Jason Kölker Date: Tue, 11 Dec 2012 15:12:29 -0600 Subject: Add extension to allow hiding of addresses * Servers in certain states will have network_info but it may change, (eg. rescheduled build on another host). The extension allows the operator to hide the address information in those states * Fixes bug LP 1089092 Change-Id: Ie843e34a41c77903b201b45c5b67a6f75334cb5e --- .../compute/contrib/hide_server_addresses.py | 89 ++++++++++++ .../compute/contrib/test_hide_server_addresses.py | 151 +++++++++++++++++++++ nova/tests/fake_policy.py | 1 + .../all_extensions/extensions-get-resp.json.tpl | 8 ++ .../all_extensions/extensions-get-resp.xml.tpl | 3 + .../server-get-resp.json.tpl | 54 ++++++++ .../server-get-resp.xml.tpl | 19 +++ .../server-post-req.json.tpl | 16 +++ .../server-post-req.xml.tpl | 19 +++ .../server-post-resp.json.tpl | 16 +++ .../server-post-resp.xml.tpl | 6 + .../servers-details-resp.json.tpl | 56 ++++++++ .../servers-details-resp.xml.tpl | 21 +++ .../servers-list-resp.json.tpl | 18 +++ .../servers-list-resp.xml.tpl | 7 + nova/tests/integrated/test_api_samples.py | 10 ++ 16 files changed, 494 insertions(+) create mode 100644 nova/api/openstack/compute/contrib/hide_server_addresses.py create mode 100644 nova/tests/api/openstack/compute/contrib/test_hide_server_addresses.py create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.xml.tpl (limited to 'nova') diff --git a/nova/api/openstack/compute/contrib/hide_server_addresses.py b/nova/api/openstack/compute/contrib/hide_server_addresses.py new file mode 100644 index 000000000..bb8ee553a --- /dev/null +++ b/nova/api/openstack/compute/contrib/hide_server_addresses.py @@ -0,0 +1,89 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +"""Extension for hiding server addresses in certain states.""" + + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.compute import vm_states +from nova.openstack.common import cfg +from nova.openstack.common import log as logging + +opts = [ + cfg.ListOpt('osapi_hide_server_address_states', + default=[vm_states.BUILDING], + help='List of instance states that should hide network info'), +] + + +CONF = cfg.CONF +CONF.register_opts(opts) +LOG = logging.getLogger(__name__) + +authorize = extensions.soft_extension_authorizer('compute', + 'hide_server_addresses') + + +class Controller(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(Controller, self).__init__(*args, **kwargs) + hidden_states = CONF.osapi_hide_server_address_states + + # NOTE(jkoelker) _ is not considered uppercase ;) + valid_vm_states = [getattr(vm_states, state) + for state in dir(vm_states) + if state.isupper()] + self.hide_address_states = [state.lower() + for state in hidden_states + if state in valid_vm_states] + + def _perhaps_hide_addresses(self, instance, resp_server): + if instance.get('vm_state') in self.hide_address_states: + resp_server['addresses'] = {} + + @wsgi.extends + def show(self, req, resp_obj, id): + resp = resp_obj + if not authorize(req.environ['nova.context']): + return + + if 'server' in resp.obj and 'addresses' in resp.obj['server']: + instance = req.get_db_instance(id) + self._perhaps_hide_addresses(instance, resp.obj['server']) + + @wsgi.extends + def detail(self, req, resp_obj): + resp = resp_obj + if not authorize(req.environ['nova.context']): + return + + for server in list(resp.obj['servers']): + if 'addresses' in server: + instance = req.get_db_instance(server['id']) + self._perhaps_hide_addresses(instance, server) + + +class Hide_server_addresses(extensions.ExtensionDescriptor): + """Support hiding server addresses in certain states.""" + + name = 'HideServerAddresses' + alias = 'os-hide-server-addresses' + namespace = ('http://docs.openstack.org/compute/ext/' + 'hide_server_addresses/api/v1.1') + updated = '2012-12-11T00:00:00+00:00' + + def get_controller_extensions(self): + return [extensions.ControllerExtension(self, 'servers', Controller())] diff --git a/nova/tests/api/openstack/compute/contrib/test_hide_server_addresses.py b/nova/tests/api/openstack/compute/contrib/test_hide_server_addresses.py new file mode 100644 index 000000000..7991bc27f --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_hide_server_addresses.py @@ -0,0 +1,151 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# 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 itertools + +from lxml import etree +import webob + +from nova.api.openstack import wsgi +from nova import compute +from nova.compute import vm_states +from nova import exception +from nova.openstack.common import jsonutils +from nova import test +from nova.tests.api.openstack import fakes + + +SENTINEL = object() + + +def fake_compute_get(*args, **kwargs): + def _return_server(*_args, **_kwargs): + return fakes.stub_instance(*args, **kwargs) + return _return_server + + +class HideServerAddressesTest(test.TestCase): + content_type = 'application/json' + + def setUp(self): + super(HideServerAddressesTest, self).setUp() + fakes.stub_out_nw_api(self.stubs) + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Hide_server_addresses']) + + def _make_request(self, url): + req = webob.Request.blank(url) + req.headers['Accept'] = self.content_type + res = req.get_response(fakes.wsgi_app(init_only=('servers',))) + return res + + @staticmethod + def _get_server(body): + return jsonutils.loads(body).get('server') + + @staticmethod + def _get_servers(body): + return jsonutils.loads(body).get('servers') + + @staticmethod + def _get_addresses(server): + return server.get('addresses', SENTINEL) + + def _check_addresses(self, addresses, exists): + self.assertTrue(addresses is not SENTINEL) + if exists: + self.assertTrue(addresses) + else: + self.assertFalse(addresses) + + def test_show_hides_in_building(self): + instance_id = 1 + uuid = fakes.get_fake_uuid(instance_id) + self.stubs.Set(compute.api.API, 'get', + fake_compute_get(instance_id, uuid=uuid, + vm_state=vm_states.BUILDING)) + res = self._make_request('/v2/fake/servers/%s' % uuid) + self.assertEqual(res.status_int, 200) + + server = self._get_server(res.body) + addresses = self._get_addresses(server) + self._check_addresses(addresses, exists=False) + + def test_show(self): + instance_id = 1 + uuid = fakes.get_fake_uuid(instance_id) + self.stubs.Set(compute.api.API, 'get', + fake_compute_get(instance_id, uuid=uuid, + vm_state=vm_states.ACTIVE)) + res = self._make_request('/v2/fake/servers/%s' % uuid) + self.assertEqual(res.status_int, 200) + + server = self._get_server(res.body) + addresses = self._get_addresses(server) + self._check_addresses(addresses, exists=True) + + def test_detail_hides_building_server_addresses(self): + instance_0 = fakes.stub_instance(0, uuid=fakes.get_fake_uuid(0), + vm_state=vm_states.ACTIVE) + instance_1 = fakes.stub_instance(1, uuid=fakes.get_fake_uuid(1), + vm_state=vm_states.BUILDING) + instances = [instance_0, instance_1] + + def get_all(*args, **kwargs): + return instances + + self.stubs.Set(compute.api.API, 'get_all', get_all) + res = self._make_request('/v2/fake/servers/detail') + + self.assertEqual(res.status_int, 200) + servers = self._get_servers(res.body) + + self.assertEqual(len(servers), len(instances)) + + for instance, server in itertools.izip(instances, servers): + addresses = self._get_addresses(server) + exists = (instance['vm_state'] == vm_states.ACTIVE) + self._check_addresses(addresses, exists=exists) + + def test_no_instance_passthrough_404(self): + + def fake_compute_get(*args, **kwargs): + raise exception.InstanceNotFound() + + self.stubs.Set(compute.api.API, 'get', fake_compute_get) + res = self._make_request('/v2/fake/servers/' + fakes.get_fake_uuid()) + + self.assertEqual(res.status_int, 404) + + +class HideAddressesXmlTest(HideServerAddressesTest): + content_type = 'application/xml' + + @staticmethod + def _get_server(body): + return etree.XML(body) + + @staticmethod + def _get_servers(body): + return etree.XML(body).getchildren() + + @staticmethod + def _get_addresses(server): + addresses = server.find('{%s}addresses' % wsgi.XMLNS_V11) + if addresses is None: + return SENTINEL + return addresses diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 9c1140922..80eb08743 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -130,6 +130,7 @@ policy_data = """ "compute_extension:floating_ips_bulk": "", "compute_extension:fping": "", "compute_extension:fping:all_tenants": "is_admin:True", + "compute_extension:hide_server_addresses": "", "compute_extension:hosts": "", "compute_extension:hypervisors": "", "compute_extension:instance_usage_audit_log": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 4a8d96844..20f9044f9 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -240,6 +240,14 @@ "namespace": "http://docs.openstack.org/compute/ext/floating_ips_bulk/api/v2", "updated": "%(timestamp)s" }, + { + "alias": "os-hide-server-addresses", + "description": "Support hiding server addresses in certain states.", + "links": [], + "name": "HideServerAddresses", + "namespace": "http://docs.openstack.org/compute/ext/hide_server_addresses/api/v1.1", + "updated": "2012-12-11T00:00:00+00:00" + }, { "alias": "os-hosts", "description": "%(text)s", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index 7d4683986..1669ff957 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -90,6 +90,9 @@ %(text)s + + Support hiding server addresses in certain states. + %(text)s diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.json.tpl new file mode 100644 index 000000000..86e39aedc --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.json.tpl @@ -0,0 +1,54 @@ +{ + "server": { + "accessIPv4": "", + "accessIPv6": "", + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4 + } + ] + }, + "created": "%(timestamp)s", + "flavor": { + "id": "1", + "links": [ + { + "href": "%(host)s/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "%(hostid)s", + "id": "%(id)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(host)s/openstack/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(id)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(id)s", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "tenant_id": "openstack", + "updated": "%(timestamp)s", + "user_id": "fake" + } +} diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.xml.tpl new file mode 100644 index 000000000..adc8a5c1b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-get-resp.xml.tpl @@ -0,0 +1,19 @@ + + + + + + + + + + Apache1 + + + + + + + + + diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.json.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.json.tpl new file mode 100644 index 000000000..d3916d1aa --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.json.tpl @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "%(host)s/openstack/images/%(image_id)s", + "flavorRef" : "%(host)s/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.xml.tpl new file mode 100644 index 000000000..f92614984 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-req.xml.tpl @@ -0,0 +1,19 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.json.tpl new file mode 100644 index 000000000..d5f030c87 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "%(password)s", + "id": "%(id)s", + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(uuid)s", + "rel": "bookmark" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.xml.tpl new file mode 100644 index 000000000..3bb13e69b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/server-post-resp.xml.tpl @@ -0,0 +1,6 @@ + + + + + + diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.json.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.json.tpl new file mode 100644 index 000000000..e244ea0df --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.json.tpl @@ -0,0 +1,56 @@ +{ + "servers": [ + { + "accessIPv4": "", + "accessIPv6": "", + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4 + } + ] + }, + "created": "%(timestamp)s", + "flavor": { + "id": "1", + "links": [ + { + "href": "%(host)s/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "%(hostid)s", + "id": "%(id)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(host)s/openstack/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(id)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(id)s", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "tenant_id": "openstack", + "updated": "%(timestamp)s", + "user_id": "fake" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.xml.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.xml.tpl new file mode 100644 index 000000000..568807ecb --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-details-resp.xml.tpl @@ -0,0 +1,21 @@ + + + + + + + + + + + Apache1 + + + + + + + + + + diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.json.tpl new file mode 100644 index 000000000..8b97dc28d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.json.tpl @@ -0,0 +1,18 @@ +{ + "servers": [ + { + "id": "%(id)s", + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(id)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(id)s", + "rel": "bookmark" + } + ], + "name": "new-server-test" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.xml.tpl new file mode 100644 index 000000000..03bee03a6 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-hide-server-addresses/servers-list-resp.xml.tpl @@ -0,0 +1,7 @@ + + + + + + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 46cf408f1..a971addae 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -457,6 +457,16 @@ class ServersSampleAllExtensionXmlTest(ServersSampleXmlTest): all_extensions = True +class ServersSampleHideAddressesJsonTest(ServersSampleJsonTest): + extension_name = '.'.join(('nova.api.openstack.compute.contrib', + 'hide_server_addresses', + 'Hide_server_addresses')) + + +class ServersSampleHideAddressesXMLTest(ServersSampleHideAddressesJsonTest): + ctype = 'xml' + + class ServersMetadataJsonTest(ServersSampleBase): def _create_and_set(self, subs): uuid = self._post_server() -- cgit