86601b800c
W0403 relative-import is no longer suppressed by pylint.rc Note: six.moves.urllib.request was considered a relative import from six.moves import urllib is considered valid. The code is updated to satisfy pylint and pep8. nfv_scenario_tests main.py still needs to use a relative import based on how it is invoked. Story: 2004515 Task: 28575 Change-Id: I3f576f3990352e9456fe3ee5d3a80cb1307f31e0 Signed-off-by: Al Bailey <Al.Bailey@windriver.com>
399 lines
19 KiB
Python
Executable File
399 lines
19 KiB
Python
Executable File
#
|
|
# Copyright (c) 2015-2016 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
import mock
|
|
import uuid
|
|
|
|
from nfv_vim import nfvi
|
|
from nfv_vim import objects
|
|
|
|
from nfv_vim.directors._instance_director import InstanceDirector
|
|
from nfv_vim.tables._image_table import ImageTable
|
|
from nfv_vim.tables._table import Table
|
|
|
|
from . import testcase # noqa: H304
|
|
from . import utils # noqa: H304
|
|
|
|
# Constants
|
|
_audit_interval = 330
|
|
_audit_cooldown = 30
|
|
_recovery_cooldown = 30
|
|
_rebuild_timeout = 300
|
|
_reboot_timeout = 300
|
|
_migrate_timeout = 600
|
|
|
|
|
|
def fake_timer(a, b, c, d):
|
|
return 1234
|
|
|
|
|
|
class TestInstanceDirector(testcase.NFVTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestInstanceDirector, self).setUp()
|
|
self._image_table = ImageTable()
|
|
self._instance_table = Table()
|
|
self._instance_type_table = Table()
|
|
self._tenant_table = Table()
|
|
|
|
# Don't attempt to write to the database while unit testing
|
|
self._image_table.persist = False
|
|
self._instance_table.persist = False
|
|
self._instance_type_table.persist = False
|
|
self._tenant_table.persist = False
|
|
self._director = None
|
|
|
|
self.instance_setup_func()
|
|
|
|
def tearDown(self):
|
|
super(TestInstanceDirector, self).tearDown()
|
|
self._image_table.clear()
|
|
self._instance_table.clear()
|
|
self._instance_type_table.clear()
|
|
self._tenant_table.clear()
|
|
self._director = None
|
|
|
|
def create_instance(self, instance_type_name, instance_name, recovery_priority=None):
|
|
"""
|
|
Create an instance
|
|
"""
|
|
tenant_uuid = uuid.uuid4()
|
|
image_uuid = uuid.uuid4()
|
|
|
|
tenant = objects.Tenant(tenant_uuid, "%s_name" % tenant_uuid, '', True)
|
|
self._tenant_table[tenant_uuid] = tenant
|
|
|
|
for instance_type in self._instance_type_table.values():
|
|
if instance_type.name == instance_type_name:
|
|
instance_uuid = uuid.uuid4()
|
|
|
|
nfvi_instance = nfvi.objects.v1.Instance(
|
|
instance_uuid, instance_name, tenant_uuid,
|
|
admin_state=nfvi.objects.v1.INSTANCE_ADMIN_STATE.UNLOCKED,
|
|
oper_state=nfvi.objects.v1.INSTANCE_OPER_STATE.ENABLED,
|
|
avail_status=list(),
|
|
action=nfvi.objects.v1.INSTANCE_ACTION.NONE,
|
|
host_name='compute-0',
|
|
instance_type=utils.instance_type_to_flavor_dict(
|
|
instance_type),
|
|
image_uuid=image_uuid,
|
|
recovery_priority=recovery_priority)
|
|
|
|
return objects.Instance(nfvi_instance)
|
|
|
|
return None
|
|
|
|
@mock.patch('nfv_common.timers.timers_create_timer', fake_timer)
|
|
def instance_setup_func(self):
|
|
"""
|
|
Setup for testing.
|
|
"""
|
|
instance_type_uuid = uuid.uuid4()
|
|
|
|
instance_type = objects.InstanceType(instance_type_uuid, 'small')
|
|
instance_type.update_details(vcpus=1,
|
|
mem_mb=64,
|
|
disk_gb=1,
|
|
ephemeral_gb=0,
|
|
swap_gb=0,
|
|
guest_services=None,
|
|
auto_recovery=True,
|
|
live_migration_timeout=800,
|
|
live_migration_max_downtime=500,
|
|
storage_type='local_image')
|
|
self._instance_type_table[instance_type_uuid] = instance_type
|
|
|
|
self._director = InstanceDirector(
|
|
max_concurrent_recovering_instances=4,
|
|
max_concurrent_migrates_per_host=1,
|
|
max_concurrent_evacuates_per_host=1,
|
|
recovery_audit_interval=_audit_interval,
|
|
recovery_audit_cooldown=_audit_cooldown,
|
|
recovery_audit_batch_interval=2,
|
|
recovery_cooldown=_recovery_cooldown,
|
|
rebuild_timeout=_rebuild_timeout,
|
|
reboot_timeout=_reboot_timeout,
|
|
migrate_timeout=_migrate_timeout,
|
|
single_hypervisor=False,
|
|
recovery_threshold=250,
|
|
max_throttled_recovering_instances=2)
|
|
|
|
@mock.patch('nfv_vim.tables.tables_get_tenant_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_type_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_table')
|
|
def test_instance_director_recovery_list(self,
|
|
tables_get_instance_table_mock,
|
|
tables_get_instance_type_table_mock,
|
|
tables_get_tenant_table_mock):
|
|
"""
|
|
Test the instance director recovery list logic
|
|
"""
|
|
tables_get_tenant_table_mock.return_value = self._tenant_table
|
|
tables_get_instance_table_mock.return_value = self._instance_table
|
|
tables_get_instance_type_table_mock.return_value = self._instance_type_table
|
|
|
|
instance_1 = self.create_instance('small', 'instance_1')
|
|
self._instance_table[instance_1.uuid] = instance_1
|
|
|
|
# Validate that the Instance Director recovery_list creation
|
|
# -- with no instances in the failed state, verify that the list is
|
|
# empty and the normal audit interval is returned
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _audit_interval
|
|
assert 0 == len(instance_recovery_list)
|
|
|
|
instance_1._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
|
|
# -- with one instance in the failed state, but elapsed time is less
|
|
# than the recovery cooldown, verify that the list is empty, but
|
|
# the audit interval is set to the recovery cooldown period
|
|
instance_1._elapsed_time_in_state = _recovery_cooldown - 1
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 0 == len(instance_recovery_list)
|
|
|
|
# -- with one instance in the failed state, but elapsed time is greater
|
|
# than or equal to the recovery cooldown, verify that the list contains
|
|
# one instance and the audit interval is set to the recovery cooldown
|
|
# period
|
|
instance_1._elapsed_time_in_state = _recovery_cooldown
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 1 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_1.name
|
|
|
|
@mock.patch('nfv_vim.tables.tables_get_tenant_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_type_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_table')
|
|
def test_instance_director_recovery_list_order(self,
|
|
tables_get_instance_table_mock,
|
|
tables_get_instance_type_table_mock,
|
|
tables_get_tenant_table_mock):
|
|
"""
|
|
Test the instance director recovery list ordering
|
|
"""
|
|
tables_get_tenant_table_mock.return_value = self._tenant_table
|
|
tables_get_instance_table_mock.return_value = self._instance_table
|
|
tables_get_instance_type_table_mock.return_value = self._instance_type_table
|
|
|
|
instance_1 = self.create_instance('small', 'instance_1')
|
|
instance_1._elapsed_time_in_state = _recovery_cooldown
|
|
instance_1._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
instance_1._nfvi_instance['instance_type']['vcpus'] = 1
|
|
instance_1._nfvi_instance['instance_type']['ram'] = 32
|
|
instance_1._nfvi_instance['instance_type']['disk'] = 2
|
|
instance_1._nfvi_instance['instance_type']['swap'] = 0
|
|
self._instance_table[instance_1.uuid] = instance_1
|
|
|
|
# Validate the Instance Director recovery_list order
|
|
# -- with one instance in the failed state
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 1 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_1.name
|
|
|
|
instance_2 = self.create_instance('small', 'instance_2')
|
|
instance_2._elapsed_time_in_state = _recovery_cooldown
|
|
instance_2._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
instance_2._nfvi_instance['instance_type']['vcpus'] = 2
|
|
instance_2._nfvi_instance['instance_type']['ram'] = 32
|
|
instance_2._nfvi_instance['instance_type']['disk'] = 1
|
|
instance_2._nfvi_instance['instance_type']['swap'] = 0
|
|
self._instance_table[instance_2.uuid] = instance_2
|
|
|
|
# -- with two instances in the failed state
|
|
# vcpus takes precedence over disk_gb
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 2 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_2.name
|
|
assert instance_recovery_list[1].name == instance_1.name
|
|
|
|
instance_3 = self.create_instance('small', 'instance_3', recovery_priority=5)
|
|
instance_3._elapsed_time_in_state = _recovery_cooldown
|
|
instance_3._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
instance_3._nfvi_instance['instance_type']['vcpus'] = 1
|
|
instance_3._nfvi_instance['instance_type']['ram'] = 32
|
|
instance_3._nfvi_instance['instance_type']['disk'] = 0
|
|
instance_3._nfvi_instance['instance_type']['swap'] = 0
|
|
self._instance_table[instance_3.uuid] = instance_3
|
|
|
|
# -- with three instances in the failed state
|
|
# recovery_priority takes precedence over instance size
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 3 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_3.name
|
|
assert instance_recovery_list[1].name == instance_2.name
|
|
assert instance_recovery_list[2].name == instance_1.name
|
|
|
|
instance_4 = self.create_instance('small', 'instance_4', recovery_priority=1)
|
|
instance_4._elapsed_time_in_state = _recovery_cooldown
|
|
instance_4._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
instance_4._nfvi_instance['instance_type']['vcpus'] = 1
|
|
instance_4._nfvi_instance['instance_type']['ram'] = 32
|
|
instance_4._nfvi_instance['instance_type']['disk'] = 0
|
|
instance_4._nfvi_instance['instance_type']['swap'] = 0
|
|
self._instance_table[instance_4.uuid] = instance_4
|
|
|
|
# -- with four instances in the failed state
|
|
# recovery_priority sorts instances
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 4 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_4.name
|
|
assert instance_recovery_list[1].name == instance_3.name
|
|
assert instance_recovery_list[2].name == instance_2.name
|
|
assert instance_recovery_list[3].name == instance_1.name
|
|
|
|
instance_5 = self.create_instance('small', 'instance_5', recovery_priority=10)
|
|
instance_5._elapsed_time_in_state = _recovery_cooldown
|
|
instance_5._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
instance_5._nfvi_instance['instance_type']['vcpus'] = 2
|
|
instance_5._nfvi_instance['instance_type']['ram'] = 32
|
|
instance_5._nfvi_instance['instance_type']['disk'] = 0
|
|
instance_5._nfvi_instance['instance_type']['swap'] = 0
|
|
self._instance_table[instance_5.uuid] = instance_5
|
|
|
|
# -- with five instances in the failed state
|
|
# no recovery_priority treated the same as priority 10
|
|
(next_audit_interval, instance_recovery_list, instance_failed_list,
|
|
instance_rebuilding_list, instance_migrating_list,
|
|
instance_rebooting_list) = self._director._get_instance_recovery_list()
|
|
assert next_audit_interval == _recovery_cooldown
|
|
assert 5 == len(instance_recovery_list)
|
|
assert instance_recovery_list[0].name == instance_4.name
|
|
assert instance_recovery_list[1].name == instance_3.name
|
|
assert instance_recovery_list[2].name == instance_2.name
|
|
assert instance_recovery_list[3].name == instance_5.name
|
|
assert instance_recovery_list[4].name == instance_1.name
|
|
|
|
@mock.patch('nfv_vim.tables.tables_get_image_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_tenant_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_type_table')
|
|
@mock.patch('nfv_vim.tables.tables_get_instance_table')
|
|
@mock.patch('nfv_vim.dor.system_is_stabilized')
|
|
@mock.patch('nfv_vim.dor.dor_is_complete')
|
|
def test_instance_director_recover_instance(self,
|
|
dor_is_complete_mock,
|
|
system_is_stabilized_mock,
|
|
tables_get_instance_table_mock,
|
|
tables_get_instance_type_table_mock,
|
|
tables_get_tenant_table_mock,
|
|
tables_get_image_table_mock):
|
|
"""
|
|
Test the instance director recover instance logic
|
|
"""
|
|
tables_get_tenant_table_mock.return_value = self._tenant_table
|
|
tables_get_instance_table_mock.return_value = self._instance_table
|
|
tables_get_instance_type_table_mock.return_value = self._instance_type_table
|
|
tables_get_image_table_mock.return_value = self._image_table
|
|
|
|
system_is_stabilized_mock.return_value = True
|
|
dor_is_complete_mock.return_value = True
|
|
|
|
instance_1 = self.create_instance('small', 'instance_1')
|
|
instance_1.fail = mock.Mock()
|
|
instance_1.do_action = mock.Mock()
|
|
instance_1._nfvi_instance.avail_status.append(
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED)
|
|
|
|
self._director._is_host_enabled = mock.Mock(return_value=True)
|
|
self._director._is_hypervisor_enabled = mock.Mock(return_value=True)
|
|
self._director.upgrade_inprogress = mock.Mock(return_value=False)
|
|
|
|
# Set_A
|
|
# -- the first attempt to recover an instance that is failed on an
|
|
# enabled host, verify a reboot is attempted
|
|
self._director.instance_recovered(instance_1)
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBOOT,
|
|
initiated_by='director')
|
|
|
|
# -- a subsequent attempt to recover an instance that is failed on an
|
|
# enabled host and the instance has an image to rebuild from, verify
|
|
# a rebuild is attempted
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBUILD,
|
|
initiated_by='director')
|
|
|
|
# Set_B
|
|
# -- the first attempt to recover an instance that is failed on an
|
|
# enabled host, verify a reboot is attempted
|
|
original_image_uuid = instance_1._nfvi_instance.image_uuid
|
|
instance_1._nfvi_instance.image_uuid = None
|
|
self._director.instance_recovered(instance_1)
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBOOT,
|
|
initiated_by='director')
|
|
|
|
# -- a subsequent attempt to recover an instance that is failed on an
|
|
# enabled host and the instance does not have an image to rebuild from,
|
|
# verify a reboot is attempted
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBOOT,
|
|
initiated_by='director')
|
|
instance_1._nfvi_instance.image_uuid = original_image_uuid
|
|
|
|
# Set_C
|
|
# -- instance is rebuilding or evacuating and the instance has an image
|
|
# to rebuild from, verify that a rebuild is attempted
|
|
self._director.instance_recovered(instance_1)
|
|
instance_1._nfvi_instance.action = nfvi.objects.v1.INSTANCE_ACTION.REBUILDING
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBUILD,
|
|
initiated_by='director')
|
|
|
|
# Set_D
|
|
# -- instance is rebuilding or evacuating and the instance does not have an
|
|
# image to rebuild from, verify that a reboot is attempted
|
|
original_image_uuid = instance_1._nfvi_instance.image_uuid
|
|
instance_1._nfvi_instance.image_uuid = None
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBOOT,
|
|
initiated_by='director')
|
|
instance_1._nfvi_instance.image_uuid = original_image_uuid
|
|
instance_1._nfvi_instance.action = nfvi.objects.v1.INSTANCE_ACTION.NONE
|
|
|
|
# Set_E
|
|
# -- instance is migrating and the instance has an image to rebuild from,
|
|
# verify that a rebuild is attempted
|
|
self._director.instance_recovered(instance_1)
|
|
instance_1._nfvi_instance.action = nfvi.objects.v1.INSTANCE_ACTION.MIGRATING
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBUILD,
|
|
initiated_by='director')
|
|
|
|
# Set_F
|
|
# -- instance is migrating and the instance does not have an image to
|
|
# rebuild from, verify that a reboot is attempted
|
|
original_image_uuid = instance_1._nfvi_instance.image_uuid
|
|
instance_1._nfvi_instance.image_uuid = None
|
|
self._director.recover_instance(instance_1)
|
|
instance_1.do_action.assert_called_with(objects.INSTANCE_ACTION_TYPE.REBOOT,
|
|
initiated_by='director')
|
|
instance_1._nfvi_instance.image_uuid = original_image_uuid
|
|
instance_1._nfvi_instance.action = nfvi.objects.v1.INSTANCE_ACTION.NONE
|