Merge "Add support for AIO-SX to DX migration on subcloud"

This commit is contained in:
Zuul 2021-03-29 21:58:44 +00:00 committed by Gerrit Code Review
commit 245440d262
8 changed files with 285 additions and 11 deletions

View File

@ -108,10 +108,6 @@ def do_modify(cc, args):
if isystem.system_type != constants.TS_AIO:
raise exc.CommandError("system_mode can only be modified on an "
"AIO system")
if isystem.system_mode == constants.SYSTEM_MODE_SIMPLEX:
raise exc.CommandError("system_mode can not be modified if it is "
"currently set to '%s'" %
constants.SYSTEM_MODE_SIMPLEX)
mode = args.system_mode
if isystem.system_mode == mode:
raise exc.CommandError("system_mode value already set to '%s'" %

View File

@ -3446,6 +3446,29 @@ class HostController(rest.RestController):
# Check for new hardware since upgrade-start
self._semantic_check_upgrade_refresh(upgrade, ihost, force_unlock)
@staticmethod
def _semantic_check_duplex_oam_config(ihost):
system = pecan.request.dbapi.isystem_get_one()
if system.capabilities.get('simplex_to_duplex_migration'):
network = pecan.request.dbapi.network_get_by_type(constants.NETWORK_TYPE_OAM)
address_names = {'oam_c0_ip': '%s-%s' % (constants.CONTROLLER_0_HOSTNAME,
constants.NETWORK_TYPE_OAM),
'oam_c1_ip': '%s-%s' % (constants.CONTROLLER_1_HOSTNAME,
constants.NETWORK_TYPE_OAM)}
addresses = {a['name']: a for a in
pecan.request.dbapi.addresses_get_by_pool_uuid(network.pool_uuid)}
# check if controller-0-oam and controller-1-oam entries exist
for key, name in address_names.items():
if addresses.get(name) is None:
msg = _("Can not unlock controller on a duplex without "
"configuring %s." % key)
raise wsme.exc.ClientSideError(msg)
if addresses[name].address is None:
msg = _("Can not unlock controller on a duplex without "
"configuring a unit IP for %s." % key)
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _semantic_check_oam_interface(ihost):
"""
@ -5532,6 +5555,8 @@ class HostController(rest.RestController):
# If HTTPS is enabled then we may be in TPM configuration mode
if utils.get_https_enabled():
self._semantic_check_tpm_config(hostupdate.ihost_orig)
if utils.get_system_mode() == constants.SYSTEM_MODE_DUPLEX:
self._semantic_check_duplex_oam_config(hostupdate.ihost_orig)
def check_unlock_worker(self, hostupdate, force_unlock=False):
"""Check semantics on host-unlock of a worker."""

View File

@ -466,6 +466,12 @@ class OAMNetworkController(rest.RestController):
rpc_extoam.save() # pylint: disable=no-value-for-parameter
# Update OAM networking configuration with the new unit IPs of each
# controller when transitioning to a duplex system
system = pecan.request.dbapi.isystem_get_one()
if system.capabilities.get('simplex_to_duplex_migration'):
rpc_extoam.migrate_to_duplex()
pecan.request.rpcapi.update_oam_config(pecan.request.context)
return OAMNetwork.convert_with_links(rpc_extoam)

View File

@ -275,6 +275,26 @@ class SystemController(rest.RestController):
raise wsme.exc.ClientSideError(
_("Host {} must be locked.".format(h['hostname'])))
def _check_mgmt(self, system_mode):
iinterfaces = pecan.request.dbapi.iinterface_get_all()
mgmt_if = None
for iif in iinterfaces:
if (iif.networktypelist and
constants.NETWORK_TYPE_MGMT in iif.networktypelist):
mgmt_if = iif
break
if mgmt_if is None:
msg = _("Cannot modify system mode to %s "
"without configuring the management "
"interface." % system_mode)
raise wsme.exc.ClientSideError(msg)
if mgmt_if.ifname == constants.LOOPBACK_IFNAME:
msg = _("Cannot modify system mode to %s "
"when the management interface is "
"configured on loopback. "
% system_mode)
raise wsme.exc.ClientSideError(msg)
def _get_isystem_collection(self, marker, limit, sort_key, sort_dir,
expand=False, resource_url=None):
limit = api_utils.validate_limit(limit)
@ -360,6 +380,7 @@ class SystemController(rest.RestController):
change_sdn = False
change_dc_role = False
vswitch_type = None
new_system_mode = None
# prevent description field from being updated
for p in jsonpatch.JsonPatch(patch):
@ -390,14 +411,12 @@ class SystemController(rest.RestController):
# be bound to the conditions below.
if cutils.is_initial_config_complete():
if rpc_isystem.system_mode == \
constants.SYSTEM_MODE_SIMPLEX:
constants.SYSTEM_MODE_DUPLEX:
msg = _("Cannot modify system mode when it is "
"already set to %s." % rpc_isystem.system_mode)
raise wsme.exc.ClientSideError(msg)
elif new_system_mode == constants.SYSTEM_MODE_SIMPLEX:
msg = _("Cannot modify system mode to simplex when "
"it is set to %s " % rpc_isystem.system_mode)
"set to %s." % rpc_isystem.system_mode)
raise wsme.exc.ClientSideError(msg)
elif new_system_mode != constants.SYSTEM_MODE_SIMPLEX:
self._check_mgmt(new_system_mode)
else:
system_mode_options.append(constants.SYSTEM_MODE_SIMPLEX)
@ -447,6 +466,14 @@ class SystemController(rest.RestController):
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
if 'system_mode' in updates:
# Update capabilities if system mode is changed from simplex to
# duplex after the initial config is complete
if (cutils.is_initial_config_complete() and
rpc_isystem.system_mode == constants.SYSTEM_MODE_SIMPLEX and
new_system_mode == constants.SYSTEM_MODE_DUPLEX):
patched_system['capabilities']['simplex_to_duplex_migration'] = True
if 'sdn_enabled' in updates:
if sdn_enabled != rpc_isystem['capabilities']['sdn_enabled']:
self._check_hosts()

View File

@ -285,6 +285,8 @@ class ConductorManager(service.PeriodicService):
self._handle_restore_in_progress()
self._reset_simplex_to_duplex_flag(system)
LOG.info("sysinv-conductor start committed system=%s" %
system.as_dict())
@ -390,6 +392,21 @@ class ConductorManager(service.PeriodicService):
self._create_default_service_parameter()
return system
def _reset_simplex_to_duplex_flag(self, system):
# Skip if the flag is not set or if the system mode is not set to duplex
if (not system.capabilities.get('simplex_to_duplex_migration') or
system.system_mode != constants.SYSTEM_MODE_DUPLEX):
return
host = self.dbapi.ihost_get(self.host_uuid)
if host.administrative != constants.ADMIN_UNLOCKED:
return
system_dict = system.as_dict()
del system_dict['capabilities']['simplex_to_duplex_migration']
self.dbapi.isystem_update(system.uuid, system_dict)
def _upgrade_init_actions(self):
""" Perform any upgrade related startup actions"""
try:
@ -6357,6 +6374,18 @@ class ConductorManager(service.PeriodicService):
def update_system_mode_config(self, context):
"""Update the system mode configuration"""
personalities = [constants.CONTROLLER]
# Update manifest files if system mode is updated for simplex to
# duplex migration
system = self.dbapi.isystem_get_one()
if system.capabilities.get('simplex_to_duplex_migration'):
config_uuid = self._config_update_hosts(context, personalities)
# NOTE: no specific classes need to be specified since the default
# platform::config will be applied to configure the system mode
config_dict = {"personalities": personalities}
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
self._config_update_hosts(context, personalities, reboot=True)
def configure_system_timezone(self, context):

View File

@ -15,7 +15,6 @@ from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
ADDRESS_FORMAT_ARGS = (constants.CONTROLLER_HOSTNAME,
constants.NETWORK_TYPE_OAM)
@ -124,6 +123,71 @@ class OAMNetwork(base.SysinvObject):
self.obj_reset_changes()
@base.remotable
def migrate_to_duplex(self, context):
"""Add controller unit IPs for OAM configuration when transitioning to
a duplex system.
:param context: Security context
"""
network = self.dbapi._network_get(self.uuid) # pylint: disable=no-member
address_pool = network.address_pool
addresses = OAMNetwork._get_pool_addresses(address_pool)
subnet = netaddr.IPNetwork(self['oam_subnet'])
# Add address entry
values = {
'address_pool_id': address_pool.id,
'family': subnet.version,
'prefix': subnet.prefixlen,
'enable_dad': False
}
address_pool_values = {}
if self['oam_c0_ip']:
if self.address_names['oam_c0_ip'] in addresses:
self.dbapi.address_update(addresses.get(self.address_names['oam_c0_ip']).uuid,
{'address': self['oam_c0_ip']})
else:
# Only update the floating address entry if the controller-0
# unit IP is being added for the first time
for name, address in addresses.items():
if (address.interface_id and
name == self.address_names['oam_floating_ip']):
# Clear the interface id for the floating oam address
self.dbapi.address_update(address.uuid, {'interface_id': None})
# Address values specific to controller-0
c0_values = {
'name': self.address_names['oam_c0_ip'],
'address': self['oam_c0_ip'],
'interface_id': address.interface_id
}
c0_values.update(values)
c0_address = self.dbapi.address_create(c0_values)
address_pool_values.update({'controller0_address_id': c0_address.id})
break
if self['oam_c1_ip']:
if self.address_names['oam_c1_ip'] in addresses:
self.dbapi.address_update(addresses.get(self.address_names['oam_c1_ip']).uuid,
{'address': self['oam_c1_ip']})
else:
# Address values specific to controller-1
c1_values = {
'name': self.address_names['oam_c1_ip'],
'address': self['oam_c1_ip'],
}
c1_values.update(values)
c1_address = self.dbapi.address_create(c1_values)
address_pool_values.update({'controller1_address_id': c1_address.id})
# Update address pool if new address entries for controllers were added
if address_pool_values:
self.dbapi.address_pool_update(address_pool.uuid, address_pool_values)
@staticmethod
def _get_pool_addresses(pool):
"""Return a dictionary of addresses for the supplied pool keyed by name

View File

@ -362,6 +362,31 @@ class TestPatchMixin(OAMNetworkTestCase):
self._test_patch_fail(patch_obj, http_client.BAD_REQUEST,
error_message)
def test_patch_oam_simplex_to_duplex(self):
system_dict = self.system.as_dict()
system_dict['capabilities'].update({'simplex_to_duplex_migration': True})
self.dbapi.isystem_update(self.system.uuid, system_dict)
oam_floating_ip = self.oam_subnet[2] + 100
oam_c0_ip = self.oam_subnet[3] + 100
oam_c1_ip = self.oam_subnet[4] + 100
patch_obj = {
'oam_floating_ip': str(oam_floating_ip),
'oam_c0_ip': str(oam_c0_ip),
'oam_c1_ip': str(oam_c1_ip),
}
addresses = {a['name']: a for a in
self.dbapi.addresses_get_all()}
self.assertIn('%s-%s' % (constants.CONTROLLER_0_HOSTNAME,
constants.NETWORK_TYPE_OAM),
addresses.keys())
self.assertIn('%s-%s' % (constants.CONTROLLER_1_HOSTNAME,
constants.NETWORK_TYPE_OAM),
addresses.keys())
self._test_patch_success(patch_obj)
class IPv4TestDelete(TestDeleteMixin,
OAMNetworkTestCase):

View File

@ -7,7 +7,10 @@
"""
Tests for the API /isystems/ methods.
"""
import mock
from sysinv.common import constants
from sysinv.db import api as db_api
from sysinv.tests.api import base
from sysinv.tests.db import utils as dbutils
from six.moves import http_client
@ -78,3 +81,102 @@ class TestSystemUpdate(TestSystem):
update = {"longitude": None}
self._patch_and_check(self._get_path(self.system.uuid),
update)
class TestSystemUpdateModeFromSimplex(TestSystem):
def setUp(self):
super(TestSystemUpdateModeFromSimplex, self).setUp()
self.dbapi = db_api.get_instance()
self.system = dbutils.create_test_isystem(system_type=constants.TIS_AIO_BUILD,
system_mode=constants.SYSTEM_MODE_SIMPLEX)
def _create_mgmt_interface_network(self, interface='mgmt'):
self.controller = dbutils.create_test_ihost(
id='1',
uuid=None,
forisystemid=self.system.id,
hostname='controller-0',
personality=constants.CONTROLLER,
subfunctions=constants.CONTROLLER,
invprovision=constants.PROVISIONED,
)
self.address_pool_mgmt = dbutils.create_test_address_pool(
id=1,
network='192.168.204.0',
name='management',
ranges=[['192.168.204.2', '192.168.204.254']],
prefix=24)
self.mgmt_network = dbutils.create_test_network(
id=1,
name='mgmt',
type=constants.NETWORK_TYPE_MGMT,
link_capacity=1000,
vlan_id=2,
address_pool_id=self.address_pool_mgmt.id)
self.mgmt_interface = dbutils.create_test_interface(ifname=interface,
id=1,
ifclass=constants.INTERFACE_CLASS_PLATFORM,
forihostid=self.controller.id,
ihost_uuid=self.controller.uuid,
networktypelist=[constants.NETWORK_TYPE_MGMT])
dbutils.create_test_interface_network(
interface_id=self.mgmt_interface.id,
network_id=self.mgmt_network.id)
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=True)
def test_update_system_mode_simplex_to_duplex_with_mgmt_if(self, mock_exists):
self._create_mgmt_interface_network()
update = {"system_mode": constants.SYSTEM_MODE_DUPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update)
system = self.dbapi.isystem_get_one()
system_dict = system.as_dict()
self.assertIn('simplex_to_duplex_migration', system_dict['capabilities'])
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=True)
def test_update_system_mode_simplex_to_duplex_mgmt_on_lo(self, mock_exists):
self._create_mgmt_interface_network(interface=constants.LOOPBACK_IFNAME)
update = {"system_mode": constants.SYSTEM_MODE_DUPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update, expect_errors=True)
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=True)
def test_update_system_mode_simplex_to_duplex_no_mgmt_if(self, mock_exists):
update = {"system_mode": constants.SYSTEM_MODE_DUPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update, expect_errors=True)
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=True)
def test_update_system_mode_simplex_to_simplex(self, mock_exists):
update = {"system_mode": constants.SYSTEM_MODE_SIMPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update)
system = self.dbapi.isystem_get_one()
system_dict = system.as_dict()
self.assertNotIn('simplex_to_duplex_migration', system_dict['capabilities'])
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=False)
def test_update_system_mode_before_initial_config_complete(self, mock_exists):
update = {"system_mode": constants.SYSTEM_MODE_DUPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update)
system = self.dbapi.isystem_get_one()
system_dict = system.as_dict()
self.assertNotIn('simplex_to_duplex_migration', system_dict['capabilities'])
class TestSystemUpdateModeFromDuplex(TestSystem):
def setUp(self):
super(TestSystemUpdateModeFromDuplex, self).setUp()
self.system = dbutils.create_test_isystem(system_type=constants.TIS_AIO_BUILD,
system_mode=constants.SYSTEM_MODE_DUPLEX)
@mock.patch('sysinv.common.utils.is_initial_config_complete', return_value=True)
def test_update_system_mode_duplex_to_simplex(self, mock_exists):
update = {"system_mode": constants.SYSTEM_MODE_SIMPLEX}
self._patch_and_check(self._get_path(self.system.uuid),
update, expect_errors=True)