Adds Nova vm live migration benchmark scenarios
-NovaServers.boot_and_live_migrate_server Implements blueprint nova-benchmark-scenarios-for-vm-migration Change-Id: Ib92bf0640c5bdff03162c0cc735df85912295466
This commit is contained in:
parent
d4b843d6e5
commit
9a06c00407
26
doc/samples/tasks/scenarios/nova/boot-and-live-migrate.json
Normal file
26
doc/samples/tasks/scenarios/nova/boot-and-live-migrate.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"NovaServers.boot_and_live_migrate_server": [
|
||||
{
|
||||
"args": {
|
||||
"flavor": {
|
||||
"name": "m1.nano"
|
||||
},
|
||||
"image": {
|
||||
"name": "^cirros.*uec$"
|
||||
},
|
||||
"block_migration": false
|
||||
},
|
||||
"runner": {
|
||||
"type": "constant",
|
||||
"times": 10,
|
||||
"concurrency": 2
|
||||
},
|
||||
"context": {
|
||||
"users": {
|
||||
"tenants": 1,
|
||||
"users_per_tenant": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
16
doc/samples/tasks/scenarios/nova/boot-and-live-migrate.yaml
Normal file
16
doc/samples/tasks/scenarios/nova/boot-and-live-migrate.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
NovaServers.boot_and_live_migrate_server:
|
||||
- args:
|
||||
flavor:
|
||||
name: m1.nano
|
||||
image:
|
||||
name: "^cirros.*uec$"
|
||||
block_migration: false
|
||||
runner:
|
||||
type: constant
|
||||
times: 10
|
||||
concurrency: 2
|
||||
context:
|
||||
users:
|
||||
tenants: 1
|
||||
users_per_tenant: 1
|
@ -229,3 +229,33 @@ class NovaServers(utils.NovaScenario,
|
||||
else:
|
||||
self._resize_revert(server)
|
||||
self._delete_server(server, force=force_delete)
|
||||
|
||||
@types.set(image=types.ImageResourceType,
|
||||
flavor=types.FlavorResourceType)
|
||||
@validation.image_valid_on_flavor("flavor", "image")
|
||||
@validation.required_services(consts.Service.NOVA)
|
||||
@validation.required_openstack(admin=True, users=True)
|
||||
@base.scenario(context={"cleanup": ["nova"]})
|
||||
def boot_and_live_migrate_server(self, image,
|
||||
flavor, block_migration=False,
|
||||
disk_over_commit=False, **kwargs):
|
||||
"""Tests VM Live Migration.
|
||||
|
||||
This scenario launches a VM on a compute node available in
|
||||
the availability zone and then migrates the VM to another
|
||||
compute node on the same availability zone.
|
||||
|
||||
:param image: Glance image to be used to launch an instance
|
||||
:param flavor: Nova flavor to be used to launch an instance
|
||||
:param block_migration: Specifies the migration type
|
||||
:param disk_over_commit: Specifies whether to allow overcommit
|
||||
on migrated instance or not
|
||||
"""
|
||||
server = self._boot_server(self._generate_random_name(),
|
||||
image, flavor, **kwargs)
|
||||
|
||||
new_host = self._find_host_to_migrate(server)
|
||||
self._live_migrate(server, new_host,
|
||||
block_migration, disk_over_commit)
|
||||
|
||||
self._delete_server(server)
|
||||
|
@ -13,12 +13,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from rally.benchmark.scenarios import base
|
||||
from rally.benchmark import utils as bench_utils
|
||||
from rally import exceptions
|
||||
|
||||
|
||||
nova_benchmark_opts = []
|
||||
@ -37,6 +39,7 @@ option_names_and_defaults = [
|
||||
('resize', 2, 400, 5),
|
||||
('resize_confirm', 0, 200, 2),
|
||||
('resize_revert', 0, 200, 2),
|
||||
('live_migrate', 1, 400, 2),
|
||||
]
|
||||
|
||||
for action, prepoll, timeout, poll in option_names_and_defaults:
|
||||
@ -112,7 +115,6 @@ class NovaScenario(base.Scenario):
|
||||
if net.label not in fip_pool:
|
||||
kwargs['nics'] = [{'net-id': net.id}]
|
||||
break
|
||||
|
||||
server = self.clients("nova").servers.create(server_name, image_id,
|
||||
flavor_id, **kwargs)
|
||||
|
||||
@ -507,3 +509,60 @@ class NovaScenario(base.Scenario):
|
||||
check_interval=(
|
||||
CONF.benchmark.nova_server_resize_revert_poll_interval)
|
||||
)
|
||||
|
||||
@base.atomic_action_timer('nova.live_migrate')
|
||||
def _live_migrate(self, server, target_host, block_migration=False,
|
||||
disk_over_commit=False, skip_host_check=False):
|
||||
"""Live Migration of an specified server(Instance).
|
||||
|
||||
:param server: Server object
|
||||
:param target_host: Specifies the target compute node to migrate
|
||||
:param block_migration: Specifies the migration type
|
||||
:Param disk_over_commit: Specifies whether to overcommit migrated
|
||||
instance or not
|
||||
:param skip_host_check: Specifies whether to verify the targeted host
|
||||
availability
|
||||
"""
|
||||
server_admin = self.admin_clients("nova").servers.get(server.id)
|
||||
host_pre_migrate = getattr(server_admin, "OS-EXT-SRV-ATTR:host")
|
||||
server_admin.live_migrate(target_host,
|
||||
block_migration=block_migration,
|
||||
disk_over_commit=disk_over_commit)
|
||||
bench_utils.wait_for(
|
||||
server,
|
||||
is_ready=bench_utils.resource_is("ACTIVE"),
|
||||
update_resource=bench_utils.get_from_manager(),
|
||||
timeout=CONF.benchmark.nova_server_live_migrate_timeout,
|
||||
check_interval=(
|
||||
CONF.benchmark.nova_server_live_migrate_poll_interval)
|
||||
)
|
||||
server_admin = self.admin_clients("nova").servers.get(server.id)
|
||||
if (host_pre_migrate == getattr(server_admin, "OS-EXT-SRV-ATTR:host")
|
||||
and not skip_host_check):
|
||||
raise exceptions.LiveMigrateException(
|
||||
"Migration complete but instance did not change host: %s" %
|
||||
host_pre_migrate)
|
||||
|
||||
@base.atomic_action_timer('nova.find_host_to_migrate')
|
||||
def _find_host_to_migrate(self, server):
|
||||
"""Finds a compute node for live migration.
|
||||
|
||||
:param server: Server object
|
||||
"""
|
||||
server_admin = self.admin_clients("nova").servers.get(server.id)
|
||||
host = getattr(server_admin, "OS-EXT-SRV-ATTR:host")
|
||||
az_name = getattr(server_admin, "OS-EXT-AZ:availability_zone")
|
||||
az = None
|
||||
for a in self.admin_clients("nova").availability_zones.list():
|
||||
if az_name == a.zoneName:
|
||||
az = a
|
||||
break
|
||||
try:
|
||||
new_host = random.choice(
|
||||
[key for key, value in az.hosts.iteritems()
|
||||
if key != host and
|
||||
value["nova-compute"]["available"] is True])
|
||||
return new_host
|
||||
except IndexError:
|
||||
raise exceptions.InvalidHostException(
|
||||
"No valid host found to migrate")
|
||||
|
@ -255,3 +255,11 @@ class WorkerAlreadyRegistered(RallyException):
|
||||
class SaharaClusterFailure(RallyException):
|
||||
msg_fmt = _("Sahara cluster %(name)s has failed to %(action)s. "
|
||||
"Reason: '%(reason)s'")
|
||||
|
||||
|
||||
class LiveMigrateException(RallyException):
|
||||
msg_fmt = _("Live Migration failed: %(message)s")
|
||||
|
||||
|
||||
class InvalidHostException(RallyException):
|
||||
msg_fmt = _("Live Migration failed: %(message)s")
|
||||
|
@ -325,3 +325,26 @@ class NovaServersTestCase(test.TestCase):
|
||||
|
||||
def test_resize_with_revert(self):
|
||||
self._test_resize(confirm=False)
|
||||
|
||||
def test_boot_and_live_migrate_server(self):
|
||||
fake_server = mock.MagicMock()
|
||||
|
||||
scenario = servers.NovaServers()
|
||||
scenario._generate_random_name = mock.MagicMock(return_value="name")
|
||||
scenario._boot_server = mock.MagicMock(return_value=fake_server)
|
||||
scenario._find_host_to_migrate = mock.MagicMock(
|
||||
return_value="host_name")
|
||||
scenario._live_migrate = mock.MagicMock()
|
||||
scenario._delete_server = mock.MagicMock()
|
||||
|
||||
scenario.boot_and_live_migrate_server("img", 0, fakearg="fakearg")
|
||||
|
||||
scenario._boot_server.assert_called_once_with("name", "img", 0,
|
||||
fakearg="fakearg")
|
||||
|
||||
scenario._find_host_to_migrate.assert_called_once_with(fake_server)
|
||||
|
||||
scenario._live_migrate.assert_called_once_with(fake_server,
|
||||
"host_name",
|
||||
False, False)
|
||||
scenario._delete_server.assert_called_once_with(fake_server)
|
||||
|
@ -526,3 +526,47 @@ class NovaScenarioTestCase(test.TestCase):
|
||||
nova_scenario._detach_volume(self.server, self.volume)
|
||||
self._test_atomic_action_timer(nova_scenario.atomic_actions(),
|
||||
'nova.detach_volume')
|
||||
|
||||
@mock.patch(NOVA_UTILS + '.NovaScenario.clients')
|
||||
def test__live_migrate_server(self, mock_clients):
|
||||
fake_host = mock.MagicMock()
|
||||
mock_clients("nova").servers.get(return_value=self.server)
|
||||
nova_scenario = utils.NovaScenario(admin_clients=mock_clients)
|
||||
nova_scenario._live_migrate(self.server,
|
||||
fake_host,
|
||||
block_migration=False,
|
||||
disk_over_commit=False,
|
||||
skip_host_check=True)
|
||||
|
||||
self._test_assert_called_once_with(
|
||||
self.wait_for.mock, self.server,
|
||||
CONF.benchmark.nova_server_live_migrate_poll_interval,
|
||||
CONF.benchmark.nova_server_live_migrate_timeout)
|
||||
self.res_is.mock.assert_has_calls(mock.call('ACTIVE'))
|
||||
self._test_atomic_action_timer(nova_scenario.atomic_actions(),
|
||||
'nova.live_migrate')
|
||||
|
||||
@mock.patch(NOVA_UTILS + '.NovaScenario.admin_clients')
|
||||
def test__find_host_to_migrate(self, mock_clients):
|
||||
fake_server = self.server
|
||||
fake_host = {"nova-compute": {"available": True}}
|
||||
nova_client = mock.MagicMock()
|
||||
mock_clients.return_value = nova_client
|
||||
nova_client.servers.get.return_value = fake_server
|
||||
nova_client.availability_zones.list.return_value = [
|
||||
mock.MagicMock(zoneName="a",
|
||||
hosts={"a1": fake_host, "a2": fake_host,
|
||||
"a3": fake_host}),
|
||||
mock.MagicMock(zoneName="b",
|
||||
hosts={"b1": fake_host, "b2": fake_host,
|
||||
"b3": fake_host}),
|
||||
mock.MagicMock(zoneName="c",
|
||||
hosts={"c1": fake_host,
|
||||
"c2": fake_host, "c3": fake_host})
|
||||
]
|
||||
setattr(fake_server, "OS-EXT-SRV-ATTR:host", "b2")
|
||||
setattr(fake_server, "OS-EXT-AZ:availability_zone", "b")
|
||||
nova_scenario = utils.NovaScenario(admin_clients=fakes.FakeClients())
|
||||
|
||||
self.assertIn(
|
||||
nova_scenario._find_host_to_migrate(fake_server), ["b1", "b3"])
|
||||
|
@ -260,6 +260,14 @@ class FakeMessage(FakeResource):
|
||||
self.ttl = kwargs.get('ttl', 100)
|
||||
|
||||
|
||||
class FakeAvailabilityZone(FakeResource):
|
||||
def __init__(self, manager=None):
|
||||
super(FakeAvailabilityZone, self).__init__(manager)
|
||||
self.zoneName = mock.MagicMock()
|
||||
self.zoneState = mock.MagicMock()
|
||||
self.hosts = mock.MagicMock()
|
||||
|
||||
|
||||
class FakeManager(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -752,6 +760,15 @@ class FakeMessagesManager(FakeManager):
|
||||
del self.__messages[message.id]
|
||||
|
||||
|
||||
class FakeAvailabilityZonesManager(FakeManager):
|
||||
def __init__(self):
|
||||
super(FakeAvailabilityZonesManager, self).__init__()
|
||||
self.zones = FakeAvailabilityZone()
|
||||
|
||||
def list(self):
|
||||
return [self.zones]
|
||||
|
||||
|
||||
class FakeServiceCatalog(object):
|
||||
def get_endpoints(self):
|
||||
return {'image': [{'publicURL': 'http://fake.to'}],
|
||||
@ -799,6 +816,7 @@ class FakeNovaClient(object):
|
||||
rule_manager=self.security_group_rules)
|
||||
self.quotas = FakeNovaQuotasManager()
|
||||
self.set_management_url = mock.MagicMock()
|
||||
self.availability_zones = FakeAvailabilityZonesManager()
|
||||
|
||||
|
||||
class FakeHeatClient(object):
|
||||
|
Loading…
x
Reference in New Issue
Block a user