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