Add write_static_inventory to TripleoInventory class

This is a reimplementation of the similarly names function in
tripleo-ansible-inventory, but this outputs yaml format instead
of the current ini format.

The reason for this change is getting consistent behavior of the
configparser module for unit tests over py27 & py35 is difficult,
and also some of the roles_data keys I plan to add in a subsequent
commit can't easily be represented in the ini format (particularly
lists don't seem to work, everything must be a string value).

The tripleo-ansible-inventory script can be changed to use this
interface, and we'll have to deprecate the current ini based interface.

Change-Id: Ia3c6b9931e2d38507917cd80a93857d952784bd2
This commit is contained in:
Steven Hardy 2017-12-19 22:34:13 +00:00 committed by Emilien Macchi
parent 35e470776b
commit 513620b115
2 changed files with 124 additions and 8 deletions

View File

@ -16,12 +16,23 @@
# under the License.
from collections import OrderedDict
import os.path
import yaml
from heatclient.exc import HTTPNotFound
HOST_NETWORK = 'ctlplane'
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 StackOutputs(object):
"""Item getter for stack outputs.
@ -77,6 +88,7 @@ class TripleoInventory(object):
cacert=None, username=None, ansible_ssh_user=None):
self.session = session
self.hclient = hclient
self.hosts_format_dict = False
if configs is not None:
# FIXME(shardy) backwards compatibility until we switch
# tripleo-validations to pass the individual values
@ -130,10 +142,17 @@ class TripleoInventory(object):
"""
return self.UNDERCLOUD_SERVICES
def _hosts(self, alist):
"""Static yaml inventories reqire a different hosts format?!"""
if self.hosts_format_dict:
return {x: {} for x in alist}
else:
return alist
def list(self):
ret = OrderedDict({
'undercloud': {
'hosts': ['localhost'],
'hosts': self._hosts(['localhost']),
'vars': {
'ansible_connection': 'local',
'auth_url': self.auth_url,
@ -185,7 +204,7 @@ class TripleoInventory(object):
# Create a group per hostname to map hostname to IP
ips = role_net_ip_map[role][HOST_NETWORK]
for idx, name in enumerate(shortnames):
ret[name] = {'hosts': [ips[idx]]}
ret[name] = {'hosts': self._hosts([ips[idx]])}
if 'server_ids' in role_node_id_map:
ret[name]['vars'] = {
'deploy_server_id': role_node_id_map[
@ -201,7 +220,7 @@ class TripleoInventory(object):
children.append(role)
ret[role] = {
'children': sorted(shortnames),
'children': self._hosts(sorted(shortnames)),
'vars': {
'ansible_ssh_user': self.ansible_ssh_user,
'bootstrap_server_id': role_node_id_map.get(
@ -216,7 +235,7 @@ class TripleoInventory(object):
for vip_name, vip in vip_map.items()
if vip and (vip_name in networks or vip_name == 'redis')}
ret['overcloud'] = {
'children': sorted(children),
'children': self._hosts(sorted(children)),
'vars': vips
}
@ -228,7 +247,7 @@ class TripleoInventory(object):
if ret.get(role) is not None]
if service_children:
ret[service.lower()] = {
'children': service_children,
'children': self._hosts(service_children),
'vars': {
'ansible_ssh_user': self.ansible_ssh_user
}
@ -245,3 +264,19 @@ class TripleoInventory(object):
# provide detailed info for hosts:
# http://docs.ansible.com/ansible/developing_inventory.html
return {}
def write_static_inventory(self, inventory_file_path):
"""Convert inventory list 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)))
# For some reason the json/yaml format needed for static and
# dynamic inventories is different for the hosts/children?!
self.hosts_format_dict = True
inventory = self.list()
with open(inventory_file_path, 'w') as inventory_file:
yaml.dump(inventory, inventory_file, TemplateDumper)

View File

@ -12,6 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import os
import yaml
from heatclient.exc import HTTPNotFound
from mock import MagicMock
@ -54,9 +58,9 @@ class TestInventory(base.TestCase):
self.outputs_data = {'outputs': [
{'output_key': 'EnabledServices',
'output_value': {
'Controller': ['a', 'b', 'c'],
'Compute': ['d', 'e', 'f'],
'CustomRole': ['g', 'h', 'i']}},
'Controller': ['sa', 'sb'],
'Compute': ['sd', 'se'],
'CustomRole': ['sg', 'sh']}},
{'output_key': 'KeystoneURL',
'output_value': 'xyz://keystone'},
{'output_key': 'ServerIdData',
@ -327,3 +331,80 @@ class TestInventory(base.TestCase):
inv_list = self.inventory.list()
for k in expected:
self.assertEqual(expected[k], inv_list[k])
def test_inventory_write_static(self):
tmp_dir = self.useFixture(fixtures.TempDir()).path
inv_path = os.path.join(tmp_dir, "inventory.yaml")
self.inventory.write_static_inventory(inv_path)
expected = {
'Compute': {'children': {'cp-0': {}},
'vars': {'ansible_ssh_user': 'heat-admin',
'bootstrap_server_id': 'a',
'role_name': 'Compute'}},
'Controller': {'children': {'c-0': {}, 'c-1': {}, 'c-2': {}},
'vars': {'ansible_ssh_user': 'heat-admin',
'bootstrap_server_id': 'a',
'role_name': 'Controller'}},
'CustomRole': {'children': {'cs-0': {}},
'vars': {'ansible_ssh_user': 'heat-admin',
'bootstrap_server_id': 'a',
'role_name': 'CustomRole'}},
'_meta': {'hostvars': {}},
'c-0': {'hosts': {'x.x.x.1': {}},
'vars': {'ctlplane_ip': 'x.x.x.1',
'deploy_server_id': 'a',
'enabled_networks': ['ctlplane']}},
'c-1': {'hosts': {'x.x.x.2': {}},
'vars': {'ctlplane_ip': 'x.x.x.2',
'deploy_server_id': 'b',
'enabled_networks': ['ctlplane']}},
'c-2': {'hosts': {'x.x.x.3': {}},
'vars': {'ctlplane_ip': 'x.x.x.3',
'deploy_server_id': 'c',
'enabled_networks': ['ctlplane']}},
'cp-0': {'hosts': {'y.y.y.1': {}},
'vars': {'ctlplane_ip': 'y.y.y.1',
'deploy_server_id': 'd',
'enabled_networks': ['ctlplane']}},
'cs-0': {'hosts': {'z.z.z.1': {}},
'vars': {'ctlplane_ip': 'z.z.z.1',
'deploy_server_id': 'e',
'enabled_networks': ['ctlplane']}},
'overcloud': {'children': {'Compute': {},
'Controller': {},
'CustomRole': {}},
'vars': {'ctlplane_vip': 'x.x.x.4',
'redis_vip': 'x.x.x.6'}},
'sa': {'children': {'Controller': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'sb': {'children': {'Controller': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'sd': {'children': {'Compute': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'se': {'children': {'Compute': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'sg': {'children': {'CustomRole': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'sh': {'children': {'CustomRole': {}},
'vars': {'ansible_ssh_user': 'heat-admin'}},
'undercloud': {'hosts': {'localhost': {}},
'vars': {'ansible_connection': 'local',
'auth_url': 'xyz://keystone.local',
'cacert': 'acacert',
'os_auth_token': 'atoken',
'overcloud_admin_password': 'theadminpw',
'overcloud_keystone_url': 'xyz://keystone',
'plan': 'overcloud',
'project_name': 'admin',
'undercloud_service_list': [
'openstack-nova-compute',
'openstack-heat-engine',
'openstack-ironic-conductor',
'openstack-swift-container',
'openstack-swift-object',
'openstack-mistral-engine'],
'undercloud_swift_url': 'anendpoint',
'username': 'admin'}}}
with open(inv_path, 'r') as f:
loaded_inv = yaml.safe_load(f)
self.assertEqual(expected, loaded_inv)