From 905b0231e93ce2409a45dd6c4f5f983689fdb790 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Wed, 1 Nov 2017 11:56:50 +0800 Subject: Add compass-deck RESTful API and DB Handlers for Compass Change-Id: I1ce411f279943764c286ea48dca9185d453cf254 Signed-off-by: Harry Huang --- .../installers/os_installers/cobbler/cobbler.py | 449 +++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 compass-deck/deployment/installers/os_installers/cobbler/cobbler.py (limited to 'compass-deck/deployment/installers/os_installers/cobbler/cobbler.py') diff --git a/compass-deck/deployment/installers/os_installers/cobbler/cobbler.py b/compass-deck/deployment/installers/os_installers/cobbler/cobbler.py new file mode 100644 index 0000000..302c9be --- /dev/null +++ b/compass-deck/deployment/installers/os_installers/cobbler/cobbler.py @@ -0,0 +1,449 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# 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. + +"""os installer cobbler plugin. +""" +import logging +import os +import shutil +import xmlrpclib + +from compass.deployment.installers.installer import OSInstaller +from compass.deployment.utils import constants as const +from compass.utils import setting_wrapper as compass_setting +from compass.utils import util +from copy import deepcopy + + +NAME = 'CobblerInstaller' + + +class CobblerInstaller(OSInstaller): + """cobbler installer""" + CREDENTIALS = "credentials" + USERNAME = 'username' + PASSWORD = 'password' + + INSTALLER_URL = "cobbler_url" + TMPL_DIR = 'tmpl_dir' + SYS_TMPL = 'system.tmpl' + SYS_TMPL_NAME = 'system.tmpl' + SYS_PROFILE_NAME = 'profile.tmpl' + PROFILE = 'profile' + + POWER_TYPE = 'power_type' + POWER_ADDR = 'power_address' + POWER_USER = 'power_user' + POWER_PASS = 'power_pass' + + def __init__(self, config_manager): + super(CobblerInstaller, self).__init__() + + self.config_manager = config_manager + installer_settings = self.config_manager.get_os_installer_settings() + try: + username = installer_settings[self.CREDENTIALS][self.USERNAME] + password = installer_settings[self.CREDENTIALS][self.PASSWORD] + cobbler_url = installer_settings[self.INSTALLER_URL] + self.tmpl_dir = CobblerInstaller.get_tmpl_path() + + except KeyError as ex: + raise KeyError(ex.message) + + # The connection is created when cobbler installer is initialized. + self.remote = self._get_cobbler_server(cobbler_url) + self.token = self._get_token(username, password) + self.pk_installer_config = None + + logging.debug('%s instance created', 'CobblerInstaller') + + @classmethod + def get_tmpl_path(cls): + return os.path.join(compass_setting.TMPL_DIR, 'cobbler') + + def __repr__(self): + return '%r[remote=%r,token=%r' % ( + self.__class__.__name__, self.remote, self.token) + + def _get_cobbler_server(self, cobbler_url): + if not cobbler_url: + logging.error("Cobbler URL is None!") + raise Exception("Cobbler URL cannot be None!") + + return xmlrpclib.Server(cobbler_url) + + def _get_token(self, username, password): + if self.remote is None: + raise Exception("Cobbler remote instance is None!") + return self.remote.login(username, password) + + def get_supported_oses(self): + """get supported os versions. + + note:: + In cobbler, we treat profile name as the indicator + of os version. It is just a simple indicator + and not accurate. + """ + profiles = self.remote.get_profiles() + oses = [] + for profile in profiles: + oses.append(profile['name']) + return oses + + def deploy(self): + """Sync cobbler to catch up the latest update config and start to + + install OS. Return both cluster and hosts deploy configs. The return + format: + { + "cluster": { + "id": 1, + "deployed_os_config": {}, + }, + "hosts": { + 1($clusterhost_id): { + "deployed_os_config": {...}, + }, + .... + } + } + """ + host_ids = self.config_manager.get_hosts_id_list_for_os_installation() + if not host_ids: + # No hosts need to install OS + logging.info("Cobbler: No host needs to install OS.") + return {} + + os_version = self.config_manager.get_os_version() + profile = self._get_profile_from_server(os_version) + + global_vars_dict = self._get_cluster_tmpl_vars_dict() + + self.update_profile_config_to_cobbler(profile, global_vars_dict) + + hosts_deploy_config = {} + + for host_id in host_ids: + hostname = self.config_manager.get_hostname(host_id) + vars_dict = self._get_host_tmpl_vars_dict(host_id, + global_vars_dict, + hostname=hostname, + profile=profile) + + self.update_host_config_to_cobbler(host_id, hostname, vars_dict) + + # set host deploy config + host_config = {} + host_config[const.DEPLOYED_OS_CONFIG] = vars_dict[const.OS_CONFIG] + hosts_deploy_config[host_id] = host_config + + # sync to cobbler and trigger installtion. + self._sync() + + cluster_config = global_vars_dict.setdefault(const.OS_CONFIG, {}) + + return { + const.CLUSTER: { + const.ID: self.config_manager.get_cluster_id(), + const.DEPLOYED_OS_CONFIG: cluster_config + }, + const.HOSTS: hosts_deploy_config + } + + def clean_progress(self): + """clean log files and config for hosts which to deploy.""" + clusterhost_list = self.config_manager.get_host_id_list() + log_dir_prefix = compass_setting.INSTALLATION_LOGDIR[NAME] + + for host_id in clusterhost_list: + hostname = self.config_manager.get_hostname(host_id) + self._clean_log(log_dir_prefix, hostname) + + def redeploy(self): + """redeploy hosts.""" + host_ids = self.config_manager.get_host_id_list() + if not host_ids: + logging.info("Cobbler: hostlist is None, no host is redeployed") + return + for host_id in host_ids: + hostname = self.config_manager.get_hostname(host_id) + sys_id = self._get_create_system(hostname) + if sys_id: + # enable netboot for this host + self._netboot_enabled(sys_id) + + self._sync() + + def set_package_installer_config(self, package_configs): + """Cobbler can install and configure package installer right after + + OS installation compelets by setting package_config info provided + by package installer. + + :param dict package_configs: The dict of config generated by package + installer for each clusterhost. The IDs + of clusterhosts are the keys of + package_configs. + """ + self.pk_installer_config = package_configs + + def _sync(self): + """Sync the updated config to cobbler and trigger installation.""" + try: + self.remote.sync(self.token) + os.system('sudo service rsyslog restart') + except Exception as ex: + logging.debug("Failed to sync cobbler server! Error: %s" % ex) + raise ex + + def dump_system_info(self, host_id): + + hostname = self.config_manager.get_hostname(host_id) + if self.remote is None or not hostname: + logging.info("[dump_system_info]Remote or hostname is None.") + return {} + + return self.remote.get_system_as_rendered(hostname) + + def _generate_system_config(self, host_id, host_vars_dict): + """Generate updated system config from the template. + + :param host_vars_dict: dict of variables for the system template to + generate system config dict for each host. + """ + os_version = self.config_manager.get_os_version() + + tmpl_path = os.path.join( + os.path.join(self.tmpl_dir, os_version), self.SYS_TMPL_NAME + ) + if not os.path.exists(tmpl_path): + err_msg = "Template '%s' does not exists!" % tmpl_path + logging.error(err_msg) + raise Exception(err_msg) + + system_config = self.get_config_from_template(tmpl_path, + host_vars_dict) + + # update package config info to cobbler ksmeta + if self.pk_installer_config and host_id in self.pk_installer_config: + pk_config = self.pk_installer_config[host_id] + ksmeta = system_config.setdefault("ksmeta", {}) + util.merge_dict(ksmeta, pk_config) + system_config["ksmeta"] = ksmeta + + return system_config + + def _generate_profile_config(self, cluster_vars_dict): + os_version = self.config_manager.get_os_version() + tmpl_path = os.path.join( + os.path.join(self.tmpl_dir, os_version), self.SYS_PROFILE_NAME + ) + + return self.get_config_from_template(tmpl_path, cluster_vars_dict) + + def _get_profile_from_server(self, os_version): + """Get profile from cobbler server.""" + result = self.remote.find_profile({'name': os_version}) + if not result: + raise Exception("Cannot find profile for '%s'", os_version) + + profile = result[0] + return profile + + def _get_create_system(self, hostname): + """get system reference id for the host.""" + sys_name = hostname + sys_id = None + system_info = self.remote.find_system({"name": hostname}) + + if not system_info: + # Create a new system + sys_id = self.remote.new_system(self.token) + self.remote.modify_system(sys_id, "name", hostname, self.token) + logging.debug('create new system %s for %s', sys_id, sys_name) + else: + sys_id = self.remote.get_system_handle(sys_name, self.token) + + return sys_id + + def _get_profile_id(self, profilename): + """get profile reference id for the cluster.""" + return self.remote.get_profile_handle(profilename, self.token) + + def _clean_system(self, hostname): + """clean system.""" + sys_name = hostname + try: + self.remote.remove_system(sys_name, self.token) + logging.debug('system %s is removed', sys_name) + except Exception: + logging.debug('no system %s found to remove', sys_name) + + def _update_system_config(self, sys_id, system_config): + """update modify system.""" + for key, value in system_config.iteritems(): + self.remote.modify_system(sys_id, str(key), value, self.token) + + self.remote.save_system(sys_id, self.token) + + def _update_profile_config(self, profile_id, profile_config): + for key, value in profile_config.iteritems(): + self.remote.modify_profile(profile_id, str(key), value, self.token) + + self.remote.save_profile(profile_id, self.token) + + def _netboot_enabled(self, sys_id): + """enable netboot.""" + self.remote.modify_system(sys_id, 'netboot_enabled', True, self.token) + self.remote.save_system(sys_id, self.token) + + def _clean_log(self, log_dir_prefix, system_name): + """clean log.""" + log_dir = os.path.join(log_dir_prefix, system_name) + shutil.rmtree(log_dir, True) + + def update_host_config_to_cobbler(self, host_id, hostname, host_vars_dict): + """update host config and upload to cobbler server.""" + sys_id = self._get_create_system(hostname) + + system_config = self._generate_system_config(host_id, host_vars_dict) + logging.debug('%s system config to update: %s', host_id, system_config) + + self._update_system_config(sys_id, system_config) + self._netboot_enabled(sys_id) + + def update_profile_config_to_cobbler(self, profilename, cluster_vars_dict): + """update profile config and upload to cobbler server.""" + + profile_id = self._get_profile_id(profilename) + + profile_config = self._generate_profile_config(cluster_vars_dict) + logging.debug( + '%s profile config to update: %s', profilename, profile_config + ) + + self._update_profile_config(profile_id, profile_config) + + def delete_hosts(self): + hosts_id_list = self.config_manager.get_host_id_list() + logging.debug('delete hosts %s', hosts_id_list) + for host_id in hosts_id_list: + self.delete_single_host(host_id) + self._sync() + + def delete_single_host(self, host_id): + """Delete the host from cobbler server and clean up the installation + + progress. + """ + hostname = self.config_manager.get_hostname(host_id) + try: + log_dir_prefix = compass_setting.INSTALLATION_LOGDIR[NAME] + self._clean_system(hostname) + self._clean_log(log_dir_prefix, hostname) + except Exception as ex: + logging.error("Deleting host got exception: %s", ex) + logging.exception(ex) + + def _get_host_tmpl_vars_dict(self, host_id, global_vars_dict, **kwargs): + """Generate template variables dictionary.""" + vars_dict = {} + if global_vars_dict: + # Set cluster template vars_dict from cluster os_config. + vars_dict = deepcopy(global_vars_dict) + + # Set hostname, MAC address and hostname, networks, dns and so on. + host_baseinfo = self.config_manager.get_host_baseinfo(host_id) + vars_dict[const.BASEINFO] = host_baseinfo + + # Set profile + if self.PROFILE in kwargs: + profile = kwargs[self.PROFILE] + else: + os_version = self.config_manager.get_os_version() + profile = self._get_profile_from_server(os_version) + + vars_dict[const.BASEINFO][self.PROFILE] = profile + + metadata = self.config_manager.get_os_config_metadata() + os_config = self.config_manager.get_host_os_config(host_id) + + # Get template variables values from host os_config + host_vars_dict = self.get_tmpl_vars_from_metadata(metadata, os_config) + util.merge_dict( + vars_dict.setdefault(const.OS_CONFIG, {}), host_vars_dict + ) + return vars_dict + + def _get_cluster_tmpl_vars_dict(self): + metadata = self.config_manager.get_os_config_metadata() + os_config = self.config_manager.get_cluster_os_config() + + cluster_vas_dict = {} + cluster_vas_dict[const.OS_CONFIG] = \ + self.get_tmpl_vars_from_metadata(metadata, os_config) + + return cluster_vas_dict + + def _check_and_set_system_impi(self, host_id, sys_id): + if not sys_id: + logging.info("System is None!") + return False + + system = self.dump_system_info(host_id) + if system[self.POWER_TYPE] != 'ipmilan' or not system[self.POWER_USER]: + # Set sytem power type to ipmilan if needs and set IPMI info + ipmi_info = self.config_manager.get_host_ipmi_info(host_id) + if not ipmi_info: + logging.info('No IPMI information found! Failed power on.') + return False + + ipmi_ip, ipmi_user, ipmi_pass = ipmi_info + power_opts = {} + power_opts[self.POWER_TYPE] = 'ipmilan' + power_opts[self.POWER_ADDR] = ipmi_ip + power_opts[self.POWER_USER] = ipmi_user + power_opts[self.POWER_PASS] = ipmi_pass + + self._update_system_config(sys_id, power_opts) + + return True + + def poweron(self, host_id): + hostname = self.config_manager.get_hostname(host_id) + sys_id = self._get_create_system(hostname) + if not self._check_and_set_system_impi(sys_id): + return + + self.remote.power_system(sys_id, self.token, power='on') + logging.info("Host with ID=%d starts to power on!" % host_id) + + def poweroff(self, host_id): + hostname = self.config_manager.get_hostname(host_id) + sys_id = self._get_create_system(hostname) + if not self._check_and_set_system_impi(sys_id): + return + + self.remote.power_system(sys_id, self.token, power='off') + logging.info("Host with ID=%d starts to power off!" % host_id) + + def reset(self, host_id): + hostname = self.config_manager.get_hostname(host_id) + sys_id = self._get_create_system(hostname) + if not self._check_and_set_system_impi(sys_id): + return + + self.remote.power_system(sys_id, self.token, power='reboot') + logging.info("Host with ID=%d starts to reboot!" % host_id) -- cgit 1.2.3-korg