aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/core
diff options
context:
space:
mode:
authorMarcel Enguehard <mengueha+fdio@cisco.com>2017-05-23 10:50:17 +0200
committerMarcel Enguehard <mengueha+fdio@cisco.com>2017-05-23 08:57:12 +0000
commitbe0b435d307173598c30fcacc421b17112137099 (patch)
treeed8aae7eef796eb7381d21937b095a036733789a /vicn/core
parent895a6328d6e64948ed213e8fbbb3ab15aca0df43 (diff)
Introduced groups + lxd profiles + diverted control network handling to lxd + misc bug fixes
Change-Id: Iae26bc2994ac9704dde7dfa8fbe4be1b74cf9e6f Signed-off-by: Marcel Enguehard <mengueha+fdio@cisco.com>
Diffstat (limited to 'vicn/core')
-rw-r--r--vicn/core/api.py72
-rw-r--r--vicn/core/attribute.py19
-rw-r--r--vicn/core/collection.py23
-rw-r--r--vicn/core/exception.py7
-rw-r--r--vicn/core/resource.py265
-rw-r--r--vicn/core/resource_mgr.py58
-rw-r--r--vicn/core/sa_collections.py25
-rw-r--r--vicn/core/state.py9
-rw-r--r--vicn/core/task.py8
9 files changed, 317 insertions, 169 deletions
diff --git a/vicn/core/api.py b/vicn/core/api.py
index 09167aa0..708e2581 100644
--- a/vicn/core/api.py
+++ b/vicn/core/api.py
@@ -33,6 +33,7 @@ from vicn.resource.node import Node
DEFAULT_SETTINGS = {
'network': '192.168.0.0/16',
+ 'bridge_name': 'br0',
'mac_address_base': '0x00163e000000',
'websocket_port': 9999
}
@@ -48,48 +49,25 @@ class Event_ts(asyncio.Event):
class API(metaclass = Singleton):
def terminate(self):
+ # XXX not valid if nothing has been initialized
ResourceManager().terminate()
- def parse_topology_file(self, topology_fn):
- log.debug("Parsing topology file %(topology_fn)s" % locals())
+ def parse_topology_file(self, topology_fn, resources, settings):
+ log.info("Parsing topology file %(topology_fn)s" % locals())
try:
topology_fd = open(topology_fn, 'r')
except IOError:
- self.error("Topology file '%(topology_fn)s not found" % locals())
- return None
+ log.error("Topology file '%(topology_fn)s not found" % locals())
+ sys.exit(1)
try:
topology = json.loads(topology_fd.read())
# SETTING
- settings = DEFAULT_SETTINGS
settings.update(topology.get('settings', dict()))
- # VICN process-related initializations
- nofile = settings.get('ulimit-n', None)
- if nofile is not None and nofile > 0:
- if nofile < 1024:
- log.error('Too few allowed open files for the process')
- import os; os._exit(1)
-
- log.info('Setting open file descriptor limit to {}'.format(
- nofile))
- ulimit.setrlimit(
- ulimit.RLIMIT_NOFILE,
- (nofile, nofile))
-
- ResourceManager(base=topology_fn, settings=settings)
-
# NODES
- resources = topology.get('resources', list())
- for resource in resources:
- try:
- ResourceManager().create_from_dict(**resource)
- except Exception as e:
- log.warning("Could not create resource '%r': %r" % \
- (resource, e,))
- import traceback; traceback.print_exc()
- continue
+ resources.extend(topology.get('resources', list()))
except SyntaxError:
log.error("Error reading topology file '%s'" % (topology_fn,))
@@ -97,16 +75,42 @@ class API(metaclass = Singleton):
log.debug("Done parsing topology file %(topology_fn)s" % locals())
- def configure(self, name, setup=False):
+ def configure(self, scenario_list):
log.info("Parsing configuration file", extra={'category': 'blue'})
- self.parse_topology_file(name)
+ resources = list()
+ settings = DEFAULT_SETTINGS
+ for scenario in scenario_list:
+ self.parse_topology_file(scenario, resources, settings)
+
+ # VICN process-related initializations
+ nofile = settings.get('ulimit-n', None)
+ if nofile is not None and nofile > 0:
+ if nofile < 1024:
+ log.error('Too few allowed open files for the process')
+ import os; os._exit(1)
+
+ log.info('Setting open file descriptor limit to {}'.format(
+ nofile))
+ ulimit.setrlimit(
+ ulimit.RLIMIT_NOFILE,
+ (nofile, nofile))
+
+ ResourceManager(base=scenario[-1], settings=settings)
+
+ for resource in resources:
+ try:
+ ResourceManager().create_from_dict(**resource)
+ except Exception as e:
+ log.error("Could not create resource '%r': %r" % \
+ (resource, e,))
+ import os; os._exit(1)
+
self._configured = True
- ResourceManager().setup(commit=setup)
- def setup(self):
+ def setup(self, commit = False):
if not self._configured:
raise NotConfigured
- ResourceManager().setup()
+ ResourceManager().setup(commit)
def teardown(self):
ResourceManager().teardown()
diff --git a/vicn/core/attribute.py b/vicn/core/attribute.py
index f6ec7c70..22f44487 100644
--- a/vicn/core/attribute.py
+++ b/vicn/core/attribute.py
@@ -42,7 +42,6 @@ class Multiplicity:
OneToMany = '1_N'
ManyToOne = 'N_1'
ManyToMany = 'N_N'
-
@staticmethod
def reverse(value):
@@ -108,7 +107,7 @@ class Attribute(abc.ABC, ObjectSpecification):
self.is_aggregate = False
self._reverse_attributes = list()
-
+
#--------------------------------------------------------------------------
# Display
#--------------------------------------------------------------------------
@@ -157,7 +156,7 @@ class Attribute(abc.ABC, ObjectSpecification):
value = value.get_uuid()
return value
else:
- try:
+ try:
cur_value = vars(instance)[self.name]
if self.is_collection:
# copy the list
@@ -167,11 +166,11 @@ class Attribute(abc.ABC, ObjectSpecification):
if self.is_collection:
cur_value = list()
- instance._state.dirty[self.name].trigger(Operations.LIST_ADD,
+ instance._state.dirty[self.name].trigger(Operations.LIST_ADD,
value, cur_value)
# prevent instrumented list to perform operation
- raise VICNListException
+ raise VICNListException
def do_list_remove(self, instance, value):
if instance.is_local_attribute(self.name):
@@ -184,11 +183,11 @@ class Attribute(abc.ABC, ObjectSpecification):
if self.is_collection:
# copy the list
cur_value = list(cur_value)
- instance._state.dirty[self.name].trigger(Operations.LIST_REMOVE,
+ instance._state.dirty[self.name].trigger(Operations.LIST_REMOVE,
value, cur_value)
# prevent instrumented list to perform operation
- raise VICNListException
+ raise VICNListException
def do_list_clear(self, instance):
if instance.is_local_attribute(self.name):
@@ -198,11 +197,11 @@ class Attribute(abc.ABC, ObjectSpecification):
if self.is_collection:
# copy the list
cur_value = list(cur_value)
- instance._state.dirty[self.name].trigger(Operations.LIST_CLEAR,
+ instance._state.dirty[self.name].trigger(Operations.LIST_CLEAR,
value, cur_value)
# prevent instrumented list to perform operation
- raise VICNListException
+ raise VICNListException
def handle_getitem(self, instance, item):
if isinstance(item, UUID):
@@ -227,7 +226,7 @@ class Attribute(abc.ABC, ObjectSpecification):
@property
def is_collection(self):
- return self.multiplicity in (Multiplicity.OneToMany,
+ return self.multiplicity in (Multiplicity.OneToMany,
Multiplicity.ManyToMany)
def is_set(self, instance):
diff --git a/vicn/core/collection.py b/vicn/core/collection.py
new file mode 100644
index 00000000..fb222891
--- /dev/null
+++ b/vicn/core/collection.py
@@ -0,0 +1,23 @@
+#!/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.
+#
+
+from vicn.core.sa_collections import InstrumentedList
+from netmodel.model.collection import Collection
+
+class Collection(InstrumentedList, Collection):
+ pass
diff --git a/vicn/core/exception.py b/vicn/core/exception.py
index d7422723..977fc8ad 100644
--- a/vicn/core/exception.py
+++ b/vicn/core/exception.py
@@ -37,3 +37,10 @@ class SetupException(VICNException): pass
class VICNListException(VICNException): pass
class ResourceNotFound(VICNException): pass
+
+class VICNWouldBlock(VICNException):
+ """
+ Exception called when a request would block and the user explicitely
+ required non-blocking behaviour
+ """
+ pass
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):
diff --git a/vicn/core/resource_mgr.py b/vicn/core/resource_mgr.py
index c6ce77ab..4ca8060c 100644
--- a/vicn/core/resource_mgr.py
+++ b/vicn/core/resource_mgr.py
@@ -288,6 +288,7 @@ class ResourceManager(metaclass=Singleton):
def create_from_dict(self, **resource):
resource_type = resource.pop('type', None)
+
assert resource_type
return self.create(resource_type.lower(), **resource)
@@ -354,6 +355,21 @@ class ResourceManager(metaclass=Singleton):
if commit:
self.commit()
+ def teardown(self):
+ asyncio.ensure_future(self._teardown())
+
+ async def _teardown(self):
+ task = EmptyTask()
+ # XXX we should never have to autoinstanciate
+ # XXX why keeping this code
+ for resource in self.get_resources():
+ if resource.get_type() == 'lxccontainer':
+ task = task | resource.__delete__()
+ print("RESOURCE", resource.name)
+ self.schedule(task)
+ ret = await wait_task(task)
+ return ret
+
def get_resource_with_capabilities(self, cls, capabilities):
if '__type__' in cls.__dict__ and cls.__type__ == FactoryResource:
candidates = inheritors(cls)
@@ -806,7 +822,10 @@ class ResourceManager(metaclass=Singleton):
resource._state.attr_change_success[attribute.name] = True
else:
log.error('Attribute error {} for resource {}'.format(
- resource.get_uuid(), attribute.name))
+ attribute.name, resource.get_uuid()))
+ print("task1=", task)
+ sys.stdout.flush()
+
import traceback; traceback.print_tb(e.__traceback__)
log.error('Failed with exception: {}'.format(e))
import os; os._exit(1)
@@ -931,10 +950,13 @@ class ResourceManager(metaclass=Singleton):
resource._state.attr_change_success[attribute.name] = True
else:
log.error('Attribute error {} for resource {}'.format(
- resource.get_uuid(), attribute.name))
+ attribute.name, resource.get_uuid()))
+ # XXX need better logging
+ print("task2=", task._node.name, task.get_full_cmd())
+ sys.stdout.flush()
e = resource._state.attr_change_value[attribute.name]
- new_state = AttributeState.ERROR
import traceback; traceback.print_tb(e.__traceback__)
+ new_state = AttributeState.ERROR
import os; os._exit(1)
else:
@@ -956,9 +978,10 @@ class ResourceManager(metaclass=Singleton):
return Query.from_dict(dic)
def _monitor_netmon(self, resource):
- ip = resource.node.host_interface.ip4_address
+ ip = resource.node.management_interface.ip4_address
if not ip:
- log.error('IP of monitored Node is None')
+ log.error('IP of monitored Node {} is None'.format(resource.node))
+ #return # XXX
import os; os._exit(1)
ws = self._router.add_interface('websocketclient', address=ip,
@@ -1009,7 +1032,7 @@ class ResourceManager(metaclass=Singleton):
def _monitor_emulator(self, resource):
ns = resource
- ip = ns.node.bridge.ip4_address # host_interface.ip_address
+ ip = ns.node.bridge.ip4_address # management_interface.ip_address
ws_ns = self._router.add_interface('websocketclient', address = ip,
port = ns.control_port,
@@ -1252,7 +1275,13 @@ class ResourceManager(metaclass=Singleton):
state = resource._state.state
self.log(resource, 'Current state is {}'.format(state))
- if state == ResourceState.UNINITIALIZED:
+ if state == ResourceState.ERROR:
+ e = resource._state.change_value
+ print("------")
+ import traceback; traceback.print_tb(e.__traceback__)
+ log.error('Resource: {} - Exception: {}'.format(pfx, e))
+ import os; os._exit(1)
+ elif state == ResourceState.UNINITIALIZED:
pending_state = ResourceState.PENDING_DEPS
elif state == ResourceState.DEPS_OK:
pending_state = ResourceState.PENDING_INIT
@@ -1379,24 +1408,26 @@ class ResourceManager(metaclass=Singleton):
# with container.execute(), not container.get()
log.warning('LXD Fix (not found). Reset resource')
new_state = ResourceState.INITIALIZED
+ resource._state.change_success = True
elif ENABLE_LXD_WORKAROUND and isinstance(e, LXDAPIException):
# "not found" is the normal exception when the container
# does not exists. anyways the bug should only occur
# with container.execute(), not container.get()
log.warning('LXD Fix (API error). Reset resource')
new_state = ResourceState.INITIALIZED
+ resource._state.change_success = True
elif isinstance(e, ResourceNotFound):
# The resource does not exist
self.log(resource, S_GET_DONE.format(
resource._state.change_value))
new_state = ResourceState.GET_DONE
resource._state.change_value = None
+ resource._state.change_success = True
else:
e = resource._state.change_value
log.error('Cannot get resource state {} : {}'.format(
resource.get_uuid(), e))
new_state = ResourceState.ERROR
- resource._state.change_success = True
elif pending_state == ResourceState.PENDING_KEYS:
if resource._state.change_success == True:
@@ -1442,10 +1473,7 @@ class ResourceManager(metaclass=Singleton):
resource._state.change_success = True
else:
self.log(resource, 'CREATE failed: {}'.format(e))
- e = resource._state.change_value
- import traceback; traceback.print_tb(e.__traceback__)
- log.error('Failed with exception {}'.format(e))
- import os; os._exit(1)
+ new_state = ResourceState.ERROR
elif pending_state == ResourceState.PENDING_UPDATE:
if resource._state.change_success == True:
@@ -1462,11 +1490,8 @@ class ResourceManager(metaclass=Singleton):
resource._state.change_success = True
resource._state.write_lock.release()
else:
- e = resource._state.change_value
resource._state.write_lock.release()
- import traceback; traceback.print_tb(e.__traceback__)
- log.error('Failed with exception {}'.format(e))
- import os; os._exit(1)
+ new_state = ResourceState.ERROR
elif pending_state == ResourceState.PENDING_DELETE:
raise NotImplementedError
@@ -1475,4 +1500,3 @@ class ResourceManager(metaclass=Singleton):
raise RuntimeError
await self._set_resource_state(resource, new_state)
-
diff --git a/vicn/core/sa_collections.py b/vicn/core/sa_collections.py
index e627caa5..a4a24f85 100644
--- a/vicn/core/sa_collections.py
+++ b/vicn/core/sa_collections.py
@@ -12,8 +12,9 @@
import logging
-from vicn.core.sa_compat import py2k
from vicn.core.exception import VICNListException
+from vicn.core.sa_compat import py2k
+from vicn.core.state import UUID
log = logging.getLogger(__name__)
@@ -29,7 +30,7 @@ def _list_decorators():
try:
item = self._attribute.do_list_add(self._instance, item)
fn(self, item)
- except VICNListException as e:
+ except VICNListException as e:
pass
_tidy(append)
return append
@@ -121,7 +122,7 @@ def _list_decorators():
try:
self._attribute.do_list_remove(self._instance, item)
except : has_except = True
- if not has_except:
+ if not has_except:
fn(self, index)
_tidy(__delitem__)
return __delitem__
@@ -180,7 +181,7 @@ def _list_decorators():
self._attribute.do_list_remove(self._instance, item)
item = fn(self, index)
return item
- except : return None
+ except : return None
_tidy(pop)
return pop
@@ -230,13 +231,27 @@ def _instrument_list(cls):
# inspired by sqlalchemy
for method, decorator in _list_decorators().items():
fn = getattr(cls, method, None)
- if fn:
+ if fn:
#if (fn and method not in methods and
# not hasattr(fn, '_sa_instrumented')):
setattr(cls, method, decorator(fn))
class InstrumentedList(list):
+ @classmethod
+ def from_list(cls, value, instance, attribute):
+ lst = list()
+ if value:
+ for x in value:
+ if isinstance(x, UUID):
+ x = instance.from_uuid(x)
+ lst.append(x)
+ # Having a class method is important for inheritance
+ value = cls(lst)
+ value._attribute = attribute
+ value._instance = instance
+ return value
+
def __contains__(self, key):
from vicn.core.resource import Resource
if isinstance(key, Resource):
diff --git a/vicn/core/state.py b/vicn/core/state.py
index bb108b2b..81876790 100644
--- a/vicn/core/state.py
+++ b/vicn/core/state.py
@@ -75,7 +75,7 @@ class UUID:
random identifier of length UUID_LEN. Components of the UUID are
separated by UUID_SEP.
"""
- uuid = ''.join(random.choice(string.ascii_uppercase + string.digits)
+ uuid = ''.join(random.choice(string.ascii_uppercase + string.digits)
for _ in range(UUID_LEN))
if name:
uuid = name # + UUID_SEP + uuid
@@ -105,7 +105,7 @@ class PendingValue:
if action == Operations.SET:
self.value = value
- self.operations = [(Operations.SET, value)]
+ self.operations = [(Operations.SET, value)]
elif action == Operations.LIST_CLEAR:
self.value = list()
self.operations = [(Operations.LIST_CLEAR, None)]
@@ -136,9 +136,8 @@ class InstanceState:
# LIST set add remove clear
self.dirty = dict()
-
# Initialize resource state
- self.lock = asyncio.Lock()
+ self.lock = asyncio.Lock()
self.write_lock = asyncio.Lock()
self.state = ResourceState.UNINITIALIZED
self.clean = asyncio.Event()
@@ -161,7 +160,7 @@ class InstanceState:
self.attr_log = dict()
# Initialize attribute state
for attribute in instance.iter_attributes():
- self.attr_lock[attribute.name] = asyncio.Lock()
+ self.attr_lock[attribute.name] = asyncio.Lock()
self.attr_init[attribute.name] = asyncio.Event()
self.attr_clean[attribute.name] = asyncio.Event()
self.attr_state[attribute.name] = AttributeState.UNINITIALIZED
diff --git a/vicn/core/task.py b/vicn/core/task.py
index 53321972..8346c65e 100644
--- a/vicn/core/task.py
+++ b/vicn/core/task.py
@@ -219,6 +219,14 @@ class PythonTask(Task):
fut = loop.run_in_executor(None, partial)
fut.add_done_callback(self._done_callback)
+ def execute_blocking(self, *args, **kwargs):
+ all_args = self._args + args
+ all_kwargs = dict()
+ all_kwargs.update(self._kwargs)
+ all_kwargs.update(kwargs)
+
+ return self._func(*all_args, **all_kwargs)
+
def __repr__(self):
s = _get_func_desc(self._func)
return '<Task[py] {}>'.format(s) if s else '<Task[py]>'