95
class JujuAssertionError(AssertionError):
96
"""Exception for juju assertion failures."""
99
class JujuResourceTimeout(Exception):
100
"""A timeout exception for a resource not being downloaded into a unit."""
92
class ErrJujuPath(Exception):
93
"""An exception for an invalid juju binary path."""
96
class enforce_juju_path(argparse.Action):
97
"""Enforces that a path ending with juju is given."""
98
def __call__(self, parser, namespace, values, option_string=None):
99
if not values.endswith(('/juju', '\\juju.exe')):
101
"%s: The full path to the juju binary is required." % values)
102
setattr(namespace, self.dest, values)
103
105
def _clean_dir(maybe_dir):
110
112
contents = os.listdir(maybe_dir)
111
113
except OSError as e:
112
if e.errno == errno.ENOENT:
113
# we don't raise this error due to tests abusing /tmp/logs
114
warnings.warn('Not a directory {}'.format(maybe_dir))
115
if e.errno == errno.EEXIST:
116
warnings.warn('Directory {} already exists'.format(maybe_dir))
114
if e.errno != errno.ENOENT:
116
# GZ 2016-03-01: We may want to raise or just create the dir here, but
117
# that confuses expectations of all existing parse_args tests.
118
119
if contents and contents != ["empty"]:
120
'Directory {!r} has existing contents.'.format(maybe_dir))
120
warnings.warn("Directory %r has existing contents." % (maybe_dir,))
124
124
def pause(seconds):
125
print_now('Sleeping for {:d} seconds.'.format(seconds))
125
print_now('Sleeping for %d seconds.' % seconds)
257
def get_auth_token(root, job):
258
tree = ET.parse(os.path.join(root, 'jobs', job, 'config.xml'))
259
return tree.getroot().find('authToken').text
257
262
def check_free_disk_space(path, required, purpose):
258
263
df_result = subprocess.check_output(["df", "-k", path])
259
264
df_result = df_result.split('\n')[1]
292
297
return subprocess.check_output(command)
295
def _get_test_name_from_filename():
297
calling_file = sys._getframe(2).f_back.f_globals['__file__']
298
return os.path.splitext(os.path.basename(calling_file))[0]
300
return 'unknown_test'
303
def _generate_default_temp_env_name():
304
"""Creates a new unique name for environment and returns the name"""
305
# we need to sanitize the name
306
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
307
test_name = re.sub('[^a-zA-Z]', '', _get_test_name_from_filename())
308
return '{}-{}-temp-env'.format(test_name, timestamp)
311
def _generate_default_binary():
312
"""Returns GOPATH juju binary if it exists, otherwise /usr/bin/juju"""
313
if os.getenv('GOPATH'):
314
go_bin = os.getenv('GOPATH') + '/bin/juju'
315
if os.path.isfile(go_bin):
318
return '/usr/bin/juju'
321
def _to_deadline(timeout):
322
return datetime.utcnow() + timedelta(seconds=int(timeout))
325
def add_basic_testing_arguments(parser, using_jes=False, deadline=True):
300
def add_basic_testing_arguments(parser, using_jes=False):
326
301
"""Returns the parser loaded with basic testing arguments.
328
303
The basic testing arguments, used in conjuction with boot_context ensures
329
304
a test can be run in any supported substrate in parallel.
331
This helper adds 4 positional arguments that defines the minimum needed
332
to run a test script.
334
These arguments (env, juju_bin, logs, temp_env_name) allow you to specify
335
specifics for which env, juju binary, which folder for logging and an
336
environment name for your test respectively.
306
This helper adds 4 positional arguments that define the minimum needed
307
to run a test script: env, juju_bin, logs, and temp_env_name.
338
309
There are many optional args that either update the env's config or
339
310
manipulate the juju command line options to test in controlled situations
344
315
:param parser: an ArgumentParser.
345
316
:param using_jes: whether args should be tailored for JES testing.
346
:param deadline: If true, support the --timeout option and convert to a
350
# Optional postional arguments
353
help='The juju environment to base the temp test environment on.',
355
parser.add_argument('juju_bin', nargs='?',
356
help='Full path to the Juju binary. By default, this'
357
' will use $GOPATH/bin/juju or /usr/bin/juju in that'
359
default=_generate_default_binary())
360
parser.add_argument('logs', nargs='?', type=_clean_dir,
361
help='A directory in which to store logs. By default,'
362
' this will use the current directory',
364
parser.add_argument('temp_env_name', nargs='?',
365
help='A temporary test environment name. By default, '
366
' this will generate an enviroment name using the '
367
' timestamp and testname. '
368
' test_name_timestamp_temp_env',
369
default=_generate_default_temp_env_name())
318
# Required positional arguments.
321
help='The juju environment to base the temp test environment on.')
323
'juju_bin', action=enforce_juju_path,
324
help='Full path to the Juju binary.')
326
'logs', type=_clean_dir, help='A directory in which to store logs.')
328
'temp_env_name', help='A temporary test environment name.')
371
329
# Optional keyword arguments.
372
330
parser.add_argument('--debug', action='store_true',
373
331
help='Pass --debug to Juju.')
392
350
parser.add_argument('--keep-env', action='store_true',
393
351
help='Keep the Juju environment after the test'
396
parser.add_argument('--timeout', dest='deadline', type=_to_deadline,
397
help="The script timeout, in seconds.")
489
444
print_now(output)
492
def wait_for_removed_services(client, charm):
493
"""Timeout until the remove process ends"""
494
for ignored in until_timeout(60):
495
status = client.get_status()
496
if charm not in status.get_applications():
500
def unqualified_model_name(model_name):
501
"""Return the model name with the owner qualifier stripped if present."""
502
return model_name.split('/', 1)[-1]
505
def qualified_model_name(model_name, owner_name):
506
"""Return the model name qualified with the given owner name."""
507
if model_name == '' or owner_name == '':
509
'Neither model_name nor owner_name can be blank strings')
511
parts = model_name.split('/', 1)
512
if len(parts) == 2 and parts[0] != owner_name:
514
'qualified model name {} with owner not matching {}'.format(
515
model_name, owner_name))
516
return '{}/{}'.format(owner_name, parts[-1])
447
def local_charm_path(charm, juju_ver, series=None, repository=None,
449
"""Create either Juju 1.x or 2.x local charm path."""
450
if juju_ver.startswith('1.'):
452
series = '{}/'.format(series)
455
local_path = 'local:{}{}'.format(series, charm)
461
'centos': 'charms-centos'}
464
abs_path = os.path.join(repository, charm)
465
elif os.environ.get('JUJU_REPOSITORY'):
466
repository = os.path.join(
467
os.environ['JUJU_REPOSITORY'], charm_dir[platform])
468
abs_path = os.path.join(repository, charm)
472
def make_charm(charm_dir, min_ver='1.25.0', name='dummy',
473
description='description', summary='summary', series='trusty'):
474
metadata = os.path.join(charm_dir, 'metadata.yaml')
476
content['name'] = name
477
if min_ver is not None:
478
content['min-juju-version'] = min_ver
479
content['summary'] = summary
480
content['description'] = description
481
if series is not None:
482
content['series'] = [series] if isinstance(series, str) else series
483
with open(metadata, 'w') as f:
484
yaml.safe_dump(content, f, default_flow_style=False)