Merge "Update the nova backup protection plugin"
This commit is contained in:
commit
84d72e9baa
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
from karbor.common import constants
|
from karbor.common import constants
|
||||||
from karbor.resource import Resource
|
from karbor.resource import Resource
|
||||||
from karbor.services.protection.protectable_registry import ProtectableRegistry
|
|
||||||
from karbor.services.protection import resource_flow
|
from karbor.services.protection import resource_flow
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from taskflow import task
|
from taskflow import task
|
||||||
@ -45,8 +44,8 @@ class CompleteProtectTask(task.Task):
|
|||||||
checkpoint.commit()
|
checkpoint.commit()
|
||||||
|
|
||||||
|
|
||||||
def get_flow(context, workflow_engine, plan, provider, checkpoint):
|
def get_flow(context, protectable_registry, workflow_engine, plan, provider,
|
||||||
protectable_registry = ProtectableRegistry()
|
checkpoint):
|
||||||
resources = set(Resource(**item) for item in plan.get("resources"))
|
resources = set(Resource(**item) for item in plan.get("resources"))
|
||||||
resource_graph = protectable_registry.build_graph(context,
|
resource_graph = protectable_registry.build_graph(context,
|
||||||
resources)
|
resources)
|
||||||
|
@ -53,8 +53,10 @@ class Worker(object):
|
|||||||
**kwargs):
|
**kwargs):
|
||||||
if operation_type == constants.OPERATION_PROTECT:
|
if operation_type == constants.OPERATION_PROTECT:
|
||||||
plan = kwargs.get('plan', None)
|
plan = kwargs.get('plan', None)
|
||||||
|
protectable_registry = kwargs.get('protectable_registry', None)
|
||||||
flow = flow_protect.get_flow(
|
flow = flow_protect.get_flow(
|
||||||
context,
|
context,
|
||||||
|
protectable_registry,
|
||||||
self.workflow_engine,
|
self.workflow_engine,
|
||||||
plan,
|
plan,
|
||||||
provider,
|
provider,
|
||||||
|
@ -117,6 +117,7 @@ class ProtectionManager(manager.Manager):
|
|||||||
try:
|
try:
|
||||||
flow = self.worker.get_flow(
|
flow = self.worker.get_flow(
|
||||||
context=context,
|
context=context,
|
||||||
|
protectable_registry=self.protectable_registry,
|
||||||
operation_type=constants.OPERATION_PROTECT,
|
operation_type=constants.OPERATION_PROTECT,
|
||||||
plan=plan,
|
plan=plan,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
|
@ -10,17 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import eventlet
|
|
||||||
from io import BytesIO
|
|
||||||
import os
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
from karbor.common import constants
|
from karbor.common import constants
|
||||||
from karbor import exception
|
from karbor import exception
|
||||||
from karbor.i18n import _LE, _LI
|
from karbor.i18n import _LE, _LI
|
||||||
from karbor.services.protection.client_factory import ClientFactory
|
from karbor.services.protection.client_factory import ClientFactory
|
||||||
from karbor.services.protection.protection_plugins.base_protection_plugin \
|
from karbor.services.protection import protection_plugin
|
||||||
import BaseProtectionPlugin
|
|
||||||
from karbor.services.protection.protection_plugins.server \
|
from karbor.services.protection.protection_plugins.server \
|
||||||
import server_plugin_schemas
|
import server_plugin_schemas
|
||||||
from karbor.services.protection.restore_heat import HeatResource
|
from karbor.services.protection.restore_heat import HeatResource
|
||||||
@ -28,95 +22,60 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
protection_opts = [
|
|
||||||
cfg.IntOpt('backup_image_object_size',
|
|
||||||
default=52428800,
|
|
||||||
help='The size in bytes of instance image objects')
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(protection_opts)
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
VOLUME_ATTACHMENT_RESOURCE = 'OS::Cinder::VolumeAttachment'
|
VOLUME_ATTACHMENT_RESOURCE = 'OS::Cinder::VolumeAttachment'
|
||||||
FLOATING_IP_ASSOCIATION = 'OS::Nova::FloatingIPAssociation'
|
FLOATING_IP_ASSOCIATION = 'OS::Nova::FloatingIPAssociation'
|
||||||
|
|
||||||
|
|
||||||
class NovaProtectionPlugin(BaseProtectionPlugin):
|
class ProtectOperation(protection_plugin.Operation):
|
||||||
_SUPPORT_RESOURCE_TYPES = [constants.SERVER_RESOURCE_TYPE]
|
def __init__(self):
|
||||||
|
super(ProtectOperation, self).__init__()
|
||||||
def __init__(self, config=None):
|
|
||||||
super(NovaProtectionPlugin, self).__init__(config)
|
|
||||||
self._tp = eventlet.GreenPool()
|
|
||||||
self.image_object_size = CONF.backup_image_object_size
|
|
||||||
|
|
||||||
def _add_to_threadpool(self, func, *args, **kwargs):
|
|
||||||
self._tp.spawn_n(func, *args, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_options_schema(cls, resource_type):
|
|
||||||
return server_plugin_schemas.OPTIONS_SCHEMA
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_restore_schema(cls, resource_type):
|
|
||||||
return server_plugin_schemas.RESTORE_SCHEMA
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_saved_info_schema(cls, resource_type):
|
|
||||||
return server_plugin_schemas.SAVED_INFO_SCHEMA
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_saved_info(cls, metadata_store, resource):
|
|
||||||
# TODO(luobin)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _glance_client(self, cntxt):
|
|
||||||
return ClientFactory.create_client("glance", cntxt)
|
|
||||||
|
|
||||||
def _nova_client(self, cntxt):
|
|
||||||
return ClientFactory.create_client("nova", cntxt)
|
|
||||||
|
|
||||||
def _cinder_client(self, cntxt):
|
|
||||||
return ClientFactory.create_client("cinder", cntxt)
|
|
||||||
|
|
||||||
def _neutron_client(self, cntxt):
|
|
||||||
return ClientFactory.create_client("neutron", cntxt)
|
|
||||||
|
|
||||||
def create_backup(self, cntxt, checkpoint, **kwargs):
|
|
||||||
resource_node = kwargs.get("node")
|
|
||||||
server_id = resource_node.value.id
|
|
||||||
|
|
||||||
|
def on_main(self, checkpoint, resource, context, parameters, **kwargs):
|
||||||
|
server_id = resource.id
|
||||||
bank_section = checkpoint.get_resource_bank_section(server_id)
|
bank_section = checkpoint.get_resource_bank_section(server_id)
|
||||||
|
|
||||||
nova_client = self._nova_client(cntxt)
|
nova_client = ClientFactory.create_client("nova", context)
|
||||||
glance_client = self._glance_client(cntxt)
|
cinder_client = ClientFactory.create_client("cinder", context)
|
||||||
cinder_client = self._cinder_client(cntxt)
|
neutron_client = ClientFactory.create_client("neutron", context)
|
||||||
neutron_client = self._neutron_client(cntxt)
|
|
||||||
|
|
||||||
resource_definition = {"resource_id": server_id}
|
resource_definition = {"resource_id": server_id}
|
||||||
child_nodes = resource_node.child_nodes
|
|
||||||
attach_metadata = {}
|
|
||||||
|
|
||||||
LOG.info(_LI("creating server backup, server_id: %s."), server_id)
|
# get dependent resources
|
||||||
|
server_child_nodes = []
|
||||||
|
resources = checkpoint.resource_graph
|
||||||
|
for resource_node in resources:
|
||||||
|
resource = resource_node.value
|
||||||
|
if resource.id == server_id:
|
||||||
|
server_child_nodes = resource_node.child_nodes
|
||||||
|
|
||||||
|
LOG.info(_LI("Creating server backup, server_id: %s. "), server_id)
|
||||||
try:
|
try:
|
||||||
bank_section.update_object("status",
|
bank_section.update_object("status",
|
||||||
constants.RESOURCE_STATUS_PROTECTING)
|
constants.RESOURCE_STATUS_PROTECTING)
|
||||||
|
|
||||||
for child_node in child_nodes:
|
# get attach_metadata about volume
|
||||||
child_resource = child_node.value
|
attach_metadata = {}
|
||||||
|
for server_child_node in server_child_nodes:
|
||||||
|
child_resource = server_child_node.value
|
||||||
if child_resource.type == constants.VOLUME_RESOURCE_TYPE:
|
if child_resource.type == constants.VOLUME_RESOURCE_TYPE:
|
||||||
volume = cinder_client.volumes.get(child_resource.id)
|
volume = cinder_client.volumes.get(child_resource.id)
|
||||||
attachments = getattr(volume, "attachments")
|
attachments = getattr(volume, "attachments")
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
if attachment["server_id"] == server_id:
|
if attachment["server_id"] == server_id:
|
||||||
attach_metadata[child_resource.id] = attachment[
|
attachment["bootable"] = getattr(
|
||||||
"device"]
|
volume, "bootable")
|
||||||
|
attach_metadata[child_resource.id] = attachment
|
||||||
resource_definition["attach_metadata"] = attach_metadata
|
resource_definition["attach_metadata"] = attach_metadata
|
||||||
|
|
||||||
|
# get metadata about AZ
|
||||||
server = nova_client.servers.get(server_id)
|
server = nova_client.servers.get(server_id)
|
||||||
availability_zone = getattr(server, "OS-EXT-AZ:availability_zone")
|
availability_zone = getattr(server, "OS-EXT-AZ:availability_zone")
|
||||||
|
|
||||||
|
# get metadata about network, flavor, key_name, security_groups
|
||||||
addresses = getattr(server, "addresses")
|
addresses = getattr(server, "addresses")
|
||||||
networks = []
|
networks = []
|
||||||
floating_ips = []
|
floating_ips = []
|
||||||
@ -132,177 +91,112 @@ class NovaProtectionPlugin(BaseProtectionPlugin):
|
|||||||
networks.append(port["network_id"])
|
networks.append(port["network_id"])
|
||||||
elif network_type == "floating":
|
elif network_type == "floating":
|
||||||
floating_ips.append(addr)
|
floating_ips.append(addr)
|
||||||
|
|
||||||
flavor = getattr(server, "flavor")["id"]
|
flavor = getattr(server, "flavor")["id"]
|
||||||
key_name = getattr(server, "key_name", None)
|
key_name = getattr(server, "key_name", None)
|
||||||
security_groups = getattr(server, "security_groups", None)
|
security_groups = getattr(server, "security_groups", None)
|
||||||
|
|
||||||
|
# get metadata about boot device
|
||||||
|
boot_metadata = {}
|
||||||
|
image_info = getattr(server, "image", None)
|
||||||
|
if image_info is not None and isinstance(image_info, dict):
|
||||||
|
boot_metadata["boot_device_type"] = "image"
|
||||||
|
boot_metadata["boot_image_id"] = image_info['id']
|
||||||
|
else:
|
||||||
|
boot_metadata["boot_device_type"] = "volume"
|
||||||
|
volumes_attached = getattr(
|
||||||
|
server, "os-extended-volumes:volumes_attached", [])
|
||||||
|
for volume_attached in volumes_attached:
|
||||||
|
volume_id = volume_attached["id"]
|
||||||
|
volume_attach_metadata = attach_metadata.get(
|
||||||
|
volume_id, None)
|
||||||
|
if volume_attach_metadata is not None and (
|
||||||
|
volume_attach_metadata["bootable"] == "true"):
|
||||||
|
boot_metadata["boot_volume_id"] = volume_id
|
||||||
|
boot_metadata["boot_attach_metadata"] = (
|
||||||
|
volume_attach_metadata)
|
||||||
|
resource_definition["boot_metadata"] = boot_metadata
|
||||||
|
|
||||||
|
# save all server's metadata
|
||||||
server_metadata = {"availability_zone": availability_zone,
|
server_metadata = {"availability_zone": availability_zone,
|
||||||
"networks": networks,
|
"networks": networks,
|
||||||
"floating_ips": floating_ips,
|
"floating_ips": floating_ips,
|
||||||
"flavor": flavor,
|
"flavor": flavor,
|
||||||
"key_name": key_name,
|
"key_name": key_name,
|
||||||
"security_groups": security_groups
|
"security_groups": security_groups,
|
||||||
}
|
}
|
||||||
resource_definition["server_metadata"] = server_metadata
|
resource_definition["server_metadata"] = server_metadata
|
||||||
|
LOG.info("Creating server backup, resource_definition: %s.",
|
||||||
snapshot_id = nova_client.servers.create_image(
|
resource_definition)
|
||||||
server_id, "snapshot_%s" % server_id)
|
|
||||||
|
|
||||||
bank_section.update_object("metadata", resource_definition)
|
bank_section.update_object("metadata", resource_definition)
|
||||||
except Exception as err:
|
|
||||||
# update resource_definition backup_status
|
|
||||||
LOG.error(_LE("create backup failed, server_id: %s."), server_id)
|
|
||||||
bank_section.update_object("status",
|
|
||||||
constants.RESOURCE_STATUS_ERROR)
|
|
||||||
raise exception.CreateBackupFailed(
|
|
||||||
reason=err,
|
|
||||||
resource_id=server_id,
|
|
||||||
resource_type=constants.SERVER_RESOURCE_TYPE)
|
|
||||||
|
|
||||||
self._add_to_threadpool(self._create_backup, glance_client,
|
|
||||||
bank_section, server_id, snapshot_id,
|
|
||||||
resource_definition, checkpoint)
|
|
||||||
|
|
||||||
def _create_backup(self, glance_client, bank_section, server_id,
|
|
||||||
snapshot_id, resource_definition, checkpoint):
|
|
||||||
try:
|
|
||||||
image = glance_client.images.get(snapshot_id)
|
|
||||||
# TODO(luobin): config retry_attempts
|
|
||||||
retry_attempts = 10
|
|
||||||
while image.status == "queued" and retry_attempts != 0:
|
|
||||||
sleep(60)
|
|
||||||
image = glance_client.images.get(snapshot_id)
|
|
||||||
retry_attempts -= 1
|
|
||||||
if retry_attempts == 0:
|
|
||||||
raise Exception
|
|
||||||
|
|
||||||
resource_definition["snapshot_id"] = snapshot_id
|
|
||||||
snapshot_metadata = {
|
|
||||||
"disk_format": image.disk_format,
|
|
||||||
"container_format": image.container_format,
|
|
||||||
"name": "snapshot_%s@%s" % (checkpoint.id, server_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if getattr(image, "kernel_id", None) is not None:
|
|
||||||
kernel = glance_client.images.get(image.kernel_id)
|
|
||||||
kernel_metadata = {
|
|
||||||
"disk_format": kernel.disk_format,
|
|
||||||
"container_format": kernel.container_format,
|
|
||||||
"name": "kernel_%s@%s" % (checkpoint.id, server_id)
|
|
||||||
}
|
|
||||||
snapshot_metadata["kernel_metadata"] = kernel_metadata
|
|
||||||
|
|
||||||
if getattr(image, "ramdisk_id", None) is not None:
|
|
||||||
ramdisk = glance_client.images.get(image.ramdisk_id)
|
|
||||||
ramdisk_metadata = {
|
|
||||||
"disk_format": ramdisk.disk_format,
|
|
||||||
"container_format": ramdisk.container_format,
|
|
||||||
"name": "ramdisk_%s@%s" % (checkpoint.id, server_id)
|
|
||||||
}
|
|
||||||
snapshot_metadata["ramdisk_metadata"] = ramdisk_metadata
|
|
||||||
|
|
||||||
resource_definition["snapshot_metadata"] = snapshot_metadata
|
|
||||||
# write resource_definition in bank
|
|
||||||
bank_section.update_object("metadata", resource_definition)
|
|
||||||
|
|
||||||
image = glance_client.images.get(snapshot_id)
|
|
||||||
# TODO(luobin): config retry_attempts
|
|
||||||
retry_attempts = 10
|
|
||||||
while image.status != "active" and retry_attempts != 0:
|
|
||||||
sleep(60)
|
|
||||||
image = glance_client.images.get(snapshot_id)
|
|
||||||
retry_attempts -= 1
|
|
||||||
if retry_attempts == 0:
|
|
||||||
raise Exception
|
|
||||||
|
|
||||||
# store kernel_data if need
|
|
||||||
if getattr(image, "kernel_id", None) is not None:
|
|
||||||
kernel_id = image.kernel_id
|
|
||||||
kernel_response = glance_client.images.data(kernel_id)
|
|
||||||
kernel_response_data = BytesIO()
|
|
||||||
for chunk in kernel_response:
|
|
||||||
kernel_response_data.write(chunk)
|
|
||||||
kernel_response_data.seek(0, os.SEEK_SET)
|
|
||||||
|
|
||||||
chunks = 0
|
|
||||||
while True:
|
|
||||||
data = kernel_response_data.read(self.image_object_size)
|
|
||||||
if data == '':
|
|
||||||
break
|
|
||||||
bank_section.update_object("kernel_" + str(chunks), data)
|
|
||||||
chunks += 1
|
|
||||||
|
|
||||||
# store ramdisk_data if need
|
|
||||||
if getattr(image, "ramdisk_id", None) is not None:
|
|
||||||
ramdisk_id = image.ramdisk_id
|
|
||||||
ramdisk_response = glance_client.images.data(ramdisk_id)
|
|
||||||
ramdisk_response_data = BytesIO()
|
|
||||||
for chunk in ramdisk_response:
|
|
||||||
ramdisk_response_data.write(chunk)
|
|
||||||
ramdisk_response_data.seek(0, os.SEEK_SET)
|
|
||||||
|
|
||||||
chunks = 0
|
|
||||||
while True:
|
|
||||||
data = ramdisk_response_data.read(self.image_object_size)
|
|
||||||
if data == '':
|
|
||||||
break
|
|
||||||
bank_section.update_object("ramdisk_" + str(chunks), data)
|
|
||||||
chunks += 1
|
|
||||||
|
|
||||||
# store snapshot_data
|
|
||||||
image_response = glance_client.images.data(snapshot_id)
|
|
||||||
image_response_data = BytesIO()
|
|
||||||
for chunk in image_response:
|
|
||||||
image_response_data.write(chunk)
|
|
||||||
image_response_data.seek(0, os.SEEK_SET)
|
|
||||||
|
|
||||||
chunks = 0
|
|
||||||
while True:
|
|
||||||
data = image_response_data.read(self.image_object_size)
|
|
||||||
if data == '':
|
|
||||||
break
|
|
||||||
bank_section.update_object("snapshot_" + str(chunks), data)
|
|
||||||
chunks += 1
|
|
||||||
|
|
||||||
glance_client.images.delete(snapshot_id)
|
|
||||||
|
|
||||||
# update resource_definition backup_status
|
# update resource_definition backup_status
|
||||||
bank_section.update_object("status",
|
bank_section.update_object("status",
|
||||||
constants.RESOURCE_STATUS_AVAILABLE)
|
constants.RESOURCE_STATUS_AVAILABLE)
|
||||||
LOG.info(_LI("finish backup server, server_id: %s."), server_id)
|
LOG.info(_LI("Finish backup server, server_id: %s."), server_id)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
LOG.error(_LE("create backup failed, server_id: %s."), server_id)
|
# update resource_definition backup_status
|
||||||
|
LOG.exception(_LE("Create backup failed, server_id: %s."),
|
||||||
|
server_id)
|
||||||
bank_section.update_object("status",
|
bank_section.update_object("status",
|
||||||
constants.RESOURCE_STATUS_ERROR)
|
constants.RESOURCE_STATUS_ERROR)
|
||||||
|
|
||||||
raise exception.CreateBackupFailed(
|
raise exception.CreateBackupFailed(
|
||||||
reason=err,
|
reason=err,
|
||||||
resource_id=server_id,
|
resource_id=server_id,
|
||||||
resource_type=constants.SERVER_RESOURCE_TYPE)
|
resource_type=constants.SERVER_RESOURCE_TYPE)
|
||||||
|
|
||||||
def restore_backup(self, cntxt, checkpoint, **kwargs):
|
|
||||||
resource_node = kwargs.get("node")
|
class DeleteOperation(protection_plugin.Operation):
|
||||||
original_server_id = resource_node.value.id
|
def __init__(self):
|
||||||
|
super(DeleteOperation, self).__init__()
|
||||||
|
|
||||||
|
def on_main(self, checkpoint, resource, context, parameters, **kwargs):
|
||||||
|
resource_id = resource.id
|
||||||
|
bank_section = checkpoint.get_resource_bank_section(resource_id)
|
||||||
|
|
||||||
|
LOG.info(_LI("deleting server backup, server_id: %s."), resource_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
bank_section.update_object("status",
|
||||||
|
constants.RESOURCE_STATUS_DELETING)
|
||||||
|
objects = bank_section.list_objects()
|
||||||
|
for obj in objects:
|
||||||
|
if obj == "status":
|
||||||
|
continue
|
||||||
|
bank_section.delete_object(obj)
|
||||||
|
bank_section.update_object("status",
|
||||||
|
constants.RESOURCE_STATUS_DELETED)
|
||||||
|
LOG.info(_LI("finish delete server, server_id: %s."), resource_id)
|
||||||
|
except Exception as err:
|
||||||
|
# update resource_definition backup_status
|
||||||
|
LOG.error(_LE("Delete backup failed, server_id: %s."), resource_id)
|
||||||
|
bank_section.update_object("status",
|
||||||
|
constants.RESOURCE_STATUS_ERROR)
|
||||||
|
raise exception.DeleteBackupFailed(
|
||||||
|
reason=err,
|
||||||
|
resource_id=resource_id,
|
||||||
|
resource_type=constants.SERVER_RESOURCE_TYPE)
|
||||||
|
|
||||||
|
|
||||||
|
class RestoreOperation(protection_plugin.Operation):
|
||||||
|
def __init__(self):
|
||||||
|
super(RestoreOperation, self).__init__()
|
||||||
|
|
||||||
|
def on_complete(self, checkpoint, resource, context, parameters, **kwargs):
|
||||||
|
original_server_id = resource.id
|
||||||
heat_template = kwargs.get("heat_template")
|
heat_template = kwargs.get("heat_template")
|
||||||
|
|
||||||
restore_name = kwargs.get("restore_name", "karbor-restore-server")
|
restore_name = parameters.get("restore_name", "karbor-restore-server")
|
||||||
|
|
||||||
LOG.info(_LI("restoring server backup, server_id: %s."),
|
LOG.info(_LI("Restoring server backup, server_id: %s."),
|
||||||
original_server_id)
|
original_server_id)
|
||||||
|
|
||||||
bank_section = checkpoint.get_resource_bank_section(original_server_id)
|
bank_section = checkpoint.get_resource_bank_section(original_server_id)
|
||||||
try:
|
try:
|
||||||
resource_definition = bank_section.get_object("metadata")
|
resource_definition = bank_section.get_object("metadata")
|
||||||
|
|
||||||
# restore server snapshot
|
|
||||||
image_id = self._restore_server_snapshot(
|
|
||||||
bank_section, checkpoint, cntxt,
|
|
||||||
original_server_id, resource_definition)
|
|
||||||
|
|
||||||
# restore server instance
|
# restore server instance
|
||||||
self._heat_restore_server_instance(
|
self._heat_restore_server_instance(
|
||||||
heat_template, image_id, original_server_id,
|
heat_template, original_server_id,
|
||||||
restore_name, resource_definition)
|
restore_name, resource_definition)
|
||||||
|
|
||||||
# restore volume attachment
|
# restore volume attachment
|
||||||
@ -312,93 +206,55 @@ class NovaProtectionPlugin(BaseProtectionPlugin):
|
|||||||
# restore floating ip association
|
# restore floating ip association
|
||||||
self._heat_restore_floating_association(
|
self._heat_restore_floating_association(
|
||||||
heat_template, original_server_id, resource_definition)
|
heat_template, original_server_id, resource_definition)
|
||||||
|
LOG.debug("Restoring server backup, heat_template: %s.",
|
||||||
|
heat_template)
|
||||||
|
LOG.info(_LI("Finish restore server, server_id: %s."),
|
||||||
|
original_server_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(_LE("restore server backup failed, server_id: %s."),
|
LOG.exception(_LE("restore server backup failed, server_id: %s."),
|
||||||
original_server_id)
|
original_server_id)
|
||||||
raise exception.RestoreBackupFailed(
|
raise exception.RestoreBackupFailed(
|
||||||
reason=e,
|
reason=e,
|
||||||
resource_id=original_server_id,
|
resource_id=original_server_id,
|
||||||
resource_type=constants.SERVER_RESOURCE_TYPE
|
resource_type=constants.SERVER_RESOURCE_TYPE
|
||||||
)
|
)
|
||||||
|
|
||||||
def _restore_server_snapshot(self, bank_section, checkpoint, cntxt,
|
def _heat_restore_server_instance(self, heat_template,
|
||||||
original_id, resource_definition):
|
|
||||||
snapshot_metadata = resource_definition["snapshot_metadata"]
|
|
||||||
|
|
||||||
glance_client = self._glance_client(cntxt)
|
|
||||||
objects = [key.split("/")[-1] for key in
|
|
||||||
bank_section.list_objects()]
|
|
||||||
|
|
||||||
# restore kernel if needed
|
|
||||||
kernel_id = None
|
|
||||||
if snapshot_metadata.get("kernel_metadata") is not None:
|
|
||||||
kernel_id = self._restore_image(
|
|
||||||
bank_section, checkpoint, glance_client, "kernel",
|
|
||||||
snapshot_metadata["kernel_metadata"], objects,
|
|
||||||
original_id)
|
|
||||||
|
|
||||||
# restore ramdisk if needed
|
|
||||||
ramdisk_id = None
|
|
||||||
if snapshot_metadata.get("ramdisk_metadata") is not None:
|
|
||||||
ramdisk_id = self._restore_image(
|
|
||||||
bank_section, checkpoint, glance_client, "ramdisk",
|
|
||||||
snapshot_metadata["ramdisk_metadata"], objects,
|
|
||||||
original_id)
|
|
||||||
|
|
||||||
# restore image
|
|
||||||
image_id = self._restore_image(
|
|
||||||
bank_section, checkpoint, glance_client, "snapshot",
|
|
||||||
snapshot_metadata, objects, original_id,
|
|
||||||
kernel_id=kernel_id, ramdisk_id=ramdisk_id)
|
|
||||||
|
|
||||||
image_info = glance_client.images.get(image_id)
|
|
||||||
retry_attempts = 10
|
|
||||||
while image_info.status != "active" and retry_attempts != 0:
|
|
||||||
sleep(60)
|
|
||||||
image_info = glance_client.images.get(image_id)
|
|
||||||
retry_attempts -= 1
|
|
||||||
if retry_attempts == 0:
|
|
||||||
raise Exception
|
|
||||||
return image_id
|
|
||||||
|
|
||||||
def _restore_image(self, bank_section, checkpoint, glance_client,
|
|
||||||
image_format, image_metadata, objects, original_id,
|
|
||||||
**kwargs):
|
|
||||||
if image_metadata.get("name") is None:
|
|
||||||
name = "%s_%s@%s" % (image_format, checkpoint.id,
|
|
||||||
original_id)
|
|
||||||
else:
|
|
||||||
name = image_metadata["name"]
|
|
||||||
disk_format = image_metadata["disk_format"]
|
|
||||||
container_format = image_metadata["container_format"]
|
|
||||||
image_data = BytesIO()
|
|
||||||
for obj in objects:
|
|
||||||
if obj.find("%s_" % image_format) == 0:
|
|
||||||
data = bank_section.get_object(obj)
|
|
||||||
image_data.write(data)
|
|
||||||
image_data.seek(0, os.SEEK_SET)
|
|
||||||
image = glance_client.images.create(
|
|
||||||
disk_format=disk_format,
|
|
||||||
container_format=container_format,
|
|
||||||
name=name,
|
|
||||||
kernel_id=kwargs.get("kernel_id"),
|
|
||||||
ramdisk_id=kwargs.get("ramdisk_id"))
|
|
||||||
image_id = image.id
|
|
||||||
glance_client.images.upload(image_id, image_data)
|
|
||||||
return image_id
|
|
||||||
|
|
||||||
def _heat_restore_server_instance(self, heat_template, image_id,
|
|
||||||
original_id, restore_name,
|
original_id, restore_name,
|
||||||
resource_definition):
|
resource_definition):
|
||||||
server_metadata = resource_definition["server_metadata"]
|
server_metadata = resource_definition["server_metadata"]
|
||||||
properties = {
|
properties = {
|
||||||
"availability_zone": server_metadata["availability_zone"],
|
"availability_zone": server_metadata["availability_zone"],
|
||||||
"flavor": server_metadata["flavor"],
|
"flavor": server_metadata["flavor"],
|
||||||
"image": image_id,
|
|
||||||
"name": restore_name,
|
"name": restore_name,
|
||||||
}
|
}
|
||||||
|
# server boot device
|
||||||
|
boot_metadata = resource_definition["boot_metadata"]
|
||||||
|
boot_device_type = boot_metadata["boot_device_type"]
|
||||||
|
if boot_device_type == "image":
|
||||||
|
original_image_id = boot_metadata["boot_image_id"]
|
||||||
|
image_id = heat_template.get_resource_reference(
|
||||||
|
original_image_id)
|
||||||
|
properties["image"] = image_id
|
||||||
|
elif boot_device_type == "volume":
|
||||||
|
original_volume_id = boot_metadata["boot_volume_id"]
|
||||||
|
volume_id = heat_template.get_resource_reference(
|
||||||
|
original_volume_id)
|
||||||
|
properties["block_device_mapping_v2"] = [{
|
||||||
|
"volume_id": volume_id,
|
||||||
|
"delete_on_termination": False,
|
||||||
|
"boot_index": 0,
|
||||||
|
}]
|
||||||
|
else:
|
||||||
|
LOG.exception(_LE("Restore server backup failed, server_id: %s."),
|
||||||
|
original_id)
|
||||||
|
raise exception.RestoreBackupFailed(
|
||||||
|
reason="Can not find the boot device of the server.",
|
||||||
|
resource_id=original_id,
|
||||||
|
resource_type=constants.SERVER_RESOURCE_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
# server key_name, security_groups, networks
|
||||||
if server_metadata["key_name"] is not None:
|
if server_metadata["key_name"] is not None:
|
||||||
properties["key_name"] = server_metadata["key_name"]
|
properties["key_name"] = server_metadata["key_name"]
|
||||||
|
|
||||||
@ -426,24 +282,25 @@ class NovaProtectionPlugin(BaseProtectionPlugin):
|
|||||||
original_server_id,
|
original_server_id,
|
||||||
resource_definition):
|
resource_definition):
|
||||||
attach_metadata = resource_definition["attach_metadata"]
|
attach_metadata = resource_definition["attach_metadata"]
|
||||||
for original_volume_id, device in attach_metadata.items():
|
for original_id, attach_metadata_item in attach_metadata.items():
|
||||||
instance_uuid = heat_template.get_resource_reference(
|
device = attach_metadata_item.get("device", None)
|
||||||
original_server_id)
|
if attach_metadata_item.get("bootable", None) != "true":
|
||||||
volume_id = heat_template.get_resource_reference(
|
instance_uuid = heat_template.get_resource_reference(
|
||||||
original_volume_id)
|
original_server_id)
|
||||||
properties = {"mountpoint": device,
|
volume_id = heat_template.get_resource_reference(
|
||||||
"instance_uuid": instance_uuid,
|
original_id)
|
||||||
"volume_id": volume_id}
|
properties = {"mountpoint": device,
|
||||||
|
"instance_uuid": instance_uuid,
|
||||||
heat_resource_id = uuidutils.generate_uuid()
|
"volume_id": volume_id}
|
||||||
heat_attachment_resource = HeatResource(
|
heat_resource_id = uuidutils.generate_uuid()
|
||||||
heat_resource_id,
|
heat_attachment_resource = HeatResource(
|
||||||
VOLUME_ATTACHMENT_RESOURCE)
|
heat_resource_id,
|
||||||
for key, value in properties.items():
|
VOLUME_ATTACHMENT_RESOURCE)
|
||||||
heat_attachment_resource.set_property(key, value)
|
for key, value in properties.items():
|
||||||
heat_template.put_resource(
|
heat_attachment_resource.set_property(key, value)
|
||||||
"%s_%s" % (original_server_id, original_volume_id),
|
heat_template.put_resource(
|
||||||
heat_attachment_resource)
|
"%s_%s" % (original_server_id, original_id),
|
||||||
|
heat_attachment_resource)
|
||||||
|
|
||||||
def _heat_restore_floating_association(self, heat_template,
|
def _heat_restore_floating_association(self, heat_template,
|
||||||
original_server_id,
|
original_server_id,
|
||||||
@ -464,33 +321,38 @@ class NovaProtectionPlugin(BaseProtectionPlugin):
|
|||||||
"%s_%s" % (original_server_id, floating_ip),
|
"%s_%s" % (original_server_id, floating_ip),
|
||||||
heat_floating_resource)
|
heat_floating_resource)
|
||||||
|
|
||||||
def delete_backup(self, cntxt, checkpoint, **kwargs):
|
|
||||||
resource_node = kwargs.get("node")
|
|
||||||
resource_id = resource_node.value.id
|
|
||||||
bank_section = checkpoint.get_resource_bank_section(resource_id)
|
|
||||||
|
|
||||||
LOG.info(_LI("deleting server backup, server_id: %s."), resource_id)
|
class NovaProtectionPlugin(protection_plugin.ProtectionPlugin):
|
||||||
|
_SUPPORT_RESOURCE_TYPES = [constants.SERVER_RESOURCE_TYPE]
|
||||||
|
|
||||||
try:
|
def __init__(self, config=None):
|
||||||
bank_section.update_object("status",
|
super(NovaProtectionPlugin, self).__init__(config)
|
||||||
constants.RESOURCE_STATUS_DELETING)
|
|
||||||
objects = bank_section.list_objects()
|
|
||||||
for obj in objects:
|
|
||||||
if obj == "status":
|
|
||||||
continue
|
|
||||||
bank_section.delete_object(obj)
|
|
||||||
bank_section.update_object("status",
|
|
||||||
constants.RESOURCE_STATUS_DELETED)
|
|
||||||
except Exception as err:
|
|
||||||
# update resource_definition backup_status
|
|
||||||
LOG.error(_LE("delete backup failed, server_id: %s."), resource_id)
|
|
||||||
bank_section.update_object("status",
|
|
||||||
constants.RESOURCE_STATUS_ERROR)
|
|
||||||
raise exception.DeleteBackupFailed(
|
|
||||||
reason=err,
|
|
||||||
resource_id=resource_id,
|
|
||||||
resource_type=constants.SERVER_RESOURCE_TYPE)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_supported_resources_types(cls):
|
def get_supported_resources_types(cls):
|
||||||
return cls._SUPPORT_RESOURCE_TYPES
|
return cls._SUPPORT_RESOURCE_TYPES
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_options_schema(cls, resource_type):
|
||||||
|
return server_plugin_schemas.OPTIONS_SCHEMA
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_restore_schema(cls, resource_type):
|
||||||
|
return server_plugin_schemas.RESTORE_SCHEMA
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_saved_info_schema(cls, resource_type):
|
||||||
|
return server_plugin_schemas.SAVED_INFO_SCHEMA
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_saved_info(cls, metadata_store, resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_protect_operation(self, resource):
|
||||||
|
return ProtectOperation()
|
||||||
|
|
||||||
|
def get_restore_operation(self, resource):
|
||||||
|
return RestoreOperation()
|
||||||
|
|
||||||
|
def get_delete_operation(self, resource):
|
||||||
|
return DeleteOperation()
|
||||||
|
@ -94,7 +94,6 @@ class CheckpointsTest(karbor_base.KarborBaseTest):
|
|||||||
|
|
||||||
def test_checkpoint_for_server_attached_volume(self):
|
def test_checkpoint_for_server_attached_volume(self):
|
||||||
"""Test checkpoint for server which has attached some volumes"""
|
"""Test checkpoint for server which has attached some volumes"""
|
||||||
self.skipTest('Requires server protection plugin adjustment')
|
|
||||||
volume = self.store(objects.Volume())
|
volume = self.store(objects.Volume())
|
||||||
volume.create(1)
|
volume.create(1)
|
||||||
server = self.store(objects.Server())
|
server = self.store(objects.Server())
|
||||||
|
@ -11,16 +11,18 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
from karbor.common import constants
|
from karbor.common import constants
|
||||||
from karbor.context import RequestContext
|
from karbor.context import RequestContext
|
||||||
from karbor.resource import Resource
|
from karbor.resource import Resource
|
||||||
from karbor.services.protection.bank_plugin import Bank
|
from karbor.services.protection.bank_plugin import Bank
|
||||||
from karbor.services.protection.bank_plugin import BankPlugin
|
from karbor.services.protection.bank_plugin import BankPlugin
|
||||||
from karbor.services.protection.bank_plugin import BankSection
|
from karbor.services.protection.bank_plugin import BankSection
|
||||||
from karbor.services.protection.protection_plugins.server. \
|
|
||||||
nova_protection_plugin import NovaProtectionPlugin
|
|
||||||
from karbor.services.protection.protection_plugins.server \
|
from karbor.services.protection.protection_plugins.server \
|
||||||
import server_plugin_schemas
|
import server_plugin_schemas
|
||||||
|
from karbor.services.protection.protection_plugins.server. \
|
||||||
|
nova_protection_plugin import NovaProtectionPlugin
|
||||||
from karbor.tests import base
|
from karbor.tests import base
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
@ -37,11 +39,14 @@ class Server(object):
|
|||||||
|
|
||||||
|
|
||||||
class Volume(object):
|
class Volume(object):
|
||||||
def __init__(self, id, volume_type, status, attachments):
|
def __init__(self, id, volume_type, status, bootable,
|
||||||
|
attachments, name=None):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.volume_type = volume_type
|
self.volume_type = volume_type
|
||||||
self.status = status
|
self.status = status
|
||||||
|
self.bootable = bootable
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
class Image(object):
|
class Image(object):
|
||||||
@ -108,12 +113,14 @@ FakeVolumes = {
|
|||||||
"vol_id_1": Volume(id="vol_id_1",
|
"vol_id_1": Volume(id="vol_id_1",
|
||||||
volume_type="",
|
volume_type="",
|
||||||
status="in-use",
|
status="in-use",
|
||||||
|
bootable="",
|
||||||
attachments=[{'server_id': 'vm_id_2',
|
attachments=[{'server_id': 'vm_id_2',
|
||||||
'attachment_id': '',
|
'attachment_id': '',
|
||||||
'host_name': '',
|
'host_name': '',
|
||||||
'volume_id': 'vol_id_1',
|
'volume_id': 'vol_id_1',
|
||||||
'device': '/dev/vdb',
|
'device': '/dev/vdb',
|
||||||
'id': 'attach_id_1'}])
|
'id': 'attach_id_1'}],
|
||||||
|
name="vol_id_1_name")
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeImages = {
|
FakeImages = {
|
||||||
@ -123,6 +130,11 @@ FakeImages = {
|
|||||||
status="active")
|
status="active")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FakeGraphNode = namedtuple("GraphNode", (
|
||||||
|
"value",
|
||||||
|
"child_nodes",
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class FakeNovaClient(object):
|
class FakeNovaClient(object):
|
||||||
class Servers(object):
|
class Servers(object):
|
||||||
@ -167,6 +179,9 @@ class FakeCinderClient(object):
|
|||||||
def get(self, volume_id):
|
def get(self, volume_id):
|
||||||
return FakeVolumes[volume_id]
|
return FakeVolumes[volume_id]
|
||||||
|
|
||||||
|
def list(self, detailed=True, search_opts=None, limit=None):
|
||||||
|
return [FakeVolumes['vol_id_1'], ]
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -227,6 +242,15 @@ ResourceNode = collections.namedtuple(
|
|||||||
class Checkpoint(object):
|
class Checkpoint(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = "checkpoint_id"
|
self.id = "checkpoint_id"
|
||||||
|
self.graph = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resource_graph(self):
|
||||||
|
return self.graph
|
||||||
|
|
||||||
|
@resource_graph.setter
|
||||||
|
def resource_graph(self, resource_graph):
|
||||||
|
self.graph = resource_graph
|
||||||
|
|
||||||
def get_resource_bank_section(self, resource_id):
|
def get_resource_bank_section(self, resource_id):
|
||||||
return BankSection(
|
return BankSection(
|
||||||
@ -235,6 +259,21 @@ class Checkpoint(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def call_hooks(operation, checkpoint, resource, context, parameters, **kwargs):
|
||||||
|
def noop(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
hooks = (
|
||||||
|
'on_prepare_begin',
|
||||||
|
'on_prepare_finish',
|
||||||
|
'on_main',
|
||||||
|
'on_complete',
|
||||||
|
)
|
||||||
|
for hook_name in hooks:
|
||||||
|
hook = getattr(operation, hook_name, noop)
|
||||||
|
hook(checkpoint, resource, context, parameters, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class NovaProtectionPluginTest(base.TestCase):
|
class NovaProtectionPluginTest(base.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NovaProtectionPluginTest, self).setUp()
|
super(NovaProtectionPluginTest, self).setUp()
|
||||||
@ -264,38 +303,36 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
self.assertEqual(options_schema,
|
self.assertEqual(options_schema,
|
||||||
server_plugin_schemas.SAVED_INFO_SCHEMA)
|
server_plugin_schemas.SAVED_INFO_SCHEMA)
|
||||||
|
|
||||||
def test_create_backup_without_volumes(self):
|
@mock.patch('karbor.services.protection.clients.neutron.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.glance.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.nova.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.cinder.create')
|
||||||
|
def test_create_backup_without_volumes(self, mock_cinder_client,
|
||||||
|
mock_nova_client,
|
||||||
|
mock_glance_client,
|
||||||
|
mock_neutron_client):
|
||||||
resource = Resource(id="vm_id_1",
|
resource = Resource(id="vm_id_1",
|
||||||
type=constants.SERVER_RESOURCE_TYPE,
|
type=constants.SERVER_RESOURCE_TYPE,
|
||||||
name="fake_vm")
|
name="fake_vm")
|
||||||
resource_node = ResourceNode(value=resource,
|
|
||||||
child_nodes=[])
|
|
||||||
backup_name = "fake_backup"
|
|
||||||
|
|
||||||
self.plugin._cinder_client = mock.MagicMock()
|
protect_operation = self.plugin.get_protect_operation(resource)
|
||||||
self.plugin._cinder_client.return_value = self.cinder_client
|
mock_cinder_client.return_value = self.cinder_client
|
||||||
|
mock_nova_client.return_value = self.nova_client
|
||||||
|
mock_glance_client.return_value = self.glance_client
|
||||||
|
mock_neutron_client.return_value = self.neutron_client
|
||||||
|
|
||||||
self.plugin._nova_client = mock.MagicMock()
|
call_hooks(protect_operation, self.checkpoint, resource, self.cntxt,
|
||||||
self.plugin._nova_client.return_value = self.nova_client
|
{})
|
||||||
|
|
||||||
self.plugin._glance_client = mock.MagicMock()
|
|
||||||
self.plugin._glance_client.return_value = self.glance_client
|
|
||||||
|
|
||||||
self.plugin._neutron_client = mock.MagicMock()
|
|
||||||
self.plugin._neutron_client.return_value = self.neutron_client
|
|
||||||
|
|
||||||
self.plugin.create_backup(self.cntxt, self.checkpoint,
|
|
||||||
node=resource_node,
|
|
||||||
backup_name=backup_name)
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
constants.RESOURCE_STATUS_PROTECTING,
|
constants.RESOURCE_STATUS_AVAILABLE,
|
||||||
fake_bank._plugin._objects[
|
fake_bank._plugin._objects[
|
||||||
"/resource_data/checkpoint_id/vm_id_1/status"]
|
"/resource_data/checkpoint_id/vm_id_1/status"]
|
||||||
)
|
)
|
||||||
resource_definition = {
|
resource_definition = {
|
||||||
"resource_id": "vm_id_1",
|
"resource_id": "vm_id_1",
|
||||||
"attach_metadata": {},
|
"attach_metadata": {},
|
||||||
|
'boot_metadata': {'boot_device_type': 'volume'},
|
||||||
"server_metadata": {
|
"server_metadata": {
|
||||||
"availability_zone": "nova",
|
"availability_zone": "nova",
|
||||||
"networks": ["network_id_1"],
|
"networks": ["network_id_1"],
|
||||||
@ -311,43 +348,42 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
"/resource_data/checkpoint_id/vm_id_1/metadata"]
|
"/resource_data/checkpoint_id/vm_id_1/metadata"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_backup_with_volumes(self):
|
@mock.patch('karbor.services.protection.clients.neutron.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.glance.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.nova.create')
|
||||||
|
@mock.patch('karbor.services.protection.clients.cinder.create')
|
||||||
|
def test_create_backup_with_volumes(self, mock_cinder_client,
|
||||||
|
mock_nova_client,
|
||||||
|
mock_glance_client,
|
||||||
|
mock_neutron_client):
|
||||||
vm_resource = Resource(id="vm_id_2",
|
vm_resource = Resource(id="vm_id_2",
|
||||||
type=constants.SERVER_RESOURCE_TYPE,
|
type=constants.SERVER_RESOURCE_TYPE,
|
||||||
name="fake_vm")
|
name="fake_vm")
|
||||||
vol_resource = Resource(id="vol_id_1",
|
|
||||||
type=constants.VOLUME_RESOURCE_TYPE,
|
|
||||||
name="fake_vol")
|
|
||||||
vol_node = ResourceNode(value=vol_resource,
|
|
||||||
child_nodes=[])
|
|
||||||
vm_node = ResourceNode(value=vm_resource,
|
|
||||||
child_nodes=[vol_node])
|
|
||||||
backup_name = "fake_backup"
|
|
||||||
|
|
||||||
self.plugin._cinder_client = mock.MagicMock()
|
protect_operation = self.plugin.get_protect_operation(vm_resource)
|
||||||
self.plugin._cinder_client.return_value = self.cinder_client
|
mock_cinder_client.return_value = self.cinder_client
|
||||||
|
mock_nova_client.return_value = self.nova_client
|
||||||
|
mock_glance_client.return_value = self.glance_client
|
||||||
|
mock_neutron_client.return_value = self.neutron_client
|
||||||
|
checkpoint = Checkpoint()
|
||||||
|
checkpoint.resource_graph = [FakeGraphNode(value=Resource(
|
||||||
|
type='OS::Nova::Server', id='vm_id_2', name='None'),
|
||||||
|
child_nodes=[FakeGraphNode(value=Resource(
|
||||||
|
type='OS::Cinder::Volume', id='vol_id_1', name=None),
|
||||||
|
child_nodes=())])]
|
||||||
|
|
||||||
self.plugin._nova_client = mock.MagicMock()
|
call_hooks(protect_operation, checkpoint, vm_resource, self.cntxt,
|
||||||
self.plugin._nova_client.return_value = self.nova_client
|
{})
|
||||||
|
|
||||||
self.plugin._glance_client = mock.MagicMock()
|
|
||||||
self.plugin._glance_client.return_value = self.glance_client
|
|
||||||
|
|
||||||
self.plugin._neutron_client = mock.MagicMock()
|
|
||||||
self.plugin._neutron_client.return_value = self.neutron_client
|
|
||||||
|
|
||||||
self.plugin.create_backup(self.cntxt, self.checkpoint,
|
|
||||||
node=vm_node,
|
|
||||||
backup_name=backup_name)
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
fake_bank._plugin._objects[
|
fake_bank._plugin._objects[
|
||||||
"/resource_data/checkpoint_id/vm_id_2/status"],
|
"/resource_data/checkpoint_id/vm_id_2/status"],
|
||||||
constants.RESOURCE_STATUS_PROTECTING
|
constants.RESOURCE_STATUS_AVAILABLE
|
||||||
)
|
)
|
||||||
resource_definition = {
|
resource_definition = {
|
||||||
"resource_id": "vm_id_2",
|
"resource_id": "vm_id_2",
|
||||||
"attach_metadata": {"vol_id_1": "/dev/vdb"},
|
"attach_metadata": {"vol_id_1": "/dev/vdb"},
|
||||||
|
'boot_metadata': {'boot_device_type': 'volume'},
|
||||||
"server_metadata": {
|
"server_metadata": {
|
||||||
"availability_zone": "nova",
|
"availability_zone": "nova",
|
||||||
"networks": ["network_id_2"],
|
"networks": ["network_id_2"],
|
||||||
@ -356,6 +392,15 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
"key_name": None,
|
"key_name": None,
|
||||||
"security_groups": "default",
|
"security_groups": "default",
|
||||||
},
|
},
|
||||||
|
'attach_metadata': {
|
||||||
|
'vol_id_1': {'attachment_id': '',
|
||||||
|
'bootable': '',
|
||||||
|
'device': '/dev/vdb',
|
||||||
|
'host_name': '',
|
||||||
|
'id': 'attach_id_1',
|
||||||
|
'server_id': 'vm_id_2',
|
||||||
|
'volume_id': 'vol_id_1'}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
fake_bank._plugin._objects[
|
fake_bank._plugin._objects[
|
||||||
@ -363,12 +408,11 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
resource_definition
|
resource_definition
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete_backup(self):
|
@mock.patch('karbor.services.protection.clients.glance.create')
|
||||||
|
def test_delete_backup(self, mock_glance_client):
|
||||||
resource = Resource(id="vm_id_1",
|
resource = Resource(id="vm_id_1",
|
||||||
type=constants.SERVER_RESOURCE_TYPE,
|
type=constants.SERVER_RESOURCE_TYPE,
|
||||||
name="fake_vm")
|
name="fake_vm")
|
||||||
resource_node = ResourceNode(value=resource,
|
|
||||||
child_nodes=[])
|
|
||||||
|
|
||||||
fake_bank._plugin._objects[
|
fake_bank._plugin._objects[
|
||||||
"/resource_data/checkpoint_id/vm_id_1/metadata"] = {
|
"/resource_data/checkpoint_id/vm_id_1/metadata"] = {
|
||||||
@ -390,8 +434,8 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.plugin._glance_client = mock.MagicMock()
|
delete_operation = self.plugin.get_delete_operation(resource)
|
||||||
self.plugin._glance_client.return_value = self.glance_client
|
mock_glance_client.return_value = self.glance_client
|
||||||
|
|
||||||
fake_bank._plugin._objects[
|
fake_bank._plugin._objects[
|
||||||
"/resource_data/checkpoint_id/vm_id_1/data_0"
|
"/resource_data/checkpoint_id/vm_id_1/data_0"
|
||||||
@ -400,8 +444,8 @@ class NovaProtectionPluginTest(base.TestCase):
|
|||||||
"/resource_data/checkpoint_id/vm_id_1/data_1"
|
"/resource_data/checkpoint_id/vm_id_1/data_1"
|
||||||
] = "image_data_1"
|
] = "image_data_1"
|
||||||
|
|
||||||
self.plugin.delete_backup(self.cntxt, self.checkpoint,
|
call_hooks(delete_operation, self.checkpoint, resource, self.cntxt,
|
||||||
node=resource_node)
|
{})
|
||||||
|
|
||||||
def test_get_supported_resources_types(self):
|
def test_get_supported_resources_types(self):
|
||||||
types = self.plugin.get_supported_resources_types()
|
types = self.plugin.get_supported_resources_types()
|
||||||
|
Loading…
Reference in New Issue
Block a user