Warn if there are not enough node IPs in pools

Enhance the network-validation with an additional check which ensures
sufficient number of IPs for assigned nodes. Static IP pools are
defined in the environments/ips-from-pool-all.yaml file in THT,
and assigned nodes are saved in plan-environment.yaml. This change
adds a check which compares the above values to ensure there are enough
IPs allocated in each pool for every role.

Change-Id: I63298a1bdd8ae793ec37070559537497e81b8ca4
This commit is contained in:
Ana Krivokapic 2017-10-11 19:08:19 +02:00
parent bc3b90c2a8
commit b870fd52bc
4 changed files with 204 additions and 7 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
Enhanced the network environment validation to validate the node IP pool
size. Warnings are issued if there are not enough IPs in any pools for all
assigned nodes.

View File

@ -819,3 +819,119 @@ class TestDuplicateStaticIps(base.TestCase):
}
errors = validation.duplicate_static_ips(static_ips)
self.assertEqual([], errors)
class TestNodePoolSize(base.TestCase):
def test_pool_size_ok(self):
plan_env_path = 'plan-environment.yaml'
ip_pools_path = 'environments/ips-from-pool-all.yaml'
plan_env_content = """parameter_defaults:
BlockStorageCount: 0
CephStorageCount: 0
ComputeCount: 1
ControllerCount: 1
ObjectStorageCount: 0"""
ip_pools_content = """parameter_defaults:
ControllerIPs:
external:
- 10.0.0.251
internal_api:
- 172.16.2.251
storage:
- 172.16.1.251
storage_mgmt:
- 172.16.3.251
tenant:
- 172.16.0.251
ComputeIPs:
internal_api:
- 172.16.2.252
storage:
- 172.16.1.252
tenant:
- 172.16.0.252"""
template_files = {
plan_env_path: plan_env_content,
ip_pools_path: ip_pools_content
}
warnings = validation.validate_node_pool_size(
plan_env_path, ip_pools_path, template_files)
self.assertEqual(len(warnings), 0)
def test_pool_size_pool_too_small(self):
plan_env_path = 'plan-environment.yaml'
ip_pools_path = 'environments/ips-from-pool-all.yaml'
plan_env_content = """parameter_defaults:
BlockStorageCount: 0
CephStorageCount: 0
ComputeCount: 2
ControllerCount: 1
ObjectStorageCount: 0"""
ip_pools_content = """parameter_defaults:
ControllerIPs:
external:
- 10.0.0.251
internal_api:
- 172.16.2.251
storage:
- 172.16.1.251
storage_mgmt:
- 172.16.3.251
tenant:
- 172.16.0.251
ComputeIPs:
internal_api:
- 172.16.2.252
storage:
- 172.16.1.252
tenant:
- 172.16.0.252"""
template_files = {
plan_env_path: plan_env_content,
ip_pools_path: ip_pools_content
}
warnings = validation.validate_node_pool_size(
plan_env_path, ip_pools_path, template_files)
self.assertEqual(len(warnings), 3)
self.assertEqual(set(warnings), {
"Insufficient number of IPs in 'internal_api' pool for 'Compute' "
"role: 1 IP(s) found in pool, but 2 nodes assigned to role.",
"Insufficient number of IPs in 'storage' pool for 'Compute' "
"role: 1 IP(s) found in pool, but 2 nodes assigned to role.",
"Insufficient number of IPs in 'tenant' pool for 'Compute' "
"role: 1 IP(s) found in pool, but 2 nodes assigned to role."
})
def test_pool_size_pool_missing(self):
plan_env_path = 'plan-environment.yaml'
ip_pools_path = 'environments/ips-from-pool-all.yaml'
plan_env_content = """parameter_defaults:
BlockStorageCount: 0
CephStorageCount: 0
ComputeCount: 1
ControllerCount: 1
ObjectStorageCount: 0"""
ip_pools_content = """parameter_defaults:
ControllerIPs:
external:
- 10.0.0.251
internal_api:
- 172.16.2.251
storage:
- 172.16.1.251
storage_mgmt:
- 172.16.3.251
tenant:
- 172.16.0.251"""
template_files = {
plan_env_path: plan_env_content,
ip_pools_path: ip_pools_content
}
warnings = validation.validate_node_pool_size(
plan_env_path, ip_pools_path, template_files)
self.assertEqual(len(warnings), 1)
self.assertEqual(warnings, [
"Found 1 node(s) assigned to 'Compute' role, but no static IP "
"pools defined."
])

View File

@ -33,11 +33,21 @@ short_description: Validate networking templates
description:
- Performs networking-related checks on a set of TripleO templates
options:
path:
netenv_path:
required: true
description:
- The path of the base network environment file
type: str
plan_env_path:
required: true
description:
- The path of the plan environment file
type: str
ip_pools_path:
required: true
description:
- The path of the IP pools network environment file
type: str
template_files:
required: true
description:
@ -51,8 +61,10 @@ EXAMPLES = '''
tasks:
- name: Check the Network environment
network_environment:
path: environments/network-environment.yaml
netenv_path: environments/network-environment.yaml
template_files: "{{ lookup('tht') }}"
plan_env_path: plan-environment.yaml
ip_pools_path: environments/ips-from-pool-all.yaml
'''
@ -446,23 +458,82 @@ def duplicate_static_ips(static_ips):
return errors
def validate_node_pool_size(plan_env_path, ip_pools_path, template_files):
warnings = []
plan_env = yaml.load(template_files[plan_env_path])
ip_pools = yaml.load(template_files[ip_pools_path])
param_defaults = plan_env.get('parameter_defaults')
node_counts = {
param.replace('Count', ''): count
for param, count in six.iteritems(param_defaults)
if param.endswith('Count') and count > 0
}
# TODO(akrivoka): There are a lot of inconsistency issues with parameter
# naming in THT :( Once those issues are fixed, this block should be
# removed.
if 'ObjectStorage' in node_counts:
node_counts['SwiftStorage'] = node_counts['ObjectStorage']
del node_counts['ObjectStorage']
param_defaults = ip_pools.get('parameter_defaults')
role_pools = {
param.replace('IPs', ''): pool
for param, pool in six.iteritems(param_defaults)
if param.endswith('IPs') and param.replace('IPs', '') in node_counts
}
for role, node_count in six.iteritems(node_counts):
try:
pools = role_pools[role]
except KeyError:
warnings.append(
"Found {} node(s) assigned to '{}' role, but no static IP "
"pools defined.".format(node_count, role)
)
continue
for pool_name, pool_ips in six.iteritems(pools):
if len(pool_ips) < node_count:
warnings.append(
"Insufficient number of IPs in '{}' pool for '{}' role: "
"{} IP(s) found in pool, but {} nodes assigned to role."
.format(pool_name, role, len(pool_ips), node_count)
)
return warnings
def main():
module = AnsibleModule(argument_spec=dict(
path=dict(required=True, type='str'),
netenv_path=dict(required=True, type='str'),
plan_env_path=dict(required=True, type='str'),
ip_pools_path=dict(required=True, type='str'),
template_files=dict(required=True, type='list')
))
netenv_path = module.params.get('path')
netenv_path = module.params.get('netenv_path')
plan_env_path = module.params.get('plan_env_path')
ip_pools_path = module.params.get('ip_pools_path')
template_files = {name: content[1] for (name, content) in
module.params.get('template_files')}
errors = validate(netenv_path, template_files)
warnings = []
try:
warnings = validate_node_pool_size(plan_env_path, ip_pools_path,
template_files)
except Exception as e:
errors.append("{}".format(e))
if errors:
module.fail_json(msg="\n".join(errors))
else:
module.exit_json(msg="No errors found for the '{}' file.".format(
netenv_path))
module.exit_json(
msg="No errors found for the '{}' file.".format(netenv_path),
warnings=warnings,
)
if __name__ == '__main__':

View File

@ -15,8 +15,12 @@
groups:
- pre-deployment
network_environment_path: environments/network-environment.yaml
plan_env_path: plan-environment.yaml
ip_pools_path: environments/ips-from-pool-all.yaml
tasks:
- name: Validate the network environment files
network_environment:
path: "{{ network_environment_path }}"
netenv_path: "{{ network_environment_path }}"
plan_env_path: "{{ plan_env_path }}"
ip_pools_path: "{{ ip_pools_path }}"
template_files: "{{ lookup('tht') }}"