diff --git a/coriolisclient/cli/migrations.py b/coriolisclient/cli/deployments.py similarity index 53% rename from coriolisclient/cli/migrations.py rename to coriolisclient/cli/deployments.py index 7c10b56..74c6139 100644 --- a/coriolisclient/cli/migrations.py +++ b/coriolisclient/cli/deployments.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Cloudbase Solutions Srl +# Copyright (c) 2024 Cloudbase Solutions Srl # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. """ -Command-line interface sub-commands related to migrations. +Command-line interface sub-commands related to deployments. """ import os @@ -27,9 +27,10 @@ from coriolisclient.cli import utils as cli_utils -class MigrationFormatter(formatter.EntityFormatter): +class DeploymentFormatter(formatter.EntityFormatter): columns = ("ID", + "Transfer ID", "Status", "Instances", "Notes", @@ -41,6 +42,7 @@ def _get_sorted_list(self, obj_list): def _get_formatted_data(self, obj): data = (obj.id, + obj.transfer_id, obj.last_execution_status, "\n".join(obj.instances), obj.notes, @@ -49,7 +51,7 @@ def _get_formatted_data(self, obj): return data -class MigrationDetailFormatter(formatter.EntityFormatter): +class DeploymentDetailFormatter(formatter.EntityFormatter): def __init__(self, show_instances_data=False): self.columns = [ @@ -57,6 +59,8 @@ def __init__(self, show_instances_data=False): "status", "created", "last_updated", + "transfer_id", + "transfer_scenario_type", "reservation_id", "instances", "notes", @@ -65,7 +69,6 @@ def __init__(self, show_instances_data=False): "destination_endpoint_id", "destination_minion_pool_id", "instance_osmorphing_minion_pool_mappings", - "replication_count", "shutdown_instances", "destination_environment", "source_environment", @@ -122,6 +125,8 @@ def _get_formatted_data(self, obj): obj.last_execution_status, obj.created_at, obj.updated_at, + obj.transfer_id, + obj.transfer_scenario_type, obj.reservation_id, self._format_instances(obj), obj.notes, @@ -131,7 +136,6 @@ def _get_formatted_data(self, obj): obj.destination_minion_pool_id, cli_utils.format_json_for_object_property( obj, 'instance_osmorphing_minion_pool_mappings'), - getattr(obj, 'replication_count', None), getattr(obj, 'shutdown_instances', False), cli_utils.format_json_for_object_property( obj, prop_name="destination_environment"), @@ -153,118 +157,18 @@ def _get_formatted_data(self, obj): return data -class CreateMigration(show.ShowOne): - """Start a new migration""" +class CreateDeployment(show.ShowOne): + """Start a new deployment from an existing transfer""" def get_parser(self, prog_name): - parser = super(CreateMigration, self).get_parser(prog_name) - parser.add_argument('--origin-endpoint', required=True, - help='The origin endpoint id') - parser.add_argument('--destination-endpoint', required=True, - help='The destination endpoint id') - parser.add_argument('--instance', action='append', required=True, - dest="instances", metavar="INSTANCE_IDENTIFIER", - help='The identifier of a source instance to be ' - 'migrated. Can be specified multiple times') - parser.add_argument('--notes', dest='notes', - help='Notes about the migration') - parser.add_argument('--user-script-global', action='append', - required=False, - dest="global_scripts", - help='A script that will run for a particular ' - 'os_type. This option can be used multiple ' - 'times. Use: linux=/path/to/script.sh or ' - 'windows=/path/to/script.ps1') - parser.add_argument('--user-script-instance', action='append', - required=False, - dest="instance_scripts", - help='A script that will run for a particular ' - 'instance specified by the --instance option. ' - 'This option can be used multiple times. ' - 'Use: "instance_name"=/path/to/script.sh.' - ' This option overwrites any OS specific script ' - 'specified in --user-script-global for this ' - 'instance') - parser.add_argument('--skip-os-morphing', - help='Skip the OS morphing process', - action='store_true', - default=False) - parser.add_argument('--replication-count', - type=int, - help='Number of times to perform a replica sync ' - 'before deploying the migrated instance.') - parser.add_argument('--shutdown-instances', - action='store_true', - help='Whether or not to shut down the instance on ' - 'the source platform before performing the ' - 'final Replica sync') - - cli_utils.add_args_for_json_option_to_parser( - parser, 'destination-environment') - cli_utils.add_args_for_json_option_to_parser(parser, 'network-map') - cli_utils.add_args_for_json_option_to_parser( - parser, 'source-environment') - cli_utils.add_storage_mappings_arguments_to_parser(parser) - cli_utils.add_minion_pool_args_to_parser( - parser, include_origin_pool_arg=True, - include_destination_pool_arg=True, - include_osmorphing_pool_mappings_arg=True) - - return parser - - def take_action(self, args): - destination_environment = cli_utils.get_option_value_from_args( - args, 'destination-environment') - source_environment = cli_utils.get_option_value_from_args( - args, 'source-environment') - network_map = cli_utils.get_option_value_from_args( - args, 'network-map') - storage_mappings = cli_utils.get_storage_mappings_dict_from_args(args) - endpoints = self.app.client_manager.coriolis.endpoints - origin_endpoint_id = endpoints.get_endpoint_id_for_name( - args.origin_endpoint) - destination_endpoint_id = endpoints.get_endpoint_id_for_name( - args.destination_endpoint) - user_scripts = cli_utils.compose_user_scripts( - args.global_scripts, args.instance_scripts) - instance_osmorphing_minion_pool_mappings = None - if args.instance_osmorphing_minion_pool_mappings: - instance_osmorphing_minion_pool_mappings = { - mp['instance_id']: mp['pool_id'] - for mp in args.instance_osmorphing_minion_pool_mappings} - - migration = self.app.client_manager.coriolis.migrations.create( - origin_endpoint_id, - destination_endpoint_id, - source_environment, - destination_environment, - args.instances, - network_map=network_map, - notes=args.notes, - storage_mappings=storage_mappings, - skip_os_morphing=args.skip_os_morphing, - replication_count=args.replication_count, - shutdown_instances=args.shutdown_instances, - origin_minion_pool_id=args.origin_minion_pool_id, - destination_minion_pool_id=args.destination_minion_pool_id, - instance_osmorphing_minion_pool_mappings=( - instance_osmorphing_minion_pool_mappings), - user_scripts=user_scripts) - - return MigrationDetailFormatter().get_formatted_entity(migration) - - -class CreateMigrationFromReplica(show.ShowOne): - """Start a new migration from an existing replica""" - def get_parser(self, prog_name): - parser = super(CreateMigrationFromReplica, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica to migrate') + parser = super(CreateDeployment, self).get_parser(prog_name) + parser.add_argument('transfer', + help='The ID of the transfer to migrate') parser.add_argument('--force', - help='Force the migration in case of a replica ' + help='Force the deployment in case of a transfer ' 'with failed executions', action='store_true', default=False) parser.add_argument('--dont-clone-disks', - help='Retain the replica disks by cloning them', + help='Retain the transfer disks by cloning them', action='store_false', dest="clone_disks", default=True) parser.add_argument('--skip-os-morphing', @@ -296,7 +200,7 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - m = self.app.client_manager.coriolis.migrations + d = self.app.client_manager.coriolis.deployments user_scripts = cli_utils.compose_user_scripts( args.global_scripts, args.instance_scripts) instance_osmorphing_minion_pool_mappings = None @@ -305,8 +209,8 @@ def take_action(self, args): mp['instance_id']: mp['pool_id'] for mp in args.instance_osmorphing_minion_pool_mappings} - migration = m.create_from_replica( - args.replica, + deployment = d.create_from_transfer( + args.transfer, args.clone_disks, args.force, args.skip_os_morphing, @@ -314,15 +218,15 @@ def take_action(self, args): instance_osmorphing_minion_pool_mappings=( instance_osmorphing_minion_pool_mappings)) - return MigrationDetailFormatter().get_formatted_entity(migration) + return DeploymentDetailFormatter().get_formatted_entity(deployment) -class ShowMigration(show.ShowOne): - """Show a migration""" +class ShowDeployment(show.ShowOne): + """Show a deployment""" def get_parser(self, prog_name): - parser = super(ShowMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') + parser = super(ShowDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') parser.add_argument('--show-instances-data', action='store_true', help='Includes the instances data used for tasks ' 'execution, this is useful for troubleshooting', @@ -330,17 +234,17 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - migration = self.app.client_manager.coriolis.migrations.get(args.id) - return MigrationDetailFormatter( - args.show_instances_data).get_formatted_entity(migration) + deployment = self.app.client_manager.coriolis.deployments.get(args.id) + return DeploymentDetailFormatter( + args.show_instances_data).get_formatted_entity(deployment) -class CancelMigration(command.Command): - """Cancel a migration""" +class CancelDeployment(command.Command): + """Cancel a deployment""" def get_parser(self, prog_name): - parser = super(CancelMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') + parser = super(CancelDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') parser.add_argument('--force', help='Perform a forced termination of running ' 'tasks', action='store_true', @@ -348,28 +252,29 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - self.app.client_manager.coriolis.migrations.cancel(args.id, args.force) + self.app.client_manager.coriolis.deployments.cancel( + args.id, args.force) -class DeleteMigration(command.Command): - """Delete a migration""" +class DeleteDeployment(command.Command): + """Delete a deployment""" def get_parser(self, prog_name): - parser = super(DeleteMigration, self).get_parser(prog_name) - parser.add_argument('id', help='The migration\'s id') + parser = super(DeleteDeployment, self).get_parser(prog_name) + parser.add_argument('id', help='The deployment\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.migrations.delete(args.id) + self.app.client_manager.coriolis.deployments.delete(args.id) -class ListMigration(lister.Lister): - """List migrations""" +class ListDeployment(lister.Lister): + """List deployments""" def get_parser(self, prog_name): - parser = super(ListMigration, self).get_parser(prog_name) + parser = super(ListDeployment, self).get_parser(prog_name) return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.migrations.list() - return MigrationFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.deployments.list() + return DeploymentFormatter().list_objects(obj_list) diff --git a/coriolisclient/cli/endpoint_instances.py b/coriolisclient/cli/endpoint_instances.py index 749a94b..5d2ac5b 100644 --- a/coriolisclient/cli/endpoint_instances.py +++ b/coriolisclient/cli/endpoint_instances.py @@ -103,6 +103,11 @@ def get_parser(self, prog_name): parser.add_argument( '--name', help='Filter results based on regular expression search') + parser.add_argument( + '--refresh', + action='store_true', + default=False, + help='Force refresh of cached instance data') cli_utils.add_args_for_json_option_to_parser(parser, 'environment') @@ -116,7 +121,8 @@ def take_action(self, args): args, 'environment', error_on_no_value=False) obj_list = ei.list( - endpoint_id, env, args.marker, args.limit, args.name) + endpoint_id, env, args.marker, args.limit, args.name, + refresh=args.refresh) return EndpointInstanceFormatter().list_objects(obj_list) diff --git a/coriolisclient/cli/shell.py b/coriolisclient/cli/shell.py index 2b1f37c..c30c1ef 100644 --- a/coriolisclient/cli/shell.py +++ b/coriolisclient/cli/shell.py @@ -35,6 +35,7 @@ import six from coriolisclient import client +from coriolisclient import exceptions from coriolisclient import version @@ -100,7 +101,7 @@ def check_auth_arguments(self, args, api_version=None, raise_exc=False): successful = False if not successful and raise_exc: - raise Exception(msg) + raise exceptions.CoriolisException(msg) return successful @@ -157,7 +158,7 @@ def create_client(self, args): api_version = args.os_identity_api_version verify = args.os_cacert or not args.insecure if args.no_auth and args.os_auth_url: - raise Exception( + raise exceptions.CoriolisException( 'ERROR: argument --os-auth-url/-A: not allowed ' 'with argument --no-auth/-N' ) @@ -165,7 +166,7 @@ def create_client(self, args): if args.no_auth: if not all([args.endpoint, args.os_tenant_id or args.os_project_id]): - raise Exception( + raise exceptions.CoriolisException( 'ERROR: please specify --endpoint and ' '--os-project-id (or --os-tenant-id)') created_client = client.Client( @@ -177,7 +178,8 @@ def create_client(self, args): # Token-based authentication elif args.os_auth_token: if not args.os_auth_url: - raise Exception('ERROR: please specify --os-auth-url') + raise exceptions.CoriolisException( + 'ERROR: please specify --os-auth-url') token_kwargs = { 'auth_url': args.os_auth_url, 'token': args.os_auth_token @@ -210,7 +212,8 @@ def create_client(self, args): **endpoint_filter_kwargs ) else: - raise Exception('ERROR: please specify authentication credentials') + raise exceptions.CoriolisException( + 'ERROR: please specify authentication credentials') return created_client diff --git a/coriolisclient/cli/replica_executions.py b/coriolisclient/cli/transfer_executions.py similarity index 57% rename from coriolisclient/cli/replica_executions.py rename to coriolisclient/cli/transfer_executions.py index f102284..0b1e199 100644 --- a/coriolisclient/cli/replica_executions.py +++ b/coriolisclient/cli/transfer_executions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import os @@ -24,9 +24,9 @@ from coriolisclient.cli import formatter -class ReplicaExecutionFormatter(formatter.EntityFormatter): +class TransferExecutionFormatter(formatter.EntityFormatter): - columns = ("Replica ID", + columns = ("Transfer ID", "ID", "Status", "Created", @@ -44,10 +44,10 @@ def _get_formatted_data(self, obj): return data -class ReplicaExecutionDetailFormatter(formatter.EntityFormatter): +class TransferExecutionDetailFormatter(formatter.EntityFormatter): columns = ("id", - "replica_id", + "transfer_id", "status", "created", "last_updated", @@ -100,49 +100,55 @@ def _get_formatted_data(self, obj): return data -class CreateReplicaExecution(show.ShowOne): - """Start a replica execution""" +class CreateTransferExecution(show.ShowOne): + """Start a transfer execution""" def get_parser(self, prog_name): - parser = super(CreateReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica to execute') + parser = super(CreateTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', + help='The ID of the transfer to execute') parser.add_argument('--shutdown-instances', help='Shutdown instances before executing the ' - 'replica', action='store_true', + 'transfer', action='store_true', + default=False) + parser.add_argument('--auto-deploy', + help="Automatically execute deployment after the " + "transfer execution finishes", + action='store_true', default=False) return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replica_executions.create( - args.replica, args.shutdown_instances) + execution = ( + self.app.client_manager.coriolis.transfer_executions.create( + args.transfer, args.shutdown_instances, args.auto_deploy)) - return ReplicaExecutionDetailFormatter().get_formatted_entity( + return TransferExecutionDetailFormatter().get_formatted_entity( execution) -class ShowReplicaExecution(show.ShowOne): - """Show a replica execution""" +class ShowTransferExecution(show.ShowOne): + """Show a transfer execution""" def get_parser(self, prog_name): - parser = super(ShowReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(ShowTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replica_executions.get( - args.replica, args.id) - return ReplicaExecutionDetailFormatter().get_formatted_entity( + execution = self.app.client_manager.coriolis.transfer_executions.get( + args.transfer, args.id) + return TransferExecutionDetailFormatter().get_formatted_entity( execution) -class CancelReplicaExecution(command.Command): - """Cancel a replica execution""" +class CancelTransferExecution(command.Command): + """Cancel a transfer execution""" def get_parser(self, prog_name): - parser = super(CancelReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(CancelTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') parser.add_argument('--force', help='Perform a forced termination of running ' 'tasks', action='store_true', @@ -150,33 +156,33 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_executions.cancel( - args.replica, args.id, args.force) + self.app.client_manager.coriolis.transfer_executions.cancel( + args.transfer, args.id, args.force) -class DeleteReplicaExecution(command.Command): - """Delete a replica execution""" +class DeleteTransferExecution(command.Command): + """Delete a transfer execution""" def get_parser(self, prog_name): - parser = super(DeleteReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica execution\'s id') + parser = super(DeleteTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer execution\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_executions.delete( - args.replica, args.id) + self.app.client_manager.coriolis.transfer_executions.delete( + args.transfer, args.id) -class ListReplicaExecution(lister.Lister): - """List replica executions""" +class ListTransferExecution(lister.Lister): + """List transfer executions""" def get_parser(self, prog_name): - parser = super(ListReplicaExecution, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') + parser = super(ListTransferExecution, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replica_executions.list( - args.replica) - return ReplicaExecutionFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfer_executions.list( + args.transfer) + return TransferExecutionFormatter().list_objects(obj_list) diff --git a/coriolisclient/cli/replica_schedules.py b/coriolisclient/cli/transfer_schedules.py similarity index 68% rename from coriolisclient/cli/replica_schedules.py rename to coriolisclient/cli/transfer_schedules.py index 7287071..c4d758a 100644 --- a/coriolisclient/cli/replica_schedules.py +++ b/coriolisclient/cli/transfer_schedules.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import argparse @@ -42,9 +42,9 @@ def __call__(self, parser, namespace, value, option_string=None): setattr(namespace, self.dest, value) -class ReplicaScheduleFormatter(formatter.EntityFormatter): +class TransferScheduleFormatter(formatter.EntityFormatter): - columns = ("Replica ID", + columns = ("Transfer ID", "ID", "Schedule", "Created", @@ -55,7 +55,7 @@ def _get_sorted_list(self, obj_list): return sorted(obj_list, key=lambda o: o.created_at) def _get_formatted_data(self, obj): - data = (obj.replica_id, + data = (obj.transfer_id, obj.id, obj.schedule, obj.created_at, @@ -63,35 +63,37 @@ def _get_formatted_data(self, obj): return data -class ReplicaScheduleDetailFormatter(formatter.EntityFormatter): +class TransferScheduleDetailFormatter(formatter.EntityFormatter): columns = ("id", - "replica_id", + "transfer_id", "schedule", "created", "last_updated", "enabled", "expires", - "shutdown_instance") + "shutdown_instance", + "auto_deploy") def _get_formatted_data(self, obj): data = (obj.id, - obj.replica_id, + obj.transfer_id, obj.schedule, obj.created_at, obj.updated_at, obj.enabled, obj.expiration_date, - obj.shutdown_instance) + obj.shutdown_instance, + obj.auto_deploy) return data -class CreateReplicaSchedule(show.ShowOne): - """Start a replica schedule""" +class CreateTransferSchedule(show.ShowOne): + """Start a transfer schedule""" def get_parser(self, prog_name): - parser = super(CreateReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', - help='The ID of the replica') + parser = super(CreateTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', + help='The ID of the transfer') _add_schedule_group(parser) parser.add_argument('--expires-at', help='ISO8601 formatted date', @@ -104,6 +106,11 @@ def get_parser(self, prog_name): help='Shutdown instance', action='store_true', default=False) + parser.add_argument('--auto-deploy', + help="Auto Deploy transfer after scheduled " + "execution completes.", + action="store_true", + default=False) return parser def take_action(self, args): @@ -113,35 +120,36 @@ def take_action(self, args): "Please provide at least one value in the Schedule group") exp = _parse_expiration_date(args.expires_at) - schedule = self.app.client_manager.coriolis.replica_schedules.create( - args.replica, parsed_schedule, - args.disabled is False, exp, args.shutdown_instance) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + schedule = self.app.client_manager.coriolis.transfer_schedules.create( + args.transfer, parsed_schedule, + args.disabled is False, exp, args.shutdown_instance, + args.auto_deploy) + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class ShowReplicaSchedule(show.ShowOne): - """Show a replica schedule""" +class ShowTransferSchedule(show.ShowOne): + """Show a transfer schedule""" def get_parser(self, prog_name): - parser = super(ShowReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(ShowTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') return parser def take_action(self, args): - schedule = self.app.client_manager.coriolis.replica_schedules.get( - args.replica, args.id) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + schedule = self.app.client_manager.coriolis.transfer_schedules.get( + args.transfer, args.id) + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class UpdateReplicaSchedule(show.ShowOne): - """Updates a replica schedule""" +class UpdateTransferSchedule(show.ShowOne): + """Updates a transfer schedule""" def get_parser(self, prog_name): - parser = super(UpdateReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(UpdateTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') _add_schedule_group(parser) expires_parser = parser.add_mutually_exclusive_group(required=False) expires_parser.add_argument( @@ -179,6 +187,18 @@ def get_parser(self, prog_name): help="Don't shutdown instance", dest="shutdown", action='store_false') + auto_deploy_parser = parser.add_mutually_exclusive_group( + required=False) + auto_deploy_parser.add_argument( + "--auto-deploy", + help="Auto Deploy transfer after scheduled execution completes.", + dest="auto_deploy", + action="store_true") + auto_deploy_parser.add_argument( + "--dont-auto-deploy", + help="Stops auto deployment when starting scheduled execution", + dest="auto_deploy", + action="store_false") return parser def take_action(self, args): @@ -198,34 +218,36 @@ def take_action(self, args): updated_values["shutdown_instance"] = args.shutdown if args.enabled is not None: updated_values["enabled"] = args.enabled + if args.auto_deploy is not None: + updated_values['auto_deploy'] = args.auto_deploy - schedule = self.app.client_manager.coriolis.replica_schedules.update( - args.replica, args.id, updated_values) + schedule = self.app.client_manager.coriolis.transfer_schedules.update( + args.transfer, args.id, updated_values) - return ReplicaScheduleDetailFormatter().get_formatted_entity( + return TransferScheduleDetailFormatter().get_formatted_entity( schedule) -class DeleteReplicaSchedule(command.Command): - """Delete a replica schedule""" +class DeleteTransferSchedule(command.Command): + """Delete a transfer schedule""" def get_parser(self, prog_name): - parser = super(DeleteReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') - parser.add_argument('id', help='The replica schedule\'s id') + parser = super(DeleteTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') + parser.add_argument('id', help='The transfer schedule\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replica_schedules.delete( - args.replica, args.id) + self.app.client_manager.coriolis.transfer_schedules.delete( + args.transfer, args.id) -class ListReplicaSchedule(lister.Lister): - """List replica schedules""" +class ListTransferSchedule(lister.Lister): + """List transfer schedules""" def get_parser(self, prog_name): - parser = super(ListReplicaSchedule, self).get_parser(prog_name) - parser.add_argument('replica', help='The replica\'s id') + parser = super(ListTransferSchedule, self).get_parser(prog_name) + parser.add_argument('transfer', help='The transfer\'s id') parser.add_argument('--hide-expired', help='Hide expired schedules', action='store_true', @@ -233,9 +255,9 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replica_schedules.list( - args.replica, hide_expired=args.hide_expired) - return ReplicaScheduleFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfer_schedules.list( + args.transfer, hide_expired=args.hide_expired) + return TransferScheduleFormatter().list_objects(obj_list) def _add_schedule_group(parser): diff --git a/coriolisclient/cli/replicas.py b/coriolisclient/cli/transfers.py similarity index 70% rename from coriolisclient/cli/replicas.py rename to coriolisclient/cli/transfers.py index b4ab7be..37e1bbb 100644 --- a/coriolisclient/cli/replicas.py +++ b/coriolisclient/cli/transfers.py @@ -14,7 +14,7 @@ # limitations under the License. """ -Command-line interface sub-commands related to replicas. +Command-line interface sub-commands related to transfers. """ import os @@ -24,13 +24,43 @@ from cliff import show from coriolisclient.cli import formatter -from coriolisclient.cli import replica_executions +from coriolisclient.cli import transfer_executions from coriolisclient.cli import utils as cli_utils - -class ReplicaFormatter(formatter.EntityFormatter): +TRANSFER_SCENARIO_REPLICA = "replica" +TRANSFER_SCENARIO_LIVE_MIGRATION = "live_migration" + + +def _add_default_deployment_args_to_parser(parser): + cd_group = parser.add_mutually_exclusive_group() + cd_group.add_argument('--clone-disks', + help='Retain the transfer disks by cloning them ' + 'when launching deployment', + action='store_true', dest="clone_disks", + default=None) + cd_group.add_argument('--dont-clone-disks', + help="Deploy directly on transfer disks, without " + "cloning them.", + action="store_false", dest="clone_disks", + default=None) + + osm_group = parser.add_mutually_exclusive_group() + osm_group.add_argument('--os-morphing', + help="Include the OSMorphing process on the " + "deployments of this transfer.", + action="store_false", dest="skip_os_morphing", + default=None) + osm_group.add_argument('--skip-os-morphing', + help='Skip the OS morphing process on the ' + 'deployments of this transfer', + action='store_true', default=None, + dest="skip_os_morphing") + + +class TransferFormatter(formatter.EntityFormatter): columns = ("ID", + "Scenario", "Instances", "Notes", "Last Execution Status", @@ -48,6 +78,7 @@ def _format_last_execution(self, obj): def _get_formatted_data(self, obj): data = (obj.id, + getattr(obj, "scenario", "replica"), "\n".join(obj.instances), obj.notes, obj.last_execution_status, @@ -56,13 +87,14 @@ def _get_formatted_data(self, obj): return data -class ReplicaDetailFormatter(formatter.EntityFormatter): +class TransferDetailFormatter(formatter.EntityFormatter): def __init__(self, show_instances_data=False): self.columns = [ "id", "created", "last_updated", + "scenario_type", "reservation_id", "instances", "notes", @@ -78,6 +110,8 @@ def __init__(self, show_instances_data=False): "storage_backend_mappings", "default_storage_backend", "user_scripts", + "clone_disks", + "skip_os_morphing", "executions", ] @@ -102,6 +136,7 @@ def _get_formatted_data(self, obj): data = [obj.id, obj.created_at, obj.updated_at, + getattr(obj, "scenario", ""), obj.reservation_id, self._format_instances(obj), obj.notes, @@ -121,6 +156,8 @@ def _get_formatted_data(self, obj): cli_utils.format_mapping(backend_mappings), default_storage, cli_utils.format_json_for_object_property(obj, 'user_scripts'), + obj.clone_disks, + obj.skip_os_morphing, self._format_executions(obj.executions)] if "instances-data" in self.columns: @@ -129,10 +166,10 @@ def _get_formatted_data(self, obj): return data -class CreateReplica(show.ShowOne): - """Create a new replica""" +class CreateTransfer(show.ShowOne): + """Create a new transfer""" def get_parser(self, prog_name): - parser = super(CreateReplica, self).get_parser(prog_name) + parser = super(CreateTransfer, self).get_parser(prog_name) parser.add_argument('--origin-endpoint', required=True, help='The origin endpoint id') parser.add_argument('--destination-endpoint', required=True, @@ -140,9 +177,24 @@ def get_parser(self, prog_name): parser.add_argument('--instance', action='append', required=True, dest="instances", metavar="INSTANCE_IDENTIFIER", help='The identifier of a source instance to be ' - 'replicated. Can be specified multiple times') + 'transferred. Can be specified multiple ' + 'times') + parser.add_argument('--scenario', + dest="scenario", metavar="SCENARIO", + choices=[ + TRANSFER_SCENARIO_REPLICA, + TRANSFER_SCENARIO_LIVE_MIGRATION], + default=TRANSFER_SCENARIO_REPLICA, + help='The type of scenario to use when creating ' + 'the Transfer. "replica" will create a ' + 'monthly-billed Replica which can be ' + 'executed and deployed as many times as ' + 'desired, while "live_migration" will ' + 'create a Transfer which can be synced ' + 'as many times as needed but only ' + 'deployed once.') parser.add_argument('--notes', dest='notes', - help='Notes about the replica') + help='Notes about the transfer') parser.add_argument('--user-script-global', action='append', required=False, dest="global_scripts", @@ -172,6 +224,7 @@ def get_parser(self, prog_name): include_osmorphing_pool_mappings_arg=True) cli_utils.add_storage_mappings_arguments_to_parser(parser) + _add_default_deployment_args_to_parser(parser) return parser @@ -196,12 +249,13 @@ def take_action(self, args): user_scripts = cli_utils.compose_user_scripts( args.global_scripts, args.instance_scripts) - replica = self.app.client_manager.coriolis.replicas.create( + transfer = self.app.client_manager.coriolis.transfers.create( origin_endpoint_id, destination_endpoint_id, source_environment, destination_environment, args.instances, + args.scenario, network_map=network_map, notes=args.notes, storage_mappings=storage_mappings, @@ -209,17 +263,19 @@ def take_action(self, args): destination_minion_pool_id=args.destination_minion_pool_id, instance_osmorphing_minion_pool_mappings=( instance_osmorphing_minion_pool_mappings), - user_scripts=user_scripts) + user_scripts=user_scripts, + clone_disks=args.clone_disks, + skip_os_morphing=args.skip_os_morphing) - return ReplicaDetailFormatter().get_formatted_entity(replica) + return TransferDetailFormatter().get_formatted_entity(transfer) -class ShowReplica(show.ShowOne): - """Show a replica""" +class ShowTransfer(show.ShowOne): + """Show a transfer""" def get_parser(self, prog_name): - parser = super(ShowReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(ShowTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') parser.add_argument('--show-instances-data', action='store_true', help='Includes the instances data used for tasks ' 'execution, this is useful for troubleshooting', @@ -227,57 +283,57 @@ def get_parser(self, prog_name): return parser def take_action(self, args): - replica = self.app.client_manager.coriolis.replicas.get(args.id) - return ReplicaDetailFormatter( - args.show_instances_data).get_formatted_entity(replica) + transfer = self.app.client_manager.coriolis.transfers.get(args.id) + return TransferDetailFormatter( + args.show_instances_data).get_formatted_entity(transfer) -class DeleteReplica(command.Command): - """Delete a replica""" +class DeleteTransfer(command.Command): + """Delete a transfer""" def get_parser(self, prog_name): - parser = super(DeleteReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(DeleteTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') return parser def take_action(self, args): - self.app.client_manager.coriolis.replicas.delete(args.id) + self.app.client_manager.coriolis.transfers.delete(args.id) -class DeleteReplicaDisks(show.ShowOne): - """Delete replica target disks""" +class DeleteTransferDisks(show.ShowOne): + """Delete transfer target disks""" def get_parser(self, prog_name): - parser = super(DeleteReplicaDisks, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(DeleteTransferDisks, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') return parser def take_action(self, args): - execution = self.app.client_manager.coriolis.replicas.delete_disks( + execution = self.app.client_manager.coriolis.transfers.delete_disks( args.id) - return replica_executions.ReplicaExecutionDetailFormatter( + return transfer_executions.TransferExecutionDetailFormatter( ).get_formatted_entity(execution) -class ListReplica(lister.Lister): - """List replicas""" +class ListTransfer(lister.Lister): + """List transfers""" def get_parser(self, prog_name): - parser = super(ListReplica, self).get_parser(prog_name) + parser = super(ListTransfer, self).get_parser(prog_name) return parser def take_action(self, args): - obj_list = self.app.client_manager.coriolis.replicas.list() - return ReplicaFormatter().list_objects(obj_list) + obj_list = self.app.client_manager.coriolis.transfers.list() + return TransferFormatter().list_objects(obj_list) -class UpdateReplica(show.ShowOne): - """Create a new replica""" +class UpdateTransfer(show.ShowOne): + """Create a new transfer""" def get_parser(self, prog_name): - parser = super(UpdateReplica, self).get_parser(prog_name) - parser.add_argument('id', help='The replica\'s id') + parser = super(UpdateTransfer, self).get_parser(prog_name) + parser.add_argument('id', help='The transfer\'s id') parser.add_argument('--notes', dest='notes', - help='Notes about the replica.') + help='Notes about the transfer.') parser.add_argument('--user-script-global', action='append', required=False, dest="global_scripts", @@ -306,6 +362,7 @@ def get_parser(self, prog_name): parser, include_origin_pool_arg=True, include_destination_pool_arg=True, include_osmorphing_pool_mappings_arg=True) + _add_default_deployment_args_to_parser(parser) return parser @@ -348,14 +405,18 @@ def take_action(self, args): instance_osmorphing_minion_pool_mappings) if user_scripts: updated_properties['user_scripts'] = user_scripts + if args.clone_disks is not None: + updated_properties['clone_disks'] = args.clone_disks + if args.skip_os_morphing is not None: + updated_properties['skip_os_morphing'] = args.skip_os_morphing if not updated_properties: raise ValueError( "No options provided for update. Please run `coriolis help " - "replica update` for details on accepted parameters.") + "transfer update` for details on accepted parameters.") - execution = self.app.client_manager.coriolis.replicas.update( + execution = self.app.client_manager.coriolis.transfers.update( args.id, updated_properties) - return replica_executions.ReplicaExecutionDetailFormatter( + return transfer_executions.TransferExecutionDetailFormatter( ).get_formatted_entity(execution) diff --git a/coriolisclient/cli/utils.py b/coriolisclient/cli/utils.py index b61ee28..730363f 100644 --- a/coriolisclient/cli/utils.py +++ b/coriolisclient/cli/utils.py @@ -175,7 +175,7 @@ def get_option_value_from_args(args, option_name, error_on_no_value=True): with file_arg as fin: raw_value = fin.read() - if not value and raw_value: + if raw_value: try: value = json.loads(raw_value) except ValueError as ex: diff --git a/coriolisclient/client.py b/coriolisclient/client.py index d032d3d..60ef840 100644 --- a/coriolisclient/client.py +++ b/coriolisclient/client.py @@ -17,6 +17,7 @@ from keystoneauth1 import adapter +from coriolisclient.v1 import deployments from coriolisclient.v1 import diagnostics from coriolisclient.v1 import endpoint_destination_minion_pool_options from coriolisclient.v1 import endpoint_destination_options @@ -31,14 +32,13 @@ from coriolisclient.v1 import licensing_reservations from coriolisclient.v1 import licensing_server from coriolisclient.v1 import logging as coriolis_logging -from coriolisclient.v1 import migrations from coriolisclient.v1 import minion_pools from coriolisclient.v1 import providers from coriolisclient.v1 import regions -from coriolisclient.v1 import replica_executions -from coriolisclient.v1 import replica_schedules -from coriolisclient.v1 import replicas from coriolisclient.v1 import services +from coriolisclient.v1 import transfer_executions +from coriolisclient.v1 import transfer_schedules +from coriolisclient.v1 import transfers LOG = logging.getLogger(__name__) @@ -84,14 +84,14 @@ def __init__(self, session=None, *args, **kwargs): endpoint_source_options.EndpointSourceOptionsManager(httpclient)) self.endpoint_storage = endpoint_storage.EndpointStorageManager( httpclient) - self.migrations = migrations.MigrationManager(httpclient) + self.deployments = deployments.DeploymentManager(httpclient) self.minion_pools = minion_pools.MinionPoolManager(httpclient) self.providers = providers.ProvidersManager(httpclient) - self.replicas = replicas.ReplicaManager(httpclient) - self.replica_schedules = replica_schedules.ReplicaScheduleManager( - httpclient) - self.replica_executions = replica_executions.ReplicaExecutionManager( + self.transfers = transfers.TransferManager(httpclient) + self.transfer_schedules = transfer_schedules.TransferScheduleManager( httpclient) + self.transfer_executions = ( + transfer_executions.TransferExecutionManager(httpclient)) self.regions = regions.RegionManager(httpclient) self.services = services.ServiceManager(httpclient) self.logging = coriolis_logging.CoriolisLogDownloadManager(httpclient) diff --git a/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml new file mode 100644 index 0000000..79118cf --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml @@ -0,0 +1,129 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_id: "mock_os_project_id" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '3' + raise_exc: False + expected_result: True + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: True + expected_result: + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + api_version: '2' + raise_exc: False + expected_result: False + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '2' + raise_exc: False + expected_result: False diff --git a/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml new file mode 100644 index 0000000..b5503cf --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml @@ -0,0 +1,137 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + insecure: True + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + project_name: "mock_os_project_name" + project_domain_name: "mock_os_project_domain_name" + project_domain_id: "mock_os_project_domain_id" + verify: False + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + password: "mock_password" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + password: "mock_password" + api_version: '3' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v3.Password' + + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + password: "mock_password" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + password: "mock_password" + api_version: '3' + auth_type: + auth_fun: 'keystoneauth1.identity.v3.Password' + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v2.Password' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: + auth_fun: 'keystoneauth1.identity.v2.Password' diff --git a/coriolisclient/tests/cli/data/user_scripts.yml b/coriolisclient/tests/cli/data/user_scripts.yml new file mode 100644 index 0000000..7510465 --- /dev/null +++ b/coriolisclient/tests/cli/data/user_scripts.yml @@ -0,0 +1,2 @@ +"mock_script1" +"mock_script2" diff --git a/coriolisclient/tests/cli/test_deployments.py b/coriolisclient/tests/cli/test_deployments.py new file mode 100644 index 0000000..f00065f --- /dev/null +++ b/coriolisclient/tests/cli/test_deployments.py @@ -0,0 +1,340 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# 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. + +import argparse +import copy +import os +from unittest import mock + +from coriolisclient.cli import deployments +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import deployments as v1_deployments + +DEPLOYMENT_ID = "depl_id" +DEPLOYMENT_DATA = { + "storage_mappings": None, + "id": DEPLOYMENT_ID, + "last_execution_status": "COMPLETED", + "created_at": None, + "updated_at": None, + "transfer_id": "transfer_1", + "transfer_scenario_type": "replica", + "reservation_id": "res1", + "instances": ["instance1"], + "notes": "", + "origin_endpoint_id": "source1", + "origin_minion_pool_id": None, + "destination_endpoint_id": "dest1", + "destination_minion_pool_id": None, + "instance_osmorphing_minion_pool_mappings": None, + "shutdown_instances": False, + "destination_environment": {"opt1": "env1"}, + "source_environment": None, + "network_map": {"net_source": "net_dest"}, + "user_scripts": None, + "tasks": [], + "transfer_result": None, +} +DEPLOYMENT_FORMATTED_DATA = [ + "depl_id", "COMPLETED", None, None, "transfer_1", "replica", "res1", + "instance1", "", "source1", None, "dest1", None, '{}', False, + '{\n "opt1": "env1"\n}', '{}', '{\n "net_source": "net_dest"\n}', "", "", + None, '{}', "", '{}'] + +DEPLOYMENT_LIST_DATA = { + "id": "1", + "transfer_id": "2", + "last_execution_status": "RUNNING", + "instances": ["instance1", "instance2"], + "notes": "test_notes", + "created_at": "2024-11-28T15:18:25.000000", +} +DEPLOYMENT_LIST_FORMATTED_DATA = ( + "1", "2", "RUNNING", "instance1\ninstance2", "test_notes", + "2024-11-28T15:18:25.000000") +APP_MOCK = mock.MagicMock() + + +class DeploymentFormatterTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentFormatterTestCase, self).setUp() + self.formatter = deployments.DeploymentFormatter() + + def test__get_sorted_list(self): + obj1 = mock.Mock(created_at="2024-10-24T18:51:32.000000") + obj2 = mock.Mock(created_at="2024-11-18T15:18:25.000000") + obj3 = mock.Mock(created_at="2024-11-28T15:18:25.000000") + obj_list = [obj1, obj3, obj2] + self.assertEqual( + [obj1, obj2, obj3], self.formatter._get_sorted_list(obj_list)) + + def test__get_formatted_data(self): + obj = mock.Mock(**DEPLOYMENT_LIST_DATA) + self.assertEqual( + DEPLOYMENT_LIST_FORMATTED_DATA, + self.formatter._get_formatted_data(obj)) + + +class DeploymentDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentDetailFormatterTestCase, self).setUp() + self.formatter = deployments.DeploymentDetailFormatter( + show_instances_data=True) + self.progress_updates = [ + {"created_at": "2024-10-24T18:51:32.000000", + "index": 0, + "message": "message 0"}, + {"created_at": "2024-11-18T15:18:25.000000", + "index": 2, + "message": "message 2"}, + {"created_at": "2024-11-28T15:18:25.000000", + "index": 1, + "message": "message 1"}, + ] + self.formatted_progress_updates = ( + f"2024-10-24T18:51:32.000000 message 0{os.linesep}" + f"2024-11-28T15:18:25.000000 message 1{os.linesep}" + f"2024-11-18T15:18:25.000000 message 2") + self.tasks_data = [ + { + "id": "1", + "task_type": "type1", + "instance": "instance1", + "status": "COMPLETED", + "depends_on": ["task0"], + "exception_details": None, + "progress_updates": self.progress_updates, + }, + { + "id": "2", + "task_type": "type2", + "instance": "instance1", + "status": "COMPLETED", + "depends_on": ["task0"], + "exception_details": None, + "progress_updates": self.progress_updates, + } + ] + self.formatted_tasks = [ + f'id: 1{os.linesep}' + f'task_type: type1{os.linesep}' + f'instance: instance1{os.linesep}' + f'status: COMPLETED{os.linesep}' + f'depends_on: task0{os.linesep}' + f'exception_details: {os.linesep}' + f'progress_updates:{os.linesep}' + f'{self.formatted_progress_updates}', + + f'id: 2{os.linesep}' + f'task_type: type2{os.linesep}' + f'instance: instance1{os.linesep}' + f'status: COMPLETED{os.linesep}' + f'depends_on: task0{os.linesep}' + f'exception_details: {os.linesep}' + f'progress_updates:{os.linesep}' + f'{self.formatted_progress_updates}' + ] + self.manager_mock = mock.MagicMock() + + def test_init_no_instances_data(self): + formatter = deployments.DeploymentDetailFormatter( + show_instances_data=False) + self.assertNotIn('instances_data', formatter.columns) + + def test__format_instances(self): + obj = mock.Mock(instances=["instance2", "instance3", "instance1"]) + self.assertEqual( + f"instance1{os.linesep}instance2{os.linesep}instance3", + self.formatter._format_instances(obj)) + + def test__format_progress_updates(self): + task_dict = { + "progress_updates": self.progress_updates} + self.assertEqual( + self.formatted_progress_updates, + self.formatter._format_progress_updates(task_dict)) + + def test__format_task(self): + task_data = self.tasks_data[0] + + task = common.Task(self.manager_mock, task_data) + + self.assertEqual( + self.formatted_tasks[0], self.formatter._format_task(task)) + + def test__format_tasks(self): + obj = mock.Mock( + tasks=[common.Task(self.manager_mock, task_data) + for task_data in self.tasks_data]) + expected_result = (f'{self.formatted_tasks[0]}{os.linesep}{os.linesep}' + f'{self.formatted_tasks[1]}') + self.assertEqual(expected_result, self.formatter._format_tasks(obj)) + + def test__get_formatted_data(self): + obj_data = { + **DEPLOYMENT_DATA, + "info": {"depl": "info"}, + } + obj = mock.Mock(**obj_data) + obj.to_dict.return_value = obj_data + expected_result = copy.copy(DEPLOYMENT_FORMATTED_DATA) + expected_result.append(obj_data['info']) + + self.assertEqual( + expected_result, self.formatter._get_formatted_data(obj)) + + +class CreateDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(CreateDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.CreateDeployment(self.mock_app, 'app_arg') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + global_script = "linux=/linux/path" + instance_script = "instance1=/instance1/path" + args = parser.parse_args([ + 'transfer_id', '--force', '--dont-clone-disks', + '--skip-os-morphing', '--user-script-global', global_script, + '--user-script-instance', instance_script]) + self.assertEqual( + ('transfer_id', True, False, True, [global_script], + [instance_script]), + (args.transfer, args.force, args.clone_disks, + args.skip_os_morphing, args.global_scripts, + args.instance_scripts)) + + def test_take_action(self): + args = mock.Mock(global_scripts=None, instance_scripts=None) + args.instance_osmorphing_minion_pool_mappings = [ + {"instance_id": "instance1", "pool_id": "pool1"}] + mock_fun = (self.mock_app.client_manager.coriolis.deployments. + create_from_transfer) + mock_fun.return_value = ( + v1_deployments.Deployment(mock.MagicMock(), DEPLOYMENT_DATA)) + expected_pool_mappings = {"instance1": "pool1"} + expected_user_scripts = {"global": {}, "instances": {}} + expected_data = copy.copy(DEPLOYMENT_FORMATTED_DATA) + + columns, data = self.cli.take_action(args) + + self.assertEqual( + deployments.DeploymentDetailFormatter().columns, columns) + self.assertEqual(expected_data, data) + mock_fun.assert_called_once_with( + args.transfer, args.clone_disks, args.force, args.skip_os_morphing, + user_scripts=expected_user_scripts, + instance_osmorphing_minion_pool_mappings=expected_pool_mappings) + + +class ShowDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ShowDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.ShowDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID, '--show-instances-data']) + self.assertEqual( + (DEPLOYMENT_ID, True), + (args.id, args.show_instances_data)) + + def test_take_action(self): + show_instances_data = False + args = mock.Mock( + id=DEPLOYMENT_ID, show_instances_data=show_instances_data) + mock_fun = self.mock_app.client_manager.coriolis.deployments.get + mock_fun.return_value = v1_deployments.Deployment( + mock.MagicMock(), DEPLOYMENT_DATA) + expected_data = DEPLOYMENT_FORMATTED_DATA + + columns, data = self.cli.take_action(args) + + self.assertEqual( + deployments.DeploymentDetailFormatter( + show_instances_data=show_instances_data).columns, columns) + self.assertEqual(expected_data, data) + mock_fun.assert_called_once_with(DEPLOYMENT_ID) + + +class CancelDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(CancelDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.CancelDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID, '--force']) + self.assertEqual((DEPLOYMENT_ID, True), (args.id, args.force)) + + def test_take_action(self): + force = True + args = mock.Mock(id=DEPLOYMENT_ID, force=force) + mock_fun = self.mock_app.client_manager.coriolis.deployments.cancel + + self.cli.take_action(args) + + mock_fun.assert_called_once_with(DEPLOYMENT_ID, force) + + +class DeleteDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeleteDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.DeleteDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + args = parser.parse_args([DEPLOYMENT_ID]) + self.assertEqual(DEPLOYMENT_ID, args.id) + + def test_take_action(self): + args = mock.Mock(id=DEPLOYMENT_ID) + mock_fun = self.mock_app.client_manager.coriolis.deployments.delete + + self.cli.take_action(args) + mock_fun.assert_called_once_with(DEPLOYMENT_ID) + + +class ListDeploymentTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(ListDeploymentTestCase, self).setUp() + self.mock_app = APP_MOCK + self.cli = deployments.ListDeployment(self.mock_app, 'app_args') + + def test_get_parser(self): + parser = self.cli.get_parser('coriolis') + self.assertIsInstance(parser, argparse.ArgumentParser) + + def test_take_action(self): + mock_fun = self.mock_app.client_manager.coriolis.deployments.list + mock_fun.return_value = [ + v1_deployments.Deployment(mock.MagicMock(), DEPLOYMENT_LIST_DATA)] + + columns, data = self.cli.take_action(mock.ANY) + + self.assertEqual(deployments.DeploymentFormatter().columns, columns) + self.assertEqual([DEPLOYMENT_LIST_FORMATTED_DATA], list(data)) diff --git a/coriolisclient/tests/cli/test_diagnostics.py b/coriolisclient/tests/cli/test_diagnostics.py index 46d26c1..ba79c12 100644 --- a/coriolisclient/tests/cli/test_diagnostics.py +++ b/coriolisclient/tests/cli/test_diagnostics.py @@ -60,7 +60,8 @@ class GetCoriolisDiagnosticsTestCase(test_base.CoriolisBaseTestCase): def setUp(self, mock__init__): mock__init__.return_value = None super(GetCoriolisDiagnosticsTestCase, self).setUp() - self.diag = diagnostics.GetCoriolisDiagnostics() + self.diag = diagnostics.GetCoriolisDiagnostics( + mock.sentinel.app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') def test_get_parser(self, mock_get_parser): diff --git a/coriolisclient/tests/cli/test_endpoint_instances.py b/coriolisclient/tests/cli/test_endpoint_instances.py index 6f30705..4a0ce2c 100644 --- a/coriolisclient/tests/cli/test_endpoint_instances.py +++ b/coriolisclient/tests/cli/test_endpoint_instances.py @@ -134,6 +134,7 @@ def test_take_action( args.marker = mock.sentinel.marker args.limit = mock.sentinel.limit args.name = mock.sentinel.name + args.refresh = mock.sentinel.refresh mock_endpoints = mock.Mock() mock_ei = mock.Mock() self.mock_app.client_manager.coriolis.endpoints = mock_endpoints @@ -152,7 +153,8 @@ def test_take_action( mock_get_option_value_from_args.return_value, mock.sentinel.marker, mock.sentinel.limit, - mock.sentinel.name + mock.sentinel.name, + refresh=mock.sentinel.refresh, ) mock_list_objects.assert_called_once_with(mock_ei.list.return_value) diff --git a/coriolisclient/tests/cli/test_formatter.py b/coriolisclient/tests/cli/test_formatter.py new file mode 100644 index 0000000..6b9830d --- /dev/null +++ b/coriolisclient/tests/cli/test_formatter.py @@ -0,0 +1,158 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.cli import formatter +from coriolisclient.tests import test_base + + +class TestEntityFormattter(formatter.EntityFormatter): + + def __init__(self): + self.columns = [ + "column_1", + "column_2" + ] + + def _get_formatted_data(self, obj): + return obj + + +@ddt.ddt +class TestEntityFormattterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Entity Formatter.""" + + def setUp(self): + super(TestEntityFormattterTestCase, self).setUp() + self.format = TestEntityFormattter() + + def test_get_sorted_list(self): + obj_list = ["obj2", "obj1"] + + result = self.format._get_sorted_list(obj_list) + + self.assertEqual( + obj_list, + result + ) + + def test_list_objects(self): + obj_list = ["obj2", "obj1"] + + (columns, data) = self.format.list_objects(obj_list) + + self.assertEqual( + ( + self.format.columns, + ["obj2", "obj1"] + ), + ( + columns, + list(data) + ) + ) + + def test_get_generic_data(self): + obj = mock.Mock() + + result = self.format._get_generic_data(obj) + + self.assertEqual( + obj, + result + ) + + def test_get_generic_columns(self): + result = self.format._get_generic_columns() + + self.assertEqual( + self.format.columns, + result + ) + + def test_get_formatted_entity(self): + obj = mock.Mock() + + (columns, data) = self.format.get_formatted_entity(obj) + + self.assertEqual( + (self.format.columns, obj), + (columns, data) + ) + + @ddt.data( + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": "33%" + }, + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.2f}%", + "expected_result": "33.33%" + }, + { + "current_value": 0, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": None + }, + { + "current_value": 3, + "max_value": None, + "percent_format": "{:.0f}%", + "expected_result": None + } + ) + def test_get_percent_string(self, data): + result = self.format._get_percent_string( + data["current_value"], + data["max_value"], + percent_format=data["percent_format"] + ) + + self.assertEqual( + data["expected_result"], + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update(self, mock_get_percent_string): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = "mock_percent_string" + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at [mock_percent_string] mock_message", + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update_no_percent_string( + self, + mock_get_percent_string + ): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = None + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at mock_message", + result + ) diff --git a/coriolisclient/tests/cli/test_migrations.py b/coriolisclient/tests/cli/test_migrations.py deleted file mode 100644 index 85d9720..0000000 --- a/coriolisclient/tests/cli/test_migrations.py +++ /dev/null @@ -1,568 +0,0 @@ -# Copyright 2024 Cloudbase Solutions Srl -# All Rights Reserved. - -import os -from unittest import mock - -from cliff import command -from cliff import lister -from cliff import show - -from coriolisclient.cli import formatter -from coriolisclient.cli import migrations -from coriolisclient.cli import utils as cli_utils -from coriolisclient.tests import test_base - - -class MigrationFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Migration Formatter.""" - - def setUp(self): - super(MigrationFormatterTestCase, self).setUp() - self.migration = migrations.MigrationFormatter() - - def test_get_sorted_list(self): - obj1 = mock.Mock() - obj2 = mock.Mock() - obj3 = mock.Mock() - obj1.created_at = "date1" - obj2.created_at = "date2" - obj3.created_at = "date3" - obj_list = [obj2, obj1, obj3] - - result = self.migration._get_sorted_list(obj_list) - - self.assertEqual( - [obj1, obj2, obj3], - result - ) - - def test_get_formatted_data(self): - obj = mock.Mock() - obj.id = mock.sentinel.id - obj.last_execution_status = mock.sentinel.last_execution_status - obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] - obj.notes = mock.sentinel.notes - obj.created_at = mock.sentinel.created_at - - result = self.migration._get_formatted_data(obj) - - self.assertEqual( - ( - mock.sentinel.id, - mock.sentinel.last_execution_status, - ('mock_instance3%(ls)smock_instance1%(ls)smock_instance2' - % {"ls": os.linesep}), - mock.sentinel.notes, - mock.sentinel.created_at - ), - result - ) - - -class MigrationDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Migration Detail Formatter.""" - - def setUp(self): - super(MigrationDetailFormatterTestCase, self).setUp() - self.migration = migrations.MigrationDetailFormatter( - show_instances_data=True) - - def test_format_instances(self): - obj = mock.Mock() - obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] - expected_result = ( - 'mock_instance1%(ls)smock_instance2%(ls)smock_instance3' - % {"ls": os.linesep} - ) - - result = self.migration._format_instances(obj) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(formatter.EntityFormatter, '_format_progress_update') - def test_format_progress_updates(self, mock_format_progress_update): - update1 = {"created_at": "date2", "message2": "message2"} - update2 = {"created_at": "date3", "message3": "message3"} - update3 = {"index": 2, "created_at": "date1"} - ret_update1 = "date2 [10] message2" - ret_update2 = "date3 [20] message3" - ret_update3 = "date1 [30] None" - task_dict = {"progress_updates": [update3, update1, update2]} - mock_format_progress_update.side_effect = [ - ret_update1, ret_update2, ret_update3] - expected_result = ( - 'date2 [10] message2%(ls)sdate3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep} - ) - - result = self.migration._format_progress_updates(task_dict) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_progress_updates') - def test_format_task(self, mock_format_progress_updates): - mock_task = mock.Mock() - task = { - "depends_on": ["mock_dep1", "mock_dep2"], - "id": mock.sentinel.id, - "task_type": mock.sentinel.task_type, - "instance": mock.sentinel.instance, - "status": mock.sentinel.status, - "exception_details": mock.sentinel.exception_details - } - mock_task.to_dict.return_value = task - mock_format_progress_updates.return_value = ( - 'date2 [10] message2%(ls)sdate3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep} - ) - expected_result = ( - 'id: sentinel.id%(ls)s' - 'task_type: sentinel.task_type%(ls)s' - 'instance: sentinel.instance%(ls)s' - 'status: sentinel.status%(ls)s' - 'depends_on: mock_dep1, mock_dep2%(ls)s' - 'exception_details: sentinel.exception_details%(ls)s' - 'progress_updates:%(ls)s' - 'date2 [10] message2%(ls)s' - 'date3 [20] message3%(ls)s' - 'date1 [30] None' % {"ls": os.linesep}) - - result = self.migration._format_task(mock_task) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_progress_updates') - def test_format_task_no_progress_updates( - self, mock_format_progress_updates): - mock_task = mock.Mock() - task = { - "depends_on": ["mock_dep1", "mock_dep2"], - "id": mock.sentinel.id, - "task_type": mock.sentinel.task_type, - "instance": mock.sentinel.instance, - "status": mock.sentinel.status, - "exception_details": mock.sentinel.exception_details - } - mock_task.to_dict.return_value = task - mock_format_progress_updates.return_value = None - expected_result = ( - 'id: sentinel.id%(ls)s' - 'task_type: sentinel.task_type%(ls)s' - 'instance: sentinel.instance%(ls)s' - 'status: sentinel.status%(ls)s' - 'depends_on: mock_dep1, mock_dep2%(ls)s' - 'exception_details: sentinel.exception_details%(ls)s' - 'progress_updates:' % {"ls": os.linesep} - ) - - result = self.migration._format_task(mock_task) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, '_format_task') - def test_format_tasks(self, mock_format_task): - obj = mock.Mock() - obj.tasks = [mock.sentinel.task1, mock.sentinel.task2] - mock_format_task.side_effect = ["task1", "task2"] - expected_result = 'task1%(ls)s%(ls)stask2' % {"ls": os.linesep} - - result = self.migration._format_tasks(obj) - - self.assertEqual( - expected_result, - result - ) - - @mock.patch.object(migrations.MigrationDetailFormatter, '_format_tasks') - @mock.patch.object(cli_utils, 'format_mapping') - @mock.patch.object(cli_utils, 'format_json_for_object_property') - @mock.patch.object(migrations.MigrationDetailFormatter, - '_format_instances') - @mock.patch.object(cli_utils, 'parse_storage_mappings') - def test_get_formatted_data( - self, - mock_parse_storage_mappings, - mock_format_instances, - mock_format_json_for_object_property, - mock_format_mapping, - mock_format_tasks - ): - mock_obj = mock.Mock() - obj = { - "storage_mappings": {'default_storage': mock.sentinel.storage} - } - mock_obj.to_dict.return_value = obj - mock_obj.id = mock.sentinel.id - mock_obj.last_execution_status = mock.sentinel.last_execution_status - mock_obj.created_at = mock.sentinel.created_at - mock_obj.updated_at = mock.sentinel.updated_at - mock_obj.reservation_id = mock.sentinel.reservation_id - mock_format_instances.return_value = mock.sentinel.formatted_instances - mock_obj.notes = mock.sentinel.notes - mock_obj.origin_endpoint_id = mock.sentinel.origin_endpoint_id - mock_obj.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id - mock_obj.destination_endpoint_id = \ - mock.sentinel.destination_endpoint_id - mock_obj.destination_minion_pool_id = \ - mock.sentinel.destination_minion_pool_id - mock_obj.replication_count = mock.sentinel.replication_count - mock_obj.shutdown_instances = mock.sentinel.shutdown_instances - mock_parse_storage_mappings.return_value = ( - mock.sentinel.default_storage, - mock.sentinel.backend_mappings, - mock.sentinel.disk_mappings - ) - mock_format_json_for_object_property.side_effect = [ - mock.sentinel.instance_osmorphing_minion_pool_mappings, - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - mock.sentinel.user_scripts, - mock.sentinel.transfer_result, - ] - mock_format_mapping.side_effect = [ - mock.sentinel.disk_mapping, mock.sentinel.backend_mappings] - mock_format_tasks.return_value = mock.sentinel.formatted_tasks - mock_obj.info = mock.sentinel.info - expected_result = [ - mock.sentinel.id, - mock.sentinel.last_execution_status, - mock.sentinel.created_at, - mock.sentinel.updated_at, - mock.sentinel.reservation_id, - mock.sentinel.formatted_instances, - mock.sentinel.notes, - mock.sentinel.origin_endpoint_id, - mock.sentinel.origin_minion_pool_id, - mock.sentinel.destination_endpoint_id, - mock.sentinel.destination_minion_pool_id, - mock.sentinel.instance_osmorphing_minion_pool_mappings, - mock.sentinel.replication_count, - mock.sentinel.shutdown_instances, - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - mock.sentinel.disk_mapping, - mock.sentinel.backend_mappings, - mock.sentinel.default_storage, - mock.sentinel.user_scripts, - mock.sentinel.formatted_tasks, - mock.sentinel.transfer_result, - mock.sentinel.info - ] - - result = self.migration._get_formatted_data(mock_obj) - - self.assertEqual( - expected_result, - result - ) - - -class CreateMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CreateMigrationTestCase, self).setUp() - self.migration = migrations.CreateMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser( - self, - mock_get_parser, - *_ - ): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(cli_utils, 'compose_user_scripts') - @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') - @mock.patch.object(cli_utils, 'get_option_value_from_args') - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action( - self, - mock_get_formatted_entity, - mock_get_option_value_from_args, - mock_get_storage_mappings_dict_from_args, - mock_compose_user_scripts - ): - args = mock.Mock() - args.instances = mock.sentinel.instances - args.notes = mock.sentinel.notes - args.skip_os_morphing = mock.sentinel.skip_os_morphing - args.replication_count = mock.sentinel.replication_count - args.shutdown_instances = mock.sentinel.shutdown_instances - args.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id - args.destination_minion_pool_id = \ - mock.sentinel.destination_minion_pool_id - args.instance_osmorphing_minion_pool_mappings = [ - {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, - {'instance_id': "instance_id2", 'pool_id': "pool_id2"} - ] - mock_endpoints = mock.Mock() - mock_migrations = mock.Mock() - self.mock_app.client_manager.coriolis.endpoints = mock_endpoints - mock_endpoints.get_endpoint_id_for_name.side_effect = [ - mock.sentinel.origin_endpoint_id, - mock.sentinel.destination_endpoint_id - ] - self.mock_app.client_manager.coriolis.migrations.create = \ - mock_migrations - mock_get_option_value_from_args.side_effect = [ - mock.sentinel.destination_environment, - mock.sentinel.source_environment, - mock.sentinel.network_map, - ] - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migrations.assert_called_once_with( - mock.sentinel.origin_endpoint_id, - mock.sentinel.destination_endpoint_id, - mock.sentinel.source_environment, - mock.sentinel.destination_environment, - mock.sentinel.instances, - network_map=mock.sentinel.network_map, - notes=mock.sentinel.notes, - storage_mappings=(mock_get_storage_mappings_dict_from_args. - return_value), - skip_os_morphing=mock.sentinel.skip_os_morphing, - replication_count=mock.sentinel.replication_count, - shutdown_instances=mock.sentinel.shutdown_instances, - origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, - destination_minion_pool_id= - mock.sentinel.destination_minion_pool_id, - instance_osmorphing_minion_pool_mappings={ - 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'}, - user_scripts=mock_compose_user_scripts.return_value - ) - mock_get_formatted_entity.assert_called_once_with( - mock_migrations.return_value) - - -class CreateMigrationFromReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Migration From Replica.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CreateMigrationFromReplicaTestCase, self).setUp() - self.migration = migrations.CreateMigrationFromReplica( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser( - self, - mock_get_parser, - *_ - ): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(cli_utils, 'compose_user_scripts') - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action( - self, - mock_get_formatted_entity, - mock_compose_user_scripts - ): - args = mock.Mock() - args.replica = mock.sentinel.replica - args.clone_disks = mock.sentinel.clone_disks - args.force = mock.sentinel.force - args.skip_os_morphing = mock.sentinel.skip_os_morphing - args.instance_osmorphing_minion_pool_mappings = [ - {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, - {'instance_id': "instance_id2", 'pool_id': "pool_id2"} - ] - mock_migrations = mock.Mock() - self.mock_app.client_manager.coriolis.migrations = mock_migrations - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migrations.create_from_replica.assert_called_once_with( - mock.sentinel.replica, - mock.sentinel.clone_disks, - mock.sentinel.force, - mock.sentinel.skip_os_morphing, - user_scripts=mock_compose_user_scripts.return_value, - instance_osmorphing_minion_pool_mappings={ - 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'} - ) - mock_get_formatted_entity.assert_called_once_with( - mock_migrations.create_from_replica.return_value) - - -class ShowMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(ShowMigrationTestCase, self).setUp() - self.migration = migrations.ShowMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(show.ShowOne, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(migrations.MigrationDetailFormatter, - 'get_formatted_entity') - def test_take_action(self, mock_get_formatted_entity): - args = mock.Mock() - args.id = mock.sentinel.id - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.get = mock_migration - - result = self.migration.take_action(args) - - self.assertEqual( - mock_get_formatted_entity.return_value, - result - ) - mock_migration.assert_called_once_with(mock.sentinel.id) - mock_get_formatted_entity.assert_called_once_with( - mock_migration.return_value) - - -class CancelMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Cancel Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(CancelMigrationTestCase, self).setUp() - self.migration = migrations.CancelMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(command.Command, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - def test_take_action(self): - args = mock.Mock() - args.id = mock.sentinel.id - args.force = mock.sentinel.force - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.cancel = \ - mock_migration - - self.migration.take_action(args) - - mock_migration.assert_called_once_with( - mock.sentinel.id, mock.sentinel.force) - - -class DeleteMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(DeleteMigrationTestCase, self).setUp() - self.migration = migrations.DeleteMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(command.Command, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - def test_take_action(self): - args = mock.Mock() - args.id = mock.sentinel.id - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.delete = \ - mock_migration - - self.migration.take_action(args) - - mock_migration.assert_called_once_with(mock.sentinel.id) - - -class ListMigrationTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Migration.""" - - def setUp(self): - self.mock_app = mock.Mock() - super(ListMigrationTestCase, self).setUp() - self.migration = migrations.ListMigration( - self.mock_app, mock.sentinel.app_args) - - @mock.patch.object(lister.Lister, 'get_parser') - def test_get_parser(self, mock_get_parser): - result = self.migration.get_parser(mock.sentinel.prog_name) - - self.assertEqual( - mock_get_parser.return_value, - result - ) - mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - - @mock.patch.object(migrations.MigrationFormatter, 'list_objects') - def test_take_action(self, mock_list_objects): - args = mock.Mock() - mock_migration = mock.Mock() - self.mock_app.client_manager.coriolis.migrations.list = \ - mock_migration - - result = self.migration.take_action(args) - - self.assertEqual( - mock_list_objects.return_value, - result - ) - mock_list_objects.assert_called_once_with(mock_migration.return_value) diff --git a/coriolisclient/tests/cli/test_shell.py b/coriolisclient/tests/cli/test_shell.py new file mode 100644 index 0000000..196808e --- /dev/null +++ b/coriolisclient/tests/cli/test_shell.py @@ -0,0 +1,438 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +import logging +import os +from unittest import mock + +from cliff import app +from keystoneauth1 import loading +from keystoneauth1 import session + +from coriolisclient.cli import shell +from coriolisclient import client +from coriolisclient import exceptions +from coriolisclient.tests import test_base + + +class CustomMock(mock.MagicMock): + def __getattr__(self, name): + return None + + +@ddt.ddt +class CoriolisTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell App.""" + + def setUp(self): + super(CoriolisTestCase, self).setUp() + self.coriolis = shell.Coriolis() + + @ddt.file_data('data/shell_check_auth_arguments.yml') + @ddt.unpack + def test_check_auth_arguments(self, config): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + expected_result = config.get("expected_result") + + if expected_result is not None: + result = self.coriolis.check_auth_arguments( + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.check_auth_arguments, + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + @ddt.file_data('data/shell_create_keystone_auth.yml') + @ddt.unpack + @mock.patch.object(session, 'Session') + def test_create_keystone_session( + self, + mock_Session, + config + ): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + kwargs = config.get("kwargs", {}) + expected_kwargs = config.get("expected_kwargs", {}) + mock_stderr = mock.Mock() + self.coriolis.stderr = mock_stderr + + auth_fun = config['auth_fun'] + with mock.patch(auth_fun) as mock_auth: + result = self.coriolis.create_keystone_session( + args, + api_version=config.get("api_version", None), + kwargs_dict=kwargs, + auth_type=config.get("auth_type", None), + verify=config.get("verify", True) + ) + + self.assertEqual( + mock_Session.return_value, + result + ) + mock_Session.assert_called_once_with( + auth=mock_auth.return_value, verify=config.get("verify", True)) + mock_auth.assert_called_once_with(**expected_kwargs) + + @ddt.data( + { + "args": { + "no_auth": "mock_no_auth", + "os_auth_url": "mock_os_auth_url" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint", + "os_tenant_id": "mock_os_tenant_id" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "project_id": "mock_os_tenant_id", + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": False + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True, + "os_cacert": "mock_os_cacert" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": "mock_os_cacert" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token" + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_url": "mock_os_auth_url", + "os_password": "mock_os_password", + "os_user_id": "mock_os_user_id", + "os_username": "mock_os_username", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "password": "mock_os_password", + "user_id": "mock_os_user_id", + "username": "mock_os_username", + }, + "auth_type": "password" + }, + {} + ) + @mock.patch.object(shell.Coriolis, 'create_keystone_session') + @mock.patch.object(client, 'Client') + def test_create_client( + self, + data, + mock_Client, + mock_create_ks_session + ): + args = CustomMock() + args.os_identity_api_version = mock.sentinel.os_identity_api_version + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + expected_client_kwargs = data.get("expected_client_kwargs") + expected_ks_session_kwargs = data.get("expected_ks_session_kwargs", {}) + + if expected_client_kwargs is not None: + result = self.coriolis.create_client(args) + self.assertEqual( + result, mock_Client.return_value) + if expected_ks_session_kwargs: + mock_Client.assert_called_once_with( + session=mock_create_ks_session.return_value, + **expected_client_kwargs, + verify=data.get("verify", True)) + mock_create_ks_session.assert_called_once_with( + args, + mock.sentinel.os_identity_api_version, + expected_ks_session_kwargs, + auth_type=data["auth_type"], + verify=data.get("verify", True)) + else: + mock_Client.assert_called_once_with( + **expected_client_kwargs, + verify=data.get("verify", True)) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.create_client, + args + ) + + @ddt.data( + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 3, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 2, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": None, + "expected_kwargs": { + "project_id": "mock_project_id" + } + }, + { + "args": {}, + "api_version": None, + "expected_kwargs": {} + } + ) + def test_build_kwargs_based_on_version(self, data): + args = CustomMock() + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + api_version = data["api_version"] + + result = self.coriolis.build_kwargs_based_on_version( + args, api_version=api_version) + + self.assertEqual( + data["expected_kwargs"], + result + ) + + def test_get_endpoint_filter_kwargs( + self + ): + args = mock.Mock() + args.interface = mock.sentinel.interface + args.service_type = mock.sentinel.service_type + args.service_name = mock.sentinel.service_name + args.coriolis_api_version = mock.sentinel.coriolis_api_version + args.region_name = mock.sentinel.region_name + expected_result = { + "interface": mock.sentinel.interface, + "service_type": mock.sentinel.service_type, + "service_name": mock.sentinel.service_name, + "region_name": mock.sentinel.region_name, + "version": mock.sentinel.coriolis_api_version, + } + + result = self.coriolis._get_endpoint_filter_kwargs(args) + + self.assertEqual( + expected_result, + result + ) + + @mock.patch.object(shell.Coriolis, '_env') + @mock.patch.object(loading, 'register_session_argparse_arguments') + @mock.patch.object(app.App, 'build_option_parser') + def test_build_option_parser( + self, + mock_build_option_parser, + mock_register_session_argparse_arguments, + *_ + ): + result = self.coriolis.build_option_parser( + mock.sentinel.description, mock.sentinel.version) + + self.assertEqual( + mock_build_option_parser.return_value, + result + ) + mock_build_option_parser.assert_called_once_with( + mock.sentinel.description, mock.sentinel.version, None) + mock_register_session_argparse_arguments.assert_called_once_with( + mock_build_option_parser.return_value) + + def test_env(self): + os.environ["TEST_ENV"] = "test_value" + result = self.coriolis._env( + "TEST_ENV", mock.sentinel.default) + + self.assertEqual( + "test_value", + result + ) + + @mock.patch.object(shell.Coriolis, 'create_client') + def test_prepare_to_run_command(self, mock_create_client): + cmd = mock.Mock() + cmd.auth_required = True + self.coriolis.options = mock.sentinel.options + + self.coriolis.prepare_to_run_command(cmd) + + self.assertEqual( + mock_create_client.return_value, + self.coriolis.client_manager.coriolis + ) + mock_create_client.assert_called_once_with(mock.sentinel.options) + + @mock.patch.object(app.App, 'run') + def test_run(self, mock_run): + mock_stderr = mock.Mock() + mock_parser = mock.Mock() + self.coriolis.stderr = mock_stderr + self.coriolis.parser = mock_parser + + result = self.coriolis.run(None) + + self.assertEqual( + 1, + result + ) + mock_stderr.write.assert_called_once_with( + mock_parser.format_usage.return_value) + mock_run.assert_not_called() + + mock_stderr.reset_mock() + mock_parser.reset_mock() + + result = self.coriolis.run(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_parser.format_usage.assert_not_called() + + +class ShellTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell.""" + + def test_setup_logging(self): + shell._setup_logging() + self.assertEqual( + ( + logging.WARNING, + logging.ERROR + ), + ( + logging.getLogger("requests").level, + logging.getLogger("keystoneclient").level + ) + ) + + @mock.patch.object(shell.Coriolis, 'run') + def test_main( + self, + mock_run + ): + result = shell.main(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_run.assert_called_once_with(mock.sentinel.argv) diff --git a/coriolisclient/tests/cli/test_replica_executions.py b/coriolisclient/tests/cli/test_transfer_executions.py similarity index 66% rename from coriolisclient/tests/cli/test_replica_executions.py rename to coriolisclient/tests/cli/test_transfer_executions.py index 1d85a27..1f3dc97 100644 --- a/coriolisclient/tests/cli/test_replica_executions.py +++ b/coriolisclient/tests/cli/test_transfer_executions.py @@ -9,16 +9,16 @@ from cliff import show from coriolisclient.cli import formatter -from coriolisclient.cli import replica_executions +from coriolisclient.cli import transfer_executions from coriolisclient.tests import test_base -class ReplicaExecutionFormatterTestCase(test_base.CoriolisBaseTestCase): +class TransferExecutionFormatterTestCase(test_base.CoriolisBaseTestCase): """Test suite for the Coriolis Replic Execution Formatter.""" def setUp(self): - super(ReplicaExecutionFormatterTestCase, self).setUp() - self.replica = replica_executions.ReplicaExecutionFormatter() + super(TransferExecutionFormatterTestCase, self).setUp() + self.transfer = transfer_executions.TransferExecutionFormatter() def test_get_sorted_list(self): obj1 = mock.Mock() @@ -29,7 +29,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -43,7 +43,7 @@ def test_get_formatted_data(self): obj.status = mock.sentinel.status obj.created_at = mock.sentinel.created_at - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( @@ -56,12 +56,12 @@ def test_get_formatted_data(self): ) -class ReplicaExecutionDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Execution Detail Formatter.""" +class TransferExecutionDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Execution Detail Formatter.""" def setUp(self): - super(ReplicaExecutionDetailFormatterTestCase, self).setUp() - self.replica = replica_executions.ReplicaExecutionDetailFormatter() + super(TransferExecutionDetailFormatterTestCase, self).setUp() + self.transfer = transfer_executions.TransferExecutionDetailFormatter() def test_format_instances(self): obj = mock.Mock() @@ -75,7 +75,7 @@ def test_format_instances(self): % {"ls": os.linesep} ) - result = self.replica._format_instances(obj) + result = self.transfer._format_instances(obj) self.assertEqual( expected_result, @@ -98,14 +98,14 @@ def test_format_progress_updates(self, mock_format_progress_update): 'date1 [30] None' % {"ls": os.linesep} ) - result = self.replica._format_progress_updates(task_dict) + result = self.transfer._format_progress_updates(task_dict) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_progress_updates') def test_format_task(self, mock_format_progress_updates): mock_task = mock.Mock() @@ -134,14 +134,14 @@ def test_format_task(self, mock_format_progress_updates): 'date3 [20] message3%(ls)s' 'date1 [30] None' % {"ls": os.linesep}) - result = self.replica._format_task(mock_task) + result = self.transfer._format_task(mock_task) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_progress_updates') def test_format_task_no_progress_updates( self, mock_format_progress_updates): @@ -166,14 +166,14 @@ def test_format_task_no_progress_updates( 'progress_updates:' % {"ls": os.linesep} ) - result = self.replica._format_task(mock_task) + result = self.transfer._format_task(mock_task) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_task') def test_format_tasks(self, mock_format_task): obj = mock.Mock() @@ -181,16 +181,16 @@ def test_format_tasks(self, mock_format_task): mock_format_task.side_effect = ["task1", "task2"] expected_result = 'task1%(ls)s%(ls)stask2' % {"ls": os.linesep} - result = self.replica._format_tasks(obj) + result = self.transfer._format_tasks(obj) self.assertEqual( expected_result, result ) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_tasks') - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, '_format_instances') def test_get_formatted_data( self, @@ -219,7 +219,7 @@ def test_get_formatted_data( mock.sentinel.formatted_tasks, ) - result = self.replica._get_formatted_data(mock_obj) + result = self.transfer._get_formatted_data(mock_obj) self.assertEqual( expected_result, @@ -227,13 +227,13 @@ def test_get_formatted_data( ) -class CreateReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Create Replica Execution.""" +class CreateTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Create Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.CreateReplicaExecution( + super(CreateTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.CreateTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -241,7 +241,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -249,38 +249,40 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.shutdown_instances = mock.sentinel.shutdown_instances + args.auto_deploy = mock.sentinel.auto_deploy mock_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.create = \ + self.mock_app.client_manager.coriolis.transfer_executions.create = \ mock_execution - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) mock_execution.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.shutdown_instances) + mock.sentinel.transfer, mock.sentinel.shutdown_instances, + mock.sentinel.auto_deploy) mock_get_formatted_entity.assert_called_once_with( mock_execution.return_value) -class ShowReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica Execution.""" +class ShowTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.ShowReplicaExecution( + super(ShowTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.ShowTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -288,7 +290,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -296,37 +298,37 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.get = \ + self.mock_app.client_manager.coriolis.transfer_executions.get = \ execution - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - execution.assert_called_once_with(args.replica, args.id) + execution.assert_called_once_with(args.transfer, args.id) mock_get_formatted_entity.assert_called_once_with( execution.return_value) -class CancelReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Cancel Replica Execution.""" +class CancelTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Cancel Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(CancelReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.CancelReplicaExecution( + super(CancelTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.CancelTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -334,7 +336,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -344,26 +346,26 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id args.force = False - replica_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions = \ - replica_execution + transfer_execution = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions = \ + transfer_execution - self.replica.take_action(args) + self.transfer.take_action(args) - replica_execution.cancel.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id, False) + transfer_execution.cancel.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, False) -class DeleteReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Execution.""" +class DeleteTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.DeleteReplicaExecution( + super(DeleteTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.DeleteTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -371,7 +373,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -381,25 +383,25 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id - replica_execution = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions = \ - replica_execution + transfer_execution = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions = \ + transfer_execution - self.replica.take_action(args) + self.transfer.take_action(args) - replica_execution.delete.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id) + transfer_execution.delete.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id) -class ListReplicaExecutionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica Execution.""" +class ListTransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer Execution.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaExecutionTestCase, self).setUp() - self.replica = replica_executions.ListReplicaExecution( + super(ListTransferExecutionTestCase, self).setUp() + self.transfer = transfer_executions.ListTransferExecution( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') @@ -407,7 +409,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -415,24 +417,24 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionFormatter, + @mock.patch.object(transfer_executions.TransferExecutionFormatter, 'list_objects') def test_take_action( self, mock_list_objects ): args = mock.Mock() - args.replica = mock.sentinel.replica - mock_replica_list = mock.Mock() - self.mock_app.client_manager.coriolis.replica_executions.list = \ - mock_replica_list + args.transfer = mock.sentinel.transfer + mock_transfer_list = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_executions.list = \ + mock_transfer_list - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_replica_list.assert_called_once_with(mock.sentinel.replica) + mock_transfer_list.assert_called_once_with(mock.sentinel.transfer) mock_list_objects.assert_called_once_with( - mock_replica_list.return_value) + mock_transfer_list.return_value) diff --git a/coriolisclient/tests/cli/test_replica_schedules.py b/coriolisclient/tests/cli/test_transfer_schedules.py similarity index 58% rename from coriolisclient/tests/cli/test_replica_schedules.py rename to coriolisclient/tests/cli/test_transfer_schedules.py index 926293c..2bf301f 100644 --- a/coriolisclient/tests/cli/test_replica_schedules.py +++ b/coriolisclient/tests/cli/test_transfer_schedules.py @@ -9,17 +9,17 @@ from cliff import lister from cliff import show -from coriolisclient.cli import replica_schedules +from coriolisclient.cli import transfer_schedules from coriolisclient import exceptions from coriolisclient.tests import test_base class RangeActionTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Range Action.""" + """Test suite for the Coriolis Transfer Range Action.""" def setUp(self): super(RangeActionTestCase, self).setUp() - self.range_action = replica_schedules.RangeAction( + self.range_action = transfer_schedules.RangeAction( 1, 10, ["mock_option"], "dest") def test__call__(self): @@ -48,12 +48,13 @@ def test__call__argument_error(self): ) -class ReplicaScheduleFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedule Formatter.""" +class TransferScheduleFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedule Formatter.""" def setUp(self): - super(ReplicaScheduleFormatterTestCase, self).setUp() - self.replica = replica_schedules.ReplicaScheduleFormatter() + super(TransferScheduleFormatterTestCase, self).setUp() + self.transfer_schedules = ( + transfer_schedules.TransferScheduleFormatter()) def test_get_sorted_list(self): obj1 = mock.Mock() @@ -64,7 +65,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer_schedules._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -73,17 +74,17 @@ def test_get_sorted_list(self): def test_get_formatted_data(self): obj = mock.Mock() - obj.replica_id = mock.sentinel.replica_id + obj.transfer_id = mock.sentinel.transfer_id obj.id = mock.sentinel.id obj.schedule = mock.sentinel.schedule obj.created_at = mock.sentinel.created_at obj.expiration_date = mock.sentinel.expiration_date - result = self.replica._get_formatted_data(obj) + result = self.transfer_schedules._get_formatted_data(obj) self.assertEqual( ( - mock.sentinel.replica_id, + mock.sentinel.transfer_id, mock.sentinel.id, mock.sentinel.schedule, mock.sentinel.created_at, @@ -93,48 +94,51 @@ def test_get_formatted_data(self): ) -class ReplicaScheduleDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedule Detail Formatter.""" +class TransferScheduleDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedule Detail Formatter.""" def setUp(self): - super(ReplicaScheduleDetailFormatterTestCase, self).setUp() - self.replica = replica_schedules.ReplicaScheduleDetailFormatter() + super(TransferScheduleDetailFormatterTestCase, self).setUp() + self.transfer_schedules = ( + transfer_schedules.TransferScheduleDetailFormatter()) def test_get_formatted_data(self): obj = mock.Mock() obj.id = mock.sentinel.id - obj.replica_id = mock.sentinel.replica_id + obj.transfer_id = mock.sentinel.transfer_id obj.schedule = mock.sentinel.schedule obj.created_at = mock.sentinel.created_at obj.updated_at = mock.sentinel.updated_at obj.enabled = False obj.expiration_date = mock.sentinel.expiration_date obj.shutdown_instance = mock.sentinel.shutdown_instance + obj.auto_deploy = mock.sentinel.auto_deploy - result = self.replica._get_formatted_data(obj) + result = self.transfer_schedules._get_formatted_data(obj) self.assertEqual( ( mock.sentinel.id, - mock.sentinel.replica_id, + mock.sentinel.transfer_id, mock.sentinel.schedule, mock.sentinel.created_at, mock.sentinel.updated_at, False, mock.sentinel.expiration_date, - mock.sentinel.shutdown_instance + mock.sentinel.shutdown_instance, + mock.sentinel.auto_deploy, ), result ) -class CreateReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Create Replica Schedule.""" +class CreateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Create Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.CreateReplicaSchedule( + super(CreateTransferScheduleTestCase, self).setUp() + self.transfer_schedules = transfer_schedules.CreateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -142,7 +146,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -150,10 +154,10 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action( self, mock_parse_schedule_group_args, @@ -161,33 +165,35 @@ def test_take_action( mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.disabled = False - args.shutdown_instance = mock.sentinel.shutdown_instance + args.shutdown_instance = False + args.auto_deploy = False mock_schedule_group_args = {"minute": mock.sentinel.minute} mock_parse_schedule_group_args.return_value = mock_schedule_group_args mock_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.create = \ + self.mock_app.client_manager.coriolis.transfer_schedules.create = \ mock_schedule - result = self.replica.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) mock_schedule.assert_called_once_with( - mock.sentinel.replica, + mock.sentinel.transfer, mock_schedule_group_args, True, mock_parse_expiration_date.return_value, - mock.sentinel.shutdown_instance + False, + False, ) mock_get_formatted_entity.assert_called_once_with( mock_schedule.return_value) - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action_no_parsed_schedule( self, mock_parse_schedule_group_args, @@ -198,19 +204,19 @@ def test_take_action_no_parsed_schedule( self.assertRaises( exceptions.CoriolisException, - self.replica.take_action, + self.transfer_schedules.take_action, args ) mock_parse_expiration_date.assert_not_called() -class ShowReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica Schedule.""" +class ShowTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.ShowReplicaSchedule( + super(ShowTransferScheduleTestCase, self).setUp() + self.transfer_schedules = transfer_schedules.ShowTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -218,7 +224,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -226,37 +232,37 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') def test_take_action( self, mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.get = \ + self.mock_app.client_manager.coriolis.transfer_schedules.get = \ schedule - result = self.replica.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - schedule.assert_called_once_with(args.replica, args.id) + schedule.assert_called_once_with(args.transfer, args.id) mock_get_formatted_entity.assert_called_once_with( schedule.return_value) -class UpdateReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Update Replica Schedule.""" +class UpdateTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Update Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(UpdateReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.UpdateReplicaSchedule( + super(UpdateTransferScheduleTestCase, self).setUp() + self.transfer_schedules = transfer_schedules.UpdateTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') @@ -264,7 +270,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -272,10 +278,10 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action( self, mock_parse_schedule_group_args, @@ -286,37 +292,39 @@ def test_take_action( args.expires = True args.shutdown = True args.enabled = True - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id + args.auto_deploy = False mock_parse_schedule_group_args.return_value = \ {"minute": mock.sentinel.minute} - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule expected_updated_values = { "schedule": {"minute": mock.sentinel.minute}, "expiration_date": mock_parse_expiration_date.return_value, "shutdown_instance": True, "enabled": True, + "auto_deploy": False, } - result = self.replica.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - replica_schedule.update.assert_called_once_with( - mock.sentinel.replica, + transfer_schedule.update.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, expected_updated_values ) - @mock.patch.object(replica_schedules.ReplicaScheduleDetailFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleDetailFormatter, 'get_formatted_entity') - @mock.patch.object(replica_schedules, '_parse_expiration_date') - @mock.patch.object(replica_schedules, '_parse_schedule_group_args') + @mock.patch.object(transfer_schedules, '_parse_expiration_date') + @mock.patch.object(transfer_schedules, '_parse_schedule_group_args') def test_take_action_no_updated_values( self, mock_parse_schedule_group_args, @@ -324,18 +332,19 @@ def test_take_action_no_updated_values( mock_get_formatted_entity ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id args.expires = False args.shutdown = None args.enabled = None + args.auto_deploy = None mock_parse_schedule_group_args.return_value = {} - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule expected_updated_values = {"expiration_date": None} - result = self.replica.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -343,20 +352,20 @@ def test_take_action_no_updated_values( ) mock_parse_expiration_date.assert_not_called() - replica_schedule.update.assert_called_once_with( - mock.sentinel.replica, + transfer_schedule.update.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id, expected_updated_values ) -class DeleteReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Schedule.""" +class DeleteTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.DeleteReplicaSchedule( + super(DeleteTransferScheduleTestCase, self).setUp() + self.transfer_schedules = transfer_schedules.DeleteTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') @@ -364,7 +373,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -374,25 +383,25 @@ def test_get_parser( def test_take_action(self): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.id = mock.sentinel.id - replica_schedule = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules = \ - replica_schedule + transfer_schedule = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules = \ + transfer_schedule - self.replica.take_action(args) + self.transfer_schedules.take_action(args) - replica_schedule.delete.assert_called_once_with( - mock.sentinel.replica, mock.sentinel.id) + transfer_schedule.delete.assert_called_once_with( + mock.sentinel.transfer, mock.sentinel.id) -class ListReplicaScheduleTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica Schedule.""" +class ListTransferScheduleTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer Schedule.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaScheduleTestCase, self).setUp() - self.replica = replica_schedules.ListReplicaSchedule( + super(ListTransferScheduleTestCase, self).setUp() + self.transfer_schedules = transfer_schedules.ListTransferSchedule( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') @@ -400,7 +409,7 @@ def test_get_parser( self, mock_get_parser ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer_schedules.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -408,38 +417,38 @@ def test_get_parser( ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_schedules.ReplicaScheduleFormatter, + @mock.patch.object(transfer_schedules.TransferScheduleFormatter, 'list_objects') def test_take_action( self, mock_list_objects ): args = mock.Mock() - args.replica = mock.sentinel.replica + args.transfer = mock.sentinel.transfer args.hide_expired = mock.sentinel.hide_expired - mock_replica_list = mock.Mock() - self.mock_app.client_manager.coriolis.replica_schedules.list = \ - mock_replica_list + mock_transfer_list = mock.Mock() + self.mock_app.client_manager.coriolis.transfer_schedules.list = \ + mock_transfer_list - result = self.replica.take_action(args) + result = self.transfer_schedules.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_replica_list.assert_called_once_with( - mock.sentinel.replica, hide_expired=mock.sentinel.hide_expired) + mock_transfer_list.assert_called_once_with( + mock.sentinel.transfer, hide_expired=mock.sentinel.hide_expired) mock_list_objects.assert_called_once_with( - mock_replica_list.return_value) + mock_transfer_list.return_value) -class ReplicaSchedulesTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Replica Schedules.""" +class TransferSchedulesTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Transfer Schedules.""" def test_add_schedule_group(self): parser = argparse.ArgumentParser() - replica_schedules._add_schedule_group(parser) + transfer_schedules._add_schedule_group(parser) self.assertIn( "Schedule", @@ -458,7 +467,7 @@ def __getattr__(self, name): "hour": mock.sentinel.hour } - result = replica_schedules._parse_schedule_group_args(args) + result = transfer_schedules._parse_schedule_group_args(args) self.assertEqual( expected_result, @@ -468,7 +477,7 @@ def __getattr__(self, name): def test_parse_expiration_date(self): value = None - result = replica_schedules._parse_expiration_date(value) + result = transfer_schedules._parse_expiration_date(value) self.assertEqual( None, @@ -477,7 +486,7 @@ def test_parse_expiration_date(self): value = "2099-12-31" - result = replica_schedules._parse_expiration_date(value) + result = transfer_schedules._parse_expiration_date(value) self.assertEqual( datetime.datetime(2099, 12, 31, 0, 0), @@ -488,6 +497,6 @@ def test_parse_expiration_date(self): self.assertRaises( exceptions.CoriolisException, - replica_schedules._parse_expiration_date, + transfer_schedules._parse_expiration_date, value ) diff --git a/coriolisclient/tests/cli/test_replicas.py b/coriolisclient/tests/cli/test_transfers.py similarity index 72% rename from coriolisclient/tests/cli/test_replicas.py rename to coriolisclient/tests/cli/test_transfers.py index 834da82..9a49423 100644 --- a/coriolisclient/tests/cli/test_replicas.py +++ b/coriolisclient/tests/cli/test_transfers.py @@ -7,18 +7,18 @@ from cliff import lister from cliff import show -from coriolisclient.cli import replica_executions -from coriolisclient.cli import replicas +from coriolisclient.cli import transfer_executions +from coriolisclient.cli import transfers from coriolisclient.cli import utils as cli_utils from coriolisclient.tests import test_base -class ReplicaFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Replica Formatter.""" +class TransferFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Transfer Formatter.""" def setUp(self): - super(ReplicaFormatterTestCase, self).setUp() - self.replica = replicas.ReplicaFormatter() + super(TransferFormatterTestCase, self).setUp() + self.transfer = transfers.TransferFormatter() def test_get_sorted_list(self): obj1 = mock.Mock() @@ -29,7 +29,7 @@ def test_get_sorted_list(self): obj3.created_at = "date3" obj_list = [obj2, obj1, obj3] - result = self.replica._get_sorted_list(obj_list) + result = self.transfer._get_sorted_list(obj_list) self.assertEqual( [obj1, obj2, obj3], @@ -40,7 +40,7 @@ def test_format_last_execution(self): obj = mock.Mock() obj.executions = None - result = self.replica._format_last_execution(obj) + result = self.transfer._format_last_execution(obj) self.assertEqual( "", @@ -67,7 +67,7 @@ def test_format_last_execution(self): } obj.executions = [execution1, execution3, execution2] - result = self.replica._format_last_execution(obj) + result = self.transfer._format_last_execution(obj) self.assertEqual( "mock_id3 mock_status3", @@ -80,13 +80,15 @@ def test_get_formatted_data(self): obj.last_execution_status = mock.sentinel.last_execution_status obj.instances = ["mock_instance3", "mock_instance1", "mock_instance2"] obj.notes = mock.sentinel.notes + obj.scenario = mock.sentinel.scenario obj.created_at = mock.sentinel.created_at - result = self.replica._get_formatted_data(obj) + result = self.transfer._get_formatted_data(obj) self.assertEqual( ( mock.sentinel.id, + mock.sentinel.scenario, ('mock_instance3%(ls)smock_instance1%(ls)smock_instance2' % {"ls": "\n"}), mock.sentinel.notes, @@ -97,12 +99,12 @@ def test_get_formatted_data(self): ) -class ReplicaDetailFormatterTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Replica Detail Formatter.""" +class TransferDetailFormatterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Transfer Detail Formatter.""" def setUp(self): - super(ReplicaDetailFormatterTestCase, self).setUp() - self.replica = replicas.ReplicaDetailFormatter( + super(TransferDetailFormatterTestCase, self).setUp() + self.transfer = transfers.TransferDetailFormatter( show_instances_data=True) def test_format_instances(self): @@ -113,7 +115,7 @@ def test_format_instances(self): % {"ls": "\n"} ) - result = self.replica._format_instances(obj) + result = self.transfer._format_instances(obj) self.assertEqual( expected_result, @@ -128,14 +130,14 @@ def test_format_execution(self): } expected_result = "mock_id mock_status" - result = self.replica._format_execution(execution) + result = self.transfer._format_execution(execution) self.assertEqual( expected_result, result ) - @mock.patch.object(replicas.ReplicaDetailFormatter, '_format_execution') + @mock.patch.object(transfers.TransferDetailFormatter, '_format_execution') def test_format_executions(self, mock_format_execution): executions = mock.Mock() execution1 = mock.Mock() @@ -156,17 +158,17 @@ def test_format_executions(self, mock_format_execution): "mock_id3 mock_status3" % {"ls": "\n"} ) - result = self.replica._format_executions(executions) + result = self.transfer._format_executions(executions) self.assertEqual( expected_result, result ) - @mock.patch.object(replicas.ReplicaDetailFormatter, '_format_executions') + @mock.patch.object(transfers.TransferDetailFormatter, '_format_executions') @mock.patch.object(cli_utils, 'format_mapping') @mock.patch.object(cli_utils, 'format_json_for_object_property') - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, '_format_instances') @mock.patch.object(cli_utils, 'parse_storage_mappings') def test_get_formatted_data( @@ -211,10 +213,14 @@ def test_get_formatted_data( mock_format_executions.return_value = \ mock.sentinel.formatted_executions mock_obj.info = mock.sentinel.info + mock_obj.scenario = mock.sentinel.scenario + mock_obj.clone_disks = mock.sentinel.clone_disks + mock_obj.skip_os_morphing = mock.sentinel.skip_os_morphing expected_result = [ mock.sentinel.id, mock.sentinel.created_at, mock.sentinel.updated_at, + mock.sentinel.scenario, mock.sentinel.reservation_id, mock.sentinel.formatted_instances, mock.sentinel.notes, @@ -230,11 +236,13 @@ def test_get_formatted_data( mock.sentinel.backend_mappings, mock.sentinel.default_storage, mock.sentinel.user_scripts, + mock.sentinel.clone_disks, + mock.sentinel.skip_os_morphing, mock.sentinel.formatted_executions, - mock.sentinel.info + mock.sentinel.info, ] - result = self.replica._get_formatted_data(mock_obj) + result = self.transfer._get_formatted_data(mock_obj) self.assertEqual( expected_result, @@ -242,13 +250,13 @@ def test_get_formatted_data( ) -class CreateReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Create Replica.""" +class CreateTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Create Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(CreateReplicaTestCase, self).setUp() - self.replica = replicas.CreateReplica( + super(CreateTransferTestCase, self).setUp() + self.transfer = transfers.CreateTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(cli_utils, 'add_args_for_json_option_to_parser') @@ -258,7 +266,7 @@ def test_get_parser( mock_get_parser, *_ ): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -269,7 +277,7 @@ def test_get_parser( @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @mock.patch.object(cli_utils, 'get_option_value_from_args') - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, 'get_formatted_entity') def test_take_action( self, @@ -281,6 +289,7 @@ def test_take_action( args = mock.Mock() args.instances = mock.sentinel.instances args.notes = mock.sentinel.notes + args.scenario = mock.sentinel.scenario args.origin_minion_pool_id = mock.sentinel.origin_minion_pool_id args.destination_minion_pool_id = \ mock.sentinel.destination_minion_pool_id @@ -288,33 +297,36 @@ def test_take_action( {'instance_id': "instance_id1", 'pool_id': "pool_id1"}, {'instance_id': "instance_id2", 'pool_id': "pool_id2"} ] + args.clone_disks = True + args.skip_os_morphing = False mock_endpoints = mock.Mock() - mock_replicas = mock.Mock() + mock_transfers = mock.Mock() self.mock_app.client_manager.coriolis.endpoints = mock_endpoints mock_endpoints.get_endpoint_id_for_name.side_effect = [ mock.sentinel.origin_endpoint_id, mock.sentinel.destination_endpoint_id ] - self.mock_app.client_manager.coriolis.replicas.create = \ - mock_replicas + self.mock_app.client_manager.coriolis.transfers.create = \ + mock_transfers mock_get_option_value_from_args.side_effect = [ mock.sentinel.destination_environment, mock.sentinel.source_environment, mock.sentinel.network_map, ] - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replicas.assert_called_once_with( + mock_transfers.assert_called_once_with( mock.sentinel.origin_endpoint_id, mock.sentinel.destination_endpoint_id, mock.sentinel.source_environment, mock.sentinel.destination_environment, mock.sentinel.instances, + mock.sentinel.scenario, network_map=mock.sentinel.network_map, notes=mock.sentinel.notes, storage_mappings=(mock_get_storage_mappings_dict_from_args. @@ -324,24 +336,25 @@ def test_take_action( mock.sentinel.destination_minion_pool_id, instance_osmorphing_minion_pool_mappings={ 'instance_id1': 'pool_id1', 'instance_id2': 'pool_id2'}, - user_scripts=mock_compose_user_scripts.return_value + user_scripts=mock_compose_user_scripts.return_value, + clone_disks=True, skip_os_morphing=False, ) mock_get_formatted_entity.assert_called_once_with( - mock_replicas.return_value) + mock_transfers.return_value) -class ShowReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Show Replica.""" +class ShowTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Show Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(ShowReplicaTestCase, self).setUp() - self.replica = replicas.ShowReplica( + super(ShowTransferTestCase, self).setUp() + self.transfer = transfers.ShowTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -349,37 +362,37 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replicas.ReplicaDetailFormatter, + @mock.patch.object(transfers.TransferDetailFormatter, 'get_formatted_entity') def test_take_action(self, mock_get_formatted_entity): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.get = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.get = mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) mock_get_formatted_entity.assert_called_once_with( - mock_replica.return_value) + mock_transfer.return_value) -class DeleteReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica.""" +class DeleteTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaTestCase, self).setUp() - self.replica = replicas.DeleteReplica( + super(DeleteTransferTestCase, self).setUp() + self.transfer = transfers.DeleteTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -390,26 +403,26 @@ def test_get_parser(self, mock_get_parser): def test_take_action(self): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.delete = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.delete = mock_transfer - self.replica.take_action(args) + self.transfer.take_action(args) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) -class DeleteReplicaDisksTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Disks.""" +class DeleteTransferDisksTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Disks.""" def setUp(self): self.mock_app = mock.Mock() - super(DeleteReplicaDisksTestCase, self).setUp() - self.replica = replicas.DeleteReplicaDisks( + super(DeleteTransferDisksTestCase, self).setUp() + self.transfer = transfers.DeleteTransferDisks( self.mock_app, mock.sentinel.app_args) @mock.patch.object(command.Command, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -417,38 +430,38 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') def test_take_action(self, mock_get_formatted_entity): args = mock.Mock() args.id = mock.sentinel.id - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.delete_disks = \ - mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.delete_disks = \ + mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, result ) - mock_replica.assert_called_once_with(mock.sentinel.id) + mock_transfer.assert_called_once_with(mock.sentinel.id) mock_get_formatted_entity.assert_called_once_with( - mock_replica.return_value) + mock_transfer.return_value) -class ListReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client List Replica.""" +class ListTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client List Transfer.""" def setUp(self): self.mock_app = mock.Mock() - super(ListReplicaTestCase, self).setUp() - self.replica = replicas.ListReplica( + super(ListTransferTestCase, self).setUp() + self.transfer = transfers.ListTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(lister.Lister, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -456,33 +469,33 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replicas.ReplicaFormatter, 'list_objects') + @mock.patch.object(transfers.TransferFormatter, 'list_objects') def test_take_action(self, mock_list_objects): args = mock.Mock() - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas.list = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers.list = mock_transfer - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_list_objects.return_value, result ) - mock_list_objects.assert_called_once_with(mock_replica.return_value) + mock_list_objects.assert_called_once_with(mock_transfer.return_value) -class UpdateReplicaTestCase(test_base.CoriolisBaseTestCase): - """Test suite for the Coriolis Client Delete Replica Disks.""" +class UpdateTransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Delete Transfer Disks.""" def setUp(self): self.mock_app = mock.Mock() - super(UpdateReplicaTestCase, self).setUp() - self.replica = replicas.UpdateReplica( + super(UpdateTransferTestCase, self).setUp() + self.transfer = transfers.UpdateTransfer( self.mock_app, mock.sentinel.app_args) @mock.patch.object(show.ShowOne, 'get_parser') def test_get_parser(self, mock_get_parser): - result = self.replica.get_parser(mock.sentinel.prog_name) + result = self.transfer.get_parser(mock.sentinel.prog_name) self.assertEqual( mock_get_parser.return_value, @@ -490,7 +503,7 @@ def test_get_parser(self, mock_get_parser): ) mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) - @mock.patch.object(replica_executions.ReplicaExecutionDetailFormatter, + @mock.patch.object(transfer_executions.TransferExecutionDetailFormatter, 'get_formatted_entity') @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @@ -514,8 +527,10 @@ def test_take_action( {"instance_id": "mock_instance1", "pool_id": "mock_pool1"}, {"instance_id": "mock_instance2", "pool_id": "mock_pool2"} ] - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas = mock_replica + args.clone_disks = True + args.skip_os_morphing = False + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers = mock_transfer mock_get_option_value_from_args.side_effect = [ mock.sentinel.destination_environment, mock.sentinel.source_environment, @@ -535,10 +550,12 @@ def test_take_action( mock.sentinel.destination_minion_pool_id, "instance_osmorphing_minion_pool_mappings": {"mock_instance1": "mock_pool1", "mock_instance2": "mock_pool2"}, - "user_scripts": mock.sentinel.user_scripts + "user_scripts": mock.sentinel.user_scripts, + "clone_disks": True, + "skip_os_morphing": False, } - result = self.replica.take_action(args) + result = self.transfer.take_action(args) self.assertEqual( mock_get_formatted_entity.return_value, @@ -546,12 +563,12 @@ def test_take_action( ) mock_compose_user_scripts.assert_called_once_with( mock.sentinel.global_scripts, mock.sentinel.instance_scripts) - mock_replica.update.assert_called_once_with( + mock_transfer.update.assert_called_once_with( mock.sentinel.id, expected_updated_properties ) mock_get_formatted_entity.assert_called_once_with( - mock_replica.update.return_value) + mock_transfer.update.return_value) @mock.patch.object(cli_utils, 'compose_user_scripts') @mock.patch.object(cli_utils, 'get_storage_mappings_dict_from_args') @@ -568,18 +585,18 @@ def __getattr__(self, name): args = CustomMock() args.global_scripts = mock.sentinel.global_scripts args.instance_scripts = mock.sentinel.instance_scripts - mock_replica = mock.Mock() - self.mock_app.client_manager.coriolis.replicas = mock_replica + mock_transfer = mock.Mock() + self.mock_app.client_manager.coriolis.transfers = mock_transfer mock_get_option_value_from_args.return_value = None mock_get_storage_mappings_dict_from_args.return_value = None mock_compose_user_scripts.return_value = None self.assertRaises( ValueError, - self.replica.take_action, + self.transfer.take_action, args ) mock_compose_user_scripts.assert_called_once_with( mock.sentinel.global_scripts, mock.sentinel.instance_scripts) - mock_replica.update.assert_not_called() + mock_transfer.update.assert_not_called() diff --git a/coriolisclient/tests/cli/test_utils.py b/coriolisclient/tests/cli/test_utils.py new file mode 100644 index 0000000..61461fd --- /dev/null +++ b/coriolisclient/tests/cli/test_utils.py @@ -0,0 +1,325 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import argparse +import ddt +import os +from unittest import mock + +from coriolisclient.cli import utils +from coriolisclient.tests import test_base + + +@ddt.ddt +class UtilsTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Utils.""" + + def test_add_storage_mappings_arguments_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_storage_mappings_arguments_to_parser(parser) + args = parser.parse_args( + ['--storage-backend-mapping', 'mock_source=mock_destination', + '--disk-storage-mapping', 'mock_disk_id=mock_destination', + '--default-storage-backend', 'mock_default_storage_backend']) + + self.assertEqual( + [{'disk_id': 'mock_disk_id', 'destination': 'mock_destination'}], + args.disk_storage_mappings + ) + self.assertEqual( + [{'source': 'mock_source', 'destination': 'mock_destination'}], + args.storage_backend_mappings + ) + self.assertEqual( + 'mock_default_storage_backend', + args.default_storage_backend + ) + + def test_get_storage_mappings_dict_from_args(self): + args = mock.Mock() + args.default_storage_backend = mock.sentinel.default_storage_backend + args.disk_storage_mappings = mock.sentinel.disk_storage_mappings + args.storage_backend_mappings = mock.sentinel.storage_backend_mappings + + result = utils.get_storage_mappings_dict_from_args(args) + + self.assertEqual( + { + "backend_mappings": mock.sentinel.storage_backend_mappings, + "default": mock.sentinel.default_storage_backend, + "disk_mappings": mock.sentinel.disk_storage_mappings + }, + result + ) + + def test_format_mapping(self): + mapping = { + "mapping1": "mock_mapping1", + "mapping2": "mock_mapping2", + "mapping3": "mock_mapping3" + } + + result = utils.format_mapping(mapping) + + self.assertEqual( + ( + "'mapping1'='mock_mapping1', " + "'mapping2'='mock_mapping2', " + "'mapping3'='mock_mapping3'" + ), + result + ) + + def test_parse_storage_mappings(self): + storage_mappings = { + "default": "mock_default", + "backend_mappings": [ + { + "source": "mock_source", + "destination": "mock_destination" + } + ], + "disk_mappings": [ + { + "disk_id": "mock_disk_id", + "destination": "mock_destination" + } + ] + } + + result = utils.parse_storage_mappings(storage_mappings) + + self.assertEqual( + ( + 'mock_default', + {'mock_source': 'mock_destination'}, + {'mock_disk_id': 'mock_destination'} + ), + result + ) + + def test_parse_storage_mappings_none(self): + result = utils.parse_storage_mappings(None) + + self.assertEqual( + (None, {}, {}), + result + ) + + def test_format_json_for_object_property(self): + obj = mock.Mock() + obj.prop_name = {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_to_dict(self): + obj = mock.Mock() + obj.prop_name.to_dict.return_value = \ + {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_none(self): + obj = mock.Mock() + obj.prop_name = None + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{}', + result + ) + + def test_validate_uuid_string(self): + result = utils.validate_uuid_string( + "12345678-9ABC-DEF1-2345-6789abcdef12") + + self.assertEqual( + True, + result + ) + + result = utils.validate_uuid_string( + "123456789ABCDEF") + + self.assertEqual( + False, + result + ) + + @mock.patch.object(argparse, 'FileType') + def test_add_args_for_json_option_to_parser(self, mock_file_type): + parser = argparse.ArgumentParser() + + utils.add_args_for_json_option_to_parser( + parser, "option_name") + + args = parser.parse_args( + ['--option-name', 'mock_option']) + + args_file = parser.parse_args( + ['--option-name-file', 'mock_option_file']) + + self.assertEqual( + ('mock_option', mock_file_type.return_value.return_value), + (args.option_name, args_file.option_name_file) + ) + + def test_get_option_value_from_args(self): + args = mock.MagicMock() + args.option_name = '{"option": "raw_value"}' + args.option_name_file.__enter__.return_value.read.return_value = \ + '{"option": "file_value"}' + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'raw_value'}, + result + ) + + args.option_name = None + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'file_value'}, + result + ) + + def test_get_option_value_from_args_no_value(self): + args = mock.Mock() + args.option_name = None + args.option_name_file = None + + result = utils.get_option_value_from_args( + args, "option-name", error_on_no_value=False) + + self.assertEqual( + None, + result + ) + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + def test_get_option_value_from_args_json_value_error(self): + args = mock.Mock() + args.option_name = "invalid" + args.option_name_file = None + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + @ddt.data( + { + "global_scripts": None, + "instance_scripts": None, + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["linux="], + "instance_scripts": ["instance_1="], + "expected_result": + { + "global": {"linux": None}, + "instances": {"instance_1": None} + } + }, + { + "global_scripts": ["linux script"], + "instance_scripts": ["linux script"], + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["invalid_os=scrips"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": ["linux='invalid/file/path'"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": None, + "instance_scripts": ["linux='invalid/file/path'"], + "expected_result": None + }, + ) + def test_compose_user_scripts(self, data): + global_scripts = data["global_scripts"] + instance_scripts = data["instance_scripts"] + expected_result = data["expected_result"] + + if expected_result: + result = utils.compose_user_scripts( + global_scripts, instance_scripts) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + ValueError, + utils.compose_user_scripts, + global_scripts, + instance_scripts + ) + + def test_compose_user_scripts_from_file(self): + script_path = os.path.dirname(os.path.realpath(__file__)) + script_path = os.path.join(script_path, 'data/user_scripts.yml') + global_scripts = ["linux=%s" % script_path] + instance_scripts = ["linux=%s" % script_path] + + result = utils.compose_user_scripts(global_scripts, instance_scripts) + + self.assertEqual( + { + 'global': {'linux': '"mock_script1"\n"mock_script2"\n'}, + 'instances': {'linux': '"mock_script1"\n"mock_script2"\n'} + }, + result + ) + + def test_add_minion_pool_args_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_minion_pool_args_to_parser(parser) + args = parser.parse_args( + ['--osmorphing-minion-pool-mapping', + 'mock_instance_id=mock_pool_id']) + + self.assertEqual( + [{'instance_id': 'mock_instance_id', 'pool_id': 'mock_pool_id'}], + args.instance_osmorphing_minion_pool_mappings + ) diff --git a/coriolisclient/tests/test_base.py b/coriolisclient/tests/test_base.py index af448cc..dd6dd2f 100644 --- a/coriolisclient/tests/test_base.py +++ b/coriolisclient/tests/test_base.py @@ -3,10 +3,486 @@ """Defines base class for all tests.""" -from oslotest import base +from keystoneauth1 import exceptions as keystoneauth_exceptions +from oslotest import base as test_base +from oslotest import mock_fixture +from unittest import mock +from coriolisclient import base +from coriolisclient import exceptions +from coriolisclient.tests import testutils -class CoriolisBaseTestCase(base.BaseTestCase): +# NOTE(claudiub): this needs to be called before any mock.patch calls are +# being done, and especially before any other test classes load. This fixes +# the mock.patch autospec issue: +# https://github.com/testing-cabal/mock/issues/396 +mock_fixture.patch_mock_module() + + +class CoriolisBaseTestCase(test_base.BaseTestCase): def setUp(self): super(CoriolisBaseTestCase, self).setUp() + + +class BaseTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Base.""" + + def test_getid_uuid(self): + obj = mock.Mock() + + result = base.getid(obj) + + self.assertEqual( + obj.uuid, + result + ) + + def test_getid_id(self): + obj = mock.Mock() + + result = base.getid(obj, possible_fields=["id"]) + + self.assertEqual( + obj.id, + result + ) + + def test_getid_id_no_fields(self): + obj = mock.Mock() + + result = base.getid(obj, possible_fields=None) + + self.assertEqual( + obj.id, + result + ) + + def test_getid_none(self): + obj = "mock_obj" + + result = base.getid(obj, possible_fields=None) + + self.assertEqual( + obj, + result + ) + + def test_wrap_unauthorized_exception(self): + + @base.wrap_unauthorized_exception + def mock_fun(func): + return func() + + func = mock.Mock() + result = mock_fun(func) + + self.assertEqual( + func.return_value, + result + ) + + def test_wrap_unauthorized_exception_http_unauthorized(self): + + @base.wrap_unauthorized_exception + def mock_fun(func): + return func() + + func = mock.Mock() + func.side_effect = keystoneauth_exceptions.http.Unauthorized() + + with self.assertLogs(logger=base.LOG, level="ERROR"): + self.assertRaises( + exceptions.HTTPAuthError, + mock_fun, + func + ) + + +class ResourceTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Resource.""" + + def setUp(self): + mock_manager = mock.Mock() + super(ResourceTestCase, self).setUp() + self.resource = base.Resource( + mock_manager, + {"info": mock.sentinel.info} + ) + + def test__repr__(self): + result = repr(self.resource) + self.assertEqual( + "" % mock.sentinel.info, + result + ) + + def test_human_id(self): + self.assertEqual( + None, + self.resource.human_id + ) + + class HumanResource(base.Resource): + HUMAN_ID = True + + mock_manager = mock.Mock() + resource = HumanResource( + mock_manager, + {"name": "mock_name"} + ) + + self.assertEqual( + "mock_name", + resource.human_id + ) + + def test_add_details(self): + new_info = {"new_info": mock.sentinel.new_info} + + self.resource._add_details(new_info) + + self.assertEqual( + mock.sentinel.new_info, + self.resource.new_info + ) + + def test_add_details_attribute_already_defined(self): + info = {"human_id": mock.sentinel.human_id} + + self.resource._add_details(info) + + self.assertEqual( + None, + self.resource._info.get("human_id") + ) + + @mock.patch.object(base.Resource, "is_loaded") + @mock.patch.object(base.Resource, "get") + def test__getattr__(self, mock_get, mock_is_loaded): + mock_is_loaded.side_effect = [False, True] + mock_get.side_effect = lambda: setattr(self.resource, "attr", "value") + + self.assertEqual( + "value", + self.resource.attr + ) + + @mock.patch.object(base.Resource, "is_loaded") + @mock.patch.object(base.Resource, "get") + def test__getattr__error(self, mock_get, mock_is_loaded): + mock_is_loaded.side_effect = [False, True] + + self.assertRaises( + AttributeError, + self.resource.__getattr__, + "attr" + ) + mock_get.assert_called_once() + + @mock.patch.object(base.Resource, "_add_details") + def test_get(self, mock_add_details): + self.resource._loaded = False + self.resource.id = mock.sentinel.id + + self.resource.get() + + mock_add_details.assert_called_once_with( + self.resource.manager.get.return_value._info) + self.assertEqual(True, self.resource._loaded) + + @mock.patch.object(base.Resource, "_add_details") + def test_get_none(self, mock_add_details): + self.resource._loaded = False + self.resource.id = mock.sentinel.id + delattr(self.resource.manager, "get") + + self.resource.get() + + mock_add_details.assert_not_called() + + def test__eq__(self): + mock_manager = mock.Mock() + resource2 = base.Resource( + mock_manager, + {"info": mock.sentinel.info} + ) + + self.assertEqual( + ( + True, + NotImplemented, + ), + ( + self.resource.__eq__(resource2), + self.resource.__eq__("string"), + ) + ) + + resource2._info = {"info": mock.sentinel.info2} + + self.assertEqual( + False, + self.resource.__eq__(resource2) + ) + + def test__eq__different_subclasses(self): + mock_manager = mock.Mock() + + class SubResource1(base.Resource): + pass + + class SubResource2(base.Resource): + pass + + resource1 = SubResource1( + mock_manager, + {"info": mock.sentinel.info} + ) + resource2 = SubResource2( + mock_manager, + {"info": mock.sentinel.info} + ) + + self.assertEqual( + False, + resource1.__eq__(resource2) + ) + + def test_is_loaded(self): + self.resource._loaded = True + self.assertEqual(True, self.resource.is_loaded()) + self.resource._loaded = False + self.assertEqual(False, self.resource.is_loaded()) + + def test_set_loaded(self): + self.resource.set_loaded(True) + self.assertEqual(True, self.resource._loaded) + self.resource.set_loaded(False) + self.assertEqual(False, self.resource._loaded) + + def test_to_dict(self): + result = self.resource.to_dict() + + self.assertEqual( + {'info': mock.sentinel.info}, + result + ) + + +class BaseManagerTestCase(CoriolisBaseTestCase): + """Test suite for the Coriolis Client Base Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(BaseManagerTestCase, self).setUp() + self.manager = base.BaseManager(mock_client) + + def test_list(self): + self.manager.client.get(mock.sentinel.url).json.return_value = { + "mock_response_key": { + "data": [mock.sentinel.data1, mock.sentinel.data2]} + } + obj_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._list)( + self.manager, + url=mock.sentinel.url, + response_key="mock_response_key", + obj_class=obj_class, + json=None, + values_key="data" + ) + + self.assertEqual( + [obj_class.return_value] * 2, + result + ) + obj_class.assert_has_calls([ + mock.call(self.manager, mock.sentinel.data1, loaded=True), + mock.call(self.manager, mock.sentinel.data2, loaded=True) + ]) + + def test_list_json(self): + (self.manager.client.post(mock.sentinel.url, json=True).json. + return_value) = [mock.sentinel.data] + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._list)( + self.manager, + url=mock.sentinel.url, + response_key=None, + obj_class=None, + json=True, + values_key=None + ) + + self.assertEqual( + [self.manager.resource_class.return_value], + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_get(self): + self.manager.client.get().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._get)( + self.manager, + url=mock.sentinel.url, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_get_no_response_key(self): + self.manager.client.get().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._get)( + self.manager, + url=mock.sentinel.url, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data, loaded=True) + + def test_post(self): + self.manager.client.post().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._post)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key", + return_raw=False + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_post_no_response_key(self): + self.manager.client.post().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._post)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None, + return_raw=True + ) + + self.assertEqual( + mock.sentinel.data, + result + ) + self.manager.resource_class.assert_not_called() + + def test_put(self): + self.manager.client.put().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._put)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_put_no_response_key(self): + self.manager.client.put().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._put)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_patch(self): + self.manager.client.patch().json.return_value = { + "mock_response_key": mock.sentinel.data + } + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._patch)( + self.manager, + url=mock.sentinel.url, + json=True, + response_key="mock_response_key" + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_patch_no_response_key(self): + self.manager.client.patch().json.return_value = mock.sentinel.data + self.manager.resource_class = mock.Mock() + + result = testutils.get_wrapped_function(self.manager._patch)( + self.manager, + url=mock.sentinel.url, + json=False, + response_key=None + ) + + self.assertEqual( + self.manager.resource_class.return_value, + result + ) + self.manager.resource_class.assert_called_once_with( + self.manager, mock.sentinel.data) + + def test_delete(self): + result = testutils.get_wrapped_function(self.manager._delete)( + self.manager, + url=mock.sentinel.url + ) + + self.assertEqual( + self.manager.client.delete.return_value, + result + ) + self.manager.client.delete.assert_called_once_with(mock.sentinel.url) diff --git a/coriolisclient/tests/test_client.py b/coriolisclient/tests/test_client.py new file mode 100644 index 0000000..f4fdf41 --- /dev/null +++ b/coriolisclient/tests/test_client.py @@ -0,0 +1,40 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient import client as coriolis_client +from coriolisclient.tests import test_base + + +class _HTTPClientTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis HTTP Client.""" + + def test__init__(self): + session = mock.Mock() + self.client = coriolis_client._HTTPClient( + session, endpoint=mock.sentinel.endpoint, version='v0') + self.assertEqual( + "%s/%s" % (mock.sentinel.endpoint, 'v0'), + self.client.endpoint_override + ) + + def test__init__no_endpoint(self): + session = mock.Mock() + self.client = coriolis_client._HTTPClient(session) + self.assertEqual( + None, + self.client.endpoint_override + ) + + +class ClientTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client.""" + + @mock.patch.object(coriolis_client, "_HTTPClient") + def test__init__(self, mock_HTTPClient): + try: + self.client = coriolis_client.Client(session=mock.sentinel.session) + except Exception: + self.fail("Failed to initialize Client") + mock_HTTPClient.assert_called_once_with(session=mock.sentinel.session) diff --git a/coriolisclient/tests/testutils.py b/coriolisclient/tests/testutils.py new file mode 100644 index 0000000..418a7b9 --- /dev/null +++ b/coriolisclient/tests/testutils.py @@ -0,0 +1,24 @@ +"""Defines general utilities for all tests.""" + + +def get_wrapped_function(function): + """Get the method at the bottom of a stack of decorators.""" + if not hasattr(function, '__closure__') or not function.__closure__: + return function + + def _get_wrapped_function(function): + if not hasattr(function, '__closure__') or not function.__closure__: + return None + + for closure in function.__closure__: + func = closure.cell_contents + + deeper_func = _get_wrapped_function(func) + if deeper_func: + return deeper_func + elif hasattr(closure.cell_contents, '__call__'): + return closure.cell_contents + + return function + + return _get_wrapped_function(function) diff --git a/coriolisclient/tests/v1/__init__.py b/coriolisclient/tests/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/coriolisclient/tests/v1/test_common.py b/coriolisclient/tests/v1/test_common.py new file mode 100644 index 0000000..0f0086c --- /dev/null +++ b/coriolisclient/tests/v1/test_common.py @@ -0,0 +1,65 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import common + + +class TaskTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Task.""" + + def setUp(self): + super(TaskTestCase, self).setUp() + self.task = common.Task( + None, + { + "progress_updates": [ + {"progress_update1": "mock_update1"}, + {"progress_update2": "mock_update2"} + ] + }, + loaded=False + ) + + def test_progress_updates(self): + result = self.task.progress_updates + + self.assertEqual( + ("mock_update1", "mock_update2"), + (result[0].progress_update1, result[1].progress_update2) + ) + + +@ddt.ddt +class CommonTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Common.""" + + @ddt.data( + ("value", False, "dmFsdWU="), + ({"key": "value"}, True, "eyJrZXkiOiAidmFsdWUifQ==") + ) + @ddt.unpack + def test_encode_base64_param(self, param, is_json, expected_result): + result = common.encode_base64_param(param, is_json=is_json) + + self.assertEqual( + result, + expected_result + ) + + @ddt.data( + (12345, False), + (None, False), + ({"key value"}, True) + ) + @ddt.unpack + def test_encode_base64_param_raises(self, param, is_json): + self.assertRaises( + exceptions.CoriolisException, + common.encode_base64_param, + param, + is_json=is_json + ) diff --git a/coriolisclient/tests/v1/test_deployments.py b/coriolisclient/tests/v1/test_deployments.py new file mode 100644 index 0000000..f92fe9a --- /dev/null +++ b/coriolisclient/tests/v1/test_deployments.py @@ -0,0 +1,124 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# 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 unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import deployments + +DEPLOYMENT_ID = "1" + + +class DeploymentResourceTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentResourceTestCase, self).setUp() + self.source_env = {"source_opt": "env_value"} + self.dest_env = {"dest_opt": "env_value"} + self.transfer_result = {"result": "value"} + self.task = {"task": "type_1"} + self.info = { + "source_environment": self.source_env, + "destination_environment": self.dest_env, + "transfer_result": self.transfer_result, + "tasks": [self.task], + } + self.deployment = deployments.Deployment(None, self.info) + + def test_source_environment(self): + result = self.deployment.source_environment + + self.assertIsInstance(result, common.SourceEnvironment) + self.assertEqual(self.source_env, result._info) + + def test_destination_environment(self): + result = self.deployment.destination_environment + + self.assertIsInstance(result, common.DestinationEnvironment) + self.assertEqual(self.dest_env, result._info) + + def test_transfer_result(self): + result = self.deployment.transfer_result + + self.assertIsInstance(result, common.TransferResult) + self.assertEqual(self.transfer_result, result._info) + + def test_task(self): + result = self.deployment.tasks + + [self.assertIsInstance(t, common.Task) for t in result] + self.assertEqual(self.task, result[0]._info) + + +class DeploymentManagerTestCase(test_base.CoriolisBaseTestCase): + + def setUp(self): + super(DeploymentManagerTestCase, self).setUp() + mock_client = mock.Mock() + self.deployments = deployments.DeploymentManager(mock_client) + + def test_list(self): + with mock.patch.object(self.deployments, '_list') as mock_list: + result = self.deployments.list(detail=True) + self.assertEqual(mock_list.return_value, result) + mock_list.assert_called_once_with( + '/deployments/detail', 'deployments') + + def test_get(self): + deployment = mock.Mock(uuid=DEPLOYMENT_ID) + with mock.patch.object(self.deployments, '_get') as mock_get: + result = self.deployments.get(deployment) + self.assertEqual(mock_get.return_value, result) + mock_get.assert_called_once_with( + f'/deployments/{DEPLOYMENT_ID}', 'deployment') + + def test_create_from_transfer(self): + with mock.patch.object(self.deployments, '_post') as mock_post: + expected_data = { + "deployment": { + "transfer_id": mock.sentinel.transfer_id, + "clone_disks": True, + "force": False, + "skip_os_morphing": False, + "user_scripts": None, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.pool_mappings, + } + } + result = self.deployments.create_from_transfer( + mock.sentinel.transfer_id, clone_disks=True, force=False, + skip_os_morphing=False, user_scripts=None, + instance_osmorphing_minion_pool_mappings=( + mock.sentinel.pool_mappings)) + self.assertEqual(mock_post.return_value, result) + mock_post.assert_called_once_with( + "/deployments", expected_data, "deployment") + + def test_delete(self): + with mock.patch.object(self.deployments, '_delete') as mock_delete: + result = self.deployments.delete(DEPLOYMENT_ID) + self.assertEqual(mock_delete.return_value, result) + mock_delete.assert_called_once_with( + f'/deployments/{DEPLOYMENT_ID}') + + def test_cancel(self): + force = False + expected_data = {"cancel": {"force": force}} + with mock.patch.object(self.deployments.client, 'post') as mock_post: + result = self.deployments.cancel(DEPLOYMENT_ID, force=force) + self.assertEqual(mock_post.return_value, result) + mock_post.assert_called_once_with( + f"/deployments/{DEPLOYMENT_ID}/actions", json=expected_data) diff --git a/coriolisclient/tests/v1/test_diagnostics.py b/coriolisclient/tests/v1/test_diagnostics.py new file mode 100644 index 0000000..fdb7337 --- /dev/null +++ b/coriolisclient/tests/v1/test_diagnostics.py @@ -0,0 +1,26 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import diagnostics + + +class DiagnosticsManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Diagnostics Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(DiagnosticsManagerTestCase, self).setUp() + self.diag = diagnostics.DiagnosticsManager(mock_client) + + @mock.patch.object(diagnostics.DiagnosticsManager, '_list') + def test_get(self, mock_list): + result = self.diag.get() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with('/diagnostics', 'diagnostics') diff --git a/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py b/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py new file mode 100644 index 0000000..773a50d --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_destination_minion_pool_options.py @@ -0,0 +1,49 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_destination_minion_pool_options + + +class EndpointDestinationMinionPoolOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """ + Test suite for the Coriolis v1 Endpoint Destination Minion Pool Options + Manager. + """ + + def setUp(self): + mock_client = mock.Mock() + super(EndpointDestinationMinionPoolOptionsManagerTestCase, self + ).setUp() + self.endpoint = ( + endpoint_destination_minion_pool_options. + EndpointDestinationMinionPoolOptionsManager)(mock_client) + + @mock.patch.object(endpoint_destination_minion_pool_options. + EndpointDestinationMinionPoolOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'destination-minion-pool-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'destination_minion_pool_options') diff --git a/coriolisclient/tests/v1/test_endpoint_destination_options.py b/coriolisclient/tests/v1/test_endpoint_destination_options.py new file mode 100644 index 0000000..4cceb1c --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_destination_options.py @@ -0,0 +1,45 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_destination_options + + +class EndpointDestinationOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Destination Options Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointDestinationOptionsManagerTestCase, self).setUp() + self.endpoint = ( + endpoint_destination_options. + EndpointDestinationOptionsManager)(mock_client) + + @mock.patch.object(endpoint_destination_options. + EndpointDestinationOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'destination-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'destination_options') diff --git a/coriolisclient/tests/v1/test_endpoint_instances.py b/coriolisclient/tests/v1/test_endpoint_instances.py new file mode 100644 index 0000000..0b7b3c6 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_instances.py @@ -0,0 +1,115 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import common +from coriolisclient.v1 import endpoint_instances + + +class EndpointInstanceTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Instance.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointInstanceTestCase, self).setUp() + self.endpoint = endpoint_instances.EndpointInstance( + mock_client, + {"flavor_name": mock.sentinel.flavor_name}) + + def test_flavor_name(self): + result = self.endpoint.flavor_name + + self.assertEqual( + mock.sentinel.flavor_name, + result + ) + + +class EndpointInstanceManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Instance Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointInstanceManagerTestCase, self).setUp() + self.endpoint = endpoint_instances.EndpointInstanceManager(mock_client) + + @mock.patch.object(endpoint_instances.EndpointInstanceManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + env={"env": "mock_env"}, + marker="mock_marker", + limit="mock_limit", + name="mock_name" + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/instances' + '?marker=mock_marker&limit=mock_limit&name=mock_name&' + 'env=eyJlbnYiOiAibW9ja19lbnYifQ%3D%3D'), + 'instances') + + @mock.patch.object(common, 'encode_base64_param') + def test_list_value_error( + self, + mock_encode_base64_param + ): + mock_endpoint = mock.Mock() + mock_encode_base64_param.return_value = mock.sentinel.encoded_env + + self.assertRaises( + ValueError, + self.endpoint.list, + mock_endpoint, + env=mock.sentinel.env + ) + + mock_encode_base64_param.assert_not_called() + + @mock.patch.object(endpoint_instances.EndpointInstanceManager, '_get') + def test_get( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.get( + mock_endpoint, + "mock_id", + env={"env": "mock_env"}, + ) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/instances/' + 'bW9ja19pZA==?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'instance') + + def test_get_value_error(self): + mock_endpoint = mock.Mock() + + self.assertRaises( + ValueError, + self.endpoint.get, + mock_endpoint, + "mock_instance_id", + env=mock.sentinel.env + ) diff --git a/coriolisclient/tests/v1/test_endpoint_networks.py b/coriolisclient/tests/v1/test_endpoint_networks.py new file mode 100644 index 0000000..febe4b4 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_networks.py @@ -0,0 +1,39 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_networks + + +class EndpointNetworkManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Network Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointNetworkManagerTestCase, self).setUp() + self.endpoint = endpoint_networks.EndpointNetworkManager(mock_client) + + @mock.patch.object(endpoint_networks.EndpointNetworkManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/networks' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'networks') diff --git a/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py b/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py new file mode 100644 index 0000000..6fc7e9e --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_source_minion_pool_options.py @@ -0,0 +1,49 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_source_minion_pool_options + + +class EndpointSourceMinionPoolOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """ + Test suite for the Coriolis v1 Endpoint Source Minion Pool Options + Manager. + """ + + def setUp(self): + mock_client = mock.Mock() + super(EndpointSourceMinionPoolOptionsManagerTestCase, self + ).setUp() + self.endpoint = ( + endpoint_source_minion_pool_options. + EndpointSourceMinionPoolOptionsManager)(mock_client) + + @mock.patch.object(endpoint_source_minion_pool_options. + EndpointSourceMinionPoolOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/' + 'source-minion-pool-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'source_minion_pool_options') diff --git a/coriolisclient/tests/v1/test_endpoint_source_options.py b/coriolisclient/tests/v1/test_endpoint_source_options.py new file mode 100644 index 0000000..d2939a4 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_source_options.py @@ -0,0 +1,44 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_source_options + + +class EndpointSourceOptionsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Source Options Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointSourceOptionsManagerTestCase, self).setUp() + self.endpoint = ( + endpoint_source_options. + EndpointSourceOptionsManager)(mock_client) + + @mock.patch.object(endpoint_source_options. + EndpointSourceOptionsManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"}, + option_names=["option1", "option2"] + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/source-options' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ==' + '&options=WyJvcHRpb24xIiwgIm9wdGlvbjIiXQ=='), + 'source_options') diff --git a/coriolisclient/tests/v1/test_endpoint_storage.py b/coriolisclient/tests/v1/test_endpoint_storage.py new file mode 100644 index 0000000..23d7acc --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoint_storage.py @@ -0,0 +1,63 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoint_storage + + +class EndpointStorageManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Storage Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(EndpointStorageManagerTestCase, self).setUp() + self.endpoint = endpoint_storage.EndpointStorageManager(mock_client) + + @mock.patch.object(endpoint_storage.EndpointStorageManager, '_list') + def test_list( + self, + mock_list + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.list( + mock_endpoint, + environment={"env": "mock_env"} + ) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/storage' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'storage', + values_key='storage_backends') + + @mock.patch.object(endpoint_storage.EndpointStorageManager, '_get') + def test_get_default( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + mock_get.return_value = {"config_default": "mock_default"} + + result = self.endpoint.get_default( + mock_endpoint, + environment={"env": "mock_env"}, + ) + + self.assertEqual( + "mock_default", + result + ) + mock_get.assert_called_once_with( + ('/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/storage' + '?env=eyJlbnYiOiAibW9ja19lbnYifQ=='), + 'storage') diff --git a/coriolisclient/tests/v1/test_endpoints.py b/coriolisclient/tests/v1/test_endpoints.py new file mode 100644 index 0000000..1e3e058 --- /dev/null +++ b/coriolisclient/tests/v1/test_endpoints.py @@ -0,0 +1,262 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import endpoints + + +class EndpointTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint.""" + + def setUp(self): + super(EndpointTestCase, self).setUp() + self.endpoint = endpoints.Endpoint( + None, + { + "connection_info": { + "connection_info1": mock.sentinel.connection_info} + } + ) + + def test_connection_info(self): + result = self.endpoint.connection_info + + self.assertEqual( + mock.sentinel.connection_info, + result.connection_info1 + ) + + +class EndpointManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Endpoint Manager.""" + + def setUp(self): + self.mock_client = mock.Mock() + super(EndpointManagerTestCase, self).setUp() + self.endpoint = endpoints.EndpointManager(self.mock_client) + + @mock.patch.object(endpoints.EndpointManager, '_list') + def test_list( + self, + mock_list + ): + result = self.endpoint.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with('/endpoints', 'endpoints') + + @mock.patch.object(endpoints.EndpointManager, '_get') + def test_get( + self, + mock_get + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.get(mock_endpoint) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb', 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_post') + def test_create( + self, + mock_post + ): + result = self.endpoint.create( + mock.sentinel.name, + mock.sentinel.endpoint_type, + mock.sentinel.connection_info, + mock.sentinel.description, + mock.sentinel.regions + ) + expected_data = { + "endpoint": { + "name": mock.sentinel.name, + "type": mock.sentinel.endpoint_type, + "description": mock.sentinel.description, + "connection_info": mock.sentinel.connection_info, + "mapped_regions": mock.sentinel.regions + } + } + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/endpoints', expected_data, 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_put') + def test_update( + self, + mock_put + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.update( + mock_endpoint, + mock.sentinel.updated_values + ) + expected_data = { + "endpoint": mock.sentinel.updated_values + } + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb', + expected_data, 'endpoint') + + @mock.patch.object(endpoints.EndpointManager, '_delete') + def test_delete( + self, + mock_delete + ): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + + result = self.endpoint.delete(mock_endpoint) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb') + + def test_validate_connection(self): + mock_endpoint = mock.Mock() + mock_endpoint.uuid = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + self.mock_client.post.return_value.json.return_value = { + "validate-connection": { + "valid": mock.sentinel.valid, + "message": mock.sentinel.message + } + } + + result = self.endpoint.validate_connection(mock_endpoint) + + self.assertEqual( + (mock.sentinel.valid, mock.sentinel.message), + result + ) + self.mock_client.post.assert_called_once_with( + '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/actions', + json={'validate-connection': None}) + + @mock.patch.object(endpoints.EndpointManager, '_get_endpoint_id_for_name') + def test_get_endpoint_id_for_name_uuid( + self, + mock_get_endpoint_id_for_name + ): + mock_endpoint = '53773ab8-1474-4cf7-bf0c-a496a6595ecb' + result = self.endpoint.get_endpoint_id_for_name(mock_endpoint) + + self.assertEqual( + '53773ab8-1474-4cf7-bf0c-a496a6595ecb', + result + ) + mock_get_endpoint_id_for_name.assert_not_called() + + @mock.patch.object(endpoints.EndpointManager, '_get_endpoint_id_for_name') + def test_get_endpoint_id_for_name( + self, + mock_get_endpoint_id_for_name + ): + mock_endpoint = mock.Mock() + + result = self.endpoint.get_endpoint_id_for_name(mock_endpoint) + + self.assertEqual( + mock_get_endpoint_id_for_name.return_value, + result + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name3' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name2" + + result = self.endpoint._get_endpoint_id_for_name(endpoint_name) + + self.assertEqual( + '2', + result + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name_not_found( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name3' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name4" + + self.assertRaises( + exceptions.EndpointIDNotFound, + self.endpoint._get_endpoint_id_for_name, + endpoint_name + ) + + @mock.patch.object(endpoints.EndpointManager, 'list') + def test__get_endpoint_id_for_name_not_unique( + self, + mock_list + ): + obj1 = mock.Mock() + obj2 = mock.Mock() + obj3 = mock.Mock() + obj1.id = '1' + obj2.id = '2' + obj3.id = '3' + obj1.name = 'mock_name1' + obj2.name = 'mock_name2' + obj3.name = 'mock_name2' + obj_list = [obj1, obj2, obj3] + mock_list.return_value = obj_list + endpoint_name = "mock_name2" + + self.assertRaises( + exceptions.NoUniqueEndpointNameMatch, + self.endpoint._get_endpoint_id_for_name, + endpoint_name + ) diff --git a/coriolisclient/tests/v1/test_licensing.py b/coriolisclient/tests/v1/test_licensing.py new file mode 100644 index 0000000..c8b4de8 --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing.py @@ -0,0 +1,371 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +import requests + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing + + +class LicensingClientTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Client.""" + + def setUp(self): + mock_client = mock.Mock() + super(LicensingClientTestCase, self).setUp() + self.licence = licensing.LicensingClient( + mock_client, "endpoint_name") + mock_client.verify = True + self.licence._cli = mock_client + + def test_get_licensing_endpoint_url(self): + self.licence._cli.get_endpoint.return_value = "url/endpoint_url/" + + result = self.licence._get_licensing_endpoint_url() + + self.assertEqual( + "url/endpoint_url", + result + ) + self.licence._cli.get_endpoint.assert_called_once_with( + service_type="endpoint_name") + + def test_get_licensing_endpoint_url_raises(self): + self.licence._cli.get_endpoint.side_effect = Exception() + + with self.assertLogs(level="WARN"): + self.assertRaises( + exceptions.LicensingEndpointNotFound, + self.licence._get_licensing_endpoint_url + ) + self.licence._cli.get_endpoint.assert_called_once_with( + service_type="endpoint_name") + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_raw( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = True + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + result = self.licence._do_req( + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key=None, + raw_response=True + ) + + self.assertEqual( + mock_resp, + result + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_json( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = True + mock_resp.json.return_value = {"response_key": mock.sentinel.data} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + result = self.licence._do_req( + method_name="mock_method", + resource='url/resource_url/', + body={"mock_body": "value"}, + response_key='response_key', + raw_response=False + ) + + self.assertEqual( + mock.sentinel.data, + result + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify, + data='{"mock_body": "value"}' + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.side_effect = Exception + mock_resp.raise_for_status.side_effect = exceptions.CoriolisException + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + with self.assertLogs(level="DEBUG"): + self.assertRaises( + exceptions.CoriolisException, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='response_key', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_http_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.return_value = {"error": {"code": 123, "message": ""}} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + self.assertRaises( + exceptions.HTTPError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='response_key', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + @mock.patch.object(licensing.LicensingClient, + "_get_licensing_endpoint_url") + def test_do_req_response_key_error( + self, + mock_get_licensing_endpoint_url + ): + mock_method = mock.Mock() + mock_resp = mock.Mock() + mock_resp.ok = False + mock_resp.json.return_value = {"response_key": mock.sentinel.data} + mock_method.return_value = mock_resp + setattr(requests, "mock_method", mock_method) + mock_get_licensing_endpoint_url.return_value = 'url/endpoint_url/' + + self.assertRaises( + ValueError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='invalid', + raw_response=False + ) + mock_method.assert_called_once_with( + 'url/endpoint_url/url/resource_url/', + verify=self.licence._cli.verify + ) + + def test_do_req_method_error(self): + setattr(requests, "mock_method", None) + + self.assertRaises( + ValueError, + self.licence._do_req, + method_name="mock_method", + resource='url/resource_url/', + body=None, + response_key='invalid', + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_get(self, mock_do_req): + result = self.licence.get( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'GET', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_post(self, mock_do_req): + result = self.licence.post( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'POST', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + @mock.patch.object(licensing.LicensingClient, '_do_req') + def test_delete(self, mock_do_req): + result = self.licence.delete( + resource=mock.sentinel.resource, + body=mock.sentinel.body, + response_key=mock.sentinel.response_key, + raw_response=False + ) + self.assertEqual( + mock_do_req.return_value, + result + ) + mock_do_req.assert_called_once_with( + 'DELETE', + mock.sentinel.resource, + response_key=mock.sentinel.response_key, + body=mock.sentinel.body, + raw_response=False + ) + + +class LicensingManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Client.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + super(LicensingManagerTestCase, self).setUp() + self.licence = licensing.LicensingManager(mock_client) + self.licence._licensing_cli = mock_LicensingClient + + def test_status(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.status(mock.sentinel.appliance_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/status' % mock.sentinel.appliance_id, + response_key='appliance_licence_status') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.get.return_value, + loaded=True) + + def test_list(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + self.licence._licensing_cli.get.return_value = { + "licence1": "mock_licence1", + "licence2": "mock_licence2" + } + + result = self.licence.list(mock.sentinel.appliance_id) + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/licences' % mock.sentinel.appliance_id, + response_key='licences') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "licence1", loaded=True), + mock.call(self.licence, "licence2", loaded=True) + ]) + + def test_register(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.register( + mock.sentinel.appliance_id, mock.sentinel.licence) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.post.assert_called_once_with( + '/appliances/%s/licences' % mock.sentinel.appliance_id, + body=mock.sentinel.licence, + response_key='licence') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.post.return_value, + loaded=True) + + def test_show(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show( + mock.sentinel.appliance_id, mock.sentinel.licence_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.licence._licensing_cli.get.assert_called_once_with( + '/appliances/%s/licences/%s' % (mock.sentinel.appliance_id, + mock.sentinel.licence_id), + response_key='licence') + mock_resource_class.assert_called_once_with( + self.licence, self.licence._licensing_cli.get.return_value, + loaded=True) + + def test_delete(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.delete( + mock.sentinel.appliance_id, mock.sentinel.licence_id) + + self.assertEqual( + self.licence._licensing_cli.delete.return_value, + result + ) + self.licence._licensing_cli.delete.assert_called_once_with( + '/appliances/%s/licences/%s' % (mock.sentinel.appliance_id, + mock.sentinel.licence_id), + raw_response=True) diff --git a/coriolisclient/tests/v1/test_licensing_appliances.py b/coriolisclient/tests/v1/test_licensing_appliances.py new file mode 100644 index 0000000..394defe --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_appliances.py @@ -0,0 +1,79 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_appliances + + +class LicensingAppliancesManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Appliances Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingAppliancesManagerTestCase, self).setUp() + self.licence = licensing_appliances.LicensingAppliancesManager( + mock_client) + + def test_list(self): + self.licence._licensing_cli.get.return_value = { + "appliance1": "mock_appliance1", + "appliance2": "mock_appliance2" + } + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.list() + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances', response_key='appliances') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "appliance1", loaded=True), + mock.call(self.licence, "appliance2", loaded=True) + ]) + + def test_show(self): + self.licence._licensing_cli.get.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show(mock.sentinel.appliance_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s' % mock.sentinel.appliance_id, + response_key='appliance') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) + + def test_create(self): + self.licence._licensing_cli.post.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.create() + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.post.assert_called_once_with( + '/appliances', response_key='appliance') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) diff --git a/coriolisclient/tests/v1/test_licensing_reservations.py b/coriolisclient/tests/v1/test_licensing_reservations.py new file mode 100644 index 0000000..bbf8cb1 --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_reservations.py @@ -0,0 +1,85 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_reservations + + +class LicensingReservationsManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Reservations Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingReservationsManagerTestCase, self).setUp() + self.licence = licensing_reservations.LicensingReservationsManager( + mock_client) + + def test_list(self): + self.licence._licensing_cli.get.return_value = { + "reservation1": "mock_reservation1", + "reservation2": "mock_reservation2" + } + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.list(mock.sentinel.appliance_id) + + self.assertEqual( + [mock_resource_class.return_value, + mock_resource_class.return_value], + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s/reservations' % mock.sentinel.appliance_id, + response_key='reservations') + mock_resource_class.assert_has_calls([ + mock.call(self.licence, "reservation1", loaded=True), + mock.call(self.licence, "reservation2", loaded=True) + ]) + + def test_show(self): + self.licence._licensing_cli.get.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.show( + mock.sentinel.appliance_id, mock.sentinel.reservation_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/appliances/%s/reservations/%s' % (mock.sentinel.appliance_id, + mock.sentinel.reservation_id), + response_key='reservation') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) + + def test_refresh(self): + self.licence._licensing_cli.post.return_value = mock.sentinel.data + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.refresh( + mock.sentinel.appliance_id, mock.sentinel.reservation_id) + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.post.assert_called_once_with( + '/appliances/%s/reservations/%s/refresh' % + (mock.sentinel.appliance_id, mock.sentinel.reservation_id), + response_key='reservation') + mock_resource_class.assert_called_once_with( + self.licence, mock.sentinel.data, loaded=True + ) diff --git a/coriolisclient/tests/v1/test_licensing_server.py b/coriolisclient/tests/v1/test_licensing_server.py new file mode 100644 index 0000000..721f49f --- /dev/null +++ b/coriolisclient/tests/v1/test_licensing_server.py @@ -0,0 +1,39 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import licensing +from coriolisclient.v1 import licensing_server + + +class LicensingServerManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Licensing Server Manager.""" + + @mock.patch.object(licensing, 'LicensingClient') + def setUp(self, mock_LicensingClient): + mock_client = mock.Mock() + self.mock_licencing_client = mock.Mock() + mock_LicensingClient.return_value = self.mock_licencing_client + super(LicensingServerManagerTestCase, self).setUp() + self.licence = licensing_server.LicensingServerManager( + mock_client) + + def test_status(self): + mock_resource_class = mock.Mock() + self.licence.resource_class = mock_resource_class + + result = self.licence.status() + + self.assertEqual( + mock_resource_class.return_value, + result + ) + self.mock_licencing_client.get.assert_called_once_with( + '/status', + response_key='status') + mock_resource_class.assert_called_once_with( + self.licence, self.mock_licencing_client.get.return_value, + loaded=True) diff --git a/coriolisclient/tests/v1/test_logging.py b/coriolisclient/tests/v1/test_logging.py new file mode 100644 index 0000000..a2b449e --- /dev/null +++ b/coriolisclient/tests/v1/test_logging.py @@ -0,0 +1,282 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import copy +import datetime +import ddt +import requests +import tempfile +from unittest import mock + +from keystoneauth1.exceptions import http + +from coriolisclient import exceptions +from coriolisclient.tests import test_base +from coriolisclient.v1 import logging + + +@ddt.ddt +class LoggingClientTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Logging Client.""" + + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def setUp(self, mock_get_endpoint_url): + mock_get_endpoint_url.return_value = mock.sentinel.ep_url + mock_client = mock.Mock() + mock_client.verify = True + super(LoggingClientTestCase, self).setUp() + self.logger = logging.LoggingClient(mock_client) + self.datetime = copy.deepcopy(datetime.datetime) + + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def test__init__(self, mock_get_endpoint_url): + mock_get_endpoint_url.side_effect = Exception + + with self.assertLogs(logger=logging.LOG, level="WARNING"): + logger = logging.LoggingClient(None) + + self.assertEqual( + None, + logger._ep_url + ) + + def test_get_endpoint_url(self): + self.logger._cli.get_endpoint.return_value = "url/endpoint_url/" + + result = self.logger._get_endpoint_url(mock.sentinel.name) + + self.assertEqual( + "url/endpoint_url", + result + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + def test_get_endpoint_url_not_found(self): + self.logger._cli.get_endpoint.return_value = None + + self.assertRaises( + exceptions.LoggingEndpointNotFound, + self.logger._get_endpoint_url, + mock.sentinel.name + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + def test_get_endpoint_url_http_unauthorized(self): + self.logger._cli.get_endpoint.side_effect = http.Unauthorized + + with self.assertLogs(logger=logging.LOG, level="ERROR"): + self.assertRaises( + exceptions.HTTPAuthError, + self.logger._get_endpoint_url, + mock.sentinel.name + ) + self.logger._cli.get_endpoint.assert_called_once_with( + service_type=mock.sentinel.name) + + @ddt.data( + { + "query_args": { + "arg1": None, + "arg2": None + }, + "is_websocket": True, + "expected_result": + "ws:///None/sentinel.resource" + }, + { + "query_args": { + "arg1": None, + "arg2": "mock_arg2" + }, + "_ep_url": "https:///ep_url", + "is_websocket": True, + "expected_result": + "wss:///ep_url/sentinel.resource?arg2=mock_arg2" + }, + { + "query_args": { + "arg1": "mock_arg1", + "arg2": "mock_arg2" + }, + "_ep_url": "https:///ep_url", + "is_websocket": False, + "expected_result": "https:///ep_url/sentinel.resource" + "?arg1=mock_arg1&arg2=mock_arg2" + } + ) + @mock.patch.object(logging.LoggingClient, '_get_endpoint_url') + def test_construct_url(self, data, mock_get_endpoint_url): + self.logger._ep_url = None + mock_get_endpoint_url.return_value = data.get("_ep_url", None) + + result = self.logger._construct_url( + mock.sentinel.resource, + data.get("query_args"), + is_websocket=data.get("is_websocket", False), + ) + + self.assertEqual( + data.get("expected_result"), + result + ) + + @ddt.data( + (None, None, False), + ("1", 1, False), + ("1234567890123456789", None, True), + ("abc", None, True), + ("", None, True), + ) + @ddt.unpack + def test_convert_period_to_timestamp( + self, + period, + expected_result, + raises + ): + if raises is False: + result = self.logger._convert_period_to_timestamp(period) + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + exceptions.CoriolisException, + self.logger._convert_period_to_timestamp, + period + ) + + @mock.patch.object(datetime, 'datetime') + def test_convert_period_to_timestamp_period( + self, + mock_datetime + ): + mock_datetime.utcnow.return_value = self.datetime.fromtimestamp(100000) + result = self.logger._convert_period_to_timestamp("1d") + self.assertEqual( + 13600, + result + ) + + @mock.patch.object(requests, "get") + @mock.patch.object(logging.LoggingClient, "_construct_url") + @mock.patch.object(logging.LoggingClient, "_convert_period_to_timestamp") + def test_download_logs( + self, + mock_convert_period_to_timestamp, + mock_construct_url, + mock_get + ): + mock_r = mock.Mock() + mock_r.iter_content.return_value = [b'test_chunk1', b'test_chunk2'] + mock_get.return_value.__enter__.return_value = mock_r + with tempfile.NamedTemporaryFile() as fd: + self.logger.download_logs( + mock.sentinel.app, + fd.name, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time + ) + + result = fd.read() + self.assertEqual( + result, + b'test_chunk1test_chunk2' + ) + mock_get.assert_called_once_with( + mock_construct_url.return_value, + headers=self.logger._auth_headers, + stream=True, + verify=True + ) + mock_construct_url.assert_called_once_with( + "logs/sentinel.app/", + { + "start_date": mock_convert_period_to_timestamp.return_value, + "end_date": mock_convert_period_to_timestamp.return_value, + } + ) + + def test_download_logs_no_app(self): + self.assertRaises( + exceptions.CoriolisException, + self.logger.download_logs, + "", + None + ) + + @mock.patch.object(requests, "get") + def test_list_logs(self, mock_get): + mock_get.return_value.raise_for_status.return_value = None + mock_get.return_value.json.return_value = { + "logs": ["mock_log1", "mock_log2"] + } + + result = self.logger.list_logs() + + self.assertEqual( + ['mock_log1', 'mock_log2'], + result + ) + + +class CoriolisLogDownloadManagerTestCase( + test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Coriolis Log Download Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(CoriolisLogDownloadManagerTestCase, self).setUp() + self.logger = logging.CoriolisLogDownloadManager(mock_client) + self.logger._coriolis_cli = mock.Mock() + self.logger.resource_class = mock.Mock() + + def test_list(self): + self.logger._coriolis_cli.list_logs.return_value = [ + "mock_log1", "mock_log2"] + + result = self.logger.list() + + self.assertEqual( + [self.logger.resource_class.return_value, + self.logger.resource_class.return_value], + result + ) + self.logger.resource_class.assert_has_calls([ + mock.call(self.logger, "mock_log1", loaded=True), + mock.call(self.logger, "mock_log2", loaded=True) + ]) + + def test_get(self): + result = self.logger.get( + mock.sentinel.app, + mock.sentinel.to, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time, + ) + + self.assertEqual( + self.logger._coriolis_cli.download_logs.return_value, + result + ) + self.logger._coriolis_cli.download_logs.assert_called_once_with( + mock.sentinel.app, + mock.sentinel.to, + start_time=mock.sentinel.start_time, + end_time=mock.sentinel.end_time, + ) + + def test_stream(self): + self.logger.stream( + app_name=mock.sentinel.app_name, + severity=mock.sentinel.severity, + ) + + self.logger._coriolis_cli.stream_logs.assert_called_once_with( + app_name=mock.sentinel.app_name, + severity=mock.sentinel.severity, + ) diff --git a/coriolisclient/tests/v1/test_minion_pools.py b/coriolisclient/tests/v1/test_minion_pools.py new file mode 100644 index 0000000..d1d950c --- /dev/null +++ b/coriolisclient/tests/v1/test_minion_pools.py @@ -0,0 +1,141 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import minion_pools + + +class MinionPoolManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Migration Pool Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(MinionPoolManagerTestCase, self).setUp() + self.minion_pool = minion_pools.MinionPoolManager(mock_client) + + @mock.patch.object(minion_pools.MinionPoolManager, "_list") + def test_list(self, mock_list): + result = self.minion_pool.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/minion_pools", response_key="minion_pools") + + @mock.patch.object(minion_pools.MinionPoolManager, "_get") + def test_get(self, mock_get): + result = self.minion_pool.get(mock.sentinel.minion_pool) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/minion_pools/sentinel.minion_pool", response_key="minion_pool") + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_create(self, mock_post): + expected_data = { + "name": mock.sentinel.name, + "endpoint_id": mock.sentinel.endpoint, + "platform": mock.sentinel.platform, + "os_type": mock.sentinel.os_type, + "environment_options": mock.sentinel.environment_options, + "minimum_minions": mock.sentinel.minimum_minions, + "maximum_minions": mock.sentinel.maximum_minions, + "minion_max_idle_time": mock.sentinel.minion_max_idle_time, + "minion_retention_strategy": + mock.sentinel.minion_retention_strategy, + "notes": mock.sentinel.notes, + "skip_allocation": False, + } + expected_data = {"minion_pool": expected_data} + + result = self.minion_pool.create( + mock.sentinel.name, + mock.sentinel.endpoint, + mock.sentinel.platform, + mock.sentinel.os_type, + environment_options=mock.sentinel.environment_options, + minimum_minions=mock.sentinel.minimum_minions, + maximum_minions=mock.sentinel.maximum_minions, + minion_max_idle_time=mock.sentinel.minion_max_idle_time, + minion_retention_strategy=mock.sentinel.minion_retention_strategy, + notes=mock.sentinel.notes, + skip_allocation=False, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools", expected_data, response_key="minion_pool") + + @mock.patch.object(minion_pools.MinionPoolManager, "_put") + def test_update(self, mock_put): + result = self.minion_pool.update( + mock.sentinel.minion_pool, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/minion_pools/%s" % mock.sentinel.minion_pool, + {"minion_pool": mock.sentinel.updated_values}, 'minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_delete") + def test_delete(self, mock_delete): + result = self.minion_pool.delete(mock.sentinel.minion_pool) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/minion_pools/%s" % mock.sentinel.minion_pool) + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_allocate_minion_pool(self, mock_post): + result = self.minion_pool.allocate_minion_pool( + mock.sentinel.minion_pool) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'allocate': None}, response_key='minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_refresh_minion_pool(self, mock_post): + result = self.minion_pool.refresh_minion_pool( + mock.sentinel.minion_pool) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'refresh': None}, response_key='minion_pool') + + @mock.patch.object(minion_pools.MinionPoolManager, "_post") + def test_deallocate_minion_pool(self, mock_post): + result = self.minion_pool.deallocate_minion_pool( + mock.sentinel.minion_pool, force=False) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/minion_pools/%s/actions" % mock.sentinel.minion_pool, + {'deallocate': {'force': False}}, + response_key='minion_pool') diff --git a/coriolisclient/tests/v1/test_providers.py b/coriolisclient/tests/v1/test_providers.py new file mode 100644 index 0000000..0d14d39 --- /dev/null +++ b/coriolisclient/tests/v1/test_providers.py @@ -0,0 +1,85 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import providers + + +class ProvidersTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Providers.""" + + def test_properties(self): + mock_client = mock.Mock() + self.provider = providers.Providers( + mock_client, + { + "provider1": { + "types": [mock.sentinel.type1, mock.sentinel.type2], + }, + "provider2": { + "types": [mock.sentinel.type3, mock.sentinel.type4], + } + } + ) + expected_provider_list = [ + { + 'name': 'provider1', + 'types': [mock.sentinel.type1, mock.sentinel.type2] + }, + { + 'name': 'provider2', + 'types': [mock.sentinel.type3, mock.sentinel.type4] + } + ] + expected_provider_schemas = [ + { + 'schema': { + 'types': [mock.sentinel.type1, mock.sentinel.type2]}, + 'type': 'provider1' + }, + { + 'schema': { + 'types': [mock.sentinel.type3, mock.sentinel.type4]}, + 'type': 'provider2' + } + ] + + self.assertEqual( + (expected_provider_list, expected_provider_schemas), + (self.provider.providers_list, self.provider.provider_schemas) + ) + + +class ProvidersManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Providers Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ProvidersManagerTestCase, self).setUp() + self.provider = providers.ProvidersManager(mock_client) + + @mock.patch.object(providers.ProvidersManager, "_get") + def test_list(self, mock_get): + result = self.provider.list() + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with("/providers", "providers") + + @mock.patch.object(providers.ProvidersManager, "_get") + def test_schemas_list(self, mock_get): + result = self.provider.schemas_list( + mock.sentinel.provider_name, mock.sentinel.provider_type) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + '/providers/%s/schemas/%s' % (mock.sentinel.provider_name, + mock.sentinel.provider_type), + "schemas") diff --git a/coriolisclient/tests/v1/test_regions.py b/coriolisclient/tests/v1/test_regions.py new file mode 100644 index 0000000..f413821 --- /dev/null +++ b/coriolisclient/tests/v1/test_regions.py @@ -0,0 +1,196 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import regions + + +@ddt.ddt +class RegionManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Region Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(RegionManagerTestCase, self).setUp() + self.region = regions.RegionManager(mock_client) + + @mock.patch.object(regions.RegionManager, "_list") + def test_list(self, mock_list): + result = self.region.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/regions", "regions") + + @mock.patch.object(regions.RegionManager, "_get") + def test_get(self, mock_get): + result = self.region.get(mock.sentinel.region) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/regions/sentinel.region", "region") + + @mock.patch.object(regions.RegionManager, "_post") + def test_create(self, mock_post): + expected_data = { + "name": mock.sentinel.name, + "description": mock.sentinel.description, + "enabled": False, + } + expected_data = {"region": expected_data} + + result = self.region.create( + mock.sentinel.name, + description=mock.sentinel.description, + enabled=False, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/regions", expected_data, "region") + + @mock.patch.object(regions.RegionManager, "_put") + def test_update(self, mock_put): + result = self.region.update( + mock.sentinel.region, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/regions/%s" % mock.sentinel.region, + {"region": mock.sentinel.updated_values}, 'region') + + @mock.patch.object(regions.RegionManager, "_delete") + def test_delete(self, mock_delete): + result = self.region.delete(mock.sentinel.region) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/regions/%s" % mock.sentinel.region) + + @mock.patch.object(regions.RegionManager, "list") + @ddt.data( + { + "region_name_or_id": "mock_name", + "has_regions_cache": True, + "expected_region": "region1", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_name_duplicate", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_name4", + "has_regions_cache": False, + "expected_region": "region4", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_name_not_found", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_name_not_found", + "has_regions_cache": True, + "expected_region": None, + "raise_on_not_found": False, + "raises": False + }, + { + "region_name_or_id": "mock_id", + "has_regions_cache": True, + "expected_region": "region1", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_id_duplicate", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_id4", + "has_regions_cache": False, + "expected_region": "region4", + "raise_on_not_found": True, + "raises": False + }, + { + "region_name_or_id": "mock_id_not_found", + "has_regions_cache": True, + "raise_on_not_found": True, + "raises": True + }, + { + "region_name_or_id": "mock_id_not_found", + "has_regions_cache": True, + "expected_region": None, + "raise_on_not_found": False, + "raises": False + } + ) + def test_get_region_by_name_or_id( + self, + data, + mock_list + ): + self.region1 = mock.Mock() + self.region2 = mock.Mock() + self.region3 = mock.Mock() + self.region4 = mock.Mock() + self.region1.id = "mock_id" + self.region1.name = "mock_name" + self.region2.id = "mock_id_duplicate" + self.region2.name = "mock_name_duplicate" + self.region3.id = "mock_id_duplicate" + self.region3.name = "mock_name_duplicate" + self.region4.id = "mock_id4" + self.region4.name = "mock_name4" + regions_cache = [self.region1, self.region2, self.region3] + if data.get("has_regions_cache") is False: + regions_cache = None + mock_list.return_value = [self.region4] + + if data.get("raises", False): + self.assertRaises( + ValueError, + self.region.get_region_by_name_or_id, + data.get("region_name_or_id"), + regions_cache=regions_cache, + raise_on_not_found=data.get("raise_on_not_found") + ) + else: + result = self.region.get_region_by_name_or_id( + data.get("region_name_or_id"), + regions_cache=regions_cache, + raise_on_not_found=data.get("raise_on_not_found") + ) + self.assertEqual( + getattr(self, str(data.get("expected_region")), None), + result + ) diff --git a/coriolisclient/tests/v1/test_services.py b/coriolisclient/tests/v1/test_services.py new file mode 100644 index 0000000..98cf133 --- /dev/null +++ b/coriolisclient/tests/v1/test_services.py @@ -0,0 +1,148 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import services + + +@ddt.ddt +class ServiceManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Service Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(ServiceManagerTestCase, self).setUp() + self.service = services.ServiceManager(mock_client) + + @mock.patch.object(services.ServiceManager, "_list") + def test_list(self, mock_list): + result = self.service.list() + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + "/services", "services") + + @mock.patch.object(services.ServiceManager, "_get") + def test_get(self, mock_get): + result = self.service.get(mock.sentinel.service) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/services/sentinel.service", "service") + + @mock.patch.object(services.ServiceManager, "_post") + def test_create(self, mock_post): + expected_data = { + "host": mock.sentinel.host, + "binary": mock.sentinel.binary, + "topic": mock.sentinel.topic, + "mapped_regions": mock.sentinel.regions, + "enabled": True + } + expected_data = {"service": expected_data} + + result = self.service.create( + mock.sentinel.host, + mock.sentinel.binary, + mock.sentinel.topic, + mock.sentinel.regions, + enabled=True, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/services", expected_data, "service") + + @mock.patch.object(services.ServiceManager, "_put") + def test_update(self, mock_put): + result = self.service.update( + mock.sentinel.service, mock.sentinel.updated_values) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/services/%s" % mock.sentinel.service, + {"service": mock.sentinel.updated_values}, 'service') + + @mock.patch.object(services.ServiceManager, "_delete") + def test_delete(self, mock_delete): + result = self.service.delete(mock.sentinel.service) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/services/%s" % mock.sentinel.service) + + @mock.patch.object(services.ServiceManager, "list") + @ddt.data( + { + "host": "mock_host", + "topic": "mock_topic", + "expected_service": "service1", + "raises": False + }, + { + "host": "mock_host", + "topic": "mock_topic_not_found", + "raises": True + }, + { + "host": "mock_host_duplicate", + "topic": "mock_topic_duplicate", + "raises": True + }, + { + "host": "mock_host_not_found", + "topic": "mock_topic", + "raises": True + }, + ) + def test_find_service_by_host_and_topic( + self, + data, + mock_list + ): + self.service1 = mock.Mock() + self.service2 = mock.Mock() + self.service3 = mock.Mock() + self.service1.host = "mock_host" + self.service1.topic = "mock_topic" + self.service2.host = "mock_host_duplicate" + self.service2.topic = "mock_topic_duplicate" + self.service3.host = "mock_host_duplicate" + self.service3.topic = "mock_topic_duplicate" + services = [self.service1, self.service2, self.service3] + mock_list.return_value = services + + if data.get("raises", False): + self.assertRaises( + ValueError, + self.service.find_service_by_host_and_topic, + data.get("host"), + data.get("topic") + ) + else: + result = self.service.find_service_by_host_and_topic( + data.get("host"), + data.get("topic") + ) + self.assertEqual( + getattr(self, str(data.get("expected_service")), None), + result + ) diff --git a/coriolisclient/tests/v1/test_transfer_executions.py b/coriolisclient/tests/v1/test_transfer_executions.py new file mode 100644 index 0000000..ab5a7f0 --- /dev/null +++ b/coriolisclient/tests/v1/test_transfer_executions.py @@ -0,0 +1,114 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import transfer_executions + + +class TransferExecutionTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Execution.""" + + def test_tasks(self): + mock_client = mock.Mock() + self.transfer_execution = transfer_executions.TransferExecution( + mock_client, + { + "tasks": [{"task1": mock.sentinel.task1}, + {"task2": mock.sentinel.task2}], + } + ) + self.assertEqual( + ( + mock.sentinel.task1, + mock.sentinel.task2 + ), + ( + self.transfer_execution.tasks[0].task1, + self.transfer_execution.tasks[1].task2 + ) + ) + + +class TransferExecutionManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Execution Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(TransferExecutionManagerTestCase, self).setUp() + self.transfer_execution = transfer_executions.TransferExecutionManager( + mock_client) + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_list") + def test_list(self, mock_list): + result = self.transfer_execution.list(mock.sentinel.transfer) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/transfers/%s/executions' % mock.sentinel.transfer, "executions") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_get") + def test_get(self, mock_get): + result = self.transfer_execution.get( + mock.sentinel.transfer, mock.sentinel.execution) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/transfers/%s/executions/%s" % (mock.sentinel.transfer, + mock.sentinel.execution), + "execution") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_post") + def test_create(self, mock_post): + expected_data = { + "shutdown_instances": mock.sentinel.shutdown_instances, + "auto_deploy": mock.sentinel.auto_deploy, + } + expected_data = {"execution": expected_data} + + result = self.transfer_execution.create( + mock.sentinel.transfer, + shutdown_instances=mock.sentinel.shutdown_instances, + auto_deploy=mock.sentinel.auto_deploy, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/transfers/%s/executions' % mock.sentinel.transfer, + expected_data, "execution") + + @mock.patch.object(transfer_executions.TransferExecutionManager, "_delete") + def test_delete(self, mock_delete): + result = self.transfer_execution.delete( + mock.sentinel.transfer, mock.sentinel.execution) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/transfers/%s/executions/%s" % (mock.sentinel.transfer, + mock.sentinel.execution)) + + def test_cancel(self): + result = self.transfer_execution.cancel( + mock.sentinel.transfer, mock.sentinel.execution, force=False) + + self.assertEqual( + self.transfer_execution.client.post.return_value, + result + ) + self.transfer_execution.client.post.assert_called_once_with( + "/transfers/%s/executions/%s/actions" % (mock.sentinel.transfer, + mock.sentinel.execution), + json={'cancel': {'force': False}}) diff --git a/coriolisclient/tests/v1/test_transfer_schedules.py b/coriolisclient/tests/v1/test_transfer_schedules.py new file mode 100644 index 0000000..4cebaa2 --- /dev/null +++ b/coriolisclient/tests/v1/test_transfer_schedules.py @@ -0,0 +1,128 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import datetime +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import transfer_schedules + + +class TransferScheduleManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Schedule Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(TransferScheduleManagerTestCase, self).setUp() + self.transfer_schedule = transfer_schedules.TransferScheduleManager( + mock_client) + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_list") + def test_list(self, mock_list): + result = self.transfer_schedule.list( + mock.sentinel.transfer, hide_expired=False) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/transfers/%s/schedules' % mock.sentinel.transfer, "schedules") + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_list") + def test_list_hide_expired(self, mock_list): + result = self.transfer_schedule.list( + mock.sentinel.transfer, hide_expired=True) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with( + '/transfers/%s/schedules?show_expired=False' + % mock.sentinel.transfer, "schedules") + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_get") + def test_get(self, mock_get): + result = self.transfer_schedule.get( + mock.sentinel.transfer, mock.sentinel.schedule) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer, + mock.sentinel.schedule), + "schedule") + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_post") + def test_create(self, mock_post): + expiration_date = datetime.datetime.fromisoformat("2034-11-26") + expected_data = { + "schedule": mock.sentinel.schedule, + "enabled": True, + "expiration_date": '2034-11-26T00:00:00Z', + "shutdown_instance": mock.sentinel.shutdown_instance, + "auto_deploy": mock.sentinel.auto_deploy, + } + + result = self.transfer_schedule.create( + mock.sentinel.transfer, + mock.sentinel.schedule, + True, + expiration_date, + mock.sentinel.shutdown_instance, + mock.sentinel.auto_deploy, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + '/transfers/%s/schedules' % mock.sentinel.transfer, + expected_data, "schedule") + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_put") + def test_update(self, mock_put): + expiration_date = datetime.datetime.fromisoformat("2034-11-26") + updated_values = {"expiration_date": expiration_date} + result = self.transfer_schedule.update( + mock.sentinel.transfer_id, + mock.sentinel.schedule_id, + updated_values + ) + + self.assertEqual( + mock_put.return_value, + result + ) + mock_put.assert_called_once_with( + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer_id, + mock.sentinel.schedule_id), + {"expiration_date": '2034-11-26T00:00:00Z'}, + "schedule") + + @mock.patch.object(transfer_schedules.TransferScheduleManager, "_delete") + def test_delete(self, mock_delete): + result = self.transfer_schedule.delete( + mock.sentinel.transfer, mock.sentinel.schedule) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/transfers/%s/schedules/%s" % (mock.sentinel.transfer, + mock.sentinel.schedule),) + + def test_format_rfc3339_datetime(self): + dt = datetime.date(2024, 1, 1) + + result = self.transfer_schedule._format_rfc3339_datetime(dt) + + self.assertEqual( + "2024-01-01Z", + result + ) diff --git a/coriolisclient/tests/v1/test_transfers.py b/coriolisclient/tests/v1/test_transfers.py new file mode 100644 index 0000000..7966dc3 --- /dev/null +++ b/coriolisclient/tests/v1/test_transfers.py @@ -0,0 +1,213 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +from unittest import mock + +from coriolisclient.tests import test_base +from coriolisclient.v1 import transfer_executions +from coriolisclient.v1 import transfers + + +class TransferTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer.""" + + @mock.patch.object(transfers.Transfer, "get") + def test_properties(self, mock_get): + mock_client = mock.Mock() + self.transfer = transfers.Transfer( + mock_client, + { + "source_environment": { + "source_environment1": + mock.sentinel.source_environment1, + }, + "destination_environment": { + "destination_environment1": + mock.sentinel.destination_environment1, + }, + "executions": [{"execution1": mock.sentinel.execution1}, + {"execution2": mock.sentinel.execution2}], + } + ) + + self.assertEqual( + ( + mock.sentinel.source_environment1, + mock.sentinel.destination_environment1, + mock.sentinel.execution1, + mock.sentinel.execution2 + ), + ( + self.transfer.source_environment.source_environment1, + self.transfer.destination_environment.destination_environment1, + self.transfer.executions[0].execution1, + self.transfer.executions[1].execution2, + ) + ) + mock_get.assert_not_called() + + @mock.patch.object(transfers.Transfer, "get") + def test_properties_none(self, mock_get): + mock_client = mock.Mock() + self.transfer = transfers.Transfer( + mock_client, + {} + ) + + self.assertEqual( + ( + None, + None, + [] + ), + ( + self.transfer.source_environment, + self.transfer.destination_environment, + self.transfer.executions + ) + ) + mock_get.assert_called_once() + + +class TransferManagerTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis v1 Transfer Manager.""" + + def setUp(self): + mock_client = mock.Mock() + super(TransferManagerTestCase, self).setUp() + self.transfer = transfers.TransferManager(mock_client) + + @mock.patch.object(transfers.TransferManager, "_list") + def test_list(self, mock_list): + result = self.transfer.list(detail=False) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with("/transfers", "transfers") + + @mock.patch.object(transfers.TransferManager, "_list") + def test_list_details(self, mock_list): + result = self.transfer.list(detail=True) + + self.assertEqual( + mock_list.return_value, + result + ) + mock_list.assert_called_once_with("/transfers/detail", "transfers") + + @mock.patch.object(transfers.TransferManager, "_get") + def test_get(self, mock_get): + result = self.transfer.get(mock.sentinel.transfer) + + self.assertEqual( + mock_get.return_value, + result + ) + mock_get.assert_called_once_with( + "/transfers/%s" % mock.sentinel.transfer, "transfer") + + @mock.patch.object(transfers.TransferManager, "_post") + def test_create(self, mock_post): + expected_data = { + "origin_endpoint_id": mock.sentinel.origin_endpoint_id, + "destination_endpoint_id": mock.sentinel.destination_endpoint_id, + "source_environment": mock.sentinel.source_environment, + "destination_environment": { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + "instances": mock.sentinel.instances, + "scenario": mock.sentinel.scenario, + "network_map": mock.sentinel.network_map, + "notes": mock.sentinel.notes, + "storage_mappings": mock.sentinel.storage_mappings, + "user_scripts": mock.sentinel.user_scripts, + "origin_minion_pool_id": mock.sentinel.origin_minion_pool_id, + "destination_minion_pool_id": + mock.sentinel.destination_minion_pool_id, + "instance_osmorphing_minion_pool_mappings": + mock.sentinel.instance_osmorphing_minion_pool_mappings, + "clone_disks": True, + "skip_os_morphing": False, + } + expected_data = {"transfer": expected_data} + + result = self.transfer.create( + mock.sentinel.origin_endpoint_id, + mock.sentinel.destination_endpoint_id, + mock.sentinel.source_environment, + { + "network_map": mock.sentinel.network_map, + "storage_mappings": mock.sentinel.storage_mappings + }, + mock.sentinel.instances, + mock.sentinel.scenario, + network_map=None, + notes=mock.sentinel.notes, + storage_mappings=None, + user_scripts=mock.sentinel.user_scripts, + origin_minion_pool_id=mock.sentinel.origin_minion_pool_id, + destination_minion_pool_id= + mock.sentinel.destination_minion_pool_id, + instance_osmorphing_minion_pool_mappings= + mock.sentinel.instance_osmorphing_minion_pool_mappings, + clone_disks=True, + skip_os_morphing=False, + ) + + self.assertEqual( + mock_post.return_value, + result + ) + mock_post.assert_called_once_with( + "/transfers", expected_data, "transfer") + + @mock.patch.object(transfers.TransferManager, "_delete") + def test_delete(self, mock_delete): + result = self.transfer.delete(mock.sentinel.transfer) + + self.assertEqual( + mock_delete.return_value, + result + ) + mock_delete.assert_called_once_with( + "/transfers/%s" % mock.sentinel.transfer) + + @mock.patch.object(transfer_executions, "TransferExecution") + def test_delete_disks(self, mock_TransferExecution): + result = self.transfer.delete_disks(mock.sentinel.transfer) + + self.assertEqual( + mock_TransferExecution.return_value, + result + ) + self.transfer.client.post.assert_called_once_with( + "/transfers/%s/actions" % mock.sentinel.transfer, + json={'delete-disks': None}) + mock_TransferExecution.assert_called_once_with( + self.transfer, + (self.transfer.client.post.return_value.json.return_value. + get("execution")), + loaded=True + ) + + @mock.patch.object(transfer_executions, "TransferExecution") + def test_update(self, mock_TransferExecution): + updated_values = {"network_map": mock.sentinel.network_map} + result = self.transfer.update(mock.sentinel.transfer, updated_values) + + self.assertEqual( + mock_TransferExecution.return_value, + result + ) + self.transfer.client.put.assert_called_once_with( + "/transfers/%s" % mock.sentinel.transfer, + json={"transfer": updated_values}) + mock_TransferExecution.assert_called_once_with( + self.transfer, + (self.transfer.client.put.return_value.json.return_value. + get("execution")), + loaded=True + ) diff --git a/coriolisclient/v1/deployments.py b/coriolisclient/v1/deployments.py new file mode 100644 index 0000000..78558e7 --- /dev/null +++ b/coriolisclient/v1/deployments.py @@ -0,0 +1,86 @@ +# Copyright (c) 2024 Cloudbase Solutions Srl +# +# 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 coriolisclient import base +from coriolisclient.v1 import common + + +class Deployment(base.Resource): + _tasks = None + + @property + def source_environment(self): + source_env = self._info.get("source_environment") + if source_env is not None: + return common.SourceEnvironment(None, source_env, loaded=True) + + @property + def destination_environment(self): + dest_env = self._info.get("destination_environment") + if dest_env is not None: + return common.DestinationEnvironment(None, dest_env, loaded=True) + + @property + def transfer_result(self): + res = self._info.get("transfer_result") + if res is not None: + return common.TransferResult(None, res, loaded=True) + + @property + def tasks(self): + if self._info.get('tasks') is None: + self.get() + return [common.Task(None, d, loaded=True) for d in + self._info.get('tasks', [])] + + +class DeploymentManager(base.BaseManager): + resource_class = Deployment + + def __init__(self, api): + super(DeploymentManager, self).__init__(api) + + def list(self, detail=False): + path = "/deployments" + if detail: + path = "%s/detail" % path + return self._list(path, 'deployments') + + def get(self, deployment): + return self._get( + '/deployments/%s' % base.getid(deployment), 'deployment') + + def create_from_transfer(self, transfer_id, clone_disks=True, force=False, + skip_os_morphing=False, user_scripts=None, + instance_osmorphing_minion_pool_mappings=None): + data = { + "deployment": { + "transfer_id": transfer_id, + "clone_disks": clone_disks, + "force": force, + "skip_os_morphing": skip_os_morphing, + "user_scripts": user_scripts}} + if instance_osmorphing_minion_pool_mappings is not None: + data['deployment']['instance_osmorphing_minion_pool_mappings'] = ( + instance_osmorphing_minion_pool_mappings) + return self._post('/deployments', data, 'deployment') + + def delete(self, deployment): + return self._delete('/deployments/%s' % base.getid(deployment)) + + def cancel(self, deployment, force=False): + return self.client.post( + '/deployments/%s/actions' % base.getid(deployment), + json={'cancel': {'force': force}}) diff --git a/coriolisclient/v1/endpoint_instances.py b/coriolisclient/v1/endpoint_instances.py index 4a62a55..0f36993 100644 --- a/coriolisclient/v1/endpoint_instances.py +++ b/coriolisclient/v1/endpoint_instances.py @@ -33,7 +33,7 @@ def __init__(self, api): def list( self, endpoint, env=None, marker=None, - limit=None, name=None): + limit=None, name=None, refresh=False): query = {} if marker is not None: @@ -42,6 +42,8 @@ def list( query['limit'] = limit if name is not None: query["name"] = name + if refresh: + query['refresh'] = True if env is not None: if not isinstance(env, dict): raise ValueError("'env' param must be a dict") diff --git a/coriolisclient/v1/licensing_appliances.py b/coriolisclient/v1/licensing_appliances.py index 0121107..7cfc208 100644 --- a/coriolisclient/v1/licensing_appliances.py +++ b/coriolisclient/v1/licensing_appliances.py @@ -41,5 +41,5 @@ def show(self, appliance_id): def create(self): url = '/appliances' - data = self._licensing_cli.post(url, response_key='apppliance') + data = self._licensing_cli.post(url, response_key='appliance') return self.resource_class(self, data, loaded=True) diff --git a/coriolisclient/v1/logging.py b/coriolisclient/v1/logging.py index e69fe52..2e841c2 100644 --- a/coriolisclient/v1/logging.py +++ b/coriolisclient/v1/logging.py @@ -137,7 +137,7 @@ def _convert_period_to_timestamp(self, period): _ = datetime.datetime.fromtimestamp(int(period)) return int(period) except (OverflowError, OSError): - LOG.warning("Failed to initialize timestamp from period value: %d", + LOG.warning("Failed to initialize timestamp from period value: %s", period) except ValueError: LOG.warning("Invalid value type for period: %s", period) @@ -157,7 +157,7 @@ def _convert_period_to_timestamp(self, period): if conv_unit is None: raise exceptions.CoriolisException("invalid period %s" % period) - args = {conv_unit: count} + args = {conv_unit: int(count)} tm = datetime.datetime.utcnow() - datetime.timedelta(**args) return int(tm.timestamp()) diff --git a/coriolisclient/v1/migrations.py b/coriolisclient/v1/migrations.py deleted file mode 100644 index e625abd..0000000 --- a/coriolisclient/v1/migrations.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright (c) 2016 Cloudbase Solutions Srl -# -# 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 coriolisclient import base -from coriolisclient.v1 import common - - -class Migration(base.Resource): - _tasks = None - - @property - def source_environment(self): - source_env = self._info.get("source_environment") - if source_env is not None: - return common.SourceEnvironment(None, source_env, loaded=True) - - @property - def destination_environment(self): - dest_env = self._info.get("destination_environment") - if dest_env is not None: - return common.DestinationEnvironment(None, dest_env, loaded=True) - - @property - def transfer_result(self): - res = self._info.get("transfer_result") - if res is not None: - return common.TransferResult(None, res, loaded=True) - - @property - def tasks(self): - if self._info.get('tasks') is None: - self.get() - return [common.Task(None, d, loaded=True) for d in - self._info.get('tasks', [])] - - -class MigrationManager(base.BaseManager): - resource_class = Migration - - def __init__(self, api): - super(MigrationManager, self).__init__(api) - - def list(self, detail=False): - path = "/migrations" - if detail: - path = "%s/detail" % path - return self._list(path, 'migrations') - - def get(self, migration): - return self._get('/migrations/%s' % base.getid(migration), 'migration') - - def create(self, origin_endpoint_id, destination_endpoint_id, - source_environment, destination_environment, instances, - network_map=None, notes=None, storage_mappings=None, - skip_os_morphing=False, replication_count=None, - shutdown_instances=None, user_scripts=None, - origin_minion_pool_id=None, destination_minion_pool_id=None, - instance_osmorphing_minion_pool_mappings=None): - if not network_map: - network_map = destination_environment.get('network_map', {}) - if not storage_mappings: - storage_mappings = destination_environment.get( - 'storage_mappings', {}) - - data = { - "migration": { - "origin_endpoint_id": origin_endpoint_id, - "destination_endpoint_id": destination_endpoint_id, - "destination_environment": destination_environment, - "instances": instances, - "skip_os_morphing": skip_os_morphing, - "network_map": network_map, - "notes": notes, - "storage_mappings": storage_mappings, - "user_scripts": user_scripts}} - if source_environment is not None: - data['migration']['source_environment'] = source_environment - if shutdown_instances is not None: - data['migration']['shutdown_instances'] = shutdown_instances - if replication_count is not None: - data['migration']['replication_count'] = replication_count - if origin_minion_pool_id is not None: - data['migration']['origin_minion_pool_id'] = origin_minion_pool_id - if destination_minion_pool_id is not None: - data['migration']['destination_minion_pool_id'] = ( - destination_minion_pool_id) - if instance_osmorphing_minion_pool_mappings: - data['migration']['instance_osmorphing_minion_pool_mappings'] = ( - instance_osmorphing_minion_pool_mappings) - - return self._post('/migrations', data, 'migration') - - def create_from_replica(self, replica_id, clone_disks=True, force=False, - skip_os_morphing=False, user_scripts=None, - instance_osmorphing_minion_pool_mappings=None): - data = {"migration": { - "replica_id": replica_id, - "clone_disks": clone_disks, - "force": force, - "skip_os_morphing": skip_os_morphing, - "user_scripts": user_scripts}} - if instance_osmorphing_minion_pool_mappings is not None: - data['migration']['instance_osmorphing_minion_pool_mappings'] = ( - instance_osmorphing_minion_pool_mappings) - return self._post('/migrations', data, 'migration') - - def delete(self, migration): - return self._delete('/migrations/%s' % base.getid(migration)) - - def cancel(self, migration, force=False): - return self.client.post( - '/migrations/%s/actions' % base.getid(migration), - json={'cancel': {'force': force}}) diff --git a/coriolisclient/v1/replica_executions.py b/coriolisclient/v1/transfer_executions.py similarity index 54% rename from coriolisclient/v1/replica_executions.py rename to coriolisclient/v1/transfer_executions.py index 2f906f8..0552aae 100644 --- a/coriolisclient/v1/replica_executions.py +++ b/coriolisclient/v1/transfer_executions.py @@ -17,7 +17,7 @@ from coriolisclient.v1 import common -class ReplicaExecution(base.Resource): +class TransferExecution(base.Resource): _tasks = None @property @@ -28,37 +28,40 @@ def tasks(self): self._info.get('tasks', [])] -class ReplicaExecutionManager(base.BaseManager): - resource_class = ReplicaExecution +class TransferExecutionManager(base.BaseManager): + resource_class = TransferExecution def __init__(self, api): - super(ReplicaExecutionManager, self).__init__(api) + super(TransferExecutionManager, self).__init__(api) - def list(self, replica): + def list(self, transfer): return self._list( - '/replicas/%s/executions' % base.getid(replica), 'executions') + '/transfers/%s/executions' % base.getid(transfer), 'executions') - def get(self, replica, execution): + def get(self, transfer, execution): return self._get( - '/replicas/%(replica_id)s/executions/%(execution_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}, 'execution') - def create(self, replica, shutdown_instances=False): - data = {"execution": {"shutdown_instances": shutdown_instances}} + def create(self, transfer, shutdown_instances=False, auto_deploy=False): + data = {"execution": { + "shutdown_instances": shutdown_instances, + "auto_deploy": auto_deploy}} return self._post( - '/replicas/%s/executions' % base.getid(replica), data, 'execution') + '/transfers/%s/executions' % + base.getid(transfer), data, 'execution') - def delete(self, replica, execution): + def delete(self, transfer, execution): return self._delete( - '/replicas/%(replica_id)s/executions/%(execution_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}) - def cancel(self, replica, execution, force=False): + def cancel(self, transfer, execution, force=False): return self.client.post( - '/replicas/%(replica_id)s/executions/%(execution_id)s/actions' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/executions/%(execution_id)s/actions' % + {"transfer_id": base.getid(transfer), "execution_id": base.getid(execution)}, json={'cancel': {'force': force}}) diff --git a/coriolisclient/v1/replica_schedules.py b/coriolisclient/v1/transfer_schedules.py similarity index 65% rename from coriolisclient/v1/replica_schedules.py rename to coriolisclient/v1/transfer_schedules.py index c439196..0dca527 100644 --- a/coriolisclient/v1/replica_schedules.py +++ b/coriolisclient/v1/transfer_schedules.py @@ -18,47 +18,47 @@ from coriolisclient import base -class ReplicaSchedule(base.Resource): +class TransferSchedule(base.Resource): _tasks = None -class ReplicaScheduleManager(base.BaseManager): - resource_class = ReplicaSchedule +class TransferScheduleManager(base.BaseManager): + resource_class = TransferSchedule def __init__(self, api): - super(ReplicaScheduleManager, self).__init__(api) + super(TransferScheduleManager, self).__init__(api) - def list(self, replica, hide_expired=False): + def list(self, transfer, hide_expired=False): query = {} if hide_expired: query["show_expired"] = hide_expired is False - url = '/replicas/%s/schedules' % base.getid(replica) + url = '/transfers/%s/schedules' % base.getid(transfer) if query: url += "?" + urlparse.urlencode(query) - return self._list( - url, 'schedules') + return self._list(url, 'schedules') - def get(self, replica, schedule): + def get(self, transfer, schedule): return self._get( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % + {"transfer_id": base.getid(transfer), "schedule_id": base.getid(schedule)}, 'schedule') - def create(self, replica, schedule, enabled, expiration_date, - shutdown_instance): + def create(self, transfer, schedule, enabled, expiration_date, + shutdown_instance, auto_deploy): data = { "schedule": schedule, "enabled": enabled, "shutdown_instance": shutdown_instance, + "auto_deploy": auto_deploy, } if expiration_date: data["expiration_date"] = self._format_rfc3339_datetime( expiration_date) return self._post( - '/replicas/%s/schedules' % base.getid(replica), data, 'schedule') + '/transfers/%s/schedules' % base.getid(transfer), data, 'schedule') - def update(self, replica_id, schedule_id, updated_values): + def update(self, transfer_id, schedule_id, updated_values): expiration_date = updated_values.get("expiration_date") if expiration_date: updated_values = updated_values.copy() @@ -66,15 +66,15 @@ def update(self, replica_id, schedule_id, updated_values): expiration_date) return self._put( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % { - "replica_id": base.getid(replica_id), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % { + "transfer_id": base.getid(transfer_id), "schedule_id": base.getid(schedule_id)}, updated_values, 'schedule') - def delete(self, replica, schedule): + def delete(self, transfer, schedule): return self._delete( - '/replicas/%(replica_id)s/schedules/%(schedule_id)s' % - {"replica_id": base.getid(replica), + '/transfers/%(transfer_id)s/schedules/%(schedule_id)s' % + {"transfer_id": base.getid(transfer), "schedule_id": base.getid(schedule)}) @staticmethod diff --git a/coriolisclient/v1/replicas.py b/coriolisclient/v1/transfers.py similarity index 67% rename from coriolisclient/v1/replicas.py rename to coriolisclient/v1/transfers.py index e82c023..d4559d5 100644 --- a/coriolisclient/v1/replicas.py +++ b/coriolisclient/v1/transfers.py @@ -15,10 +15,10 @@ from coriolisclient import base from coriolisclient.v1 import common -from coriolisclient.v1 import replica_executions +from coriolisclient.v1 import transfer_executions -class Replica(base.Resource): +class Transfer(base.Resource): _tasks = None @property @@ -41,74 +41,78 @@ def executions(self): self._info.get('executions', [])] -class ReplicaManager(base.BaseManager): - resource_class = Replica +class TransferManager(base.BaseManager): + resource_class = Transfer def __init__(self, api): - super(ReplicaManager, self).__init__(api) + super(TransferManager, self).__init__(api) def list(self, detail=False): - path = "/replicas" + path = "/transfers" if detail: path = "%s/detail" % path - return self._list(path, 'replicas') + return self._list(path, 'transfers') - def get(self, replica): - return self._get('/replicas/%s' % base.getid(replica), 'replica') + def get(self, transfer): + return self._get('/transfers/%s' % base.getid(transfer), 'transfer') def create(self, origin_endpoint_id, destination_endpoint_id, source_environment, destination_environment, instances, + transfer_scenario, network_map=None, notes=None, storage_mappings=None, origin_minion_pool_id=None, destination_minion_pool_id=None, instance_osmorphing_minion_pool_mappings=None, - user_scripts=None): + user_scripts=None, clone_disks=True, skip_os_morphing=False): if not network_map: network_map = destination_environment.get('network_map', {}) if not storage_mappings: storage_mappings = destination_environment.get( 'storage_mappings', {}) data = { - "replica": { + "transfer": { "origin_endpoint_id": origin_endpoint_id, "destination_endpoint_id": destination_endpoint_id, "destination_environment": destination_environment, "instances": instances, + "scenario": transfer_scenario, "network_map": network_map, "notes": notes, "storage_mappings": storage_mappings, "user_scripts": user_scripts, + "clone_disks": clone_disks, + "skip_os_morphing": skip_os_morphing, } } if source_environment: - data['replica']['source_environment'] = source_environment + data['transfer']['source_environment'] = source_environment if origin_minion_pool_id is not None: - data['replica']['origin_minion_pool_id'] = origin_minion_pool_id + data['transfer']['origin_minion_pool_id'] = origin_minion_pool_id if destination_minion_pool_id is not None: - data['replica']['destination_minion_pool_id'] = ( + data['transfer']['destination_minion_pool_id'] = ( destination_minion_pool_id) if instance_osmorphing_minion_pool_mappings: - data['replica']['instance_osmorphing_minion_pool_mappings'] = ( + data['transfer']['instance_osmorphing_minion_pool_mappings'] = ( instance_osmorphing_minion_pool_mappings) - return self._post('/replicas', data, 'replica') + return self._post('/transfers', data, 'transfer') - def delete(self, replica): - return self._delete('/replicas/%s' % base.getid(replica)) + def delete(self, transfer): + return self._delete('/transfers/%s' % base.getid(transfer)) - def delete_disks(self, replica): + def delete_disks(self, transfer): response = self.client.post( - '/replicas/%s/actions' % base.getid(replica), + '/transfers/%s/actions' % base.getid(transfer), json={'delete-disks': None}) - return replica_executions.ReplicaExecution( + return transfer_executions.TransferExecution( self, response.json().get("execution"), loaded=True) - def update(self, replica, updated_values): + def update(self, transfer, updated_values): data = { - "replica": updated_values + "transfer": updated_values } response = self.client.put( - '/replicas/%s' % base.getid(replica), json=data) + '/transfers/%s' % base.getid(transfer), json=data) - return replica_executions.ReplicaExecution( + return transfer_executions.TransferExecution( self, response.json().get("execution"), loaded=True) diff --git a/samples/replica_samples.py b/samples/replica_samples.py index 1df76dc..7fec360 100644 --- a/samples/replica_samples.py +++ b/samples/replica_samples.py @@ -102,7 +102,7 @@ def create_vm_replicas(coriolis, source_endpoint, destination_endpoint, print("Creating Replica for VM(s): %s" % vm_group) replicas.append( - coriolis.replicas.create( + coriolis.transfers.create( source_endpoint, destination_endpoint, source_options, destination_options, vm_group, @@ -118,7 +118,7 @@ def get_replicas_for_endpoint(coriolis, endpoint, endpoint = coriolis.endpoints.get(endpoint) found = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if as_source and replica.origin_endpoint_id == endpoint.id: found.append(replica) @@ -131,7 +131,7 @@ def get_replicas_for_endpoint(coriolis, endpoint, def get_replicas_for_vm(coriolis, vm_name): """ Returns all Replicas which include the given VM name. """ replicas = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if vm_name in replica.instances: replicas.append(replica) @@ -141,7 +141,7 @@ def get_replicas_for_vm(coriolis, vm_name): def get_errord_replicas(coriolis): """ Returns a list of all the Replicas whose last execution error'd. """ errord = [] - for replica in coriolis.replicas.list(): + for replica in coriolis.transfers.list(): if not replica.executions: # Replica was never executed continue @@ -162,13 +162,13 @@ def wait_for_replica_execution(coriolis, replica, execution, """ tries = 0 - execution = coriolis.replica_executions.get( + execution = coriolis.transfer_executions.get( replica, execution) while tries < max_tries and execution.status == "RUNNING": print("Waiting on execution %s (currently %s)" % ( execution.id, execution.status)) time.sleep(retry_period) - execution = coriolis.replica_executions.get( + execution = coriolis.transfer_executions.get( replica, execution) if execution.status == "ERROR": @@ -220,15 +220,15 @@ def delete_replica(coriolis, replica, delete_replica_disks=True): the Replica itself. """ if delete_replica_disks: - execution = coriolis.replicas.delete_disks(replica) + execution = coriolis.transfers.delete_disks(replica) wait_for_replica_execution(coriolis, replica, execution) # NOTE: this is redundant as executions are cascade-deleted on # the deletion of the parent Replica anyway: for execution in reversed(replica.executions): - coriolis.replica_executions.delete(execution) + coriolis.transfer_executions.delete(execution) - coriolis.replicas.delete(replica) + coriolis.transfers.delete(replica) def main(): @@ -252,14 +252,14 @@ def main(): # execute the Replica: test_replica = replicas[0] - execution = coriolis.replica_executions.create( + execution = coriolis.transfer_executions.create( test_replica, shutdown_instances=True) # wait for execution to finish: wait_for_replica_execution(coriolis, test_replica, execution) # create a Migration from the Replica: - migration = coriolis.migrations.create_from_replica( + migration = coriolis.migrations.create_from_transfer( test_replica.id, clone_disks=True, skip_os_morphing=False) migration = wait_for_replica_migration(coriolis, migration) print("Migrated VM info is: %s" % ( diff --git a/setup.cfg b/setup.cfg index eeaabb9..b67cc70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,34 +44,33 @@ coriolis.v1 = endpoint_source_minion_pool_options_list = coriolisclient.cli.endpoint_source_minion_pool_options:ListEndpointSourceMinionPoolOptions endpoint_destination_minion_pool_options_list = coriolisclient.cli.endpoint_destination_minion_pool_options:ListEndpointDestinationMinionPoolOptions - migration_cancel = coriolisclient.cli.migrations:CancelMigration - migration_create = coriolisclient.cli.migrations:CreateMigration - migration_deploy_replica = coriolisclient.cli.migrations:CreateMigrationFromReplica - migration_delete = coriolisclient.cli.migrations:DeleteMigration - migration_list = coriolisclient.cli.migrations:ListMigration - migration_show = coriolisclient.cli.migrations:ShowMigration + deployment_cancel = coriolisclient.cli.deployments:CancelDeployment + deployment_create = coriolisclient.cli.deployments:CreateDeployment + deployment_delete = coriolisclient.cli.deployments:DeleteDeployment + deployment_list = coriolisclient.cli.deployments:ListDeployment + deployment_show = coriolisclient.cli.deployments:ShowDeployment provider_list = coriolisclient.cli.providers:ListProvider provider_schema_list = coriolisclient.cli.providers:ListProviderSchemas - replica_create = coriolisclient.cli.replicas:CreateReplica - replica_delete = coriolisclient.cli.replicas:DeleteReplica - replica_disks_delete = coriolisclient.cli.replicas:DeleteReplicaDisks - replica_list = coriolisclient.cli.replicas:ListReplica - replica_show = coriolisclient.cli.replicas:ShowReplica - replica_update = coriolisclient.cli.replicas:UpdateReplica - - replica_execution_cancel = coriolisclient.cli.replica_executions:CancelReplicaExecution - replica_execute = coriolisclient.cli.replica_executions:CreateReplicaExecution - replica_execution_delete = coriolisclient.cli.replica_executions:DeleteReplicaExecution - replica_execution_list = coriolisclient.cli.replica_executions:ListReplicaExecution - replica_execution_show = coriolisclient.cli.replica_executions:ShowReplicaExecution - - replica_schedule_delete = coriolisclient.cli.replica_schedules:DeleteReplicaSchedule - replica_schedule_list = coriolisclient.cli.replica_schedules:ListReplicaSchedule - replica_schedule_show = coriolisclient.cli.replica_schedules:ShowReplicaSchedule - replica_schedule_create = coriolisclient.cli.replica_schedules:CreateReplicaSchedule - replica_schedule_update = coriolisclient.cli.replica_schedules:UpdateReplicaSchedule + transfer_create = coriolisclient.cli.transfers:CreateTransfer + transfer_delete = coriolisclient.cli.transfers:DeleteTransfer + transfer_disks_delete = coriolisclient.cli.transfers:DeleteTransferDisks + transfer_list = coriolisclient.cli.transfers:ListTransfer + transfer_show = coriolisclient.cli.transfers:ShowTransfer + transfer_update = coriolisclient.cli.transfers:UpdateTransfer + + transfer_execution_cancel = coriolisclient.cli.transfer_executions:CancelTransferExecution + transfer_execute = coriolisclient.cli.transfer_executions:CreateTransferExecution + transfer_execution_delete = coriolisclient.cli.transfer_executions:DeleteTransferExecution + transfer_execution_list = coriolisclient.cli.transfer_executions:ListTransferExecution + transfer_execution_show = coriolisclient.cli.transfer_executions:ShowTransferExecution + + transfer_schedule_delete = coriolisclient.cli.transfer_schedules:DeleteTransferSchedule + transfer_schedule_list = coriolisclient.cli.transfer_schedules:ListTransferSchedule + transfer_schedule_show = coriolisclient.cli.transfer_schedules:ShowTransferSchedule + transfer_schedule_create = coriolisclient.cli.transfer_schedules:CreateTransferSchedule + transfer_schedule_update = coriolisclient.cli.transfer_schedules:UpdateTransferSchedule region_create = coriolisclient.cli.regions:CreateRegion region_list = coriolisclient.cli.regions:ListRegions diff --git a/tox.ini b/tox.ini index 63e5067..82ae014 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.0.2 -envlist = py3,pep8 +envlist = py3,pep8,cover skipsdist = True [testenv] @@ -21,7 +21,7 @@ setenv = commands = stestr run --no-subunit-trace {posargs} coverage combine - coverage report --skip-covered + coverage report --fail-under=82 --skip-covered coverage html -d cover coverage xml -o cover/coverage.xml