Nova: test live migration with serial console

Nova offers a "serial console" as an alternative to graphical consoles
like VNC/SPICE/RDP. This is useful for platforms which don't have
graphical consoles, for example the "IBM system z" platform.

This change introduces a test which ensures that the interaction with
the serial console is possible before and after a live-migration.
As the unified remote console API is available since microversion 2.6,
I use this as a base for the tests. This made id necessary to update
the schemas.

This change introduces a config option to enable new test cases for
the serial console.
A Nova change (see I7af395a867e0657c26fa064d2b0134345cd96814),
which uses the hook for live-migration testing, will use the config
option of this change to alter the testing system on the fly to
enable the testing of the serial console.

Closes-Bug: #1560358
Needed-By: I7af395a867e0657c26fa064d2b0134345cd96814
Change-Id: I020fd94d970ad0cdf7ab65d7656da6ca5766094b
This commit is contained in:
Markus Zoeller 2017-02-17 10:09:22 +01:00
parent b86de8898f
commit 69d58b8f34
8 changed files with 195 additions and 2 deletions

View File

@ -0,0 +1,7 @@
---
features:
- |
A new boolean config option ``serial_console`` is added to the section
``compute-feature-enabled``. If enabled, tests, which validate the
behavior of Nova's *serial console* feature (an alternative to VNC,
RDP, SPICE) can be executed.

View File

@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_log import log as logging
import testtools
from tempest.api.compute import base
from tempest.common import compute
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@ -169,6 +172,80 @@ class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
self.assertEqual(target_host, self._get_host_for_server(server_id))
class LiveBlockMigrationRemoteConsolesV26TestJson(LiveBlockMigrationTestJSON):
min_microversion = '2.6'
max_microversion = 'latest'
@decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7')
@testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
'Serial console not supported.')
@testtools.skipUnless(
test.is_scheduler_filter_enabled("DifferentHostFilter"),
'DifferentHostFilter is not available.')
def test_live_migration_serial_console(self):
"""Test the live-migration of an instance which has a serial console
The serial console feature of an instance uses ports on the host.
These ports need to be updated when they are already in use by
another instance on the target host. This test checks if this
update behavior is correctly done, by connecting to the serial
consoles of the instances before and after the live migration.
"""
server01_id = self.create_test_server(wait_until='ACTIVE')['id']
hints = {'different_host': server01_id}
server02_id = self.create_test_server(scheduler_hints=hints,
wait_until='ACTIVE')['id']
host01_id = self._get_host_for_server(server01_id)
host02_id = self._get_host_for_server(server02_id)
self.assertNotEqual(host01_id, host02_id)
# At this step we have 2 instances on different hosts, both with
# serial consoles, both with port 10000 (the default value).
# https://bugs.launchpad.net/nova/+bug/1455252 describes the issue
# when live-migrating in such a scenario.
self._verify_console_interaction(server01_id)
self._verify_console_interaction(server02_id)
self._migrate_server_to(server01_id, host02_id)
waiters.wait_for_server_status(self.servers_client,
server01_id, 'ACTIVE')
self.assertEqual(host02_id, self._get_host_for_server(server01_id))
self._verify_console_interaction(server01_id)
# At this point, both instances have a valid serial console
# connection, which means the ports got updated.
def _verify_console_interaction(self, server_id):
body = self.servers_client.get_remote_console(server_id,
console_type='serial',
protocol='serial')
console_url = body['remote_console']['url']
data = "test_live_migration_serial_console"
console_output = ''
t = 0.0
interval = 0.1
ws = compute.create_websocket(console_url)
try:
# NOTE (markus_z): It can take a long time until the terminal
# of the instance is available for interaction. Hence the
# long timeout value.
while data not in console_output and t <= 120.0:
try:
ws.send_frame(data)
recieved = ws.receive_frame()
console_output += recieved
except Exception:
# In case we had an issue with send/receive on the
# websocket connection, we create a new one.
ws = compute.create_websocket(console_url)
time.sleep(interval)
t += interval
finally:
ws.close()
self.assertIn(data, console_output)
class LiveAutoBlockMigrationV225TestJSON(LiveBlockMigrationTestJSON):
min_microversion = '2.25'
max_microversion = 'latest'

View File

@ -399,6 +399,11 @@ ComputeFeaturesGroup = [
default=False,
help='Enable RDP console. This configuration value should '
'be same as [nova.rdp]->enabled in nova.conf'),
cfg.BoolOpt('serial_console',
default=False,
help='Enable serial console. This configuration value '
'should be the same as [nova.serial_console]->enabled '
'in nova.conf'),
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '

View File

@ -0,0 +1,48 @@
# Copyright 2016 IBM Corp.
#
# 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_3 import servers
list_servers = copy.deepcopy(servers.list_servers)
get_server = copy.deepcopy(servers.get_server)
list_servers_detail = copy.deepcopy(servers.list_servers_detail)
# NOTE: The consolidated remote console API got introduced with v2.6
# with bp/consolidate-console-api. See Nova commit 578bafeda
get_remote_consoles = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'remote_console': {
'type': 'object',
'properties': {
'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']},
'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5',
'spice-html5', 'serial']},
'url': {
'type': 'string',
'format': 'uri'
}
},
'additionalProperties': False,
'required': ['protocol', 'type', 'url']
}
},
'additionalProperties': False,
'required': ['remote_console']
}
}

View File

@ -14,7 +14,7 @@
import copy
from tempest.lib.api_schema.response.compute.v2_3 import servers
from tempest.lib.api_schema.response.compute.v2_6 import servers
list_servers = copy.deepcopy(servers.list_servers)

View File

@ -27,6 +27,7 @@ from tempest.lib.api_schema.response.compute.v2_16 import servers as schemav216
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_3 import servers as schemav23
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.common import rest_client
from tempest.lib.services.compute import base_compute_client
@ -37,7 +38,8 @@ class ServersClient(base_compute_client.BaseComputeClient):
schema_versions_info = [
{'min': None, 'max': '2.2', 'schema': schema},
{'min': '2.3', 'max': '2.8', 'schema': schemav23},
{'min': '2.3', 'max': '2.5', 'schema': schemav23},
{'min': '2.6', 'max': '2.8', 'schema': schemav26},
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
@ -598,6 +600,29 @@ class ServersClient(base_compute_client.BaseComputeClient):
return self.action(server_id, 'os-getConsoleOutput',
schema.get_console_output, **kwargs)
def get_remote_console(self, server_id, console_type, protocol, **kwargs):
"""Get a remote console.
For a full list of available parameters, please refer to the official
API reference:
TODO (markus_z) The api-ref for that isn't yet available, update this
here when the docs in Nova are updated. The old API is at
http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
"""
param = {
'remote_console': {
'type': console_type,
'protocol': protocol,
}
}
post_body = json.dumps(param)
resp, body = self.post("servers/%s/remote-consoles" % server_id,
post_body)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.get_remote_consoles, resp, body)
return rest_client.ResponseBody(resp, body)
def list_virtual_interfaces(self, server_id):
"""List the virtual interfaces used in an instance."""
resp, body = self.get('/'.join(['servers', server_id,

View File

@ -1168,3 +1168,34 @@ class TestServersClient(base.BaseServiceTest):
tag=self.FAKE_TAGS[0],
status=204,
to_utf=bytes_body)
class TestServersClientMinV26(base.BaseServiceTest):
def setUp(self):
super(TestServersClientMinV26, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = servers_client.ServersClient(fake_auth, 'compute',
'regionOne')
base_compute_client.COMPUTE_MICROVERSION = '2.6'
self.server_id = "920eaac8-a284-4fd1-9c2c-b30f0181b125"
def tearDown(self):
super(TestServersClientMinV26, self).tearDown()
base_compute_client.COMPUTE_MICROVERSION = None
def test_get_remote_consoles(self):
self.check_service_client_function(
self.client.get_remote_console,
'tempest.lib.common.rest_client.RestClient.post',
{
'remote_console': {
'protocol': 'serial',
'type': 'serial',
'url': 'ws://127.0.0.1:6083/?token=IllAllowIt'
}
},
server_id=self.server_id,
console_type='serial',
protocol='serial',
)