Merge "Image argument validation and declaration"

This commit is contained in:
Jenkins 2016-08-17 23:42:03 +00:00 committed by Gerrit Code Review
commit 7749f50ede
7 changed files with 271 additions and 133 deletions

View File

@ -115,7 +115,6 @@ full sample is presented for context.
arguments:
java-distro:
description: The java distribution.
target_variable: JAVA_DISTRO
default: openjdk
required: false
choices:
@ -167,9 +166,8 @@ to adjust properties of the image:
::
arguments: # The section header
- java-distro: # The friendly name of the argument
- java-distro: # The friendly name of the argument, and the name of the variable passed to scripts
description: The java distribution. # A friendly description to be used in help text
target_variable: JAVA_DISTRO # The value of the argument will be passed to scripts as an environment variable with this name
default: openjdk # A default value for the argument
required: false # Whether or not the argument is required
choices: # The argument value must match an element of this list
@ -245,21 +243,22 @@ as well:
- script: simple_script.sh # Runs this file
- script:
set_java_home: # The name of a script file
env_vars: # Only the named environment variables are passed, for clarity
- JDK_HOME
- JRE_HOME
arguments: # Only the named environment arguments are passed, for clarity
- jdk-home
- jre-home
output: OUTPUT_VAR
- script:
store_nfs_version: # Because inline is set, this is just a friendly name
- inline: rpm -q nfs-utils # Runs this text directly, rather than reading a file
- output: NFS_VERSION # Places the stdout of this script into a variable
# for future scripts to consume
- output: nfs-version # Places the stdout of this script into an argument
# for future scripts to consume; if none exists, the
# argument is created
Two variables are always available to scripts run under this framework:
* SIV_DISTRO: The distro of the image, in case you want to switch on distro
* ``distro``: The distro of the image, in case you want to switch on distro
within your script (rather than by using the os_case validator).
* SIV_RECONCILE: If this value equates to boolean true, then the script should
* ``reconcile``: If this value equates to boolean true, then the script should
attempt to change the image or instance if it does not already meet the
specification. If this equates to boolean false, the script should exit with
a failure code if the image or instance does not already meet the

View File

@ -101,7 +101,7 @@ class ImageRemote(remote.TerminalOnlyRemote):
return 1, ex.message
def get_os_distrib(self):
return self.guest.inspect_get_distro()
return self.guest.inspect_get_distro(self.root_drive)
def setup_plugins():

View File

@ -76,7 +76,7 @@ def add_plugin_parsers(subparsers):
dest="version",
help=_("Available versions"))
for version, args in six.iteritems(args_by_version):
if not args:
if args is NotImplemented:
continue
version_parser = version_parsers.add_parser(
version, help=_('{plugin} version {version}').format(
@ -85,12 +85,12 @@ def add_plugin_parsers(subparsers):
arg_token = ("--%s" % arg.name if len(arg.name) > 1 else
"-%s" % arg.name)
version_parser.add_argument(arg_token,
dest=arg.target_variable,
dest=arg.name,
help=arg.description,
default=arg.default,
required=arg.required,
choices=arg.choices)
version_parser.set_defaults(args={arg.target_variable
version_parser.set_defaults(args={arg.name
for arg in args})

View File

@ -0,0 +1,6 @@
arguments:
fish:
description: awesome
default: trout
validators: []

View File

@ -73,10 +73,72 @@ def validate_instance(instance, validators, reconcile=True, **kwargs):
class ImageArgument(object):
"""An argument used by an image manifest."""
def __init__(self, name, target_variable, description=None,
default=None, required=False, choices=None):
SPEC_SCHEMA = {
"type": "object",
"items": {
"type": "object",
"properties": {
"target_variable": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"minLength": 1
},
"default": {
"type": "string",
"minLength": 1
},
"required": {
"type": "boolean",
"minLength": 1
},
"choices": {
"type": "array",
"minLength": 1,
"items": {
"type": "string"
}
}
}
}
}
@classmethod
def from_spec(cls, spec):
"""Constructs and returns a set of arguments from a specification.
:param spec: The specification for the argument set.
:return A dict of arguments built to the specification.
"""
jsonschema.validate(spec, cls.SPEC_SCHEMA)
arguments = {name: cls(name,
arg.get('description'),
arg.get('default'),
arg.get('required'),
arg.get('choices'))
for name, arg in six.iteritems(spec)}
reserved_names = ['distro', 'reconcile']
for name, arg in six.iteritems(arguments):
if name in reserved_names:
raise p_ex.ImageValidationSpecificationError(
_("The following argument names are reserved: "
"{names}").format(reserved_names))
if not arg.default and not arg.required:
raise p_ex.ImageValidationSpecificationError(
_("Argument {name} is not required and must specify a "
"default value.").format(name=arg.name))
if arg.choices and arg.default and arg.default not in arg.choices:
raise p_ex.ImageValidationSpecificationError(
_("Argument {name} specifies a default which is not one "
"of its choices.").format(name=arg.name))
return arguments
def __init__(self, name, description=None, default=None, required=False,
choices=None):
self.name = name
self.target_variable = target_variable
self.description = description
self.default = default
self.required = required
@ -105,8 +167,8 @@ class ImageValidator(object):
class SaharaImageValidatorBase(ImageValidator):
"""Base class for Sahara's native image validation."""
DISTRO_KEY = 'SIV_DISTRO'
RECONCILE_KEY = 'SIV_RECONCILE'
DISTRO_KEY = 'distro'
RECONCILE_KEY = 'reconcile'
ORDERED_VALIDATORS_SCHEMA = {
"type": "array",
@ -231,7 +293,8 @@ class SaharaImageValidatorBase(ImageValidator):
def __nonzero__(self):
return False
def try_validate(self, remote, reconcile=True, env_map=None, **kwargs):
def try_validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate, but returns rather than raising on failure.
:param remote: A remote socket to the instance.
@ -239,12 +302,14 @@ class SaharaImageValidatorBase(ImageValidator):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:return True if successful, ValidationAttemptFailed object if failed.
"""
try:
self.validate(
remote, reconcile=reconcile, env_map=env_map, **kwargs)
remote, reconcile=reconcile,
image_arguments=image_arguments, **kwargs)
return True
except p_ex.ImageValidationError as exc:
return self.ValidationAttemptFailed(exc)
@ -266,6 +331,10 @@ class SaharaImageValidator(SaharaImageValidatorBase):
"required": ["validators"]
}
def get_argument_list(self):
return [argument for name, argument
in six.iteritems(self.arguments)]
@classmethod
def from_spec(cls, spec, validator_map, resource_roots):
"""Constructs and returns a validator from a specification object.
@ -280,12 +349,14 @@ class SaharaImageValidator(SaharaImageValidatorBase):
:return A SaharaImageValidator containing all specified validators.
"""
jsonschema.validate(spec, cls.SPEC_SCHEMA)
specs = spec['validators']
arguments_spec = spec.get('arguments', {})
arguments = ImageArgument.from_spec(arguments_spec)
validators_spec = spec['validators']
validator = SaharaAllValidator.from_spec(
specs, validator_map, resource_roots)
return cls(validator)
validators_spec, validator_map, resource_roots)
return cls(validator, arguments)
def __init__(self, validator):
def __init__(self, validator, arguments):
"""Constructor method.
:param validator: A SaharaAllValidator containing the specified
@ -293,9 +364,11 @@ class SaharaImageValidator(SaharaImageValidatorBase):
"""
self.validator = validator
self.validators = validator.validators
self.arguments = arguments
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate the image.
Before deferring to contained validators, performs one-time setup
@ -306,15 +379,31 @@ class SaharaImageValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
env_map = copy.deepcopy(env_map) if env_map else {}
env_map[self.RECONCILE_KEY] = 1 if reconcile else 0
raw_distro = remote.execute_command('lsb_release -is')
distro = raw_distro[1].strip().lower()
env_map[self.DISTRO_KEY] = distro
self.validator.validate(remote, reconcile=reconcile, env_map=env_map)
argument_values = {}
for name, argument in six.iteritems(self.arguments):
if name not in image_arguments:
if argument.required:
raise p_ex.ImageValidationError(
_("Argument {name} is required for image "
"processing.").format(name=name))
else:
argument_values[name] = argument.default
else:
value = image_arguments[name]
choices = argument.choices
if choices and value not in choices:
raise p_ex.ImageValidationError(
_("Value for argument {name} must be one of "
"{choices}.").format(name=name, choices=choices))
else:
argument_values[name] = value
argument_values[self.DISTRO_KEY] = remote.get_os_distrib()
self.validator.validate(remote, reconcile=reconcile,
image_arguments=argument_values)
class SaharaPackageValidator(SaharaImageValidatorBase):
@ -406,7 +495,8 @@ class SaharaPackageValidator(SaharaImageValidatorBase):
self.packages = packages
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate package installation on the image.
Even if reconcile=True, attempts to verify previous package
@ -418,10 +508,11 @@ class SaharaPackageValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
env_distro = env_map[self.DISTRO_KEY]
env_distro = image_arguments[self.DISTRO_KEY]
env_family = self._DISTRO_FAMILES[env_distro]
check, install = self._DISTRO_TOOLS[env_family]
if not env_family:
@ -513,7 +604,7 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
env_vars: A list of environment variable names to send to the
script.
output: A key into which to put the stdout of the script in the
env_map of the validation run.
image_arguments of the validation run.
:param validator_map: A map of validator name to class.
:param resource_roots: The roots from which relative paths to
resources (scripts and such) will be referenced. Any resource will
@ -552,7 +643,7 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
:param env_vars: A list of environment variables to send to the
script.
:param output_var: A key into which to put the stdout of the script in
the env_map of the validation run.
the image_arguments of the validation run.
:return: A SaharaScriptValidator.
"""
self.script_contents = script_contents
@ -560,7 +651,8 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
self.output_var = output_var
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate by running a script on the image.
:param remote: A remote socket to the instance.
@ -568,25 +660,28 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
Note that the key SIV_RECONCILE will be set to 1 if the script
should reconcile and 0 otherwise; all scripts should act on this
input if possible. The key SIV_DISTRO will also contain the
distro representation, per `lsb_release -is`.
:raises ImageValidationError: If validation fails.
"""
arguments = copy.deepcopy(image_arguments)
arguments[self.RECONCILE_KEY] = 1 if reconcile else 0
script = "\n".join(["%(env_vars)s",
"bash <<_SIV_",
"%(script)s",
"_SIV_"])
env_vars = "\n".join("export %s=%s" % (key, value) for (key, value)
in six.iteritems(env_map)
in six.iteritems(image_arguments)
if key in self.env_vars)
script = script % {"env_vars": env_vars,
"script": self.script_contents}
code, stdout = _sudo(remote, script)
if self.output_var:
env_map[self.output_var] = stdout
image_arguments[self.output_var] = stdout
@six.add_metaclass(abc.ABCMeta)
@ -618,17 +713,20 @@ class SaharaAggregateValidator(SaharaImageValidatorBase):
class SaharaAnyValidator(SaharaAggregateValidator):
"""A list of validators, only one of which must succeed."""
def _try_all(self, remote, reconcile=True, env_map=None, **kwargs):
def _try_all(self, remote, reconcile=True,
image_arguments=None, **kwargs):
results = []
for validator in self.validators:
result = validator.try_validate(remote, reconcile=reconcile,
env_map=env_map, **kwargs)
image_arguments=image_arguments,
**kwargs)
results.append(result)
if result:
break
return results
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate any of the contained validators.
Note that if reconcile=True, this validator will first run all
@ -641,13 +739,15 @@ class SaharaAnyValidator(SaharaAggregateValidator):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
results = self._try_all(remote, reconcile=False, env_map=env_map)
results = self._try_all(remote, reconcile=False,
image_arguments=image_arguments)
if reconcile and not any(results):
results = self._try_all(remote, reconcile=True, env_map=env_map)
results = self._try_all(remote, reconcile=True,
image_arguments=image_arguments)
if not any(results):
raise p_ex.AllValidationsFailedError(result.exception for result
in results)
@ -656,7 +756,7 @@ class SaharaAnyValidator(SaharaAggregateValidator):
class SaharaAllValidator(SaharaAggregateValidator):
"""A list of validators, all of which must succeed."""
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True, image_arguments=None, **kwargs):
"""Attempts to validate all of the contained validators.
:param remote: A remote socket to the instance.
@ -664,12 +764,13 @@ class SaharaAllValidator(SaharaAggregateValidator):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
for validator in self.validators:
validator.validate(remote, reconcile=reconcile, env_map=env_map)
validator.validate(remote, reconcile=reconcile,
image_arguments=image_arguments)
class SaharaOSCaseValidator(SaharaImageValidatorBase):
@ -718,7 +819,8 @@ class SaharaOSCaseValidator(SaharaImageValidatorBase):
"""
self.distros = distros
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate depending on distro.
May match the OS by specific distro or by family (centos may match
@ -731,17 +833,18 @@ class SaharaOSCaseValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
env_distro = env_map[self.DISTRO_KEY]
env_distro = image_arguments[self.DISTRO_KEY]
family = self._DISTRO_FAMILES.get(env_distro)
matches = {env_distro, family} if family else {env_distro}
for distro, validator in self.distros:
if distro in matches:
validator.validate(
remote, reconcile=reconcile, env_map=env_map)
remote, reconcile=reconcile,
image_arguments=image_arguments)
break
@ -799,7 +902,8 @@ class SaharaArgumentCaseValidator(SaharaImageValidatorBase):
self.argument_name = argument_name
self.cases = cases
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate depending on argument value.
:param remote: A remote socket to the instance.
@ -807,18 +911,19 @@ class SaharaArgumentCaseValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
:raises ImageValidationError: If validation fails.
"""
arg = self.argument_name
if arg not in env_map:
if arg not in image_arguments:
raise p_ex.ImageValidationError(
_("Argument {name} not found.").format(name=arg))
value = env_map[arg]
value = image_arguments[arg]
if value in self.cases:
self.cases[value].validate(
remote, reconcile=reconcile, env_map=env_map)
remote, reconcile=reconcile,
image_arguments=image_arguments)
class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
@ -868,7 +973,8 @@ class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
self.argument_name = argument_name
self.value = value
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
def validate(self, remote, reconcile=True,
image_arguments=None, **kwargs):
"""Attempts to validate depending on argument value.
:param remote: A remote socket to the instance.
@ -876,9 +982,10 @@ class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
desired state is present, and fail if it is not. If true, all
validators will attempt to enforce the desired state if possible,
and succeed if this enforcement succeeds.
:param env_map: A map of environment variables to pass to scripts.
:param image_arguments: A dictionary of image argument values keyed by
argument name.
"""
env_map[self.argument_name] = self.value
image_arguments[self.argument_name] = self.value
def _sudo(remote, cmd, **kwargs):

View File

@ -122,8 +122,7 @@ class ProvisioningPluginBase(plugins_base.PluginInterface):
This can be used to test images without modification. If set to
True per the default, this method will modify the image if any
requirements are not met.
:param image_arguments: A dict of image argument target variable (not
the friendly name used for documentation purposes) to argument
:param image_arguments: A dict of image argument name to argument
value.
:raises: sahara.plugins.exceptions.ImageValidationError: If the method
fails to modify the image to specification (if reconcile is True),
@ -146,8 +145,7 @@ class ProvisioningPluginBase(plugins_base.PluginInterface):
This can be used to test images without modification. If set to
True per the default, this method will modify the image if any
requirements are not met.
:param image_arguments: A dict of image argument target variable (not
the friendly name used for documentation purposes) to argument
:param image_arguments: A dict of image argument name to argument
value.
:raises: sahara.plugins.exceptions.ImageValidationError: If the method
fails to modify the image to specification (if reconcile is True),

View File

@ -47,15 +47,15 @@ class TestImages(b.SaharaTestCase):
validator = cls.from_spec('test_images.py', {}, resource_roots)
self.assertIsInstance(validator, cls)
self.assertEqual(validator.env_vars, ['SIV_RECONCILE', 'SIV_DISTRO'])
self.assertEqual(validator.env_vars, ['reconcile', 'distro'])
validator = cls.from_spec(
{'test_images.py': {'env_vars': ['EXTRA_FILE', 'USER']}},
{'test_images.py': {'env_vars': ['extra-file', 'user']}},
{}, resource_roots)
self.assertIsInstance(validator, cls)
self.assertEqual(validator.env_vars,
['SIV_RECONCILE', 'SIV_DISTRO',
'EXTRA_FILE', 'USER'])
['reconcile', 'distro',
'extra-file', 'user'])
def test_all_spec(self):
cls = images.SaharaAllValidator
@ -106,6 +106,15 @@ class TestImages(b.SaharaTestCase):
resource_roots = ['tests/unit/plugins']
spec = """
arguments:
java-version:
description: The version of java.
default: openjdk
required: false
choices:
- openjdk
- oracle-java
validators:
- os_case:
- redhat:
@ -116,12 +125,12 @@ class TestImages(b.SaharaTestCase):
- all:
- package: java-1.8.0-openjdk-devel
- argument_set:
argument_name: JAVA_VERSION
argument_name: java-version
value: 1.8.0
- all:
- package: java-1.7.0-openjdk-devel
- argument_set:
argument_name: JAVA_VERSION
argument_name: java-version
value: 1.7.0
- script: test_images.py
- package:
@ -156,33 +165,43 @@ class TestImages(b.SaharaTestCase):
self.assertIsInstance(validators[3], images.SaharaPackageValidator)
self.assertIsInstance(
validators[4], images.SaharaArgumentCaseValidator)
self.assertEqual(1, len(validator.arguments))
self.assertEqual(validator.arguments['java-version'].required, False)
self.assertEqual(validator.arguments['java-version'].default,
'openjdk')
self.assertEqual(validator.arguments['java-version'].description,
'The version of java.')
self.assertEqual(validator.arguments['java-version'].choices,
['openjdk', 'oracle-java'])
def test_package_validator_redhat(self):
cls = images.SaharaPackageValidator
env_map = {"SIV_DISTRO": 'centos'}
image_arguments = {"distro": 'centos'}
packages = [cls.Package("java", "8")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
validator.validate(remote, reconcile=False, env_map=env_map)
validator.validate(remote, reconcile=False,
image_arguments=image_arguments)
remote.execute_command.assert_called_with(
"rpm -q java-8", run_as_root=True)
env_map = {"SIV_DISTRO": 'fedora'}
image_arguments = {"distro": 'fedora'}
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
remote.execute_command.side_effect = (
ex.RemoteCommandException("So bad!"))
try:
validator.validate(remote, reconcile=False, env_map=env_map)
validator.validate(remote, reconcile=False,
image_arguments=image_arguments)
except p_ex.ImageValidationError as e:
self.assertIn("So bad!", e.message)
remote.execute_command.assert_called_with(
"rpm -q java-8 hadoop", run_as_root=True)
self.assertEqual(remote.execute_command.call_count, 1)
env_map = {"SIV_DISTRO": 'redhatenterpriseserver'}
image_arguments = {"distro": 'redhatenterpriseserver'}
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
@ -193,7 +212,8 @@ class TestImages(b.SaharaTestCase):
remote.execute_command.side_effect = side_effect
try:
validator.validate(remote, reconcile=True, env_map=env_map)
validator.validate(remote, reconcile=True,
image_arguments=image_arguments)
except p_ex.ImageValidationError as e:
self.assertIn("So bad!", e.message)
self.assertEqual(remote.execute_command.call_count, 3)
@ -204,37 +224,40 @@ class TestImages(b.SaharaTestCase):
def test_package_validator_debian(self):
cls = images.SaharaPackageValidator
env_map = {"SIV_DISTRO": 'ubuntu'}
image_arguments = {"distro": 'ubuntu'}
packages = [cls.Package("java", "8")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
validator.validate(remote, reconcile=False, env_map=env_map)
validator.validate(remote, reconcile=False,
image_arguments=image_arguments)
remote.execute_command.assert_called_with(
"dpkg -s java-8", run_as_root=True)
env_map = {"SIV_DISTRO": 'ubuntu'}
image_arguments = {"distro": 'ubuntu'}
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
remote.execute_command.side_effect = (
ex.RemoteCommandException("So bad!"))
try:
validator.validate(remote, reconcile=False, env_map=env_map)
validator.validate(remote, reconcile=False,
image_arguments=image_arguments)
except p_ex.ImageValidationError as e:
self.assertIn("So bad!", e.message)
remote.execute_command.assert_called_with(
"dpkg -s java-8 hadoop", run_as_root=True)
self.assertEqual(remote.execute_command.call_count, 1)
env_map = {"SIV_DISTRO": 'ubuntu'}
image_arguments = {"distro": 'ubuntu'}
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
validator = images.SaharaPackageValidator(packages)
remote = mock.Mock()
remote.execute_command.side_effect = (
ex.RemoteCommandException("So bad!"))
try:
validator.validate(remote, reconcile=True, env_map=env_map)
validator.validate(remote, reconcile=True,
image_arguments=image_arguments)
except p_ex.ImageValidationError as e:
self.assertIn("So bad!", e.message)
self.assertEqual(remote.execute_command.call_count, 2)
@ -246,20 +269,22 @@ class TestImages(b.SaharaTestCase):
def test_script_validator(self):
cls = images.SaharaScriptValidator
env_map = {"SIV_DISTRO": 'centos'}
map_rep = "export SIV_DISTRO=centos\n"
image_arguments = {"distro": 'centos'}
map_rep = "export distro=centos\n"
cmd = "It's dangerous to go alone. Run this."
expected_cmd = "bash <<_SIV_\n%s\n_SIV_" % cmd
validator = cls(cmd, env_vars=env_map.keys(), output_var="SIV_DISTRO")
validator = cls(cmd, env_vars=image_arguments.keys(),
output_var="distro")
remote = mock.Mock(
execute_command=mock.Mock(
return_value=(0, 'fedora')))
validator.validate(remote, reconcile=True, env_map=env_map)
validator.validate(remote, reconcile=True,
image_arguments=image_arguments)
call = [mock.call(map_rep + expected_cmd, run_as_root=True)]
remote.execute_command.assert_has_calls(call)
self.assertEqual(env_map['SIV_DISTRO'], 'fedora')
self.assertEqual(image_arguments['distro'], 'fedora')
def test_any_validator(self):
cls = images.SaharaAnyValidator
@ -316,7 +341,7 @@ class TestImages(b.SaharaTestCase):
validator.validate(None, reconcile=True)
self.assertEqual(always_tells_the_truth.validate.call_count, 2)
always_tells_the_truth.validate.assert_called_with(
None, reconcile=True, env_map=None)
None, reconcile=True, image_arguments=None)
# Second fails
always_tells_the_truth = mock.Mock()
@ -330,9 +355,9 @@ class TestImages(b.SaharaTestCase):
self.assertEqual(always_tells_the_truth.validate.call_count, 1)
self.assertEqual(always_lies.validate.call_count, 1)
always_tells_the_truth.validate.assert_called_with(
None, reconcile=False, env_map=None)
None, reconcile=False, image_arguments=None)
always_lies.validate.assert_called_with(
None, reconcile=False, env_map=None)
None, reconcile=False, image_arguments=None)
# First fails
always_tells_the_truth = mock.Mock()
@ -340,12 +365,12 @@ class TestImages(b.SaharaTestCase):
side_effect=p_ex.ImageValidationError("Boom!")))
validator = cls([always_lies, always_tells_the_truth])
try:
validator.validate(None, reconcile=False, env_map={})
validator.validate(None, reconcile=False, image_arguments={})
except p_ex.ImageValidationError:
pass
self.assertEqual(always_lies.validate.call_count, 1)
always_lies.validate.assert_called_with(
None, reconcile=False, env_map={})
None, reconcile=False, image_arguments={})
self.assertEqual(always_tells_the_truth.validate.call_count, 0)
def test_os_case_validator(self):
@ -356,33 +381,36 @@ class TestImages(b.SaharaTestCase):
centos = Distro("centos", mock.Mock())
redhat = Distro("redhat", mock.Mock())
distros = [centos, redhat]
env_map = {images.SaharaImageValidator.DISTRO_KEY: "centos"}
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "centos"}
validator = cls(distros)
validator.validate(None, reconcile=True, env_map=env_map)
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(centos.validator.validate.call_count, 1)
self.assertEqual(redhat.validator.validate.call_count, 0)
centos.validator.validate.assert_called_with(
None, reconcile=True, env_map=env_map)
None, reconcile=True, image_arguments=image_arguments)
# Familes match
centos = Distro("centos", mock.Mock())
redhat = Distro("redhat", mock.Mock())
distros = [centos, redhat]
env_map = {images.SaharaImageValidator.DISTRO_KEY: "fedora"}
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "fedora"}
validator = cls(distros)
validator.validate(None, reconcile=True, env_map=env_map)
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(centos.validator.validate.call_count, 0)
self.assertEqual(redhat.validator.validate.call_count, 1)
redhat.validator.validate.assert_called_with(
None, reconcile=True, env_map=env_map)
None, reconcile=True, image_arguments=image_arguments)
# Non-matches do nothing
centos = Distro("centos", mock.Mock())
redhat = Distro("redhat", mock.Mock())
distros = [centos, redhat]
env_map = {images.SaharaImageValidator.DISTRO_KEY: "ubuntu"}
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "ubuntu"}
validator = cls(distros)
validator.validate(None, reconcile=True, env_map=env_map)
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(centos.validator.validate.call_count, 0)
self.assertEqual(redhat.validator.validate.call_count, 0)
@ -390,59 +418,59 @@ class TestImages(b.SaharaTestCase):
cls = images.SaharaArgumentCaseValidator
# Match gets called
env_map = {"argument": "value"}
image_arguments = {"argument": "value"}
match = mock.Mock()
nomatch = mock.Mock()
cases = {"value": match,
"another_value": nomatch}
validator = cls("argument", cases)
validator.validate(None, reconcile=True, env_map=env_map)
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(match.validate.call_count, 1)
self.assertEqual(nomatch.validate.call_count, 0)
match.validate.assert_called_with(
None, reconcile=True, env_map=env_map)
None, reconcile=True, image_arguments=image_arguments)
# Non-matches do nothing
env_map = {"argument": "value"}
image_arguments = {"argument": "value"}
nomatch = mock.Mock()
cases = {"some_value": nomatch,
"another_value": nomatch}
validator = cls("argument", cases)
validator.validate(None, reconcile=True, env_map=env_map)
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(nomatch.validate.call_count, 0)
def test_sahara_argument_set_validator(self):
cls = images.SaharaArgumentSetterValidator
# Old variable is overwritten
env_map = {"argument": "value"}
image_arguments = {"argument": "value"}
validator = cls("argument", "new_value")
validator.validate(None, reconcile=True, env_map=env_map)
self.assertEqual(env_map["argument"], "new_value")
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(image_arguments["argument"], "new_value")
# New variable is set
env_map = {"argument": "value"}
image_arguments = {"argument": "value"}
validator = cls("another_argument", "value")
validator.validate(None, reconcile=True, env_map=env_map)
self.assertEqual(env_map, {"argument": "value",
"another_argument": "value"})
validator.validate(None, reconcile=True,
image_arguments=image_arguments)
self.assertEqual(image_arguments,
{"argument": "value", "another_argument": "value"})
def test_sahara_image_validator(self):
cls = images.SaharaImageValidator
sub_validator = mock.Mock(validate=mock.Mock())
remote = mock.Mock(execute_command=mock.Mock(
return_value=(None, "CENTOS ")))
validator = cls(sub_validator)
validator.validate(remote, reconcile=True, env_map={})
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos",
images.SaharaImageValidatorBase.RECONCILE_KEY: 1}
remote.execute_command.assert_called_with('lsb_release -is')
remote = mock.Mock(get_os_distrib=mock.Mock(
return_value="centos"))
validator = cls(sub_validator, {})
validator.validate(remote, reconcile=True, image_arguments={})
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos"}
sub_validator.validate.assert_called_with(
remote, reconcile=True, env_map=expected_map)
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos",
images.SaharaImageValidatorBase.RECONCILE_KEY: 0}
validator.validate(remote, reconcile=False, env_map={})
remote, reconcile=True, image_arguments=expected_map)
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos"}
validator.validate(remote, reconcile=False, image_arguments={})
sub_validator.validate.assert_called_with(
remote, reconcile=False, env_map=expected_map)
remote, reconcile=False, image_arguments=expected_map)