Merge "Add support for AIO-SX to DX migration on subcloud"
This commit is contained in:
commit
245440d262
|
@ -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'" %
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue