tripleo-ansible/tripleo_ansible/ansible_plugins/action/tripleo_all_nodes_data.py
Emilien Macchi 9fefb30c5d tripleo_all_nodes_data: sort enabled_services (idempotency)
Sorting the enabled_services list so we can have idempotent hieradata
where the content doesn't change between Ansible runs of the
tripleo_all_nodes_data action plugin.

Change-Id: I8f54265dbadf21da6badd690f5cb123b549cf48c
2020-04-08 10:11:10 -04:00

255 lines
11 KiB
Python

# Copyright 2020 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
DOCUMENTATION = """
---
module: tripleo_all_nodes_data
author:
- James Slagle (@slagle) <jslagle@redhat.com>
version_added: '2.8'
short_description: Renders the all_nodes data for TripleO as group_vars
notes: []
description:
- This module renders the all_nodes data for TripleO as group_vars which are
then available on overcloud nodes.
options:
forks:
description:
- The number of forks to spawn in parallel to compute the data for each
service. Defaults to the forks set for ansible.
required: False
"""
EXAMPLES = """
- name: Render all_nodes data
tripleo_all_nodes_data:
"""
import json
from multiprocessing import Manager, Process
import os
import traceback
from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase
from ansible.plugins.filter import ipaddr
from ansible.utils.display import Display
DISPLAY = Display()
class ActionModule(ActionBase):
"""Renders the all_nodes data for TripleO as group_vars"""
def compute_service(self, service, all_nodes):
DISPLAY.vv("Processing {}".format(service))
# <service>_enabled: true
all_nodes[service + '_enabled'] = True
# <service>_node_ips: <list of ips>
DISPLAY.vv(" Computing data for {}_node_ips".format(service))
service_network = self.service_net_map.get(
service + '_network', 'ctlplane')
service_hosts = self.groups.get(service, [])
service_node_ips = list(
map(lambda host: self.h_vars[host][service_network + '_ip'],
service_hosts))
for extra_node_ip in self.all_nodes_extra_map_data.get(
service + '_node_ips', []):
if extra_node_ip not in service_node_ips:
service_node_ips.append(extra_node_ip)
all_nodes[service + '_node_ips'] = service_node_ips
if self.nova_additional_cell:
# <service>_cell_node_names: <list of hostnames>
v = service_network + '_hostname'
service_cell_node_names = \
list(map(lambda host: self.h_vars[host][v],
service_hosts))
all_nodes[service + '_cell_node_names'] = \
service_cell_node_names
else:
# <service>_node_names: <list of hostnames>
DISPLAY.vv(" Computing data for {}_node_names".format(service))
v = service_network + '_hostname'
service_node_names = \
list(map(lambda host: self.h_vars[host][v],
service_hosts))
for extra_node_name in self.all_nodes_extra_map_data.get(
service + '_node_names', []):
if extra_node_name not in service_node_names:
service_node_names.append(extra_node_name)
all_nodes[service + '_node_names'] = service_node_names
# <service>_short_node_names: <list of hostnames>
DISPLAY.vv(" Computing data for {}_short_node_names".format(service))
service_short_node_names = \
list(map(lambda host: self.h_vars[host]['inventory_hostname'],
service_hosts))
for extra_short_node_name in self.all_nodes_extra_map_data.get(
service + '_short_node_names', []):
if extra_short_node_name not in service_node_names:
service_short_node_names.append(extra_short_node_name)
all_nodes[service + '_short_node_names'] = \
service_short_node_names
# <service>_short_bootstrap_node_name: hostname
DISPLAY.vv(" Computing data for {}_short_bootstrap_node_name".format(service))
if self.all_nodes_extra_map_data.get(
service + '_short_bootstrap_node_name', None):
v = service + '_short_bootstrap_node_name'
service_hosts += self.all_nodes_extra_map_data[v]
service_hosts.sort()
if service_hosts:
all_nodes[service + '_short_bootstrap_node_name'] = \
service_hosts[0]
# <service>_bootstrap_node_ip: hostname
DISPLAY.vv(" Computing data for {}_short_bootstrap_node_ip".format(service))
if self.all_nodes_extra_map_data.get(
service + '_bootstrap_node_ip', None):
v = service + '_bootstrap_node_ip'
service_bootstrap_node_ips = \
service_node_ips.append(self.all_nodes_extra_map_data[v])
else:
service_bootstrap_node_ips = service_node_ips
if service_bootstrap_node_ips:
all_nodes[service + '_bootstrap_node_ip'] = \
service_bootstrap_node_ips[0]
def process_services(self, enabled_services, all_nodes, forks):
# This breaks up the enabled_services list into smaller lists with
# length equal to the number of forks.
enabled_services_length = len(enabled_services)
for i in range(0, enabled_services_length, forks):
# It would be nice to be able to use multiprocessing.Pool here,
# however, that resulted in many pickle errors.
# For each smaller list, spawn a process to compute each service in
# that chunk.
end = i + forks
if end > enabled_services_length:
end = enabled_services_length
processes = [Process(target=self.compute_service,
args=(enabled_services[x], all_nodes))
for x in range(i, end)]
[p.start() for p in processes]
[p.join() for p in processes]
[p.terminate() for p in processes]
def compute_all_nodes(self, all_nodes, task_vars):
DISPLAY.vv("Starting compute and render for all_nodes data")
# Internal Ansible objects for inventory and variables
inventory = self._task.get_variable_manager()._inventory
self.groups = inventory.get_groups_dict()
# host_vars
self.h_vars = self._task.get_variable_manager().get_vars()['hostvars']
# Needed tripleo variables for convenience
self.service_net_map = task_vars['service_net_map']
self.nova_additional_cell = task_vars['nova_additional_cell']
self.all_nodes_extra_map_data = task_vars['all_nodes_extra_map_data']
net_vip_map = task_vars['net_vip_map']
enabled_services = task_vars['enabled_services']
primary_role_name = task_vars['primary_role_name']
enabled_services += self.all_nodes_extra_map_data.get(
'enabled_services', [])
# make enabled_services unique and sorted
enabled_services = list(set(enabled_services))
enabled_services.sort()
all_nodes['enabled_services'] = enabled_services
forks = self._task.args.get('forks', task_vars['ansible_forks'])
DISPLAY.vv("forks set to {}".format(forks))
self.process_services(enabled_services, all_nodes, forks)
# <service>: service_network
DISPLAY.vv("Computing data for service_net_map")
for key, value in self.service_net_map.items():
all_nodes[key] = value
# all values from all_nodes_extra_map_data when nova_additional_cell
if self.nova_additional_cell:
for key, value in self.all_nodes_extra_map_data.items():
all_nodes[key] = value
# redis_vip: ip
DISPLAY.vv("Computing data for redis_vip")
if 'redis' in enabled_services or self.nova_additional_cell:
if 'redis_vip' in self.all_nodes_extra_map_data:
all_nodes['redis_vip'] = self.all_nodes_extra_map_data['redis_vip']
elif 'redis' in net_vip_map:
all_nodes['redis_vip'] = net_vip_map['redis']
# ovn_dbs_vip: ip
DISPLAY.vv("Computing data for ovn_dbs_vip")
if 'ovn_dbs' in enabled_services or self.nova_additional_cell:
if 'ovn_dbs_vip' in self.all_nodes_extra_map_data:
all_nodes['ovn_dbs_vip'] = \
self.all_nodes_extra_map_data['ovn_dbs_vip']
elif 'ovn_dbs' in net_vip_map:
all_nodes['ovn_dbs_vip'] = net_vip_map['ovn_dbs']
DISPLAY.vv("Computing data for top level vars")
all_nodes['deploy_identifier'] = task_vars['deploy_identifier']
all_nodes['stack_action'] = task_vars['stack_action']
all_nodes['stack_update_type'] = task_vars['stack_update_type']
all_nodes['container_cli'] = task_vars['container_cli']
# controller_node_<ips/names>
# note that these are supposed to be strings, not lists
DISPLAY.vv("Computing data for controller node ips/names")
primary_hosts = self.groups.get(primary_role_name, [])
all_nodes['controller_node_ips'] = \
','.join(list(map(lambda host: self.h_vars[host]['ctlplane_ip'],
primary_hosts)))
all_nodes['controller_node_names'] = \
','.join(list(map(lambda host: self.h_vars[host]['inventory_hostname'],
primary_hosts)))
DISPLAY.vv("Done")
def run(self, tmp=None, task_vars=None):
"""Renders the all_nodes data for TripleO as group_vars"""
manager = Manager()
all_nodes = manager.dict()
try:
self.compute_all_nodes(all_nodes, task_vars)
all_nodes = dict(all_nodes)
all_nodes_path = os.path.join(task_vars['playbook_dir'],
'group_vars', 'overcloud.json')
with open(all_nodes_path, 'w') as f:
DISPLAY.vv("Rendering all_nodes to {}".format(all_nodes_path))
json.dump(all_nodes, f, sort_keys=True, indent=4)
except Exception as e:
DISPLAY.error(traceback.format_exc())
raise AnsibleError(str(e))
finally:
manager.shutdown()
# multiprocessing can hang the plugin exit if there are still
# references to the Manager() object. Even though we have called
# .shutdown(), clean up all_nodes just to be safe.
all_nodes = None
DISPLAY.vv("returning")
return dict(all_nodes=all_nodes)