d03a890a34
Currently there is no indication that the rebuild was refused, and worse, we may have a wrong imageref for the instance. This patch set the instance to ERROR status if rebuild failed in the scheduling stage. The user can rebuild the instance with valid image to get it out of ERROR state and reset with right instance metadata and properties. Closes-Bug: 1744325 Change-Id: Ibb7bee15a3d4ee6f0ef53ba12e8b41f65a1fe999
848 lines
36 KiB
Python
848 lines
36 KiB
Python
# Copyright 2015 Ericsson AB
|
|
# All Rights Reserved.
|
|
#
|
|
# 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
|
|
|
|
import mock
|
|
from oslo_config import cfg
|
|
|
|
from nova import context
|
|
from nova import db
|
|
from nova.db.sqlalchemy import api as db_api
|
|
from nova import test
|
|
from nova.tests import fixtures as nova_fixtures
|
|
from nova.tests.functional.api import client
|
|
from nova.tests.functional import integrated_helpers
|
|
from nova.tests.unit import fake_network
|
|
from nova.tests.unit import policy_fixture
|
|
from nova.virt import fake
|
|
|
|
import nova.scheduler.utils
|
|
import nova.servicegroup
|
|
import nova.tests.unit.image.fake
|
|
|
|
# An alternate project id
|
|
PROJECT_ID_ALT = "616c6c796f7572626173656172656f73"
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class ServerGroupTestBase(test.TestCase,
|
|
integrated_helpers.InstanceHelperMixin):
|
|
REQUIRES_LOCKING = True
|
|
api_major_version = 'v2.1'
|
|
microversion = None
|
|
|
|
_enabled_filters = (CONF.filter_scheduler.enabled_filters
|
|
+ ['ServerGroupAntiAffinityFilter',
|
|
'ServerGroupAffinityFilter'])
|
|
|
|
# Override servicegroup parameters to make the tests run faster
|
|
_service_down_time = 10
|
|
_report_interval = 1
|
|
|
|
anti_affinity = {'name': 'fake-name-1', 'policies': ['anti-affinity']}
|
|
affinity = {'name': 'fake-name-2', 'policies': ['affinity']}
|
|
|
|
def _get_weight_classes(self):
|
|
return []
|
|
|
|
def setUp(self):
|
|
super(ServerGroupTestBase, self).setUp()
|
|
self.flags(enabled_filters=self._enabled_filters,
|
|
group='filter_scheduler')
|
|
# NOTE(sbauza): Don't verify VCPUS and disks given the current nodes.
|
|
self.flags(cpu_allocation_ratio=9999.0)
|
|
self.flags(disk_allocation_ratio=9999.0)
|
|
self.flags(weight_classes=self._get_weight_classes(),
|
|
group='filter_scheduler')
|
|
self.flags(service_down_time=self._service_down_time)
|
|
self.flags(report_interval=self._report_interval)
|
|
|
|
self.useFixture(policy_fixture.RealPolicyFixture())
|
|
self.useFixture(nova_fixtures.NeutronFixture(self))
|
|
|
|
self.useFixture(nova_fixtures.PlacementFixture())
|
|
api_fixture = self.useFixture(nova_fixtures.OSAPIFixture(
|
|
api_version='v2.1'))
|
|
|
|
self.api = api_fixture.api
|
|
self.api.microversion = self.microversion
|
|
self.admin_api = api_fixture.admin_api
|
|
self.admin_api.microversion = self.microversion
|
|
|
|
# the image fake backend needed for image discovery
|
|
nova.tests.unit.image.fake.stub_out_image_service(self)
|
|
|
|
self.start_service('conductor')
|
|
self.start_service('scheduler')
|
|
|
|
self.addCleanup(nova.tests.unit.image.fake.FakeImageService_reset)
|
|
|
|
def _boot_a_server_to_group(self, group,
|
|
expected_status='ACTIVE', flavor=None):
|
|
server = self._build_minimal_create_server_request(self.api,
|
|
'some-server')
|
|
if flavor:
|
|
server['flavorRef'] = ('http://fake.server/%s'
|
|
% flavor['id'])
|
|
post = {'server': server,
|
|
'os:scheduler_hints': {'group': group['id']}}
|
|
created_server = self.api.post_server(post)
|
|
self.assertTrue(created_server['id'])
|
|
|
|
# Wait for it to finish being created
|
|
found_server = self._wait_for_state_change(
|
|
self.admin_api, created_server, expected_status)
|
|
|
|
return found_server
|
|
|
|
|
|
class ServerGroupFakeDriver(fake.SmallFakeDriver):
|
|
"""A specific fake driver for our tests.
|
|
|
|
Here, we only want to be RAM-bound.
|
|
"""
|
|
|
|
vcpus = 1000
|
|
memory_mb = 8192
|
|
local_gb = 100000
|
|
|
|
|
|
# A fake way to change the FakeDriver given we don't have a possibility yet to
|
|
# modify the resources for the FakeDriver
|
|
def _fake_load_compute_driver(virtapi, compute_driver=None):
|
|
return ServerGroupFakeDriver(virtapi)
|
|
|
|
|
|
class ServerGroupTestV21(ServerGroupTestBase):
|
|
|
|
def setUp(self):
|
|
super(ServerGroupTestV21, self).setUp()
|
|
|
|
# TODO(sbauza): Remove that once there is a way to have a custom
|
|
# FakeDriver supporting different resources. Note that we can't also
|
|
# simply change the config option for choosing our custom fake driver
|
|
# as the mocked method only accepts to load drivers in the nova.virt
|
|
# tree.
|
|
self.stub_out('nova.virt.driver.load_compute_driver',
|
|
_fake_load_compute_driver)
|
|
fake.set_nodes(['compute'])
|
|
self.compute = self.start_service('compute', host='compute')
|
|
|
|
# NOTE(gibi): start a second compute host to be able to test affinity
|
|
# NOTE(sbauza): Make sure the FakeDriver returns a different nodename
|
|
# for the second compute node.
|
|
fake.set_nodes(['host2'])
|
|
self.addCleanup(fake.restore_nodes)
|
|
self.compute2 = self.start_service('compute', host='host2')
|
|
fake_network.set_stub_network_methods(self)
|
|
|
|
def test_get_no_groups(self):
|
|
groups = self.api.get_server_groups()
|
|
self.assertEqual([], groups)
|
|
|
|
def test_create_and_delete_groups(self):
|
|
groups = [self.anti_affinity,
|
|
self.affinity]
|
|
created_groups = []
|
|
for group in groups:
|
|
created_group = self.api.post_server_groups(group)
|
|
created_groups.append(created_group)
|
|
self.assertEqual(group['name'], created_group['name'])
|
|
self.assertEqual(group['policies'], created_group['policies'])
|
|
self.assertEqual([], created_group['members'])
|
|
self.assertEqual({}, created_group['metadata'])
|
|
self.assertIn('id', created_group)
|
|
|
|
group_details = self.api.get_server_group(created_group['id'])
|
|
self.assertEqual(created_group, group_details)
|
|
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertIn(created_group, existing_groups)
|
|
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertEqual(len(groups), len(existing_groups))
|
|
|
|
for group in created_groups:
|
|
self.api.delete_server_group(group['id'])
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertNotIn(group, existing_groups)
|
|
|
|
def test_create_wrong_policy(self):
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self.api.post_server_groups,
|
|
{'name': 'fake-name-1',
|
|
'policies': ['wrong-policy']})
|
|
self.assertEqual(400, ex.response.status_code)
|
|
self.assertIn('Invalid input', ex.response.text)
|
|
self.assertIn('wrong-policy', ex.response.text)
|
|
|
|
def test_get_groups_all_projects(self):
|
|
# This test requires APIs using two projects.
|
|
|
|
# Create an API using project 'openstack1'.
|
|
# This is a non-admin API.
|
|
#
|
|
# NOTE(sdague): this is actually very much *not* how this
|
|
# fixture should be used. This actually spawns a whole
|
|
# additional API server. Should be addressed in the future.
|
|
api_openstack1 = self.useFixture(nova_fixtures.OSAPIFixture(
|
|
api_version=self.api_major_version,
|
|
project_id=PROJECT_ID_ALT)).api
|
|
api_openstack1.microversion = self.microversion
|
|
|
|
# Create a server group in project 'openstack'
|
|
# Project 'openstack' is used by self.api
|
|
group1 = self.anti_affinity
|
|
openstack_group = self.api.post_server_groups(group1)
|
|
|
|
# Create a server group in project 'openstack1'
|
|
group2 = self.affinity
|
|
openstack1_group = api_openstack1.post_server_groups(group2)
|
|
|
|
# The admin should be able to get server groups in all projects.
|
|
all_projects_admin = self.admin_api.get_server_groups(
|
|
all_projects=True)
|
|
self.assertIn(openstack_group, all_projects_admin)
|
|
self.assertIn(openstack1_group, all_projects_admin)
|
|
|
|
# The non-admin should only be able to get server groups
|
|
# in his project.
|
|
# The all_projects parameter is ignored for non-admin clients.
|
|
all_projects_non_admin = api_openstack1.get_server_groups(
|
|
all_projects=True)
|
|
self.assertNotIn(openstack_group, all_projects_non_admin)
|
|
self.assertIn(openstack1_group, all_projects_non_admin)
|
|
|
|
def test_create_duplicated_policy(self):
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self.api.post_server_groups,
|
|
{"name": "fake-name-1",
|
|
"policies": ["affinity", "affinity"]})
|
|
self.assertEqual(400, ex.response.status_code)
|
|
self.assertIn('Invalid input', ex.response.text)
|
|
|
|
def test_create_multiple_policies(self):
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self.api.post_server_groups,
|
|
{"name": "fake-name-1",
|
|
"policies": ["anti-affinity", "affinity"]})
|
|
self.assertEqual(400, ex.response.status_code)
|
|
|
|
def _boot_servers_to_group(self, group, flavor=None):
|
|
servers = []
|
|
for _ in range(0, 2):
|
|
server = self._boot_a_server_to_group(group,
|
|
flavor=flavor)
|
|
servers.append(server)
|
|
return servers
|
|
|
|
def test_boot_servers_with_affinity(self):
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
host = servers[0]['OS-EXT-SRV-ATTR:host']
|
|
for server in servers:
|
|
self.assertIn(server['id'], members)
|
|
self.assertEqual(host, server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_boot_servers_with_affinity_overquota(self):
|
|
# Tests that we check server group member quotas and cleanup created
|
|
# resources when we fail with OverQuota.
|
|
self.flags(server_group_members=1, group='quota')
|
|
# make sure we start with 0 servers
|
|
servers = self.api.get_servers(detail=False)
|
|
self.assertEqual(0, len(servers))
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self._boot_servers_to_group,
|
|
created_group)
|
|
self.assertEqual(403, ex.response.status_code)
|
|
# _boot_servers_to_group creates 2 instances in the group in order, not
|
|
# multiple servers in a single request. Since our quota is 1, the first
|
|
# server create would pass, the second should fail, and we should be
|
|
# left with 1 server and it's 1 block device mapping.
|
|
servers = self.api.get_servers(detail=False)
|
|
self.assertEqual(1, len(servers))
|
|
ctxt = context.get_admin_context()
|
|
servers = db.instance_get_all(ctxt)
|
|
self.assertEqual(1, len(servers))
|
|
ctxt_mgr = db_api.get_context_manager(ctxt)
|
|
with ctxt_mgr.reader.using(ctxt):
|
|
bdms = db_api._block_device_mapping_get_query(ctxt).all()
|
|
self.assertEqual(1, len(bdms))
|
|
self.assertEqual(servers[0]['uuid'], bdms[0]['instance_uuid'])
|
|
|
|
def test_boot_servers_with_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
# Using big enough flavor to use up the resources on the host
|
|
flavor = self.api.get_flavors()[2]
|
|
self._boot_servers_to_group(created_group, flavor=flavor)
|
|
|
|
# The third server cannot be booted as there is not enough resource
|
|
# on the host where the first two server was booted
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
flavor=flavor,
|
|
expected_status='ERROR')
|
|
self.assertEqual('No valid host was found. '
|
|
'There are not enough hosts available.',
|
|
failed_server['fault']['message'])
|
|
|
|
def test_boot_servers_with_anti_affinity(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
self.assertNotEqual(servers[0]['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
for server in servers:
|
|
self.assertIn(server['id'], members)
|
|
|
|
def test_boot_server_with_anti_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
self._boot_servers_to_group(created_group)
|
|
|
|
# We have 2 computes so the third server won't fit into the same group
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
expected_status='ERROR')
|
|
self.assertEqual('No valid host was found. '
|
|
'There are not enough hosts available.',
|
|
failed_server['fault']['message'])
|
|
|
|
def _rebuild_with_group(self, group):
|
|
created_group = self.api.post_server_groups(group)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
post = {'rebuild': {'imageRef':
|
|
'76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'}}
|
|
self.api.post_server_action(servers[1]['id'], post)
|
|
|
|
rebuilt_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ACTIVE')
|
|
|
|
self.assertEqual(post['rebuild']['imageRef'],
|
|
rebuilt_server.get('image')['id'])
|
|
return [servers[0], rebuilt_server]
|
|
|
|
def test_rebuild_with_affinity(self):
|
|
untouched_server, rebuilt_server = self._rebuild_with_group(
|
|
self.affinity)
|
|
self.assertEqual(untouched_server['OS-EXT-SRV-ATTR:host'],
|
|
rebuilt_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_rebuild_with_anti_affinity(self):
|
|
untouched_server, rebuilt_server = self._rebuild_with_group(
|
|
self.anti_affinity)
|
|
self.assertNotEqual(untouched_server['OS-EXT-SRV-ATTR:host'],
|
|
rebuilt_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def _migrate_with_group_no_valid_host(self, group):
|
|
created_group = self.api.post_server_groups(group)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
post = {'migrate': {}}
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self.admin_api.post_server_action,
|
|
servers[1]['id'], post)
|
|
self.assertEqual(400, ex.response.status_code)
|
|
self.assertIn('No valid host found for cold migrate', ex.response.text)
|
|
|
|
def test_migrate_with_group_no_valid_host(self):
|
|
for group in [self.affinity, self.anti_affinity]:
|
|
self._migrate_with_group_no_valid_host(group)
|
|
|
|
def test_migrate_with_anti_affinity(self):
|
|
# Start additional host to test migration with anti-affinity
|
|
fake.set_nodes(['host3'])
|
|
self.start_service('compute', host='host3')
|
|
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
post = {'migrate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
migrated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'VERIFY_RESIZE')
|
|
|
|
self.assertNotEqual(servers[0]['OS-EXT-SRV-ATTR:host'],
|
|
migrated_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_resize_to_same_host_with_anti_affinity(self):
|
|
self.flags(allow_resize_to_same_host=True)
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group,
|
|
flavor=self.api.get_flavors()[0])
|
|
|
|
post = {'resize': {'flavorRef': '2'}}
|
|
server1_old_host = servers[1]['OS-EXT-SRV-ATTR:host']
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
migrated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'VERIFY_RESIZE')
|
|
|
|
self.assertEqual(server1_old_host,
|
|
migrated_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def _get_compute_service_by_host_name(self, host_name):
|
|
host = None
|
|
if self.compute.host == host_name:
|
|
host = self.compute
|
|
elif self.compute2.host == host_name:
|
|
host = self.compute2
|
|
else:
|
|
raise AssertionError('host = %s does not found in '
|
|
'existing hosts %s' %
|
|
(host_name, str([self.compute.host,
|
|
self.compute2.host])))
|
|
|
|
return host
|
|
|
|
def test_evacuate_with_anti_affinity(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
# Start additional host to test evacuation
|
|
fake.set_nodes(['host3'])
|
|
self.start_service('compute', host='host3')
|
|
|
|
post = {'evacuate': {'onSharedStorage': False}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'done')
|
|
evacuated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ACTIVE')
|
|
|
|
# check that the server is evacuated to another host
|
|
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
# check that anti-affinity policy is kept during evacuation
|
|
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[0]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
host.start()
|
|
|
|
def test_evacuate_with_anti_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
post = {'evacuate': {'onSharedStorage': False}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'error')
|
|
server_after_failed_evac = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ERROR')
|
|
|
|
# assert that after a failed evac the server active on the same host
|
|
# as before
|
|
self.assertEqual(server_after_failed_evac['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
host.start()
|
|
|
|
def test_evacuate_with_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
post = {'evacuate': {'onSharedStorage': False}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'error')
|
|
server_after_failed_evac = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ERROR')
|
|
|
|
# assert that after a failed evac the server active on the same host
|
|
# as before
|
|
self.assertEqual(server_after_failed_evac['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
host.start()
|
|
|
|
def test_soft_affinity_not_supported(self):
|
|
ex = self.assertRaises(client.OpenStackApiException,
|
|
self.api.post_server_groups,
|
|
{'name': 'fake-name-1',
|
|
'policies': ['soft-affinity']})
|
|
self.assertEqual(400, ex.response.status_code)
|
|
self.assertIn('Invalid input', ex.response.text)
|
|
self.assertIn('soft-affinity', ex.response.text)
|
|
|
|
|
|
class ServerGroupAffinityConfTest(ServerGroupTestBase):
|
|
api_major_version = 'v2.1'
|
|
# Load only anti-affinity filter so affinity will be missing
|
|
_enabled_filters = ['ServerGroupAntiAffinityFilter']
|
|
|
|
@mock.patch('nova.scheduler.utils._SUPPORTS_AFFINITY', None)
|
|
def test_affinity_no_filter(self):
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
expected_status='ERROR')
|
|
self.assertEqual('ServerGroup policy is not supported: '
|
|
'ServerGroupAffinityFilter not configured',
|
|
failed_server['fault']['message'])
|
|
self.assertEqual(400, failed_server['fault']['code'])
|
|
|
|
|
|
class ServerGroupAntiAffinityConfTest(ServerGroupTestBase):
|
|
api_major_version = 'v2.1'
|
|
# Load only affinity filter so anti-affinity will be missing
|
|
_enabled_filters = ['ServerGroupAffinityFilter']
|
|
|
|
@mock.patch('nova.scheduler.utils._SUPPORTS_ANTI_AFFINITY', None)
|
|
def test_anti_affinity_no_filter(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
expected_status='ERROR')
|
|
self.assertEqual('ServerGroup policy is not supported: '
|
|
'ServerGroupAntiAffinityFilter not configured',
|
|
failed_server['fault']['message'])
|
|
self.assertEqual(400, failed_server['fault']['code'])
|
|
|
|
|
|
class ServerGroupSoftAffinityConfTest(ServerGroupTestBase):
|
|
api_major_version = 'v2.1'
|
|
microversion = '2.15'
|
|
soft_affinity = {'name': 'fake-name-4',
|
|
'policies': ['soft-affinity']}
|
|
|
|
def _get_weight_classes(self):
|
|
# Load only soft-anti-affinity weigher so affinity will be missing
|
|
return ['nova.scheduler.weights.affinity.'
|
|
'ServerGroupSoftAntiAffinityWeigher']
|
|
|
|
@mock.patch('nova.scheduler.utils._SUPPORTS_SOFT_AFFINITY', None)
|
|
def test_soft_affinity_no_filter(self):
|
|
created_group = self.api.post_server_groups(self.soft_affinity)
|
|
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
expected_status='ERROR')
|
|
self.assertEqual('ServerGroup policy is not supported: '
|
|
'ServerGroupSoftAffinityWeigher not configured',
|
|
failed_server['fault']['message'])
|
|
self.assertEqual(400, failed_server['fault']['code'])
|
|
|
|
|
|
class ServerGroupSoftAntiAffinityConfTest(ServerGroupTestBase):
|
|
api_major_version = 'v2.1'
|
|
microversion = '2.15'
|
|
soft_anti_affinity = {'name': 'fake-name-3',
|
|
'policies': ['soft-anti-affinity']}
|
|
|
|
def _get_weight_classes(self):
|
|
# Load only soft affinity filter so anti-affinity will be missing
|
|
return ['nova.scheduler.weights.affinity.'
|
|
'ServerGroupSoftAffinityWeigher']
|
|
|
|
@mock.patch('nova.scheduler.utils._SUPPORTS_SOFT_ANTI_AFFINITY', None)
|
|
def test_soft_anti_affinity_no_filter(self):
|
|
created_group = self.api.post_server_groups(self.soft_anti_affinity)
|
|
|
|
failed_server = self._boot_a_server_to_group(created_group,
|
|
expected_status='ERROR')
|
|
self.assertEqual('ServerGroup policy is not supported: '
|
|
'ServerGroupSoftAntiAffinityWeigher not configured',
|
|
failed_server['fault']['message'])
|
|
self.assertEqual(400, failed_server['fault']['code'])
|
|
|
|
|
|
class ServerGroupTestV215(ServerGroupTestV21):
|
|
api_major_version = 'v2.1'
|
|
microversion = '2.15'
|
|
|
|
soft_anti_affinity = {'name': 'fake-name-3',
|
|
'policies': ['soft-anti-affinity']}
|
|
soft_affinity = {'name': 'fake-name-4',
|
|
'policies': ['soft-affinity']}
|
|
|
|
def setUp(self):
|
|
super(ServerGroupTestV215, self).setUp()
|
|
|
|
soft_affinity_patcher = mock.patch(
|
|
'nova.scheduler.utils._SUPPORTS_SOFT_AFFINITY')
|
|
soft_anti_affinity_patcher = mock.patch(
|
|
'nova.scheduler.utils._SUPPORTS_SOFT_ANTI_AFFINITY')
|
|
self.addCleanup(soft_affinity_patcher.stop)
|
|
self.addCleanup(soft_anti_affinity_patcher.stop)
|
|
self.mock_soft_affinity = soft_affinity_patcher.start()
|
|
self.mock_soft_anti_affinity = soft_anti_affinity_patcher.start()
|
|
self.mock_soft_affinity.return_value = None
|
|
self.mock_soft_anti_affinity.return_value = None
|
|
|
|
def _get_weight_classes(self):
|
|
return ['nova.scheduler.weights.affinity.'
|
|
'ServerGroupSoftAffinityWeigher',
|
|
'nova.scheduler.weights.affinity.'
|
|
'ServerGroupSoftAntiAffinityWeigher']
|
|
|
|
def test_evacuate_with_anti_affinity(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
# Start additional host to test evacuation
|
|
fake.set_nodes(['host3'])
|
|
compute3 = self.start_service('compute', host='host3')
|
|
|
|
post = {'evacuate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'done')
|
|
evacuated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ACTIVE')
|
|
|
|
# check that the server is evacuated
|
|
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
# check that policy is kept
|
|
self.assertNotEqual(evacuated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[0]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
compute3.kill()
|
|
host.start()
|
|
|
|
def test_evacuate_with_anti_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
post = {'evacuate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'error')
|
|
server_after_failed_evac = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ERROR')
|
|
|
|
# assert that after a failed evac the server active on the same host
|
|
# as before
|
|
self.assertEqual(server_after_failed_evac['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
host.start()
|
|
|
|
def test_evacuate_with_affinity_no_valid_host(self):
|
|
created_group = self.api.post_server_groups(self.affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
post = {'evacuate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'error')
|
|
server_after_failed_evac = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ERROR')
|
|
|
|
# assert that after a failed evac the server active on the same host
|
|
# as before
|
|
self.assertEqual(server_after_failed_evac['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
host.start()
|
|
|
|
def test_create_and_delete_groups(self):
|
|
groups = [self.anti_affinity,
|
|
self.affinity,
|
|
self.soft_affinity,
|
|
self.soft_anti_affinity]
|
|
|
|
created_groups = []
|
|
|
|
for group in groups:
|
|
created_group = self.api.post_server_groups(group)
|
|
created_groups.append(created_group)
|
|
self.assertEqual(group['name'], created_group['name'])
|
|
self.assertEqual(group['policies'], created_group['policies'])
|
|
self.assertEqual([], created_group['members'])
|
|
self.assertEqual({}, created_group['metadata'])
|
|
self.assertIn('id', created_group)
|
|
|
|
group_details = self.api.get_server_group(created_group['id'])
|
|
self.assertEqual(created_group, group_details)
|
|
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertIn(created_group, existing_groups)
|
|
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertEqual(len(groups), len(existing_groups))
|
|
|
|
for group in created_groups:
|
|
self.api.delete_server_group(group['id'])
|
|
existing_groups = self.api.get_server_groups()
|
|
self.assertNotIn(group, existing_groups)
|
|
|
|
def test_boot_servers_with_soft_affinity(self):
|
|
created_group = self.api.post_server_groups(self.soft_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
|
|
self.assertEqual(2, len(servers))
|
|
self.assertIn(servers[0]['id'], members)
|
|
self.assertIn(servers[1]['id'], members)
|
|
self.assertEqual(servers[0]['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_boot_servers_with_soft_affinity_no_resource_on_first_host(self):
|
|
created_group = self.api.post_server_groups(self.soft_affinity)
|
|
|
|
# Using big enough flavor to use up the resources on the first host
|
|
flavor = self.api.get_flavors()[2]
|
|
servers = self._boot_servers_to_group(created_group, flavor)
|
|
|
|
# The third server cannot be booted on the first host as there
|
|
# is not enough resource there, but as opposed to the affinity policy
|
|
# it will be booted on the other host, which has enough resources.
|
|
third_server = self._boot_a_server_to_group(created_group,
|
|
flavor=flavor)
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
hosts = []
|
|
for server in servers:
|
|
hosts.append(server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
self.assertIn(third_server['id'], members)
|
|
self.assertNotIn(third_server['OS-EXT-SRV-ATTR:host'], hosts)
|
|
|
|
def test_boot_servers_with_soft_anti_affinity(self):
|
|
created_group = self.api.post_server_groups(self.soft_anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
|
|
self.assertEqual(2, len(servers))
|
|
self.assertIn(servers[0]['id'], members)
|
|
self.assertIn(servers[1]['id'], members)
|
|
self.assertNotEqual(servers[0]['OS-EXT-SRV-ATTR:host'],
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_boot_servers_with_soft_anti_affinity_one_available_host(self):
|
|
self.compute2.kill()
|
|
created_group = self.api.post_server_groups(self.soft_anti_affinity)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
members = self.api.get_server_group(created_group['id'])['members']
|
|
host = servers[0]['OS-EXT-SRV-ATTR:host']
|
|
for server in servers:
|
|
self.assertIn(server['id'], members)
|
|
self.assertEqual(host, server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_rebuild_with_soft_affinity(self):
|
|
untouched_server, rebuilt_server = self._rebuild_with_group(
|
|
self.soft_affinity)
|
|
self.assertEqual(untouched_server['OS-EXT-SRV-ATTR:host'],
|
|
rebuilt_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def test_rebuild_with_soft_anti_affinity(self):
|
|
untouched_server, rebuilt_server = self._rebuild_with_group(
|
|
self.soft_anti_affinity)
|
|
self.assertNotEqual(untouched_server['OS-EXT-SRV-ATTR:host'],
|
|
rebuilt_server['OS-EXT-SRV-ATTR:host'])
|
|
|
|
def _migrate_with_soft_affinity_policies(self, group):
|
|
created_group = self.api.post_server_groups(group)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
post = {'migrate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
migrated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'VERIFY_RESIZE')
|
|
|
|
return [migrated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[0]['OS-EXT-SRV-ATTR:host']]
|
|
|
|
def test_migrate_with_soft_affinity(self):
|
|
migrated_server, other_server = (
|
|
self._migrate_with_soft_affinity_policies(self.soft_affinity))
|
|
self.assertNotEqual(migrated_server, other_server)
|
|
|
|
def test_migrate_with_soft_anti_affinity(self):
|
|
migrated_server, other_server = (
|
|
self._migrate_with_soft_affinity_policies(self.soft_anti_affinity))
|
|
self.assertEqual(migrated_server, other_server)
|
|
|
|
def _evacuate_with_soft_anti_affinity_policies(self, group):
|
|
created_group = self.api.post_server_groups(group)
|
|
servers = self._boot_servers_to_group(created_group)
|
|
|
|
host = self._get_compute_service_by_host_name(
|
|
servers[1]['OS-EXT-SRV-ATTR:host'])
|
|
host.stop()
|
|
# Need to wait service_down_time amount of seconds to ensure
|
|
# nova considers the host down
|
|
time.sleep(self._service_down_time)
|
|
|
|
post = {'evacuate': {}}
|
|
self.admin_api.post_server_action(servers[1]['id'], post)
|
|
self._wait_for_migration_status(servers[1], 'done')
|
|
evacuated_server = self._wait_for_state_change(
|
|
self.admin_api, servers[1], 'ACTIVE')
|
|
|
|
# Note(gibi): need to get the server again as the state of the instance
|
|
# goes to ACTIVE first then the host of the instance changes to the
|
|
# new host later
|
|
evacuated_server = self.admin_api.get_server(evacuated_server['id'])
|
|
|
|
host.start()
|
|
|
|
return [evacuated_server['OS-EXT-SRV-ATTR:host'],
|
|
servers[0]['OS-EXT-SRV-ATTR:host']]
|
|
|
|
def test_evacuate_with_soft_affinity(self):
|
|
evacuated_server, other_server = (
|
|
self._evacuate_with_soft_anti_affinity_policies(
|
|
self.soft_affinity))
|
|
self.assertNotEqual(evacuated_server, other_server)
|
|
|
|
def test_evacuate_with_soft_anti_affinity(self):
|
|
evacuated_server, other_server = (
|
|
self._evacuate_with_soft_anti_affinity_policies(
|
|
self.soft_anti_affinity))
|
|
self.assertEqual(evacuated_server, other_server)
|
|
|
|
def test_soft_affinity_not_supported(self):
|
|
pass
|