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
This commit is contained in:
Martin Magr 2013-02-05 16:03:13 +01:00
parent f6a0b0781b
commit a3d2a6d616
26 changed files with 523 additions and 691 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.pyc
*.py[co]
*.swp
*.log
.tox
packstack.egg-info

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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")

View File

@ -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):

View File

@ -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

View File

@ -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():

View File

@ -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",

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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"):

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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,

View File

@ -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,

View File

@ -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 <ipaddress>[/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"):

2
run_tests.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
tox -epy26,py27,pep8

View File

@ -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

76
tests/test_base.py Normal file
View File

@ -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))

View File

@ -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"}

View File

@ -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
)

82
tests/test_validators.py Normal file
View File

@ -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')