aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/core/resource.py
diff options
context:
space:
mode:
Diffstat (limited to 'vicn/core/resource.py')
-rw-r--r--vicn/core/resource.py265
1 files changed, 167 insertions, 98 deletions
diff --git a/vicn/core/resource.py b/vicn/core/resource.py
index 9355cd07..ab96daa5 100644
--- a/vicn/core/resource.py
+++ b/vicn/core/resource.py
@@ -27,6 +27,10 @@ import traceback
import types
from abc import ABC, ABCMeta
+from threading import Event as ThreadEvent
+
+# LXD workaround
+from pylxd.exceptions import NotFound as LXDAPIException
from netmodel.model.mapper import ObjectSpecification
from netmodel.model.type import String, Bool, Integer, Dict
@@ -35,12 +39,13 @@ from netmodel.util.deprecated import deprecated
from netmodel.util.singleton import Singleton
from vicn.core.attribute import Attribute, Multiplicity, Reference
from vicn.core.attribute import NEVER_SET
+from vicn.core.collection import Collection
from vicn.core.commands import ReturnValue
from vicn.core.event import Event, AttributeChangedEvent
from vicn.core.exception import VICNException, ResourceNotFound
+from vicn.core.exception import VICNWouldBlock
from vicn.core.resource_factory import ResourceFactory
from vicn.core.requirement import Requirement, Property
-from vicn.core.sa_collections import InstrumentedList, _list_decorators
from vicn.core.scheduling_algebra import SchedulingAlgebra
from vicn.core.state import ResourceState, UUID
from vicn.core.state import Operations, InstanceState
@@ -95,12 +100,24 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
The base Resource class implements all the logic related to resource
instances.
-
- See also :
+
+ See also :
* ResourceManager : logic related to class instanciation
* Resource metaclass : logic related to class construction
* ResourceFactory : logic related to available classes and mapping from
name to type
+
+ Internal attributes:
+
+ - _reverse_attributes: a dict mapping attribute objects with the class
+ that declared the reverse attribute.
+
+ For instance, a Group declares a collection of Resource objects through
+ its resources attributes. It also mentions a reverse attribute named
+ 'groups'. This means every Resource class will be equipped with a
+ groups attribute, being a collection of Group objects.
+
+ Resource._reverse_attributes = { <Attribute: groups> : Resource }
"""
__type__ = TopLevelResource
@@ -139,7 +156,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
# Cache dependencies
self._deps = None
-
+
# Internal data tag for resources
self._internal_data = dict()
@@ -168,7 +185,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
else:
resource = x
if not resource:
- raise VICNException(E_UNK_RES_NAME.format(key,
+ raise VICNException(E_UNK_RES_NAME.format(key,
self.name, self.__class__.__name__, x))
element = resource if isinstance(resource, Reference) \
else resource._state.uuid
@@ -176,13 +193,13 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
value = new_value
else:
if isinstance(value, str):
- resource = ResourceManager().by_name(value)
+ resource = ResourceManager().by_name(value)
elif isinstance(value, UUID):
- resource = ResourceManager().by_uuid(value)
+ resource = ResourceManager().by_uuid(value)
else:
resource = value
if not resource:
- raise VICNException(E_UNK_RES_NAME.format(key,
+ raise VICNException(E_UNK_RES_NAME.format(key,
self.name, self.__class__.__name__, value))
value = value if isinstance(resource, Reference) \
else resource._state.uuid
@@ -202,7 +219,6 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
default = self.get_default_collection(attr) if attr.is_collection else \
self.get_default(attr)
if vars(attr)['default'] != NEVER_SET:
- #import pdb; pdb.set_trace()
self.set(attr.name, default, blocking=False)
if issubclass(attr.type, Resource) and attr.requirements:
for req in attr.requirements:
@@ -218,7 +234,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
def __after_init__(self):
return tuple()
-
+
def __subresources__(self):
return None
@@ -248,8 +264,8 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
attribute = self.get_attribute(attribute_name)
- # Handling Lambda attributes
if hasattr(attribute, 'func') and attribute.func:
+ # Handling Lambda attributes
value = attribute.func(self)
else:
if self.is_local_attribute(attribute.name):
@@ -266,25 +282,44 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
if value is NEVER_SET:
if not allow_never_set:
- log.error(E_GET_NON_LOCAL.format(attribute_name,
+ log.error(E_GET_NON_LOCAL.format(attribute_name,
self._state.uuid))
raise NotImplementedError
- if attribute.is_collection:
- value = self.get_default_collection(attribute)
- else:
- if attribute.auto:
- # Automatic instanciation
- if attribute.requirements:
- log.warning('Ignored requirements {}'.format(
- attribute.requirements))
- value = self.auto_instanciate(attribute)
-
- if value is NEVER_SET:
- value = self.get_default(attribute)
-
- if self.is_local_attribute(attribute.name):
- self.set(attribute.name, value)
+ # node.routing_table is local and auto, so this needs to be tested first...
+ if attribute.auto:
+ # Automatic instanciation
+ #
+ # Used for instance in route.node.routing_table.routes
+ if attribute.requirements:
+ log.warning('Ignored requirements {}'.format(
+ attribute.requirements))
+ value = self.auto_instanciate(attribute)
+
+ if value is NEVER_SET:
+ if self.is_local_attribute(attribute.name):
+ if attribute.is_collection:
+ value = self.get_default_collection(attribute)
+ else:
+ value = self.get_default(attribute)
+ self.set(attribute.name, value)
+ else:
+ log.info("Fetching remote value for {}.{}".format(self,attribute.name))
+ task = getattr(self, "_get_{}".format(attribute.name))()
+ #XXX This is ugly but it prevents the LxdNotFound exception
+ while True:
+ try:
+ rv = task.execute_blocking()
+ break
+ except LxdAPIException:
+ log.warning("LxdAPIException, retrying to fetch value")
+ continue
+ except Exception as e:
+ import traceback; traceback.print_tb(e.__traceback__)
+ log.error("Failed to retrieve remote value for {} on {}".format(attribute.name, self))
+ import os; os._exit(1)
+ value = rv[attribute.name]
+ vars(self)[attribute.name] = value
if unref and isinstance(value, UUID):
value = self.from_uuid(value)
@@ -297,6 +332,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
return value
+ # XXX async_get should not be blocking
async def async_get(self, attribute_name, default=NEVER_SET, unref=True,
resolve=True, allow_never_set=False, blocking=True):
attribute = self.get_attribute(attribute_name)
@@ -318,14 +354,14 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
# exists
value = vars(self).get(attribute.name, NEVER_SET)
if value is NEVER_SET:
- await self._state.manager.attribute_get(self,
+ await self._state.manager.attribute_get(self,
attribute_name, value)
value = vars(self).get(attribute.name, NEVER_SET)
# Handling NEVER_SET
if value is NEVER_SET:
if not allow_never_set:
- log.error(E_GET_NON_LOCAL.format(attribute_name,
+ log.error(E_GET_NON_LOCAL.format(attribute_name,
self._state.uuid))
raise NotImplementedError
@@ -366,27 +402,19 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
"""
attribute = self.get_attribute(attribute_name)
- if set_reverse and attribute.reverse_name:
+ # Let's transform value if not in the proper format
+ if attribute.is_collection and not isinstance(value, Collection):
+ value = Collection.from_list(value, self, attribute)
+ else:
+ if isinstance(value, UUID):
+ value = self.from_uuid(value)
+
+ if set_reverse and attribute.reverse_name:
for base in self.__class__.mro():
if not hasattr(base, '_reverse_attributes'):
continue
for ra in base._reverse_attributes.get(attribute, list()):
- # Value information : we need resources, not uuids
- if attribute.is_collection:
- lst = list()
- if value:
- for x in value:
- if isinstance(x, UUID):
- x = self.from_uuid(x)
- lst.append(x)
- value = InstrumentedList(lst)
- value._attribute = attribute
- value._instance = self
- else:
- if isinstance(value, UUID):
- value = self.from_uuid(value)
-
if ra.multiplicity == Multiplicity.OneToOne:
if value is not None:
value.set(ra.name, self, set_reverse = False)
@@ -400,30 +428,23 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
else:
value is None
elif ra.multiplicity == Multiplicity.ManyToMany:
- collection = value.get(ra.name)
- value.extend(self)
-
- # Handling value : we need uuids, not resources
- if attribute.is_collection:
- if not isinstance(value, InstrumentedList):
- lst = list()
- if value:
- for x in value:
- if isinstance(x, Resource):
- x = x.get_uuid()
- lst.append(x)
-
- value = InstrumentedList(lst)
- else:
- value = InstrumentedList([])
- value._attribute = attribute
- value._instance = self
- else:
- if isinstance(value, Resource):
- value = value.get_uuid()
+ # Example:
+ # _set(self, attribute_name)
+ # self = Resource()
+ # attribute_name = <Attribute groups>
+ # value = <Collection 140052309461896 [<Group: topology resources=[], name=topology, owner=None, managed=True>]>
+ # element = <Group: ...>
+
+ # We add each element of the collection to the remote
+ # attribute which is also a collection
+ for element in value:
+ collection = element.get(ra.name)
+ # XXX duplicates ?
+ collection.append(self)
+
return value
- def set(self, attribute_name, value, current=False, set_reverse=True,
+ def set(self, attribute_name, value, current=False, set_reverse=True,
blocking = True):
value = self._set(attribute_name, value, current=current,
set_reverse=set_reverse)
@@ -479,14 +500,11 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
if attribute.default._resource is Self:
default = getattr(self, attribute.default._attribute)
else:
- default = getattr(attribute.default._resource,
+ default = getattr(attribute.default._resource,
attribute.default._attribute)
else:
default = attribute.default
- value = InstrumentedList(default)
- value._attribute = attribute
- value._instance = self
- return value
+ return Collection.from_list(default, self, attribute)
def get_default(self, attribute):
if isinstance(attribute.default, types.FunctionType):
@@ -495,7 +513,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
if attribute.default._resource is Self:
value = getattr(self, attribute.default._attribute)
else:
- value = getattr(attribute.default._resource,
+ value = getattr(attribute.default._resource,
attribute.default._attribute)
else:
value = copy.deepcopy(attribute.default)
@@ -525,17 +543,22 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
@classmethod
def _sanitize(cls):
- """Sanitize the object model to accomodate for multiple declaration
- styles
+ """
+ This methods performs sanitization of the object declaration.
+
+ More specifically:
+ - it goes over all attributes and sets their name based on the python
+ object attribute name.
+ - it establishes mutual object relationships through reverse attributes.
- In particular, this method:
- - set names to all attributes
"""
cls._reverse_attributes = dict()
cur_reverse_attributes = dict()
for name, obj in vars(cls).items():
if not isinstance(obj, ObjectSpecification):
continue
+
+ # XXX it seems obj should always be an attribute, confirm !
if isinstance(obj, Attribute):
obj.name = name
@@ -555,23 +578,62 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
#
# NOTE: we need to do this after merging to be sure we get all
# properties inherited from parent (eg. multiplicity)
+ #
+ # See "Reverse attributes" section in BaseResource docstring.
+ #
+ # Continueing with the same example, let's detail how it is handled:
+ #
+ # Original declaration:
+ # >>>
+ # class Group(Resource):
+ # resources = Attribute(Resource, description = 'Resources belonging to the group',
+ # multiplicity = Multiplicity.ManyToMany,
+ # default = [],
+ # reverse_name = 'groups',
+ # reverse_description = 'Groups to which the resource belongs')
+ # <<<
+ #
+ # Local variables:
+ # cls = <class 'vicn.resource.group.Group'>
+ # obj = <Attribute resources>
+ # obj.type = <class 'vicn.core.Resource'>
+ # reverse_attribute = <Attribute groups>
+ #
+ # Result:
+ # 1) Group._reverse_attributes =
+ # { <Attribute resources> : [<Attribute groups>, ...], ...}
+ # 2) Add attribute <Attribute groups> to class Resource
+ # 3) Resource._reverse_attributes =
+ # { <Attribute groups> : [<Attribute resources], ...], ...}
+ #
if has_reverse:
a = {
- 'name' : obj.reverse_name,
- 'description' : obj.reverse_description,
- 'multiplicity' : Multiplicity.reverse(obj.multiplicity),
- 'auto' : obj.reverse_auto,
+ 'name' : obj.reverse_name,
+ 'description' : obj.reverse_description,
+ 'multiplicity' : Multiplicity.reverse(obj.multiplicity),
+ 'reverse_name' : obj.name,
+ 'reverse_description' : obj.description,
+ 'auto' : obj.reverse_auto,
}
reverse_attribute = Attribute(cls, **a)
reverse_attribute.is_aggregate = True
+ # 1) Store the reverse attributes to be later inserted in the
+ # remote class, at the end of the function
+ # TODO : clarify the reasons to perform this in two steps
cur_reverse_attributes[obj.type] = reverse_attribute
- #print('*** class backref ***', cls, obj, reverse_attribute)
+ # 2)
if not obj in cls._reverse_attributes:
cls._reverse_attributes[obj] = list()
cls._reverse_attributes[obj].append(reverse_attribute)
+ # 3)
+ if not reverse_attribute in obj.type._reverse_attributes:
+ obj.type._reverse_attributes[reverse_attribute] = list()
+ obj.type._reverse_attributes[reverse_attribute].append(obj)
+
+ # Insert newly created reverse attributes in the remote class
for kls, a in cur_reverse_attributes.items():
setattr(kls, a.name, a)
@@ -583,7 +645,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
continue
if attribute.is_aggregate and not aggregates:
continue
-
+
yield attribute
def iter_keys(self):
@@ -617,7 +679,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
for req in reqs:
if req._type != attribute.name:
continue
-
+
for attr_name, prop in req.properties.items():
value = next(iter(prop.value))
capabilities |= req._capabilities
@@ -626,7 +688,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
cls = self._state.manager.get_resource_with_capabilities(
attribute.type, capabilities)
- # Before creating a new instance of a class, let's check
+ # Before creating a new instance of a class, let's check
resource = cls(**cstr_attributes)
self._state.manager.commit_resource(resource)
@@ -636,10 +698,10 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
return list(self.iter_attributes(aggregates = aggregates))
def get_attribute_names(self, aggregates = False):
- return set(a.name
+ return set(a.name
for a in self.iter_attributes(aggregates = aggregates))
- def get_attribute_dict(self, field_names = None, aggregates = False,
+ def get_attribute_dict(self, field_names = None, aggregates = False,
uuid = True):
assert not field_names or field_names.is_star()
attributes = self.get_attributes(aggregates = aggregates)
@@ -653,11 +715,11 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
ret[a.name] = list()
for x in value:
if uuid and isinstance(x, Resource):
- x = x._state.uuid._uuid
+ x = x._state.uuid._uuid
ret[a.name].append(x)
else:
if uuid and isinstance(value, Resource):
- value = value._state.uuid._uuid
+ value = value._state.uuid._uuid
ret[a.name] = value
return ret
@@ -673,7 +735,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
self._state.state = state
def get_types(self):
- return [cls.__name__.lower() for cls in self.__class__.mro()
+ return [cls.__name__.lower() for cls in self.__class__.mro()
if cls.__name__ not in ('ABC', 'BaseType', 'object')]
def get_type(self):
@@ -686,12 +748,20 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
# Showing aggregate attributes can cause infinite loops
name = self._state.uuid if self.name in (None, NEVER_SET) else self.name
return '<{}: {} {}>'.format(self.__class__.__name__, name,
- ', '.join('{}={}'.format(k,v)
- for k, v in self.get_attribute_dict().items()))
+ ', '.join('{}={}'.format(k,v)
+ for k, v in self.get_attribute_dict().items()))
def __str__(self):
return self.__repr__()
+ def to_dict(self):
+ dic = self.get_attribute_dict(aggregates = True)
+ dic['id'] = self._state.uuid._uuid
+ dic['type'] = self.get_types()
+ dic['state'] = self._state.state
+ dic['log'] = self._state.log
+ return dic
+
#---------------------------------------------------------------------------
# Resource helpers
#---------------------------------------------------------------------------
@@ -709,7 +779,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
if not value:
continue
- if a.multiplicity in (Multiplicity.OneToOne,
+ if a.multiplicity in (Multiplicity.OneToOne,
Multiplicity.ManyToOne):
resource = value
if not resource:
@@ -742,13 +812,12 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
l.extend(list(args))
if id:
N = 3
- uuid = ''.join(random.choice(string.ascii_uppercase +
+ uuid = ''.join(random.choice(string.ascii_uppercase +
string.digits) for _ in range(N))
l.append(uuid)
name = NAME_SEP.join(str(x) for x in l)
return name
-
def check_requirements(self):
for attr in self.iter_attributes():
if issubclass(attr.type, Resource) and attr.requirements:
@@ -762,7 +831,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
@deprecated
def trigger(self, action, attribute_name, *args, **kwargs):
- self._state.manager.trigger(self, action, attribute_name,
+ self._state.manager.trigger(self, action, attribute_name,
*args, **kwargs)
#--------------------------------------------------------------------------
@@ -816,7 +885,7 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
return hasattr(self, '_{}_{}'.format(action, attribute.name))
def is_setup(self):
- return self.state in (ResourceState.SETUP_PENDING,
+ return self.state in (ResourceState.SETUP_PENDING,
ResourceState.SETUP, ResourceState.DIRTY)
__get__ = None
@@ -851,7 +920,7 @@ class CompositionMixin:
await element._state.clean.wait()
self._state.clean.set()
-_Resource, EmptyResource = SchedulingAlgebra(BaseResource, ConcurrentMixin,
+_Resource, EmptyResource = SchedulingAlgebra(BaseResource, ConcurrentMixin,
CompositionMixin, SequentialMixin)
class ManagedResource(_Resource):