From 92b4cc30d7388917a27931b35f53a2655688b3ef Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 21 Apr 2015 10:17:18 -0400 Subject: [PATCH] Add inventory command to shade The ansible inventory plugin is actually really useful for general purpose debugging of cloud metadata. Also, having this in tree means we can test it and make sure we don't break the interface for people. Change-Id: Ibb1463936fac9a7e959e291a3856c4f8d32898e0 --- setup.cfg | 4 ++ shade/__init__.py | 1 + shade/cmd/__init__.py | 0 shade/cmd/inventory.py | 66 +++++++++++++++++++++ shade/inventory.py | 61 ++++++++++++++++++++ shade/tests/functional/test_inventory.py | 73 ++++++++++++++++++++++++ 6 files changed, 205 insertions(+) create mode 100644 shade/cmd/__init__.py create mode 100755 shade/cmd/inventory.py create mode 100644 shade/inventory.py create mode 100644 shade/tests/functional/test_inventory.py diff --git a/setup.cfg b/setup.cfg index 4e7aebba1..b726f8d52 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,10 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 +[entry_points] +console_scripts = + shade-inventory = shade.cmd.inventory:main + [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/shade/__init__.py b/shade/__init__.py index 836facb39..bbe05bcbd 100644 --- a/shade/__init__.py +++ b/shade/__init__.py @@ -1612,6 +1612,7 @@ class OpenStackCloud(object): return self.get_openstack_vars(server) def get_server_meta(self, server): + # TODO(mordred) remove once ansible has moved to Inventory interface server_vars = meta.get_hostvars_from_server(self, server) groups = meta.get_groups_from_server(self, server, server_vars) return dict(server_vars=server_vars, groups=groups) diff --git a/shade/cmd/__init__.py b/shade/cmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/shade/cmd/inventory.py b/shade/cmd/inventory.py new file mode 100755 index 000000000..c4d396f46 --- /dev/null +++ b/shade/cmd/inventory.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# 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. + +import argparse +import json +import sys +import yaml + +import shade +import shade.inventory + + +def output_format_dict(data, use_yaml): + if use_yaml: + return yaml.safe_dump(data, default_flow_style=False) + else: + return json.dumps(data, sort_keys=True, indent=2) + + +def parse_args(): + parser = argparse.ArgumentParser(description='OpenStack Inventory Module') + parser.add_argument('--refresh', action='store_true', + help='Refresh cached information') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--list', action='store_true', + help='List active servers') + group.add_argument('--host', help='List details about the specific host') + parser.add_argument('--yaml', action='store_true', default=False, + help='Output data in nicely readable yaml') + parser.add_argument('--debug', action='store_true', default=False, + help='Enable debug output') + return parser.parse_args() + + +def main(): + args = parse_args() + try: + shade.simple_logging(debug=args.debug) + inventory = shade.inventory.OpenStackInventory( + refresh=args.refresh) + if args.list: + output = inventory.list_hosts() + elif args.host: + output = inventory.get_host(args.host) + print(output_format_dict(output, args.yaml)) + except shade.OpenStackCloudException as e: + sys.stderr.write(e.message + '\n') + sys.exit(1) + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/shade/inventory.py b/shade/inventory.py new file mode 100644 index 000000000..d66808a84 --- /dev/null +++ b/shade/inventory.py @@ -0,0 +1,61 @@ +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# 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. + +import os_client_config + +import shade +from shade import _utils + + +class OpenStackInventory(object): + + def __init__( + self, config_files=[], refresh=False): + config = os_client_config.config.OpenStackConfig( + config_files=os_client_config.config.CONFIG_FILES + config_files) + + self.clouds = [ + shade.OpenStackCloud( + cloud=f.name, + cache_interval=config.get_cache_max_age(), + cache_class=config.get_cache_class(), + cache_arguments=config.get_cache_arguments(), + **f.config) + for f in config.get_all_clouds() + ] + + # Handle manual invalidation of entire persistent cache + if refresh: + for cloud in self.clouds: + cloud._cache.invalidate() + + def list_hosts(self): + hostvars = [] + + for cloud in self.clouds: + + # Cycle on servers + for server in cloud.list_servers(): + + meta = cloud.get_openstack_vars(server) + hostvars.append(meta) + + return hostvars + + def search_hosts(self, name_or_id=None, filters=None): + hosts = self.list_hosts() + return _utils._filter_list(hosts, name_or_id, filters) + + def get_host(self, name_or_id, filters=None): + return _utils._get_entity(self.search_hosts, name_or_id, filters) diff --git a/shade/tests/functional/test_inventory.py b/shade/tests/functional/test_inventory.py new file mode 100644 index 000000000..3a4bfcdb8 --- /dev/null +++ b/shade/tests/functional/test_inventory.py @@ -0,0 +1,73 @@ +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# 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. + +""" +test_inventory +---------------------------------- + +Functional tests for `shade` inventory methods. +""" + +from shade import openstack_cloud +from shade import inventory + +from shade.tests import base +from shade.tests.functional.util import pick_flavor, pick_image + + +class TestInventory(base.TestCase): + def setUp(self): + super(TestInventory, self).setUp() + # Shell should have OS-* envvars from openrc, typically loaded by job + self.cloud = openstack_cloud() + self.inventory = inventory.OpenStackInventory() + self.server_name = 'test_inventory_server' + self.nova = self.cloud.nova_client + self.flavor = pick_flavor(self.nova.flavors.list()) + if self.flavor is None: + self.assertTrue(False, 'no sensible flavor available') + self.image = pick_image(self.nova.images.list()) + if self.image is None: + self.assertTrue(False, 'no sensible image available') + self.addCleanup(self._cleanup_servers) + self.cloud.create_server( + name=self.server_name, image=self.image, flavor=self.flavor, + wait=True, auto_ip=True) + + def _cleanup_servers(self): + for i in self.nova.servers.list(): + if i.name.startswith(self.server_name): + self.nova.servers.delete(i) + + def _test_host_content(self, host): + self.assertEquals(host['image']['id'], self.image.id) + self.assertNotIn('links', host['image']) + self.assertEquals(host['flavor']['id'], self.flavor.id) + self.assertNotIn('links', host['flavor']) + self.assertNotIn('links', host) + self.assertIsInstance(host['volumes'], list) + self.assertIsInstance(host['metadata'], dict) + self.assertIn('interface_ip', host) + + def test_get_host(self): + host = self.inventory.get_host(self.server_name) + self.assertIsNotNone(host) + self.assertEquals(host['name'], self.server_name) + self._test_host_content(host) + host_found = False + for host in self.inventory.list_hosts(): + if host['name'] == self.server_name: + host_found = True + self._test_host_content(host) + self.assertTrue(host_found)