diff --git a/doc/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json b/doc/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json new file mode 100644 index 000000000000..bbcc25fc9515 --- /dev/null +++ b/doc/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json @@ -0,0 +1,69 @@ +{ + "server": { + "addresses": { + "private": [ + { + "addr": "192.168.0.3", + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed", + "version": 4 + } + ] + }, + "created": "2013-09-16T02:55:07Z", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "3bf189131c61d0e71b0a8686a897a0f50d1693b48c47b721fe77155b", + "id": "c278163e-36f9-4cf2-b1ac-80db4c63f7a8", + "image": { + "id": "70a599e0-31e7-49b7-b260-868f441e862b", + "links": [ + { + "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "rel": "bookmark" + } + ] + }, + "key_name": null, + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/c278163e-36f9-4cf2-b1ac-80db4c63f7a8", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/c278163e-36f9-4cf2-b1ac-80db4c63f7a8", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "OS-EXT-SRV-ATTR:host": "c5f474bf81474f9dbbc404d5b2e4e9b3", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini", + "OS-EXT-SRV-ATTR:instance_name": "instance-00000001", + "OS-EXT-SRV-ATTR:hostname": "new-server-test", + "OS-EXT-SRV-ATTR:launch_index": 0, + "OS-EXT-SRV-ATTR:reservation_id": "r-12345678", + "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda", + "OS-EXT-SRV-ATTR:kernel_id": null, + "OS-EXT-SRV-ATTR:ramdisk_id": null, + "OS-EXT-SRV-ATTR:user_data": null, + "locked": false, + "accessIPv4": "1.2.3.4", + "accessIPv6": "80fe::", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "updated": "2013-09-16T02:55:08Z", + "user_id": "fake" + } +} diff --git a/doc/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json b/doc/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json new file mode 100644 index 000000000000..25c4de601be8 --- /dev/null +++ b/doc/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json @@ -0,0 +1,71 @@ +{ + "servers": [ + { + "addresses": { + "private": [ + { + "addr": "192.168.0.3", + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed", + "version": 4 + } + ] + }, + "created": "2013-09-16T02:55:03Z", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "63cf07a9fd82e1d2294926ec5c0d2e1e0ca449224246df75e16f23dc", + "id": "a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46", + "image": { + "id": "70a599e0-31e7-49b7-b260-868f441e862b", + "links": [ + { + "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "rel": "bookmark" + } + ] + }, + "key_name": null, + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "OS-EXT-SRV-ATTR:host": "bc8efe4fdb7148a4bb921a2b03d17de6", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini", + "OS-EXT-SRV-ATTR:instance_name": "instance-00000001", + "OS-EXT-SRV-ATTR:hostname": "new-server-test", + "OS-EXT-SRV-ATTR:launch_index": 0, + "OS-EXT-SRV-ATTR:reservation_id": "r-12345678", + "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda", + "OS-EXT-SRV-ATTR:kernel_id": null, + "OS-EXT-SRV-ATTR:ramdisk_id": null, + "OS-EXT-SRV-ATTR:user_data": null, + "locked": false, + "accessIPv4": "1.2.3.4", + "accessIPv6": "80fe::", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "updated": "2013-09-16T02:55:05Z", + "user_id": "fake" + } + ] +} diff --git a/doc/api_samples/servers/v2.16/server-get-resp.json b/doc/api_samples/servers/v2.16/server-get-resp.json new file mode 100644 index 000000000000..7b09e164634b --- /dev/null +++ b/doc/api_samples/servers/v2.16/server-get-resp.json @@ -0,0 +1,58 @@ +{ + "server": { + "accessIPv4": "1.2.3.4", + "accessIPv6": "80fe::", + "addresses": { + "private": [ + { + "addr": "192.168.0.3", + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed", + "version": 4 + } + ] + }, + "created": "2013-09-03T04:01:32Z", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "92154fab69d5883ba2c8622b7e65f745dd33257221c07af363c51b29", + "id": "0e44cc9c-e052-415d-afbf-469b0d384170", + "image": { + "id": "70a599e0-31e7-49b7-b260-868f441e862b", + "links": [ + { + "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "rel": "bookmark" + } + ] + }, + "key_name": null, + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/0e44cc9c-e052-415d-afbf-469b0d384170", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/0e44cc9c-e052-415d-afbf-469b0d384170", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "updated": "2013-09-03T04:01:33Z", + "user_id": "fake" + } +} diff --git a/doc/api_samples/servers/v2.16/servers-details-resp.json b/doc/api_samples/servers/v2.16/servers-details-resp.json new file mode 100644 index 000000000000..5256384042ea --- /dev/null +++ b/doc/api_samples/servers/v2.16/servers-details-resp.json @@ -0,0 +1,60 @@ +{ + "servers": [ + { + "accessIPv4": "1.2.3.4", + "accessIPv6": "80fe::", + "addresses": { + "private": [ + { + "addr": "192.168.0.3", + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed", + "version": 4 + } + ] + }, + "created": "2013-09-03T04:01:32Z", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "bcf92836fc9ed4203a75cb0337afc7f917d2be504164b995c2334b25", + "id": "f5dc173b-6804-445a-a6d8-c705dad5b5eb", + "image": { + "id": "70a599e0-31e7-49b7-b260-868f441e862b", + "links": [ + { + "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "rel": "bookmark" + } + ] + }, + "key_name": null, + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/f5dc173b-6804-445a-a6d8-c705dad5b5eb", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/f5dc173b-6804-445a-a6d8-c705dad5b5eb", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "updated": "2013-09-03T04:01:32Z", + "user_id": "fake" + } + ] +} diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index cba453de1431..4cb8d06a338e 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index fea7835f2a4a..67f189f8c1aa 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 071b0568e595..84cd7465f3dd 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -264,6 +264,7 @@ "os_compute_api:servers:resize": "", "os_compute_api:servers:revert_resize": "", "os_compute_api:servers:show": "", + "os_compute_api:servers:show:host_status": "rule:admin_api", "os_compute_api:servers:create_image": "", "os_compute_api:servers:create_image:allow_volume_backed": "", "os_compute_api:servers:start": "rule:admin_or_owner", diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index 47fa7083d107..81d19d43685b 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -57,6 +57,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 2.14 - Remove onSharedStorage from evacuate request body and remove adminPass from the response body * 2.15 - Add soft-affinity and soft-anti-affinity policies + * 2.16 - Exposes host_status for servers/detail and servers/{server_id} """ # The minimum and maximum versions of the API supported @@ -65,7 +66,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: # Note(cyeoh): This only applies for the v2.1 API once microversions # support is fully merged. It does not affect the V2 API. _MIN_API_VERSION = "2.1" -_MAX_API_VERSION = "2.15" +_MAX_API_VERSION = "2.16" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/nova/api/openstack/compute/extended_server_attributes.py b/nova/api/openstack/compute/extended_server_attributes.py index 867f4b9e9706..ea0aecca37ce 100644 --- a/nova/api/openstack/compute/extended_server_attributes.py +++ b/nova/api/openstack/compute/extended_server_attributes.py @@ -17,16 +17,18 @@ from nova.api.openstack import api_version_request from nova.api.openstack import extensions from nova.api.openstack import wsgi - +from nova import compute ALIAS = "os-extended-server-attributes" authorize = extensions.os_compute_soft_authorizer(ALIAS) +soft_authorize = extensions.os_compute_soft_authorizer('servers') class ExtendedServerAttributesController(wsgi.Controller): def __init__(self, *args, **kwargs): super(ExtendedServerAttributesController, self).__init__(*args, **kwargs) + self.compute_api = compute.API(skip_policy_check=True) def _extend_server(self, context, server, instance, req): key = "OS-EXT-SRV-ATTR:hypervisor_hostname" @@ -44,26 +46,54 @@ class ExtendedServerAttributesController(wsgi.Controller): key = "OS-EXT-SRV-ATTR:%s" % attr server[key] = instance[attr] + def _server_host_status(self, context, server, instance, req): + host_status = self.compute_api.get_instance_host_status(instance) + server['host_status'] = host_status + @wsgi.extends def show(self, req, resp_obj, id): context = req.environ['nova.context'] + authorize_extend = False + authorize_host_status = False if authorize(context): + authorize_extend = True + if (api_version_request.is_supported(req, min_version='2.16') and + soft_authorize(context, action='show:host_status')): + authorize_host_status = True + if authorize_extend or authorize_host_status: server = resp_obj.obj['server'] db_instance = req.get_db_instance(server['id']) # server['id'] is guaranteed to be in the cache due to # the core API adding it in its 'show' method. - self._extend_server(context, server, db_instance, req) + if authorize_extend: + self._extend_server(context, server, db_instance, req) + if authorize_host_status: + self._server_host_status(context, server, db_instance, req) @wsgi.extends def detail(self, req, resp_obj): context = req.environ['nova.context'] + authorize_extend = False + authorize_host_status = False if authorize(context): + authorize_extend = True + if (api_version_request.is_supported(req, min_version='2.16') and + soft_authorize(context, action='show:host_status')): + authorize_host_status = True + if authorize_extend or authorize_host_status: servers = list(resp_obj.obj['servers']) + instances = req.get_db_instances() + # Instances is guaranteed to be in the cache due to + # the core API adding it in its 'detail' method. + if authorize_host_status: + host_statuses = self.compute_api.get_instances_host_statuses( + instances.values()) for server in servers: - db_instance = req.get_db_instance(server['id']) - # server['id'] is guaranteed to be in the cache due to - # the core API adding it in its 'detail' method. - self._extend_server(context, server, db_instance, req) + if authorize_extend: + instance = instances[server['id']] + self._extend_server(context, server, instance, req) + if authorize_host_status: + server['host_status'] = host_statuses[server['id']] class ExtendedServerAttributes(extensions.V21APIExtensionBase): diff --git a/nova/api/openstack/rest_api_version_history.rst b/nova/api/openstack/rest_api_version_history.rst index d3a67a2fb189..4564d8fb33e5 100644 --- a/nova/api/openstack/rest_api_version_history.rst +++ b/nova/api/openstack/rest_api_version_history.rst @@ -150,3 +150,10 @@ user documentation. From this version of the API users can choose 'soft-affinity' and 'soft-anti-affinity' rules too for server-groups. + +2.16 +---- + + Exposes new host_status attribute for servers/detail and servers/{server_id}. + Ability to get nova-compute status when querying servers. By default, this is + only exposed to cloud administrators. diff --git a/nova/compute/api.py b/nova/compute/api.py index a8be00a1531a..acdce3c33749 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -67,6 +67,7 @@ from nova import notifications from nova import objects from nova.objects import base as obj_base from nova.objects import block_device as block_device_obj +from nova.objects import fields as fields_obj from nova.objects import keypair as keypair_obj from nova.objects import quotas as quotas_obj from nova.objects import security_group as security_group_obj @@ -3304,6 +3305,40 @@ class API(base.Base): self.compute_rpcapi.external_instance_event( context, instances_by_host[host], events_by_host[host]) + def get_instance_host_status(self, instance): + if instance.host: + try: + service = [service for service in instance.services if + service.binary == 'nova-compute'][0] + if service.forced_down: + host_status = fields_obj.HostStatus.DOWN + elif service.disabled: + host_status = fields_obj.HostStatus.MAINTENANCE + else: + alive = self.servicegroup_api.service_is_up(service) + host_status = ((alive and fields_obj.HostStatus.UP) or + fields_obj.HostStatus.UNKNOWN) + except IndexError: + host_status = fields_obj.HostStatus.NONE + else: + host_status = fields_obj.HostStatus.NONE + return host_status + + def get_instances_host_statuses(self, instance_list): + host_status_dict = dict() + host_statuses = dict() + for instance in instance_list: + if instance.host: + if instance.host not in host_status_dict: + host_status = self.get_instance_host_status(instance) + host_status_dict[instance.host] = host_status + else: + host_status = host_status_dict[instance.host] + else: + host_status = fields_obj.HostStatus.NONE + host_statuses[instance.uuid] = host_status + return host_statuses + class HostAPI(base.Base): """Sub-set of the Compute Manager API for managing host operations.""" diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 991c3d7f2f55..f32e7748558e 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -426,8 +426,9 @@ class HostStatus(Enum): DOWN = "DOWN" # The nova-compute is forced_down. MAINTENANCE = "MAINTENANCE" # The nova-compute is disabled. UNKNOWN = "UNKNOWN" # The nova-compute has not reported. + NONE = "" # No host or nova-compute. - ALL = (UP, DOWN, MAINTENANCE, UNKNOWN) + ALL = (UP, DOWN, MAINTENANCE, UNKNOWN, NONE) def __init__(self): super(HostStatus, self).__init__( diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json.tpl new file mode 100644 index 000000000000..8d646fa447a2 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/server-get-resp.json.tpl @@ -0,0 +1,69 @@ +{ + "server": { + "OS-EXT-SRV-ATTR:host": "%(compute_host)s", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s", + "OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s", + "OS-EXT-SRV-ATTR:hostname": "new-server-test", + "OS-EXT-SRV-ATTR:launch_index": "0", + "OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s", + "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda", + "OS-EXT-SRV-ATTR:kernel_id": null, + "OS-EXT-SRV-ATTR:ramdisk_id": null, + "OS-EXT-SRV-ATTR:user_data": null, + "locked": false, + "accessIPv4": "%(access_ip_v4)s", + "accessIPv6": "%(access_ip_v6)s", + "updated": "%(isotime)s", + "created": "%(isotime)s", + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4, + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed" + } + ] + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "%(compute_endpoint)s/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "%(hostid)s", + "id": "%(uuid)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(compute_endpoint)s/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(versioned_compute_endpoint)s/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/servers/%(uuid)s", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "user_id": "fake", + "key_name": null + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json.tpl new file mode 100644 index 000000000000..ae97fa9b74e2 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-extended-server-attributes/v2.16/servers-detail-resp.json.tpl @@ -0,0 +1,71 @@ +{ + "servers": [ + { + "OS-EXT-SRV-ATTR:host": "%(compute_host)s", + "OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s", + "OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s", + "OS-EXT-SRV-ATTR:hostname": "new-server-test", + "OS-EXT-SRV-ATTR:launch_index": "0", + "OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s", + "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda", + "OS-EXT-SRV-ATTR:kernel_id": null, + "OS-EXT-SRV-ATTR:ramdisk_id": null, + "OS-EXT-SRV-ATTR:user_data": null, + "locked": false, + "accessIPv4": "%(access_ip_v4)s", + "accessIPv6": "%(access_ip_v6)s", + "updated": "%(isotime)s", + "created": "%(isotime)s", + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4, + "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff", + "OS-EXT-IPS:type": "fixed" + } + ] + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "%(compute_endpoint)s/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "%(hostid)s", + "id": "%(uuid)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(compute_endpoint)s/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(versioned_compute_endpoint)s/servers/%(id)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/servers/%(id)s", + "rel": "bookmark" + } + ], + "metadata": { + "My Server Name": "Apache1" + }, + "name": "new-server-test", + "progress": 0, + "status": "ACTIVE", + "host_status": "UP", + "tenant_id": "openstack", + "user_id": "fake", + "key_name": null + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl index 62843315822e..61da9c796e4b 100644 --- a/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl +++ b/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl index 7d84250a085b..fd2160d81621 100644 --- a/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl +++ b/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/tests/functional/api_sample_tests/test_extended_server_attributes.py b/nova/tests/functional/api_sample_tests/test_extended_server_attributes.py index c6d35d12b06e..a749c00a1101 100644 --- a/nova/tests/functional/api_sample_tests/test_extended_server_attributes.py +++ b/nova/tests/functional/api_sample_tests/test_extended_server_attributes.py @@ -65,3 +65,34 @@ class ExtendedServerAttributesJsonTest(test_servers.ServersSampleBase): subs['access_ip_v4'] = '1.2.3.4' subs['access_ip_v6'] = '80fe::' self._verify_response('servers-detail-resp', subs, response, 200) + + +class ExtendedServerAttributesJsonTestV216(ExtendedServerAttributesJsonTest): + microversion = '2.16' + scenarios = [('v2_16', {'api_major_version': 'v2.1'})] + + def test_show(self): + uuid = self._post_server() + + response = self._do_get('servers/%s' % uuid) + subs = {} + subs['hostid'] = '[a-f0-9]+' + subs['id'] = uuid + subs['instance_name'] = 'instance-\d{8}' + subs['hypervisor_hostname'] = r'[\w\.\-]+' + subs['access_ip_v4'] = '1.2.3.4' + subs['access_ip_v6'] = '80fe::' + self._verify_response('server-get-resp', subs, response, 200) + + def test_detail(self): + uuid = self._post_server() + + response = self._do_get('servers/detail') + subs = {} + subs['hostid'] = '[a-f0-9]+' + subs['id'] = uuid + subs['instance_name'] = 'instance-\d{8}' + subs['hypervisor_hostname'] = r'[\w\.\-]+' + subs['access_ip_v4'] = '1.2.3.4' + subs['access_ip_v6'] = '80fe::' + self._verify_response('servers-detail-resp', subs, response, 200) diff --git a/nova/tests/unit/api/openstack/compute/test_extended_server_attributes.py b/nova/tests/unit/api/openstack/compute/test_extended_server_attributes.py index 6ca36ec23bc7..8899a9138450 100644 --- a/nova/tests/unit/api/openstack/compute/test_extended_server_attributes.py +++ b/nova/tests/unit/api/openstack/compute/test_extended_server_attributes.py @@ -34,6 +34,12 @@ UUID4 = '00000000-0000-0000-0000-000000000004' UUID5 = '00000000-0000-0000-0000-000000000005' +def fake_services(host): + service_list = [objects.Service(id=0, host=host, forced_down=True, + binary='nova-compute')] + return objects.ServiceList(objects=service_list) + + def fake_compute_get(*args, **kwargs): return fakes.stub_instance_obj( None, 1, uuid=UUID3, host="host-fake", @@ -42,7 +48,8 @@ def fake_compute_get(*args, **kwargs): kernel_id=UUID4, ramdisk_id=UUID5, display_name="hostname-1", root_device_name="/dev/vda", - user_data="userdata") + user_data="userdata", + services=fake_services("host-fake")) def fake_compute_get_all(*args, **kwargs): @@ -53,14 +60,16 @@ def fake_compute_get_all(*args, **kwargs): kernel_id=UUID4, ramdisk_id=UUID5, display_name="hostname-1", root_device_name="/dev/vda", - user_data="userdata"), + user_data="userdata", + services=fake_services("host-1")), fakes.stub_instance_obj( None, 2, uuid=UUID2, host="host-2", node="node-2", reservation_id="r-2", launch_index=1, kernel_id=UUID4, ramdisk_id=UUID5, display_name="hostname-2", root_device_name="/dev/vda", - user_data="userdata"), + user_data="userdata", + services=fake_services("host-2")), ] return objects.InstanceList(objects=inst_list) @@ -208,3 +217,36 @@ class ExtendedServerAttributesTestV23(ExtendedServerAttributesTestV21): hostname="hostname-%s" % (i + 1), root_device_name="/dev/vda", user_data="userdata") + + +class ExtendedServerAttributesTestV216(ExtendedServerAttributesTestV21): + wsgi_api_version = '2.16' + + def assertServerAttributes(self, server, host, node, instance_name, + host_status): + super(ExtendedServerAttributesTestV216, self).assertServerAttributes( + server, host, node, instance_name) + self.assertEqual(server.get('host_status'), host_status) + + def test_show(self): + url = self.fake_url + '/servers/%s' % UUID3 + res = self._make_request(url) + + self.assertEqual(res.status_int, 200) + self.assertServerAttributes(self._get_server(res.body), + host='host-fake', + node='node-fake', + instance_name=NAME_FMT % 1, + host_status="DOWN") + + def test_detail(self): + url = self.fake_url + '/servers/detail' + res = self._make_request(url) + + self.assertEqual(res.status_int, 200) + for i, server in enumerate(self._get_servers(res.body)): + self.assertServerAttributes(server, + host='host-%s' % (i + 1), + node='node-%s' % (i + 1), + instance_name=NAME_FMT % (i + 1), + host_status="DOWN") diff --git a/nova/tests/unit/api/openstack/compute/test_versions.py b/nova/tests/unit/api/openstack/compute/test_versions.py index f3369d46bb30..6a61c21731da 100644 --- a/nova/tests/unit/api/openstack/compute/test_versions.py +++ b/nova/tests/unit/api/openstack/compute/test_versions.py @@ -66,7 +66,7 @@ EXP_VERSIONS = { "v2.1": { "id": "v2.1", "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z", "links": [ @@ -128,7 +128,7 @@ class VersionsTestV20(test.NoDBTestCase): { "id": "v2.1", "status": "CURRENT", - "version": "2.15", + "version": "2.16", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z", "links": [ diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index 4367b5d1587e..e7dabc08b522 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -440,7 +440,8 @@ def stub_instance(id=1, user_id=None, project_id=None, host=None, availability_zone='', locked_by=None, cleaned=False, memory_mb=0, vcpus=0, root_gb=0, ephemeral_gb=0, instance_type=None, launch_index=0, kernel_id="", - ramdisk_id="", user_data=None, system_metadata=None): + ramdisk_id="", user_data=None, system_metadata=None, + services=None): if user_id is None: user_id = 'fake_user' if project_id is None: @@ -548,7 +549,8 @@ def stub_instance(id=1, user_id=None, project_id=None, host=None, "pci_requests": None, "flavor": flavorinfo, }, - "cleaned": cleaned} + "cleaned": cleaned, + "services": services} instance.update(info_cache) instance['info_cache']['instance_uuid'] = instance['uuid'] @@ -564,6 +566,10 @@ def stub_instance_obj(ctxt, *args, **kwargs): db_inst, expected_attrs=expected) inst.fault = None + if db_inst["services"] is not None: + # This ensures services there if one wanted so + inst.services = db_inst["services"] + return inst diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 3936efebafe6..b20d7ae82415 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -41,6 +41,7 @@ from nova import db from nova import exception from nova import objects from nova.objects import base as obj_base +from nova.objects import fields as fields_obj from nova.objects import quotas as quotas_obj from nova import policy from nova import quota @@ -70,6 +71,7 @@ class _ComputeAPIUnitTestMixIn(object): super(_ComputeAPIUnitTestMixIn, self).setUp() self.user_id = 'fake' self.project_id = 'fake' + self.compute_api = compute_api.API() self.context = context.RequestContext(self.user_id, self.project_id) @@ -155,6 +157,13 @@ class _ComputeAPIUnitTestMixIn(object): instance.obj_reset_changes() return instance + def _obj_to_list_obj(self, list_obj, obj): + list_obj.objects = [] + list_obj.objects.append(obj) + list_obj._context = self.context + list_obj.obj_reset_changes() + return list_obj + def test_create_quota_exceeded_messages(self): image_href = "image_href" image_id = 0 @@ -3019,6 +3028,61 @@ class _ComputeAPIUnitTestMixIn(object): instance, 1) self.assertEqual('Server-%s' % instance.uuid, instance.hostname) + def test_host_statuses(self): + # NOTE(tojuvone) Some test cases break utcnow() by calling + # timeutils.set_time_override() with some own time. Have to issue a + # bug to fix those cases to reset time back like line below so next + # test cases will work. + timeutils.clear_time_override() + instances = [ + objects.Instance(uuid='uuid1', host='host1', services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host1', + disabled=True, forced_down=True, + binary='nova-compute'))), + objects.Instance(uuid='uuid2', host='host2', services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host2', + disabled=True, forced_down=False, + binary='nova-compute'))), + objects.Instance(uuid='uuid3', host='host3', services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host3', + disabled=False, last_seen_up=timeutils.utcnow() + - datetime.timedelta(minutes=5), + forced_down=False, binary='nova-compute'))), + objects.Instance(uuid='uuid4', host='host4', services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host4', + disabled=False, last_seen_up=timeutils.utcnow(), + forced_down=False, binary='nova-compute'))), + objects.Instance(uuid='uuid5', host='host5', services= + objects.ServiceList()), + objects.Instance(uuid='uuid6', host=None, services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host6', + disabled=True, forced_down=False, + binary='nova-compute'))), + objects.Instance(uuid='uuid7', host='host2', services= + self._obj_to_list_obj(objects.ServiceList( + self.context), objects.Service(id=0, host='host2', + disabled=True, forced_down=False, + binary='nova-compute'))) + ] + + host_statuses = self.compute_api.get_instances_host_statuses( + instances) + expect_statuses = {'uuid1': fields_obj.HostStatus.DOWN, + 'uuid2': fields_obj.HostStatus.MAINTENANCE, + 'uuid3': fields_obj.HostStatus.UNKNOWN, + 'uuid4': fields_obj.HostStatus.UP, + 'uuid5': fields_obj.HostStatus.NONE, + 'uuid6': fields_obj.HostStatus.NONE, + 'uuid7': fields_obj.HostStatus.MAINTENANCE} + for instance in instances: + self.assertEqual(expect_statuses[instance.uuid], + host_statuses[instance.uuid]) + class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index 7eec76f257e6..49877c516158 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -118,6 +118,7 @@ policy_data = """ "os_compute_api:servers:resize": "", "os_compute_api:servers:revert_resize": "", "os_compute_api:servers:show": "", + "os_compute_api:servers:show:host_status": "", "os_compute_api:servers:create_image": "", "os_compute_api:servers:create_image:allow_volume_backed": "", "os_compute_api:servers:update": "", diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index 63f118aec4ff..ef33717b22f3 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -299,6 +299,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:servers:create:forced_host", "os_compute_api:servers:detail:get_all_tenants", "os_compute_api:servers:index:get_all_tenants", +"os_compute_api:servers:show:host_status", "network:attach_external_network", "os_compute_api:os-admin-actions", "os_compute_api:os-admin-actions:reset_network", diff --git a/releasenotes/notes/bp-get-valid-server-state-a817488f4c8d3822.yaml b/releasenotes/notes/bp-get-valid-server-state-a817488f4c8d3822.yaml new file mode 100644 index 000000000000..a17d5ebb19ce --- /dev/null +++ b/releasenotes/notes/bp-get-valid-server-state-a817488f4c8d3822.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + A new host_status attribute for servers/detail and servers/{server_id}. + In order to use this new feature, user have to contain the header of + request microversion v2.16 in the API request. A new policy + ``os_compute_api:servers:show:host_status`` added to enable the feature. + By default, this is only exposed to cloud administrators.