diff options
author | Jordan Augé <jordan.auge+fdio@email.com> | 2017-02-24 14:58:01 +0100 |
---|---|---|
committer | Jordan Augé <jordan.auge+fdio@cisco.com> | 2017-02-24 18:36:29 +0000 |
commit | 85a341d645b57b7cd88a26ed2ea0a314704240ea (patch) | |
tree | bdda2b35003aae20103a796f86daced160b8a730 /vicn/core/resource.py | |
parent | 9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (diff) |
Initial commit: vICN
Change-Id: I7ce66c4e84a6a1921c63442f858b49e083adc7a7
Signed-off-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Diffstat (limited to 'vicn/core/resource.py')
-rw-r--r-- | vicn/core/resource.py | 898 |
1 files changed, 898 insertions, 0 deletions
diff --git a/vicn/core/resource.py b/vicn/core/resource.py new file mode 100644 index 00000000..53ad2181 --- /dev/null +++ b/vicn/core/resource.py @@ -0,0 +1,898 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 asyncio +import copy +import logging +import operator +import random +import string +import sys +import traceback +import types + +from abc import ABC, ABCMeta + +from netmodel.model.mapper import ObjectSpecification +from netmodel.model.type import String, Bool, Integer, Dict +from netmodel.model.type import BaseType, Self +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.commands import ReturnValue +from vicn.core.event import Event, AttributeChangedEvent +from vicn.core.exception import VICNException, ResourceNotFound +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 +from vicn.core.task import run_task, BashTask + +log = logging.getLogger(__name__) + +NAME_SEP = '-' + +# Warning and error messages + +W_UNK_ATTR = 'Ignored unknown attribute {} for resource {}' +E_UNK_RES_NAME = 'Unknown resource name for attribute {} in {} ({}) : {}' +E_GET_NON_LOCAL = 'Cannot get non-local attribute {} for resource {}' +E_AUTO_UNM = 'Trying to auto-instanciate attribute {} on unmanaged resource {}' + +#------------------------------------------------------------------------------ +# Resource category +#------------------------------------------------------------------------------ + +# A base resource is not instanciated itself but uses delegates. Which one to +# use is resolved during initialization +class TopLevelResource: pass + +class FactoryResource(TopLevelResource): pass +class CategoryResource(TopLevelResource): pass + +#------------------------------------------------------------------------------ + +class ResourceMetaclass(ABCMeta): + def __init__(cls, class_name, parents, attrs): + """ + Args: + cls: The class type we're registering. + class_name: A String containing the class_name. + parents: The parent class types of 'cls'. + attrs: The attribute (members) of 'cls'. + """ + super().__init__(class_name, parents, attrs) + + # We use the metaclass to create attributes for instance, even before + # the Resource Factory is called. They are needed both for initializing + # attributes and reverse attributes, in whatever order. Only class + # creation allow us to clear _attributes, otherwise, we will just add + # those from the parent, siblings, etc... + cls._sanitize() + +#------------------------------------------------------------------------------ + +class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass): + """Base Resource class + + The base Resource class implements all the logic related to resource + instances. + + 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 + """ + + __type__ = TopLevelResource + + name = Attribute(String, description = 'Alias name for the resource') + managed = Attribute(Bool, description = 'Flag: resource is managed', + default = True) + owner = Attribute(Self, description = 'Owning resource', default = None) + data = Attribute(Dict, description = 'User data') + + #--------------------------------------------------------------------------- + # Constructor + #--------------------------------------------------------------------------- + + def __new__(cls, *args, **kwargs): + """ + We implement a "factory method" design pattern in the constructor... + """ + # Ensure the resource factory exists and has been initialized, and thus + # that Resource objects are fully created + from vicn.core.resource_mgr import ResourceManager + + ResourceFactory() + + delegate = ResourceManager().get_resource_with_capabilities(cls, set()) + if not delegate: + log.error('No delegate for abstract resource : %s', cls.__name__) + raise VICNException + + instance = super().__new__(delegate) + + return instance + + def __init__(self, *args, **kwargs): + from vicn.core.resource_mgr import ResourceManager + + # Cache dependencies + self._deps = None + + # Internal data tag for resources + self._internal_data = dict() + + mandatory = { a.name for a in self.iter_attributes() if a.mandatory } + + for key, value in kwargs.items(): + attribute = self.get_attribute(key) + if attribute is None: + log.warning(W_UNK_ATTR.format(key, self.get_type())) + continue + + if isinstance(value, Reference): + if value._resource is Self: + value = getattr(self, value._attribute) + else: + value = getattr(value._resource, value._attribute) + + if value and issubclass(attribute.type, Resource): + if attribute.is_collection: + new_value = list() + for x in value: + if isinstance(x, str): + resource = ResourceManager().by_name(x) + elif isinstance(x, UUID): + resource = ResourceManager().by_uuid(x) + else: + resource = x + if not resource: + raise VICNException(E_UNK_RES_NAME.format(key, + self.name, self.__class__.__name__, x)) + element = resource if isinstance(resource, Reference) \ + else resource._state.uuid + new_value.append(element) + value = new_value + else: + if isinstance(value, str): + resource = ResourceManager().by_name(value) + elif isinstance(value, UUID): + resource = ResourceManager().by_uuid(value) + else: + resource = value + if not resource: + raise VICNException(E_UNK_RES_NAME.format(key, + self.name, self.__class__.__name__, value)) + value = value if isinstance(resource, Reference) \ + else resource._state.uuid + self.set(key, value, blocking=False) + mandatory -= { key } + + # Check that all mandatory atttributes have been set + # Mandatory resource attributes will be marked as pending since they + # might be discovered + # Eventually, their absence will be discovered at runtime + if mandatory: + raise VICNException('Mandatory attributes not set: %r' % (mandatory,)) + + # Check requirements + for attr in self.iter_attributes(): + if issubclass(attr.type, Resource) and attr.requirements: + for req in attr.requirements: + instance = self.get(attr.name) + if instance is None: + continue + ResourceManager().add_instance_requirement(instance, req) + + self._subresources = None + + def __after__(self): + return tuple() + + def __after_init__(self): + return tuple() + + def __subresources__(self): + return None + + def set_subresources(self, subresources): + if not subresources: + return + + # Add state to operators + for sr in subresources: + if not hasattr(sr, '_state'): + sr._state = InstanceState(self._state.manager, sr) + + self._subresources = subresources + + def get_uuid(self): + return self._state.uuid + + def from_uuid(self, uuid): + return self._state.manager.by_uuid(uuid) + + #-------------------------------------------------------------------------- + # Object model + #-------------------------------------------------------------------------- + + def get(self, attribute_name, default=NEVER_SET, unref=True, resolve=True, + allow_never_set=True, blocking=True): + + attribute = self.get_attribute(attribute_name) + + # Handling Lambda attributes + if hasattr(attribute, 'func') and attribute.func: + value = attribute.func(self) + else: + if self.is_local_attribute(attribute.name): + value = vars(self).get(attribute.name, NEVER_SET) + else: + # A pending value has priority + value = self._state.dirty.get(attribute.name, NEVER_SET) + if value.value is not NEVER_SET: + value = value.value + else: + # otherwise, let's use a previously fetched value if it + # exists + value = vars(self).get(attribute.name, NEVER_SET) + + if value is NEVER_SET: + if not allow_never_set: + 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) + + if unref and isinstance(value, UUID): + value = self.from_uuid(value) + + if resolve and isinstance(value, Reference): + if value._resource is Self: + value = getattr(self, value._attribute) + else: + value = getattr(value._resource, value._attribute) + + return value + + 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) + + # Handling Lambda attributes + if hasattr(attribute, 'func') and attribute.func: + value = self.func(self) + else: + if self.is_local_attribute(attribute.name): + value = vars(self).get(attribute.name, NEVER_SET) + else: + + # A pending value has priority + value = self._state.dirty.get(attribute.name, NEVER_SET) + if value.value is not NEVER_SET: + value = value.value + else: + # otherwise, let's use a previously fetched value if it + # exists + value = vars(self).get(attribute.name, NEVER_SET) + if value is NEVER_SET: + 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, + 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 value is self.is_local_attribute(attribute.name): + self.set(attribute.name, value) + + if unref and isinstance(value, UUID): + value = self.from_uuid(value) + + if resolve and isinstance(value, Reference): + if value._resource is Self: + value = getattr(self, value._attribute) + else: + value = getattr(value._resource, value._attribute) + + return value + + def _set(self, attribute_name, value, current=False, set_reverse=True): + """ + Note that set does not automatically mark a resource dirty. + We might need a flag to avoid dirty by default, which will be useful + when a resource is modified by another resource: eg x.up, or + x.ip_address = y, ... + Returns : task that can be monitored (note that it is not scheduled) + """ + attribute = self.get_attribute(attribute_name) + + 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) + elif ra.multiplicity == Multiplicity.ManyToOne: + for element in value: + value.set(ra.name, self, set_reverse = False) + elif ra.multiplicity == Multiplicity.OneToMany: + if value is not None: + collection = value.get(ra.name) + collection.append(self) + 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() + return value + + 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) + if self.is_local_attribute(attribute_name) or current: + # super() + if value is None: + attribute = self.get_attribute(attribute_name) + vars(self)[attribute_name] = value + + else: + fut = self._state.manager.attribute_set(self, attribute_name, value) + asyncio.ensure_future(fut) + + async def async_set(self, attribute_name, value, current=False, + set_reverse=True, blocking=True): + """ + Example: + - setting the ip address on a node's interface + + We need to communicate our intention to the resource manager, which will + process our request in a centralized fashion, and do the necessary + steps for us to set the value properly. + """ + value = self._set(attribute_name, value, current=current, + set_reverse=set_reverse) + await self._state.manager.attribute_set(self, attribute_name, value, + blocking=blocking) + + def set_many(self, attribute_dict, current=False): + if not attribute_dict: + return + for k, v in attribute_dict.items(): + self.set(k, v, current=current) + + def is_set(self, attribute_name): + return attribute_name in vars(self) + +# def clean(self, attribute_name): +# return self._state.manager.attribute_clean(self, attribute_name) + + def is_local_attribute(self, attribute_name): + ACTIONS = ['get', 'set', 'add', 'remove'] + for action in ACTIONS: + method = '_{}_{}'.format(action, attribute_name) + if hasattr(self, method) and getattr(self, method) is not None: + return False + return True + + def get_default_collection(self, attribute): + if isinstance(attribute.default, types.FunctionType): + default = attribute.default(self) + elif isinstance(attribute.default, Reference): + if attribute.default._resource is Self: + default = getattr(self, attribute.default._attribute) + else: + default = getattr(attribute.default._resource, + attribute.default._attribute) + else: + default = attribute.default + value = InstrumentedList(default) + value._attribute = attribute + value._instance = self + return value + + def get_default(self, attribute): + if isinstance(attribute.default, types.FunctionType): + value = attribute.default(self) + elif isinstance(attribute.default, Reference): + if attribute.default._resource is Self: + value = getattr(self, attribute.default._attribute) + else: + value = getattr(attribute.default._resource, + attribute.default._attribute) + else: + value = copy.deepcopy(attribute.default) + return value + + def async_get_task(self, attribute_name): + task = getattr(self, '_get_{}'.format(attribute_name))() + assert not isinstance(task, tuple) + return task + + + def async_set_task(self, attribute_name, value): + raise NotImplementedError + return async_task(async_set_task, attribute_name, value) + + @classmethod + def get_attribute(cls, key): + # Searchs if it is a recursive attribute + try: + pos = key.find('.') + if pos >= 0: + attr, subattr = key[0:pos], key[pos+1: len(key)] + return getattr(cls,attr).type.get_attribute(subattr) + return getattr(cls, key) + except AttributeError: + return None + + @classmethod + def _sanitize(cls): + """Sanitize the object model to accomodate for multiple declaration + styles + + 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 + if isinstance(obj, Attribute): + obj.name = name + + # Remember whether a reverse_name is defined before loading + # inherited properties from parent + has_reverse = bool(obj.reverse_name) + + # Handle overloaded attributes + # By recursion, it is sufficient to look into the parent + for base in cls.__bases__: + if hasattr(base, name): + parent_attribute = getattr(base, name) + obj.merge(parent_attribute) + assert obj.type + + # Handle reverse attribute + # + # NOTE: we need to do this after merging to be sure we get all + # properties inherited from parent (eg. multiplicity) + if has_reverse: + a = { + 'name' : obj.reverse_name, + 'description' : obj.reverse_description, + 'multiplicity' : Multiplicity.reverse(obj.multiplicity), + 'auto' : obj.reverse_auto, + } + reverse_attribute = Attribute(cls, **a) + reverse_attribute.is_aggregate = True + + cur_reverse_attributes[obj.type] = reverse_attribute + + #print('*** class backref ***', cls, obj, reverse_attribute) + if not obj in cls._reverse_attributes: + cls._reverse_attributes[obj] = list() + cls._reverse_attributes[obj].append(reverse_attribute) + + for kls, a in cur_reverse_attributes.items(): + setattr(kls, a.name, a) + + @classmethod + def iter_attributes(cls, aggregates = False): + for name in dir(cls): + attribute = getattr(cls, name) + if not isinstance(attribute, Attribute): + continue + if attribute.is_aggregate and not aggregates: + continue + + yield attribute + + def iter_keys(self): + for attribute in self.iter_attributes(): + if attribute.key == True: + yield attribute + + def get_keys(self): + return list(self.iter_keys()) + + def auto_instanciate(self, attribute): + if self.managed is False: + raise ResourceNotFound(E_AUTO_UNM.format(attribute, self)) + cstr_attributes = dict() + + for a in attribute.type.iter_attributes(): + if not a.mandatory: + continue + + # Let's find attributes in the remote class that are of my + # class, and let's setup them to me + if issubclass(a.type, Resource) and isinstance(self, a.type): + cstr_attributes[a.name] = self + continue + + if hasattr(self, a.name): + cstr_attributes[a.name] = getattr(self, a.name) + + capabilities = set() + reqs = self._state.manager.get_instance_requirements(self) + 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 + + # We need to find a subclass of self._resource with proper capabilities + cls = self._state.manager.get_resource_with_capabilities( + attribute.type, capabilities) + + # Before creating a new instance of a class, let's check + resource = cls(**cstr_attributes) + + self._state.manager.commit_resource(resource) + return resource + + def get_attributes(self, aggregates = False): + return list(self.iter_attributes(aggregates = aggregates)) + + def get_attribute_names(self, aggregates = False): + return set(a.name + for a in self.iter_attributes(aggregates = aggregates)) + + 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) + + ret = dict() + for a in attributes: + if not a.is_set(self): + continue + value = getattr(self, a.name) + if a.is_collection: + ret[a.name] = list() + for x in value: + if uuid and isinstance(x, Resource): + x = x._state.uuid._uuid + ret[a.name].append(x) + else: + if uuid and isinstance(value, Resource): + value = value._state.uuid._uuid + ret[a.name] = value + return ret + + def get_tuple(self): + return (self.__class__, self._get_attribute_dict()) + + @property + def state(self): + return self._state.state + + @state.setter + def state(self, state): + self._state.state = state + + def get_types(self): + return [cls.__name__.lower() for cls in self.__class__.mro() + if cls.__name__ not in ('ABC', 'BaseType', 'object')] + + def get_type(self): + return self.__class__.__name__.lower() + + def has_type(self, typ): + return typ in self.get_types() + + def __repr__(self): + # 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())) + + def __str__(self): + return self.__repr__() + + #--------------------------------------------------------------------------- + # Resource helpers + #--------------------------------------------------------------------------- + + def get_dependencies(self, allow_unresolved = False): + if not self._deps: + deps = set() + for a in self.iter_attributes(): + if not issubclass(a.type, Resource): + continue + if a.is_aggregate: + continue + + value = getattr(self, a.name) + if not value: + continue + + if a.multiplicity in (Multiplicity.OneToOne, + Multiplicity.ManyToOne): + resource = value + if not resource: + log.warning('Null resource') + continue + if not resource.managed: + continue + uuid = resource._state.uuid + # Avoid considering oneself as a dependency due to + # ResourceAttribute(Self) + if uuid != self._state.uuid: + deps.add(uuid) + else: + resources = value + for cpt, resource in enumerate(resources): + if not resource: + log.warning('Null resource in collection') + continue + if not resource.managed: + continue + uuid = resource._state.uuid + deps.add(uuid) + self._deps = deps + return self._deps + + def make_name(self, *args, type=True, id=True): + l = list() + if type: + l.append(self.__class__.__name__) + l.extend(list(args)) + if id: + N = 3 + 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: + for req in attr.requirements: + instance = getattr(self, attr.name) + req.check(instance) + + #-------------------------------------------------------------------------- + # Triggers + #-------------------------------------------------------------------------- + + @deprecated + def trigger(self, action, attribute_name, *args, **kwargs): + self._state.manager.trigger(self, action, attribute_name, + *args, **kwargs) + + #-------------------------------------------------------------------------- + # Object model + # + # Only assignment is implemented here, other operators are overloaded in + # the Attribute class (core.attribute.Attribute) + #-------------------------------------------------------------------------- + + def format(self, fmt): + return fmt.format(**self.get_attribute_dict(uuid = False)) + + def get_tag(self, tag_name, default = NEVER_SET): + """ + A tag corresponds to a propery that is required by a class in all of + its inheritors. For instance, a service requires than a subclass + informs about the 'service_name' tag, which is a class member named + according to the following convention : __service_name__. + """ + tag = '__{}__'.format(tag_name) + if not tag in vars(self.__class__): + if default is NEVER_SET: + return default + raise NotImplementedError('Missing tag {} in class {}'.format(tag, + self.__class__.__name__)) + return getattr(self.__class__, tag) + + def iter_backrefs(self): + for base in self.__class__.mro(): + if not hasattr(base, '_reverse_attributes'): + continue + for attr, rattrs in base._reverse_attributes.items(): + instances = self.get(attr.name, allow_never_set = True) + if instances in (None, NEVER_SET): + continue + if not attr.is_collection: + instances = [instances] + for instance in instances: + for rattr in rattrs: + yield instance, rattr + + #--------------------------------------------------------------------------- + # Accessors + #--------------------------------------------------------------------------- + + @classmethod + def has_attribute(cls, name): + return name in [a.name for a in cls.attributes()] + + def has_callback(self, action, attribute): + return hasattr(self, '_{}_{}'.format(action, attribute.name)) + + def is_setup(self): + return self.state in (ResourceState.SETUP_PENDING, + ResourceState.SETUP, ResourceState.DIRTY) + + __get__ = None + __create__ = None + __delete__ = None + +#------------------------------------------------------------------------------- +# Helper functions +#------------------------------------------------------------------------------- + +# The following Mixin are useful to convert an expresson of subresources into +# an expression of tasks. + +class ConcurrentMixin: + async def async_commit_to_manager(self, manager): + await asyncio.gather(*[element.async_commit_to_manager(manager) + for element in self._elements]) + await asyncio.gather(*[e._state.clean.wait() for e in self._elements]) + self._state.clean.set() + +class SequentialMixin: + async def async_commit_to_manager(self, manager): + for element in self._elements: + await element.async_commit_to_manager(manager) + await element._state.clean.wait() + self._state.clean.set() + +class CompositionMixin: + async def async_commit_to_manager(self, manager): + for element in self._elements: + await element.async_commit_to_manager(manager) + await element._state.clean.wait() + self._state.clean.set() + +_Resource, EmptyResource = SchedulingAlgebra(BaseResource, ConcurrentMixin, + CompositionMixin, SequentialMixin) + +class ManagedResource(_Resource): + def __init__(self, *args, **kwargs): + from vicn.core.resource_mgr import ResourceManager + owner = kwargs.get('owner', None) + name = kwargs.get('name', None) + + manager = ResourceManager() + self.register_to_manager(manager, name=name) + + # Manager is needed for reference and reverse attributes + super().__init__(*args, **kwargs) + + async def async_commit_to_manager(self, manager): + if not self.managed: + return + self._state.manager.commit_resource(self) + + def register_to_manager(self, manager, name = None): + if not self.managed: + return + manager.add_resource(self, name = name) + +Resource = ManagedResource + +class BashResource(Resource): + """ + __get__ : use return code of the bash command + + Intermediate values and attributes: should be dict-like + Actually, we should collect attributes: dict update/remove, map/reduce + """ + __node__ = None + __cmd_get__ = None + __cmd_create__ = None + __cmd_delete__ = None + + def __get__(self): + assert self.__cmd_get__ + return BashTask(self.node, self.__cmd_get__, {'self': self}) + + def __create__(self): + assert self.__cmd_create__ + return BashTask(self.node, self.__cmd_create__, {'self': self}) + + def __delete__(self): + assert self.__cmd_delete__ + return BashTask(self.node, self.__cmd_delete__, {'self': self}) + |