karbor/karbor/services/protection/bank_plugin.py
chenying d85ec6aa58 Add content parameter for checkpoint and bank modules
Add optional content parameter for checkpoint interface, so that other
checkpoint bank plugin like database bank plugin can be introduced to
Karbor.

Change-Id: I4eaad0b3fe38cb95a668b83acba39cd831bbb7df
Closes-Bug: #1745909
2018-01-31 14:52:10 +08:00

306 lines
8.7 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.
import abc
import os
import re
import six
from karbor import exception
from karbor.i18n import _
@six.add_metaclass(abc.ABCMeta)
class LeasePlugin(object):
@abc.abstractmethod
def acquire_lease(self):
pass
@abc.abstractmethod
def renew_lease(self):
pass
@abc.abstractmethod
def check_lease_validity(self):
pass
@six.add_metaclass(abc.ABCMeta)
class BankPlugin(object):
def __init__(self, config=None):
super(BankPlugin, self).__init__()
self._config = config
@abc.abstractmethod
def update_object(self, key, value, context=None):
return
@abc.abstractmethod
def get_object(self, key, context=None):
return
@abc.abstractmethod
def list_objects(self, prefix=None, limit=None, marker=None,
sort_dir=None, context=None):
return
@abc.abstractmethod
def delete_object(self, key, context=None):
return
@abc.abstractmethod
def get_owner_id(self, context=None):
return
def validate_key(key):
pass
def validate_dir(key):
pass
class Bank(object):
_KEY_VALIDATION = re.compile('^[A-Za-z0-9/_.\-@]+(?<!/)$')
_KEY_DOT_VALIDATION = re.compile('/\.{1,2}(/|$)')
def __init__(self, plugin):
super(Bank, self).__init__()
self._plugin = plugin
def _normalize_key(self, key):
"""Normalizes the key
To prevent small errors regarding path joining we define that
banks use path normalization similar to file systems.
This means that all paths are relative to '/' and that
'/path//dir' == '/path/dir'
"""
res = os.path.normpath(key)
if not res.startswith("/"):
res = "/" + res
if key.endswith("/"):
res += "/"
return res
@classmethod
def _validate_key(cls, key):
if not isinstance(key, six.string_types):
raise exception.InvalidParameterValue(
err=_('Key must be a string')
)
if not cls._KEY_VALIDATION.match(key):
raise exception.InvalidParameterValue(
err=_('Only alphanumeric, underscore, dash, dots, at signs, '
'and slashes are allowed. Key: "%s"') % key
)
if cls._KEY_DOT_VALIDATION.match(key):
raise exception.InvalidParameterValue(
err=_('Invalid parameter: must not contain "." or ".." parts')
)
def update_object(self, key, value, context=None):
self._validate_key(key)
return self._plugin.update_object(self._normalize_key(key), value,
context=context)
def get_object(self, key, context=None):
self._validate_key(key)
return self._plugin.get_object(self._normalize_key(key),
context=context)
def list_objects(self, prefix=None, limit=None, marker=None,
sort_dir=None, context=None):
if not prefix:
prefix = "/"
norm_prefix = self._normalize_key(prefix)
return self._plugin.list_objects(
prefix=norm_prefix,
limit=limit,
marker=marker,
sort_dir=sort_dir,
context=context
)
def delete_object(self, key, context=None):
self._validate_key(key)
return self._plugin.delete_object(self._normalize_key(key),
context=context)
def get_sub_section(self, section, is_writable=True):
return BankSection(self, section, is_writable)
@property
def is_writeable(self):
return True
def get_owner_id(self):
return self._plugin.get_owner_id()
class BankSection(object):
"""Bank Section compartmentalizes a section of a bank.
Bank section is used when an object wants to pass a section of
a bank to another entity and make sure it is only capable of
accessing part of it.
"""
_SECTION_VALIDATION = re.compile('^/?[A-Za-z0-9/_.\-@]*/?$')
_SECTION_DOT_VALIDATION = re.compile('/\.{1,2}(/|$)')
def __init__(self, bank, section, is_writable=True):
super(BankSection, self).__init__()
self._validate_section(section)
self._bank = bank
self._prefix = os.path.normpath(section)
if not self._prefix.startswith('/'):
self._prefix = '/' + self._prefix
if not self._prefix.endswith('/'):
self._prefix += '/'
self._is_writable = is_writable
def get_sub_section(self, prefix, is_writable=True):
if is_writable and not self._is_writable:
raise exception.BankReadonlyViolation()
return BankSection(self._bank, self._prefix + '/' + prefix,
self._is_writable)
@classmethod
def _validate_section(cls, section):
if not section:
raise exception.InvalidParameterValue(
err=_('Empty section')
)
if not cls._SECTION_VALIDATION.match(section):
raise exception.InvalidParameterValue(
err=_('Invalid section. Must begin and end with a slash, '
'and contain valid characters')
)
if cls._SECTION_DOT_VALIDATION.match(section):
raise exception.InvalidParameterValue(
err=_('Invalid parameter: must not contain "." or ".." parts')
)
@property
def is_writable(self):
return self._is_writable
def _prepend_prefix(self, key):
if not isinstance(key, six.string_types):
raise exception.InvalidParameterValue(
err=_('Key must be a string')
)
full_key = self._prefix + key
res = os.path.normpath(full_key)
if full_key.endswith('/'):
res += '/'
return res
@staticmethod
def _normalize_marker_with_prefix(marker, prefix):
if not isinstance(marker, six.string_types):
raise exception.InvalidParameterValue(
err=_('marker must be a string')
)
marker = prefix + marker
res = os.path.normpath(marker)
if marker.endswith('/'):
res += '/'
return res
def _validate_writable(self):
if not self.is_writable:
raise exception.BankReadonlyViolation()
def update_object(self, key, value, context=None):
self._validate_writable()
return self._bank.update_object(
self._prepend_prefix(key),
value,
context=context
)
def get_object(self, key, context=None):
return self._bank.get_object(
self._prepend_prefix(key),
context=context
)
def list_objects(self, prefix=None, limit=None, marker=None,
sort_dir=None, context=None):
if not prefix:
prefix = self._prefix
else:
prefix = self._prepend_prefix(prefix)
if marker is not None:
marker = self._normalize_marker_with_prefix(marker, prefix)
return [
key[len(self._prefix):]
for key in self._bank.list_objects(
prefix,
limit,
marker,
sort_dir,
context=context
)
]
def delete_object(self, key, context=None):
self._validate_writable()
return self._bank.delete_object(
self._prepend_prefix(key),
context=context
)
def get_owner_id(self):
return self._bank.get_owner_id()
@property
def bank(self):
return self._bank
class BankIO(object):
def __init__(self, bank_section, sorted_objects):
super(BankIO, self).__init__()
self.bank_section = bank_section
self.sorted_objects = sorted_objects
self.obj_size = len(sorted_objects)
self.length = 0
def readable(self):
return True
def __iter__(self):
return self
def read(self, length=None):
obj_index = self.length
self.length += 1
if self.length > self.obj_size:
return ''
return self.bank_section.get_object(self.sorted_objects[obj_index])