diff --git a/doc/source/api.rst b/doc/source/api.rst index a627eea..8d7ef7b 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -11,3 +11,4 @@ Utilities API documentation exception-utils generic-utils juju-utils + test-utils diff --git a/doc/source/test-utils.rst b/doc/source/test-utils.rst new file mode 100644 index 0000000..20ad2e5 --- /dev/null +++ b/doc/source/test-utils.rst @@ -0,0 +1,5 @@ +Test Utils +---------- + +.. automodule:: zaza.charm_tests.test_utils + :members: diff --git a/zaza/model.py b/zaza/model.py index 1ca7e86..d7a9ee9 100644 --- a/zaza/model.py +++ b/zaza/model.py @@ -206,8 +206,11 @@ get_unit_time = sync_wrapper(async_get_unit_time) async def async_get_unit_service_start_time(model_name, unit_name, service, timeout=None): - """Return the time (in seconds since Epoch) that the given service was - started on the given unit. If the service is not running return 0 + """Return the time that the given service was started on a unit. + + Return the time (in seconds since Epoch) that the given service was + started on the given unit. If the service is not running raise + ServiceNotRunning exception. :param model_name: Name of model to query. :type model_name: str @@ -239,8 +242,7 @@ async def async_get_application(model_name, application_name): :type model_name: str :param application_name: Name of application to retrieve units for :type application_name: str - - :returns: Appliction object + :returns: Application object :rtype: object """ async with run_in_model(model_name) as model: @@ -256,7 +258,6 @@ async def async_get_units(model_name, application_name): :type model_name: str :param application_name: Name of application to retrieve units for :type application_name: str - :returns: List of juju units :rtype: [juju.unit.Unit, juju.unit.Unit,...] """ @@ -273,7 +274,6 @@ async def async_get_machines(model_name, application_name): :type model_name: str :param application_name: Name of application to retrieve units for :type application_name: str - :returns: List of juju machines :rtype: [juju.machine.Machine, juju.machine.Machine,...] """ @@ -293,7 +293,6 @@ def get_first_unit_name(model_name, application_name): :type model_name: str :param application_name: Name of application :type application_name: str - :returns: Name of lowest numbered unit :rtype: str """ @@ -307,7 +306,6 @@ def get_app_ips(model_name, application_name): :type model_name: str :param application_name: Name of application :type application_name: str - :returns: List of ip addresses :rtype: [str, str,...] """ @@ -321,7 +319,6 @@ async def async_get_application_config(model_name, application_name): :type model_name: str :param application_name: Name of application :type application_name: str - :returns: Dictionary of configuration :rtype: dict """ @@ -341,8 +338,6 @@ async def async_set_application_config(model_name, application_name, :type application_name: str :param configuration: Dictionary of configuration setting(s) :type configuration: dict - :returns: None - :rtype: None """ async with run_in_model(model_name) as model: return await (model.applications[application_name] @@ -356,7 +351,6 @@ async def async_get_status(model_name): :param model_name: Name of model to query. :type model_name: str - :returns: dictionary of juju status :rtype: dict """ @@ -465,8 +459,9 @@ def check_model_for_hard_errors(model): def check_unit_workload_status(model, unit, state): """Check that the units workload status matches the supplied state. - This function has the side effect of also checking for *any* units - in an error state and aborting if any are found. + + This function has the side effect of also checking for *any* units + in an error state and aborting if any are found. :param model: Model object to check in :type model: juju.Model @@ -484,11 +479,13 @@ def check_unit_workload_status(model, unit, state): def check_unit_workload_status_message(model, unit, message=None, prefixes=None): - """Check that the units workload status message matches the supplied - message or starts with one of the supplied prefixes. Raises an exception - if neither prefixes or message is set. This function has the side effect - of also checking for *any* units in an error state and aborting if any - are found. + """Check that the units workload status message. + + Check that the units workload status message matches the supplied + message or starts with one of the supplied prefixes. Raises an exception + if neither prefixes or message is set. This function has the side effect + of also checking for *any* units in an error state and aborting if any + are found. :param model: Model object to check in :type model: juju.Model @@ -520,15 +517,15 @@ async def async_wait_for_application_states(model_name, states=None, message that starts with one of the approved_message_prefixes. Bespoke statuses and messages can be passed in with states. states takes - the form: - - { - 'app': { - 'workload-status': 'blocked', - 'workload-status-message': 'No requests without a prod'} - 'anotherapp': { - 'workload-status-message': 'Unit is super ready'}} + the form:: + states = { + 'app': { + 'workload-status': 'blocked', + 'workload-status-message': 'No requests without a prod'} + 'anotherapp': { + 'workload-status-message': 'Unit is super ready'}} + wait_for_application_states('modelname', states=states) :param model_name: Name of model to query. :type model_name: str @@ -581,10 +578,14 @@ wait_for_application_states = sync_wrapper(async_wait_for_application_states) async def async_block_until_all_units_idle(model_name, timeout=2700): """Block until all units in the given model are idle + An example accessing this function via its sync wrapper:: + + block_until_all_units_idle('modelname') + :param model_name: Name of model to query. :type model_name: str :param timeout: Time to wait for status to be achieved - :type timeout: int + :type timeout: float """ async with run_in_model(model_name) as model: await model.block_until( @@ -595,8 +596,16 @@ block_until_all_units_idle = sync_wrapper(async_block_until_all_units_idle) async def async_block_until_service_status(model_name, unit_name, services, target_status, timeout=2700): - """Block until all services on the unit are in the desired state (stopped - or running) + """Block until all services on the unit are in the desired state. + + Block until all services on the unit are in the desired state (stopped + or running):: + + block_until_service_status( + 'modelname', + first_unit, + ['galnce-api'], + 'running') :param model_name: Name of model to query. :type model_name: str @@ -673,11 +682,11 @@ async def async_block_until(*conditions, timeout=None, wait_period=0.5, :param conditions: Functions to evaluate. :type conditions: functions - :param timeout: Timeout in secounds + :param timeout: Timeout in seconds :type timeout: float - :param wait_period: Time to wait between re-assing conditions. + :param wait_period: Time to wait between re-assessing conditions. :type wait_period: float - :param loop: The evnt loop to use + :param loop: The event loop to use :type loop: An event loop """ @@ -697,8 +706,11 @@ async def async_block_until(*conditions, timeout=None, wait_period=0.5, async def async_block_until_file_ready(model_name, application_name, remote_file, check_function, timeout=2700): - """Block until the check_function passes against the contents of the given - file on all units of the application + """Block until the check_function passes against. + + Block until the check_function passes against the provided file. It is + unlikely that a test would call this function directly, rather it is + provided as scaffolding for tests with a more specialised purpose. :param model_name: Name of model to query. :type model_name: str @@ -738,8 +750,19 @@ async def async_block_until_file_ready(model_name, application_name, async def async_block_until_file_has_contents(model_name, application_name, remote_file, expected_contents, timeout=2700): - """Block until the expected_contents are in the given file on all units of - the application + """Block until the expected_contents are present on all units + + Block until the given string (expected_contents) is present in the file + (remote_file) on all units of the given application. + + An example accessing this function via its sync wrapper:: + + block_until_file_has_contents( + 'modelname' + 'keystone', + '/etc/apache2/apache2.conf', + 'KeepAlive On') + :param model_name: Name of model to query. :type model_name: str @@ -766,9 +789,33 @@ async def async_block_until_oslo_config_entries_match(model_name, remote_file, expected_contents, timeout=2700): - """Block until the expected_contents are in the given file on all units of - the application + """Block until dict is represented in the file using oslo.config parser + Block until the expected_contents are in the given file on all units of + the application. For example to check for the following configuration:: + + [DEFAULT] + debug = False + + [glance_store] + filesystem_store_datadir = /var/lib/glance/images/ + default_store = file + + Call the check via its sync wrapper:: + + expected_contents = { + 'DEFAULT': { + 'debug': ['False']}, + 'glance_store': { + 'filesystem_store_datadir': ['/var/lib/glance/images/'], + 'default_store': ['file']}} + + block_until_oslo_config_entries_match( + 'modelname', + 'glance', + '/etc/glance/glance-api.conf', + expected_contents) + :param model_name: Name of model to query. :type model_name: str :param application_name: Name of application @@ -781,23 +828,6 @@ async def async_block_until_oslo_config_entries_match(model_name, :param timeout: Time to wait for contents to appear in file :type timeout: float - For example to check for, - - [DEFAULT] - debug = False - - [glance_store] - filesystem_store_datadir = /var/lib/glance/images/ - default_store = file - - use: - - expected_contents = { - 'DEFAULT': { - 'debug': ['False']}, - 'glance_store': { - 'filesystem_store_datadir': ['/var/lib/glance/images/'], - 'default_store': ['file']}} """ def f(x): # Writing out the file that was just read is suboptimal @@ -823,6 +853,15 @@ block_until_oslo_config_entries_match = sync_wrapper( async def async_block_until_services_restarted(model_name, application_name, mtime, services, timeout=2700): """Block until the given services have a start time later then mtime + + For example to check that the glance-api service has been restarted:: + + block_until_services_restarted( + 'modelname' + 'glance', + 1528294585, + ['glance-api']) + :param model_name: Name of model to query. :type model_name: str :param application_name: Name of application @@ -859,6 +898,15 @@ block_until_services_restarted = sync_wrapper( async def async_block_until_unit_wl_status(model_name, unit_name, status, timeout=2700): """Block until the given unit has the desired workload status + + A units workload status may change during a given action. This function + blocks until the given unit has the desired workload status:: + + block_until_unit_wl_status( + 'modelname', + aunit, + 'active') + :param model_name: Name of model to query. :type model_name: str :param unit_name: Name of unit to run action on diff --git a/zaza/utilities/openstack.py b/zaza/utilities/openstack.py index 25ab78e..deee473 100644 --- a/zaza/utilities/openstack.py +++ b/zaza/utilities/openstack.py @@ -365,8 +365,6 @@ def configure_gateway_ext_port(novaclient, neutronclient, :type dvr_mode: boolean :param net_id: Network ID :type net_id: string - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ if dvr_mode: @@ -630,8 +628,6 @@ def update_subnet_dns(neutron_client, subnet, dns_servers): :type subnet: dict :param dns_servers: Comma separted list of IP addresses :type project_id: string - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ msg = { @@ -681,8 +677,6 @@ def plug_extnet_into_router(neutron_client, router, network): :type router: dict :param network: Network object :type network: dict - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ ports = neutron_client.list_ports(device_owner='network:router_gateway', @@ -708,8 +702,6 @@ def plug_subnet_into_router(neutron_client, router, network, subnet): :type network: dict :param subnet: Subnet object :type subnet: dict - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ routers = neutron_client.list_routers(name=router) @@ -849,8 +841,6 @@ def add_network_to_bgp_speaker(neutron_client, bgp_speaker, network_name): :type bgp_speaker: dict :param network_name: Name of network to advertise :type network_name: string - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ network_id = get_net_uuid(neutron_client, network_name) @@ -913,8 +903,6 @@ def add_peer_to_bgp_speaker(neutron_client, bgp_speaker, bgp_peer): :type bgp_speaker: dict :param bpg_peer: BGP peer object :type bgp_peer: dict - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ # Handle the expected exception if the peer is already on the @@ -936,8 +924,6 @@ def add_neutron_secgroup_rules(neutron_client, project_id): :type neutron_client: neutronclient.Client object :param project_id: Project ID :type project_id: string - :returns: Nothing: This fucntion is executed for its sideffect - :rtype: None """ secgroup = None @@ -1447,8 +1433,10 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2', def create_image(glance, image_url, image_name, image_cache_dir='tests'): - """Download the latest cirros image and upload it to glance, - validate and return a resource pointer. + """Download the image and upload it to glance. + + Download an image from image_url and upload it to glance labelling + the image with image_url, validate and return a resource pointer. :param glance: Authenticated glanceclient :type glance: glanceclient.Client