Introduce Puppet variables for primary and secondary pool addresses.

Details:

This change extracts the addresses from both the primary and secondary
address pools and makes them available for use in Puppet manifests.

To accommodate the dual stack configuration, the address allocation
for non-controller nodes was updated for both management and
cluster-host networks.

Since the task for upgrade data-migration is not ready yet, a logic
was added to access directly the network's field pool_uuid and get
the addresses with it, if the network_addresspools is empty (as it
would be the case after an upgrade)

As the data migration functionality for the upgrade is still under
development, a temporary solution was implemented.  Logic was added
to directly access the network's "pool_uuid" field and retrieve
addresses through it whenever the "network_addresspools" list is
empty, which is expected to occur immediately following an upgrade.
This allows for uninterrupted network operation during the upgrade
process.

Variable Naming:

The following naming convention will be used for the variables:
$platform::network::[network_type]::[ipv4/ipv6]::params::{var_name}

Variable Usage:

Primary Pool: Existing variables will be maintained and populated with
addresses from the primary pool. This ensures compatibility with
applications that currently rely on them. They have the format
$platform::network::[network_type]::params::{var_name}

The variable platform::network::[network_type]::params::subnet_version
indicates the primary pool protocol.

Secondary Pool: New variables with the above naming convention will
be introduced, allowing applications to utilize addresses from the
secondary pool if needed.

Benefits:

Improved modularity and reusability of network configurations.
Clear separation of concerns between primary and secondary pools.
Easier implementation of applications requiring addresses from either pool.

Notes:

Replace [network_type] can be oam. mgmt, cluster_host, ...
Replace [ipv4/ipv6] with either "ipv4" or "ipv6" depending on
         the address family.
Replace [variable_name] with a descriptive name for the specific
         variable (e.g., "subnet_version", "interface_address").

Test Plan:

[PASS] unit tests implemented
[PASS] AIO-SX, Standard instalation (IPv4 and IPv6)
       - using the dependency change the secondary pool was introduced
       - system was lock/unlocked and no puppet manifests were
          detected
       - inspection of system.yaml and controller-0.yaml to verify
         variables content
       - no alarms or disabled services were found
       - in standard added hosts with dual-stack config and verified
         that addresses were allocated for mgmt and cluster-host and
         after unlock the interface id was assigned to the respective
         entries.
[PASS] For standard systems during upgrade, simulate node unlock by:
       - Clearing the "network_addresspools" table after Ansible
         execution and before DM configuration.
       - Installing remaining nodes with the table empty. This mimics
         the post-upgrade scenario.

Story: 2011027
Task: 49679

Depends-On: https://review.opendev.org/c/starlingx/config/+/908915

Change-Id: If252fa051b2ba5b5eb3033ff269683af741091d2
Signed-off-by: Andre Kantek <andrefernandozanella.kantek@windriver.com>
This commit is contained in:
Andre Kantek 2024-02-29 15:23:19 -03:00
parent 634d491647
commit fcebab8ef3
6 changed files with 1165 additions and 144 deletions

View File

@ -1032,6 +1032,20 @@ class ConductorManager(service.PeriodicService):
True)
return address.address
except exception.AddressNotFoundByName:
LOG.info(f"cannot find address with name={name}")
return None
def _lookup_static_ip_address_family(self, name, networktype, family):
""""Find a statically configured address based on name, network type,
and address family."""
try:
# address names are refined by network type to ensure they are
# unique across different address pools
name = cutils.format_address_name(name, networktype)
address = self.dbapi.address_get_by_name_and_family(name, family)
return address.address
except exception.AddressNotFoundByNameAndFamily:
LOG.info(f"cannot find address with name={name}, family={family}")
return None
def _using_static_ip(self, ihost, personality=None, hostname=None):
@ -1295,7 +1309,7 @@ class ConductorManager(service.PeriodicService):
)
func = "_generate_dnsmasq_hosts_file"
with open(temp_dnsmasq_hosts_file, 'w') as f_out,\
with open(temp_dnsmasq_hosts_file, 'w') as f_out, \
open(temp_dnsmasq_addn_hosts_file, 'w') as f_out_addn:
# Write entry for pxecontroller into addn_hosts file
@ -1966,7 +1980,19 @@ class ConductorManager(service.PeriodicService):
pass
def _create_or_update_address(self, context, hostname, ip_address,
iface_type, iface_id=None):
iface_type, iface_id=None, pool_uuid=None):
"""Searches the address database and create or update accordingly
Args:
hostname (str): The host name
ip_address (str): The IP address to be created or updated.
iface_type (str): The interface network type.
iface_id (int, optional): Interface ID that uses this address. Defaults to None.
pool_uuid (str, optional): The address pool uuid. Defaults to None.
Returns:
sysinv.object.address: The updated or created address
"""
if hostname is None or ip_address is None:
return
address_name = cutils.format_address_name(hostname, iface_type)
@ -1974,34 +2000,41 @@ class ConductorManager(service.PeriodicService):
try:
address = self.dbapi.address_get_by_address(ip_address)
address_uuid = address['uuid']
search_addr = self.dbapi.address_get_by_name_and_family(address_name,
address_family)
# If name is already set, return
search_addr = cutils.get_primary_address_by_name(self.dbapi,
address_name,
iface_type, True)
if search_addr:
if (search_addr.uuid == address_uuid and iface_id is None):
LOG.info(f"returning, address '{address_uuid}' exists and iface_id is None")
return
except exception.AddressNotFoundByAddress:
address_uuid = None
except exception.AddressNotFoundByName:
except exception.AddressNotFoundByNameAndFamily:
pass
network = self.dbapi.network_get_by_type(iface_type)
address_pool_uuid = network.pool_uuid
address_pool = self.dbapi.address_pool_get(address_pool_uuid)
values = {
'name': address_name,
'family': address_family,
'prefix': address_pool.prefix,
'address': ip_address,
'address_pool_id': address_pool.id,
}
if iface_id:
values['interface_id'] = iface_id
if address_uuid:
address = self.dbapi.address_update(address_uuid, values)
address_pool = None
if pool_uuid:
address_pool = self.dbapi.address_pool_get(pool_uuid)
else:
address = self.dbapi.address_create(values)
network = self.dbapi.network_get_by_type(iface_type)
address_pool = self.dbapi.address_pool_get(network.pool_uuid)
if address_pool:
values = {
'name': address_name,
'family': address_family,
'prefix': address_pool.prefix,
'address': ip_address,
'address_pool_id': address_pool.id,
}
if iface_id:
values['interface_id'] = iface_id
if address_uuid:
address = self.dbapi.address_update(address_uuid, values)
else:
address = self.dbapi.address_create(values)
self._generate_dnsmasq_hosts_file()
return address
@ -2022,19 +2055,32 @@ class ConductorManager(service.PeriodicService):
# controller must have cluster-host address already allocated
if (host.personality != constants.CONTROLLER):
network = self.dbapi.network_get_by_type(constants.NETWORK_TYPE_CLUSTER_HOST)
net_pools = self.dbapi.network_addrpool_get_by_network_id(network.id)
pool_uuid_list = list()
if net_pools:
for net_pool in net_pools:
pool_uuid_list.append(net_pool.address_pool_uuid)
else:
# we are coming from an upgrade without data-migration implemented for the
# dual stack feature
LOG.warning(f"Network {network.name} does not have network to address pool objects")
pool_uuid_list.append(network.pool_uuid)
cluster_host_address = self._lookup_static_ip_address(
host.hostname, constants.NETWORK_TYPE_CLUSTER_HOST)
hostname = host.hostname
if cluster_host_address is None:
address_name = cutils.format_address_name(
host.hostname, constants.NETWORK_TYPE_CLUSTER_HOST)
LOG.info("{} address not found. Allocating address for {}.".format(
address_name, host.hostname))
host_network = self.dbapi.network_get_by_type(
constants.NETWORK_TYPE_CLUSTER_HOST)
self._allocate_pool_address(None, host_network.pool_uuid,
address_name)
for pool_uuid in pool_uuid_list:
pool = self.dbapi.address_pool_get(pool_uuid)
cluster_host_address = self._lookup_static_ip_address_family(
host.hostname, constants.NETWORK_TYPE_CLUSTER_HOST, pool.family)
if cluster_host_address is None:
address_name = cutils.format_address_name(
hostname, constants.NETWORK_TYPE_CLUSTER_HOST)
resp_addr = self._allocate_pool_address(None, pool.uuid, address_name)
LOG.info(f"{address_name} address not found."
f" Allocating address {resp_addr.address} for {hostname}.")
def _allocate_addresses_for_host(self, context, host):
"""Allocates addresses for a given host.
@ -2054,28 +2100,40 @@ class ConductorManager(service.PeriodicService):
mgmt_interface_id = None
if mgmt_interfaces:
mgmt_interface_id = mgmt_interfaces[0]['id']
hostname = host.hostname
# check for static mgmt IP
mgmt_ip = self._lookup_static_ip_address(
hostname, constants.NETWORK_TYPE_MGMT
)
# make sure address in address table and update dnsmasq host file
if mgmt_ip:
LOG.info("Static mgmt ip {} for host{}".format(mgmt_ip, hostname))
self._create_or_update_address(context, hostname, mgmt_ip,
constants.NETWORK_TYPE_MGMT,
mgmt_interface_id)
# if no static address, then allocate one
if not mgmt_ip:
mgmt_pool = self.dbapi.network_get_by_type(
constants.NETWORK_TYPE_MGMT
).pool_uuid
address_name = cutils.format_address_name(hostname,
constants.NETWORK_TYPE_MGMT)
mgmt_ip = self._allocate_pool_address(mgmt_interface_id, mgmt_pool,
address_name).address
LOG.info("Allocated mgmt ip {} for host{}".format(mgmt_ip, hostname))
hostname = host.hostname
mgmt_net = self.dbapi.network_get_by_type(constants.NETWORK_TYPE_MGMT)
net_pools = self.dbapi.network_addrpool_get_by_network_id(mgmt_net.id)
pool_uuid_list = list()
if net_pools:
for net_pool in net_pools:
pool_uuid_list.append(net_pool.address_pool_uuid)
else:
# we are coming from an upgrade without data-migration implemented for the
# dual stack feature
LOG.warning(f"Network {mgmt_net.name} does not have network to address pool objects")
pool_uuid_list.append(mgmt_net.pool_uuid)
for pool_uuid in pool_uuid_list:
pool = self.dbapi.address_pool_get(pool_uuid)
# check for static mgmt IP
mgmt_ip = self._lookup_static_ip_address_family(
hostname, constants.NETWORK_TYPE_MGMT, pool.family)
# make sure address in address table and update dnsmasq host file
if mgmt_ip:
LOG.info("Static mgmt ip {} for host{}".format(mgmt_ip, hostname))
self._create_or_update_address(context, hostname, mgmt_ip,
constants.NETWORK_TYPE_MGMT,
mgmt_interface_id, pool_uuid)
# if no static address, then allocate one
if not mgmt_ip:
address_name = cutils.format_address_name(hostname,
constants.NETWORK_TYPE_MGMT)
mgmt_ip = self._allocate_pool_address(mgmt_interface_id, pool_uuid,
address_name).address
LOG.info(f"Allocated mgmt ip {mgmt_ip} for host={hostname}")
self._generate_dnsmasq_hosts_file(existing_host=host)
self._allocate_cluster_host_address_for_host(host)
@ -2842,6 +2900,7 @@ class ConductorManager(service.PeriodicService):
puppet_common.puppet_apply_manifest(host.hostname,
constants.WORKER,
do_reboot=True)
return host
def unconfigure_ihost(self, context, ihost_obj):
@ -3518,23 +3577,29 @@ class ConductorManager(service.PeriodicService):
if set_address_interface:
if new_interface and 'id' in new_interface:
values = {'interface_id': new_interface['id']}
address = cutils.get_primary_address_by_name(self.dbapi,
cutils.format_address_name(ihost.hostname, new_interface_networktype),
new_interface_networktype)
if address:
self.dbapi.address_update(address['uuid'], values)
try:
addr_name = cutils.format_address_name(
ihost.hostname, new_interface_networktype)
addresses = self.dbapi.address_get_by_name(addr_name)
for address in addresses:
self.dbapi.address_update(address['uuid'], values)
except exception.AddressNotFoundByName:
pass
# Do any potential distributed cloud config
# We do this here where the interface is created.
cutils.perform_distributed_cloud_config(self.dbapi,
new_interface['id'])
if port:
values = {'interface_id': port.interface_id}
address = cutils.get_primary_address_by_name(self.dbapi,
cutils.format_address_name(ihost.hostname, networktype),
networktype)
if address:
if address['interface_id'] is None:
self.dbapi.address_update(address['uuid'], values)
try:
addr_name = cutils.format_address_name(ihost.hostname,
networktype)
addresses = self.dbapi.address_get_by_name(addr_name)
for address in addresses:
if address['interface_id'] is None:
self.dbapi.address_update(address['uuid'], values)
except exception.AddressNotFoundByName:
pass
if ihost.invprovision not in [constants.PROVISIONED, constants.PROVISIONING, constants.UPGRADING]:
LOG.info("Updating %s host invprovision from %s to %s" %
@ -12265,7 +12330,7 @@ class ConductorManager(service.PeriodicService):
if not drbd_fs_updated:
rc = True
else:
while(loop_timeout <= max_loop):
while (loop_timeout <= max_loop):
if constants.DRBD_PGSQL in (drbd_fs_updated - drbd_fs_resized):
if (not standby_host or (standby_host and
constants.DRBD_PGSQL in self._drbd_fs_sync())):

View File

@ -5154,3 +5154,11 @@ class Connection(object):
@abc.abstractmethod
def kube_app_bundle_destroy_by_file_path(self, file_path):
"""Delete records from kube_app_bundle that match a file path"""
@abc.abstractmethod
def address_get_by_name_and_family(self, name, family):
""" Search database address using name and family
:param name: address name.
:param family: address family (4 or 6).
"""

View File

@ -183,6 +183,14 @@ class BasePuppet(object):
return address
def _get_address_by_name_and_family(self, name, family, networktype):
"""
Retrieve an address entry by name and scoped by network type
"""
address_name = utils.format_address_name(name, networktype)
return self.dbapi.address_get_by_name_and_family(address_name,
family)
def _get_management_address(self):
address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_MGMT)

View File

@ -6,6 +6,8 @@
import netaddr
from oslo_log import log
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import utils
@ -13,6 +15,11 @@ from sysinv.common import utils
from sysinv.puppet import base
from sysinv.puppet import interface
LOG = log.getLogger(__name__)
IPv4 = constants.IP_FAMILIES[constants.IPV4_FAMILY].lower()
IPv6 = constants.IP_FAMILIES[constants.IPV6_FAMILY].lower()
class NetworkingPuppet(base.BasePuppet):
"""Class to encapsulate puppet operations for networking configuration"""
@ -49,16 +56,7 @@ class NetworkingPuppet(base.BasePuppet):
config = self._get_network_config(networktype)
try:
gateway_address = self._get_address_by_name(
constants.CONTROLLER_GATEWAY, networktype).address
except exception.AddressNotFoundByName:
gateway_address = None
config.update({
'platform::network::%s::params::gateway_address' % networktype:
gateway_address,
})
config = self._get_network_gateway_config(networktype, config)
# create flag for the mate controller to use FQDN or not
if utils.is_fqdn_ready_to_use():
@ -100,16 +98,7 @@ class NetworkingPuppet(base.BasePuppet):
config = self._get_network_config(networktype)
try:
gateway_address = self._get_address_by_name(
constants.CONTROLLER_GATEWAY, networktype).address
except exception.AddressNotFoundByName:
gateway_address = None
config.update({
'platform::network::%s::params::gateway_address' % networktype:
gateway_address,
})
config = self._get_network_gateway_config(networktype, config)
return config
@ -132,71 +121,125 @@ class NetworkingPuppet(base.BasePuppet):
try:
network = self.dbapi.network_get_by_type(networktype)
except exception.NetworkTypeNotFound:
# network not configured
LOG.debug(f"Network type {networktype} not found")
return {}
address_pool = self.dbapi.address_pool_get(network.pool_uuid)
net_pools = self.dbapi.network_addrpool_get_by_network_id(network.id)
pool_uuid_list = list()
if net_pools:
for net_pool in net_pools:
pool_uuid_list.append(net_pool.address_pool_uuid)
else:
# we are coming from an upgrade without data-migration implemented for the
# dual stack feature
LOG.warning(f"Network {network.name} does not have network to address pool objects")
pool_uuid_list.append(network.pool_uuid)
subnet = netaddr.IPNetwork(
str(address_pool.network) + '/' + str(address_pool.prefix))
configdata = dict()
config = dict()
subnet_version = address_pool.family
subnet_network = str(subnet.network)
subnet_netmask = str(subnet.netmask)
subnet_prefixlen = subnet.prefixlen
for pool_uuid in pool_uuid_list:
subnet_start = str(address_pool.ranges[0][0])
subnet_end = str(address_pool.ranges[0][-1])
address_pool = self.dbapi.address_pool_get(pool_uuid)
try:
controller_address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, networktype).address
except exception.AddressNotFoundByName:
controller_address = None
family_name = IPv4 if address_pool.family == constants.IPV4_FAMILY else IPv6
configdata.update({family_name: {}})
try:
controller0_address = self._get_address_by_name(
constants.CONTROLLER_0_HOSTNAME, networktype).address
except exception.AddressNotFoundByName:
controller0_address = None
subnet = netaddr.IPNetwork(
str(address_pool.network) + '/' + str(address_pool.prefix))
configdata[family_name].update({'subnet': subnet})
try:
controller1_address = self._get_address_by_name(
constants.CONTROLLER_1_HOSTNAME, networktype).address
except exception.AddressNotFoundByName:
controller1_address = None
configdata[family_name].update({'subnet_version': address_pool.family})
configdata[family_name].update({'subnet_network': str(subnet.network)})
configdata[family_name].update({'subnet_netmask': str(subnet.netmask)})
configdata[family_name].update({'subnet_prefixlen': subnet.prefixlen})
configdata[family_name].update({'subnet_start': str(address_pool.ranges[0][0])})
configdata[family_name].update({'subnet_end': str(address_pool.ranges[0][-1])})
controller_address_url = self._format_url_address(controller_address)
subnet_network_url = self._format_url_address(subnet_network)
try:
controller_address = self._get_address_by_name_and_family(
constants.CONTROLLER_HOSTNAME, address_pool.family, networktype).address
except exception.AddressNotFoundByNameAndFamily:
controller_address = None
configdata[family_name].update({'controller_address': controller_address})
try:
controller0_address = self._get_address_by_name_and_family(
constants.CONTROLLER_0_HOSTNAME, address_pool.family, networktype).address
except exception.AddressNotFoundByNameAndFamily:
controller0_address = None
configdata[family_name].update({'controller0_address': controller0_address})
try:
controller1_address = self._get_address_by_name_and_family(
constants.CONTROLLER_1_HOSTNAME, address_pool.family, networktype).address
except exception.AddressNotFoundByNameAndFamily:
controller1_address = None
configdata[family_name].update({'controller1_address': controller1_address})
configdata[family_name].update({'controller_address_url':
self._format_url_address(controller_address)})
configdata[family_name].update({'subnet_network_url':
self._format_url_address(str(subnet.network))})
# Convert the dash to underscore because puppet parameters cannot have
# dashes
networktype = networktype.replace('-', '_')
return {
'platform::network::%s::params::subnet_version' % networktype:
subnet_version,
'platform::network::%s::params::subnet_network' % networktype:
subnet_network,
'platform::network::%s::params::subnet_network_url' % networktype:
subnet_network_url,
'platform::network::%s::params::subnet_prefixlen' % networktype:
subnet_prefixlen,
'platform::network::%s::params::subnet_netmask' % networktype:
subnet_netmask,
'platform::network::%s::params::subnet_start' % networktype:
subnet_start,
'platform::network::%s::params::subnet_end' % networktype:
subnet_end,
'platform::network::%s::params::controller_address' % networktype:
controller_address,
'platform::network::%s::params::controller_address_url' % networktype:
controller_address_url,
'platform::network::%s::params::controller0_address' % networktype:
controller0_address,
'platform::network::%s::params::controller1_address' % networktype:
controller1_address,
}
for family in configdata:
config[f'platform::network::{networktype}::{family}::params::subnet_version'] = \
configdata[family]['subnet_version']
config[f'platform::network::{networktype}::{family}::params::subnet_network'] = \
configdata[family]['subnet_network']
config[f'platform::network::{networktype}::{family}::params::subnet_network_url'] = \
configdata[family]['subnet_network_url']
config[f'platform::network::{networktype}::{family}::params::subnet_prefixlen'] = \
configdata[family]['subnet_prefixlen']
config[f'platform::network::{networktype}::{family}::params::subnet_netmask'] = \
configdata[family]['subnet_netmask']
config[f'platform::network::{networktype}::{family}::params::subnet_start'] = \
configdata[family]['subnet_start']
config[f'platform::network::{networktype}::{family}::params::subnet_end'] = \
configdata[family]['subnet_end']
config[f'platform::network::{networktype}::{family}::params::controller_address'] = \
configdata[family]['controller_address']
config[f'platform::network::{networktype}::{family}::params::controller_address_url'] = \
configdata[family]['controller_address_url']
config[f'platform::network::{networktype}::{family}::params::controller0_address'] = \
configdata[family]['controller0_address']
config[f'platform::network::{networktype}::{family}::params::controller1_address'] = \
configdata[family]['controller1_address']
if network.primary_pool_family \
and (network.primary_pool_family).lower() in configdata.keys():
family = network.primary_pool_family.lower()
config[f'platform::network::{networktype}::params::subnet_version'] = \
configdata[family]['subnet_version']
config[f'platform::network::{networktype}::params::subnet_network'] = \
configdata[family]['subnet_network']
config[f'platform::network::{networktype}::params::subnet_network_url'] = \
configdata[family]['subnet_network_url']
config[f'platform::network::{networktype}::params::subnet_prefixlen'] = \
configdata[family]['subnet_prefixlen']
config[f'platform::network::{networktype}::params::subnet_netmask'] = \
configdata[family]['subnet_netmask']
config[f'platform::network::{networktype}::params::subnet_start'] = \
configdata[family]['subnet_start']
config[f'platform::network::{networktype}::params::subnet_end'] = \
configdata[family]['subnet_end']
config[f'platform::network::{networktype}::params::controller_address'] = \
configdata[family]['controller_address']
config[f'platform::network::{networktype}::params::controller_address_url'] = \
configdata[family]['controller_address_url']
config[f'platform::network::{networktype}::params::controller0_address'] = \
configdata[family]['controller0_address']
config[f'platform::network::{networktype}::params::controller1_address'] = \
configdata[family]['controller1_address']
else:
LOG.error(f"Network {network.name}, type {network.type} does not have a valid"
f" primary pool address family: {network.primary_pool_family}.")
return config
def _get_pxeboot_interface_config(self):
return self._get_interface_config(constants.NETWORK_TYPE_PXEBOOT)
@ -219,6 +262,53 @@ class NetworkingPuppet(base.BasePuppet):
def _get_admin_interface_config(self):
return self._get_interface_config(constants.NETWORK_TYPE_ADMIN)
def _get_network_gateway_config(self, networktype, config):
try:
network = self.dbapi.network_get_by_type(networktype)
except exception.NetworkTypeNotFound:
LOG.debug(f"Network type {networktype} not found")
return {}
net_pools = self.dbapi.network_addrpool_get_by_network_id(network.id)
pool_uuid_list = list()
if net_pools:
for net_pool in net_pools:
pool_uuid_list.append(net_pool.address_pool_uuid)
else:
# we are coming from an upgrade without data-migration implemented for the
# dual stack feature
LOG.warning(f"Network {network.name} does not have network to address pool objects")
pool_uuid_list.append(network.pool_uuid)
configdata = dict()
for pool_uuid in pool_uuid_list:
address_pool = self.dbapi.address_pool_get(pool_uuid)
family = IPv4 if address_pool.family == constants.IPV4_FAMILY else IPv6
configdata.update({family: {}})
try:
gateway_address = self._get_address_by_name_and_family(
constants.CONTROLLER_GATEWAY, address_pool.family, networktype).address
except exception.AddressNotFoundByNameAndFamily:
gateway_address = None
configdata[family].update({'gateway_address': gateway_address})
for family in configdata:
config.update({f'platform::network::{networktype}::{family}::params::gateway_address':
configdata[family]['gateway_address']})
if network.primary_pool_family \
and (network.primary_pool_family).lower() in configdata.keys():
family = network.primary_pool_family.lower()
config.update({f'platform::network::{networktype}::params::gateway_address':
configdata[family]['gateway_address']})
else:
LOG.error(f"Network {network.name}, type {network.type} does not have a valid"
f" primary pool address family: {network.primary_pool_family}.")
return config
def _set_ptp_instance_global_parameters(self, ptp_instances, ptp_parameters_instance):
default_global_parameters = {
@ -546,10 +636,9 @@ class NetworkingPuppet(base.BasePuppet):
self.context, network_interface)
interface_devices = interface.get_interface_devices(
self.context, network_interface)
network_id = interface.find_network_id_by_networktype(
self.context, networktype)
# Convert the dash to underscore because puppet parameters cannot
# have dashes
network = self.context['networks'].get(networktype, None)
networktype = networktype.replace('-', '_')
config.update({
'platform::network::%s::params::interface_name' % networktype:
@ -560,13 +649,23 @@ class NetworkingPuppet(base.BasePuppet):
network_interface.imtu
})
interface_address = interface.get_interface_primary_address(
self.context, network_interface, network_id)
if interface_address:
addresses = self.context['addresses'].get(network_interface['ifname'], [])
for address in addresses:
family = "ipv4" if address.family == constants.IPV4_FAMILY else "ipv6"
config.update({
'platform::network::%s::params::interface_address' %
networktype:
interface_address['address']
f'platform::network::{networktype}::{family}::params::interface_address':
address.address
})
if network:
for address in addresses:
family = "ipv4" if address.family == constants.IPV4_FAMILY else "ipv6"
prim_family = network.primary_pool_family.lower()
if prim_family == family:
config.update({
f'platform::network::{networktype}::params::interface_address':
address.address
})
break
return config

View File

@ -25,6 +25,7 @@
import copy
import mock
import os.path
import netaddr
import subprocess
import tempfile
import uuid
@ -5823,6 +5824,34 @@ class ManagerTestCaseInternal(base.BaseHostTestCase):
self.service = manager.ConductorManager('test-host', 'test-topic')
self.service.dbapi = dbapi.get_instance()
def _create_test_ihost(self, **kwargs):
# ensure the system ID for proper association
kwargs['forisystemid'] = self.system['id']
ihost_dict = utils.get_test_ihost(**kwargs)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kwargs:
del ihost_dict['id']
ihost = self.service.dbapi.ihost_create(ihost_dict)
return ihost
def create_ipv6_pools(self):
mgmt_subnet6 = netaddr.IPNetwork('fd01::/64')
oam_subnet6 = netaddr.IPNetwork('fd00::/64')
cluster_host_subnet6 = netaddr.IPNetwork('fd02::/64')
cluster_pod_subnet6 = netaddr.IPNetwork('fd03::/64')
cluster_service_subnet6 = netaddr.IPNetwork('fd04::/112')
multicast_subnet6 = netaddr.IPNetwork('ff08::1:1:0/124')
storage_subnet6 = netaddr.IPNetwork('fd05::/64')
admin_subnet6 = netaddr.IPNetwork('fd09::/64')
self._create_test_address_pool(name="management-ipv6", subnet=mgmt_subnet6)
self._create_test_address_pool(name="oam-ipv6", subnet=oam_subnet6)
self._create_test_address_pool(name="cluster-host-ipv6", subnet=cluster_host_subnet6)
self._create_test_address_pool(name="cluster-pod-ipv6", subnet=cluster_pod_subnet6)
self._create_test_address_pool(name="cluster-service-ipv6", subnet=cluster_service_subnet6)
self._create_test_address_pool(name="multicast-ipv6", subnet=multicast_subnet6)
self._create_test_address_pool(name="storage-ipv6", subnet=storage_subnet6)
self._create_test_address_pool(name="admin-ipv6", subnet=admin_subnet6)
def test_remove_lease_for_address(self):
# create test interface
ihost = self._create_test_host(
@ -5868,3 +5897,138 @@ class ManagerTestCaseInternal(base.BaseHostTestCase):
self.service._remove_lease_for_address(ihost.hostname,
constants.NETWORK_TYPE_MGMT)
def test_configure_ihost_allocate_addresses_for_host(self):
# Test skipped to prevent error message in Jenkins. Error thrown is:
# in test_configure_ihost_allocate_addresses_for_host
# with open(self.dnsmasq_hosts_file, 'w') as f:
# IOError: [Errno 13] Permission denied: '/tmp/dnsmasq.hosts'
# self.skipTest("Skipping to prevent failure notification on Jenkins")
self.context = context.get_admin_context()
self.service._generate_dnsmasq_hosts_file = mock.Mock()
self.service._puppet = mock.Mock()
self.service._update_pxe_config = mock.Mock()
# create a basic ihost object
ihost = self._create_test_ihost()
self.create_ipv6_pools()
net_mgmt = self.dbapi.network_get_by_type(constants.NETWORK_TYPE_MGMT)
pool_mgmt6 = self.dbapi.address_pool_query({"name": "management-ipv6"})
pool_mgmt4 = self.dbapi.address_pool_query({"name": "management"})
dbutils.create_test_network_addrpool(address_pool_id=pool_mgmt6.id, network_id=net_mgmt.id)
net_clhost = self.dbapi.network_get_by_type(constants.NETWORK_TYPE_CLUSTER_HOST)
pool_clhost6 = self.dbapi.address_pool_query({"name": "cluster-host-ipv6"})
pool_clhost4 = self.dbapi.address_pool_query({"name": "cluster-host"})
dbutils.create_test_network_addrpool(address_pool_id=pool_clhost6.id, network_id=net_clhost.id)
worker_name = 'newhost'
ihost['mgmt_mac'] = '00:11:22:33:44:55'
ihost['hostname'] = worker_name
ihost['invprovision'] = 'unprovisioned'
ihost['personality'] = 'worker'
ihost['administrative'] = 'locked'
ihost['operational'] = 'disabled'
ihost['availability'] = 'not-installed'
ihost['serialid'] = '1234567890abc'
ihost['boot_device'] = 'sda'
ihost['rootfs_device'] = 'sda'
ihost['hw_settle'] = '0'
ihost['install_output'] = 'text'
ihost['console'] = 'ttyS0,115200'
self.service.configure_ihost(self.context, ihost)
addr_mgmt4 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_MGMT}",
constants.IPV4_FAMILY)
self.assertEqual(addr_mgmt4.pool_uuid, pool_mgmt4.uuid)
self.assertEqual(addr_mgmt4.family, pool_mgmt4.family)
addr_mgmt6 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_MGMT}",
constants.IPV6_FAMILY)
self.assertEqual(addr_mgmt6.pool_uuid, pool_mgmt6.uuid)
self.assertEqual(addr_mgmt6.family, pool_mgmt6.family)
addr_clhost4 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_CLUSTER_HOST}",
constants.IPV4_FAMILY)
self.assertEqual(addr_clhost4.pool_uuid, pool_clhost4.uuid)
addr_clhost6 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_CLUSTER_HOST}",
constants.IPV6_FAMILY)
self.assertEqual(addr_clhost6.pool_uuid, pool_clhost6.uuid)
def test_configure_ihost_allocate_addresses_for_host_no_net_pool_object(self):
# the data-migration for upgrade was not implemented yet for the dual-stack feature
# this test aims to validate this condition
# self.skipTest("Skipping to prevent failure notification on Jenkins")
self.context = context.get_admin_context()
self.service._generate_dnsmasq_hosts_file = mock.Mock()
self.service._puppet = mock.Mock()
self.service._update_pxe_config = mock.Mock()
# create a basic ihost object
ihost = self._create_test_ihost()
self.create_ipv6_pools()
pool_mgmt4 = self.dbapi.address_pool_query({"name": "management"})
pool_clhost4 = self.dbapi.address_pool_query({"name": "cluster-host"})
net_pools = self.dbapi.network_addrpool_get_all()
for net_pool in net_pools:
self.dbapi.network_addrpool_destroy(net_pool.uuid)
worker_name = 'newhost'
ihost['mgmt_mac'] = '00:11:22:33:44:55'
ihost['hostname'] = worker_name
ihost['invprovision'] = 'unprovisioned'
ihost['personality'] = 'worker'
ihost['administrative'] = 'locked'
ihost['operational'] = 'disabled'
ihost['availability'] = 'not-installed'
ihost['serialid'] = '1234567890abc'
ihost['boot_device'] = 'sda'
ihost['rootfs_device'] = 'sda'
ihost['hw_settle'] = '0'
ihost['install_output'] = 'text'
ihost['console'] = 'ttyS0,115200'
self.assertRaises(exception.AddressNotFoundByNameAndFamily,
self.dbapi.address_get_by_name_and_family,
f"{worker_name}-{constants.NETWORK_TYPE_MGMT}",
constants.IPV4_FAMILY)
self.assertRaises(exception.AddressNotFoundByNameAndFamily,
self.dbapi.address_get_by_name_and_family,
f"{worker_name}-{constants.NETWORK_TYPE_CLUSTER_HOST}",
constants.IPV6_FAMILY)
self.service.configure_ihost(self.context, ihost)
addr_mgmt4 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_MGMT}",
constants.IPV4_FAMILY)
self.assertEqual(addr_mgmt4.pool_uuid, pool_mgmt4.uuid)
self.assertEqual(addr_mgmt4.family, pool_mgmt4.family)
self.assertRaises(exception.AddressNotFoundByNameAndFamily,
self.dbapi.address_get_by_name_and_family,
f"{worker_name}-{constants.NETWORK_TYPE_MGMT}",
constants.IPV6_FAMILY)
addr_clhost4 = self.dbapi.address_get_by_name_and_family(
f"{worker_name}-{constants.NETWORK_TYPE_CLUSTER_HOST}",
constants.IPV4_FAMILY)
self.assertEqual(addr_clhost4.pool_uuid, pool_clhost4.uuid)
self.assertRaises(exception.AddressNotFoundByNameAndFamily,
self.dbapi.address_get_by_name_and_family,
f"{worker_name}-{constants.NETWORK_TYPE_CLUSTER_HOST}",
constants.IPV6_FAMILY)

View File

@ -0,0 +1,677 @@
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import uuid
import os
import yaml
import netaddr
from sysinv.tests.puppet import base
from sysinv.puppet import puppet
from sysinv.tests.db import base as dbbase
from sysinv.common import constants
from sysinv.tests.db import utils as dbutils
from sysinv.db import api as db_api
class NetworkingTestCaseMixin(base.PuppetTestCaseMixin):
""" This PlatformFirewallTestCaseMixin needs to be used with a subclass
of BaseHostTestCase
"""
@puppet.puppet_context
def _update_context(self):
# interface is added as an operator by systemconfig.puppet_plugins
self.context = self.operator.interface._create_interface_context(self.host) # pylint: disable=no-member
# Update the puppet context with generated interface context
self.operator.context.update(self.context)
def _setup_context(self):
self.ports = []
self.interfaces = []
self.addresses = []
self.routes = []
self._setup_configuration()
self._update_context()
def _setup_configuration(self):
pass
def _create_hieradata_directory(self):
hiera_path = os.path.join(os.environ['VIRTUAL_ENV'], 'hieradata')
if not os.path.exists(hiera_path):
os.mkdir(hiera_path, 0o755)
return hiera_path
def _get_config_filename(self, hiera_directory):
class_name = self.__class__.__name__
return os.path.join(hiera_directory, class_name) + ".yaml"
def _find_network_by_type(self, networktype):
for network in self.networks:
if network['type'] == networktype:
return network
def _get_network_ids_by_type(self, networktype):
if isinstance(networktype, list):
networktypelist = networktype
elif networktype:
networktypelist = [networktype]
else:
networktypelist = []
networks = []
for network_type in networktypelist:
network = self._find_network_by_type(network_type)
networks.append(str(network['id']))
return networks
def _create_ethernet_test(self, ifname=None, ifclass=None,
networktype=None, host_id=None, **kwargs):
if not host_id:
host_id = self.host.id
interface_id = len(self.interfaces)
if not ifname:
ifname = (networktype or 'eth') + str(interface_id)
if not ifclass:
ifclass = constants.INTERFACE_CLASS_NONE
if ifclass == constants.INTERFACE_CLASS_PLATFORM:
networks = self._get_network_ids_by_type(networktype)
else:
networks = []
interface = {'id': interface_id,
'uuid': str(uuid.uuid4()),
'forihostid': host_id,
'ifname': ifname,
'iftype': constants.INTERFACE_TYPE_ETHERNET,
'imac': '02:11:22:33:44:' + str(10 + interface_id),
'uses': [],
'used_by': [],
'ifclass': ifclass,
'networks': networks,
'networktype': networktype,
'imtu': 1500,
'sriov_numvfs': kwargs.get('sriov_numvfs', 0),
'sriov_vf_driver': kwargs.get('iface_sriov_vf_driver', None)}
db_interface = dbutils.create_test_interface(**interface)
for network in networks:
dbutils.create_test_interface_network_assign(db_interface['id'], network)
self.interfaces.append(db_interface)
port_id = len(self.ports)
port = {'id': port_id,
'uuid': str(uuid.uuid4()),
'name': 'eth' + str(port_id),
'interface_id': interface_id,
'host_id': host_id,
'mac': interface['imac'],
'driver': kwargs.get('driver', 'ixgbe'),
'dpdksupport': kwargs.get('dpdksupport', True),
'pdevice': kwargs.get('pdevice',
"Ethernet Controller X710 for 10GbE SFP+ [1572]"),
'pciaddr': kwargs.get('pciaddr',
'0000:00:00.' + str(port_id + 1)),
'dev_id': kwargs.get('dev_id', 0),
'sriov_vf_driver': kwargs.get('port_sriov_vf_driver', None),
'sriov_vf_pdevice_id': kwargs.get('sriov_vf_pdevice_id', None),
'sriov_vfs_pci_address': kwargs.get('sriov_vfs_pci_address', '')}
db_port = dbutils.create_test_ethernet_port(**port)
self.ports.append(db_port)
return db_port, db_interface
class NetworkingTestTestCaseControllerDualStackIPv4Primary(NetworkingTestCaseMixin,
dbbase.BaseHostTestCase):
def __init__(self, *args, **kwargs):
super(NetworkingTestTestCaseControllerDualStackIPv4Primary, self).__init__(*args, **kwargs)
self.test_interfaces = dict()
def setUp(self):
super(NetworkingTestTestCaseControllerDualStackIPv4Primary, self).setUp()
self.dbapi = db_api.get_instance()
self._setup_context()
def _update_context(self):
# ensure DB entries are updated prior to updating the context which
# will re-read the entries from the DB.
self.host.save(self.admin_context)
super(NetworkingTestTestCaseControllerDualStackIPv4Primary, self)._update_context()
def _setup_configuration(self):
# Create a single port/interface for basic function testing
print("=== _setup_configuration")
self.host = self._create_test_host(personality=constants.CONTROLLER)
_, c0_oam = self._create_ethernet_test("oam0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_OAM, self.host.id)
_, c0_mgmt = self._create_ethernet_test("mgmt0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_MGMT, self.host.id)
_, c0_clhost = self._create_ethernet_test("cluster0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_CLUSTER_HOST, self.host.id)
_, c0_pxe = self._create_ethernet_test("pxe0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_PXEBOOT, self.host.id)
self.host_c1 = self._create_test_host(personality=constants.CONTROLLER,
unit=1)
port, c1_oam = self._create_ethernet_test("oam0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_OAM, self.host_c1.id)
port, c1_mgmt = self._create_ethernet_test("mgmt0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_MGMT, self.host_c1.id)
port, c1_clhost = self._create_ethernet_test("cluster0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_CLUSTER_HOST, self.host_c1.id)
port, c1_pxe = self._create_ethernet_test("pxe0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_PXEBOOT, self.host_c1.id)
self.create_ipv6_pools()
# associate addresses with its interfaces
addresses = self.dbapi.addresses_get_all()
for addr in addresses:
for hostname in [self.host.hostname, self.host_c1.hostname]:
if addr.name == f"{hostname}-{constants.NETWORK_TYPE_OAM}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_oam.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_oam.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_MGMT}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_mgmt.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_mgmt.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_CLUSTER_HOST}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_clhost.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_clhost.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_PXEBOOT}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_pxe.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_pxe.id}
self.dbapi.address_update(addr.uuid, values)
# associate addresses with its pools
for net_type in [constants.NETWORK_TYPE_OAM,
constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT]:
net = self.dbapi.network_get_by_type(net_type)
net_pools = self.dbapi.network_addrpool_get_by_network_id(net.id)
for net_pool in net_pools:
address_pool = self.dbapi.address_pool_get(net_pool.address_pool_uuid)
addresses = self.dbapi.addresses_get_all()
for addr in addresses:
if (addr.name.endswith(f"-{net_type}")) \
and (addr.family == address_pool.family):
values = {'address_pool_id': address_pool.id}
self.dbapi.address_update(addr.uuid, values)
def create_ipv6_pools(self):
to_add = [
(constants.NETWORK_TYPE_MGMT, (netaddr.IPNetwork('fd01::/64'),
'management-ipv6')),
(constants.NETWORK_TYPE_OAM, (netaddr.IPNetwork('fd00::/64'),
'oam-ipv6')),
(constants.NETWORK_TYPE_ADMIN, (netaddr.IPNetwork('fd09::/64'),
'admin-ipv6')),
(constants.NETWORK_TYPE_CLUSTER_HOST, (netaddr.IPNetwork('fd03::/64'),
'cluster-host-ipv6')),
(constants.NETWORK_TYPE_CLUSTER_POD, (netaddr.IPNetwork('fd03::/64'),
'cluster-pod-ipv6')),
(constants.NETWORK_TYPE_CLUSTER_SERVICE, (netaddr.IPNetwork('fd04::/112'),
'cluster-service-ipv6')),
(constants.NETWORK_TYPE_STORAGE, (netaddr.IPNetwork('fd05::/64'),
'storage-ipv6'))
]
hosts = [constants.CONTROLLER_HOSTNAME,
constants.CONTROLLER_0_HOSTNAME,
constants.CONTROLLER_1_HOSTNAME]
for cfgdata in to_add:
net = self.dbapi.network_get_by_type(cfgdata[0])
pool = self._create_test_address_pool(name=cfgdata[1][1],
subnet=cfgdata[1][0])
network_addrpool = dbutils.create_test_network_addrpool(address_pool_id=pool.id,
network_id=net.id)
self._create_test_addresses(hostnames=hosts, subnet=cfgdata[1][0],
network_type=cfgdata[0], start=2)
if cfgdata[0] in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_OAM]:
self._create_test_addresses(hostnames=[constants.CONTROLLER_GATEWAY],
subnet=cfgdata[1][0],
network_type=cfgdata[0], start=1, stop=2)
self.network_addrpools.append(network_addrpool)
def test_generate_networking_host_config(self):
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_host_config(self.host) # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv4', 'ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["interface_address"]:
test_key = f'platform::network::{type}::{family}::params::interface_address'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
for field in ["interface_address", "interface_devices", "interface_name", "mtu"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# ipv4 is the primary, chack the addresses match
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
self.assertEqual(hiera_data[f'platform::network::{type}::params::interface_address'],
hiera_data[f'platform::network::{type}::ipv4::params::interface_address'])
def test_generate_networking_system_config(self):
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_system_config() # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv4', 'ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::{family}::params::{field}'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
# check the primary pool (no family indication) presence
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# check if the the primary pool subnet_version is with the correct value
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertEqual(constants.IPV4_FAMILY, hiera_data[test_key])
class NetworkingTestTestCaseControllerDualStackIPv6Primary(NetworkingTestCaseMixin,
dbbase.BaseIPv6Mixin,
dbbase.BaseHostTestCase):
def __init__(self, *args, **kwargs):
super(NetworkingTestTestCaseControllerDualStackIPv6Primary, self).__init__(*args, **kwargs)
self.test_interfaces = dict()
def setUp(self):
super(NetworkingTestTestCaseControllerDualStackIPv6Primary, self).setUp()
self.dbapi = db_api.get_instance()
self._setup_context()
def _update_context(self):
# ensure DB entries are updated prior to updating the context which
# will re-read the entries from the DB.
self.host.save(self.admin_context)
super(NetworkingTestTestCaseControllerDualStackIPv6Primary, self)._update_context()
def create_ipv4_pools(self):
to_add = [
(constants.NETWORK_TYPE_MGMT, (netaddr.IPNetwork('192.168.204.0/24'),
'management-ipv4')),
(constants.NETWORK_TYPE_OAM, (netaddr.IPNetwork('10.10.10.0/24'),
'oam-ipv4')),
(constants.NETWORK_TYPE_ADMIN, (netaddr.IPNetwork('10.10.30.0/24'),
'admin-ipv4')),
(constants.NETWORK_TYPE_CLUSTER_HOST, (netaddr.IPNetwork('192.168.206.0/24'),
'cluster-host-ipv4')),
(constants.NETWORK_TYPE_CLUSTER_POD, (netaddr.IPNetwork('172.16.0.0/16'),
'cluster-pod-ipv4')),
(constants.NETWORK_TYPE_CLUSTER_SERVICE, (netaddr.IPNetwork('10.96.0.0/12'),
'cluster-service-ipv4')),
(constants.NETWORK_TYPE_STORAGE, (netaddr.IPNetwork('10.10.20.0/24'),
'storage-ipv4'))
]
hosts = [constants.CONTROLLER_HOSTNAME,
constants.CONTROLLER_0_HOSTNAME,
constants.CONTROLLER_1_HOSTNAME]
for cfgdata in to_add:
net = self.dbapi.network_get_by_type(cfgdata[0])
pool = self._create_test_address_pool(name=cfgdata[1][1],
subnet=cfgdata[1][0])
network_addrpool = dbutils.create_test_network_addrpool(address_pool_id=pool.id,
network_id=net.id)
self._create_test_addresses(hostnames=hosts, subnet=cfgdata[1][0],
network_type=cfgdata[0], start=2)
if cfgdata[0] in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_OAM]:
self._create_test_addresses(hostnames=[constants.CONTROLLER_GATEWAY],
subnet=cfgdata[1][0],
network_type=cfgdata[0], start=1, stop=2)
self.network_addrpools.append(network_addrpool)
def _setup_configuration(self):
self.host = self._create_test_host(personality=constants.CONTROLLER)
_, c0_oam = self._create_ethernet_test("oam0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_OAM, self.host.id)
_, c0_mgmt = self._create_ethernet_test("mgmt0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_MGMT, self.host.id)
_, c0_clhost = self._create_ethernet_test("cluster0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_CLUSTER_HOST, self.host.id)
_, c0_pxe = self._create_ethernet_test("pxe0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_PXEBOOT, self.host.id)
self.host_c1 = self._create_test_host(personality=constants.CONTROLLER,
unit=1)
_, c1_oam = self._create_ethernet_test("oam0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_OAM, self.host_c1.id)
_, c1_mgmt = self._create_ethernet_test("mgmt0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_MGMT, self.host_c1.id)
_, c1_clhost = self._create_ethernet_test("cluster0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_CLUSTER_HOST, self.host_c1.id)
_, c1_pxe = self._create_ethernet_test("pxe0",
constants.INTERFACE_CLASS_PLATFORM,
constants.NETWORK_TYPE_PXEBOOT, self.host_c1.id)
self.create_ipv4_pools()
# associate addresses with its interfaces
addresses = self.dbapi.addresses_get_all()
for addr in addresses:
for hostname in [self.host.hostname, self.host_c1.hostname]:
if addr.name == f"{hostname}-{constants.NETWORK_TYPE_OAM}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_oam.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_oam.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_MGMT}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_mgmt.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_mgmt.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_CLUSTER_HOST}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_clhost.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_clhost.id}
self.dbapi.address_update(addr.uuid, values)
elif addr.name == f"{hostname}-{constants.NETWORK_TYPE_PXEBOOT}":
if hostname == constants.CONTROLLER_0_HOSTNAME:
values = {'interface_id': c0_pxe.id}
self.dbapi.address_update(addr.uuid, values)
elif hostname == constants.CONTROLLER_1_HOSTNAME:
values = {'interface_id': c1_pxe.id}
self.dbapi.address_update(addr.uuid, values)
# associate addresses with its pools
for net_type in [constants.NETWORK_TYPE_OAM,
constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT]:
net = self.dbapi.network_get_by_type(net_type)
net_pools = self.dbapi.network_addrpool_get_by_network_id(net.id)
for net_pool in net_pools:
address_pool = self.dbapi.address_pool_get(net_pool.address_pool_uuid)
addresses = self.dbapi.addresses_get_all()
for addr in addresses:
if (addr.name.endswith(f"-{net_type}")) \
and (addr.family == address_pool.family):
values = {'address_pool_id': address_pool.id}
self.dbapi.address_update(addr.uuid, values)
def test_generate_networking_system_config(self):
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_system_config() # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv4', 'ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::{family}::params::{field}'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
# check the primary pool (no family indication) presence
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# check if the the primary pool subnet_version is with the correct value
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertEqual(constants.IPV6_FAMILY, hiera_data[test_key])
def test_generate_networking_system_config_no_net_pool_object(self):
"""This test aims to validate if a system can operate without network-addrpool
objects since this can happen if an upgrade is executed and the data-migration
for the diual-stack feature is not implemented yet;
"""
net_pools = self.dbapi.network_addrpool_get_all()
for net_pool in net_pools:
self.dbapi.network_addrpool_destroy(net_pool.uuid)
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_system_config() # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::{family}::params::{field}'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
# check the primary pool (no family indication) presence
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["controller0_address", "controller1_address", "controller_address",
"controller_address_url", "subnet_end", "subnet_netmask", "subnet_network",
"subnet_network_url", "subnet_prefixlen", "subnet_start", "subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# check if the the primary pool subnet_version is with the correct value
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_ADMIN,
constants.NETWORK_TYPE_CLUSTER_HOST, constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_OAM]:
for field in ["subnet_version"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertEqual(constants.IPV6_FAMILY, hiera_data[test_key])
def test_generate_networking_host_config(self):
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_host_config(self.host) # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv4', 'ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["interface_address"]:
test_key = f'platform::network::{type}::{family}::params::interface_address'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
for field in ["interface_address", "interface_devices", "interface_name", "mtu"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# ipv6 is the primary, check the addresses match
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
self.assertEqual(hiera_data[f'platform::network::{type}::params::interface_address'],
hiera_data[f'platform::network::{type}::ipv6::params::interface_address'])
def test_generate_networking_host_config_no_net_pool_objects(self):
"""This test aims to validate if a system can operate without network-addrpool
objects since this can happen if an upgrade is executed and the data-migration
for the diual-stack feature is not implemented yet;
"""
net_pools = self.dbapi.network_addrpool_get_all()
for net_pool in net_pools:
self.dbapi.network_addrpool_destroy(net_pool.uuid)
hieradata_directory = self._create_hieradata_directory()
config_filename = self._get_config_filename(hieradata_directory)
with open(config_filename, 'w') as config_file:
config = self.operator.networking.get_host_config(self.host) # pylint: disable=no-member
yaml.dump(config, config_file, default_flow_style=False)
print(config_filename)
hiera_data = dict()
with open(config_filename, 'r') as config_file:
hiera_data = yaml.safe_load(config_file)
for family in ['ipv4', 'ipv6']:
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
type = net_type.replace('-', '_')
for field in ["interface_address"]:
test_key = f'platform::network::{type}::{family}::params::interface_address'
if net_type == constants.NETWORK_TYPE_PXEBOOT and family == 'ipv6':
# there are no ipv6 allocations for pxe
self.assertNotIn(test_key, hiera_data.keys())
else:
self.assertIn(test_key, hiera_data.keys())
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
for field in ["interface_address", "interface_devices", "interface_name", "mtu"]:
test_key = f'platform::network::{type}::params::{field}'
self.assertIn(test_key, hiera_data.keys())
# ipv6 is the primary, check the addresses match
for net_type in [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_OAM]:
self.assertEqual(hiera_data[f'platform::network::{type}::params::interface_address'],
hiera_data[f'platform::network::{type}::ipv6::params::interface_address'])