582 lines
18 KiB
Python
582 lines
18 KiB
Python
# Copyright(c) 2015, Oracle and/or its affiliates. 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.
|
|
import json
|
|
import jsonpickle
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import traceback
|
|
|
|
from tempfile import mkstemp
|
|
|
|
from kollacli import exceptions
|
|
from kollacli import utils
|
|
|
|
from kollacli.sshutils import ssh_check_host
|
|
from kollacli.sshutils import ssh_check_keys
|
|
from kollacli.sshutils import ssh_install_host
|
|
from kollacli.sshutils import ssh_keygen
|
|
from kollacli.sshutils import ssh_uninstall_host
|
|
|
|
from kollacli.exceptions import CommandError
|
|
|
|
ANSIBLE_KEY_FILE = 'ansible_ssh_private_key_file'
|
|
ANSIBLE_SSH_USER = 'ansible_ssh_user'
|
|
ANSIBLE_CONNECTION = 'ansible_connection'
|
|
ANSIBLE_BECOME = 'ansible_become'
|
|
|
|
INVENTORY_PATH = 'ansible/inventory.json'
|
|
|
|
COMPUTE_GRP_NAME = 'compute'
|
|
CONTROL_GRP_NAME = 'control'
|
|
NETWORK_GRP_NAME = 'network'
|
|
STORAGE_GRP_NAME = 'storage'
|
|
|
|
DEPLOY_GROUPS = [
|
|
COMPUTE_GRP_NAME,
|
|
CONTROL_GRP_NAME,
|
|
NETWORK_GRP_NAME,
|
|
STORAGE_GRP_NAME,
|
|
]
|
|
|
|
SERVICE_GROUPS = {
|
|
'cinder': ['cinder-api', 'cinder-backup',
|
|
'cinder-scheduler', 'cinder-volume'],
|
|
'glance': ['glance-api', 'glance-registry'],
|
|
'haproxy': [],
|
|
'keystone': [],
|
|
'mariadb': [],
|
|
'ndbcluster': ['ndb-data', 'ndb-mgmt', 'ndb-mysql'],
|
|
'neutron': ['neutron-server', 'neutron-agents'],
|
|
'nova': ['nova-api', 'nova-conductor', 'nova-consoleauth',
|
|
'nova-novncproxy', 'nova-scheduler'],
|
|
'rabbitmq': [],
|
|
}
|
|
|
|
DEFAULT_HIERARCHY = {
|
|
CONTROL_GRP_NAME: [
|
|
'glance',
|
|
'keystone',
|
|
'ndbcluster',
|
|
'nova',
|
|
'rabbitmq',
|
|
],
|
|
NETWORK_GRP_NAME: [
|
|
'haproxy',
|
|
'neutron',
|
|
],
|
|
COMPUTE_GRP_NAME: [],
|
|
STORAGE_GRP_NAME: [
|
|
'cinder',
|
|
]
|
|
}
|
|
|
|
|
|
class Host(object):
|
|
class_version = 1
|
|
log = logging.getLogger(__name__)
|
|
|
|
def __init__(self, hostname):
|
|
self.name = hostname
|
|
self.alias = ''
|
|
self.is_mgmt = False
|
|
self.hypervisor = ''
|
|
self.vars = {}
|
|
self.version = self.__class__.class_version
|
|
|
|
def get_vars(self):
|
|
return self.vars.copy()
|
|
|
|
def set_var(self, name, value):
|
|
self.vars[name] = value
|
|
|
|
def upgrade(self):
|
|
pass
|
|
|
|
def check(self):
|
|
sshKeysExist = ssh_check_keys()
|
|
if not sshKeysExist:
|
|
try:
|
|
ssh_keygen()
|
|
except Exception as e:
|
|
raise exceptions.CommandError(
|
|
'ERROR: ssh key generation failed on local host : %s'
|
|
% str(e))
|
|
|
|
try:
|
|
self.log.info('Starting check of host (%s)' % self.name)
|
|
ssh_check_host(self.name)
|
|
self.log.info('Host (%s), check succeeded' % self.name)
|
|
|
|
except CommandError as e:
|
|
raise e
|
|
except Exception as e:
|
|
raise Exception(
|
|
'ERROR: Host (%s), check failed. Reason : %s'
|
|
% (self.name, str(e)))
|
|
return True
|
|
|
|
def install(self, password):
|
|
self._setup_keys()
|
|
|
|
# check if already installed
|
|
if self._is_installed():
|
|
self.log.info('Install skipped for host (%s), ' % self.name +
|
|
'kolla already installed')
|
|
return True
|
|
|
|
# not installed- we need to set up the user / remote ssh keys
|
|
# using root and the available password
|
|
try:
|
|
self.log.info('Starting install of host (%s)'
|
|
% self.name)
|
|
ssh_install_host(self.name, password)
|
|
self.log.info('Host (%s) install succeeded' % self.name)
|
|
except Exception as e:
|
|
raise exceptions.CommandError(
|
|
'ERROR: Host (%s) install failed : %s'
|
|
% (self.name, str(e)))
|
|
return True
|
|
|
|
def uninstall(self, password):
|
|
self._setup_keys()
|
|
|
|
try:
|
|
self.log.info('Starting uninstall of host (%s)' % self.name)
|
|
ssh_uninstall_host(self.name, password)
|
|
self.log.info('Host (%s) uninstall succeeded' % self.name)
|
|
except Exception as e:
|
|
raise exceptions.CommandError(
|
|
'ERROR: Host (%s) uninstall failed : %s'
|
|
% (self.name, str(e)))
|
|
return True
|
|
|
|
def _setup_keys(self):
|
|
sshKeysExist = ssh_check_keys()
|
|
if not sshKeysExist:
|
|
try:
|
|
ssh_keygen()
|
|
except Exception as e:
|
|
raise exceptions.CommandError(
|
|
'ERROR: Error generating ssh keys on local host : %s'
|
|
% str(e))
|
|
|
|
def _is_installed(self):
|
|
is_installed = False
|
|
try:
|
|
ssh_check_host(self.name)
|
|
is_installed = True
|
|
|
|
except Exception as e:
|
|
self.log.debug('%s' % str(e))
|
|
pass
|
|
return is_installed
|
|
|
|
|
|
class HostGroup(object):
|
|
class_version = 1
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.services = []
|
|
self._hosts = {} # kv = hostname:object
|
|
self._version = 1
|
|
self.vars = {}
|
|
self.version = self.__class__.class_version
|
|
|
|
def upgrade(self):
|
|
pass
|
|
|
|
def add_host(self, host):
|
|
if host.name not in self._hosts:
|
|
self._hosts[host.name] = host
|
|
else:
|
|
host = self._hosts[host.name]
|
|
|
|
def remove_host(self, host):
|
|
if host.name in self._hosts:
|
|
del self._hosts[host.name]
|
|
|
|
def add_service(self, servicename):
|
|
service = Service(servicename)
|
|
if service not in self.services:
|
|
self.services.append(service)
|
|
return service
|
|
|
|
def remove_service(self, servicename):
|
|
for service in self.services:
|
|
if servicename == service.name:
|
|
self.services.remove(service)
|
|
|
|
def get_hosts(self):
|
|
return self._hosts.values()
|
|
|
|
def get_hostnames(self):
|
|
return self._hosts.keys()
|
|
|
|
def get_servicenames(self):
|
|
names = []
|
|
for service in self.services:
|
|
names.append(service.name)
|
|
return names
|
|
|
|
def get_vars(self):
|
|
return self.vars.copy()
|
|
|
|
def set_var(self, name, value):
|
|
self.vars[name] = value
|
|
|
|
def clear_var(self, name):
|
|
if name in self.vars:
|
|
del self.vars[name]
|
|
|
|
def set_remote(self, remote_flag):
|
|
self.set_var(ANSIBLE_BECOME, 'yes')
|
|
if remote_flag:
|
|
# set the ssh info for all the servers in the group
|
|
self.set_var(ANSIBLE_KEY_FILE, utils.get_pk_file())
|
|
self.set_var(ANSIBLE_SSH_USER, utils.get_admin_user())
|
|
self.clear_var(ANSIBLE_CONNECTION)
|
|
else:
|
|
# remove ssh info, add local connection type
|
|
self.set_var(ANSIBLE_CONNECTION, 'local')
|
|
self.clear_var(ANSIBLE_KEY_FILE)
|
|
self.clear_var(ANSIBLE_SSH_USER)
|
|
|
|
|
|
class Service(object):
|
|
class_version = 1
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self._hosts = {} # kv = name:object
|
|
self.containers = SERVICE_GROUPS[name]
|
|
self.vars = {}
|
|
self.version = self.__class__.class_version
|
|
|
|
def upgrade(self):
|
|
pass
|
|
|
|
def get_hostnames(self):
|
|
return self._hosts.keys()
|
|
|
|
def get_vars(self):
|
|
return self.vars.copy()
|
|
|
|
|
|
class Inventory(object):
|
|
class_version = 1
|
|
|
|
def __init__(self):
|
|
self._groups = {} # kv = name:object
|
|
self._hosts = {} # kv = name:object
|
|
self.vars = {}
|
|
self.version = self.__class__.class_version
|
|
self.remote_mode = True
|
|
|
|
# initialize the inventory to its defaults
|
|
self._create_default_inventory()
|
|
|
|
def upgrade(self):
|
|
pass
|
|
|
|
@staticmethod
|
|
def load():
|
|
"""load the inventory from a pickle file"""
|
|
inventory_path = os.path.join(utils.get_kollacli_etc(), INVENTORY_PATH)
|
|
try:
|
|
if os.path.exists(inventory_path):
|
|
with open(inventory_path, 'rb') as inv_file:
|
|
data = inv_file.read()
|
|
|
|
inventory = jsonpickle.decode(data)
|
|
|
|
# upgrade version handling
|
|
if inventory.version != inventory.class_version:
|
|
inventory.upgrade()
|
|
else:
|
|
inventory = Inventory()
|
|
except Exception:
|
|
raise Exception('ERROR: loading inventory : %s'
|
|
% traceback.format_exc())
|
|
return inventory
|
|
|
|
@staticmethod
|
|
def save(inventory):
|
|
"""Save the inventory in a pickle file"""
|
|
inventory_path = os.path.join(utils.get_kollacli_etc(), INVENTORY_PATH)
|
|
try:
|
|
# the file handle returned from mkstemp must be closed or else
|
|
# if this is called many times you will have an unpleasant
|
|
# file handle leak
|
|
tmp_filehandle, tmp_path = mkstemp()
|
|
|
|
# multiple trips thru json to render a readable inventory file
|
|
data = jsonpickle.encode(inventory)
|
|
data_str = json.loads(data)
|
|
pretty_data = json.dumps(data_str, indent=4)
|
|
with open(tmp_path, 'w') as tmp_file:
|
|
tmp_file.write(pretty_data)
|
|
shutil.copyfile(tmp_path, inventory_path)
|
|
os.remove(tmp_path)
|
|
except Exception as e:
|
|
raise Exception('ERROR: saving inventory : %s' % str(e))
|
|
finally:
|
|
try:
|
|
os.close(tmp_filehandle)
|
|
except Exception:
|
|
pass
|
|
|
|
if tmp_filehandle is not None:
|
|
try:
|
|
os.close(tmp_filehandle)
|
|
except Exception:
|
|
pass
|
|
|
|
def _create_default_inventory(self):
|
|
for (group_name, service_names) in DEFAULT_HIERARCHY.items():
|
|
group = self.add_group(group_name)
|
|
|
|
# add services
|
|
for service_name in service_names:
|
|
group.add_service(service_name)
|
|
self._groups[group_name] = group
|
|
|
|
def get_hosts(self):
|
|
return self._hosts.values()
|
|
|
|
def get_hostnames(self):
|
|
return self._hosts.keys()
|
|
|
|
def get_host_groups(self):
|
|
"""return { hostname : groupnames }"""
|
|
|
|
host_groups = {}
|
|
for host in self._hosts.values():
|
|
host_groups[host.name] = []
|
|
groups = self.get_groups(host)
|
|
for group in groups:
|
|
host_groups[host.name].append(group.name)
|
|
return host_groups
|
|
|
|
def get_host(self, hostname):
|
|
host = None
|
|
if hostname in self._hosts:
|
|
host = self._hosts[hostname]
|
|
return host
|
|
|
|
def add_host(self, hostname, groupname=None):
|
|
"""add host
|
|
|
|
if groupname is none, create a new host
|
|
if group name is not none, add host to group
|
|
"""
|
|
if groupname and groupname not in self._groups:
|
|
raise CommandError('Group name (%s) does not exist'
|
|
% groupname)
|
|
|
|
if groupname and hostname not in self._hosts:
|
|
raise CommandError('Host name (%s) does not exist'
|
|
% hostname)
|
|
|
|
if not groupname and not self.remote_mode and len(self._hosts) >= 1:
|
|
raise CommandError('Cannot have more than one host when in ' +
|
|
'local deploy mode')
|
|
|
|
# create new host if it doesn't exist
|
|
host = Host(hostname)
|
|
if not groupname:
|
|
self._hosts[hostname] = host
|
|
else:
|
|
group = self._groups[groupname]
|
|
group.add_host(host)
|
|
|
|
def remove_host(self, hostname, groupname=None):
|
|
"""remove host
|
|
|
|
if groupname is none, delete host
|
|
if group name is not none, remove host from group
|
|
"""
|
|
if groupname and groupname not in self._groups:
|
|
raise CommandError('Group name (%s) does not exist'
|
|
% groupname)
|
|
|
|
if hostname not in self._hosts:
|
|
return
|
|
|
|
host = self._hosts[hostname]
|
|
groups = self.get_groups(host)
|
|
for group in groups:
|
|
if not groupname or groupname == group.name:
|
|
group.remove_host(host)
|
|
|
|
if not groupname:
|
|
del self._hosts[hostname]
|
|
|
|
def add_group(self, groupname):
|
|
if groupname not in self._groups:
|
|
self._groups[groupname] = HostGroup(groupname)
|
|
|
|
group = self._groups[groupname]
|
|
|
|
group.set_remote(self.remote_mode)
|
|
|
|
return group
|
|
|
|
def remove_group(self, groupname):
|
|
if groupname in self._groups:
|
|
del self._groups[groupname]
|
|
|
|
def get_groups(self, host=None):
|
|
"""return all groups containing host
|
|
|
|
if hosts is none, return all groups in inventory
|
|
"""
|
|
if not host:
|
|
return self._groups.values()
|
|
|
|
groups = []
|
|
for group in self._groups.values():
|
|
if host.name in group.get_hostnames():
|
|
groups.append(group)
|
|
return groups
|
|
|
|
def get_group_services(self):
|
|
"""return { groupname : [servicenames] }"""
|
|
group_services = {}
|
|
for group in self._groups.values():
|
|
group_services[group.name] = []
|
|
for service in group.services:
|
|
group_services[group.name].append(service.name)
|
|
return group_services
|
|
|
|
def get_group_hosts(self):
|
|
"""return { groupname : [hostnames] }"""
|
|
group_hosts = {}
|
|
for group in self._groups.values():
|
|
group_hosts[group.name] = []
|
|
for host in group.get_hosts():
|
|
group_hosts[group.name].append(host.name)
|
|
return group_hosts
|
|
|
|
def add_service(self, servicename, groupname):
|
|
if groupname not in self._groups:
|
|
raise CommandError('Group name (%s) does not exist'
|
|
% groupname)
|
|
|
|
if servicename not in SERVICE_GROUPS.keys():
|
|
raise CommandError('Service name (%s) does not exist'
|
|
% servicename)
|
|
|
|
group_services = self.get_group_services()
|
|
if servicename not in group_services[groupname]:
|
|
group = self._groups[groupname]
|
|
group.services.append(Service(servicename))
|
|
|
|
def remove_service(self, servicename, groupname):
|
|
if groupname not in self._groups:
|
|
raise CommandError('Group name (%s) does not exist'
|
|
% groupname)
|
|
|
|
if servicename not in SERVICE_GROUPS.keys():
|
|
raise CommandError('Service name (%s) does not exist'
|
|
% servicename)
|
|
|
|
group = self._groups[groupname]
|
|
group.remove_service(servicename)
|
|
|
|
def get_service_groups(self):
|
|
"""return { servicename : groupnames }"""
|
|
service_groups = {}
|
|
group_services = self.get_group_services()
|
|
for servicename in SERVICE_GROUPS.keys():
|
|
service_groups[servicename] = []
|
|
for (groupname, servicenames) in group_services.items():
|
|
if servicename in servicenames:
|
|
service_groups[servicename].append(groupname)
|
|
return service_groups
|
|
|
|
def set_deploy_mode(self, remote_flag):
|
|
if not remote_flag and len(self._hosts) > 1:
|
|
raise CommandError('Cannot set local deploy mode when multiple ' +
|
|
'hosts exist')
|
|
self.remote_mode = remote_flag
|
|
|
|
for group in self._groups.values():
|
|
group.set_remote(remote_flag)
|
|
|
|
def get_ansible_json(self):
|
|
"""generate json inventory for ansible
|
|
|
|
typical ansible json format:
|
|
{
|
|
'group': {
|
|
'hosts': [
|
|
'192.168.28.71',
|
|
'192.168.28.72'
|
|
],
|
|
'vars': {
|
|
'ansible_ssh_user': 'johndoe',
|
|
'ansible_ssh_private_key_file': '~/.ssh/mykey',
|
|
'example_variable': 'value'
|
|
}
|
|
'children': [ 'marietta', '5points' ]
|
|
},
|
|
'_meta': {
|
|
'hostvars': {
|
|
'192.168.28.71': {
|
|
'host_specific_var': 'bar'
|
|
},
|
|
'192.168.28.72': {
|
|
'host_specific_var': 'foo'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
jdict = {}
|
|
|
|
# add hostgroups
|
|
for group in self.get_groups():
|
|
jdict[group.name] = {}
|
|
jdict[group.name]['hosts'] = group.get_hostnames()
|
|
jdict[group.name]['children'] = []
|
|
jdict[group.name]['vars'] = group.get_vars()
|
|
|
|
# add services
|
|
services = []
|
|
for group in self.get_groups():
|
|
for service in group.services:
|
|
if service.name not in jdict:
|
|
services.append(service)
|
|
jdict[service.name] = {}
|
|
jdict[service.name]['vars'] = service.get_vars()
|
|
jdict[service.name]['children'] = []
|
|
if group.name not in jdict[service.name]['children']:
|
|
jdict[service.name]['children'].append(group.name)
|
|
|
|
# add containers
|
|
for service in services:
|
|
for containername in service.containers:
|
|
jdict[containername] = {}
|
|
jdict[containername]['children'] = [service.name]
|
|
jdict[containername]['vars'] = {}
|
|
|
|
# process hosts vars
|
|
jdict['_meta'] = {}
|
|
jdict['_meta']['hostvars'] = {}
|
|
for host in self.get_hosts():
|
|
jdict['_meta']['hostvars'][host.name] = host.get_vars()
|
|
return json.dumps(jdict, indent=2)
|