Tests for Nova instance diagnostics (microversion v2.48)

Existing instance diagnostics tests were moved to the new files
to test diagnostics for different microversions (2.1 and 2.48).

Depends-On: If0b1493cc5c1c7f0d9896dd68342ad4dea4f7da2

Change-Id: I7757c5beeea3d3b0bc15a51cafc5ea2ada65e76c
This commit is contained in:
Sergey Nikitin 2017-02-03 13:16:33 +04:00 committed by Sergey Nikitin
parent 8fde5658a8
commit 0cb4f2255c
10 changed files with 253 additions and 21 deletions

View File

@ -330,6 +330,10 @@ Microversion tests implemented in Tempest
.. _2.47: http://docs.openstack.org/developer/nova/api_microversion_history.html#id42 .. _2.47: http://docs.openstack.org/developer/nova/api_microversion_history.html#id42
* `2.48`_
.. _2.48: http://docs.openstack.org/developer/nova/api_microversion_history.html#id43
* Volume * Volume
* `3.3`_ * `3.3`_

View File

@ -0,0 +1,4 @@
---
features:
- |
Add validation schema for Nova server diagnostics API

View File

@ -0,0 +1,76 @@
# Copyright 2017 Mirantis Inc.
#
# 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 time
from tempest.api.compute import base
from tempest.lib import decorators
class ServerDiagnosticsTest(base.BaseV2ComputeAdminTest):
min_microversion = None
max_microversion = '2.47'
@classmethod
def setup_clients(cls):
super(ServerDiagnosticsTest, cls).setup_clients()
cls.client = cls.os_admin.servers_client
@decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
def test_get_server_diagnostics(self):
server_id = self.create_test_server(wait_until='ACTIVE')['id']
diagnostics = self.client.show_server_diagnostics(server_id)
# NOTE(snikitin): Before microversion 2.48 response data from each
# hypervisor (libvirt, xen, vmware) was different. None of the fields
# were equal. As this test is common for libvirt, xen and vmware CI
# jobs we can't check any field in the response because all fields are
# different.
self.assertNotEmpty(diagnostics)
class ServerDiagnosticsV248Test(base.BaseV2ComputeAdminTest):
min_microversion = '2.48'
max_microversion = 'latest'
@classmethod
def setup_clients(cls):
super(ServerDiagnosticsV248Test, cls).setup_clients()
cls.client = cls.os_admin.servers_client
@decorators.idempotent_id('64d0d48c-dff1-11e6-bf01-fe55135034f3')
def test_get_server_diagnostics(self):
server_id = self.create_test_server(wait_until='ACTIVE')['id']
# Response status and filed types will be checked by json schema
self.client.show_server_diagnostics(server_id)
# NOTE(snikitin): This is a special case for Xen hypervisor. In Xen
# case we're getting diagnostics stats from the RRDs which are updated
# every 5 seconds. It means that diagnostics information may be
# incomplete during first 5 seconds of VM life. In such cases methods
# which get diagnostics stats from Xen may raise exceptions or
# return `NaN` values. Such behavior must be handled correctly.
# Response must contain all diagnostics fields (may be with `None`
# values) and response status must be 200. Line above checks it by
# json schema.
time.sleep(10)
diagnostics = self.client.show_server_diagnostics(server_id)
# NOTE(snikitin): After 10 seconds diagnostics fields must contain
# not None values. But we will check only "memory_details.maximum"
# field because only this field meets all the following conditions:
# 1) This field may be unset because of Xen 5 seconds timeout.
# 2) This field is present in responses from all three supported
# hypervisors (libvirt, xen, vmware).
self.assertIsNotNone(diagnostics['memory_details']['maximum'])

View File

@ -0,0 +1,40 @@
# Copyright 2017 Mirantis Inc.
#
# 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.
from tempest.api.compute import base
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest):
min_microversion = None
max_microversion = '2.47'
@classmethod
def setup_clients(cls):
super(ServerDiagnosticsNegativeTest, cls).setup_clients()
cls.client = cls.servers_client
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
def test_get_server_diagnostics_by_non_admin(self):
# Non-admin user cannot view server diagnostics according to policy
server_id = self.create_test_server(wait_until='ACTIVE')['id']
self.assertRaises(lib_exc.Forbidden,
self.client.show_server_diagnostics, server_id)
class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest):
min_microversion = '2.48'
max_microversion = 'latest'

View File

@ -164,17 +164,6 @@ class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
server = self.client.show_server(self.s1_id)['server'] server = self.client.show_server(self.s1_id)['server']
self.assertEqual(server['status'], 'ACTIVE') self.assertEqual(server['status'], 'ACTIVE')
@decorators.skip_because(bug="1240043")
@decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3')
def test_get_server_diagnostics_by_admin(self):
# Retrieve server diagnostics by admin user
diagnostic = self.client.show_server_diagnostics(self.s1_id)
basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop',
'tx_packets', 'tx_errors', 'tx_drop',
'read_req', 'write_req', 'cpu', 'memory']
for key in basic_attrs:
self.assertIn(key, str(diagnostic.keys()))
@decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442') @decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442')
def test_rebuild_server_in_error_state(self): def test_rebuild_server_in_error_state(self):
# The server in error state should be rebuilt using the provided # The server in error state should be rebuilt using the provided

View File

@ -107,14 +107,6 @@ class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
self.assertRaises(lib_exc.NotFound, self.assertRaises(lib_exc.NotFound,
self.client.reset_state, '999', state='error') self.client.reset_state, '999', state='error')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac')
def test_get_server_diagnostics_by_non_admin(self):
# Non-admin user can not view server diagnostics according to policy
self.assertRaises(lib_exc.Forbidden,
self.non_adm_client.show_server_diagnostics,
self.s1_id)
@decorators.attr(type=['negative']) @decorators.attr(type=['negative'])
@decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221') @decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221')
def test_migrate_non_existent_server(self): def test_migrate_non_existent_server(self):

View File

@ -582,3 +582,10 @@ evacuate_server_with_admin_pass = {
'required': ['adminPass'] 'required': ['adminPass']
} }
} }
show_server_diagnostics = {
'status_code': [200],
'response_body': {
'type': 'object'
}
}

View File

@ -0,0 +1,115 @@
# Copyright 2017 Mirantis Inc.
#
# 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 copy
from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
from tempest.lib.api_schema.response.compute.v2_47 import servers as servers247
show_server_diagnostics = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'state': {
'type': 'string', 'enum': [
'pending', 'running', 'paused', 'shutdown', 'crashed',
'suspended']
},
'driver': {
'type': 'string', 'enum': [
'libvirt', 'xenapi', 'vmwareapi', 'ironic', 'hyperv']
},
'hypervisor': {'type': ['string', 'null']},
'hypervisor_os': {'type': ['string', 'null']},
'uptime': {'type': ['integer', 'null']},
'config_drive': {'type': 'boolean'},
'num_cpus': {'type': 'integer'},
'num_nics': {'type': 'integer'},
'num_disks': {'type': 'integer'},
'memory_details': {
'type': 'object',
'properties': {
'maximum': {'type': ['integer', 'null']},
'used': {'type': ['integer', 'null']}
},
'additionalProperties': False,
'required': ['maximum', 'used']
},
'cpu_details': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': {'type': ['integer', 'null']},
'time': {'type': ['integer', 'null']},
'utilisation': {'type': ['integer', 'null']}
},
'additionalProperties': False,
'required': ['id', 'time', 'utilisation']
}
},
'nic_details': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'mac_address': {'oneOf': [parameter_types.mac_address,
{'type': 'null'}]},
'rx_octets': {'type': ['integer', 'null']},
'rx_errors': {'type': ['integer', 'null']},
'rx_drop': {'type': ['integer', 'null']},
'rx_packets': {'type': ['integer', 'null']},
'rx_rate': {'type': ['integer', 'null']},
'tx_octets': {'type': ['integer', 'null']},
'tx_errors': {'type': ['integer', 'null']},
'tx_drop': {'type': ['integer', 'null']},
'tx_packets': {'type': ['integer', 'null']},
'tx_rate': {'type': ['integer', 'null']}
},
'additionalProperties': False,
'required': ['mac_address', 'rx_octets', 'rx_errors',
'rx_drop',
'rx_packets', 'rx_rate', 'tx_octets',
'tx_errors',
'tx_drop', 'tx_packets', 'tx_rate']
}
},
'disk_details': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'read_bytes': {'type': ['integer', 'null']},
'read_requests': {'type': ['integer', 'null']},
'write_bytes': {'type': ['integer', 'null']},
'write_requests': {'type': ['integer', 'null']},
'errors_count': {'type': ['integer', 'null']}
},
'additionalProperties': False,
'required': ['read_bytes', 'read_requests', 'write_bytes',
'write_requests', 'errors_count']
}
}
},
'additionalProperties': False,
'required': [
'state', 'driver', 'hypervisor', 'hypervisor_os', 'uptime',
'config_drive', 'num_cpus', 'num_nics', 'num_disks',
'memory_details', 'cpu_details', 'nic_details', 'disk_details'],
}
}
get_server = copy.deepcopy(servers247.get_server)

View File

@ -28,6 +28,7 @@ from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226 from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23 from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247 from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client from tempest.lib.common import rest_client
@ -45,7 +46,8 @@ class ServersClient(base_compute_client.BaseComputeClient):
{'min': '2.16', 'max': '2.18', 'schema': schemav216}, {'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219}, {'min': '2.19', 'max': '2.25', 'schema': schemav219},
{'min': '2.26', 'max': '2.46', 'schema': schemav226}, {'min': '2.26', 'max': '2.46', 'schema': schemav226},
{'min': '2.47', 'max': None, 'schema': schemav247}] {'min': '2.47', 'max': '2.47', 'schema': schemav247},
{'min': '2.48', 'max': None, 'schema': schemav248}]
def __init__(self, auth_provider, service, region, def __init__(self, auth_provider, service, region,
enable_instance_password=True, **kwargs): enable_instance_password=True, **kwargs):
@ -658,7 +660,10 @@ class ServersClient(base_compute_client.BaseComputeClient):
def show_server_diagnostics(self, server_id): def show_server_diagnostics(self, server_id):
"""Get the usage data for a server.""" """Get the usage data for a server."""
resp, body = self.get("servers/%s/diagnostics" % server_id) resp, body = self.get("servers/%s/diagnostics" % server_id)
return rest_client.ResponseBody(resp, json.loads(body)) body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.show_server_diagnostics, resp, body)
return rest_client.ResponseBody(resp, body)
def list_instance_actions(self, server_id): def list_instance_actions(self, server_id):
"""List the provided server action.""" """List the provided server action."""