Merge "MGMT address_pool reconfiguration for AIO-SX"
This commit is contained in:
commit
47516972f4
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,12 +654,24 @@ 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 self._is_aiosx_writable_pool(addrpool, True) and \
|
||||
not any(network.type == constants.NETWORK_TYPE_ADMIN
|
||||
for network in networks):
|
||||
raise exception.AddressPoolInUseByAddresses()
|
||||
|
|
|
@ -2187,6 +2187,11 @@ class HostController(rest.RestController):
|
|||
ihost_obj['uuid'], {'capabilities': ihost_obj['capabilities']})
|
||||
|
||||
# Notify maintenance about updated mgmt_ip
|
||||
# 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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]:
|
||||
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)
|
||||
|
|
|
@ -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):
|
||||
|
@ -2078,6 +2101,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(
|
||||
|
@ -12566,6 +12616,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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue