Calculate undercloud ctlplane DHCP allocation pools

* Make dhcp_start and dhcp_end optional for subnet definitions
  in undercloud.conf.
* Allow non-contiguous allocation pools for ctlplane subnets

Calcualte the allocation pools by removing the local_ip,
gateway, admin_host, public_host and ``inspection_iprange``,
from the subnets full ip range. Allocation_pools for all
remaining ranges will be configured. A new per-subnet option
``dhcp_exclude`` is added, a list of IP addresses or IP
ranges that will be excluded from the allocation pool. For
example:

  dhcp_exclude = 172.20.0.101,172.20.0.210-172.20.0.219
                   ^ ip addr         ^ ip range

If dhcp_start is defined and dhcp_end is not defined (or vice
versa) any addresses prior to (or after) this address is
removed from the allocation pools.

Make dhcp_start and dhcp_end options ListOpts to enable non-
contigous allocation pools. For example, to create allocation
pools: [{'start': '172.20.0.100', 'end': '172.20.0.150'},
        {'start': '172.20.0.200', 'end': '172.20.0.250'}]
the following configuration can be used in undercloud.conf:

  dhcp_start = 172.20.0.100,172.20.0.200
  dhcp_end = 172.20.0.150,172.20.0.250

A new method is added for remote_subnet_opts, same options as
for the local_subnet_opts but without the defaults.

To allow optional dhcp_start and dhcp_end for the local_subnet
which have defaults defined, a condition is used to ignore
dhcp_start and dhcp_end in case they are the default values
and the cidr is NOT the default.

Related-Bug: #1806512
Related-Bug: #1807707
Change-Id: I4ba148f465b4c452bd5b2c31009ac8a2897bcd5f
This commit is contained in:
Harald Jensås 2018-12-04 15:41:41 +01:00
parent ce9a49fe68
commit 8355e76ca4
8 changed files with 505 additions and 69 deletions

View File

@ -0,0 +1,33 @@
---
features:
- |
The ``dhcp_start`` and ``dhcp_end`` options are now optional for subnet
definitions in the Undercloud configuration (``undercloud.conf``).
The the allocation_pools are calculated by removing the ``local_ip``,
``gateway``, ``undercloud_admin_host``, ``undercloud_public_host`` and
``inspection_iprange`` from the subnets full IP range. Allocation pools for
all remaining ranges will be configured. Additionally the new option
``dhcp_exlcude`` can be used to exclude additional IP addresses and/or
IP address ranges, for example to exclude ``172.20.0.105`` and the range
``172.20.0.210-172.20.0.219``::
dhcp_exclude = 172.20.0.105,172.20.0.210-172.20.0.219
* When ``dhcp_start`` is defined any addresses prior to this address is
also removed from the allocation pools.
* When ``dhcp_end`` is defined any addresses after this address is also
removed from the allocation pools.
.. Note:: If the default cidr (``192.168.24.0/24``) is used for the local
subnet the ``dhcp_start`` and ``dhcp_end`` cannot simply be
removed to utilize the full address space of the subnet. This due
to the default values of ``dhcp_start`` and ``dhcp_end``.
- |
It is now possible to configure non-contiguous allocation pools for the
Undercloud ctlplane subnets. The ``dhcp_start`` and ``dhcp_end`` options
have been extended to allow a list of start and end address pairs. For
example to create allocation pools ``172.20.0.100-172.20.0.150`` and
``172.20.0.200-172.20.0.250``::
dhcp_start = 172.20.0.100,172.20.0.200
dhcp_end = 172.20.0.150,172.20.0.250

View File

@ -27,6 +27,27 @@ CONF = cfg.CONF
# Control plane network name # Control plane network name
SUBNETS_DEFAULT = ['ctlplane-subnet'] SUBNETS_DEFAULT = ['ctlplane-subnet']
CIDR_HELP_STR = _(
'Network CIDR for the Neutron-managed subnet for Overcloud instances.')
DHCP_START_HELP_STR = _(
'Start of DHCP allocation range for PXE and DHCP of Overcloud instances '
'on this network.')
DHCP_END_HELP_STR = _(
'End of DHCP allocation range for PXE and DHCP of Overcloud instances on '
'this network.')
DHCP_EXCLUDE_HELP_STR = _(
'List of IP addresses or IP ranges to exclude from the subnets allocation '
'pool. Example: 192.168.24.50,192.168.24.80-192.168.24.90')
INSPECTION_IPRANGE_HELP_STR = _(
'Temporary IP range that will be given to nodes on this network during '
'the inspection process. Should not overlap with the range defined by '
'dhcp_start and dhcp_end, but should be in the same ip subnet.')
GATEWAY_HELP_STR = _(
'Network gateway for the Neutron-managed network for Overcloud instances '
'on this network.')
MASQUERADE_HELP_STR = _(
'The network will be masqueraded for external access.')
# Deprecated options # Deprecated options
_deprecated_opt_network_gateway = [cfg.DeprecatedOpt( _deprecated_opt_network_gateway = [cfg.DeprecatedOpt(
'network_gateway', group='DEFAULT')] 'network_gateway', group='DEFAULT')]
@ -314,46 +335,57 @@ class UndercloudConfig(StandaloneConfig):
_service_opts = self.get_undercloud_service_opts() _service_opts = self.get_undercloud_service_opts()
return self.sort_opts(_base_opts + _service_opts) return self.sort_opts(_base_opts + _service_opts)
def get_subnet_opts(self): def get_local_subnet_opts(self):
_subnets_opts = [ _subnets_opts = [
cfg.StrOpt('cidr', cfg.StrOpt('cidr',
default='192.168.24.0/24', default=constants.CTLPLANE_CIDR_DEFAULT,
deprecated_opts=_deprecated_opt_network_cidr, deprecated_opts=_deprecated_opt_network_cidr,
help=_( help=CIDR_HELP_STR),
'Network CIDR for the Neutron-managed subnet for ' cfg.ListOpt('dhcp_start',
'Overcloud instances.')), default=constants.CTLPLANE_DHCP_START_DEFAULT,
cfg.StrOpt('dhcp_start', deprecated_opts=_deprecated_opt_dhcp_start,
default='192.168.24.5', help=DHCP_START_HELP_STR),
deprecated_opts=_deprecated_opt_dhcp_start, cfg.ListOpt('dhcp_end',
help=_( default=constants.CTLPLANE_DHCP_END_DEFAULT,
'Start of DHCP allocation range for PXE and DHCP ' deprecated_opts=_deprecated_opt_dhcp_end,
'of Overcloud instances on this network.')), help=DHCP_END_HELP_STR),
cfg.StrOpt('dhcp_end', cfg.ListOpt('dhcp_exclude',
default='192.168.24.24', default=[],
deprecated_opts=_deprecated_opt_dhcp_end, help=DHCP_EXCLUDE_HELP_STR),
help=_('End of DHCP allocation range for PXE and DHCP '
'of Overcloud instances on this network.')),
cfg.StrOpt('inspection_iprange', cfg.StrOpt('inspection_iprange',
default='192.168.24.100,192.168.24.120', default=constants.CTLPLANE_INSPECTION_IPRANGE_DEFAULT,
deprecated_opts=_deprecated_opt_inspection_iprange, deprecated_opts=_deprecated_opt_inspection_iprange,
help=_( help=INSPECTION_IPRANGE_HELP_STR),
'Temporary IP range that will be given to nodes on '
'this network during the inspection process. '
'Should not overlap with the range defined by '
'dhcp_start and dhcp_end, but should be in the '
'same ip subnet.'
)),
cfg.StrOpt('gateway', cfg.StrOpt('gateway',
default='192.168.24.1', default=constants.CTLPLANE_GATEWAY_DEFAULT,
deprecated_opts=_deprecated_opt_network_gateway, deprecated_opts=_deprecated_opt_network_gateway,
help=_( help=GATEWAY_HELP_STR),
'Network gateway for the Neutron-managed network '
'for Overcloud instances on this network.')),
cfg.BoolOpt('masquerade', cfg.BoolOpt('masquerade',
default=False, default=False,
help=_( help=MASQUERADE_HELP_STR),
'The network will be masqueraded for external ' ]
'access.')), return self.sort_opts(_subnets_opts)
def get_remote_subnet_opts(self):
_subnets_opts = [
cfg.StrOpt('cidr',
help=CIDR_HELP_STR),
cfg.ListOpt('dhcp_start',
default=[],
help=DHCP_START_HELP_STR),
cfg.ListOpt('dhcp_end',
default=[],
help=DHCP_END_HELP_STR),
cfg.ListOpt('dhcp_exclude',
default=[],
help=DHCP_EXCLUDE_HELP_STR),
cfg.StrOpt('inspection_iprange',
help=INSPECTION_IPRANGE_HELP_STR),
cfg.StrOpt('gateway',
help=GATEWAY_HELP_STR),
cfg.BoolOpt('masquerade',
default=False,
help=MASQUERADE_HELP_STR),
] ]
return self.sort_opts(_subnets_opts) return self.sort_opts(_subnets_opts)
@ -363,7 +395,8 @@ def list_opts():
config = UndercloudConfig() config = UndercloudConfig()
_opts = config.get_opts() _opts = config.get_opts()
return [(None, copy.deepcopy(_opts)), return [(None, copy.deepcopy(_opts)),
(SUBNETS_DEFAULT[0], copy.deepcopy(config.get_subnet_opts()))] (SUBNETS_DEFAULT[0],
copy.deepcopy(config.get_local_subnet_opts()))]
def load_global_config(): def load_global_config():

View File

@ -82,3 +82,10 @@ ANSIBLE_VALIDATION_DIR = '/usr/share/openstack-tripleo-validations/validations'
# The path to the local CA certificate installed on the undercloud # The path to the local CA certificate installed on the undercloud
LOCAL_CACERT_PATH = '/etc/pki/ca-trust/source/anchors/cm-local-ca.pem' LOCAL_CACERT_PATH = '/etc/pki/ca-trust/source/anchors/cm-local-ca.pem'
# ctlplane network defaults
CTLPLANE_CIDR_DEFAULT = '192.168.24.0/24'
CTLPLANE_DHCP_START_DEFAULT = ['192.168.24.5']
CTLPLANE_DHCP_END_DEFAULT = ['192.168.24.24']
CTLPLANE_INSPECTION_IPRANGE_DEFAULT = '192.168.24.100,192.168.24.120'
CTLPLANE_GATEWAY_DEFAULT = '192.168.24.1'

View File

@ -138,13 +138,18 @@ class TestUndercloudConfig(base.TestCase):
self.assertEqual(expected, [x.name for x in ret]) self.assertEqual(expected, [x.name for x in ret])
def test_get_subnet_opts(self): def test_get_subnet_opts(self):
ret = self.config.get_subnet_opts()
expected = ['cidr', expected = ['cidr',
'dhcp_end', 'dhcp_end',
'dhcp_exclude',
'dhcp_start', 'dhcp_start',
'gateway', 'gateway',
'inspection_iprange', 'inspection_iprange',
'masquerade'] 'masquerade']
ret = self.config.get_local_subnet_opts()
self.assertEqual(expected, [x.name for x in ret])
ret = self.config.get_remote_subnet_opts()
self.assertEqual(expected, [x.name for x in ret]) self.assertEqual(expected, [x.name for x in ret])
def test_get_undercloud_service_opts(self): def test_get_undercloud_service_opts(self):

View File

@ -123,8 +123,9 @@ class TestNetworkSettings(base.TestCase):
self.grp0 = cfg.OptGroup(name='ctlplane-subnet', self.grp0 = cfg.OptGroup(name='ctlplane-subnet',
title='ctlplane-subnet') title='ctlplane-subnet')
self.opts = [cfg.StrOpt('cidr'), self.opts = [cfg.StrOpt('cidr'),
cfg.StrOpt('dhcp_start'), cfg.ListOpt('dhcp_start'),
cfg.StrOpt('dhcp_end'), cfg.ListOpt('dhcp_end'),
cfg.ListOpt('dhcp_exclude'),
cfg.StrOpt('inspection_iprange'), cfg.StrOpt('inspection_iprange'),
cfg.StrOpt('gateway'), cfg.StrOpt('gateway'),
cfg.BoolOpt('masquerade')] cfg.BoolOpt('masquerade')]
@ -134,6 +135,7 @@ class TestNetworkSettings(base.TestCase):
self.conf.config(cidr='192.168.24.0/24', self.conf.config(cidr='192.168.24.0/24',
dhcp_start='192.168.24.5', dhcp_start='192.168.24.5',
dhcp_end='192.168.24.24', dhcp_end='192.168.24.24',
dhcp_exclude=[],
inspection_iprange='192.168.24.100,192.168.24.120', inspection_iprange='192.168.24.100,192.168.24.120',
gateway='192.168.24.1', gateway='192.168.24.1',
masquerade=False, masquerade=False,
@ -153,12 +155,180 @@ class TestNetworkSettings(base.TestCase):
'MasqueradeNetworks': {}, 'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': { 'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': { 'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.5', 'end': '192.168.24.24'}],
'DhcpRangeEnd': '192.168.24.24', 'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5', 'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.24.0/24', 'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}}} 'NetworkGateway': '192.168.24.1'}}}
self.assertEqual(expected, env) self.assertEqual(expected, env)
def test_start_end_all_addresses(self):
self.conf.config(dhcp_start='192.168.24.0',
dhcp_end='192.168.24.255',
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.4', 'end': '192.168.24.99'},
{'start': '192.168.24.121', 'end': '192.168.24.254'}],
'DhcpRangeEnd': '192.168.24.255',
'DhcpRangeStart': '192.168.24.0',
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}}}
self.assertEqual(expected, env)
def test_ignore_dhcp_start_end_if_default_but_cidr_not_default(self):
self.conf.config(cidr='192.168.10.0/24',
inspection_iprange='192.168.10.100,192.168.10.120',
gateway='192.168.10.1',
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.10.1',
'ip_range': '192.168.10.100,192.168.10.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.10.2', 'end': '192.168.10.99'},
{'start': '192.168.10.121', 'end': '192.168.10.254'}],
'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.1'}}}
self.assertEqual(expected, env)
def test_dhcp_exclude(self):
self.conf.config(cidr='192.168.10.0/24',
inspection_iprange='192.168.10.100,192.168.10.120',
gateway='192.168.10.1',
dhcp_exclude=['192.168.10.50',
'192.168.10.80-192.168.10.89'],
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.10.1',
'ip_range': '192.168.10.100,192.168.10.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.10.2', 'end': '192.168.10.49'},
{'start': '192.168.10.51', 'end': '192.168.10.79'},
{'start': '192.168.10.90', 'end': '192.168.10.99'},
{'start': '192.168.10.121', 'end': '192.168.10.254'}],
'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.1'}}}
self.assertEqual(expected, env)
def test_no_dhcp_start_no_dhcp_end(self):
self.conf.config(dhcp_start=[],
dhcp_end=[],
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.4', 'end': '192.168.24.99'},
{'start': '192.168.24.121', 'end': '192.168.24.254'}],
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}}}
self.assertEqual(expected, env)
def test_dhcp_start_no_dhcp_end(self):
self.conf.config(dhcp_start='192.168.24.10',
dhcp_end=[],
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.10', 'end': '192.168.24.99'},
{'start': '192.168.24.121', 'end': '192.168.24.254'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeStart': '192.168.24.10',
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}}
}
self.assertEqual(expected, env)
def test_dhcp_end_no_dhcp_start(self):
self.conf.config(dhcp_start=[],
dhcp_end='192.168.24.220',
group='ctlplane-subnet')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'}],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.4', 'end': '192.168.24.99'},
{'start': '192.168.24.121', 'end': '192.168.24.220'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeEnd': '192.168.24.220',
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}}
}
self.assertEqual(expected, env)
def test_routed_network(self): def test_routed_network(self):
self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2']) self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
self.conf.register_opts(self.opts, group=self.grp1) self.conf.register_opts(self.opts, group=self.grp1)
@ -168,6 +338,7 @@ class TestNetworkSettings(base.TestCase):
self.conf.config(cidr='192.168.10.0/24', self.conf.config(cidr='192.168.10.0/24',
dhcp_start='192.168.10.10', dhcp_start='192.168.10.10',
dhcp_end='192.168.10.99', dhcp_end='192.168.10.99',
dhcp_exclude=[],
inspection_iprange='192.168.10.100,192.168.10.189', inspection_iprange='192.168.10.100,192.168.10.189',
gateway='192.168.10.254', gateway='192.168.10.254',
masquerade=True, masquerade=True,
@ -175,6 +346,7 @@ class TestNetworkSettings(base.TestCase):
self.conf.config(cidr='192.168.20.0/24', self.conf.config(cidr='192.168.20.0/24',
dhcp_start='192.168.20.10', dhcp_start='192.168.20.10',
dhcp_end='192.168.20.99', dhcp_end='192.168.20.99',
dhcp_exclude=[],
inspection_iprange='192.168.20.100,192.168.20.189', inspection_iprange='192.168.20.100,192.168.20.189',
gateway='192.168.20.254', gateway='192.168.20.254',
masquerade=True, masquerade=True,
@ -213,16 +385,25 @@ class TestNetworkSettings(base.TestCase):
'UndercloudCtlplaneSubnets': { 'UndercloudCtlplaneSubnets': {
# The ctlplane-subnet subnet have defaults # The ctlplane-subnet subnet have defaults
'ctlplane-subnet': { 'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.5', 'end': '192.168.24.24'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeEnd': '192.168.24.24', 'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5', 'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.24.0/24', 'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}, 'NetworkGateway': '192.168.24.1'},
'subnet1': { 'subnet1': {
'AllocationPools': [
{'start': '192.168.10.10', 'end': '192.168.10.99'}],
'DhcpRangeEnd': '192.168.10.99', 'DhcpRangeEnd': '192.168.10.99',
'DhcpRangeStart': '192.168.10.10', 'DhcpRangeStart': '192.168.10.10',
'NetworkCidr': '192.168.10.0/24', 'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.254'}, 'NetworkGateway': '192.168.10.254'},
'subnet2': { 'subnet2': {
'AllocationPools': [
{'start': '192.168.20.10', 'end': '192.168.20.99'}],
'DhcpRangeEnd': '192.168.20.99', 'DhcpRangeEnd': '192.168.20.99',
'DhcpRangeStart': '192.168.20.10', 'DhcpRangeStart': '192.168.20.10',
'NetworkCidr': '192.168.20.0/24', 'NetworkCidr': '192.168.20.0/24',
@ -238,12 +419,14 @@ class TestNetworkSettings(base.TestCase):
self.conf.config(cidr='192.168.10.0/24', self.conf.config(cidr='192.168.10.0/24',
dhcp_start='192.168.10.10', dhcp_start='192.168.10.10',
dhcp_end='192.168.10.99', dhcp_end='192.168.10.99',
dhcp_exclude=[],
inspection_iprange='192.168.10.100,192.168.10.189', inspection_iprange='192.168.10.100,192.168.10.189',
gateway='192.168.10.254', gateway='192.168.10.254',
group='subnet1') group='subnet1')
self.conf.config(cidr='192.168.20.0/24', self.conf.config(cidr='192.168.20.0/24',
dhcp_start='192.168.20.10', dhcp_start='192.168.20.10',
dhcp_end='192.168.20.99', dhcp_end='192.168.20.99',
dhcp_exclude=[],
inspection_iprange='192.168.20.100,192.168.20.189', inspection_iprange='192.168.20.100,192.168.20.189',
gateway='192.168.20.254', gateway='192.168.20.254',
group='subnet2') group='subnet2')
@ -272,16 +455,25 @@ class TestNetworkSettings(base.TestCase):
'UndercloudCtlplaneSubnets': { 'UndercloudCtlplaneSubnets': {
# The ctlplane-subnet subnet have defaults # The ctlplane-subnet subnet have defaults
'ctlplane-subnet': { 'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.5', 'end': '192.168.24.24'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeEnd': '192.168.24.24', 'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5', 'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.24.0/24', 'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'}, 'NetworkGateway': '192.168.24.1'},
'subnet1': { 'subnet1': {
'AllocationPools': [
{'start': '192.168.10.10', 'end': '192.168.10.99'}],
'DhcpRangeEnd': '192.168.10.99', 'DhcpRangeEnd': '192.168.10.99',
'DhcpRangeStart': '192.168.10.10', 'DhcpRangeStart': '192.168.10.10',
'NetworkCidr': '192.168.10.0/24', 'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.254'}, 'NetworkGateway': '192.168.10.254'},
'subnet2': { 'subnet2': {
'AllocationPools': [
{'start': '192.168.20.10', 'end': '192.168.20.99'}],
'DhcpRangeEnd': '192.168.20.99', 'DhcpRangeEnd': '192.168.20.99',
'DhcpRangeStart': '192.168.20.10', 'DhcpRangeStart': '192.168.20.10',
'NetworkCidr': '192.168.20.0/24', 'NetworkCidr': '192.168.20.0/24',
@ -290,6 +482,102 @@ class TestNetworkSettings(base.TestCase):
} }
self.assertEqual(expected, env) self.assertEqual(expected, env)
def test_no_allocation_pool_on_remote_network(self):
self.conf.config(subnets=['ctlplane-subnet', 'subnet1'])
self.conf.register_opts(self.opts, group=self.grp1)
self.conf.config(cidr='192.168.10.0/24',
dhcp_exclude=[],
inspection_iprange='192.168.10.200,192.168.10.254',
gateway='192.168.10.254',
masquerade=False,
group='subnet1')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [
{'ip_netmask': '192.168.10.0/24', 'next_hop': '192.168.24.1'}],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'},
{'gateway': '192.168.10.254',
'ip_range': '192.168.10.200,192.168.10.254',
'netmask': '255.255.255.0',
'tag': 'subnet1'},
],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
# The ctlplane-subnet subnet have defaults
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.5', 'end': '192.168.24.24'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'},
'subnet1': {
'AllocationPools': [
{'start': '192.168.10.1', 'end': '192.168.10.199'}],
'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.254'}
}
}
self.assertEqual(expected, env)
def test_no_allocation_pool_on_remote_network_three_pools(self):
self.conf.config(subnets=['ctlplane-subnet', 'subnet1'])
self.conf.register_opts(self.opts, group=self.grp1)
self.conf.config(cidr='192.168.10.0/24',
dhcp_exclude=[],
inspection_iprange='192.168.10.100,192.168.10.199',
gateway='192.168.10.222',
masquerade=False,
group='subnet1')
env = {}
undercloud_config._process_network_args(env)
expected = {
'ControlPlaneStaticRoutes': [
{'ip_netmask': '192.168.10.0/24', 'next_hop': '192.168.24.1'}],
'DnsServers': '',
'IronicInspectorSubnets': [
{'gateway': '192.168.24.1',
'ip_range': '192.168.24.100,192.168.24.120',
'netmask': '255.255.255.0',
'tag': 'ctlplane-subnet'},
{'gateway': '192.168.10.222',
'ip_range': '192.168.10.100,192.168.10.199',
'netmask': '255.255.255.0',
'tag': 'subnet1'},
],
'MasqueradeNetworks': {},
'UndercloudCtlplaneSubnets': {
# The ctlplane-subnet subnet have defaults
'ctlplane-subnet': {
'AllocationPools': [
{'start': '192.168.24.5', 'end': '192.168.24.24'}],
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd
# once change: Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is
# merged and THT uses AllocationPools.
'DhcpRangeEnd': '192.168.24.24',
'DhcpRangeStart': '192.168.24.5',
'NetworkCidr': '192.168.24.0/24',
'NetworkGateway': '192.168.24.1'},
'subnet1': {
'AllocationPools': [
{'start': '192.168.10.1', 'end': '192.168.10.99'},
{'start': '192.168.10.200', 'end': '192.168.10.221'},
{'start': '192.168.10.223', 'end': '192.168.10.254'}],
'NetworkCidr': '192.168.10.0/24',
'NetworkGateway': '192.168.10.222'}
}
}
self.assertEqual(expected, env)
class TestTLSSettings(base.TestCase): class TestTLSSettings(base.TestCase):
def test_public_host_with_ip_should_give_ip_endpoint_environment(self): def test_public_host_with_ip_should_give_ip_endpoint_environment(self):

View File

@ -199,6 +199,7 @@ class TestUndercloudInstall(TestPluginV1):
self.conf.config(local_mtu='1234') self.conf.config(local_mtu='1234')
self.conf.config(undercloud_nameservers=['8.8.8.8', '8.8.4.4']) self.conf.config(undercloud_nameservers=['8.8.8.8', '8.8.4.4'])
self.conf.config(subnets='foo') self.conf.config(subnets='foo')
self.conf.config(local_subnet='foo')
mock_masq.return_value = {'1.1.1.1/11': ['2.2.2.2/22']} mock_masq.return_value = {'1.1.1.1/11': ['2.2.2.2/22']}
mock_sroutes.return_value = {'ip_netmask': '1.1.1.1/11', mock_sroutes.return_value = {'ip_netmask': '1.1.1.1/11',
'next_hop': '1.1.1.1'} 'next_hop': '1.1.1.1'}

View File

@ -77,8 +77,6 @@ PARAMETER_MAPPING = {
SUBNET_PARAMETER_MAPPING = { SUBNET_PARAMETER_MAPPING = {
'cidr': 'NetworkCidr', 'cidr': 'NetworkCidr',
'gateway': 'NetworkGateway', 'gateway': 'NetworkGateway',
'dhcp_start': 'DhcpRangeStart',
'dhcp_end': 'DhcpRangeEnd',
} }
THT_HOME = os.environ.get('THT_HOME', THT_HOME = os.environ.get('THT_HOME',
@ -108,7 +106,10 @@ load_global_config()
def _load_subnets_config_groups(): def _load_subnets_config_groups():
for group in CONF.subnets: for group in CONF.subnets:
g = cfg.OptGroup(name=group, title=group) g = cfg.OptGroup(name=group, title=group)
CONF.register_opts(config.get_subnet_opts(), group=g) if group == CONF.local_subnet:
CONF.register_opts(config.get_local_subnet_opts(), group=g)
else:
CONF.register_opts(config.get_remote_subnet_opts(), group=g)
LOG = logging.getLogger(__name__ + ".undercloud_config") LOG = logging.getLogger(__name__ + ".undercloud_config")
@ -248,6 +249,62 @@ def _generate_masquerade_networks():
return masqurade_networks return masqurade_networks
def _calculate_allocation_pools(subnet):
"""Calculate subnet allocation pools
Remove the gateway address, the inspection IP range and the undercloud IP's
from the subnets full IP range and return all remaining address ranges as
allocation pools. If dhcp_start and/or dhcp_end is defined, also remove
addresses before dhcp_start and addresses after dhcp_end.
"""
ip_network = netaddr.IPNetwork(subnet.cidr)
# NOTE(hjensas): Ignore the default dhcp_start and dhcp_end if cidr is not
# the default as well. I.e allow not specifying dhcp_start and dhcp_end.
if (subnet.cidr != constants.CTLPLANE_CIDR_DEFAULT
and subnet.dhcp_start == constants.CTLPLANE_DHCP_START_DEFAULT
and subnet.dhcp_end == constants.CTLPLANE_DHCP_END_DEFAULT):
subnet.dhcp_start, subnet.dhcp_end = None, None
if subnet.dhcp_start and subnet.dhcp_end:
ip_set = netaddr.IPSet()
for a, b in zip(subnet.dhcp_start, subnet.dhcp_end):
ip_set.add(netaddr.IPRange(netaddr.IPAddress(a),
netaddr.IPAddress(b)))
else:
ip_set = netaddr.IPSet(ip_network)
# Remove addresses before dhcp_start if defined
if subnet.dhcp_start:
a = netaddr.IPAddress(ip_network.first)
b = netaddr.IPAddress(subnet.dhcp_start[0]) - 1
ip_set.remove(netaddr.IPRange(a, b))
# Remove addresses after dhcp_end if defined
if subnet.dhcp_end:
a = netaddr.IPAddress(subnet.dhcp_end[0]) + 1
b = netaddr.IPAddress(ip_network.last)
ip_set.remove(netaddr.IPRange(a, b))
# Remove network address and broadcast address
ip_set.remove(ip_network.first)
ip_set.remove(ip_network.last)
# Remove gateway, local_ip, admin_host and public_host addresses
ip_set.remove(netaddr.IPAddress(subnet.get('gateway')))
ip_set.remove(netaddr.IPNetwork(CONF.local_ip).ip)
ip_set.remove(netaddr.IPNetwork(CONF.undercloud_admin_host))
ip_set.remove(netaddr.IPNetwork(CONF.undercloud_public_host))
# Remove addresses in the inspection_iprange
inspect_start, inspect_end = subnet.get('inspection_iprange').split(',')
ip_set.remove(netaddr.IPRange(inspect_start, inspect_end))
# Remove dhcp_exclude addresses and ip ranges
for exclude in subnet.dhcp_exclude:
if '-' in exclude:
exclude_start, exclude_end = exclude.split('-')
ip_set.remove(netaddr.IPRange(exclude_start, exclude_end))
else:
ip_set.remove(netaddr.IPAddress(exclude))
return [{'start': netaddr.IPAddress(ip_range.first).format(),
'end': netaddr.IPAddress(ip_range.last).format()}
for ip_range in list(ip_set.iter_ipranges())]
def _process_network_args(env): def _process_network_args(env):
"""Populate the environment with network configuration.""" """Populate the environment with network configuration."""
@ -256,10 +313,22 @@ def _process_network_args(env):
env['UndercloudCtlplaneSubnets'] = {} env['UndercloudCtlplaneSubnets'] = {}
for subnet in CONF.subnets: for subnet in CONF.subnets:
s = CONF.get(subnet) s = CONF.get(subnet)
env['UndercloudCtlplaneSubnets'][subnet] = {} env['UndercloudCtlplaneSubnets'][subnet] = {
for param_key, param_value in SUBNET_PARAMETER_MAPPING.items(): 'AllocationPools': _calculate_allocation_pools(s)
}
# TODO(hjensas): Remove DhcpRangeStart and DhcpRangeEnd once change:
# Ifdf3e9d22766c1b5ede151979b93754a3d244cc3 is merged and THT uses
# AllocationPools.
if s.get('dhcp_start'):
env['UndercloudCtlplaneSubnets'][subnet].update( env['UndercloudCtlplaneSubnets'][subnet].update(
{param_value: s[param_key]}) {'DhcpRangeStart': s.get('dhcp_start')[0]})
if s.get('dhcp_end'):
env['UndercloudCtlplaneSubnets'][subnet].update(
{'DhcpRangeEnd': s.get('dhcp_end')[0]})
for param_key, param_value in SUBNET_PARAMETER_MAPPING.items():
if param_value:
env['UndercloudCtlplaneSubnets'][subnet].update(
{param_value: s[param_key]})
env['MasqueradeNetworks'] = _generate_masquerade_networks() env['MasqueradeNetworks'] = _generate_masquerade_networks()
env['DnsServers'] = ','.join(CONF['undercloud_nameservers']) env['DnsServers'] = ','.join(CONF['undercloud_nameservers'])

View File

@ -288,8 +288,15 @@ def _validate_in_cidr(subnet_props, subnet_name):
raise FailedValidation(message) raise FailedValidation(message)
validate_addr_in_cidr(subnet_props.gateway, 'gateway') validate_addr_in_cidr(subnet_props.gateway, 'gateway')
validate_addr_in_cidr(subnet_props.dhcp_start, 'dhcp_start') # NOTE(hjensas): Ignore the default dhcp_start and dhcp_end if cidr is not
validate_addr_in_cidr(subnet_props.dhcp_end, 'dhcp_end') # the default as well. I.e allow not specifying dhcp_start and dhcp_end.
if not (subnet_props.cidr != constants.CTLPLANE_CIDR_DEFAULT and
subnet_props.dhcp_start == constants.CTLPLANE_DHCP_START_DEFAULT
and subnet_props.dhcp_end == constants.CTLPLANE_DHCP_END_DEFAULT):
for start in subnet_props.dhcp_start:
validate_addr_in_cidr(start, 'dhcp_start')
for end in subnet_props.dhcp_end:
validate_addr_in_cidr(end, 'dhcp_end')
if subnet_name == CONF.local_subnet: if subnet_name == CONF.local_subnet:
validate_addr_in_cidr(str(netaddr.IPNetwork(CONF.local_ip).ip), validate_addr_in_cidr(str(netaddr.IPNetwork(CONF.local_ip).ip),
'local_ip') 'local_ip')
@ -308,14 +315,26 @@ def _validate_in_cidr(subnet_props, subnet_name):
require_ip=False) require_ip=False)
def _validate_dhcp_range(subnet_props): def _validate_dhcp_range(subnet_props, subnet_name):
start = netaddr.IPAddress(subnet_props.dhcp_start) len_dhcp_start = len(subnet_props.dhcp_start)
end = netaddr.IPAddress(subnet_props.dhcp_end) len_dhcp_end = len(subnet_props.dhcp_end)
if start >= end: if (len_dhcp_start > 1 or len_dhcp_end > 1 and
message = (_('Invalid dhcp range specified, dhcp_start "{0}" does ' len_dhcp_start != len_dhcp_end):
'not come before dhcp_end "{1}"').format(start, end)) message = (_('Number of elements in dhcp_start and dhcp_end must be '
'identical. Subnet "{0}" have "{1}" dhcp_start elements '
'and "{2}" dhcp_end elements.').format(subnet_name,
len_dhcp_start,
len_dhcp_end))
LOG.error(message) LOG.error(message)
raise FailedValidation(message) raise FailedValidation(message)
for a, b in zip(subnet_props.dhcp_start, subnet_props.dhcp_end):
start = netaddr.IPAddress(a)
end = netaddr.IPAddress(b)
if start >= end:
message = (_('Invalid dhcp range specified, dhcp_start "{0}" does '
'not come before dhcp_end "{1}"').format(start, end))
LOG.error(message)
raise FailedValidation(message)
def _validate_inspection_range(subnet_props): def _validate_inspection_range(subnet_props):
@ -328,23 +347,6 @@ def _validate_inspection_range(subnet_props):
raise FailedValidation(message) raise FailedValidation(message)
def _validate_no_overlap(subnet_props):
"""Validate the provisioning and inspection ip ranges do not overlap"""
dhcp_set = netaddr.IPSet(netaddr.IPRange(subnet_props.dhcp_start,
subnet_props.dhcp_end))
inspection_set = netaddr.IPSet(netaddr.IPRange(
subnet_props.inspection_iprange.split(',')[0],
subnet_props.inspection_iprange.split(',')[1]))
if dhcp_set.intersection(inspection_set):
message = (_('Inspection DHCP range "{0}-{1} overlaps provisioning '
'DHCP range "{2}-{3}".') %
(subnet_props.inspection_iprange.split(',')[0],
subnet_props.inspection_iprange.split(',')[1],
subnet_props.dhcp_start, subnet_props.dhcp_end))
LOG.error(message)
raise FailedValidation(message)
def _validate_interface_exists(): def _validate_interface_exists():
"""Validate the provided local interface exists""" """Validate the provided local interface exists"""
if (not CONF.net_config_override if (not CONF.net_config_override
@ -528,11 +530,9 @@ def check(verbose_level, upgrade=False):
_checking_status('Subnet "%s" is in CIDR' % subnet) _checking_status('Subnet "%s" is in CIDR' % subnet)
_validate_in_cidr(s, subnet) _validate_in_cidr(s, subnet)
_checking_status('DHCP range is in subnet "%s"' % subnet) _checking_status('DHCP range is in subnet "%s"' % subnet)
_validate_dhcp_range(s) _validate_dhcp_range(s, subnet)
_checking_status('Inspection range for subnet "%s"' % subnet) _checking_status('Inspection range for subnet "%s"' % subnet)
_validate_inspection_range(s) _validate_inspection_range(s)
_checking_status('Subnet "%s" has no overlap' % subnet)
_validate_no_overlap(s)
_checking_status('IP addresses') _checking_status('IP addresses')
_validate_ips() _validate_ips()
_checking_status('Network interfaces') _checking_status('Network interfaces')