From a3d2a6d6167fbff15355106f7093482c3374bdde Mon Sep 17 00:00:00 2001 From: Martin Magr Date: Tue, 5 Feb 2013 16:03:13 +0100 Subject: [PATCH] Fail when Nova API, Nova network and Swift hosts are not set as IP addresses - Refactor value validator system - cleanup, pep8, documentation, tests and allow multiple validators for each parameter Fixes: rhbz#886603 Change-Id: I1bb3486ed7168bda2855ee877e32423033620ecc --- .gitignore | 4 +- packstack/installer/engine_validators.py | 534 ++++++------------ packstack/installer/exceptions.py | 9 +- packstack/installer/output_messages.py | 4 +- packstack/installer/run_setup.py | 134 ++--- .../sample-project/plugins/createfile_101.py | 62 -- packstack/installer/setup_params.py | 10 +- packstack/modules/ospluginutils.py | 5 +- packstack/plugins/cinder_250.py | 24 +- packstack/plugins/dashboard_500.py | 5 +- packstack/plugins/glance_200.py | 9 +- packstack/plugins/keystone_100.py | 11 +- packstack/plugins/mysql_001.py | 9 +- packstack/plugins/nova_300.py | 78 ++- packstack/plugins/openstack_client_400.py | 5 +- packstack/plugins/prescript_000.py | 13 +- packstack/plugins/qpid_002.py | 5 +- packstack/plugins/serverprep_901.py | 5 +- packstack/plugins/sshkeys_000.py | 4 +- packstack/plugins/swift_600.py | 59 +- run_tests.sh | 2 + tests/test.py | 52 -- tests/test_base.py | 76 +++ tests/test_ospluginutils.py | 6 +- tests/test_plugin_serverprep.py | 7 +- tests/test_validators.py | 82 +++ 26 files changed, 523 insertions(+), 691 deletions(-) delete mode 100644 packstack/installer/sample-project/plugins/createfile_101.py create mode 100755 run_tests.sh delete mode 100644 tests/test.py create mode 100644 tests/test_base.py create mode 100644 tests/test_validators.py diff --git a/.gitignore b/.gitignore index bd95d0df6..726885683 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -*.pyc +*.py[co] *.swp *.log +.tox +packstack.egg-info diff --git a/packstack/installer/engine_validators.py b/packstack/installer/engine_validators.py index 1f9ffd229..35053e855 100644 --- a/packstack/installer/engine_validators.py +++ b/packstack/installer/engine_validators.py @@ -1,411 +1,207 @@ +# -*- coding: utf-8 -*- + """ -contains all available validation functions +Contains all core validation functions. """ -import common_utils as utils -import re -import logging -import output_messages -import basedefs -import socket -import types -import traceback + import os -import os.path +import re +import socket +import logging import tempfile -from setup_controller import Controller +import traceback + +import basedefs +import common_utils as utils + +from .setup_controller import Controller +from .exceptions import ParamValidationError +__all__ = ('ParamValidationError', 'validate_integer', + 'validate_regexp', 'validate_port', 'validate_not_empty', + 'validate_options', 'validate_ip', 'validate_multi_ip', + 'validate_file', 'validate_ping', 'validate_ssh', + 'validate_multi_ssh') -# XXX: Validators should probably only validate (pass on success, -# raise appropriate exception on failure). We should move logging -# and printing probably to run_setup (preferably to single place) -def validateDirSize(path, size): - availableSpace = utils.getAvailableSpace(_getBasePath(path)) - if availableSpace < size: - print output_messages.INFO_VAL_PATH_SPACE % (path, - utils.transformUnits(availableSpace), - utils.transformUnits(size)) - return False - return True - -def validateInteger(param, options=[]): +def validate_integer(param, options=None): + """ + Raises ParamValidationError if given param is not integer. + """ + options = options or [] try: int(param) - return True - except: - logging.warn("validateInteger('%s') - failed" %(param)) - print output_messages.INFO_VAL_NOT_INTEGER - return False + except ValueError: + logging.debug('validate_integer(%s, options=%s) failed.' % + (param, options)) + msg = 'Given value is not an integer: %s' + raise ParamValidationError(msg % param) -def validateRe(param, options=[]): + +def validate_regexp(param, options=None): + """ + Raises ParamValidationError if given param doesn't match at least + one of regular expressions given in options. + """ + options = options or [] for regex in options: if re.search(regex, param): - return True - logging.warn("validateRe('%s') - failed" %(param)) - return False + break + else: + logging.debug('validate_regexp(%s, options=%s) failed.' % + (param, options)) + msg = 'Given value does not match required regular expresion: %s' + raise ParamValidationError(msg % param) -def validatePort(param, options = []): - #TODO: add actual port check with socket open - logging.debug("Validating %s as a valid TCP Port" % (param)) - minVal = 0 - controller = Controller() - isProxyEnabled = utils.compareStrIgnoreCase(controller.CONF["OVERRIDE_HTTPD_CONFIG"], "yes") - if not isProxyEnabled: - minVal = 1024 - if not validateInteger(param, options): - return False + +def validate_port(param, options=None): + """ + Raises ParamValidationError if given param is not a decimal number + in range (0, 65535). + """ + options = options or [] + validate_integer(param, options) port = int(param) - if not (port > minVal and port < 65535) : - logging.warn(output_messages.INFO_VAL_PORT_NOT_RANGE %(minVal)) - print output_messages.INFO_VAL_PORT_NOT_RANGE %(minVal) - return False - (portOpen, process, pid) = utils.isTcpPortOpen(param) - if portOpen: - logging.warn(output_messages.INFO_VAL_PORT_OCCUPIED % (param, process, pid)) - print output_messages.INFO_VAL_PORT_OCCUPIED % (param, process, pid) - return False - if isProxyEnabled and not checkAndSetHttpdPortPolicy(param): - logging.warn(output_messages.INFO_VAL_FAILED_ADD_PORT_TO_HTTP_POLICY, port) - print output_messages.INFO_VAL_FAILED_ADD_PORT_TO_HTTP_POLICY % port - return False - return True + if not (port >= 0 and port < 65535): + logging.debug('validate_port(%s, options=%s) failed.' % + (param, options)) + msg = 'Given value is outside the range of (0, 65535): %s' + raise ParamValidationError(msg % param) -def checkAndSetHttpdPortPolicy(port): - def parsePorts(portsStr): - ports = [] - for part in portsStr.split(","): - part = part.strip().split("-") - if len(part) > 1: - for port in range(int(part[0]),int(part[1])): - ports.append(port) - else: - ports.append(int(part[0])) - return ports - newPort = int(port) - cmd = [ - basedefs.EXEC_SEMANAGE, "port", "-l", - ] - out, rc = utils.execCmd(cmdList=cmd) #, "-t", "http_port_t"]) - if rc: - return False - httpPortsList = [] - pattern = re.compile("^http_port_t\s*tcp\s*([0-9, \-]*)$") - for line in out.splitlines(): - httpPortPolicy = re.match(pattern, line) - if httpPortPolicy: - httpPortsList = parsePorts(httpPortPolicy.groups()[0]) - logging.debug("http_port_t = %s"%(httpPortsList)) - if newPort in httpPortsList: - return True +def validate_not_empty(param, options=None): + """ + Raises ParamValidationError if given param is empty. + """ + options = options or [] + if not param and param is not False: + logging.debug('validate_not_empty(%s, options=%s) failed.' % + (param, options)) + msg = 'Given value is not allowed: %s' + raise ParamValidationError(msg % param) + + +def validate_options(param, options=None): + """ + Raises ParamValidationError if given param is not member of options. + """ + options = options or [] + + validate_not_empty(param, options) + if param not in options: + logging.debug('validate_options(%s, options=%s) failed.' % + (param, options)) + msg = 'Given value is not is allowed values %s: %s' + raise ParamValidationError(msg % (options, param)) + + +def validate_ip(param, options=None): + """ + Raises ParamValidationError if given parameter value is not in IPv4 + or IPv6 address. + """ + for family in (socket.AF_INET, socket.AF_INET6): + try: + socket.inet_pton(family, param) + break + except socket.error: + continue else: - cmd = [ - basedefs.EXEC_SEMANAGE, - "port", - "-a", - "-t", "http_port_t", - "-p", "tcp", - "%d"%(newPort), - ] - out, rc = utils.execCmd(cmdList=cmd, failOnError=False, usePipeFiles=True) - if rc: - logging.error(out) - return False - return True + logging.debug('validate_ip(%s, options=%s) failed.' % + (param, options)) + msg = 'Given host is not in IP address format: %s' + raise ParamValidationError(msg % param) - -def validateRemotePort(param, options = []): - #Validate that the port is an integer betweeen 1024 and 65535 - logging.debug("Validating %s as a valid TCP Port" % (param)) - if validateInteger(param, options): - port = int(param) - if (port > 0 and port < 65535): - return True - else: - logging.warn("validatePort('%s') - failed" %(param)) - print output_messages.INFO_VAL_PORT_NOT_RANGE - - return False - -def validateStringNotEmpty(param, options=[]): - if type(param) != types.StringType or len(param) == 0: - logging.warn("validateStringNotEmpty('%s') - failed" %(param)) - print output_messages.INFO_VAL_STRING_EMPTY %(param) - return False - else: - return True - -def validateOptions(param, options=[]): - logging.info("Validating %s as part of %s"%(param, options)) - if not validateStringNotEmpty(param, options): - return False - if "yes" in options and param.lower() == "y": - return True - if "no" in options and param.lower() == "n": - return True - if param.lower() in [option.lower() for option in options]: - return True - print output_messages.INFO_VAL_NOT_IN_OPTIONS % (", ".join(options)) - return False - -def validateDomain(param, options=[]): +def validate_multi_ip(param, options=None): """ - Validate domain name + Raises ParamValidationError if comma separated IP addresses given + parameter value are in IPv4 or IPv6 aformat. """ - logging.info("validating %s as a valid domain string" % (param)) - (errMsg, rc) = _validateString(param, 1, 1024, "^[\w\-\_]+\.[\w\.\-\_]+\w+$") + for host in param.split(','): + host, device = host.split('/', 1) + validate_ip(host.strip(), options) - # Right now we print a generic error, might want to change it in the future - if rc != 0: - print output_messages.INFO_VAL_NOT_DOMAIN - return False - else: - return True -def validateUser(param, options=[]): +def validate_file(param, options=None): """ - Validate Auth Username - Setting a logical max value of 256 + Raises ParamValidationError if provided file in param does not exist. """ - logging.info("validating %s as a valid user name" % (param)) - (errMsg, rc) = _validateString(param, 1, 256, "^\w[\w\.\-\_\%\@]{2,}$") - - # Right now we print a generic error, might want to change it in the future - if rc != 0: - print output_messages.INFO_VAL_NOT_USER - return False - else: - return True - -def validateRemoteHost(param, options=[]): - """ Validate that the we are working with remote DB host - """ - # If we received localhost, use default flow. - # If not local, REMOTE_DB group is run. - # It means returning True if remote, and False if local - - if "DB_REMOTE_INSTALL" in param.keys() and param["DB_REMOTE_INSTALL"] == "remote": - return True - else: - return False - -def validateFQDN(param, options=[]): - logging.info("Validating %s as a FQDN"%(param)) - if not validateDomain(param,options): - return False - try: - #get set of IPs - ipAddresses = utils.getConfiguredIps() - if len(ipAddresses) < 1: - logging.error("Could not find any configured IP address on the host") - raise Exception(output_messages.ERR_EXP_CANT_FIND_IP) - - #resolve fqdn - pattern = 'Address: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' - resolvedAddresses = _getPatternFromNslookup(param, pattern) - if len(resolvedAddresses) < 1: - logging.error("Failed to resolve %s"%(param)) - print output_messages.ERR_DIDNT_RESOLVED_IP%(param) - return False - - #string is generated here since we use it in all latter error messages - prettyString = " ".join(["%s"%string for string in resolvedAddresses]) - - #compare found IP with list of local IPs and match. - if not resolvedAddresses.issubset(ipAddresses): - logging.error("the following address(es): %s are not configured on this host"%(prettyString)) - #different grammar for plural and single - if len(resolvedAddresses) > 1: - print output_messages.ERR_IPS_NOT_CONFIGED%(prettyString, param) - else: - print output_messages.ERR_IPS_NOT_CONFIGED_ON_INT%(prettyString, param) - return False - - #reverse resolved IP and compare with given fqdn - counter = 0 - pattern = '[\w\.-]+\s+name\s\=\s([\w\.\-]+)\.' - for address in resolvedAddresses: - addressSet = _getPatternFromNslookup(address, pattern) - reResolvedAddress = None - if len(addressSet) > 0: - reResolvedAddress = addressSet.pop() - if reResolvedAddress == param: - counter += 1 - else: - logging.warn("%s did not reverse-resolve into %s"%(address,param)) - if counter < 1: - logging.error("The following addresses: %s did not reverse resolve into %s"%(prettyString, param)) - #different grammar for plural and single - if len(resolvedAddresses) > 1: - print output_messages.ERR_IPS_HAS_NO_PTR%(prettyString, param) - else: - print output_messages.ERR_IP_HAS_NO_PTR%(prettyString, param) - return False - - #conditions passed - return True - except: - logging.error(traceback.format_exc()) - raise - -def validateFile(param, options=[]): - """ - Check that provided param is a file - """ - if not validateStringNotEmpty(param): - return False + options = options or [] + validate_not_empty(param) if not os.path.isfile(param): - print "\n" + output_messages.ERR_FILE + ".\n" - return False + logging.debug('validate_file(%s, options=%s) failed.' % + (param, options)) + msg = 'Given file does not exist: %s' + raise ParamValidationError(msg % param) - return True -def validatePing(param, options=[]): +def validate_ping(param, options=None): """ - Check that provided host answers to ping + Raises ParamValidationError if provided host does not answer to ICMP + echo request. """ - if validateStringNotEmpty(param): - cmd = [ - "/bin/ping", - "-c", "1", - "%s" % param, - ] - out, rc = utils.execCmd(cmdList=cmd) - if rc == 0: - return True + options = options or [] + validate_not_empty(param) - print "\n" + output_messages.ERR_PING + " %s .\n"%param - return False + cmd = ["/bin/ping", "-c", "1", str(param)] + out, rc = utils.execCmd(cmdList=cmd) + if rc != 0: + logging.debug('validate_ping(%s, options=%s) failed.' % + (param, options)) + msg = 'Given host is unreachable: %s' + raise ParamValidationError(msg % param) -def validateMultiPing(param, options=[]): - if validateStringNotEmpty(param): - hosts = param.split(",") - for host in hosts: - if validatePing(host.strip()) == False: - return False - return True - print "\n" + output_messages.ERR_PING + ".\n" - return False -_testedPorts = [] -def validatePort(host, port): +def validate_multi_ping(param, options=None): """ - Check that provided host is listening on provided port + Raises ParamValidationError if comma separated host given in param + do not answer to ICMP echo request. """ - key = "%s:%d"%(host, port) - # No need to keep checking the same port multiple times - if key in _testedPorts: - return True + options = options or [] + validate_not_empty(param) + for host in param.split(","): + validate_ping(host.strip()) + + +_tested_ports = [] +def touch_port(host, port): + """ + Check that provided host is listening on provided port. + """ + key = "%s:%d" % (host, port) + if key in _tested_ports: + return s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.shutdown(socket.SHUT_RDWR) + s.close() + _tested_ports.append(key) + + +def validate_ssh(param, options=None): + """ + Raises ParamValidationError if provided host does not listen + on port 22. + """ + options = options or [] try: - s.connect((host, port)) - s.shutdown(socket.SHUT_RDWR) - s.close() - except socket.error as msg: - return False - _testedPorts.append(key) - return True + touch_port(param.strip(), 22) + except socket.error: + logging.debug('validate_ssh(%s, options=%s) failed.' % + (param, options)) + msg = 'Given host does not listen on port 22: %s' + raise ParamValidationError(msg % param) -def validateSSH(param, options=[]): + +def validate_multi_ssh(param, options=None): """ - Check that provided host is listening on port 22 + Raises ParamValidationError if comma separated host provided + in param do not listen on port 22. """ - if validatePort(param.strip(), 22): - return True - print "\n" + output_messages.ERR_SSH%param - return False - -def validateMultiSSH(param, options=[]): - if validateStringNotEmpty(param): - hosts = param.split(",") - for host in hosts: - if validateSSH(host) == False: - return False - return True - print "\n" + output_messages.ERR_SSH%param + ".\n" - return False - -def _validateString(string, minLen, maxLen, regex=".*"): - """ - Generic func to verify a string - match its min/max length - and doesn't contain illegal chars - - The func returns various return codes according to the error - plus a default error message - the calling func can decide if to use to default error msg - or to use a more specific one according the RC. - Return codes: - 1 - string length is less than min - 2 - string length is more tham max - 3 - string contain illegal chars - 0 - success - """ - # String length is less than minimum allowed - if len(string) < minLen: - msg = output_messages.INFO_STRING_LEN_LESS_THAN_MIN % (minLen) - return(msg, 1) - # String length is more than max allowed - elif len(string) > maxLen: - msg = output_messages.INFO_STRING_EXCEEDS_MAX_LENGTH % (maxLen) - return(msg, 2) - # String contains illegal chars - elif not utils.verifyStringFormat(string, regex): - return(output_messages.INFO_STRING_CONTAINS_ILLEGAL_CHARS, 3) - else: - # Success - return (None, 0) - -def _getPatternFromNslookup(address, pattern): - rePattern = re.compile(pattern) - addresses = set() - output = utils.nslookup(address) - list = output.splitlines() - #do not go over the first 2 lines in nslookup output - for line in list[2:]: - found = rePattern.search(line) - if found: - foundAddress = found.group(1) - logging.debug("%s resolved into %s"%(address, foundAddress)) - addresses.add(foundAddress) - return addresses - -def _getBasePath(path): - if os.path.exists(path): - return path - - # Iterate up in the tree structure until we get an - # existing path - return _getBasePath(os.path.dirname(path.rstrip("/"))) - -def _isPathWriteable(path): - try: - logging.debug("attempting to write temp file to %s" % (path)) - tempfile.TemporaryFile(dir=path) - return True - except: - logging.warning(traceback.format_exc()) - logging.warning("%s is not writeable" % path) - return False - -def r_validateIF(server, device): - """ Validate that a network interface exists on a remote host """ - server.append("ifconfig %s || ( echo Device %s does not exist && exit 1 )"%(device, device)) - -def r_validateDevice(server, device=None): - if device: - # the device MUST exist - server.append('ls -l /dev/%s'%device) - - # if it is not mounted then we can use it - server.append('grep "/dev/%s " /proc/self/mounts || exit 0'%device) - - # if it is mounted then the mount point has to be in /srv/node - server.append('grep "/dev/%s /srv/node" /proc/self/mounts && exit 0'%device) - - # if we got here without exiting then we can't use this device - server.append('exit 1') - return False + options = options or [] + validate_not_empty(param) + for host in param.split(","): + validate_ssh(host) diff --git a/packstack/installer/exceptions.py b/packstack/installer/exceptions.py index 101ab9e9a..26c233596 100644 --- a/packstack/installer/exceptions.py +++ b/packstack/installer/exceptions.py @@ -20,22 +20,25 @@ class PackStackError(Exception): class MissingRequirements(PackStackError): - """Raised when minimum install requirements are not met""" + """Raised when minimum install requirements are not met.""" pass class InstallError(PackStackError): pass - class FlagValidationError(InstallError): + """Raised when single flag validation fails.""" + pass + +class ParamValidationError(InstallError): + """Raised when parameter value validation fails.""" pass class PluginError(PackStackError): pass - class ParamProcessingError(PluginError): pass diff --git a/packstack/installer/output_messages.py b/packstack/installer/output_messages.py index 17b1b590c..7858e1566 100644 --- a/packstack/installer/output_messages.py +++ b/packstack/installer/output_messages.py @@ -40,8 +40,8 @@ INFO_VAL_PORT_OCCUPIED="Error: TCP Port %s is already open by %s (pid: %s)" INFO_VAL_PORT_OCCUPIED_BY_JBOSS="Error: TCP Port %s is used by JBoss" INFO_VAL_PASSWORD_DONT_MATCH="Error: passwords don't match" -INFO_VAL_IS_HOSTNAME = ("Packstack changed given hostname %s to IP " - "address %s.") +INFO_CHANGED_VALUE = ("Packstack changed given value %s to required " + "value %s") WARN_VAL_IS_HOSTNAME = ("Warning: Packstack failed to change given " "hostname %s to IP address. Note that some " "services might not run correctly when hostname" diff --git a/packstack/installer/run_setup.py b/packstack/installer/run_setup.py index 8d27f204c..cc39cede6 100644 --- a/packstack/installer/run_setup.py +++ b/packstack/installer/run_setup.py @@ -17,7 +17,7 @@ import common_utils as utils import engine_processors as process import engine_validators as validate import output_messages -from .exceptions import FlagValidationError +from .exceptions import FlagValidationError, ParamValidationError from setup_controller import Controller @@ -77,8 +77,9 @@ def _getInputFromUser(param): message = StringIO() message.write(param.getKey("PROMPT")) - if not param.getKey("VALIDATION_FUNC") == validate.validateRe \ - and param.getKey("OPTION_LIST"): + validators = param.getKey("VALIDATORS") or [] + if validate.validate_regexp not in validators \ + and param.getKey("OPTION_LIST"): message.write(" [%s]" % "|".join(param.getKey("OPTION_LIST"))) if param.getKey("DEFAULT_VALUE"): @@ -99,62 +100,33 @@ def _getInputFromUser(param): userInput = param.getKey("DEFAULT_VALUE") # Param processing - try: - logging.debug("Processing value of parameter " - "%s." % param.getKey("CONF_NAME")) - processFunc = param.getKey("PROCESSOR_FUNC") - try: - processArgs = param.getKey("PROCESSOR_ARGS") - except KeyError: - processArgs = None - try: - _userInput = processFunc(userInput, processArgs) - if userInput != _userInput: - msg = output_messages.INFO_VAL_IS_HOSTNAME - print msg % (userInput, _userInput) - userInput = _userInput - else: - logging.debug("Processor returned the original " - "value: %s" % _userInput) - except process.ParamProcessingError, ex: - try: - cn = param.getKey("CONF_NAME") - msg = param.getKey("PROCESSOR_MSG") - print getattr(output_messages, msg) % cn - except KeyError: - logging.debug("Value processing of parameter " - "%s failed." % param.getKey("CONF_NAME")) - except KeyError: - logging.debug("Parameter %s doesn't have value " - "processor." % param.getKey("CONF_NAME")) + userInput = process_param_value(param, userInput) # If param requires validation - if param.getKey("VALIDATION_FUNC")(userInput, param.getKey("OPTION_LIST")): - if "yes" in param.getKey("OPTION_LIST") and userInput.lower() == "y": - userInput = "yes" - if "no" in param.getKey("OPTION_LIST") and userInput.lower() == "n": - userInput = "no" + try: + validate_param_value(param, userInput) controller.CONF[param.getKey("CONF_NAME")] = userInput loop = False - # If validation failed but LOOSE_VALIDATION is true, ask user - elif param.getKey("LOOSE_VALIDATION"): - answer = _askYesNo("User input failed validation, do you still wish to use it") - if answer: - loop = False - controller.CONF[param.getKey("CONF_NAME")] = userInput + except ParamValidationError: + if param.getKey("LOOSE_VALIDATION"): + # If validation failed but LOOSE_VALIDATION is true, ask user + answer = _askYesNo("User input failed validation, " + "do you still wish to use it") + loop = not answer + if answer: + controller.CONF[param.getKey("CONF_NAME")] = userInput + continue + else: + if commandLineValues.has_key(param.getKey("CONF_NAME")): + del commandLineValues[param.getKey("CONF_NAME")] else: + # Delete value from commandLineValues so that we will prompt the user for input if commandLineValues.has_key(param.getKey("CONF_NAME")): del commandLineValues[param.getKey("CONF_NAME")] loop = True - else: - # Delete value from commandLineValues so that we will prompt the user for input - if commandLineValues.has_key(param.getKey("CONF_NAME")): - del commandLineValues[param.getKey("CONF_NAME")] - loop = True - except KeyboardInterrupt: print "" # add the new line so messages wont be displayed in the same line as the question - raise KeyboardInterrupt + raise except: logging.error(traceback.format_exc()) raise Exception(output_messages.ERR_EXP_READ_INPUT_PARAM % (param.getKey("CONF_NAME"))) @@ -172,7 +144,7 @@ def input_param(param): confirmedParamName = param.getKey("CONF_NAME") + "_CONFIRMED" confirmedParam.setKey("CONF_NAME", confirmedParamName) confirmedParam.setKey("PROMPT", output_messages.INFO_CONF_PARAMS_PASSWD_CONFIRM_PROMPT) - confirmedParam.setKey("VALIDATION_FUNC", validate.validateStringNotEmpty) + confirmedParam.setKey("VALIDATORS", [validate.validate_not_empty]) # Now get both values from user (with existing validations while True: _getInputFromUser(param) @@ -285,35 +257,39 @@ def maskString(str): str = str.replace(password, '*'*8) return str -def _validateParamValue(param, paramValue): - validateFunc = param.getKey("VALIDATION_FUNC") - optionsList = param.getKey("OPTION_LIST") - logging.debug("validating param %s in answer file." % param.getKey("CONF_NAME")) - if not validateFunc(paramValue, optionsList): - raise Exception(output_messages.ERR_EXP_VALIDATE_PARAM % param.getKey("CONF_NAME")) +def validate_param_value(param, value): + cname = param.getKey("CONF_NAME") + logging.debug("Validating parameter %s." % cname) -def _processParamValue(param, paramValue): - try: - processFunc = param.getKey("PROCESSOR_FUNC") - except KeyError: - return paramValue - try: - processArgs = param.getKey("PROCESSOR_ARGS") - except KeyError: - processArgs = None - logging.debug("processing param %s in answer file." % param.getKey("CONF_NAME")) - try: - return processFunc(paramValue, processArgs) - except process.ParamProcessingError, ex: - cn = param.getKey("CONF_NAME") - logging.debug("processing param %s failed, falling back to " - "original, reason: %s" % (cn, ex)) + validators = param.getKey("VALIDATORS") or [] + opt_list = param.getKey("OPTION_LIST") + for val_func in validators: try: - msg = param.getKey("PROCESSOR_MSG") - print getattr(output_messages, msg) % cn - except KeyError: - pass - return paramValue + val_func(value, opt_list) + except ParamValidationError as ex: + print 'Parameter %s failed validation: %s' % (cname, ex) + raise + +def process_param_value(param, value): + _value = value + processors = param.getKey("PROCESSORS") or [] + for proc_func in processors: + logging.debug("Processing value of parameter " + "%s." % param.getKey("CONF_NAME")) + try: + new_value = proc_func(_value, controller.CONF) + if new_value != _value: + msg = output_messages.INFO_CHANGED_VALUE + print msg % (_value, new_value) + _value = new_value + else: + logging.debug("Processor returned the original " + "value: %s" % _value) + except process.ParamProcessingError, ex: + print ("Value processing of parameter %s " + "failed.\n%s" % (param.getKey("CONF_NAME"), ex)) + raise + return _value def _handleGroupCondition(config, conditionName, conditionValue): """ @@ -349,8 +325,8 @@ def _loadParamFromFile(config, section, paramName): # Validate param value using its validation func param = controller.getParamByName(paramName) - value = _processParamValue(param, value) - _validateParamValue(param, value) + value = process_param_value(param, value) + validate_param_value(param, value) # Keep param value in our never ending global conf controller.CONF[param.getKey("CONF_NAME")] = value diff --git a/packstack/installer/sample-project/plugins/createfile_101.py b/packstack/installer/sample-project/plugins/createfile_101.py deleted file mode 100644 index 94e78236b..000000000 --- a/packstack/installer/sample-project/plugins/createfile_101.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Creates a Sample File -""" - -import logging -import os - -import engine_validators as validate -import basedefs -import common_utils as utils - -# Controller object will be initialized from main flow -controller = None - -logging.debug("plugin %s loaded", __name__) - -def initConfig(controllerObject): - global controller - controller = controllerObject - logging.debug("Initialising Plugine") - conf_params = {"SAMPLE": [ - {"CMD_OPTION" : "filename", - "USAGE" : "File to create", - "PROMPT" : "File to create", - "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, - "DEFAULT_VALUE" : "/tmp/samplefile.txt", - "MASK_INPUT" : False, - "LOOSE_VALIDATION": True, - "CONF_NAME" : "CONFIG_FILENAME", - "USE_DEFAULT" : False, - "NEED_CONFIRM" : False, - "CONDITION" : False }, - ] - } - - conf_groups = [ - { "GROUP_NAME" : "SAMPLE", - "DESCRIPTION" : "Sample config group", - "PRE_CONDITION" : utils.returnYes, - "PRE_CONDITION_MATCH" : "yes", - "POST_CONDITION" : False, - "POST_CONDITION_MATCH" : True}, - ] - - for group in conf_groups: - paramList = conf_params[group["GROUP_NAME"]] - controller.addGroup(group, paramList) - - - -def initSequences(controller): - preparesteps = [ - {'title': 'Create File', 'functions':[createfile]} - ] - controller.addSequence("Creating File", [], [], preparesteps) - - -def createfile(): - with open(controller.CONF["CONFIG_FILENAME"], "a") as fp: - fp.write("HELLO WORLD") - diff --git a/packstack/installer/setup_params.py b/packstack/installer/setup_params.py index aa4e6e99f..5a92ff5cf 100644 --- a/packstack/installer/setup_params.py +++ b/packstack/installer/setup_params.py @@ -3,9 +3,9 @@ Container set for groups and parameters """ class Param(object): allowed_keys = ('CMD_OPTION','USAGE','PROMPT','OPTION_LIST', - 'PROCESSOR_ARGS', 'PROCESSOR_FUNC', 'PROCESSOR_MSG', - 'VALIDATION_FUNC','DEFAULT_VALUE','MASK_INPUT','LOOSE_VALIDATION', - 'CONF_NAME','USE_DEFAULT','NEED_CONFIRM','CONDITION') + 'PROCESSORS', 'VALIDATORS','DEFAULT_VALUE', + 'MASK_INPUT', 'LOOSE_VALIDATION', 'CONF_NAME', + 'USE_DEFAULT','NEED_CONFIRM','CONDITION') def __init__(self, attributes=None): if not attributes: @@ -25,10 +25,10 @@ class Param(object): def getKey(self, key): self.validateKey(key) - return self.__ATTRIBUTES[key] + return self.__ATTRIBUTES.get(key) def validateKey(self, key): - if not self.__ATTRIBUTES.has_key(key): + if key not in self.allowed_keys: raise KeyError("%s is not a valid key" % key) class Group(Param): diff --git a/packstack/modules/ospluginutils.py b/packstack/modules/ospluginutils.py index 2c9067169..29f0933d5 100644 --- a/packstack/modules/ospluginutils.py +++ b/packstack/modules/ospluginutils.py @@ -5,6 +5,7 @@ import re from packstack.installer import basedefs from packstack.installer.setup_controller import Controller +from packstack.installer.exceptions import PackStackError controller = Controller() @@ -12,10 +13,6 @@ PUPPET_DIR = os.path.join(basedefs.DIR_PROJECT_DIR, "puppet") PUPPET_TEMPLATE_DIR = os.path.join(PUPPET_DIR, "templates") -class PackStackError(Exception): - pass - - class NovaConfig(object): """ Helper class to create puppet manifest entries for nova_config diff --git a/packstack/plugins/cinder_250.py b/packstack/plugins/cinder_250.py index cc88313fc..a613f4941 100644 --- a/packstack/plugins/cinder_250.py +++ b/packstack/plugins/cinder_250.py @@ -34,11 +34,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install Cinder", "PROMPT" : "Enter the IP address of the Cinder server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_CINDER_HOST", @@ -49,7 +46,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Cinder to access DB", "PROMPT" : "Enter the password for the Cinder DB access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -61,7 +58,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Cinder to authenticate with Keystone", "PROMPT" : "Enter the password for the Cinder Keystone access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -73,7 +70,7 @@ def initConfig(controllerObject): "USAGE" : "Cinder's volumes group size", "PROMPT" : "Enter Cinder's volumes group size", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "2G", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -85,7 +82,7 @@ def initConfig(controllerObject): "USAGE" : "Cinder's volumes group path", "PROMPT" : "Enter Cinder's volumes group path", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "/var/lib/cinder", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -97,7 +94,7 @@ def initConfig(controllerObject): "USAGE" : "Cinder's volumes group name", "PROMPT" : "Enter Cinder's volumes group name", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "cinder-volumes", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -109,7 +106,7 @@ def initConfig(controllerObject): "USAGE" : "Create Cinder's volumes group", "PROMPT" : "Should Cinder's volumes group be created?", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : createVolume, + "VALIDATORS" : [validate.validate_options, createVolume], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -141,18 +138,19 @@ def initSequences(controller): controller.addSequence("Installing OpenStack Cinder", [], [], cindersteps) -def createVolume(param, options=[]): +def createVolume(param, options=None): """ Check that provided host is listening on port 22 """ + options = options or [] if param == "n": - return True + return + # XXX: For this case it probably is better (cleaner) to use processor instead of validator for option in ['CONFIG_CINDER_VOLUMES_SIZE', 'CONFIG_CINDER_VOLUMES_PATH']: param = controller.getParamByName(option) param.setKey('USE_DEFAULT', False) setup.input_param(param) - return True def checkcindervg(): diff --git a/packstack/plugins/dashboard_500.py b/packstack/plugins/dashboard_500.py index fd6d38b89..72f7bdc37 100644 --- a/packstack/plugins/dashboard_500.py +++ b/packstack/plugins/dashboard_500.py @@ -30,11 +30,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install Horizon", "PROMPT" : "Enter the IP address of the Horizon server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_HORIZON_HOST", diff --git a/packstack/plugins/glance_200.py b/packstack/plugins/glance_200.py index 4c85621c9..37d7bb023 100644 --- a/packstack/plugins/glance_200.py +++ b/packstack/plugins/glance_200.py @@ -30,11 +30,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install Glance", "PROMPT" : "Enter the IP address of the Glance server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_GLANCE_HOST", @@ -45,7 +42,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Glance to access DB", "PROMPT" : "Enter the password for the Glance DB access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -57,7 +54,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Glance to authenticate with Keystone", "PROMPT" : "Enter the password for the Glance Keystone access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, diff --git a/packstack/plugins/keystone_100.py b/packstack/plugins/keystone_100.py index 9f2f1900c..c59179741 100644 --- a/packstack/plugins/keystone_100.py +++ b/packstack/plugins/keystone_100.py @@ -31,11 +31,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install Keystone", "PROMPT" : "Enter the IP address of the Keystone server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_KEYSTONE_HOST", @@ -46,7 +43,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Keystone to access DB", "PROMPT" : "Enter the password for the Keystone DB access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -58,7 +55,7 @@ def initConfig(controllerObject): "USAGE" : "The token to use for the Keystone service api", "PROMPT" : "The token to use for the Keystone service api", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex, "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -70,7 +67,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Keystone admin user", "PROMPT" : "Enter the password for the Keystone admin user", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, diff --git a/packstack/plugins/mysql_001.py b/packstack/plugins/mysql_001.py index 5a2c16c8b..61398f010 100644 --- a/packstack/plugins/mysql_001.py +++ b/packstack/plugins/mysql_001.py @@ -30,11 +30,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install MySQL", "PROMPT" : "Enter the IP address of the MySQL server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_MYSQL_HOST", @@ -45,7 +42,7 @@ def initConfig(controllerObject): "USAGE" : "Username for the MySQL admin user", "PROMPT" : "Enter the username for the MySQL admin user", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "root", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -57,7 +54,7 @@ def initConfig(controllerObject): "USAGE" : "Password for the MySQL admin user", "PROMPT" : "Enter the password for the MySQL admin user", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": True, diff --git a/packstack/plugins/nova_300.py b/packstack/plugins/nova_300.py index 01ad9444a..ef85697e7 100644 --- a/packstack/plugins/nova_300.py +++ b/packstack/plugins/nova_300.py @@ -28,11 +28,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the Nova API service", "PROMPT" : "Enter the IP address of the Nova API service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ip, validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_NOVA_API_HOST", @@ -43,11 +40,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the Nova Cert service", "PROMPT" : "Enter the IP address of the Nova Cert service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_NOVA_CERT_HOST", @@ -58,11 +52,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the Nova VNC proxy", "PROMPT" : "Enter the IP address of the Nova VNC proxy", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_NOVA_VNCPROXY_HOST", @@ -73,11 +64,11 @@ def initConfig(controllerObject): "USAGE" : "A comma separated list of IP addresses on which to install the Nova Compute services", "PROMPT" : "Enter a comma separated list of IP addresses on which to install the Nova Compute services", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateMultiSSH, + "VALIDATORS" : [validate.validate_multi_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), "MASK_INPUT" : False, "LOOSE_VALIDATION": True, - "CONF_NAME" : "CONFIG_NOVA_COMPUTE_HOSTS", # TO-DO: Create processor for CSV + "CONF_NAME" : "CONFIG_NOVA_COMPUTE_HOSTS", "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : False }, @@ -85,7 +76,7 @@ def initConfig(controllerObject): "USAGE" : "Private interface for Flat DHCP on the Nova compute servers", "PROMPT" : "Enter the Private interface for Flat DHCP on the Nova compute servers", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "eth1", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -97,11 +88,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the Nova Network service", "PROMPT" : "Enter the IP address of the Nova Network service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ip, validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_NOVA_NETWORK_HOST", @@ -112,7 +100,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Nova to access DB", "PROMPT" : "Enter the password for the Nova DB access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -124,7 +112,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Nova to authenticate with Keystone", "PROMPT" : "Enter the password for the Nova Keystone access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -136,7 +124,7 @@ def initConfig(controllerObject): "USAGE" : "Public interface on the Nova network server", "PROMPT" : "Enter the Public interface on the Nova network server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "eth0", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -148,7 +136,7 @@ def initConfig(controllerObject): "USAGE" : "Private interface for Flat DHCP on the Nova network server", "PROMPT" : "Enter the Private interface for Flat DHCP on the Nova network server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : "eth1", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -160,7 +148,7 @@ def initConfig(controllerObject): "USAGE" : "IP Range for Flat DHCP", "PROMPT" : "Enter the IP Range for Flat DHCP", "OPTION_LIST" : ["^([\d]{1,3}\.){3}[\d]{1,3}/\d\d?$"], - "VALIDATION_FUNC" : validate.validateRe, + "VALIDATORS" : [validate.validate_regexp], "DEFAULT_VALUE" : "192.168.32.0/22", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -172,7 +160,7 @@ def initConfig(controllerObject): "USAGE" : "IP Range for Floating IP's", "PROMPT" : "Enter the IP Range for Floating IP's", "OPTION_LIST" : ["^([\d]{1,3}\.){3}[\d]{1,3}/\d\d?$"], - "VALIDATION_FUNC" : validate.validateRe, + "VALIDATORS" : [validate.validate_regexp], "DEFAULT_VALUE" : "10.3.4.0/22", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -184,11 +172,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the Nova Scheduler service", "PROMPT" : "Enter the IP address of the Nova Scheduler service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_NOVA_SCHED_HOST", @@ -204,6 +189,7 @@ def initConfig(controllerObject): "POST_CONDITION_MATCH" : True} controller.addGroup(groupDict, paramsList) + def initSequences(controller): if controller.CONF['CONFIG_NOVA_INSTALL'] != 'y': return @@ -220,59 +206,71 @@ def initSequences(controller): ] controller.addSequence("Installing OpenStack Nova API", [], [], novaapisteps) + def createapimanifest(): manifestfile = "%s_api_nova.pp"%controller.CONF['CONFIG_NOVA_API_HOST'] manifestdata = getManifestTemplate("nova_api.pp") appendManifestFile(manifestfile, manifestdata, 'novaapi') + def createkeystonemanifest(): manifestfile = "%s_keystone.pp"%controller.CONF['CONFIG_KEYSTONE_HOST'] manifestdata = getManifestTemplate("keystone_nova.pp") appendManifestFile(manifestfile, manifestdata) + def createcertmanifest(): manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_CERT_HOST'] manifestdata = getManifestTemplate("nova_cert.pp") appendManifestFile(manifestfile, manifestdata) + +def check_ifcfg(host, device): + """ + Raises ScriptRuntimeError if given host does not have give device. + """ + server = utils.ScriptRunner(host) + cmd = "ifconfig %s || ( echo Device %s does not exist && exit 1 )" + server.append(cmd % (device, device)) + server.execute() + + def createcomputemanifest(): for host in controller.CONF["CONFIG_NOVA_COMPUTE_HOSTS"].split(","): controller.CONF["CONFIG_NOVA_COMPUTE_HOST"] = host manifestdata = getManifestTemplate("nova_compute.pp") manifestfile = "%s_nova.pp"%host - server = utils.ScriptRunner(host) nova_config_options = NovaConfig() - if host != controller.CONF["CONFIG_NOVA_NETWORK_HOST"]: nova_config_options.addOption("flat_interface", controller.CONF['CONFIG_NOVA_COMPUTE_PRIVIF']) - validate.r_validateIF(server, controller.CONF['CONFIG_NOVA_COMPUTE_PRIVIF']) + check_ifcfg(host, controller.CONF['CONFIG_NOVA_COMPUTE_PRIVIF']) - server.execute() appendManifestFile(manifestfile, manifestdata + "\n" + nova_config_options.getManifestEntry()) + def createnetworkmanifest(): - hostname = controller.CONF['CONFIG_NOVA_NETWORK_HOST'] + host = controller.CONF['CONFIG_NOVA_NETWORK_HOST'] + check_ifcfg(host, controller.CONF['CONFIG_NOVA_NETWORK_PRIVIF']) + check_ifcfg(host, controller.CONF['CONFIG_NOVA_NETWORK_PUBIF']) - server = utils.ScriptRunner(hostname) - validate.r_validateIF(server, controller.CONF['CONFIG_NOVA_NETWORK_PRIVIF']) - validate.r_validateIF(server, controller.CONF['CONFIG_NOVA_NETWORK_PUBIF']) - server.execute() - - manifestfile = "%s_nova.pp"%hostname + manifestfile = "%s_nova.pp" % host manifestdata = getManifestTemplate("nova_network.pp") appendManifestFile(manifestfile, manifestdata) + def createschedmanifest(): manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_SCHED_HOST'] manifestdata = getManifestTemplate("nova_sched.pp") appendManifestFile(manifestfile, manifestdata) + def createvncproxymanifest(): manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_VNCPROXY_HOST'] manifestdata = getManifestTemplate("nova_vncproxy.pp") appendManifestFile(manifestfile, manifestdata) + def createcommonmanifest(): for manifestfile, marker in manifestfiles.getFiles(): if manifestfile.endswith("_nova.pp"): diff --git a/packstack/plugins/openstack_client_400.py b/packstack/plugins/openstack_client_400.py index d98cff020..59f425fbf 100644 --- a/packstack/plugins/openstack_client_400.py +++ b/packstack/plugins/openstack_client_400.py @@ -29,11 +29,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the OpenStack client packages. An admin \"rc\" file will also be installed", "PROMPT" : "Enter the IP address of the client server", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_OSCLIENT_HOST", diff --git a/packstack/plugins/prescript_000.py b/packstack/plugins/prescript_000.py index 55f124dd2..2ba6635d0 100644 --- a/packstack/plugins/prescript_000.py +++ b/packstack/plugins/prescript_000.py @@ -29,7 +29,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install Glance", "PROMPT" : "Should Packstack install Glance image service", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -41,7 +41,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install Cinder", "PROMPT" : "Should Packstack install Cinder volume service", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -53,7 +53,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install Nova", "PROMPT" : "Should Packstack install Nova compute service", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -65,7 +65,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install Horizon", "PROMPT" : "Should Packstack install Horizon dashboard", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -77,7 +77,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install Swift", "PROMPT" : "Should Packstack install Swift object storage", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "n", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -89,7 +89,7 @@ def initConfig(controllerObject): "USAGE" : "Set to 'y' if you would like Packstack to install the OpenStack Client packages. An admin \"rc\" file will also be installed", "PROMPT" : "Should Packstack install OpenStack client tools", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "y", "MASK_INPUT" : False, "LOOSE_VALIDATION": False, @@ -101,7 +101,6 @@ def initConfig(controllerObject): "USAGE" : "Comma separated list of NTP servers. Leave plain if Packstack should not install ntpd on instances.", "PROMPT" : "Enter list of NTP server(s). Leave plain if Packstack should not install ntpd on instances.", "OPTION_LIST" : [], - "VALIDATION_FUNC" : lambda param, options: True, "DEFAULT_VALUE" : '', "MASK_INPUT" : False, "LOOSE_VALIDATION": False, diff --git a/packstack/plugins/qpid_002.py b/packstack/plugins/qpid_002.py index 25c1aa028..a9f9c2ba3 100644 --- a/packstack/plugins/qpid_002.py +++ b/packstack/plugins/qpid_002.py @@ -29,11 +29,8 @@ def initConfig(controllerObject): "USAGE" : "The IP address of the server on which to install the QPID service", "PROMPT" : "Enter the IP address of the QPID service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), - "PROCESSOR_ARGS" : {"allow_localhost": True}, - "PROCESSOR_FUNC" : process.processHost, - "PROCESSOR_MSG" : "WARN_VAL_IS_HOSTNAME", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, "CONF_NAME" : "CONFIG_QPID_HOST", diff --git a/packstack/plugins/serverprep_901.py b/packstack/plugins/serverprep_901.py index c724fbc96..0a915b6fe 100644 --- a/packstack/plugins/serverprep_901.py +++ b/packstack/plugins/serverprep_901.py @@ -28,7 +28,7 @@ def initConfig(controllerObject): "USAGE" : "Install OpenStack from EPEL. If set to \"y\" EPEL will be installed on each server", "PROMPT" : "Should Packstack install EPEL on each server", "OPTION_LIST" : ["y", "n"], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "n", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -40,7 +40,6 @@ def initConfig(controllerObject): "USAGE" : "A comma separated list of URLs to any additional yum repositories to install", "PROMPT" : "Enter a comma separated list of URLs to any additional yum repositories to install", "OPTION_LIST" : [], - "VALIDATION_FUNC" : lambda a,b: True, "DEFAULT_VALUE" : "", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -52,7 +51,6 @@ def initConfig(controllerObject): "USAGE" : "To subscribe each server with Red Hat subscription manager, include this with CONFIG_RH_PASSWORD", "PROMPT" : "To subscribe each server to Red Hat enter a username here", "OPTION_LIST" : [], - "VALIDATION_FUNC" : lambda a,b: True, "DEFAULT_VALUE" : "", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -64,7 +62,6 @@ def initConfig(controllerObject): "USAGE" : "To subscribe each server with Red Hat subscription manager, include this with CONFIG_RH_USERNAME", "PROMPT" : "To subscribe each server to Red Hat enter your password here", "OPTION_LIST" : [], - "VALIDATION_FUNC" : lambda a,b: True, "DEFAULT_VALUE" : "", "MASK_INPUT" : True, "LOOSE_VALIDATION": True, diff --git a/packstack/plugins/sshkeys_000.py b/packstack/plugins/sshkeys_000.py index a05d5ac04..b30996a15 100644 --- a/packstack/plugins/sshkeys_000.py +++ b/packstack/plugins/sshkeys_000.py @@ -32,8 +32,8 @@ def initConfig(controllerObject): "USAGE" : "Path to a Public key to install on servers. If a usable key has not been installed on the remote servers the user will be prompted for a password and this key will be installed so the password will not be required again", "PROMPT" : "Enter the path to your ssh Public key to install on servers", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateFile, - "PROCESSOR_FUNC" : process.processSSHKey, + "VALIDATORS" : [validate.validate_file], + "PROCESSORS" : [process.processSSHKey], "DEFAULT_VALUE" : (glob.glob(os.path.join(os.environ["HOME"], ".ssh/*.pub"))+[""])[0], "MASK_INPUT" : False, "LOOSE_VALIDATION": False, diff --git a/packstack/plugins/swift_600.py b/packstack/plugins/swift_600.py index b8f486a61..cfcd4f4fc 100644 --- a/packstack/plugins/swift_600.py +++ b/packstack/plugins/swift_600.py @@ -31,11 +31,11 @@ def initConfig(controllerObject): "USAGE" : "The IP address on which to install the Swift proxy service", "PROMPT" : "Enter the IP address of the Swift proxy service", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateSSH, + "VALIDATORS" : [validate.validate_ip, validate.validate_ssh], "DEFAULT_VALUE" : utils.getLocalhostIP(), "MASK_INPUT" : False, "LOOSE_VALIDATION": True, - "CONF_NAME" : "CONFIG_SWIFT_PROXY_HOSTS", # TO-DO: Create processor for CSV + "CONF_NAME" : "CONFIG_SWIFT_PROXY_HOSTS", #XXX: Shouldn't be here CONFIG_SWIFT_PROXY_HOST? "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : False }, @@ -43,7 +43,7 @@ def initConfig(controllerObject): "USAGE" : "The password to use for the Swift to authenticate with Keystone", "PROMPT" : "Enter the password for the Swift Keystone access", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty], "DEFAULT_VALUE" : uuid.uuid4().hex[:16], "MASK_INPUT" : True, "LOOSE_VALIDATION": False, @@ -55,11 +55,11 @@ def initConfig(controllerObject): "USAGE" : "A comma separated list of IP addresses on which to install the Swift Storage services, each entry should take the format [/dev], for example 127.0.0.1/vdb will install /dev/vdb on 127.0.0.1 as a swift storage device, if /dev is omitted Packstack will create a loopback device for a test setup", "PROMPT" : "Enter the Swift Storage servers e.g. host/dev,host/dev", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateStringNotEmpty, + "VALIDATORS" : [validate.validate_not_empty, validate_storage], "DEFAULT_VALUE" : utils.getLocalhostIP(), "MASK_INPUT" : False, "LOOSE_VALIDATION": True, - "CONF_NAME" : "CONFIG_SWIFT_STORAGE_HOSTS", # TO-DO: Create processor for CSV + "CONF_NAME" : "CONFIG_SWIFT_STORAGE_HOSTS", "USE_DEFAULT" : False, "NEED_CONFIRM" : False, "CONDITION" : False }, @@ -67,7 +67,7 @@ def initConfig(controllerObject): "USAGE" : "Number of swift storage zones, this number MUST be no bigger than the number of storage devices configured", "PROMPT" : "Enter the number of swift storage zones, MUST be no bigger than the number of storage devices configured", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateInteger, + "VALIDATORS" : [validate.validate_integer], "DEFAULT_VALUE" : "1", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -79,7 +79,7 @@ def initConfig(controllerObject): "USAGE" : "Number of swift storage replicas, this number MUST be no bigger than the number of storage zones configured", "PROMPT" : "Enter the number of swift storage replicas, MUST be no bigger than the number of storage zones configured", "OPTION_LIST" : [], - "VALIDATION_FUNC" : validate.validateInteger, + "VALIDATORS" : [validate.validate_integer], "DEFAULT_VALUE" : "1", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -91,7 +91,7 @@ def initConfig(controllerObject): "USAGE" : "FileSystem type for storage nodes", "PROMPT" : "Enter FileSystem type for storage nodes", "OPTION_LIST" : ['xfs','ext4'], - "VALIDATION_FUNC" : validate.validateOptions, + "VALIDATORS" : [validate.validate_options], "DEFAULT_VALUE" : "ext4", "MASK_INPUT" : False, "LOOSE_VALIDATION": True, @@ -111,6 +111,12 @@ def initConfig(controllerObject): controller.addGroup(groupDict, paramsList) +def validate_storage(param, options=None): + for host in param.split(','): + host = host.split('/', 1)[0] + validate.validate_ip(host.strip(), options) + + def initSequences(controller): if controller.CONF['CONFIG_SWIFT_INSTALL'] != 'y': return @@ -124,12 +130,14 @@ def initSequences(controller): ] controller.addSequence("Installing OpenStack Swift", [], [], steps) + def createkeystonemanifest(): manifestfile = "%s_keystone.pp"%controller.CONF['CONFIG_KEYSTONE_HOST'] controller.CONF['CONFIG_SWIFT_PROXY'] = controller.CONF['CONFIG_SWIFT_PROXY_HOSTS'].split(',')[0] manifestdata = getManifestTemplate("keystone_swift.pp") appendManifestFile(manifestfile, manifestdata) + devices = [] def parseDevices(config_swift_storage_hosts): device_number = 0 @@ -142,6 +150,7 @@ def parseDevices(config_swift_storage_hosts): devices.append({'host':host, 'device':device, 'device_name':'device%s'%device_number, 'zone':str(zone)}) return devices + # The ring file should be built and distributed befor the storage services # come up. Specifically the replicator crashes if the ring isn't present def createbuildermanifest(): @@ -163,6 +172,7 @@ def createbuildermanifest(): appendManifestFile(manifestfile, manifestdata, 'swiftbuilder') + def createproxymanifest(): manifestfile = "%s_swift.pp"%controller.CONF['CONFIG_SWIFT_PROXY_HOSTS'] manifestdata = getManifestTemplate("swift_proxy.pp") @@ -171,6 +181,32 @@ def createproxymanifest(): manifestdata += 'swift::ringsync{["account","container","object"]:\n ring_server => "%s"\n}'%controller.CONF['CONFIG_SWIFT_BUILDER_HOST'] appendManifestFile(manifestfile, manifestdata) + +def check_device(host, device): + """ + Raises ScriptRuntimeError if given device is not mounted on given + host. + """ + server = utils.ScriptRunner() + + # the device MUST exist + cmd = 'ls -l /dev/%s' + server.append(cmd % device) + + # if it is not mounted then we can use it + cmd = 'grep "/dev/%s " /proc/self/mounts || exit 0' + server.append(cmd % device) + + # if it is mounted then the mount point has to be in /srv/node + cmd = 'grep "/dev/%s /srv/node" /proc/self/mounts && exit 0' + server.append(cmd % device) + + # if we got here without exiting then we can't use this device + server.append('exit 1') + server.execute() + return False + + def createstoragemanifest(): # this need to happen once per storage host @@ -185,10 +221,8 @@ def createstoragemanifest(): host = device['host'] devicename = device['device_name'] device = device['device'] - - server = utils.ScriptRunner(host) - validate.r_validateDevice(server, device) - server.execute() + if device: + check_device(host, device) manifestfile = "%s_swift.pp"%host if device: @@ -198,6 +232,7 @@ def createstoragemanifest(): manifestdata = "\n" + getManifestTemplate("swift_loopback.pp") appendManifestFile(manifestfile, manifestdata) + def createcommonmanifest(): for manifestfile, marker in manifestfiles.getFiles(): if manifestfile.endswith("_swift.pp"): diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 000000000..1c836eb0c --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +tox -epy26,py27,pep8 diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index 861224912..000000000 --- a/tests/test.py +++ /dev/null @@ -1,52 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013, Red Hat, Inc. -# -# 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 shutil -import tempfile - -import subprocess -from unittest import TestCase - - -class fakePopen(object): - def __init__(self, returncode=0): - self.returncode = returncode - self.stdout = self.stderr = self.data = "" - - def __call__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - return self - - def communicate(self, data): - self.data += data - return self.stdout, self.stderr - - -class TestCase(TestCase): - def setUp(self): - # Creating a temp directory that can be used by tests - self.tempdir = tempfile.mkdtemp() - - # some plugins call popen, we're replacing it for tests - self._Popen = subprocess.Popen - self.fakePopen = subprocess.Popen = fakePopen() - - def tearDown(self): - # remove the temp directory - shutil.rmtree(self.tempdir) - - subprocess.Popen = self._Popen diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 000000000..fc1ae9775 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013, Red Hat, Inc. +# +# 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 shutil +import tempfile +import subprocess + + +class FakePopen(object): + def __init__(self, returncode=0): + self.returncode = returncode + self.stdout = self.stderr = self.data = "" + + def __call__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + return self + + def communicate(self, data=None): + self.data += data or '' + return self.stdout, self.stderr + + +class PackstackTestCaseMixin(object): + """ + Implementation of some assertion methods available by default + in Python2.7+ only + """ + def setUp(self): + # Creating a temp directory that can be used by tests + self.tempdir = tempfile.mkdtemp() + + # some plugins call popen, we're replacing it for tests + self._Popen = subprocess.Popen + self.fake_popen = subprocess.Popen = FakePopen() + + def tearDown(self): + # remove the temp directory + shutil.rmtree(self.tempdir) + subprocess.Popen = self._Popen + + def assertItemsEqual(self, list1, list2, msg=None): + f, s = len(list1), len(list2) + self.assertEqual(f, s, msg=('Element counts were not equal. ' + 'First has %s, Second has %s' % (f, s))) + for i in list1: + if i not in list2: + raise AssertionError('Given lists differ:' + '\n%(list1)s\n%(list2)s' % locals()) + + def assertListEqual(self, list1, list2, msg=None): + f, s = len(list1), len(list2) + self.assertEqual(f, s, msg=('Element counts were not equal. ' + 'First has %s, Second has %s' % (f, s))) + for index, item in enumerate(list1): + if item != list2[index]: + raise AssertionError('Given lists differ:' + '\n%(list1)s\n%(list2)s' % locals()) + + def assertIsInstance(self, obj, cls, msg=None): + if not isinstance(obj, cls): + raise AssertionError('%s is not an instance of %s' % (obj, cls)) diff --git a/tests/test_ospluginutils.py b/tests/test_ospluginutils.py index 9021aca25..79a904ac0 100644 --- a/tests/test_ospluginutils.py +++ b/tests/test_ospluginutils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013, Red Hat, Inc. @@ -15,14 +16,15 @@ # under the License. import os -from test import TestCase +from unittest import TestCase +from test_base import PackstackTestCaseMixin from packstack.modules.ospluginutils import gethostlist, \ validate_puppet_logfile, \ PackStackError -class OSPluginUtilsTestCase(TestCase): +class OSPluginUtilsTestCase(PackstackTestCaseMixin, TestCase): def test_gethostlist(self): conf = {"A_HOST": "1.1.1.1", "B_HOSTS": "2.2.2.2,1.1.1.1", "C_HOSTS": "3.3.3.3/vdc"} diff --git a/tests/test_plugin_serverprep.py b/tests/test_plugin_serverprep.py index 0a7dc4cdb..e6cbb51e8 100644 --- a/tests/test_plugin_serverprep.py +++ b/tests/test_plugin_serverprep.py @@ -15,15 +15,16 @@ # under the License. import os -from test import TestCase +from unittest import TestCase +from test_base import PackstackTestCaseMixin from packstack.plugins import serverprep_901 from packstack.installer.setup_controller import Controller serverprep_901.controller = Controller() -class OSPluginUtilsTestCase(TestCase): +class OSPluginUtilsTestCase(PackstackTestCaseMixin, TestCase): def test_rhn_creds_quoted(self): """Make sure RHN password is quoted""" @@ -37,5 +38,5 @@ class OSPluginUtilsTestCase(TestCase): serverprep_901.serverprep() self.assertNotEqual( - self.fakePopen.data.find('--password="%s"' % password), -1 + self.fake_popen.data.find('--password="%s"' % password), -1 ) diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 000000000..ff11bd355 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013, Red Hat, Inc. +# +# 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 +import shutil +import tempfile +from unittest import TestCase + +from test_base import PackstackTestCaseMixin +from packstack.installer.engine_validators import * + + +class ValidatorsTestCase(PackstackTestCaseMixin, TestCase): + def setUp(self): + # Creating a temp directory that can be used by tests + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + # remove the temp directory + shutil.rmtree(self.tempdir) + + def test_validate_integer(self): + validate_integer('1') + self.assertRaises(ParamValidationError, validate_integer, 'test') + + def test_validate_regexp(self): + validate_regexp('Test_123', options=['\w']) + self.assertRaises(ParamValidationError, validate_regexp, + '!#$%', options=['\w']) + + def test_validate_port(self): + validate_port('666') + self.assertRaises(ParamValidationError, validate_port, 'test') + self.assertRaises(ParamValidationError, validate_port, '-3') + + def test_validate_not_empty(self): + validate_not_empty('test') + validate_not_empty(False) + self.assertRaises(ParamValidationError, validate_not_empty, '') + self.assertRaises(ParamValidationError, validate_not_empty, []) + self.assertRaises(ParamValidationError, validate_not_empty, {}) + + def test_validate_options(self): + validate_options('a', options=['a', 'b']) + validate_options('b', options=['a', 'b']) + self.assertRaises(ParamValidationError, validate_options, + 'c', options=['a', 'b']) + + def test_validate_ip(self): + validate_ip('127.0.0.1') + validate_ip('::1') + self.assertRaises(ParamValidationError, validate_ip, 'test') + + def test_validate_file(self): + fname = os.path.join(self.tempdir, '.test_validate_file') + bad_name = os.path.join(self.tempdir, '.me_no_exists') + with open(fname, 'w') as f: + f.write('test') + validate_file(fname) + self.assertRaises(ParamValidationError, validate_file, bad_name) + + def test_validate_ping(self): + # ping to broadcast fails + self.assertRaises(ParamValidationError, validate_ping, '192.168.122.0') + + def test_validate_ssh(self): + # ssh to broadcast fails + self.assertRaises(ParamValidationError, validate_ssh, '192.168.122.0')