Custom graph support added to the tasks and graph info handlers

Following handlers are extended with graph_type query
string parameter:

    /releases/<release_id>/deployment_tasks/
    /clusters/<cluster_id>/deployment_tasks/
    /clusters/<cluster_id>/serialized_tasks/
    /clusters/<cluster_id>/deploy_tasks/graph.gv

This parameter allows handler to use deployment graph data
from the scope of the given graph type.

Change-Id: I0e9af118af9aff57df8efa1e9139134566660c4b
Implements: blueprint custom-graph-execution
This commit is contained in:
Ilya Kutukov 2016-03-15 20:34:07 +03:00
parent 29d6deb84b
commit 5b5f87b7e3
7 changed files with 125 additions and 12 deletions

View File

@ -602,12 +602,13 @@ class OrchestratorDeploymentTasksHandler(SingleHandler):
obj = self.get_object_or_404(self.single, obj_id) obj = self.get_object_or_404(self.single, obj_id)
end = web.input(end=None).end end = web.input(end=None).end
start = web.input(start=None).start start = web.input(start=None).start
graph_type = web.input(graph_type=None).graph_type
# web.py depends on [] to understand that there will be multiple inputs # web.py depends on [] to understand that there will be multiple inputs
include = web.input(include=[]).include include = web.input(include=[]).include
# merged (cluster + plugins + release) tasks is returned for cluster # merged (cluster + plugins + release) tasks is returned for cluster
# but the own release tasks is returned for release # but the own release tasks is returned for release
tasks = self.single.get_deployment_tasks(obj) tasks = self.single.get_deployment_tasks(obj, graph_type=graph_type)
if end or start: if end or start:
graph = orchestrator_graph.GraphSolver(tasks) graph = orchestrator_graph.GraphSolver(tasks)
return graph.filter_subgraph( return graph.filter_subgraph(

View File

@ -326,9 +326,10 @@ class TaskDeployGraph(BaseHandler):
* 400 (failed to get graph) * 400 (failed to get graph)
""" """
web.header('Content-Type', 'text/vnd.graphviz', unique=True) web.header('Content-Type', 'text/vnd.graphviz', unique=True)
graph_type = web.input(graph_type=None).graph_type
cluster = self.get_object_or_404(objects.Cluster, cluster_id) cluster = self.get_object_or_404(objects.Cluster, cluster_id)
tasks = objects.Cluster.get_deployment_tasks(cluster) tasks = objects.Cluster.get_deployment_tasks(cluster, graph_type)
graph = orchestrator_graph.GraphSolver(tasks) graph = orchestrator_graph.GraphSolver(tasks)
tasks = web.input(tasks=None).tasks tasks = web.input(tasks=None).tasks
@ -383,12 +384,13 @@ class SerializedTasksHandler(NodesFilterMixin, BaseHandler):
self.checked_data(self.validator.validate_placement, self.checked_data(self.validator.validate_placement,
data=nodes, cluster=cluster) data=nodes, cluster=cluster)
tasks = web.input(tasks=None).tasks tasks = web.input(tasks=None).tasks
graph_type = web.input(graph_type=None).graph_type
task_ids = [t.strip() for t in tasks.split(',')] if tasks else None task_ids = [t.strip() for t in tasks.split(',')] if tasks else None
try: try:
serialized_tasks = task_based_deployment.TasksSerializer.serialize( serialized_tasks = task_based_deployment.TasksSerializer.serialize(
cluster, cluster,
nodes, nodes,
objects.Cluster.get_deployment_tasks(cluster), objects.Cluster.get_deployment_tasks(cluster, graph_type),
task_ids=task_ids task_ids=task_ids
) )
return {'tasks_directory': serialized_tasks[0], return {'tasks_directory': serialized_tasks[0],

View File

@ -1029,7 +1029,7 @@ class Cluster(NailgunObject):
# graph types not supported by plugin manager interface yet # graph types not supported by plugin manager interface yet
plugins_deployment_tasks = PluginManager.get_plugins_deployment_tasks( plugins_deployment_tasks = PluginManager.get_plugins_deployment_tasks(
instance) instance, graph_type)
return cls._merge_tasks_lists([ return cls._merge_tasks_lists([
release_deployment_tasks, release_deployment_tasks,

View File

@ -23,6 +23,7 @@ from urlparse import urljoin
import six import six
import yaml import yaml
from nailgun import consts
from nailgun.errors import errors from nailgun.errors import errors
from nailgun.logger import logger from nailgun.logger import logger
from nailgun.objects.deployment_graph import DeploymentGraph from nailgun.objects.deployment_graph import DeploymentGraph
@ -123,13 +124,11 @@ class PluginAdapterBase(object):
return settings.PLUGINS_SLAVES_SCRIPTS_PATH.format( return settings.PLUGINS_SLAVES_SCRIPTS_PATH.format(
plugin_name=self.path_name) plugin_name=self.path_name)
# todo(ikutukov): actually getter-setter approach don't allow us to def get_deployment_tasks(self, graph_type=None):
# work with graph types on plugins level. if graph_type is None:
# Should be reworked to getters and setters graph_type = consts.DEFAULT_DEPLOYMENT_GRAPH_TYPE
@property
def deployment_tasks(self):
deployment_tasks = [] deployment_tasks = []
graph_instance = DeploymentGraph.get_for_model(self.plugin) graph_instance = DeploymentGraph.get_for_model(self.plugin, graph_type)
if graph_instance: if graph_instance:
for task in DeploymentGraph.get_tasks(graph_instance): for task in DeploymentGraph.get_tasks(graph_instance):
if task.get('parameters'): if task.get('parameters'):
@ -138,6 +137,11 @@ class PluginAdapterBase(object):
deployment_tasks.append(task) deployment_tasks.append(task)
return deployment_tasks return deployment_tasks
# fixme(ikutukov): this getter only for default graph type, drop in future
@property
def deployment_tasks(self):
return self.get_deployment_tasks()
# it will better to replace to getters and setters # it will better to replace to getters and setters
@deployment_tasks.setter @deployment_tasks.setter
def deployment_tasks(self, value): def deployment_tasks(self, value):

View File

@ -204,13 +204,13 @@ class PluginManager(object):
return list(all_roles.values()) return list(all_roles.values())
@classmethod @classmethod
def get_plugins_deployment_tasks(cls, cluster): def get_plugins_deployment_tasks(cls, cluster, graph_type=None):
deployment_tasks = [] deployment_tasks = []
processed_tasks = {} processed_tasks = {}
enabled_plugins = ClusterPlugins.get_enabled(cluster.id) enabled_plugins = ClusterPlugins.get_enabled(cluster.id)
for plugin_adapter in map(wrap_plugin, enabled_plugins): for plugin_adapter in map(wrap_plugin, enabled_plugins):
depl_tasks = plugin_adapter.deployment_tasks depl_tasks = plugin_adapter.get_deployment_tasks(graph_type)
for t in depl_tasks: for t in depl_tasks:
t_id = t['id'] t_id = t['id']

View File

@ -171,6 +171,34 @@ class TestReleaseGraphHandler(BaseGraphTasksTests, DeploymentTasksTestMixin):
self.cluster.release) self.cluster.release)
self.assertEqual(resp.json, release_tasks) self.assertEqual(resp.json, release_tasks)
def test_get_custom_deployment_tasks(self):
objects.DeploymentGraph.create_for_model(
{'tasks': [
{
'id': 'custom-task',
'type': 'puppet'
}
]}, self.cluster.release, 'custom-graph')
resp = self.app.get(
reverse(
'ReleaseDeploymentTasksHandler',
kwargs={'obj_id': self.cluster.release_id}
) + '?graph_type=custom-graph',
headers=self.default_headers
)
self.assertItemsEqual(
resp.json,
[
{
'id': 'custom-task',
'task_name': 'custom-task',
'version': '1.0.0',
'type': 'puppet'
}
]
)
def test_upload_deployment_tasks(self): def test_upload_deployment_tasks(self):
tasks = self.get_correct_tasks() tasks = self.get_correct_tasks()
resp = self.app.put( resp = self.app.put(
@ -263,6 +291,34 @@ class TestClusterGraphHandler(BaseGraphTasksTests, DeploymentTasksTestMixin):
self.cluster.release) self.cluster.release)
self.assertItemsEqual(resp.json, release_tasks) self.assertItemsEqual(resp.json, release_tasks)
def test_get_custom_deployment_tasks(self):
objects.DeploymentGraph.create_for_model(
{'tasks': [
{
'id': 'custom-task',
'type': 'puppet'
}
]}, self.cluster, 'custom-graph')
resp = self.app.get(
reverse(
'ClusterDeploymentTasksHandler',
kwargs={'obj_id': self.cluster.id}
) + '?graph_type=custom-graph',
headers=self.default_headers
)
self.assertItemsEqual(
resp.json,
[
{
'id': 'custom-task',
'task_name': 'custom-task',
'version': '1.0.0',
'type': 'puppet'
}
]
)
def test_upload_deployment_tasks(self): def test_upload_deployment_tasks(self):
tasks = self.get_correct_tasks() tasks = self.get_correct_tasks()
resp = self.app.put( resp = self.app.put(
@ -545,3 +601,28 @@ class TestTaskDeployGraph(BaseGraphTasksTests):
) )
self.assertEqual(resp.status_code, 400) self.assertEqual(resp.status_code, 400)
self.assertIn('Task types nonexistent do not exist', resp.body) self.assertIn('Task types nonexistent do not exist', resp.body)
class TestTaskDeployCustomGraph(BaseGraphTasksTests):
content_type = 'text/vnd.graphviz'
def setUp(self):
super(TestTaskDeployCustomGraph, self).setUp()
self.env.create()
self.cluster = self.env.clusters[-1]
def test_get_custom_tasks(self):
objects.DeploymentGraph.create_for_model(
{'tasks': [
{'id': 'pre_deployment', 'type': 'stage'},
{'id': 'custom-task', 'required_for': ['pre_deployment'],
'type': 'puppet'},
]}, self.cluster, 'custom-graph')
resp = self.app.get(
reverse('TaskDeployGraph', kwargs={
'cluster_id': self.cluster.id,
}) + '?graph_type=custom-graph',
)
self.assertIn('"custom-task" -> pre_deployment;', resp.body)

View File

@ -471,6 +471,31 @@ class TestSerializedTasksHandler(BaseIntegrationTest):
for task_name, task in six.iteritems(resp.json['tasks_directory']): for task_name, task in six.iteritems(resp.json['tasks_directory']):
self.assertIn(task['type'], consts.ORCHESTRATOR_TASK_TYPES) self.assertIn(task['type'], consts.ORCHESTRATOR_TASK_TYPES)
@patch.object(TaskProcessor, 'ensure_task_based_deploy_allowed')
def test_custom_serialized_tasks_returned(self, _):
objects.DeploymentGraph.create_for_model(
{'tasks': [
{
'id': 'first-custom-task',
'type': 'stage',
'requires': ['pre_deployment_end']
}, {
'id': 'second-custom-task',
'type': 'stage',
'requires': ['deploy_start']
}
]}, self.cluster, 'custom-graph')
resp = self.get_serialized_tasks(
self.cluster.id, graph_type=['custom-graph'])
self.assertEqual(resp.status_code, 200)
self.assertIn('tasks_graph', resp.json)
self.assertIn('tasks_directory', resp.json)
tasks_graph = resp.json['tasks_graph']
expected_tasks = ['first-custom-task', 'second-custom-task']
self.assertItemsEqual(
[t['id'] for t in tasks_graph['null']], expected_tasks)
@patch.object(TaskProcessor, 'ensure_task_based_deploy_allowed') @patch.object(TaskProcessor, 'ensure_task_based_deploy_allowed')
def test_query_nodes_and_tasks(self, _): def test_query_nodes_and_tasks(self, _):
not_skipped = ['globals'] not_skipped = ['globals']