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
This commit is contained in:
Monty Taylor 2015-04-21 10:17:18 -04:00
parent 2c926e618c
commit 92b4cc30d7
6 changed files with 205 additions and 0 deletions

View File

@ -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

View File

@ -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)

0
shade/cmd/__init__.py Normal file
View File

66
shade/cmd/inventory.py Executable file
View File

@ -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()

61
shade/inventory.py Normal file
View File

@ -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)

View File

@ -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)