def assert_storage_is_intact(client, expected_results): """Ensure stored tokens match the expected values in `expected_results`. Checks the token values stored on the storage assigned to the deployed dummy-storage charm. Only matches the provided expected token values and ignores any that have no values provided for them. :param client: ModelClient object where dummy-storage application is deployed :param expected_results: Dict containing 'token name' -> 'expected value' look up values. :raises JujuAssertionError: If expected token values are not present in the stored token details. """ stored_content = get_stored_token_content(client) for expected_name, expected_value in expected_results.iteritems(): try: stored_value = stored_content[expected_name] except KeyError: raise JujuAssertionError( 'Expected token "{}" not found in stored results.'.format( expected_name)) if stored_value != expected_value: raise JujuAssertionError( 'Token values do not match. Expected: {} got: {}'.format( expected_value, stored_value))
def assert_cloud_details_are_correct(client, cloud_name, example_cloud): """ Check juju add-cloud is performed successfully and it is available in the juju client. :param client: The juju client :param cloud_name: The name of the cloud added :param example_cloud: The content of the cloud """ clouds = client.env.read_clouds() try: if clouds['clouds'][cloud_name] != example_cloud: raise JujuAssertionError('Cloud mismatch') except KeyError: raise JujuAssertionError('Cloud missing {}'.format(cloud_name))
def assess_instance_type(client, provider, instance_type): """Assess the instance-type option for constraints""" if instance_type not in INSTANCE_TYPES[provider]: raise JujuAssertionError(instance_type) constraints = Constraints(instance_type=instance_type) charm_name = 'instance-type-{}'.format(instance_type.replace('.', '-')) assess_constraints_deploy(client, constraints, charm_name)
def assert_model_has_correct_controller_uuid(client): model_details = client.show_model() model_controller_uuid = model_details[ client.env.environment]['controller-uuid'] controller_uuid = client.get_controller_uuid() if model_controller_uuid != controller_uuid: raise JujuAssertionError()
def assert_deploy(client, test, charm_path, repository=None): """Deploy a charm and assert a success or fail. :param client: Juju client :type client: jujupy.ModelClient :param test: Deploy test data. :type test: Test :param charm_dir: :type charm_dir: str :param repository: Direcotry path to the repository :type repository: str :return: None """ if test.success: client.deploy(charm=charm_path, series=test.series, service=test.service, force=test.force, repository=repository) client.wait_for_started() else: try: client.deploy(charm=charm_path, series=test.series, service=test.service, force=test.force, repository=repository) except subprocess.CalledProcessError: return raise JujuAssertionError('Assert deploy failed for {}'.format(test))
def destroy_model(client, new_client): log.info('Destroying model "{}"'.format(TEST_MODEL)) new_client.destroy_model() new_model = get_current_model(client) if new_model: error = 'Juju failed to unset model after it was destroyed' raise JujuAssertionError(error)
def assert_command_succeeds(check_callable, command_type, permission): try: check_callable() except subprocess.CalledProcessError: raise JujuAssertionError( 'FAIL User unable to perform {} operation with ' 'permission {}'.format(command_type, permission))
def expect_migration_attempt_to_fail(source_client, dest_client): """Ensure that the migration attempt fails due to permissions. As we're capturing the stderr output it after we're done with it so it appears in test logs. """ try: if after_22beta4(source_client.version): args = [ '{}:{}'.format(source_client.env.controller.name, source_client.env.environment), dest_client.env.controller.name ] else: args = [ '-c', source_client.env.controller.name, source_client.env.environment, dest_client.env.controller.name ] log_output = source_client.get_juju_output('migrate', *args, merge_stderr=True, include_e=False) except CalledProcessError as e: print(e.output, file=sys.stderr) if 'permission denied' not in e.output: raise log.info('SUCCESS: Migrate command failed as expected.') else: print(log_output, file=sys.stderr) raise JujuAssertionError('Migration did not fail as expected.')
def wait_until_model_disappears(client, model_name, timeout=60): """Waits for a while for 'model_name' model to no longer be listed. :raises JujuAssertionError: If the named model continues to be listed in list-models after specified timeout. """ def model_check(client): try: models = client.get_controller_client().get_models() except CalledProcessError as e: # It's possible that we've tried to get status from the model as # it's being removed. # We can't consider the model gone yet until we don't get this # error and the model is no longer in the output. if 'cannot get model details' not in e.stderr: raise else: # 2.2-rc1 introduced new model listing output name/short-name. all_model_names = [ m.get('short-name', m['name']) for m in models['models'] ] if model_name not in all_model_names: return True try: _wait_for_model_check(client, model_check, timeout) except ModelCheckFailed: raise JujuAssertionError( 'Model "{}" failed to be removed after {} seconds'.format( model_name, timeout))
def ensure_api_login_redirects(source_client, dest_client): """Login attempts must get transparently redirected to the new controller. """ new_model_client = deploy_dummy_source_to_new_model( source_client, 'api-redirection') # show model controller details before_model_details = source_client.show_model() assert_model_has_correct_controller_uuid(source_client) log.info('Attempting migration process') migrated_model_client = migrate_model_to_controller( new_model_client, dest_client) # check show model controller details assert_model_has_correct_controller_uuid(migrated_model_client) after_migration_details = migrated_model_client.show_model() before_controller_uuid = before_model_details[ source_client.env.environment]['controller-uuid'] after_controller_uuid = after_migration_details[ migrated_model_client.env.environment]['controller-uuid'] if before_controller_uuid == after_controller_uuid: raise JujuAssertionError() # Check file for details. assert_data_file_lists_correct_controller_for_model( migrated_model_client, expected_controller=dest_client.env.controller.name)
def assert_deployed_charm_is_responding(client, expected_output=None): """Ensure that the deployed simple-server charm is still responding.""" # Set default value if needed. if expected_output is None: expected_output = 'simple-server.' ipaddress = get_unit_ipaddress(client, 'simple-resource-http/0') if expected_output != get_server_response(ipaddress): raise JujuAssertionError('Server charm is not responding as expected.')
def assert_config_value(client, attribute, source, value): config_values = client.get_model_config() try: source_value = config_values[attribute]['source'] attr_value = config_values[attribute]['value'] if attr_value != value: raise JujuAssertionError( 'Unexpected value for {}.\nGot {} instead of {}'.format( attribute, attr_value, value)) if source_value != source: raise JujuAssertionError( 'Unexpected source for {}.\nGot {} instead of {}'.format( attribute, source_value, source)) except KeyError: raise ValueError( 'Attribute {} not found in config values.'.format(attribute))
def private_address(client, host): default_route = ssh(client, host, 'ip -4 -o route list 0/0') log.info("Default route from {}: {}".format(host, default_route)) # Match the device that is the word after 'dev'. eg. # default via 10.0.30.1 dev br-eth1 onlink' route_match = re.search(r'\sdev\s([\w-]+)', default_route) if route_match is None: raise JujuAssertionError( "Failed to find device in {}".format(default_route)) device = route_match.group(1) log.info("Fetching the device IP of {}".format(device)) device_ip = ssh(client, host, 'ip -4 -o addr show {}'.format(device)) log.info("Device IP for {}: {}".format(host, device_ip)) ip_match = re.search(r'inet\s+(\S+)/\d+\s', device_ip) if ip_match is None: raise JujuAssertionError( "Failed to find ip for device: {}".format(device)) return ip_match.group(1)
def assert_command_fails(check_callable, command_type, permission): try: check_callable() except subprocess.CalledProcessError: pass else: raise JujuAssertionError('FAIL User performed {} operation with ' 'permission {}'.format( command_type, permission))
def assess_to(bs_manager, to): """Assess bootstraping with the --to option.""" if to is None: raise ValueError('--to not given when testing to') with thin_booted_context(bs_manager) as client: log.info('To {} bootstrap successful.'.format(to)) addr = get_controller_hostname(client) if addr != to: raise JujuAssertionError('Not bootstrapped to the correct address.')
def check_remote_log_for_content(remote_machine, unit, check_string, script_path): try: remote_machine.juju('ssh', (unit, 'sudo', 'python', script_path, check_string, '/var/log/syslog')) log.info('Check script passed on target machine.') except subprocess.CalledProcessError: # This is where a failure happened raise JujuAssertionError('Forwarded log message never appeared.')
def assess_virt_type(client, virt_type): """Assess the virt-type option for constraints""" if virt_type not in VIRT_TYPES: raise JujuAssertionError(virt_type) constraints = Constraints(virt_type=virt_type) charm_name = 'virt-type-{}'.format(virt_type) charm_series = 'xenial' with temp_dir() as charm_dir: deploy_charm_constraint(client, constraints, charm_name, charm_series, charm_dir)
def mem_to_int(size): """Convert an argument size into a number of megabytes.""" if not re.match(re.compile('^[0123456789]+[MGTP]?$'), size): raise JujuAssertionError('Not a size format:', size) if size[-1] in 'MGTP': val = int(size[0:-1]) unit = size[-1] return val * (1024**'MGTP'.find(unit)) else: return int(size)
def raise_if_contents_differ(resource_contents, file_contents): if resource_contents != file_contents: raise JujuAssertionError( dedent("""\ Resource contents mismatch. Expected: {} Got: {}""".format( file_contents, resource_contents)))
def check_resource_uploaded_revno( charm_command, charm_url, resource_name, revno): """Parse list-resources and ensure resource revno is equal to `revno`. :raises JujuAssertionError: If the resources revision is not equal to `revno` """ revno = int(revno) output = charm_command.run('list-resources', charm_url) for line in output.split('\n'): if line.startswith(resource_name): rev = int(line.split(None, 1)[-1]) if rev != revno: raise JujuAssertionError( 'Failed to upload resource and increment revision number.') return raise JujuAssertionError( 'Failed to find named resource "{}" in output'.format(resource_name))
def assert_data_file_lists_correct_controller_for_model( client, expected_controller): models_path = os.path.join(client.env.juju_home, 'models.yaml') with open(models_path, 'rt') as f: models_data = yaml.safe_load(f) controller_models = models_data['controllers'][expected_controller][ 'models'] if client.env.environment not in controller_models: raise JujuAssertionError()
def assess_metadata(bs_manager, local_source): client = bs_manager.client # This disconnects from the metadata source, as INVALID_URL is different. # agent-metadata-url | tools-metadata-url client.env.update_config({'agent-metadata-url': INVALID_URL}) with prepare_temp_metadata(client, local_source) as metadata_dir: log.info('Metadata written to: {}'.format(metadata_dir)) with thin_booted_context(bs_manager, metadata_source=metadata_dir): log.info('Metadata bootstrap successful.') data = client.get_model_config() if INVALID_URL != data['agent-metadata-url']['value']: raise JujuAssertionError('Error, possible web metadata.')
def verify_credentials_match(env, cred): """Verify the credentials entered match the stored credentials. :param env: String environment name :param cred: Dict of credential information """ with open(os.path.join(os.environ['JUJU_DATA'], 'credentials.yaml')) as f: test_creds = yaml.load(f) test_creds = test_creds['credentials'][env][env] if not test_creds == cred['credentials']: error = 'Credential miss-match after manual add' raise JujuAssertionError(error)
def raise_if_shared_machines(unit_machines): """Raise an exception if `unit_machines` contain double ups of machine ids. A unique list of machine ids will be equal in length to the set of those machine ids. :raises ValueError: if an empty list is passed in. :raises JujuAssertionError: if any double-ups of machine ids are detected. """ if not unit_machines: raise ValueError('Cannot share 0 machines. Empty list provided.') if len(unit_machines) != len(set(unit_machines)): raise JujuAssertionError('Appliction units reside on the same machine')
def verify_deployed_tool(agent_dir, client, agent_stream): """ Verify the bootstrapped controller makes use of the the specified agent-metadata-url. :param agent_dir: The top level directory location of agent file. :param client: Juju client :param agent_stream: String representing agent stream name """ controller_url, controller_sha256 = get_controller_url_and_sha256(client) local_url, local_sha256 = get_local_url_and_sha256( agent_dir, controller_url, agent_stream) if local_url != controller_url: raise JujuAssertionError( "mismatch local URL {} and controller URL {}".format( local_url, controller_url)) if local_sha256 != controller_sha256: raise JujuAssertionError( "mismatch local SHA256 {} and controller SHA256 {}".format( local_sha256, controller_sha256))
def assess_virt_type_constraints(client, test_kvm=False): """Assess deployment with virt-type constraints.""" if test_kvm: VIRT_TYPES.append("kvm") for virt_type in VIRT_TYPES: assess_virt_type(client, virt_type) try: assess_virt_type(client, 'aws') except JujuAssertionError: log.info("Correctly rejected virt-type aws") else: raise JujuAssertionError("FAIL: Client deployed with virt-type aws") if test_kvm: VIRT_TYPES.remove("kvm")
def verify_status(status, resource_id, name, fingerprint, size): resources = status['resources'] for resource in resources: if resource['expected']['resourceid'] == resource_id: if 'serviceid' in resource['unit']: service_app_id = 'serviceid' else: service_app_id = 'applicationId' expected_values = _resource_info(name, fingerprint, size, service_app_id) assert_dict_is_subset(expected_values, resource['expected']) assert_dict_is_subset(expected_values, resource['unit']) break else: raise JujuAssertionError('Resource id not found.')
def assert_metadata_is_correct(expected_agent_metadata_url, client): """ verify the client agent-metadata-url matches the specified value :param expected_agent_metadata_url: The expected agent file path. :param client: Juju client """ data = client.get_model_config() actual_agent_metadata_url = data['agent-metadata-url']['value'] if expected_agent_metadata_url != actual_agent_metadata_url: raise JujuAssertionError( 'agent-metadata-url mismatch. Expected: {} Got: {}'.format( expected_agent_metadata_url, actual_agent_metadata_url)) log.info('bootstrap successfully with agent-metadata-url={}'.format( actual_agent_metadata_url))
def switch_model(client, current_model, current_controller): """Switches back to the old model. :param client: Jujupy ModelClient object :param current_model: String name of initial testing model :param current_controller: String name of testing controller """ client.switch(model=current_model, controller=current_controller) new_model = get_current_model(client) if new_model == current_model: log.info('Current model and switch target match') else: error = ('Juju failed to switch back to existing model. ' 'Expected {} got {}'.format(TEST_MODEL, new_model)) raise JujuAssertionError(error)
def assert_logs_appear_in_client_model(client, expected_logs, timeout): """Assert that `expected_logs` appear in client logs within timeout. :param client: ModelClient object to query logs of. :param expected_logs: string containing log contents to check for. :param timeout: int seconds to wait for before raising JujuAssertionError. """ for _ in until_timeout(timeout): current_logs = client.get_juju_output('debug-log', '--no-tail', '--replay', '-l', 'DEBUG') if expected_logs in current_logs: log.info('SUCCESS: logs migrated.') return sleep(1) raise JujuAssertionError( 'Logs failed to be migrated after {}'.format(timeout))