MGMT address_pool reconfiguration for AIO-SX

This change allows the reconfiguration of the management
address_pool for an AIO-SX installation.
The reconfiguration can be done even when the system was
already configured and unlocked, but it needs to lock the
controller in order to reconfigure the management network.
Since there are ansible rules using the name: "management"
as the address_pool, it is necessary to enforce the use of
the address_pool named "management" in order to create the
mgmt network.
During a management network reconfiguration the DNSMASQ
changes must not be applied in runtime, to all changes
take effect the host lock/unlock is mandatory.

Test plan
PASS: AIO-SX IPv4 delete and create another management
      address-pool and create the management network with it
PASS: AIO-SX IPv6 delete and create another management
      address-pool and create the management network with it
PASS: AIO-SX IPv4 fresh install
PASS: AIO-SX IPv6 fresh install
PASS: AIO-DX IPv4 fresh install
PASS: AIO-DX IPv6 fresh install
PASS: STANDARD IPv4 fresh install
PASS: DC with AIO-SX IPv4 fresh install

Story: 2010722
Task: 48469
Change-Id: I2de156162dc83d2c16437d2d8068054de19a3b20
Signed-off-by: Fabiano Correa Mercer <fabiano.correamercer@windriver.com>
Signed-off-by: Teresa Ho <teresa.ho@windriver.com>
This commit is contained in:
Fabiano Correa Mercer 2023-07-26 12:16:04 -03:00
parent ab82680c1d
commit 8503921d55
12 changed files with 414 additions and 38 deletions

View File

@ -1,6 +1,6 @@
#!/bin/bash
#
# Copyright (c) 2013-2022 Wind River Systems, Inc.
# Copyright (c) 2013-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -22,6 +22,7 @@
. /etc/platform/platform.conf
PLATFORM_DIR=/opt/platform
ETC_PLATFORM_DIR=/etc/platform
VAULT_DIR=$PLATFORM_DIR/.keyring/${SW_VERSION}/python_keyring
CONFIG_DIR=$CONFIG_PATH
VOLATILE_CONFIG_PASS="/var/run/.config_pass"
@ -98,9 +99,31 @@ EOF
get_ip()
{
local host=$1
local ipaddr=""
# the host IP will be in the DNSMASQ files in /etc/platform/
if [ "$system_mode" = "simplex" ] && [ -e $COMPLETED ]; then
local host_local="${host}.internal"
local dnsmasq_file=dnsmasq.addn_hosts
# Replace the dnsmasq files with new Management Network range
if [ -e $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_unlock ] && \
[ -e $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_ongoing ]; then
dnsmasq_file=dnsmasq.addn_hosts.temp
fi
ipaddr=$(cat $ETC_PLATFORM_DIR/${dnsmasq_file} | awk -v host=$host_local '$2 == host {print $1}')
if [ -n "$ipaddr" ]
then
echo $ipaddr
return
fi
fi
# Check /etc/hosts for the hostname
local ipaddr=$(cat /etc/hosts | awk -v host=$host '$2 == host {print $1}')
ipaddr=$(cat /etc/hosts | awk -v host=$host '$2 == host {print $1}')
if [ -n "$ipaddr" ]
then
echo $ipaddr
@ -249,7 +272,7 @@ start()
fatal_error "Initial manifest application failed; Host must be re-installed."
fi
echo "Configuring controller node..."
echo "Configuring controller node... ( IP: ${IPADDR} )"
# Remove the flag if it exists
rm -f ${ACTIVE_CONTROLLER_NOT_FOUND_FLAG}
@ -514,6 +537,26 @@ start()
fi
fi
# Replace the dnsmasq files with new Management Network range
if [ -e $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_unlock ] && \
[ -e $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_ongoing ]; then
echo "Management networking reconfiguration ongoing, replacing dnsmasq config files."
if [ -e $CONFIG_DIR/dnsmasq.addn_hosts.temp ] && \
[ -e $CONFIG_DIR/dnsmasq.hosts.temp ]; then
mv -f $CONFIG_DIR/dnsmasq.hosts.temp $CONFIG_DIR/dnsmasq.hosts
mv -f $CONFIG_DIR/dnsmasq.addn_hosts.temp $CONFIG_DIR/dnsmasq.addn_hosts
# update the cached files too
mv -f $ETC_PLATFORM_DIR/dnsmasq.hosts.temp $ETC_PLATFORM_DIR/dnsmasq.hosts
mv -f $ETC_PLATFORM_DIR/dnsmasq.addn_hosts.temp $ETC_PLATFORM_DIR/dnsmasq.addn_hosts
else
fatal_error "Management networking reconfiguration ongoing and dnsmasq files do not exist."
fi
# delete flags
rm -f $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_ongoing
rm -f $ETC_PLATFORM_DIR/.mgmt_network_reconfiguration_unlock
fi
hostname > /etc/hostname
if [ $? -ne 0 ]
then

View File

@ -2062,6 +2062,11 @@ class AgentManager(service.PeriodicService):
# Set ready flag for maintenance to proceed with the unlock of
# the initial controller.
utils.touch(constants.UNLOCK_READY_FLAG)
elif (os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING) and
applied_classes == ['openstack::keystone::endpoint::reconfig']):
# Set ready flag for maintenance to proceed with the unlock
# after mgmt ip reconfiguration
utils.touch(constants.UNLOCK_READY_FLAG)
except Exception:
LOG.exception("failed to apply runtime manifest")
raise

View File

@ -63,6 +63,11 @@ SUBCLOUD_WRITABLE_ADDRPOOLS = ['system-controller-subnet',
# so we can't depend on the address pool having a static name.
SUBCLOUD_WRITABLE_NETWORK_TYPES = ['admin']
# Address pool for the management network in an AIO-SX installation
# is allowed to be deleted/modified post install
MANAGEMENT_ADDRESS_POOL = 'management'
AIOSX_WRITABLE_ADDRPOOLS = [MANAGEMENT_ADDRESS_POOL]
class AddressPoolPatchType(types.JsonPatchType):
"""A complex type that represents a single json-patch operation."""
@ -346,15 +351,55 @@ class AddressPoolController(rest.RestController):
addr = netaddr.IPAddress(address)
utils.is_valid_address_within_subnet(addr, subnet)
def _is_aiosx_writable_pool(self, addrpool, check_host_locked):
if (utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX and
addrpool.name in AIOSX_WRITABLE_ADDRPOOLS):
# The mgmt address pool is just writable when the controller is locked
if(check_host_locked):
chosts = pecan.request.dbapi.ihost_get_by_personality(
constants.CONTROLLER)
for host in chosts:
if utils.is_aio_simplex_host_unlocked(host):
msg = _("Cannot complete the action because Host {} "
"is in administrative state = unlocked"
.format(host['hostname']))
raise wsme.exc.ClientSideError(msg)
return True
return False
def _validate_aiosx_mgmt_update(self, addrpool, new_name=None):
# There are ansible rules using the explicit name: 'management' in the addrpool
# since the AIO-SX allows mgmt network reconfiguration it is necessary to enforce
# the use of addrpool named 'management'.
if (utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX and
addrpool.name in AIOSX_WRITABLE_ADDRPOOLS):
networks = pecan.request.dbapi.networks_get_by_pool(addrpool.id)
if networks and cutils.is_initial_config_complete() and \
any(network.type == constants.NETWORK_TYPE_MGMT
for network in networks):
if (new_name != MANAGEMENT_ADDRESS_POOL):
msg = _("Cannot complete the action because the "
"address pool for mgmt network must be named as '{}'."
.format(MANAGEMENT_ADDRESS_POOL))
raise ValueError(msg)
def _check_pool_readonly(self, addrpool):
# The admin and system controller address pools which exist on the
# subcloud are expected for re-home a subcloud to new system controllers.
if addrpool.name not in SUBCLOUD_WRITABLE_ADDRPOOLS:
if (addrpool.name not in SUBCLOUD_WRITABLE_ADDRPOOLS and
not self._is_aiosx_writable_pool(addrpool, True)):
networks = pecan.request.dbapi.networks_get_by_pool(addrpool.id)
# An addresspool except the admin and system controller's pools
# are considered read-only after the initial configuration is
# complete. During bootstrap it should be modifiable even though
# it is allocated to a network.
# The management address pool can be changed just for AIO-SX
if networks and cutils.is_initial_config_complete():
if any(network.type in SUBCLOUD_WRITABLE_NETWORK_TYPES
for network in networks):
@ -461,6 +506,7 @@ class AddressPoolController(rest.RestController):
def _validate_updates(self, addrpool, updates):
if 'name' in updates:
AddressPool._validate_name(updates['name'])
self._validate_aiosx_mgmt_update(addrpool, updates['name'])
if 'order' in updates:
AddressPool._validate_allocation_order(updates['order'])
if 'ranges' in updates:
@ -608,13 +654,25 @@ class AddressPoolController(rest.RestController):
addresses = pecan.request.dbapi.addresses_get_by_pool(
addrpool.id)
if addresses:
# check if an address of this pool was assigned to an interface
# e.g: address assigned to a data interface
addr_assigned_to_interface = False
for addr in addresses:
if(addr.interface_id):
addr_assigned_to_interface = True
break
# All of the initial configured addresspools are not deleteable,
# except the admin and system controller address pools on the
# subcloud. These can be deleted/re-added during re-homing
# except:
# - The admin and system controller address pools on the subcloud.
# - The management address pool for AIO-SX
# The admin and system controller can be deleted/re-added during re-homing
# a subcloud to new system controllers
if cutils.is_initial_config_complete() and \
(networks or addr_assigned_to_interface) and \
(addrpool.name not in SUBCLOUD_WRITABLE_ADDRPOOLS) and \
not any(network.type == constants.NETWORK_TYPE_ADMIN
not self._is_aiosx_writable_pool(addrpool, True) and \
not any(network.type == constants.NETWORK_TYPE_ADMIN
for network in networks):
raise exception.AddressPoolInUseByAddresses()
else:

View File

@ -2187,10 +2187,15 @@ class HostController(rest.RestController):
ihost_obj['uuid'], {'capabilities': ihost_obj['capabilities']})
# Notify maintenance about updated mgmt_ip
address_name = cutils.format_address_name(ihost_obj.hostname,
constants.NETWORK_TYPE_MGMT)
address = pecan.request.dbapi.address_get_by_name(address_name)
ihost_obj['mgmt_ip'] = address.address
# During mgmt network reconfiguration, do not change the mgmt IP
# in maintencance as it will be updated after the unlock.
if os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING):
ihost_obj['mgmt_ip'] = cutils.gethostbyname(constants.CONTROLLER_0_FQDN)
else:
address_name = cutils.format_address_name(ihost_obj.hostname,
constants.NETWORK_TYPE_MGMT)
address = pecan.request.dbapi.address_get_by_name(address_name)
ihost_obj['mgmt_ip'] = address.address
hostupdate.notify_mtce = True

View File

@ -160,6 +160,14 @@ class InterfaceNetworkController(rest.RestController):
result = pecan.request.dbapi.interface_network_create(interface_network_dict)
# Management Network reconfiguration after initial config complete
# is just supported by AIO-SX, set the flag
if (network_type == constants.NETWORK_TYPE_MGMT and
utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX and
cutils.is_initial_config_complete() and
host.hostname == constants.CONTROLLER_0_HOSTNAME):
pecan.request.rpcapi.set_mgmt_network_reconfig_flag(pecan.request.context)
# Update address mode based on network type
if network_type in [constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_OAM,
@ -180,6 +188,7 @@ class InterfaceNetworkController(rest.RestController):
# Assign an address to the interface
_update_host_address(host, interface_obj, network_type)
if network_type == constants.NETWORK_TYPE_MGMT:
ethernet_port_mac = None
if not interface_obj.uses:

View File

@ -161,7 +161,7 @@ class NetworkController(rest.RestController):
pecan.request.context, network_uuid)
return Network.convert_with_links(rpc_network)
def _check_network_type(self, networktype):
def _check_network_type(self, networktype, pool_uuid=None):
networks = pecan.request.dbapi.networks_get_by_type(networktype)
if networks:
raise exception.NetworkAlreadyExists(type=networktype)
@ -172,6 +172,16 @@ class NetworkController(rest.RestController):
"role of {}."
.format(networktype, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD))
raise wsme.exc.ClientSideError(msg)
if (networktype == constants.NETWORK_TYPE_MGMT):
# There are ansible rules using the explicit name: 'management' in the addrpool
# since the AIO-SX allows mgmt network reconfiguration it is necessary to enforce
# the use of addrpool named 'management'.
if pool_uuid:
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
if pool['name'] != "management":
msg = _("Network of type {} must use the addrpool named '{}'."
.format(networktype, address_pool.MANAGEMENT_ADDRESS_POOL))
raise wsme.exc.ClientSideError(msg)
def _check_network_pool(self, pool):
# ensure address pool exists and is not already inuse
@ -203,10 +213,25 @@ class NetworkController(rest.RestController):
self._populate_network_addresses(pool, network, addresses)
def _create_mgmt_network_address(self, pool):
addresses = collections.OrderedDict()
addresses[constants.CONTROLLER_HOSTNAME] = None
addresses[constants.CONTROLLER_0_HOSTNAME] = None
addresses[constants.CONTROLLER_1_HOSTNAME] = None
addresses = {}
if pool.floating_address:
addresses.update(
{constants.CONTROLLER_HOSTNAME: pool.floating_address})
else:
addresses.update({constants.CONTROLLER_HOSTNAME: None})
if pool.controller0_address:
addresses.update(
{constants.CONTROLLER_0_HOSTNAME: pool.controller0_address})
else:
addresses.update({constants.CONTROLLER_0_HOSTNAME: None})
if pool.controller1_address:
addresses.update(
{constants.CONTROLLER_1_HOSTNAME: pool.controller1_address})
else:
addresses.update({constants.CONTROLLER_1_HOSTNAME: None})
if pool.gateway_address is not None:
if utils.get_distributed_cloud_role() == \
@ -362,10 +387,11 @@ class NetworkController(rest.RestController):
network = network.as_dict()
network['uuid'] = str(uuid.uuid4())
# Perform semantic validation
self._check_network_type(network['type'])
pool_uuid = network.pop('pool_uuid', None)
# Perform semantic validation
self._check_network_type(network['type'], pool_uuid)
if pool_uuid:
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
network.update({'address_pool_id': pool.id})
@ -431,16 +457,31 @@ class NetworkController(rest.RestController):
def delete(self, network_uuid):
"""Delete a network."""
network = pecan.request.dbapi.network_get(network_uuid)
if cutils.is_initial_config_complete() and \
network['type'] in [constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_OAM,
if cutils.is_initial_config_complete():
if (network['type'] in [constants.NETWORK_TYPE_OAM,
constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_CLUSTER_POD,
constants.NETWORK_TYPE_CLUSTER_SERVICE,
constants.NETWORK_TYPE_STORAGE]:
msg = _("Cannot delete type {} network {} after initial "
"configuration completion"
.format(network['type'], network_uuid))
raise wsme.exc.ClientSideError(msg)
constants.NETWORK_TYPE_STORAGE] or
(network['type'] in [constants.NETWORK_TYPE_MGMT] and
utils.get_system_mode() != constants.SYSTEM_MODE_SIMPLEX)):
msg = _("Cannot delete type {} network {} after initial "
"configuration completion"
.format(network['type'], network_uuid))
raise wsme.exc.ClientSideError(msg)
elif (network['type'] in [constants.NETWORK_TYPE_MGMT] and
utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX):
# For AIO-SX the mgmt network can be be reconfigured if host is locked
chosts = pecan.request.dbapi.ihost_get_by_personality(
constants.CONTROLLER)
for host in chosts:
if utils.is_aio_simplex_host_unlocked(host):
msg = _("Cannot delete type {} network {} because Host {} "
"is in administrative state = unlocked"
.format(network['type'], network_uuid, host['hostname']))
raise wsme.exc.ClientSideError(msg)
pecan.request.dbapi.network_destroy(network_uuid)

View File

@ -1205,15 +1205,6 @@ class ConductorManager(service.PeriodicService):
mac_address)
f_out.write(line)
# Update host files atomically and reload dnsmasq
if (not os.path.isfile(dnsmasq_hosts_file) or
not filecmp.cmp(temp_dnsmasq_hosts_file, dnsmasq_hosts_file)):
os.rename(temp_dnsmasq_hosts_file, dnsmasq_hosts_file)
if (not os.path.isfile(dnsmasq_addn_hosts_file) or
not filecmp.cmp(temp_dnsmasq_addn_hosts_file,
dnsmasq_addn_hosts_file)):
os.rename(temp_dnsmasq_addn_hosts_file, dnsmasq_addn_hosts_file)
# If there is no distributed cloud addn_hosts file, create an empty one
# so dnsmasq will not complain.
dnsmasq_addn_hosts_dc_file = os.path.join(tsc.CONFIG_PATH, 'dnsmasq.addn_hosts_dc')
@ -1224,6 +1215,38 @@ class ConductorManager(service.PeriodicService):
f_out_addn_dc.write(' ')
os.rename(temp_dnsmasq_addn_hosts_dc_file, dnsmasq_addn_hosts_dc_file)
# The controller IP will be in the dnsmasq.addn_hosts
# since the /opt/platform is not mounted during the startup it is necessary to copy
# DNSMASQ files to /etc/platform/
if cutils.is_aio_simplex_system(self.dbapi):
ETC_PLAT = tsc.PLATFORM_CONF_PATH + '/'
if os.path.isfile(dnsmasq_hosts_file):
shutil.copy2(dnsmasq_hosts_file, ETC_PLAT)
if os.path.isfile(dnsmasq_addn_hosts_file):
shutil.copy2(dnsmasq_addn_hosts_file, ETC_PLAT)
if os.path.isfile(temp_dnsmasq_hosts_file):
shutil.copy2(temp_dnsmasq_hosts_file, ETC_PLAT)
if os.path.isfile(temp_dnsmasq_addn_hosts_file):
shutil.copy2(temp_dnsmasq_addn_hosts_file, ETC_PLAT)
# Ignore the dnsmasq restart when an management network reconfiguration is in process.
# This is necessary, otherwise the DNSMASQ will answer DNS requests with the new MGMT IP
# but the new mgmt IP range was not configured in the system yet.
# The new Management Network IP range will be applied after the host-unlock
if os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING):
LOG.info("Ignoring DNSMASQ changes in runtime due to Management Network reconfiguration.")
return
# Update host files atomically and reload dnsmasq
if (not os.path.isfile(dnsmasq_hosts_file) or
not filecmp.cmp(temp_dnsmasq_hosts_file, dnsmasq_hosts_file)):
os.rename(temp_dnsmasq_hosts_file, dnsmasq_hosts_file)
if (not os.path.isfile(dnsmasq_addn_hosts_file) or
not filecmp.cmp(temp_dnsmasq_addn_hosts_file,
dnsmasq_addn_hosts_file)):
os.rename(temp_dnsmasq_addn_hosts_file, dnsmasq_addn_hosts_file)
os.system("pkill -HUP dnsmasq")
def _generate_dnsmasq_conf_file(self):
@ -2034,6 +2057,33 @@ class ConductorManager(service.PeriodicService):
if utils.config_is_reboot_required(host.config_target):
config_uuid = self._config_set_reboot_required(config_uuid)
self._puppet.update_host_config(host, config_uuid)
elif os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING):
# Remove unlock ready flag to prevent maintenance rebooting the
# node until the runtime manifest is finished.
try:
if os.path.isfile(constants.UNLOCK_READY_FLAG):
os.remove(constants.UNLOCK_READY_FLAG)
except OSError:
LOG.exception("Failed to remove unlock ready flag: %s" %
constants.UNLOCK_READY_FLAG)
personalities = [constants.CONTROLLER]
# Update sysinv and keystone endpoints before the reboot
config_uuid = self._config_update_hosts(context, personalities,
host_uuids=[host.uuid])
config_dict = {
"personalities": personalities,
"host_uuids": [host.uuid],
"classes": ['openstack::keystone::endpoint::reconfig']
}
self._config_apply_runtime_manifest(
context, config_uuid, config_dict, force=True)
# Regenerate config target uuid, node is going for reboot!
config_uuid = self._config_update_hosts(context, personalities)
if utils.config_is_reboot_required(host.config_target):
config_uuid = self._config_set_reboot_required(config_uuid)
self._puppet.update_host_config(host, config_uuid)
def _ceph_mon_create(self, host):
if not StorageBackendConfig.has_backend(
@ -12516,6 +12566,15 @@ class ConductorManager(service.PeriodicService):
return iinterfaces
def set_mgmt_network_reconfig_flag(self, context):
"""set the management network reconfiguration
flag to ignore the DNSMASQ changes in runtime.
"""
if not os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING):
LOG.info("Management Network reconfiguration detected.")
open(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING, 'w').close()
def mgmt_ip_set_by_ihost(self,
context,
ihost_uuid,

View File

@ -837,6 +837,13 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
return self.call(context, self.make_msg(
'remove_admin_firewall_config'))
def set_mgmt_network_reconfig_flag(self, context):
"""Synchronously, have the conductor update the mgmt network reconfig flag.
:param context: request context.
"""
return self.call(context, self.make_msg(
'set_mgmt_network_reconfig_flag'))
def update_host_filesystem_config(self, context,
host=None,
filesystem_list=None):

View File

@ -13,9 +13,11 @@ import io
import os
import tempfile
import yaml
import tsconfig.tsconfig as tsc
from stevedore import extension
from tsconfig import tsconfig
from sysinv.common import constants
from oslo_log import log as logging
from sysinv.puppet import common
@ -194,6 +196,16 @@ class PuppetOperator(object):
self._write_host_config(host, config)
# Hiera file updated. Check if Management Network reconfiguration is ongoing
if (os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING) and
(host.action == constants.FORCE_UNLOCK_ACTION or
host.action == constants.UNLOCK_ACTION)):
if not os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_UNLOCK):
LOG.info("Management Network reconfiguration will be applied during "
"the startup. Hiera files updated and host-unlock detected")
open(tsc.MGMT_NETWORK_RECONFIGURATION_UNLOCK, 'w').close()
def read_host_config(self, host, version=None):
""""""
path = self.get_hieradata_path(version)

View File

@ -522,6 +522,133 @@ class TestDelete(NetworkTestCase):
)
class TestDeleteAIOSimplex(NetworkTestCase):
""" Tests AIO Simplex deletion.
Typically delete APIs return NO CONTENT.
python2 and python3 libraries may return different
content_type (None, or empty json) when NO_CONTENT returned.
"""
system_type = constants.TIS_AIO_BUILD
system_mode = constants.SYSTEM_MODE_SIMPLEX
def setUp(self):
super(TestDeleteAIOSimplex, self).setUp()
def _setup_context(self, host_locked=False):
if host_locked:
admin = constants.ADMIN_LOCKED
else:
admin = constants.ADMIN_UNLOCKED
self.host = self._create_test_host(constants.CONTROLLER, constants.WORKER,
administrative=admin,
operational=constants.OPERATIONAL_ENABLED,
availability=constants.AVAILABILITY_AVAILABLE,
invprovision=constants.PROVISIONED,
vim_progress_status=constants.VIM_SERVICES_ENABLED)
self._create_test_host_cpus(self.host, platform=2, vswitch=2, application=11)
def _test_delete_allowed(self, network_type):
# Delete the API object
self.delete_object = self._create_db_object(network_type=network_type)
uuid = self.delete_object.uuid
response = self.delete(self.get_single_url(uuid),
headers=self.API_HEADERS)
# Verify the expected API response for the delete
self.assertEqual(response.status_code, http_client.NO_CONTENT)
def _test_delete_after_initial_config_not_allowed(self, network_type):
# Delete the API object
self.delete_object = self._create_db_object(network_type=network_type)
with mock.patch('sysinv.common.utils.is_initial_config_complete',
lambda: True):
uuid = self.delete_object.uuid
response = self.delete(self.get_single_url(uuid),
headers=self.API_HEADERS,
expect_errors=True)
# Verify the expected API response for the delete
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
expected_error = ("Cannot delete type %s network %s after"
" initial configuration completion" %
(network_type, uuid))
self.assertIn(expected_error, response.json['error_message'])
def _test_delete_mgmt_after_initial_config_not_allowed(self, network_type):
# Delete the API object
self.delete_object = self._create_db_object(network_type=network_type)
with mock.patch('sysinv.common.utils.is_initial_config_complete',
lambda: True):
uuid = self.delete_object.uuid
response = self.delete(self.get_single_url(uuid),
headers=self.API_HEADERS,
expect_errors=True)
# Verify the expected API response for the delete
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
expected_error = ("Cannot delete type %s network %s because Host "
"controller-0 is in administrative state = unlocked" %
(network_type, uuid))
self.assertIn(expected_error, response.json['error_message'])
def _test_delete_after_initial_config_allowed(self, network_type):
# Delete the API object
self.delete_object = self._create_db_object(network_type=network_type)
with mock.patch('sysinv.common.utils.is_initial_config_complete',
lambda: True):
uuid = self.delete_object.uuid
response = self.delete(self.get_single_url(uuid),
headers=self.API_HEADERS)
# Verify the expected API response for the delete
self.assertEqual(response.status_code, http_client.NO_CONTENT)
def test_delete_management(self):
self._test_delete_allowed(constants.NETWORK_TYPE_MGMT)
def test_delete_management_after_initial_config_not_allowed_host_unlocked(self):
self._setup_context(host_locked=False)
self._test_delete_mgmt_after_initial_config_not_allowed(
constants.NETWORK_TYPE_MGMT
)
def test_delete_management_after_initial_config_allowed_host_locked(self):
self._setup_context(host_locked=True)
self._test_delete_after_initial_config_allowed(
constants.NETWORK_TYPE_MGMT
)
# just to make sure that the other networks can't be deleted
def test_delete_oam(self):
self._setup_context(host_locked=False)
self._test_delete_allowed(constants.NETWORK_TYPE_OAM)
def test_delete_oam_after_initial_config(self):
self._setup_context(host_locked=False)
self._test_delete_after_initial_config_not_allowed(
constants.NETWORK_TYPE_OAM
)
def test_delete_data(self):
self._setup_context(host_locked=False)
self._test_delete_allowed(constants.NETWORK_TYPE_DATA)
def test_delete_data_after_initial_config(self):
self._setup_context(host_locked=False)
self._test_delete_after_initial_config_allowed(
constants.NETWORK_TYPE_DATA
)
class TestList(NetworkTestCase):
""" Network list operations
"""

View File

@ -558,7 +558,7 @@ class StorageHostTestCase(BaseHostTestCase):
class AIOHostTestCase(BaseHostTestCase):
system_mode = constants.TIS_AIO_BUILD
system_type = constants.TIS_AIO_BUILD
def setUp(self):
super(AIOHostTestCase, self).setUp()
@ -568,7 +568,7 @@ class AIOHostTestCase(BaseHostTestCase):
class ProvisionedAIOHostTestCase(BaseHostTestCase):
system_mode = constants.TIS_AIO_BUILD
system_type = constants.TIS_AIO_BUILD
def setUp(self):
super(ProvisionedAIOHostTestCase, self).setUp()

View File

@ -208,6 +208,16 @@ INITIAL_K8S_CONFIG_COMPLETE = os.path.join(
VOLATILE_CONTROLLER_CONFIG_COMPLETE = os.path.join(
VOLATILE_PATH, ".controller_config_complete")
# Set when mgmt network reconfiguration is executed after
# INITIAL_CONTROLLER_CONFIG_COMPLETE
MGMT_NETWORK_RECONFIGURATION_ONGOING = os.path.join(
PLATFORM_CONF_PATH, ".mgmt_network_reconfiguration_ongoing")
# Set when host-unlock was executed and hieradata was updated
# with new MGMT IP RANGE.
MGMT_NETWORK_RECONFIGURATION_UNLOCK = os.path.join(
PLATFORM_CONF_PATH, ".mgmt_network_reconfiguration_unlock")
# Worker configuration flags
# Set after initial application of node manifest