From 79971c627b9ca895a465da8540d704c5ee3abfe9 Mon Sep 17 00:00:00 2001 From: Justin Pomeroy Date: Tue, 3 Nov 2015 13:59:11 -0600 Subject: [PATCH] Add action for editing instance metadata This adds the Update Metadata action to the instances table to allow managing the metadata on an instance. This is very similar to the Update Metadata actions for images, flavors, etc. Implements: blueprint edit-server-metadata Change-Id: Ia09a05f5cd93898ec9d64ac7af1e6baf07e71757 --- openstack_dashboard/api/nova.py | 8 ++++ openstack_dashboard/api/rest/nova.py | 31 ++++++++++++++- .../dashboards/project/instances/tables.py | 25 +++++++++++- .../app/core/metadata/metadata.service.js | 9 +++-- .../core/metadata/metadata.service.spec.js | 24 +++++++++++- .../static/app/core/metadata/modal/modal.html | 1 + .../openstack-service-api/nova.service.js | 38 ++++++++++++++++++- .../nova.service.spec.js | 22 +++++++++++ .../test/api_tests/nova_rest_tests.py | 29 ++++++++++++++ .../test/api_tests/nova_tests.py | 28 ++++++++++++++ ...edit-server-metadata-7e6b00946a2e793a.yaml | 2 + 11 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bp-edit-server-metadata-7e6b00946a2e793a.yaml diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 056fd7e195..d834665387 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -720,6 +720,14 @@ def server_unlock(request, instance_id): novaclient(request).servers.unlock(instance_id) +def server_metadata_update(request, instance_id, metadata): + novaclient(request).servers.set_meta(instance_id, metadata) + + +def server_metadata_delete(request, instance_id, keys): + novaclient(request).servers.delete_meta(instance_id, keys) + + def tenant_quota_get(request, tenant_id): return base.QuotaSet(novaclient(request).quotas.get(tenant_id)) diff --git a/openstack_dashboard/api/rest/nova.py b/openstack_dashboard/api/rest/nova.py index 9cfead0d43..3babc88749 100644 --- a/openstack_dashboard/api/rest/nova.py +++ b/openstack_dashboard/api/rest/nova.py @@ -199,7 +199,7 @@ class Servers(generic.View): class Server(generic.View): """API for retrieving a single server """ - url_regex = r'nova/servers/(?P.+|default)$' + url_regex = r'nova/servers/(?P[^/]+|default)$' @rest_utils.ajax() def get(self, request, server_id): @@ -210,6 +210,35 @@ class Server(generic.View): return api.nova.server_get(request, server_id).to_dict() +@urls.register +class ServerMetadata(generic.View): + """API for server metadata. + """ + url_regex = r'nova/servers/(?P[^/]+|default)/metadata$' + + @rest_utils.ajax() + def get(self, request, server_id): + """Get a specific server's metadata + + http://localhost/api/nova/servers/1/metadata + """ + return api.nova.server_get(request, + server_id).to_dict().get('metadata') + + @rest_utils.ajax() + def patch(self, request, server_id): + """Update metadata items for a server + + http://localhost/api/nova/servers/1/metadata + """ + updated = request.DATA['updated'] + removed = request.DATA['removed'] + if updated: + api.nova.server_metadata_update(request, server_id, updated) + if removed: + api.nova.server_metadata_delete(request, server_id, removed) + + @urls.register class Extensions(generic.View): """API for nova extensions. diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index eaeec5e7ca..b0455a679a 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -719,6 +719,29 @@ class SimpleDisassociateIP(policy.PolicyTargetMixin, tables.Action): return shortcuts.redirect(request.get_full_path()) +class UpdateMetadata(policy.PolicyTargetMixin, tables.LinkAction): + name = "update_metadata" + verbose_name = _("Update Metadata") + ajax = False + icon = "pencil" + attrs = {"ng-controller": "MetadataModalHelperController as modal"} + policy_rules = (("compute", "compute:update_instance_metadata"),) + + def __init__(self, attrs=None, **kwargs): + kwargs['preempt'] = True + super(UpdateMetadata, self).__init__(attrs, **kwargs) + + def get_link_url(self, datum): + instance_id = self.table.get_object_id(datum) + self.attrs['ng-click'] = ( + "modal.openMetadataModal('instance', '%s', true)" % instance_id) + return "javascript:void(0);" + + def allowed(self, request, instance=None): + return (instance and + instance.status.lower() != 'error') + + def instance_fault_to_friendly_message(instance): fault = getattr(instance, 'fault', {}) message = fault.get('message', _("Unknown")) @@ -1177,7 +1200,7 @@ class InstancesTable(tables.DataTable): row_actions = (StartInstance, ConfirmResize, RevertResize, CreateSnapshot, SimpleAssociateIP, AssociateIP, SimpleDisassociateIP, AttachInterface, - DetachInterface, EditInstance, + DetachInterface, EditInstance, UpdateMetadata, DecryptInstancePassword, EditInstanceSecurityGroups, ConsoleLink, LogLink, TogglePause, ToggleSuspend, ToggleShelve, ResizeLink, LockInstance, UnlockInstance, diff --git a/openstack_dashboard/static/app/core/metadata/metadata.service.js b/openstack_dashboard/static/app/core/metadata/metadata.service.js index 298fac0e78..6b295d86d5 100644 --- a/openstack_dashboard/static/app/core/metadata/metadata.service.js +++ b/openstack_dashboard/static/app/core/metadata/metadata.service.js @@ -51,7 +51,8 @@ return { aggregate: nova.getAggregateExtraSpecs, flavor: nova.getFlavorExtraSpecs, - image: glance.getImageProps + image: glance.getImageProps, + instance: nova.getInstanceMetadata }[resource](id); } @@ -67,7 +68,8 @@ return { aggregate: nova.editAggregateExtraSpecs, flavor: nova.editFlavorExtraSpecs, - image: glance.editImageProps + image: glance.editImageProps, + instance: nova.editInstanceMetadata }[resource](id, updated, removed); } @@ -81,7 +83,8 @@ resource_type: { aggregate: 'OS::Nova::Aggregate', flavor: 'OS::Nova::Flavor', - image: 'OS::Glance::Image' + image: 'OS::Glance::Image', + instance: 'OS::Nova::Instance' }[resource] }, false); } diff --git a/openstack_dashboard/static/app/core/metadata/metadata.service.spec.js b/openstack_dashboard/static/app/core/metadata/metadata.service.spec.js index 53c004ab43..27ba8d1eb0 100644 --- a/openstack_dashboard/static/app/core/metadata/metadata.service.spec.js +++ b/openstack_dashboard/static/app/core/metadata/metadata.service.spec.js @@ -23,7 +23,9 @@ var nova = {getAggregateExtraSpecs: function() {}, getFlavorExtraSpecs: function() {}, editAggregateExtraSpecs: function() {}, - editFlavorExtraSpecs: function() {} }; + editFlavorExtraSpecs: function() {}, + getInstanceMetadata: function() {}, + editInstanceMetadata: function() {} }; var glance = {getImageProps: function() {}, editImageProps: function() {}, @@ -102,6 +104,26 @@ .toHaveBeenCalledWith({ resource_type: 'OS::Glance::Image' }, false); }); + it('should get instance metadata', function() { + var expected = 'instance metadata'; + spyOn(nova, 'getInstanceMetadata').and.returnValue(expected); + var actual = metadataService.getMetadata('instance', '1'); + expect(actual).toBe(expected); + }); + + it('should edit instance metadata', function() { + spyOn(nova, 'editInstanceMetadata'); + metadataService.editMetadata('instance', '1', 'updated', ['removed']); + expect(nova.editInstanceMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']); + }); + + it('should get instance namespace', function() { + spyOn(glance, 'getNamespaces'); + metadataService.getNamespaces('instance'); + expect(glance.getNamespaces) + .toHaveBeenCalledWith({ resource_type: 'OS::Nova::Instance' }, false); + }); + }); })(); diff --git a/openstack_dashboard/static/app/core/metadata/modal/modal.html b/openstack_dashboard/static/app/core/metadata/modal/modal.html index ea7addecc9..9983ea4c92 100644 --- a/openstack_dashboard/static/app/core/metadata/modal/modal.html +++ b/openstack_dashboard/static/app/core/metadata/modal/modal.html @@ -3,6 +3,7 @@ Update Aggregate Metadata Update Flavor Metadata Update Image Metadata + Update Instance Metadata