""" All in one plugin. Installs and configures VDSM on the same host as ovirt-engine """ import logging import sys import os import os.path import urllib2 import crypt import time import nfsutils import traceback import engine_validators as validate import basedefs import common_utils as utils from ovirtsdk.api import API from ovirtsdk.xml import params # Override basedefs default so that status message are aligned basedefs.SPACE_LEN = 80 # Product version MAJOR = '3' MINOR = '1' # Controller object will be initialized from main flow controller = None # Plugin name PLUGIN_NAME = "AIO" PLUGIN_NAME_COLORED = utils.getColoredText(PLUGIN_NAME, basedefs.BLUE) # INFO Messages INFO_CONF_PARAMS_ALL_IN_ONE_USAGE = "Configure all in one" INFO_CONF_PARAMS_ALL_IN_ONE_PROMPT = "Configure VDSM on this host?" INFO_CONF_PARAMS_LOCAL_STORAGE = "Local storage domain path" INFO_LIBVIRT_START = "libvirt service is started" INFO_CREATE_HOST_WAITING_UP = "Waiting for the host to start" # ERROR MESSAGES SYSTEM_ERROR = "System Error" ERROR_CREATE_API_OBJECT = "Error: could not create ovirtsdk API object" ERROR_CREATE_LOCAL_DATACENTER = "Error: Could not create local datacenter" ERROR_CREATE_LOCAL_CLUSTER = "Error: Could not create local cluster" ERROR_CREATE_LOCAL_HOST = "Error: Could not install local host" ERROR_CREATE_HOST_FAILED = "Error: Host was found in a 'Failed' state. Please check engine and bootstrap installation logs." ERROR_CREATE_HOST_TIMEOUT = "Error: Timed out while waiting for host to start" ERROR_ADD_LOCAL_DOMAIN = "Error: could not add local storage domain" ERROR_CREATE_STORAGE_PATH = "Error: could not create local domain path" ERROR_UNSUPPORTED_CPU_MODEL = "Error: This host does not have any of the supported CPU models" ERROR_CAPABILITIES = "Error: couldn't obtain host CPU capabilities." ERROR_LIBVIRT_START = "Error: Could not start libvirt service. Please try \ starting it manually and rerun the setup." ERROR_LIBVIRT_STATUS = "Error: Could not get status of the libvirt service" ERROR_JBOSS_STATUS = "Error: There's a problem with JBoss service.\ Check that it's up and rerun setup." # PARAMS PAUSE = 10 SLEEP_PERIOD = 25 # period in seconds, this is waiting until JBoss is up MAX_CYCLES = 33 # (5.5 (minutes) * 60 )/ 10, since we sleep 10 seconds after each iteration LOCAL_STORAGE_MIN_SIZE = 10 # Size in Gb API_OBJECT_PATH = "https://%s:%s/api" JBOSS_HEALTH_URL = "http://%s:%s/OvirtEngineWeb/HealthStatus" LOCAL_CLUSTER = "local_cluster" LOCAL_DATA_CENTER = "local_datacenter" LOCAL_HOST = "local_host" LOCAL_STORAGE = "local_storage" # PATH PARAMS VDSM_PATH = "/usr/share/vdsm" SHADOW_FILE = "/etc/shadow" logging.debug("plugin %s loaded", __name__) def initConfig(controllerObject): global controller controller = controllerObject logging.debug("Adding parameters for VDSM configuration") paramsList = [{ "CMD_OPTION" : "config-allinone", "USAGE" : INFO_CONF_PARAMS_ALL_IN_ONE_USAGE, "PROMPT" : INFO_CONF_PARAMS_ALL_IN_ONE_PROMPT, "OPTION_LIST" : ["yes", "no"], "VALIDATION_FUNC" : validate.validateOptions, "DEFAULT_VALUE" : "yes", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, "CONF_NAME" : "CONFIG_ALLINONE", "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : True }, { "CMD_OPTION" : "storage-path", "USAGE" : INFO_CONF_PARAMS_LOCAL_STORAGE, "PROMPT" : INFO_CONF_PARAMS_LOCAL_STORAGE, "OPTION_LIST" : [], "VALIDATION_FUNC" : validateStoragePath, "DEFAULT_VALUE" : "", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "STORAGE_PATH", "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : False }, { "CMD_OPTION" : "superuser-pass", "USAGE" : "root password", "PROMPT" : "Confirm root password", "OPTION_LIST" : [], "VALIDATION_FUNC" : validateSuperUserPasswd, "DEFAULT_VALUE" : "", "MASK_INPUT" : True, "LOOSE_VALIDATION": False, "CONF_NAME" : "SUPERUSER_PASS", "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : False} ] groupDict = { "GROUP_NAME" : "VDSM", "DESCRIPTION" : "VDSM Configuration", "PRE_CONDITION" : "CONFIG_ALLINONE", "PRE_CONDITION_MATCH" : "yes", "POST_CONDITION" : False, "POST_CONDITION_MATCH" : True} controller.addGroup(groupDict, paramsList) # Hack: # We disable the question regarding the NFS configuration (ISO domain) because we always want # it in All In One installation. controller.getParamByName("CONFIG_NFS").setKey("USE_DEFAULT", True) controller.getParamByName("CONFIG_NFS").setKey("CONDITION", False) controller.getGroupByName("NFS").setKey("PRE_CONDITION", returnYes) def initSequences(controller): logging.debug("Setting the Sequences for VDSM all in one installation") cpuSteps = [{ 'title' : "%s: Validating CPU Compatibility" % PLUGIN_NAME_COLORED, 'functions' : [startLibvirt, getSupportedCpus, getCPUFamily] } ] logging.debug("Setting sequence to validate cpu") controller.insertSequenceBeforeSequence("Initial Steps", "Validate CPU", [controller.CONF["CONFIG_ALLINONE"]], ["yes"], cpuSteps) # Main AIO sequences aioSteps = [ { 'title' : "%s: Creating storage directory" % PLUGIN_NAME_COLORED, 'functions' : [makeStorageDir] }, { 'title' : "%s: Adding Local Datacenter and cluster" % PLUGIN_NAME_COLORED, 'functions' : [waitForJbossUp, initAPI, createDC, createCluster]}, { 'title' : "%s: Adding Local host (This may take several minutes)" % PLUGIN_NAME_COLORED, 'functions' : [createHost, waitForHostUp]}, { 'title' : "%s: Adding Local storage (This may take several minutes)" % PLUGIN_NAME_COLORED, 'functions' : [addStorageDomain]} ] logging.debug("Adding sequence to create host") controller.addSequence("Local host", [controller.CONF["CONFIG_ALLINONE"]], ["yes"], aioSteps) def startLibvirt(): """ Start service libvirt """ libvirtService = utils.Service("libvirtd") # Check status and return if up output, rc = libvirtService.status() if rc == 0: logging.info(INFO_LIBVIRT_START) return # Otherwise, start the service output, rc = libvirtService.start(True) cycle = 1 while cycle <= MAX_CYCLES: output, rc = libvirtService.status() if rc == 0: logging.info(INFO_LIBVIRT_START) return cycle += 1 time.sleep(PAUSE) raise Exception(ERROR_LIBVIRT_START) def returnYes(controller): return "yes" def waitForJbossUp(): """ Wait for Jboss to start """ utils.retry(isHealthPageUp, tries=25, timeout=15, sleep=5) def initAPI(): global controller logging.debug("Initiating the API object") URL = API_OBJECT_PATH % (controller.CONF["HOST_FQDN"], controller.CONF["HTTPS_PORT"]) USERNAME = 'admin@internal' try: controller.CONF["API_OBJECT"] = API(url=URL, username=USERNAME, password=controller.CONF['AUTH_PASS']) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CREATE_API_OBJECT) def createDC(): global controller logging.debug("Creating the local datacenter") try: controller.CONF["API_OBJECT"].datacenters.add(params.DataCenter(name=LOCAL_DATA_CENTER, storage_type='localfs', version=params.Version(major=MAJOR, minor=MINOR))) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CREATE_LOCAL_DATACENTER) def createCluster(): global controller logging.debug("Creating the local cluster") CPU_TYPE = controller.CONF['VDSM_CPU_FAMILY'] try: controller.CONF["API_OBJECT"].clusters.add(params.Cluster(name=LOCAL_CLUSTER, cpu=params.CPU(id=CPU_TYPE), data_center=controller.CONF["API_OBJECT"].datacenters.get(LOCAL_DATA_CENTER), version=params.Version(major=MAJOR, minor=MINOR))) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CREATE_LOCAL_CLUSTER) def createHost(): global controller logging.debug("Adding the local host") try: controller.CONF["API_OBJECT"].hosts.add(params.Host(name=LOCAL_HOST, address=controller.CONF["HOST_FQDN"], reboot_after_installation=False, cluster=controller.CONF["API_OBJECT"].clusters.get(LOCAL_CLUSTER), root_password=controller.CONF["SUPERUSER_PASS"])) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CREATE_LOCAL_HOST) def waitForHostUp(): # utils.retry(isHostUp, tries=100, timeout=500, sleep=5) utils.retry(isHostUp, tries=40, timeout=300, sleep=5) def isHostUp(): logging.debug("Waiting for host to become operational") try: hostStatus = controller.CONF['API_OBJECT'].hosts.get(LOCAL_HOST).status.state logging.debug("current host status is: %s", hostStatus) if hostStatus == "up": logging.debug("The host is up.") return if "failed" in hostStatus: raise Exception(ERROR_CREATE_HOST_FAILED) raise Exception(INFO_CREATE_HOST_WAITING_UP) except: logging.debug(traceback.format_exc()) raise Exception(ERROR_CREATE_HOST_TIMEOUT) def addStorageDomain(): global controller logging.debug("Adding local storage domain") # strip last '/' from path if it's given. Otherwise, adding storage will # fail. try: logging.info("Creating local storage") stParams = params.Storage(path=controller.CONF["STORAGE_PATH"].rstrip('/')) stParams.set_type('localfs') logging.info("Creating local storage domain") sdParams = params.StorageDomain(name=LOCAL_STORAGE, data_center=controller.CONF["API_OBJECT"].datacenters.get(LOCAL_DATA_CENTER), storage_format='v1', host=controller.CONF["API_OBJECT"].hosts.get(LOCAL_HOST), storage=stParams) sdParams.set_type('data') logging.info("Adding local storage domain") controller.CONF["API_OBJECT"].storagedomains.add(sdParams) except: logging.error(traceback.format_exc()) raise Exception(ERROR_ADD_LOCAL_DOMAIN) def validateSuperUserPasswd(param, options=[]): logging.debug("Validating superuser password") logging.debug("Reading encrypted password from %s", SHADOW_FILE) encryptedPasswd = None with open(SHADOW_FILE, "r") as f: rootString = f.readline() encryptedPasswd = rootString.split(':')[1] # If we encrypt the given string with the salt which is the # encrypted text password, the result should match the encrypted password. # Read more on crypt.crypt if this is giberish to you. if crypt.crypt(param, encryptedPasswd) == encryptedPasswd: logging.debug("Superuser password is validated") return True return False def validateStoragePath(param, options = []): """ Validate that a given path is a valid mount point and has at least LOCAL_STORAGE_MIN_SIZE GB. """ logging.info("Validating provided storage path") if validate.validateMountPoint(param) and validate.validateDirSize(param, LOCAL_STORAGE_MIN_SIZE * 1024): return True return False def makeStorageDir(): logging.debug("Creating/Verifying local domain path") try: if not os.path.exists(controller.CONF["STORAGE_PATH"]): logging.debug("Creating directory %s ", controller.CONF["STORAGE_PATH"]) os.makedirs(controller.CONF["STORAGE_PATH"]) logging.debug("Setting selinux context") nfsutils.setSELinuxContextForDir(controller.CONF["STORAGE_PATH"], nfsutils.SELINUX_RW_LABEL) os.chown(controller.CONF["STORAGE_PATH"], basedefs.CONST_VDSM_UID, basedefs.CONST_KVM_GID) os.chmod(controller.CONF["STORAGE_PATH"], 0755) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CREATE_STORAGE_PATH) def getSupportedCpus(): global controller try: # Load and import the module logging.debug("Attempting to load the caps vdsm module") sys.path.append(VDSM_PATH) moduleobj = __import__('caps', globals()) moduleobj.__file__ = "%s/caps.py" % VDSM_PATH except: raise Exception("Could not find the VDSM caps module in %s" % VDSM_PATH) # Get the list try: controller.CONF['VDSM_SUPPORTED_MODELS'] = moduleobj._getCompatibleCpuModels() logging.debug("Supported CPU models are:") for item in controller.CONF['VDSM_SUPPORTED_MODELS']: logging.debug(item) except: logging.error(traceback.format_exc()) raise Exception(ERROR_CAPABILITIES) def getCPUFamily(): global controller controller.CONF['VDSM_CPU_FAMILY'] = None logging.debug("Determening the CPU family supported by the host") families = {'model_Westmere' : 'Intel Westmere Family', 'model_Nehalem' : 'Intel Nehalem Family', 'model_Penryn' : 'Intel Penryn Family', 'model_Conroe' : 'Intel Conroe Family', 'model_Opteron_G3' : 'AMD Opteron G3', 'model_Opteron_G2' : 'AMD Opteron G2', 'model_Opteron_G1' : 'AMD Opteron G1'} # We loop over the list and not the dictionaries because we want to sort it # per our own priorities. (when looping over a dict, we cannot force a certain order) sortedList = ('model_Westmere', 'model_Nehalem', 'model_Penryn', 'model_Conroe', 'model_Opteron_G3', 'model_Opteron_G2', 'model_Opteron_G1') for model in sortedList: logging.debug("comparing %s with supported models", model) if model in controller.CONF['VDSM_SUPPORTED_MODELS']: logging.debug("Supported model family is: %s", families[model]) controller.CONF['VDSM_CPU_FAMILY'] = families[model] return # we're still here, it means we don't have supported CPU model logging.error(ERROR_UNSUPPORTED_CPU_MODEL) raise Exception(ERROR_UNSUPPORTED_CPU_MODEL) def isHealthPageUp(): """ check if project health page is and accesible will throw exception on error and not return a value """ health_url = JBOSS_HEALTH_URL % (controller.CONF["HOST_FQDN"], controller.CONF["HTTP_PORT"]) logging.debug("Checking JBoss status.") content = getUrlContent(health_url) if content and utils.verifyStringFormat(content, ".*DB Up.*"): logging.info("JBoss is up and running.") return True else: logging.error(ERROR_JBOSS_STATUS) raise Exception(ERROR_JBOSS_STATUS) def getUrlContent(url): try: urlObj = urllib2.urlopen(url) urlContent = urlObj.read() except: return None return urlContent