karbor/karbor/services/protection/bank_plugin.py

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(r'^[A-Za-z0-9/_.\-@]+(?<!/)$')
_KEY_DOT_VALIDATION = re.compile(r'/\.{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(r'^/?[A-Za-z0-9/_.\-@]*/?$')
_SECTION_DOT_VALIDATION = re.compile(r'/\.{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])