ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a...@apache.org
Subject incubator-ariatosca git commit: ARIA-126 Add node states [Forced Update!]
Date Sun, 26 Mar 2017 14:15:56 GMT
Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-126-update-node-statuses 9d097b2b0 -> 4d5e83993 (forced update)


ARIA-126 Add node states

1. The states are described in section 3.3.1 of the TOSCA spec.

2. The state is changed if a standard lifecycle operation runs of the
node, as described in sections 5.7.4.1, 5.7.4.4.1, and 5.7.4.4.2 of the TOSCA spec.

3. We did not address the 'error' state yet.
This state is defined as part of the possible node states in the model, but currently no execution
path leads to setting a node's state to 'error'.

4. No validation of state transiontions.
For example, we do not validate if a node goes from 'created'
to 'started' without going through the 'configured' state in between.


Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/4d5e8399
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/4d5e8399
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/4d5e8399

Branch: refs/heads/ARIA-126-update-node-statuses
Commit: 4d5e839933f208a497626beae2f04d3cb32ad6e6
Parents: b3cf69a
Author: Avia Efrat <avia@gigaspaces.com>
Authored: Wed Mar 22 17:19:58 2017 +0200
Committer: Avia Efrat <avia@gigaspaces.com>
Committed: Sun Mar 26 17:15:43 2017 +0300

----------------------------------------------------------------------
 aria/modeling/service_instance.py               |  52 ++++++-
 aria/modeling/service_template.py               |   2 +-
 .../workflows/core/events_handler.py            |  18 ++-
 tests/mock/models.py                            |   4 +-
 tests/modeling/test_mixins.py                   |   2 +-
 tests/modeling/test_models.py                   |  12 +-
 .../orchestrator/workflows/core/test_events.py  | 147 +++++++++++++++++++
 7 files changed, 222 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index f120734..a10afbc 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -18,7 +18,8 @@
 from sqlalchemy import (
     Column,
     Text,
-    Integer
+    Integer,
+    Enum,
 )
 from sqlalchemy import DateTime
 from sqlalchemy.ext.associationproxy import association_proxy
@@ -322,8 +323,8 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
     :vartype runtime_properties: {}
     :ivar scaling_groups: ??
     :vartype scaling_groups: []
-    :ivar state: ??
-    :vartype state: basestring
+    :ivar state: The state of the node, according to to the TOSCA-defined node states
+    :vartype state: string
     :ivar version: Used by `aria.storage.instrumentation`
     :vartype version: int
 
@@ -347,6 +348,49 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
                           'node_template_fk',
                           'service_name']
 
+    INITIAL = 'initial'
+    CREATING = 'creating'
+    CREATED = 'created'
+    CONFIGURING = 'configuring'
+    CONFIGURED = 'configured'
+    STARTING = 'starting'
+    STARTED = 'started'
+    STOPPING = 'stopping'
+    DELETING = 'deleting'
+    # 'deleted' isn't actually part of the tosca spec, since according the description of
the
+    # 'deleting' state: "Node is transitioning from its current state to one where it is
deleted and
+    #  its state is no longer tracked by the instance model."
+    # However, we prefer to be able to retrieve information about deleted nodes, so we chose
to add
+    # this 'deleted' state to enable us to do so.
+    DELETED = 'deleted'
+    ERROR = 'error'
+
+    STATES = [INITIAL, CREATING, CREATED, CONFIGURING, CONFIGURED, STARTING, STARTED, STOPPING,
+              DELETING, DELETED, ERROR]
+
+    _op_to_state = {'create': {'transitional': CREATING, 'finished': CREATED},
+                    'configure': {'transitional': CONFIGURING, 'finished': CONFIGURED},
+                    'start': {'transitional': STARTING, 'finished': STARTED},
+                    'stop': {'transitional': STOPPING, 'finished': CONFIGURED},
+                    'delete': {'transitional': DELETING, 'finished': DELETED}}
+
+    @classmethod
+    def determine_state(cls, op_name, is_transitional):
+        """ :returns the state the node should be in as a result of running the
+            operation on this node.
+
+            e.g. if we are running tosca.interfaces.node.lifecycle.Standard.create, then
+            the resulting state should either 'creating' (if the task just started) or 'created'
+            (if the task ended).
+
+            If the operation is not a standard tosca lifecycle operation, then we return
None"""
+
+        state_type = 'is_transitional' if is_transitional else 'finished'
+        try:
+            return cls._op_to_state[op_name][state_type]
+        except KeyError:
+            return None
+
     @declared_attr
     def node_template(cls):
         return relationship.many_to_one(cls, 'node_template')
@@ -391,7 +435,7 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
 
     runtime_properties = Column(modeling_types.Dict)
     scaling_groups = Column(modeling_types.List)
-    state = Column(Text, nullable=False)
+    state = Column(Enum(*STATES, name='node_state'), nullable=False, default=INITIAL)
     version = Column(Integer, default=1)
 
     __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7246ff1..8b619bf 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -497,7 +497,7 @@ class NodeTemplateBase(TemplateModelMixin):
         node = models.Node(name=name,
                            type=self.type,
                            description=deepcopy_with_locators(self.description),
-                           state='',
+                           state=models.Node.INITIAL,
                            node_template=self)
         utils.instantiate_dict(node, node.properties, self.properties)
         utils.instantiate_dict(node, node.interfaces, self.interface_templates)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/aria/orchestrator/workflows/core/events_handler.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/events_handler.py b/aria/orchestrator/workflows/core/events_handler.py
index a420d2b..0350ebb 100644
--- a/aria/orchestrator/workflows/core/events_handler.py
+++ b/aria/orchestrator/workflows/core/events_handler.py
@@ -20,7 +20,7 @@ Path: aria.events.storage_event_handler
 Implementation of storage handlers for workflow and operation events.
 """
 
-
+import re
 from datetime import (
     datetime,
     timedelta,
@@ -29,6 +29,7 @@ from datetime import (
 from ... import events
 from ... import exceptions
 
+
 @events.sent_task_signal.connect
 def _task_sent(task, *args, **kwargs):
     with task._update():
@@ -41,6 +42,8 @@ def _task_started(task, *args, **kwargs):
         task.started_at = datetime.utcnow()
         task.status = task.STARTED
 
+        _update_node_state_if_necessary(task, is_transitional=True)
+
 
 @events.on_failure_task_signal.connect
 def _task_failed(task, exception, *args, **kwargs):
@@ -73,6 +76,8 @@ def _task_succeeded(task, *args, **kwargs):
         task.ended_at = datetime.utcnow()
         task.status = task.SUCCESS
 
+        _update_node_state_if_necessary(task)
+
 
 @events.start_workflow_signal.connect
 def _workflow_started(workflow_context, *args, **kwargs):
@@ -118,3 +123,14 @@ def _workflow_cancelling(workflow_context, *args, **kwargs):
         return _workflow_cancelled(workflow_context=workflow_context)
     execution.status = execution.CANCELLING
     workflow_context.execution = execution
+
+
+def _update_node_state_if_necessary(task, is_transitional=False):
+        match = re.search('^(?:tosca.interfaces.node.lifecycle.Standard|Standard):(\S+)@node',
+                          task.context.name)
+        if match:
+            node = task.runs_on
+            state = node.determine_state(op_name=match.group(1), is_transitional=is_transitional)
+            if state:
+                node.state = state
+                task.context.model.node.update(node)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/tests/mock/models.py
----------------------------------------------------------------------
diff --git a/tests/mock/models.py b/tests/mock/models.py
index a60b35e..9ae2815 100644
--- a/tests/mock/models.py
+++ b/tests/mock/models.py
@@ -121,7 +121,7 @@ def create_dependency_node(dependency_node_template, service):
         runtime_properties={'ip': '1.1.1.1'},
         version=None,
         node_template=dependency_node_template,
-        state='',
+        state=models.Node.INITIAL,
         scaling_groups=[],
         service=service
     )
@@ -136,7 +136,7 @@ def create_dependent_node(dependent_node_template, service):
         runtime_properties={},
         version=None,
         node_template=dependent_node_template,
-        state='',
+        state=models.Node.INITIAL,
         scaling_groups=[],
         service=service
     )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/tests/modeling/test_mixins.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_mixins.py b/tests/modeling/test_mixins.py
index 7795b57..651f53f 100644
--- a/tests/modeling/test_mixins.py
+++ b/tests/modeling/test_mixins.py
@@ -127,7 +127,7 @@ def test_relationship_model_ordering(context):
         service=service,
         version=None,
         node_template=new_node_template,
-        state='',
+        state=modeling.models.Node.INITIAL,
         scaling_groups=[]
     )
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
index c3b98c1..84200d5 100644
--- a/tests/modeling/test_models.py
+++ b/tests/modeling/test_models.py
@@ -554,11 +554,11 @@ class TestNode(object):
             (False, 'name', {}, [], m_cls, 1),
             (False, m_cls, {}, [], 'state', m_cls),
 
-            (True, 'name', {}, [], 'state', 1),
-            (True, None, {}, [], 'state', 1),
-            (True, 'name', None, [], 'state', 1),
-            (True, 'name', {}, None, 'state', 1),
-            (True, 'name', {}, [], 'state', None),
+            (True, 'name', {}, [], 'initial', 1),
+            (True, None, {}, [], 'initial', 1),
+            (True, 'name', None, [], 'initial', 1),
+            (True, 'name', {}, None, 'initial', 1),
+            (True, 'name', {}, [], 'initial', None),
         ]
     )
     def test_node_model_creation(self, node_template_storage, is_valid, name, runtime_properties,
@@ -645,7 +645,7 @@ class TestNodeIP(object):
             node_template=node,
             type=storage.type.list()[0],
             runtime_properties={},
-            state='',
+            state='initial',
             service=storage.service.list()[0]
         )
         if ip:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4d5e8399/tests/orchestrator/workflows/core/test_events.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_events.py b/tests/orchestrator/workflows/core/test_events.py
new file mode 100644
index 0000000..720b95a
--- /dev/null
+++ b/tests/orchestrator/workflows/core/test_events.py
@@ -0,0 +1,147 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 pytest
+
+from tests import mock, storage
+from aria.modeling.service_instance import NodeBase
+from aria.orchestrator.decorators import operation, workflow
+from aria.orchestrator.workflows.core import engine
+from aria.orchestrator.workflows.executor.thread import ThreadExecutor
+from aria.orchestrator.workflows import api
+
+global_test_dict = {}  # used to capture transitional node state changes
+
+
+@pytest.fixture
+def ctx(tmpdir):
+    context = mock.context.simple(str(tmpdir))
+    yield context
+    storage.release_sqlite_storage(context.model)
+
+# TODO another possible approach of writing these tests:
+# Don't create a ctx for every test.
+# Problem is, that if for every test we create a workflow that contains just one standard
+# lifecycle operation, then by the time we try to run the second test, the workflow failes
since
+# the execution tries to go from 'terminated' to 'pending'.
+# And if we write a workflow that contains all the lifecycle operations, then first we need
to
+# change the api of `mock.models.create_interface`, which a lot of other tests use, and second
how
+# do we check all the state transition during the workflow execution in a convenient way.
+
+TYPE_URI_NAME = 'tosca.interfaces.node.lifecycle.Standard'
+SHORTHAND_NAME = 'Standard'
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_create(ctx):
+    node = run_operation_on_node(ctx, interface_name=TYPE_URI_NAME, op_name='create')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'create')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_configure(ctx):
+    node = run_operation_on_node(ctx, interface_name=TYPE_URI_NAME, op_name='configure')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'configure')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_start(ctx):
+    node = run_operation_on_node(ctx, interface_name=TYPE_URI_NAME, op_name='start')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'start')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_stop(ctx):
+    node = run_operation_on_node(ctx, interface_name=TYPE_URI_NAME, op_name='stop')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'stop')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_delete(ctx):
+    node = run_operation_on_node(ctx, interface_name=TYPE_URI_NAME, op_name='delete')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'delete')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_create_shorthand_name(ctx):
+    node = run_operation_on_node(ctx, interface_name=SHORTHAND_NAME, op_name='create')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'create')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_configure_shorthand_name(ctx):
+    node = run_operation_on_node(ctx, interface_name=SHORTHAND_NAME, op_name='configure')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'configure')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_start_shorthand_name(ctx):
+    node = run_operation_on_node(ctx, interface_name=SHORTHAND_NAME, op_name='start')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'start')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_stop_shorthand_name(ctx):
+    node = run_operation_on_node(ctx, interface_name=SHORTHAND_NAME, op_name='stop')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'stop')
+
+
+def test_node_state_changes_as_a_result_of_standard_lifecycle_delete_shorthand_name(ctx):
+    node = run_operation_on_node(ctx, interface_name=SHORTHAND_NAME, op_name='delete')
+    _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, 'delete')
+
+
+def test_node_state_doesnt_change_as_a_result_of_an_operation_that_is_not_standard_lifecycle1(ctx):
+    node = run_operation_on_node(ctx, interface_name='interface_name', op_name='op_name')
+    assert node.state == node.INITIAL
+
+
+def test_node_state_doesnt_change_as_a_result_of_an_operation_that_is_not_standard_lifecycle2(ctx):
+    node = run_operation_on_node(ctx, interface_name='interface_name', op_name='create')
+    assert node.state == node.INITIAL
+
+
+def run_operation_on_node(ctx, op_name, interface_name):
+    node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+    interface = mock.models.create_interface(
+        service=node.service,
+        interface_name=interface_name,
+        operation_name=op_name,
+        operation_kwargs=dict(implementation='{name}.{func.__name__}'.format(name=__name__,
+                                                                             func=func)))
+    node.interfaces[interface.name] = interface
+
+    eng = engine.Engine(executor=ThreadExecutor(),
+                        workflow_context=ctx,
+                        tasks_graph=single_operation_workflow(ctx=ctx,
+                                                              node=node,
+                                                              interface_name=interface_name,
+                                                              op_name=op_name))
+    eng.execute()
+    return node
+
+
+def run_standard_lifecycle_operation_on_node(ctx, op_name):
+    return run_operation_on_node(ctx, interface_name='aria.interfaces.lifecycle.Standard',
+                                 op_name=op_name)
+
+
+def _assert_node_state_changed_as_a_result_of_standard_lifecycle_operation(node, op_name):
+    assert global_test_dict['transitional_state'] == NodeBase._op_to_state[op_name]['transitional']
+    assert node.state == NodeBase._op_to_state[op_name]['finished']
+
+
+@workflow
+def single_operation_workflow(ctx, graph, node, interface_name, op_name):
+    graph.add_tasks(api.task.OperationTask.for_node(
+        node=node,
+        interface_name=interface_name,
+        operation_name=op_name))
+
+
+@operation
+def func(ctx):
+    global_test_dict['transitional_state'] = ctx.node.state


Mime
View raw message