Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
.ropeproject
.tox
.venv
venv
AUTHORS
Authors
build-stamp
Expand All @@ -38,3 +39,4 @@ nova/tests/cover/*
nova/vcsversion.py
tools/conf/nova.conf*
resources/write_data
migration-config.conf
33 changes: 26 additions & 7 deletions coriolis_openstack_utils/actions/coriolis_endpoint_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def endpoint_name_format(self):
def connection_info(self):
return self._source_openstack_client.connection_info

@property
def should_override_project(self):
# Source endpoints SHOULD override project_name to scope to the
# tenant where the instance is located
return True

def get_tenant_name(self):
return self.tenant_name_format % {
"original": self.payload["instance_tenant_name"]}
Expand Down Expand Up @@ -86,9 +92,11 @@ def print_operations(self):
def check_already_done(self):
super(SourceEndpointCreationAction, self).execute_operations()
connection_info = copy.deepcopy(self.connection_info)
# override the con info with the right one:
tenant_name = self.get_tenant_name()
connection_info["project_name"] = tenant_name
# Only override project_name for destination endpoints
# Source endpoints keep admin project to see all tenants' resources
if self.should_override_project:
tenant_name = self.get_tenant_name()
connection_info["project_name"] = tenant_name

endpoint_name = self.get_endpoint_name()
existing_endpoint = None
Expand Down Expand Up @@ -128,15 +136,19 @@ def execute_operations(self):
return done["result"]

connection_info = copy.deepcopy(self.connection_info)
tenant_name = self.get_tenant_name()
connection_info["project_name"] = tenant_name
# Only override project_name for destination endpoints
if self.should_override_project:
tenant_name = self.get_tenant_name()
connection_info["project_name"] = tenant_name

endpoint_name = self.get_endpoint_name()

LOG.info("Creating new endpoint named '%s'", endpoint_name)
# Get available regions for the endpoint
regions = [r.id for r in self._coriolis_client.regions.list()]
endpoint = self._coriolis_client.endpoints.create(
endpoint_name, ENDPOINT_TYPE_OPENSTACK,
connection_info, DEFAULT_ENDPOINT_DESCRIPTION)
connection_info, DEFAULT_ENDPOINT_DESCRIPTION, regions)

return endpoint.id

Expand All @@ -158,7 +170,8 @@ def cleanup(self):
LOG.info("Cannot delete endpoint '%s' with connection_info '%s',"
"not found.")
if endpoints_length == 1:
self._coriolis_client.endpoints.delete(similar_endpoints[0].id)
# similar_endpoints contains endpoint IDs (strings), not objects
self._coriolis_client.endpoints.delete(similar_endpoints[0])
elif endpoints_length > 1:
LOG.warn("Multiple endpoints with name '%s' and "
"connection_info '%s' found, skipping deletion."
Expand All @@ -184,3 +197,9 @@ def endpoint_name_format(self):
@property
def connection_info(self):
return self._destination_openstack_client.connection_info

@property
def should_override_project(self):
# Destination endpoints SHOULD override project_name to create
# resources in the target tenant
return True
90 changes: 71 additions & 19 deletions coriolis_openstack_utils/actions/coriolis_transfer_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ def execute_operations(self, subtasks_pre_executed=False):
"new": True}

def migration_same(self, other_migration):
if other_migration.status != MIGRATION_STATUS_RUNNING:
# New API uses last_execution_status instead of status
transfer_status = getattr(other_migration, 'status', None) or \
getattr(other_migration, 'last_execution_status', None)
if transfer_status != MIGRATION_STATUS_RUNNING:
return False
elif len(other_migration.instances) != 1:
return False
Expand Down Expand Up @@ -203,9 +206,9 @@ def migration_same(self, other_migration):
return True

def cleanup(self):
for migration in self._coriolis_client.migrations.list():
if self.migration_same(migration):
self._coriolis_client.migrations.cancel(migration.id)
for transfer in self._coriolis_client.transfers.list():
if self.migration_same(transfer):
self._coriolis_client.transfers.delete(transfer.id)
self.source_endpoint_create_action.cleanup()
self.dest_endpoint_create_action.cleanup()

Expand Down Expand Up @@ -257,19 +260,22 @@ class MigrationCreationAction(TransferAction):
action_type = base.ACTION_TYPE_CHECK_CREATE_MIGRATION

def get_transfers_list(self):
return reversed(self._coriolis_client.migrations.list())
return reversed(self._coriolis_client.transfers.list())

def get_transfer_status(self, transfer):
return transfer.status
# New Coriolis API uses last_execution_status instead of status
return getattr(transfer, 'status', None) or \
getattr(transfer, 'last_execution_status', 'UNKNOWN')

def check_existing_transfer(self, existing_transfer):
migration = existing_transfer
if migration.status == MIGRATION_STATUS_ERROR:
migration_status = self.get_transfer_status(migration)
if migration_status == MIGRATION_STATUS_ERROR:
LOG.info(
"Found existing migration with id '%s' for VM '%s', but "
"it is in '%s' state, thus, a new one will be created" % (
migration.id, self.payload["instance_name"],
migration.status))
migration_status))
done = {
"done": False,
"result": None}
Expand All @@ -279,13 +285,13 @@ def check_existing_transfer(self, existing_transfer):
"Found existing migration with id '%s' for VM '%s'. "
"Current status is '%s'. NOT creating a new migration.",
migration.id, self.payload["instance_name"],
migration.status)
migration_status)
done = {
"done": True,
"result": {
"instance_name": self.payload['instance_name'],
"transfer_id": migration.id,
"status": migration.status,
"status": migration_status,
"origin_endpoint_id":
migration.origin_endpoint_id,
"destination_endpoint_id":
Expand All @@ -304,14 +310,52 @@ def print_operations(self):

def create_transfer(self, source_endpoint, destination_endpoint):
skip_os_morphing = CONF.destination.skip_os_morphing
shutdown_instances = CONF.destination.shutdown_instances
pre_create_neutron_ports = CONF.destination.pre_create_neutron_ports
if pre_create_neutron_ports:
self.pre_create_neutron_ports()

return self._coriolis_client.migrations.create(
source_endpoint, destination_endpoint, {},
self._destination_env, [self.payload['instance_name']],
skip_os_morphing=skip_os_morphing)
# Source environment options for Coriolis transfer
# Use 'coriolis_backups' for Glance-based VMs
source_env = {
'replica_export_mechanism': 'coriolis_backups'
}

# Add Coriolis backup export options if configured
# These must be nested under 'coriolis_backups_options'
coriolis_backups_options = {}
if CONF.source.export_image:
coriolis_backups_options['export_image'] = CONF.source.export_image
if CONF.source.export_network:
coriolis_backups_options['export_network'] = CONF.source.export_network
if CONF.source.export_flavor_name:
coriolis_backups_options['export_flavor_name'] = CONF.source.export_flavor_name
if CONF.source.export_interim_volume_type:
coriolis_backups_options['export_interim_volume_type'] = CONF.source.export_interim_volume_type
if hasattr(CONF.source, 'export_worker_use_fip'):
coriolis_backups_options['export_worker_use_fip'] = CONF.source.export_worker_use_fip
if coriolis_backups_options:
source_env['coriolis_backups_options'] = coriolis_backups_options

# New Coriolis API uses transfers with scenario type
dest_minion_pool_id = getattr(CONF.destination, 'destination_minion_pool_id', None)
transfer = self._coriolis_client.transfers.create(
source_endpoint, destination_endpoint,
source_env, # source_environment
self._destination_env, # destination_environment
[self.payload['instance_name']], # instances
"live_migration", # transfer_scenario
skip_os_morphing=skip_os_morphing,
destination_minion_pool_id=dest_minion_pool_id)

# New Coriolis API requires explicit execution after creating transfer
LOG.info(
"Executing transfer %s for VM '%s'",
transfer.id, self.payload['instance_name'])
self._coriolis_client.transfer_executions.create(
transfer.id, shutdown_instances=shutdown_instances)

return transfer


class ReplicaCreationAction(TransferAction):
Expand All @@ -332,7 +376,11 @@ def last_execution_status(replica):
return REPLICA_EXECUTION_STATUS_NONE

def get_transfers_list(self):
return reversed(self._coriolis_client.replicas.list())
# Filter for replica scenario transfers
all_transfers = self._coriolis_client.transfers.list()
replicas = [t for t in all_transfers
if getattr(t, 'scenario', 'replica') == 'replica']
return reversed(replicas)

def get_transfer_status(self, transfer):
status = "NOT EXECUTED"
Expand Down Expand Up @@ -384,13 +432,17 @@ def create_transfer(self, source_endpoint, destination_endpoint):
if pre_create_neutron_ports:
self.pre_create_neutron_ports()

replica = self._coriolis_client.replicas.create(
source_endpoint, destination_endpoint, {},
self._destination_env, [self.payload['instance_name']])
# New Coriolis API uses transfers with scenario type
replica = self._coriolis_client.transfers.create(
source_endpoint, destination_endpoint,
{}, # source_environment
self._destination_env, # destination_environment
[self.payload['instance_name']], # instances
"replica") # transfer_scenario

if self.payload['execute_replica'] is True:
shutdown_instances = CONF.destination.shutdown_instances
self._coriolis_client.replica_executions.create(
self._coriolis_client.transfer_executions.create(
replica.id, shutdown_instances=shutdown_instances)

return replica
Expand Down
17 changes: 10 additions & 7 deletions coriolis_openstack_utils/actions/network_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,15 @@ def check_already_done(self):
return {"done": True, "result": dest_network['id']}

if len(conflicting) == 1:
raise Exception("Found destination network with "
"with name '%s' but different attributes!"
% dest_network_name)
# Network exists but attributes differ - accept it for retry scenarios
LOG.warning("Found destination network '%s' with different attributes, "
"but accepting it for migration continuity.", dest_network_name)
return {"done": True, "result": conflicting[0]['id']}
elif conflicting:
raise Exception("Found multiple destination networks with "
"with name '%s'! Aborting subnet "
"migration." % dest_network_name)
# Multiple networks with same name - use the first one
LOG.warning("Found multiple destination networks with name '%s', "
"using the first one.", dest_network_name)
return {"done": True, "result": conflicting[0]['id']}

return {"done": False, "result": None}

Expand Down Expand Up @@ -254,7 +256,8 @@ def execute_operations(self):
body = networks.create_network_body(
self._source_openstack_client, self.payload['src_network_id'],
self.payload['dest_tenant_id'],
self.get_new_network_name(), description)
self.get_new_network_name(), description,
destination_client=self._destination_openstack_client)

dest_network_id = networks.create_network(
self._destination_openstack_client, body)
Expand Down
7 changes: 5 additions & 2 deletions coriolis_openstack_utils/actions/secgroup_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,11 @@ def execute_operations(self):
raise Exception("Source security group named %s in tenant %s "
"not found! " % (src_secgroup_name, src_tenant_id))
elif len(source_secgroup_list) > 1:
raise Exception("Multiple secgroups named %s on source in "
"tenant %s " % (src_secgroup_name, src_tenant_id))
LOG.warn("Multiple secgroups named %s on source in tenant %s, "
"using the first one (id: %s)",
src_secgroup_name, src_tenant_id,
source_secgroup_list[0]['id'])
source_secgroup = source_secgroup_list[0]
else:
source_secgroup = source_secgroup_list[0]

Expand Down
15 changes: 14 additions & 1 deletion coriolis_openstack_utils/actions/tenant_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ def check_already_done(self):
if tenant == tenant_name:
LOG.debug("Tenant with name '%s' already exists. Skipping." % (
tenant_name))
return {"done": True, "result": tenant_name}
# Return the tenant ID, not the name, as dest_tenant_id is used downstream
tenant_id = self._destination_openstack_client.get_project_id(
tenant_name)
return {"done": True, "result": tenant_id}
return {"done": False, "result": None}

def execute_operations(self):
Expand Down Expand Up @@ -416,6 +419,16 @@ def execute_operations(self):

dest_target_migr_env = {'network_map': dest_net_map,
'security_groups': dest_secgroups}
# Add Coriolis migration-specific parameters if configured
if CONF.destination.migr_network:
dest_target_migr_env['migr_network'] = (
CONF.destination.migr_network)
if CONF.destination.migr_flavor_name:
dest_target_migr_env['migr_flavor_name'] = (
CONF.destination.migr_flavor_name)
if CONF.destination.migr_image_map:
dest_target_migr_env['migr_image_map'] = (
CONF.destination.migr_image_map)
instance_transfer_action = None
if self.payload['use_replicas']:
instance_transfer_action = (
Expand Down
5 changes: 4 additions & 1 deletion coriolis_openstack_utils/cli/tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ def take_action(self, args):

done = tenant_creation_action.check_already_done()
tenant = None
if done["done"]:
# If instances are requested, we need to run execute_operations even if
# tenant exists, because instances might not have been migrated yet
has_instances = not args.no_instances
if done["done"] and not has_instances:
LOG.info(
"Tenant %s Creation seemingly done."
% done["result"])
Expand Down
Loading