170 lines
6.1 KiB
Python
170 lines
6.1 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 collections
|
|
import logging
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
|
|
try:
|
|
import funcsigs as inspect # Python 2.7
|
|
except ImportError:
|
|
import inspect
|
|
import six
|
|
|
|
from ospurge import exceptions
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
import argparse # noqa: F401
|
|
import openstack.connection as os_connection # noqa: F401
|
|
from typing import Optional # noqa: F401
|
|
|
|
|
|
class MatchSignaturesMeta(type):
|
|
def __init__(self, clsname, bases, clsdict):
|
|
super(MatchSignaturesMeta, self).__init__(clsname, bases, clsdict)
|
|
sup = super(self, self) # type: ignore # See python/mypy #857
|
|
for name, value in clsdict.items():
|
|
if name.startswith('_') or not callable(value):
|
|
continue
|
|
|
|
# Get the previous definition (if any) and compare the signatures
|
|
prev_dfn = getattr(sup, name, None)
|
|
if prev_dfn:
|
|
prev_sig = inspect.signature(prev_dfn)
|
|
val_sig = inspect.signature(value)
|
|
if prev_sig != val_sig:
|
|
value_name = getattr(value, '__qualname__', value.__name__)
|
|
logging.warning('Signature mismatch in %s. %s != %s',
|
|
value_name, prev_sig, val_sig)
|
|
|
|
|
|
if six.PY3:
|
|
class OrderedMeta(type):
|
|
def __new__(cls, clsname, bases, clsdict):
|
|
ordered_methods = cls.ordered_methods
|
|
allowed_next_methods = list(ordered_methods)
|
|
for name, value in clsdict.items():
|
|
if name not in ordered_methods:
|
|
continue
|
|
|
|
if name not in allowed_next_methods:
|
|
value_name = value.__qualname__
|
|
logging.warning(
|
|
"Method %s not defined at the correct location."
|
|
" Methods in class %s must be defined in the following"
|
|
" order %r",
|
|
value_name, clsname, ordered_methods
|
|
)
|
|
continue # pragma: no cover
|
|
|
|
_slice = slice(allowed_next_methods.index(name) + 1, None)
|
|
allowed_next_methods = allowed_next_methods[_slice]
|
|
|
|
# Cast to dict is required. We can't pass an OrderedDict here.
|
|
return super().__new__(cls, clsname, bases, dict(clsdict))
|
|
|
|
@classmethod
|
|
def __prepare__(cls, clsname, bases):
|
|
return collections.OrderedDict()
|
|
|
|
class CodingStyleMixin(OrderedMeta, MatchSignaturesMeta, abc.ABCMeta):
|
|
ordered_methods = ['order', 'check_prerequisite', 'list',
|
|
'should_delete', 'delete', 'to_string']
|
|
else: # pragma: no cover here
|
|
# OrderedMeta is not supported on Python 2. Class members are unordered in
|
|
# Python 2 and __prepare__() was introduced in Python 3.
|
|
class CodingStyleMixin(MatchSignaturesMeta, abc.ABCMeta):
|
|
pass
|
|
|
|
|
|
class BaseServiceResource(object):
|
|
def __init__(self):
|
|
self.cleanup_project_id = None # type: Optional[str]
|
|
self.cloud = None # type: Optional[os_connection.Connection]
|
|
self.options = None # type: Optional[argparse.Namespace]
|
|
|
|
|
|
class ServiceResource(six.with_metaclass(CodingStyleMixin,
|
|
BaseServiceResource)):
|
|
ORDER = None # type: int
|
|
|
|
def __init__(self, creds_manager):
|
|
super(ServiceResource, self).__init__()
|
|
if self.ORDER is None:
|
|
raise ValueError(
|
|
'Class {}.{} must override the "ORDER" class attribute'.format(
|
|
self.__module__, self.__class__.__name__) # type: ignore
|
|
)
|
|
|
|
self.cleanup_project_id = creds_manager.project_id
|
|
self.cloud = creds_manager.cloud
|
|
self.options = creds_manager.options
|
|
|
|
@classmethod
|
|
def order(cls):
|
|
return cls.ORDER
|
|
|
|
def check_prerequisite(self):
|
|
return True
|
|
|
|
@abc.abstractmethod
|
|
def list(self):
|
|
raise NotImplementedError
|
|
|
|
def should_delete(self, resource):
|
|
project_id = resource.get('project_id', resource.get('tenant_id'))
|
|
if project_id:
|
|
return project_id == self.cleanup_project_id
|
|
else:
|
|
# Uncomment the following line once the SDK and all OpenStack
|
|
# services returns the resource owner. In the mean time no need
|
|
# to be worrying.
|
|
# logging.warning("Can't determine owner of resource %s", resource)
|
|
return True
|
|
|
|
@abc.abstractmethod
|
|
def delete(self, resource):
|
|
raise NotImplementedError
|
|
|
|
def disable(self, resource):
|
|
msg = "The disable feature is not supported for %s, No action will" \
|
|
"be taken against the resource(id=%s, name=%s)."
|
|
logging.warning(
|
|
msg, self.__class__.__name__,
|
|
resource.get('id'), resource.get('name')
|
|
)
|
|
|
|
@staticmethod
|
|
@abc.abstractmethod
|
|
def to_str(resource):
|
|
raise NotImplementedError
|
|
|
|
def wait_for_check_prerequisite(self, exit):
|
|
timeout = time.time() + 120
|
|
sleep = 2
|
|
while time.time() < timeout:
|
|
if exit.is_set():
|
|
raise RuntimeError(
|
|
"Resource manager exited because it was interrupted or "
|
|
"another resource manager failed"
|
|
)
|
|
if self.check_prerequisite():
|
|
break
|
|
logging.info("Waiting for check_prerequisite() in %s",
|
|
self.__class__.__name__)
|
|
time.sleep(sleep)
|
|
sleep = min(sleep * 2, 8)
|
|
else:
|
|
raise exceptions.TimeoutError(
|
|
"Timeout exceeded waiting for check_prerequisite()")
|