Просмотр исходного кода

Add unit tests for '_check_execution_tasks_sanity'

The unit tests are automatically generated based on input data and the
expected results using the DDT library.

The input data is stored in a seperate .yml file.
Sergiu Miclea 4 лет назад
Родитель
Сommit
65309de589

+ 118 - 0
coriolis/tests/conductor/rpc/data/execution_tasks_config.yml

@@ -0,0 +1,118 @@
+- 
+  # One task with default parameters should execute succesfully
+  tasks_config: [{}]
+  init_task_info: ~
+  expected_result: ~
+-
+  # Invalid task status
+  tasks_config:
+    - status: STARTING
+  init_task_info: ~
+  expected_result: 
+    type: 'INVALID_STATE'
+    message: "Invalid initial state 'STARTING'"
+-
+  # Empty initial task info
+  tasks_config: [{}]
+  init_task_info: {}
+  expected_result: 
+    type: "MISSING_PARAMS"
+-
+  # Task that depends on a missing dependency
+  tasks_config:
+    - depends_on: ["invalid_id"]
+  init_task_info: ~
+  expected_result: 
+    type: "MISSING_DEPENDENCIES"
+    message: |- 
+      non-existent .* dependencies: \[\'invalid_id\'\]
+-
+  # Multiple tasks with the same default parameters and instance should conflict
+  tasks_config: [{}, {}]
+  init_task_info: ~
+  expected_result: 
+    type: "FIELDS_CONFLICT"
+-
+  # Setting a different instance for one task should resolve the conflict, but should raise a missing parameters for the second instance
+  tasks_config: 
+    - {}
+    - instance: "instance_2"
+  init_task_info: ~
+  expected_result:
+    type: "MISSING_PARAMS" 
+    message: "task parameters for instance 'instance_2' are missing"
+-
+  # Setting task parameters for second instance should not raise any errors
+  tasks_config: 
+    - instance: "instance_1"
+    - instance: "instance_2"
+  init_task_info:
+    instance_1: 
+      source_environment: {}
+      export_info: {}
+    instance_2: 
+      source_environment: {}
+      export_info: {}
+  expected_result: ~
+-
+  #  First task depends on second task
+  tasks_config: 
+    - depends_on: ["task_2"]
+    - id: "task_2"
+  init_task_info: ~
+  expected_result: ~
+-
+  # 3-way task dependency
+  tasks_config: 
+    - 
+      id: "task_1"
+      depends_on: ["task_2"]
+    - id: "task_2"
+    - 
+      id: "task_3"
+      depends_on: ["task_1", "task_2"]
+  init_task_info: ~
+  expected_result: ~
+-
+  # Task depends on another task further down the list should cause deadlock
+  tasks_config: 
+    - 
+      id: "task_1"
+      depends_on: ["task_3"]
+    - id: "task_2"
+    - 
+      id: "task_3"
+      depends_on: ["task_1", "task_2"]
+  init_task_info: ~
+  expected_result: 
+    type: "DEADLOCK"
+-
+  # Circular dependency causes deadlock
+  tasks_config: 
+    - 
+      id: "task_1"
+      depends_on: ["task_2"]
+    - 
+      id: "task_2"
+      depends_on: ["task_1"]
+  init_task_info: ~
+  expected_result:
+    type: "DEADLOCK"
+-
+  # Different task type should require different task parameters
+  tasks_config:
+    - task_type: "DEPLOY_OS_MORPHING_RESOURCES"
+  init_task_info: ~
+  expected_result:
+    type: "MISSING_PARAMS"
+-
+  # A different task type with its task parameters set should execute succesfully
+  tasks_config:
+    - 
+      task_type: "DEPLOY_OS_MORPHING_RESOURCES"
+      instance: "instance_1"
+  init_task_info:
+    instance_1: 
+      target_environment: {}
+      instance_deployment_info: {}
+  expected_result: ~

+ 47 - 1
coriolis/tests/conductor/rpc/test_server.py

@@ -1,11 +1,13 @@
 # Copyright 2017 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+import re
+import uuid
 import ddt
 from unittest import mock
 
 from coriolis.conductor.rpc import server
-from coriolis import exception
+from coriolis import constants, exception
 from coriolis.tests import test_base
 
 
@@ -38,3 +40,47 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
         mock_check_replica_running.assert_called_once_with(
             mock.sentinel.context, mock_replica)
         mock_create_task.assert_not_called()
+
+    @ddt.file_data('data/execution_tasks_config.yml')
+    @ddt.unpack
+    def test_check_execution_tasks_sanity(self, tasks_config, init_task_info, expected_result):
+        def convertToTask(task_config):
+            instance_task = mock.Mock()
+            instance_task.instance = task_config.get(
+                'instance', mock.sentinel.instance)
+            instance_task.id = task_config.get('id', str(uuid.uuid4()))
+            instance_task.status = task_config.get(
+                'status', constants.TASK_STATUS_SCHEDULED)
+            instance_task.depends_on = task_config.get('depends_on', None)
+            instance_task.task_type = task_config.get(
+                'task_type', constants.TASK_TYPE_DEPLOY_MIGRATION_SOURCE_RESOURCES)
+            return instance_task
+
+        execution = mock.sentinel.execution
+        execution.id = str(uuid.uuid4())
+        execution.type = mock.sentinel.execution_type
+
+        execution.tasks = [convertToTask(t) for t in tasks_config]
+
+        if init_task_info != None:
+            initial_task_info = init_task_info
+        else:
+            initial_task_info = {mock.sentinel.instance: {
+                'source_environment': mock.sentinel.source_environment,
+                'export_info': mock.sentinel.export_info
+            }}
+
+        if not expected_result:
+            self.server._check_execution_tasks_sanity(
+                execution, initial_task_info)
+        else:
+            exception_mappings = {
+                'INVALID_STATE': exception.InvalidTaskState,
+                'MISSING_PARAMS': exception.TaskParametersException,
+                'MISSING_DEPENDENCIES': exception.TaskDependencyException,
+                'FIELDS_CONFLICT': exception.TaskFieldsConflict,
+                'DEADLOCK': exception.ExecutionDeadlockException,
+            }
+            with self.assertRaisesRegex(exception_mappings[expected_result['type']], expected_result.get('message', "")):
+                self.server._check_execution_tasks_sanity(
+                    execution, initial_task_info)