Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrations as replicas refactor #93

Merged
merged 8 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 44 additions & 139 deletions coriolisclient/cli/migrations.py → coriolisclient/cli/deployments.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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,
Expand All @@ -49,14 +51,16 @@ 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 = [
"id",
"status",
"created",
"last_updated",
"transfer_id",
"transfer_scenario_type",
"reservation_id",
"instances",
"notes",
Expand All @@ -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",
Expand Down Expand Up @@ -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,
Expand All @@ -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"),
Expand All @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -305,71 +209,72 @@ 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,
user_scripts=user_scripts,
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',
default=False)
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',
default=False)
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)
Loading