Merge "MGMT address_pool reconfiguration for AIO-SX"
This commit is contained in:
commit
47516972f4
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013-2022 Wind River Systems, Inc.
|
# Copyright (c) 2013-2023 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
@ -22,6 +22,7 @@
|
||||||
. /etc/platform/platform.conf
|
. /etc/platform/platform.conf
|
||||||
|
|
||||||
PLATFORM_DIR=/opt/platform
|
PLATFORM_DIR=/opt/platform
|
||||||
|
ETC_PLATFORM_DIR=/etc/platform
|
||||||
VAULT_DIR=$PLATFORM_DIR/.keyring/${SW_VERSION}/python_keyring
|
VAULT_DIR=$PLATFORM_DIR/.keyring/${SW_VERSION}/python_keyring
|
||||||
CONFIG_DIR=$CONFIG_PATH
|
CONFIG_DIR=$CONFIG_PATH
|
||||||
VOLATILE_CONFIG_PASS="/var/run/.config_pass"
|
VOLATILE_CONFIG_PASS="/var/run/.config_pass"
|
||||||
|
@ -98,9 +99,31 @@ EOF
|
||||||
get_ip()
|
get_ip()
|
||||||
{
|
{
|
||||||
local host=$1
|
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
|
# 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" ]
|
if [ -n "$ipaddr" ]
|
||||||
then
|
then
|
||||||
echo $ipaddr
|
echo $ipaddr
|
||||||
|
@ -249,7 +272,7 @@ start()
|
||||||
fatal_error "Initial manifest application failed; Host must be re-installed."
|
fatal_error "Initial manifest application failed; Host must be re-installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Configuring controller node..."
|
echo "Configuring controller node... ( IP: ${IPADDR} )"
|
||||||
|
|
||||||
# Remove the flag if it exists
|
# Remove the flag if it exists
|
||||||
rm -f ${ACTIVE_CONTROLLER_NOT_FOUND_FLAG}
|
rm -f ${ACTIVE_CONTROLLER_NOT_FOUND_FLAG}
|
||||||
|
@ -514,6 +537,26 @@ start()
|
||||||
fi
|
fi
|
||||||
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
|
hostname > /etc/hostname
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
|
|
|
@ -2062,6 +2062,11 @@ class AgentManager(service.PeriodicService):
|
||||||
# Set ready flag for maintenance to proceed with the unlock of
|
# Set ready flag for maintenance to proceed with the unlock of
|
||||||
# the initial controller.
|
# the initial controller.
|
||||||
utils.touch(constants.UNLOCK_READY_FLAG)
|
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:
|
except Exception:
|
||||||
LOG.exception("failed to apply runtime manifest")
|
LOG.exception("failed to apply runtime manifest")
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -63,6 +63,11 @@ SUBCLOUD_WRITABLE_ADDRPOOLS = ['system-controller-subnet',
|
||||||
# so we can't depend on the address pool having a static name.
|
# so we can't depend on the address pool having a static name.
|
||||||
SUBCLOUD_WRITABLE_NETWORK_TYPES = ['admin']
|
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):
|
class AddressPoolPatchType(types.JsonPatchType):
|
||||||
"""A complex type that represents a single json-patch operation."""
|
"""A complex type that represents a single json-patch operation."""
|
||||||
|
@ -346,15 +351,55 @@ class AddressPoolController(rest.RestController):
|
||||||
addr = netaddr.IPAddress(address)
|
addr = netaddr.IPAddress(address)
|
||||||
utils.is_valid_address_within_subnet(addr, subnet)
|
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):
|
def _check_pool_readonly(self, addrpool):
|
||||||
# The admin and system controller address pools which exist on the
|
# The admin and system controller address pools which exist on the
|
||||||
# subcloud are expected for re-home a subcloud to new system controllers.
|
# 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)
|
networks = pecan.request.dbapi.networks_get_by_pool(addrpool.id)
|
||||||
# An addresspool except the admin and system controller's pools
|
# An addresspool except the admin and system controller's pools
|
||||||
# are considered read-only after the initial configuration is
|
# are considered read-only after the initial configuration is
|
||||||
# complete. During bootstrap it should be modifiable even though
|
# complete. During bootstrap it should be modifiable even though
|
||||||
# it is allocated to a network.
|
# 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 networks and cutils.is_initial_config_complete():
|
||||||
if any(network.type in SUBCLOUD_WRITABLE_NETWORK_TYPES
|
if any(network.type in SUBCLOUD_WRITABLE_NETWORK_TYPES
|
||||||
for network in networks):
|
for network in networks):
|
||||||
|
@ -461,6 +506,7 @@ class AddressPoolController(rest.RestController):
|
||||||
def _validate_updates(self, addrpool, updates):
|
def _validate_updates(self, addrpool, updates):
|
||||||
if 'name' in updates:
|
if 'name' in updates:
|
||||||
AddressPool._validate_name(updates['name'])
|
AddressPool._validate_name(updates['name'])
|
||||||
|
self._validate_aiosx_mgmt_update(addrpool, updates['name'])
|
||||||
if 'order' in updates:
|
if 'order' in updates:
|
||||||
AddressPool._validate_allocation_order(updates['order'])
|
AddressPool._validate_allocation_order(updates['order'])
|
||||||
if 'ranges' in updates:
|
if 'ranges' in updates:
|
||||||
|
@ -608,13 +654,25 @@ class AddressPoolController(rest.RestController):
|
||||||
addresses = pecan.request.dbapi.addresses_get_by_pool(
|
addresses = pecan.request.dbapi.addresses_get_by_pool(
|
||||||
addrpool.id)
|
addrpool.id)
|
||||||
if addresses:
|
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,
|
# All of the initial configured addresspools are not deleteable,
|
||||||
# except the admin and system controller address pools on the
|
# except:
|
||||||
# subcloud. These can be deleted/re-added during re-homing
|
# - 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
|
# a subcloud to new system controllers
|
||||||
if cutils.is_initial_config_complete() and \
|
if cutils.is_initial_config_complete() and \
|
||||||
|
(networks or addr_assigned_to_interface) and \
|
||||||
(addrpool.name not in SUBCLOUD_WRITABLE_ADDRPOOLS) 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):
|
for network in networks):
|
||||||
raise exception.AddressPoolInUseByAddresses()
|
raise exception.AddressPoolInUseByAddresses()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -2187,10 +2187,15 @@ class HostController(rest.RestController):
|
||||||
ihost_obj['uuid'], {'capabilities': ihost_obj['capabilities']})
|
ihost_obj['uuid'], {'capabilities': ihost_obj['capabilities']})
|
||||||
|
|
||||||
# Notify maintenance about updated mgmt_ip
|
# Notify maintenance about updated mgmt_ip
|
||||||
address_name = cutils.format_address_name(ihost_obj.hostname,
|
# During mgmt network reconfiguration, do not change the mgmt IP
|
||||||
constants.NETWORK_TYPE_MGMT)
|
# in maintencance as it will be updated after the unlock.
|
||||||
address = pecan.request.dbapi.address_get_by_name(address_name)
|
if os.path.isfile(tsc.MGMT_NETWORK_RECONFIGURATION_ONGOING):
|
||||||
ihost_obj['mgmt_ip'] = address.address
|
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
|
hostupdate.notify_mtce = True
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,14 @@ class InterfaceNetworkController(rest.RestController):
|
||||||
|
|
||||||
result = pecan.request.dbapi.interface_network_create(interface_network_dict)
|
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
|
# Update address mode based on network type
|
||||||
if network_type in [constants.NETWORK_TYPE_MGMT,
|
if network_type in [constants.NETWORK_TYPE_MGMT,
|
||||||
constants.NETWORK_TYPE_OAM,
|
constants.NETWORK_TYPE_OAM,
|
||||||
|
@ -180,6 +188,7 @@ class InterfaceNetworkController(rest.RestController):
|
||||||
|
|
||||||
# Assign an address to the interface
|
# Assign an address to the interface
|
||||||
_update_host_address(host, interface_obj, network_type)
|
_update_host_address(host, interface_obj, network_type)
|
||||||
|
|
||||||
if network_type == constants.NETWORK_TYPE_MGMT:
|
if network_type == constants.NETWORK_TYPE_MGMT:
|
||||||
ethernet_port_mac = None
|
ethernet_port_mac = None
|
||||||
if not interface_obj.uses:
|
if not interface_obj.uses:
|
||||||
|
|
|
@ -161,7 +161,7 @@ class NetworkController(rest.RestController):
|
||||||
pecan.request.context, network_uuid)
|
pecan.request.context, network_uuid)
|
||||||
return Network.convert_with_links(rpc_network)
|
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)
|
networks = pecan.request.dbapi.networks_get_by_type(networktype)
|
||||||
if networks:
|
if networks:
|
||||||
raise exception.NetworkAlreadyExists(type=networktype)
|
raise exception.NetworkAlreadyExists(type=networktype)
|
||||||
|
@ -172,6 +172,16 @@ class NetworkController(rest.RestController):
|
||||||
"role of {}."
|
"role of {}."
|
||||||
.format(networktype, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD))
|
.format(networktype, constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD))
|
||||||
raise wsme.exc.ClientSideError(msg)
|
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):
|
def _check_network_pool(self, pool):
|
||||||
# ensure address pool exists and is not already inuse
|
# ensure address pool exists and is not already inuse
|
||||||
|
@ -203,10 +213,25 @@ class NetworkController(rest.RestController):
|
||||||
self._populate_network_addresses(pool, network, addresses)
|
self._populate_network_addresses(pool, network, addresses)
|
||||||
|
|
||||||
def _create_mgmt_network_address(self, pool):
|
def _create_mgmt_network_address(self, pool):
|
||||||
addresses = collections.OrderedDict()
|
addresses = {}
|
||||||
addresses[constants.CONTROLLER_HOSTNAME] = None
|
|
||||||
addresses[constants.CONTROLLER_0_HOSTNAME] = None
|
if pool.floating_address:
|
||||||
addresses[constants.CONTROLLER_1_HOSTNAME] = None
|
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 pool.gateway_address is not None:
|
||||||
if utils.get_distributed_cloud_role() == \
|
if utils.get_distributed_cloud_role() == \
|
||||||
|
@ -362,10 +387,11 @@ class NetworkController(rest.RestController):
|
||||||
network = network.as_dict()
|
network = network.as_dict()
|
||||||
network['uuid'] = str(uuid.uuid4())
|
network['uuid'] = str(uuid.uuid4())
|
||||||
|
|
||||||
# Perform semantic validation
|
|
||||||
self._check_network_type(network['type'])
|
|
||||||
|
|
||||||
pool_uuid = network.pop('pool_uuid', None)
|
pool_uuid = network.pop('pool_uuid', None)
|
||||||
|
|
||||||
|
# Perform semantic validation
|
||||||
|
self._check_network_type(network['type'], pool_uuid)
|
||||||
|
|
||||||
if pool_uuid:
|
if pool_uuid:
|
||||||
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
|
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
|
||||||
network.update({'address_pool_id': pool.id})
|
network.update({'address_pool_id': pool.id})
|
||||||
|
@ -431,16 +457,31 @@ class NetworkController(rest.RestController):
|
||||||
def delete(self, network_uuid):
|
def delete(self, network_uuid):
|
||||||
"""Delete a network."""
|
"""Delete a network."""
|
||||||
network = pecan.request.dbapi.network_get(network_uuid)
|
network = pecan.request.dbapi.network_get(network_uuid)
|
||||||
if cutils.is_initial_config_complete() and \
|
if cutils.is_initial_config_complete():
|
||||||
network['type'] in [constants.NETWORK_TYPE_MGMT,
|
if (network['type'] in [constants.NETWORK_TYPE_OAM,
|
||||||
constants.NETWORK_TYPE_OAM,
|
|
||||||
constants.NETWORK_TYPE_CLUSTER_HOST,
|
constants.NETWORK_TYPE_CLUSTER_HOST,
|
||||||
constants.NETWORK_TYPE_PXEBOOT,
|
constants.NETWORK_TYPE_PXEBOOT,
|
||||||
constants.NETWORK_TYPE_CLUSTER_POD,
|
constants.NETWORK_TYPE_CLUSTER_POD,
|
||||||
constants.NETWORK_TYPE_CLUSTER_SERVICE,
|
constants.NETWORK_TYPE_CLUSTER_SERVICE,
|
||||||
constants.NETWORK_TYPE_STORAGE]:
|
constants.NETWORK_TYPE_STORAGE] or
|
||||||
msg = _("Cannot delete type {} network {} after initial "
|
(network['type'] in [constants.NETWORK_TYPE_MGMT] and
|
||||||
"configuration completion"
|
utils.get_system_mode() != constants.SYSTEM_MODE_SIMPLEX)):
|
||||||
.format(network['type'], network_uuid))
|
msg = _("Cannot delete type {} network {} after initial "
|
||||||
raise wsme.exc.ClientSideError(msg)
|
"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)
|
pecan.request.dbapi.network_destroy(network_uuid)
|
||||||
|
|
|
@ -1205,15 +1205,6 @@ class ConductorManager(service.PeriodicService):
|
||||||
mac_address)
|
mac_address)
|
||||||
f_out.write(line)
|
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
|
# If there is no distributed cloud addn_hosts file, create an empty one
|
||||||
# so dnsmasq will not complain.
|
# so dnsmasq will not complain.
|
||||||
dnsmasq_addn_hosts_dc_file = os.path.join(tsc.CONFIG_PATH, 'dnsmasq.addn_hosts_dc')
|
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(' ')
|
f_out_addn_dc.write(' ')
|
||||||
os.rename(temp_dnsmasq_addn_hosts_dc_file, dnsmasq_addn_hosts_dc_file)
|
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")
|
os.system("pkill -HUP dnsmasq")
|
||||||
|
|
||||||
def _generate_dnsmasq_conf_file(self):
|
def _generate_dnsmasq_conf_file(self):
|
||||||
|
@ -2078,6 +2101,33 @@ class ConductorManager(service.PeriodicService):
|
||||||
if utils.config_is_reboot_required(host.config_target):
|
if utils.config_is_reboot_required(host.config_target):
|
||||||
config_uuid = self._config_set_reboot_required(config_uuid)
|
config_uuid = self._config_set_reboot_required(config_uuid)
|
||||||
self._puppet.update_host_config(host, 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):
|
def _ceph_mon_create(self, host):
|
||||||
if not StorageBackendConfig.has_backend(
|
if not StorageBackendConfig.has_backend(
|
||||||
|
@ -12566,6 +12616,15 @@ class ConductorManager(service.PeriodicService):
|
||||||
|
|
||||||
return iinterfaces
|
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,
|
def mgmt_ip_set_by_ihost(self,
|
||||||
context,
|
context,
|
||||||
ihost_uuid,
|
ihost_uuid,
|
||||||
|
|
|
@ -837,6 +837,13 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
|
||||||
return self.call(context, self.make_msg(
|
return self.call(context, self.make_msg(
|
||||||
'remove_admin_firewall_config'))
|
'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,
|
def update_host_filesystem_config(self, context,
|
||||||
host=None,
|
host=None,
|
||||||
filesystem_list=None):
|
filesystem_list=None):
|
||||||
|
|
|
@ -13,9 +13,11 @@ import io
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import yaml
|
import yaml
|
||||||
|
import tsconfig.tsconfig as tsc
|
||||||
|
|
||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
from tsconfig import tsconfig
|
from tsconfig import tsconfig
|
||||||
|
from sysinv.common import constants
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from sysinv.puppet import common
|
from sysinv.puppet import common
|
||||||
|
@ -194,6 +196,16 @@ class PuppetOperator(object):
|
||||||
|
|
||||||
self._write_host_config(host, config)
|
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):
|
def read_host_config(self, host, version=None):
|
||||||
""""""
|
""""""
|
||||||
path = self.get_hieradata_path(version)
|
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):
|
class TestList(NetworkTestCase):
|
||||||
""" Network list operations
|
""" Network list operations
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -558,7 +558,7 @@ class StorageHostTestCase(BaseHostTestCase):
|
||||||
|
|
||||||
class AIOHostTestCase(BaseHostTestCase):
|
class AIOHostTestCase(BaseHostTestCase):
|
||||||
|
|
||||||
system_mode = constants.TIS_AIO_BUILD
|
system_type = constants.TIS_AIO_BUILD
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AIOHostTestCase, self).setUp()
|
super(AIOHostTestCase, self).setUp()
|
||||||
|
@ -568,7 +568,7 @@ class AIOHostTestCase(BaseHostTestCase):
|
||||||
|
|
||||||
class ProvisionedAIOHostTestCase(BaseHostTestCase):
|
class ProvisionedAIOHostTestCase(BaseHostTestCase):
|
||||||
|
|
||||||
system_mode = constants.TIS_AIO_BUILD
|
system_type = constants.TIS_AIO_BUILD
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ProvisionedAIOHostTestCase, self).setUp()
|
super(ProvisionedAIOHostTestCase, self).setUp()
|
||||||
|
|
|
@ -208,6 +208,16 @@ INITIAL_K8S_CONFIG_COMPLETE = os.path.join(
|
||||||
VOLATILE_CONTROLLER_CONFIG_COMPLETE = os.path.join(
|
VOLATILE_CONTROLLER_CONFIG_COMPLETE = os.path.join(
|
||||||
VOLATILE_PATH, ".controller_config_complete")
|
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
|
# Worker configuration flags
|
||||||
|
|
||||||
# Set after initial application of node manifest
|
# Set after initial application of node manifest
|
||||||
|
|
Loading…
Reference in New Issue