Merge pull request #278 from cchurch/openstack_inventory_updates

OpenStack inventory updates
This commit is contained in:
Chris Church 2015-06-12 01:00:12 -04:00
commit 3b7b930bcd
6 changed files with 102 additions and 40 deletions

View File

@ -530,7 +530,8 @@ class Command(NoArgsCommand):
'to load'),
make_option('--enabled-var', dest='enabled_var', type='str',
default=None, metavar='v', help='host variable used to '
'set/clear enabled flag when host is online/offline'),
'set/clear enabled flag when host is online/offline, may '
'be specified as "foo.bar" to traverse nested dicts.'),
make_option('--enabled-value', dest='enabled_value', type='str',
default=None, metavar='v', help='value of host variable '
'specified by --enabled-var that indicates host is '
@ -547,7 +548,8 @@ class Command(NoArgsCommand):
'variables.'),
make_option('--instance-id-var', dest='instance_id_var', type='str',
default=None, metavar='v', help='host variable that '
'specifies the unique, immutable instance ID'),
'specifies the unique, immutable instance ID, may be '
'specified as "foo.bar" to traverse nested dicts.'),
)
def init_logging(self):
@ -567,6 +569,51 @@ class Command(NoArgsCommand):
self.logger.addHandler(handler)
self.logger.propagate = False
def _get_instance_id(self, from_dict, default=''):
'''
Retrieve the instance ID from the given dict of host variables.
The instance ID variable may be specified as 'foo.bar', in which case
the lookup will traverse into nested dicts, equivalent to:
from_dict.get('foo', {}).get('bar', default)
'''
instance_id = default
if getattr(self, 'instance_id_var', None):
for key in self.instance_id_var.split('.'):
if not hasattr(from_dict, 'get'):
instance_id = default
break
instance_id = from_dict.get(key, default)
from_dict = instance_id
return instance_id
def _get_enabled(self, from_dict, default=None):
'''
Retrieve the enabled state from the given dict of host variables.
The enabled variable may be specified as 'foo.bar', in which case
the lookup will traverse into nested dicts, equivalent to:
from_dict.get('foo', {}).get('bar', default)
'''
enabled = default
if getattr(self, 'enabled_var', None):
default = object()
for key in self.enabled_var.split('.'):
if not hasattr(from_dict, 'get'):
enabled = default
break
enabled = from_dict.get(key, default)
from_dict = enabled
if enabled is not default:
enabled_value = getattr(self, 'enabled_value', None)
if enabled_value is not None:
enabled = bool(unicode(enabled_value) == unicode(enabled))
else:
enabled = bool(enabled)
return enabled
def load_inventory_from_database(self):
'''
Load inventory and related objects from the database.
@ -643,9 +690,9 @@ class Command(NoArgsCommand):
else:
host_qs = self.inventory.hosts.all()
host_qs = host_qs.filter(active=True, instance_id='',
variables__contains=self.instance_id_var)
variables__contains=self.instance_id_var.split('.')[0])
for host in host_qs:
instance_id = host.variables_dict.get(self.instance_id_var, '')
instance_id = self._get_instance_id(host.variables_dict)
if not instance_id:
continue
self.db_instance_id_map[instance_id] = host.pk
@ -658,7 +705,7 @@ class Command(NoArgsCommand):
self.mem_instance_id_map = {}
if self.instance_id_var:
for mem_host in self.all_group.all_hosts.values():
instance_id = mem_host.variables.get(self.instance_id_var, '')
instance_id = self._get_instance_id(mem_host.variables)
if not instance_id:
self.logger.warning('Host "%s" has no "%s" variable',
mem_host.name, self.instance_id_var)
@ -908,13 +955,7 @@ class Command(NoArgsCommand):
db_host.variables = json.dumps(db_variables)
update_fields.append('variables')
# Update host enabled flag.
enabled = None
if self.enabled_var and self.enabled_var in mem_host.variables:
value = mem_host.variables[self.enabled_var]
if self.enabled_value is not None:
enabled = bool(unicode(self.enabled_value) == unicode(value))
else:
enabled = bool(value)
enabled = self._get_enabled(mem_host.variables)
if enabled is not None and db_host.enabled != enabled:
db_host.enabled = enabled
update_fields.append('enabled')
@ -924,10 +965,7 @@ class Command(NoArgsCommand):
db_host.name = mem_host.name
update_fields.append('name')
# Update host instance_id.
if self.instance_id_var:
instance_id = mem_host.variables.get(self.instance_id_var, '')
else:
instance_id = ''
instance_id = self._get_instance_id(mem_host.variables)
if instance_id != db_host.instance_id:
old_instance_id = db_host.instance_id
db_host.instance_id = instance_id
@ -973,10 +1011,8 @@ class Command(NoArgsCommand):
mem_host_name_map = {}
mem_host_names_to_update = set(self.all_group.all_hosts.keys())
for k,v in self.all_group.all_hosts.iteritems():
instance_id = ''
mem_host_name_map[k] = v
if self.instance_id_var:
instance_id = v.variables.get(self.instance_id_var, '')
instance_id = self._get_instance_id(v.variables)
if instance_id in self.db_instance_id_map:
mem_host_pk_map[self.db_instance_id_map[instance_id]] = v
elif instance_id:
@ -1023,16 +1059,11 @@ class Command(NoArgsCommand):
mem_host = self.all_group.all_hosts[mem_host_name]
host_attrs = dict(variables=json.dumps(mem_host.variables),
name=mem_host_name, description='imported')
enabled = None
if self.enabled_var and self.enabled_var in mem_host.variables:
value = mem_host.variables[self.enabled_var]
if self.enabled_value is not None:
enabled = bool(unicode(self.enabled_value) == unicode(value))
else:
enabled = bool(value)
enabled = self._get_enabled(mem_host.variables)
if enabled is not None:
host_attrs['enabled'] = enabled
if self.instance_id_var:
instance_id = mem_host.variables.get(self.instance_id_var, '')
instance_id = self._get_instance_id(mem_host.variables)
host_attrs['instance_id'] = instance_id
db_host = self.inventory.hosts.create(**host_attrs)
if enabled is False:

View File

@ -986,8 +986,24 @@ class RunInventoryUpdate(BaseTask):
username=credential.username,
password=decrypt_field(credential, "password"),
project_name=credential.project)
private_state = str(inventory_update.source_vars_dict.get("private", "true"))
openstack_data = {"clouds": {"devstack": {"private": private_state, "auth": openstack_auth}}}
private_state = str(inventory_update.source_vars_dict.get('private', 'true'))
# Retrieve cache path from inventory update vars if available,
# otherwise create a temporary cache path only for this update.
cache = inventory_update.source_vars_dict.get('cache', {})
if not isinstance(cache, dict):
cache = {}
if not cache.get('path', ''):
cache_path = tempfile.mkdtemp(prefix='openstack_cache', dir=kwargs.get('private_data_dir', None))
cache['path'] = cache_path
openstack_data = {
'clouds': {
'devstack': {
'private': private_state,
'auth': openstack_auth,
},
},
'cache': cache,
}
return dict(cloud_credential=yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True))
cp = ConfigParser.ConfigParser()

View File

@ -2000,6 +2000,7 @@ class InventoryUpdatesTest(BaseTransactionTest):
project=api_project)
inventory_source = self.update_inventory_source(self.group, source='openstack', credential=credential)
self.check_inventory_source(inventory_source)
self.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
def test_update_from_azure(self):
source_username = getattr(settings, 'TEST_AZURE_USERNAME', '')

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
# The OpenStack Inventory module uses os-client-config for configuation.
# The OpenStack Inventory module uses os-client-config for configuration.
# https://github.com/stackforge/os-client-config
# This means it will either:
# - Respect normal OS_* environment variables like other OpenStack tools
@ -50,13 +50,16 @@ import shade
class OpenStackInventory(object):
def __init__(self, refresh=False):
config_files = [ os.environ.get('OPENSTACK_CONFIG_FILE', None)
or '/etc/ansible/openstack.yml' ]
def __init__(self, private=False, refresh=False):
config_files = os_client_config.config.CONFIG_FILES
if os.environ.get('OPENSTACK_CONFIG_FILE', None):
config_files.insert(0, os.environ['OPENSTACK_CONFIG_FILE'])
config_files.append('/etc/ansible/openstack.yml')
self.openstack_config = os_client_config.config.OpenStackConfig(
config_files)
self.clouds = shade.openstack_clouds(self.openstack_config)
self.refresh = True
self.private = private
self.refresh = refresh
self.cache_max_age = self.openstack_config.get_cache_max_age()
cache_path = self.openstack_config.get_cache_path()
@ -92,8 +95,11 @@ class OpenStackInventory(object):
hostvars = collections.defaultdict(dict)
for cloud in self.clouds:
cloud.private = cloud.private or self.private
# Cycle on servers
for server in cloud.list_servers():
meta = cloud.get_server_meta(server)
if 'interface_ip' not in meta['server_vars']:
@ -101,9 +107,9 @@ class OpenStackInventory(object):
continue
server_vars = meta['server_vars']
hostvars[server.name]['ansible_ssh_host'] = server_vars['interface_ip']
hostvars[server.name][
'ansible_ssh_host'] = server_vars['interface_ip']
hostvars[server.name]['openstack'] = server_vars
hostvars[server.name]['id'] = server_vars['id']
for group in meta['groups']:
groups[group].append(server.name)
@ -129,6 +135,9 @@ class OpenStackInventory(object):
def parse_args():
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
parser.add_argument('--private',
action='store_true',
help='Use private address for ansible host')
parser.add_argument('--refresh', action='store_true',
help='Refresh cached information')
group = parser.add_mutually_exclusive_group(required=True)
@ -141,14 +150,13 @@ def parse_args():
def main():
args = parse_args()
try:
inventory = OpenStackInventory(args.refresh)
inventory = OpenStackInventory(args.private, args.refresh)
if args.list:
inventory.list_instances()
elif args.host:
inventory.get_host(args.host)
except shade.OpenStackCloudException as e:
print(e.message)
sys.exit(1)
sys.exit(e.message)
sys.exit(0)

View File

@ -534,7 +534,7 @@ OPENSTACK_ENABLED_VALUE = 'ACTIVE'
OPENSTACK_GROUP_FILTER = r'^.+$'
OPENSTACK_HOST_FILTER = r'^.+$'
OPENSTACK_EXCLUDE_EMPTY_GROUPS = True
OPENSTACK_INSTANCE_ID_VAR = "id"
OPENSTACK_INSTANCE_ID_VAR = 'openstack.id'
# ---------------------
# -- Activity Stream --

View File

@ -492,6 +492,12 @@ TEST_VMWARE_HOST = ''
TEST_VMWARE_USER = ''
TEST_VMWARE_PASSWORD = ''
# OpenStack credentials
TEST_OPENSTACK_HOST = ''
TEST_OPENSTACK_USER = ''
TEST_OPENSTACK_PASSWORD = ''
TEST_OPENSTACK_PROJECT = ''
# Azure credentials.
TEST_AZURE_USERNAME = ''
TEST_AZURE_KEY_DATA = ''