Merge "Add New Relic License module driver"
This commit is contained in:
commit
6e2922bc49
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added a module driver for New Relics licenses.
|
||||||
|
This allows activation of any New Relic software
|
||||||
|
that is installed on the image. Bug 1571711
|
||||||
|
|
|
@ -38,6 +38,7 @@ trove.api.extensions =
|
||||||
|
|
||||||
trove.guestagent.module.drivers =
|
trove.guestagent.module.drivers =
|
||||||
ping = trove.guestagent.module.drivers.ping_driver:PingDriver
|
ping = trove.guestagent.module.drivers.ping_driver:PingDriver
|
||||||
|
new_relic_license = trove.guestagent.module.drivers.new_relic_license_driver:NewRelicLicenseDriver
|
||||||
|
|
||||||
# These are for backwards compatibility with Havana notification_driver configuration values
|
# These are for backwards compatibility with Havana notification_driver configuration values
|
||||||
oslo.messaging.notify.drivers =
|
oslo.messaging.notify.drivers =
|
||||||
|
|
|
@ -403,7 +403,7 @@ common_opts = [
|
||||||
'become alive.'),
|
'become alive.'),
|
||||||
cfg.StrOpt('module_aes_cbc_key', default='module_aes_cbc_key',
|
cfg.StrOpt('module_aes_cbc_key', default='module_aes_cbc_key',
|
||||||
help='OpenSSL aes_cbc key for module encryption.'),
|
help='OpenSSL aes_cbc key for module encryption.'),
|
||||||
cfg.ListOpt('module_types', default=['ping'],
|
cfg.ListOpt('module_types', default=['ping', 'new_relic_license'],
|
||||||
help='A list of module types supported. A module type '
|
help='A list of module types supported. A module type '
|
||||||
'corresponds to the name of a ModuleDriver.'),
|
'corresponds to the name of a ModuleDriver.'),
|
||||||
cfg.StrOpt('guest_log_container_name',
|
cfg.StrOpt('guest_log_container_name',
|
||||||
|
|
|
@ -528,6 +528,11 @@ class ModuleAccessForbidden(Forbidden):
|
||||||
"options. %(options)s")
|
"options. %(options)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleInvalid(Forbidden):
|
||||||
|
|
||||||
|
message = _("The module you are applying is invalid: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
class ClusterNotFound(NotFound):
|
class ClusterNotFound(NotFound):
|
||||||
message = _("Cluster '%(cluster)s' cannot be found.")
|
message = _("Cluster '%(cluster)s' cannot be found.")
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import functools
|
||||||
|
import re
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from trove.common import cfg
|
from trove.common import cfg
|
||||||
|
from trove.common import exception
|
||||||
|
from trove.common.i18n import _
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
@ -28,21 +32,64 @@ class ModuleDriver(object):
|
||||||
"""Base class that defines the contract for module drivers.
|
"""Base class that defines the contract for module drivers.
|
||||||
|
|
||||||
Note that you don't have to derive from this class to have a valid
|
Note that you don't have to derive from this class to have a valid
|
||||||
driver; it is purely a convenience.
|
driver; it is purely a convenience. Any class that adheres to the
|
||||||
|
'interface' as dictated by this class' abstractmethod decorators
|
||||||
|
(and other methods such as get_type, get_name and configure)
|
||||||
|
will work.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(ModuleDriver, self).__init__()
|
||||||
|
|
||||||
|
# This is used to store any message args to be substituted by
|
||||||
|
# the output decorator when logging/returning messages.
|
||||||
|
self._module_message_args = {}
|
||||||
|
self._message_args = None
|
||||||
|
self._generated_name = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message_args(self):
|
||||||
|
"""Return a dict of message args that can be used to enhance
|
||||||
|
the output decorator messages. This shouldn't be overridden; use
|
||||||
|
self.message_args = <dict> instead to append values.
|
||||||
|
"""
|
||||||
|
if not self._message_args:
|
||||||
|
self._message_args = {
|
||||||
|
'name': self.get_name(),
|
||||||
|
'type': self.get_type()}
|
||||||
|
self._message_args.update(self._module_message_args)
|
||||||
|
return self._message_args
|
||||||
|
|
||||||
|
@message_args.setter
|
||||||
|
def message_args(self, values):
|
||||||
|
"""Set the message args that can be used to enhance
|
||||||
|
the output decorator messages.
|
||||||
|
"""
|
||||||
|
values = values or {}
|
||||||
|
self._module_message_args = values
|
||||||
|
self._message_args = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generated_name(self):
|
||||||
|
if not self._generated_name:
|
||||||
|
# Turn class name into 'module type' format.
|
||||||
|
# For example: DoCustomWorkDriver -> do_custom_work
|
||||||
|
temp = re.sub('(.)[Dd]river$', r'\1', self.__class__.__name__)
|
||||||
|
temp2 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', temp)
|
||||||
|
temp3 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp2)
|
||||||
|
self._generated_name = temp3.lower()
|
||||||
|
return self._generated_name
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""This is used when setting up a module in Trove, and is here for
|
"""This is used when setting up a module in Trove, and is here for
|
||||||
code clarity. It just returns the name of the driver.
|
code clarity. It just returns the name of the driver by default.
|
||||||
"""
|
"""
|
||||||
return self.get_name()
|
return self.get_name()
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
"""Attempt to generate a usable name based on the class name. If
|
"""Use the generated name based on the class name. If
|
||||||
overridden, must be in lower-case.
|
overridden, must be in lower-case.
|
||||||
"""
|
"""
|
||||||
return self.__class__.__name__.lower().replace(
|
return self.generated_name
|
||||||
'driver', '').replace(' ', '_')
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
|
@ -55,15 +102,104 @@ class ModuleDriver(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def apply(self, name, datastore, ds_version, data_file):
|
def apply(self, name, datastore, ds_version, data_file, admin_module):
|
||||||
"""Apply the data to the guest instance. Return status and message
|
"""Apply the module to the guest instance. Return status and message
|
||||||
as a tupple.
|
as a tuple. Passes in whether the module was created with 'admin'
|
||||||
|
privileges. This can be used as a form of access control by having
|
||||||
|
the driver refuse to apply a module if it wasn't created with options
|
||||||
|
that indicate that it was done by an 'admin' user.
|
||||||
"""
|
"""
|
||||||
return False, "Not a concrete driver"
|
return False, "Not a concrete driver"
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def remove(self, name, datastore, ds_version, data_file):
|
def remove(self, name, datastore, ds_version, data_file):
|
||||||
"""Remove the data from the guest instance. Return status and message
|
"""Remove the module from the guest instance. Return
|
||||||
as a tupple.
|
status and message as a tuple.
|
||||||
"""
|
"""
|
||||||
return False, "Not a concrete driver"
|
return False, "Not a concrete driver"
|
||||||
|
|
||||||
|
def configure(self, name, datastore, ds_version, data_file):
|
||||||
|
"""Configure the driver. This is particularly useful for adding values
|
||||||
|
to message_args, by having a line such as: self.message_args = <dict>.
|
||||||
|
These values will be appended to the default ones defined
|
||||||
|
in the message_args @property.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def output(log_message=None, success_message=None,
|
||||||
|
fail_message=None):
|
||||||
|
"""This is a decorator to trap the typical exceptions that occur
|
||||||
|
when applying and removing modules. It returns the proper output
|
||||||
|
corresponding to the error messages automatically. If the function
|
||||||
|
returns output (success_flag, message) then those are returned,
|
||||||
|
otherwise success is assumed and the success_message returned.
|
||||||
|
Using this removes a lot of potential boiler-plate code, however
|
||||||
|
it is not necessary.
|
||||||
|
Keyword arguments can be used in the message string. Default
|
||||||
|
values can be found in the message_args @property, however a
|
||||||
|
driver can add whatever it see fit, by setting message_args
|
||||||
|
to a dict in the configure call (see above). Thus if you set
|
||||||
|
self.message_args = {'my_key': 'my_key_val'} then the message
|
||||||
|
string could look like "My key is '$(my_key)s'".
|
||||||
|
"""
|
||||||
|
success_message = success_message or "Success"
|
||||||
|
fail_message = fail_message or "Fail"
|
||||||
|
|
||||||
|
def output_decorator(func):
|
||||||
|
"""This is the actual decorator."""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
"""Here's where we handle the error messages and return values
|
||||||
|
from the actual function.
|
||||||
|
"""
|
||||||
|
log_msg = log_message
|
||||||
|
success_msg = success_message
|
||||||
|
fail_msg = fail_message
|
||||||
|
if isinstance(args[0], ModuleDriver):
|
||||||
|
# Try and insert any message args if they exist in the driver
|
||||||
|
message_args = args[0].message_args
|
||||||
|
if message_args:
|
||||||
|
try:
|
||||||
|
log_msg = log_msg % message_args
|
||||||
|
success_msg = success_msg % message_args
|
||||||
|
fail_msg = fail_msg % message_args
|
||||||
|
except Exception:
|
||||||
|
# if there's a problem, just log it and drive on
|
||||||
|
LOG.warning(_("Could not apply message args: %s") %
|
||||||
|
message_args)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if log_msg:
|
||||||
|
LOG.info(log_msg)
|
||||||
|
success = False
|
||||||
|
try:
|
||||||
|
rv = func(*args, **kwargs)
|
||||||
|
if rv:
|
||||||
|
# Use the actual values, if there are some
|
||||||
|
success, message = rv
|
||||||
|
else:
|
||||||
|
success = True
|
||||||
|
message = success_msg
|
||||||
|
except exception.ProcessExecutionError as ex:
|
||||||
|
message = (_("%(msg)s: %(out)s\n%(err)s") %
|
||||||
|
{'msg': fail_msg,
|
||||||
|
'out': ex.stdout,
|
||||||
|
'err': ex.stderr})
|
||||||
|
message = message.replace(': \n', ': ')
|
||||||
|
message = message.rstrip()
|
||||||
|
LOG.exception(message)
|
||||||
|
except exception.TroveError as ex:
|
||||||
|
message = (_("%(msg)s: %(err)s") %
|
||||||
|
{'msg': fail_msg, 'err': ex._error_string})
|
||||||
|
LOG.exception(message)
|
||||||
|
except Exception as ex:
|
||||||
|
message = (_("%(msg)s: %(err)s") %
|
||||||
|
{'msg': fail_msg, 'err': ex.message})
|
||||||
|
LOG.exception(message)
|
||||||
|
return success, message
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return output_decorator
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Copyright 2016 Tesora, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from trove.common import cfg
|
||||||
|
from trove.common.i18n import _
|
||||||
|
from trove.common import stream_codecs
|
||||||
|
from trove.common import utils
|
||||||
|
from trove.guestagent.common import operating_system
|
||||||
|
from trove.guestagent.module.drivers import module_driver
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
NR_ADD_LICENSE_CMD = ['nrsysmond-config', '--set', 'license_key=%s']
|
||||||
|
NR_SRV_CONTROL_CMD = ['/etc/init.d/newrelic-sysmond']
|
||||||
|
|
||||||
|
|
||||||
|
class NewRelicLicenseDriver(module_driver.ModuleDriver):
|
||||||
|
"""Module to set up the license for the NewRelic service."""
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "New Relic License Module Driver"
|
||||||
|
|
||||||
|
def get_updated(self):
|
||||||
|
return date(2016, 4, 12)
|
||||||
|
|
||||||
|
@module_driver.output(
|
||||||
|
log_message=_('Installing New Relic license key'),
|
||||||
|
success_message=_('New Relic license key installed'),
|
||||||
|
fail_message=_('New Relic license key not installed'))
|
||||||
|
def apply(self, name, datastore, ds_version, data_file, admin_module):
|
||||||
|
license_key = None
|
||||||
|
data = operating_system.read_file(
|
||||||
|
data_file, codec=stream_codecs.KeyValueCodec())
|
||||||
|
for key, value in data.items():
|
||||||
|
if 'license_key' == key.lower():
|
||||||
|
license_key = value
|
||||||
|
break
|
||||||
|
if license_key:
|
||||||
|
self._add_license_key(license_key)
|
||||||
|
self._server_control('start')
|
||||||
|
else:
|
||||||
|
return False, "'license_key' not found in contents file"
|
||||||
|
|
||||||
|
def _add_license_key(self, license_key):
|
||||||
|
try:
|
||||||
|
exec_args = {'timeout': 10,
|
||||||
|
'run_as_root': True,
|
||||||
|
'root_helper': 'sudo'}
|
||||||
|
cmd = list(NR_ADD_LICENSE_CMD)
|
||||||
|
cmd[-1] = cmd[-1] % license_key
|
||||||
|
utils.execute_with_timeout(*cmd, **exec_args)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception(_("Could not install license key '%s'") %
|
||||||
|
license_key)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _server_control(self, command):
|
||||||
|
try:
|
||||||
|
exec_args = {'timeout': 10,
|
||||||
|
'run_as_root': True,
|
||||||
|
'root_helper': 'sudo'}
|
||||||
|
cmd = list(NR_SRV_CONTROL_CMD)
|
||||||
|
cmd.append(command)
|
||||||
|
utils.execute_with_timeout(*cmd, **exec_args)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception(_("Could not %s New Relic server") % command)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@module_driver.output(
|
||||||
|
log_message=_('Removing New Relic license key'),
|
||||||
|
success_message=_('New Relic license key removed'),
|
||||||
|
fail_message=_('New Relic license key not removed'))
|
||||||
|
def remove(self, name, datastore, ds_version, data_file):
|
||||||
|
self._add_license_key("bad_key_that_is_exactly_40_characters_xx")
|
||||||
|
self._server_control('stop')
|
|
@ -37,37 +37,24 @@ class PingDriver(module_driver.ModuleDriver):
|
||||||
be 'Hello.'
|
be 'Hello.'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_type(self):
|
|
||||||
return 'ping'
|
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return "Ping Guestagent Module Driver"
|
return "Ping Module Driver"
|
||||||
|
|
||||||
def get_updated(self):
|
def get_updated(self):
|
||||||
return date(2016, 3, 4)
|
return date(2016, 3, 4)
|
||||||
|
|
||||||
def apply(self, name, datastore, ds_version, data_file):
|
@module_driver.output(
|
||||||
success = False
|
log_message=_('Extracting %(type)s message'),
|
||||||
message = "Message not found in contents file"
|
fail_message=_('Could not extract %(type)s message'))
|
||||||
try:
|
def apply(self, name, datastore, ds_version, data_file, admin_module):
|
||||||
data = operating_system.read_file(
|
data = operating_system.read_file(
|
||||||
data_file, codec=stream_codecs.KeyValueCodec())
|
data_file, codec=stream_codecs.KeyValueCodec())
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if 'message' == key.lower():
|
if 'message' == key.lower():
|
||||||
success = True
|
return True, value
|
||||||
message = value
|
return False, 'Message not found in contents file'
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
# assume we couldn't read the file, because there was some
|
|
||||||
# issue with it (for example, it's a binary file). Just log
|
|
||||||
# it and drive on.
|
|
||||||
LOG.error(_("Could not extract contents from '%s' - possibly "
|
|
||||||
"a binary file?") % name)
|
|
||||||
|
|
||||||
return success, message
|
|
||||||
|
|
||||||
def _is_binary(self, data_str):
|
|
||||||
bool(data_str.translate(None, self.TEXT_CHARS))
|
|
||||||
|
|
||||||
|
@module_driver.output(
|
||||||
|
log_message=_('Removing %(type)s module'))
|
||||||
def remove(self, name, datastore, ds_version, data_file):
|
def remove(self, name, datastore, ds_version, data_file):
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
|
@ -61,17 +61,17 @@ class ModuleManager(object):
|
||||||
module_type, name, tenant, datastore,
|
module_type, name, tenant, datastore,
|
||||||
ds_version, module_id, md5, auto_apply, visible, now)
|
ds_version, module_id, md5, auto_apply, visible, now)
|
||||||
result = cls.read_module_result(module_dir, default_result)
|
result = cls.read_module_result(module_dir, default_result)
|
||||||
|
admin_module = cls.is_admin_module(tenant, auto_apply, visible)
|
||||||
try:
|
try:
|
||||||
|
driver.configure(name, datastore, ds_version, data_file)
|
||||||
applied, message = driver.apply(
|
applied, message = driver.apply(
|
||||||
name, datastore, ds_version, data_file)
|
name, datastore, ds_version, data_file, admin_module)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.exception(_("Could not apply module '%s'") % name)
|
LOG.exception(_("Could not apply module '%s'") % name)
|
||||||
applied = False
|
applied = False
|
||||||
message = ex.message
|
message = ex.message
|
||||||
finally:
|
finally:
|
||||||
status = 'OK' if applied else 'ERROR'
|
status = 'OK' if applied else 'ERROR'
|
||||||
admin_only = (not visible or tenant == cls.MODULE_APPLY_TO_ALL or
|
|
||||||
auto_apply)
|
|
||||||
result['removed'] = None
|
result['removed'] = None
|
||||||
result['status'] = status
|
result['status'] = status
|
||||||
result['message'] = message
|
result['message'] = message
|
||||||
|
@ -81,7 +81,7 @@ class ModuleManager(object):
|
||||||
result['tenant'] = tenant
|
result['tenant'] = tenant
|
||||||
result['auto_apply'] = auto_apply
|
result['auto_apply'] = auto_apply
|
||||||
result['visible'] = visible
|
result['visible'] = visible
|
||||||
result['admin_only'] = admin_only
|
result['admin_only'] = admin_module
|
||||||
cls.write_module_result(module_dir, result)
|
cls.write_module_result(module_dir, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -112,8 +112,7 @@ class ModuleManager(object):
|
||||||
def build_default_result(cls, module_type, name, tenant,
|
def build_default_result(cls, module_type, name, tenant,
|
||||||
datastore, ds_version, module_id, md5,
|
datastore, ds_version, module_id, md5,
|
||||||
auto_apply, visible, now):
|
auto_apply, visible, now):
|
||||||
admin_only = (not visible or tenant == cls.MODULE_APPLY_TO_ALL or
|
admin_module = cls.is_admin_module(tenant, auto_apply, visible)
|
||||||
auto_apply)
|
|
||||||
result = {
|
result = {
|
||||||
'type': module_type,
|
'type': module_type,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
@ -129,11 +128,16 @@ class ModuleManager(object):
|
||||||
'removed': None,
|
'removed': None,
|
||||||
'auto_apply': auto_apply,
|
'auto_apply': auto_apply,
|
||||||
'visible': visible,
|
'visible': visible,
|
||||||
'admin_only': admin_only,
|
'admin_only': admin_module,
|
||||||
'contents': None,
|
'contents': None,
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_admin_module(cls, tenant, auto_apply, visible):
|
||||||
|
return (not visible or tenant == cls.MODULE_APPLY_TO_ALL or
|
||||||
|
auto_apply)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read_module_result(cls, result_file, default=None):
|
def read_module_result(cls, result_file, default=None):
|
||||||
result_file = cls.get_result_filename(result_file)
|
result_file = cls.get_result_filename(result_file)
|
||||||
|
@ -203,6 +207,7 @@ class ModuleManager(object):
|
||||||
raise exception.NotFound(
|
raise exception.NotFound(
|
||||||
_("Module '%s' has not been applied") % name)
|
_("Module '%s' has not been applied") % name)
|
||||||
try:
|
try:
|
||||||
|
driver.configure(name, datastore, ds_version, contents_file)
|
||||||
removed, message = driver.remove(
|
removed, message = driver.remove(
|
||||||
name, datastore, ds_version, contents_file)
|
name, datastore, ds_version, contents_file)
|
||||||
cls.remove_module_result(module_dir)
|
cls.remove_module_result(module_dir)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import Crypto.Random
|
import Crypto.Random
|
||||||
from proboscis import SkipTest
|
from proboscis import SkipTest
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from troveclient.compat import exceptions
|
from troveclient.compat import exceptions
|
||||||
|
@ -794,8 +795,10 @@ class ModuleRunner(TestRunner):
|
||||||
self.assert_equal(expected_contents, module_apply.contents,
|
self.assert_equal(expected_contents, module_apply.contents,
|
||||||
'%s Unexpected contents' % prefix)
|
'%s Unexpected contents' % prefix)
|
||||||
if expected_message is not None:
|
if expected_message is not None:
|
||||||
self.assert_equal(expected_message, module_apply.message,
|
regex = re.compile(expected_message)
|
||||||
'%s Unexpected message' % prefix)
|
self.assert_true(regex.match(module_apply.message),
|
||||||
|
"%s Unexpected message '%s', expected '%s'" %
|
||||||
|
(prefix, module_apply.message, expected_message))
|
||||||
if expected_status is not None:
|
if expected_status is not None:
|
||||||
self.assert_equal(expected_status, module_apply.status,
|
self.assert_equal(expected_status, module_apply.status,
|
||||||
'%s Unexpected status' % prefix)
|
'%s Unexpected status' % prefix)
|
||||||
|
@ -824,7 +827,8 @@ class ModuleRunner(TestRunner):
|
||||||
% module.name)
|
% module.name)
|
||||||
elif self.MODULE_BINARY_SUFFIX in module.name:
|
elif self.MODULE_BINARY_SUFFIX in module.name:
|
||||||
status = 'ERROR'
|
status = 'ERROR'
|
||||||
message = 'Message not found in contents file'
|
message = ('^(Could not extract ping message|'
|
||||||
|
'Message not found in contents file).*')
|
||||||
contents = self.MODULE_BINARY_CONTENTS
|
contents = self.MODULE_BINARY_CONTENTS
|
||||||
if self.MODULE_BINARY_SUFFIX2 in module.name:
|
if self.MODULE_BINARY_SUFFIX2 in module.name:
|
||||||
contents = self.MODULE_BINARY_CONTENTS2
|
contents = self.MODULE_BINARY_CONTENTS2
|
||||||
|
|
Loading…
Reference in New Issue