425 lines
16 KiB
Python
425 lines
16 KiB
Python
# 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 datetime import datetime
|
|
from karbor.common import constants
|
|
from karbor import exception
|
|
from karbor.i18n import _
|
|
from karbor.services.protection import graph
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import timeutils
|
|
from oslo_utils import uuidutils
|
|
|
|
CONF = cfg.CONF
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_INDEX_FILE_NAME = "index.json"
|
|
_UUID_STR_LEN = 36
|
|
|
|
|
|
class Checkpoint(object):
|
|
VERSION = "0.9"
|
|
SUPPORTED_VERSIONS = ["0.9"]
|
|
|
|
def __init__(self, checkpoint_section, indices_section,
|
|
bank_lease, checkpoint_id):
|
|
super(Checkpoint, self).__init__()
|
|
self._id = checkpoint_id
|
|
self._checkpoint_section = checkpoint_section
|
|
self._indices_section = indices_section
|
|
self._bank_lease = bank_lease
|
|
self.reload_meta_data()
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"id": self.id,
|
|
"status": self.status,
|
|
"protection_plan": self.protection_plan,
|
|
"extra_info": self._md_cache.get("extra_info", None),
|
|
"project_id": self.project_id,
|
|
"resource_graph": self._md_cache.get("resource_graph", None),
|
|
"created_at": self._md_cache.get("created_at", None)
|
|
}
|
|
|
|
@property
|
|
def checkpoint_section(self):
|
|
return self._checkpoint_section
|
|
|
|
@property
|
|
def id(self):
|
|
return self._id
|
|
|
|
@property
|
|
def provider_id(self):
|
|
return self._md_cache["provider_id"]
|
|
|
|
@property
|
|
def created_at(self):
|
|
return self._md_cache["created_at"]
|
|
|
|
@property
|
|
def status(self):
|
|
# TODO(saggi): check for valid values and transitions
|
|
return self._md_cache["status"]
|
|
|
|
@property
|
|
def extra_info(self):
|
|
return self._md_cache["extra_info"]
|
|
|
|
@property
|
|
def project_id(self):
|
|
return self._md_cache["project_id"]
|
|
|
|
@property
|
|
def owner_id(self):
|
|
# TODO(yinwei): check for valid values and transitions
|
|
return self._md_cache["owner_id"]
|
|
|
|
@property
|
|
def resource_graph(self):
|
|
serialized_resource_graph = self._md_cache.get("resource_graph", None)
|
|
if serialized_resource_graph is not None:
|
|
resource_graph = graph.deserialize_resource_graph(
|
|
serialized_resource_graph)
|
|
return resource_graph
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def protection_plan(self):
|
|
return self._md_cache["protection_plan"]
|
|
|
|
@status.setter
|
|
def status(self, value):
|
|
self._md_cache["status"] = value
|
|
|
|
@extra_info.setter
|
|
def extra_info(self, value):
|
|
self._md_cache["extra_info"] = value
|
|
|
|
@resource_graph.setter
|
|
def resource_graph(self, resource_graph):
|
|
serialized_resource_graph = graph.serialize_resource_graph(
|
|
resource_graph)
|
|
self._md_cache["resource_graph"] = serialized_resource_graph
|
|
|
|
def _is_supported_version(self, version):
|
|
return version in self.SUPPORTED_VERSIONS
|
|
|
|
def _assert_supported_version(self, new_md):
|
|
if new_md["version"] not in self.SUPPORTED_VERSIONS:
|
|
# Something bad happened invalidate the object
|
|
self._md_cache = None
|
|
self._checkpoint_section = None
|
|
raise RuntimeError(
|
|
_("Checkpoint was created in an unsupported version"))
|
|
|
|
def reload_meta_data(self):
|
|
try:
|
|
new_md = self._checkpoint_section.get_object(_INDEX_FILE_NAME)
|
|
except exception.BankGetObjectFailed:
|
|
LOG.error("unable to reload metadata for checkpoint id: %s",
|
|
self.id)
|
|
raise exception.CheckpointNotFound(checkpoint_id=self.id)
|
|
self._assert_supported_version(new_md)
|
|
self._md_cache = new_md
|
|
|
|
@classmethod
|
|
def _generate_id(self):
|
|
return uuidutils.generate_uuid()
|
|
|
|
@classmethod
|
|
def get_by_section(cls, checkpoints_section, indices_section,
|
|
bank_lease, checkpoint_id, context=None):
|
|
# TODO(yuvalbr) add validation that the checkpoint exists
|
|
checkpoint_section = checkpoints_section.get_sub_section(checkpoint_id)
|
|
return Checkpoint(checkpoint_section, indices_section,
|
|
bank_lease, checkpoint_id)
|
|
|
|
@staticmethod
|
|
def _get_checkpoint_path_by_provider(
|
|
provider_id, project_id, timestamp, checkpoint_id):
|
|
return "/by-provider/%s/%s/%s@%s" % (
|
|
provider_id, project_id, timestamp, checkpoint_id)
|
|
|
|
@staticmethod
|
|
def _get_checkpoint_path_by_plan(
|
|
plan_id, project_id, created_at, timestamp, checkpoint_id):
|
|
return "/by-plan/%s/%s/%s/%s@%s" % (
|
|
plan_id, project_id, created_at, timestamp, checkpoint_id)
|
|
|
|
@staticmethod
|
|
def _get_checkpoint_path_by_date(
|
|
created_at, project_id, timestamp, checkpoint_id):
|
|
return "/by-date/%s/%s/%s@%s" % (
|
|
created_at, project_id, timestamp, checkpoint_id)
|
|
|
|
@classmethod
|
|
def create_in_section(cls, checkpoints_section, indices_section,
|
|
bank_lease, owner_id, plan,
|
|
checkpoint_id=None, checkpoint_properties=None,
|
|
context=None):
|
|
checkpoint_id = checkpoint_id or cls._generate_id()
|
|
checkpoint_section = checkpoints_section.get_sub_section(checkpoint_id)
|
|
|
|
timestamp = timeutils.utcnow_ts()
|
|
created_at = timeutils.utcnow().strftime('%Y-%m-%d')
|
|
|
|
provider_id = plan.get("provider_id")
|
|
project_id = plan.get("project_id")
|
|
extra_info = None
|
|
checkpoint_status = constants.CHECKPOINT_STATUS_PROTECTING
|
|
if checkpoint_properties:
|
|
extra_info = checkpoint_properties.get("extra_info", None)
|
|
status = checkpoint_properties.get("status", None)
|
|
if status:
|
|
checkpoint_status = status
|
|
checkpoint_section.update_object(
|
|
key=_INDEX_FILE_NAME,
|
|
value={
|
|
"version": cls.VERSION,
|
|
"id": checkpoint_id,
|
|
"status": checkpoint_status,
|
|
"owner_id": owner_id,
|
|
"provider_id": provider_id,
|
|
"project_id": project_id,
|
|
"protection_plan": {
|
|
"id": plan.get("id"),
|
|
"name": plan.get("name"),
|
|
"provider_id": plan.get("provider_id"),
|
|
"resources": plan.get("resources")
|
|
},
|
|
"extra_info": extra_info,
|
|
"created_at": created_at,
|
|
"timestamp": timestamp
|
|
},
|
|
context=context
|
|
)
|
|
|
|
indices_section.update_object(
|
|
key=cls._get_checkpoint_path_by_provider(
|
|
provider_id, project_id, timestamp, checkpoint_id),
|
|
value=checkpoint_id,
|
|
context=context
|
|
)
|
|
|
|
indices_section.update_object(
|
|
key=cls._get_checkpoint_path_by_date(
|
|
created_at, project_id, timestamp, checkpoint_id),
|
|
value=checkpoint_id,
|
|
context=context
|
|
)
|
|
|
|
indices_section.update_object(
|
|
key=cls._get_checkpoint_path_by_plan(
|
|
plan.get("id"), project_id, created_at, timestamp,
|
|
checkpoint_id),
|
|
value=checkpoint_id,
|
|
context=context)
|
|
|
|
return Checkpoint(checkpoint_section,
|
|
indices_section,
|
|
bank_lease,
|
|
checkpoint_id)
|
|
|
|
def commit(self, context=None):
|
|
self._checkpoint_section.update_object(
|
|
key=_INDEX_FILE_NAME,
|
|
value=self._md_cache,
|
|
context=context
|
|
)
|
|
|
|
def purge(self, context=None):
|
|
"""Purge the index file of the checkpoint.
|
|
|
|
Can only be done if the checkpoint has no other files apart from the
|
|
index.
|
|
"""
|
|
all_objects = self._checkpoint_section.list_objects()
|
|
if len(all_objects) == 1 and all_objects[0] == _INDEX_FILE_NAME:
|
|
created_at = self._md_cache["created_at"]
|
|
timestamp = self._md_cache["timestamp"]
|
|
plan_id = self._md_cache["protection_plan"]["id"]
|
|
provider_id = self._md_cache["protection_plan"]["provider_id"]
|
|
project_id = self._md_cache["project_id"]
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_provider(
|
|
provider_id, project_id, timestamp, self.id))
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_date(
|
|
created_at, project_id, timestamp, self.id))
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_plan(
|
|
plan_id, project_id, created_at, timestamp, self.id))
|
|
|
|
self._checkpoint_section.delete_object(_INDEX_FILE_NAME)
|
|
else:
|
|
raise RuntimeError(_("Could not delete: Checkpoint is not empty"))
|
|
|
|
def delete(self, context=None):
|
|
self.status = constants.CHECKPOINT_STATUS_DELETED
|
|
self.commit(context=context)
|
|
# delete indices
|
|
created_at = self._md_cache["created_at"]
|
|
timestamp = self._md_cache["timestamp"]
|
|
plan_id = self._md_cache["protection_plan"]["id"]
|
|
provider_id = self._md_cache["protection_plan"]["provider_id"]
|
|
project_id = self._md_cache["project_id"]
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_provider(
|
|
provider_id, project_id, timestamp, self.id),
|
|
context=context)
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_date(
|
|
created_at, project_id, timestamp, self.id),
|
|
context=context)
|
|
self._indices_section.delete_object(
|
|
self._get_checkpoint_path_by_plan(
|
|
plan_id, project_id, created_at, timestamp, self.id),
|
|
context=context)
|
|
|
|
def get_resource_bank_section(self, resource_id):
|
|
prefix = "/resource-data/%s/" % resource_id
|
|
return self._checkpoint_section.get_sub_section(prefix)
|
|
|
|
|
|
class CheckpointCollection(object):
|
|
|
|
def __init__(self, bank, bank_lease=None):
|
|
super(CheckpointCollection, self).__init__()
|
|
self._bank = bank
|
|
self._bank_lease = bank_lease
|
|
self._checkpoints_section = bank.get_sub_section("/checkpoints")
|
|
self._indices_section = bank.get_sub_section("/indices")
|
|
|
|
@staticmethod
|
|
def _get_prefix_and_marker_by_provider(provider_id, project_id, marker,
|
|
marker_checkpoint, all_tenants):
|
|
if all_tenants:
|
|
prefix = "/by-provider/%s/" % provider_id
|
|
marker = "/%s/%s" % (
|
|
marker_checkpoint["project_id"], marker) if marker else marker
|
|
else:
|
|
prefix = "/by-provider/%s/%s/" % (provider_id, project_id)
|
|
marker = "/%s" % marker if marker else marker
|
|
return prefix, marker
|
|
|
|
@staticmethod
|
|
def _get_prefix_and_marker_by_plan(plan_id, project_id, marker,
|
|
marker_checkpoint, all_tenants):
|
|
if all_tenants:
|
|
prefix = "/by-plan/%s/" % plan_id
|
|
marker = "/%s/%s/%s" % (
|
|
marker_checkpoint["project_id"],
|
|
marker_checkpoint["created_at"], marker) if marker else marker
|
|
else:
|
|
prefix = "/by-plan/%s/%s/" % (plan_id, project_id)
|
|
marker = "/%s/%s" % (
|
|
marker_checkpoint["created_at"], marker) if marker else marker
|
|
return prefix, marker
|
|
|
|
@staticmethod
|
|
def _get_prefix_and_marker_by_date(project_id, marker, marker_checkpoint,
|
|
all_tenants):
|
|
prefix = "/by-date/"
|
|
if all_tenants:
|
|
marker = "/%s/%s/%s" % (
|
|
marker_checkpoint["created_at"],
|
|
marker_checkpoint["project_id"], marker) if marker else marker
|
|
else:
|
|
marker = "/%s/%s/%s" % (
|
|
marker_checkpoint["created_at"], project_id,
|
|
marker) if marker else marker
|
|
return prefix, marker
|
|
|
|
def list_ids(self, project_id, provider_id, limit=None, marker=None,
|
|
plan_id=None, start_date=None, end_date=None, sort_dir=None,
|
|
context=None, all_tenants=False):
|
|
marker_checkpoint = None
|
|
if marker is not None:
|
|
checkpoint_section = self._checkpoints_section.get_sub_section(
|
|
marker)
|
|
marker_checkpoint = checkpoint_section.get_object(_INDEX_FILE_NAME)
|
|
timestamp = marker_checkpoint["timestamp"]
|
|
marker = "%s@%s" % (timestamp, marker)
|
|
|
|
if start_date is not None:
|
|
if end_date is None:
|
|
end_date = timeutils.utcnow()
|
|
|
|
if plan_id is None and start_date is None:
|
|
prefix, marker = self._get_prefix_and_marker_by_provider(
|
|
provider_id, project_id, marker,
|
|
marker_checkpoint, all_tenants)
|
|
elif plan_id is not None:
|
|
prefix, marker = self._get_prefix_and_marker_by_plan(
|
|
plan_id, project_id, marker, marker_checkpoint, all_tenants)
|
|
else:
|
|
prefix, marker = self._get_prefix_and_marker_by_date(
|
|
project_id, marker, marker_checkpoint, all_tenants)
|
|
|
|
return self._list_ids(project_id, prefix, limit, marker, start_date,
|
|
end_date, sort_dir, context=context,
|
|
all_tenants=all_tenants)
|
|
|
|
def _list_ids(self, project_id, prefix, limit, marker, start_date,
|
|
end_date, sort_dir, context=None, all_tenants=False):
|
|
if start_date is None:
|
|
return [key[key.find("@") + 1:]
|
|
for key in self._indices_section.list_objects(
|
|
prefix=prefix,
|
|
limit=limit,
|
|
marker=marker,
|
|
sort_dir=sort_dir,
|
|
context=context
|
|
)]
|
|
else:
|
|
ids = []
|
|
for key in self._indices_section.list_objects(prefix=prefix,
|
|
marker=marker,
|
|
sort_dir=sort_dir,
|
|
context=context):
|
|
date_cursor = -2 if (prefix.find('by-plan') >= 0) else -3
|
|
project_id_cursor = -3 if (prefix.find('by-plan') >= 0) else -2
|
|
date = datetime.strptime(
|
|
key.split("/")[date_cursor], "%Y-%m-%d")
|
|
checkpoint_project_id = key.split("/")[project_id_cursor]
|
|
if start_date <= date <= end_date and (
|
|
all_tenants or (
|
|
checkpoint_project_id == project_id)):
|
|
ids.append(key[key.find("@") + 1:])
|
|
if limit is not None and len(ids) == limit:
|
|
return ids
|
|
return ids
|
|
|
|
def get(self, checkpoint_id, context=None):
|
|
# TODO(saggi): handle multiple instances of the same checkpoint
|
|
return Checkpoint.get_by_section(self._checkpoints_section,
|
|
self._indices_section,
|
|
self._bank_lease,
|
|
checkpoint_id,
|
|
context=context)
|
|
|
|
def create(self, plan, checkpoint_properties=None, context=None):
|
|
# TODO(saggi): Serialize plan to checkpoint. Will be done in
|
|
# future patches.
|
|
return Checkpoint.create_in_section(
|
|
self._checkpoints_section,
|
|
self._indices_section,
|
|
self._bank_lease,
|
|
self._bank.get_owner_id(),
|
|
plan,
|
|
checkpoint_properties=checkpoint_properties,
|
|
context=context)
|