Add host_status attribute for servers/detail and servers/{server_id}

When a compute service fails, the power states of the hosted VMs are not
updated. A normal user querying his or her VMs does not get any indication
about the failure. Also there is no indication about maintenance.

This change will expose new attribute host_status to user querying his
VMs. Attribute is only seen if policy allows.

DocImpact: This adds API microversion
Implements blueprint get-valid-server-state
APIImpact

Change-Id: I5abea08bdc27624a7f23a7db8964f8c2a7b0eaa7
This commit is contained in:
Tomi Juvonen 2016-01-12 11:18:00 +02:00
parent 98d0969254
commit 9345d5835f
24 changed files with 645 additions and 19 deletions

View File

@ -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"
}
}

View File

@ -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"
}
]
}

View File

@ -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"
}
}

View File

@ -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"
}
]
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.15",
"version": "2.16",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.15",
"version": "2.16",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -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",

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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."""

View File

@ -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__(

View File

@ -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
}
}

View File

@ -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
}
]
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.15",
"version": "2.16",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.15",
"version": "2.16",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -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)

View File

@ -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")

View File

@ -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": [

View File

@ -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

View File

@ -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):

View File

@ -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": "",

View File

@ -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",

View File

@ -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.