tripleo-common/tripleo_common/inventories.py
Harald Jensås 131ff2b4b8 Re-factor inventory.py use setdefault pervasively
This refactors the inventory code to pervasively
use 'setdefault' to assign values to all inventory
keys.

The idea is to use multiple sources in order, and
build up the inventory enriching it with more data as
it becomes available.

With the pervasive 'setdefault' usage all values in
the inventory will reflect the value first discovered.

The follow up change
  I9e1851aeb92c9e257fd18fdc500ee5c01b1eb53e
adds support to use neutron as a source of data for
the inventory in addition to the heat stack.

Discovery order will be neutron then heat.

Partial-Implements: blueprint network-data-v2-ports
Change-Id: Ia00ef50d6272fd1b3dd6dbbf7ff6edd2472d7dd0
2021-01-19 09:17:01 +01:00

146 lines
6.0 KiB
Python

#!/usr/bin/env python
# Copyright 2019 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.
from collections import OrderedDict
import os
import tempfile
import yaml
class TemplateDumper(yaml.SafeDumper):
def represent_ordered_dict(self, data):
return self.represent_dict(data.items())
TemplateDumper.add_representer(OrderedDict,
TemplateDumper.represent_ordered_dict)
class TripleoInventories(object):
def __init__(self, stack_to_inv_obj_map):
"""
Input: a mapping of stack->TripleoInventory objects, e.g.
stack_to_inv_obj_map['central'] = TripleoInventory('central')
stack_to_inv_obj_map['edge0'] = TripleoInventory('edge0')
"""
self.stack_to_inv_obj_map = stack_to_inv_obj_map
def _merge(self, dynamic=True):
"""Merge TripleoInventory objects"""
inventory = OrderedDict()
if dynamic:
inventory['_meta'] = {'hostvars': {}}
for stack, inv_obj in self.stack_to_inv_obj_map.items():
# convert each inventory object into an ordered dict
inv = inv_obj.list(dynamic)
# only want one undercloud, shouldn't matter which
if 'Undercloud' not in inventory.keys():
inventory['Undercloud'] = inv['Undercloud']
if dynamic:
inventory['Undercloud']['hosts'] = ['undercloud']
else:
inventory['Undercloud']['hosts'] = {'undercloud': {}}
# add 'plans' to create a list to append to
inventory['Undercloud']['vars']['plans'] = []
# save the plan for this stack in the plans list
plan = inv['Undercloud']['vars'].get('plan')
if plan is not None:
inventory['Undercloud']['vars']['plans'].append(plan)
for key in inv.keys():
if key != 'Undercloud':
new_key = stack + '_' + key
if key not in ('_meta', 'overcloud', stack):
# Merge into a top level group
if dynamic:
inventory.setdefault(key, {'children': []})
inventory[key]['children'].append(new_key)
inventory[key]['children'].sort()
else:
inventory.setdefault(key, {'children': {}})
inventory[key]['children'][new_key] = {}
if 'children' in inv[key].keys():
roles = []
for child in inv[key]['children']:
roles.append(stack + '_' + child)
roles.sort()
if dynamic:
inventory[new_key] = {
'children': roles
}
else:
inventory[new_key] = {
'children': {x: {} for x in roles}
}
if 'vars' in inv[key]:
inventory[new_key]['vars'] = inv[key]['vars']
if key == 'allovercloud':
# useful to have just stack name refer to children
if dynamic:
inventory[stack] = {'children': [new_key]}
else:
inventory[stack] = {'children': {new_key: {}}}
else:
if key != '_meta':
inventory[new_key] = inv[key]
elif dynamic:
inventory['_meta']['hostvars'].update(
inv['_meta'].get('hostvars', {})
)
# 'plan' doesn't make sense when using multiple plans
if len(self.stack_to_inv_obj_map) > 1:
del inventory['Undercloud']['vars']['plan']
# sort plans list for consistency
inventory['Undercloud']['vars']['plans'].sort()
return inventory
def list(self, dynamic=True):
return self._merge(dynamic)
def write_static_inventory(self, inventory_file_path, extra_vars=None):
"""Convert OrderedDict inventory to static yaml format in a file."""
allowed_extensions = ('.yaml', '.yml', '.json')
if not os.path.splitext(inventory_file_path)[1] in allowed_extensions:
raise ValueError("Path %s does not end with one of %s extensions"
% (inventory_file_path,
",".join(allowed_extensions)))
inventory = self._merge(dynamic=False)
if extra_vars:
for var, value in extra_vars.items():
if var in inventory:
inventory[var]['vars'].update(value)
# Atomic update as concurrent tripleoclient commands can call this
inventory_file_dir = os.path.dirname(inventory_file_path)
with tempfile.NamedTemporaryFile(
'w',
dir=inventory_file_dir,
delete=False) as inventory_file:
yaml.dump(inventory, inventory_file, TemplateDumper)
os.rename(inventory_file.name, inventory_file_path)
def host(self):
# Dynamic inventory scripts must return empty json if they don't
# provide detailed info for hosts:
# http://docs.ansible.com/ansible/developing_inventory.html
return {}