Skip to content

Commit

Permalink
Possibility to create and run playbook configuration in one command
Browse files Browse the repository at this point in the history
Change-Id: Ibe25765e1f11593bba27910e92ee0010baeec9da
  • Loading branch information
9seconds committed May 17, 2017
1 parent 535944d commit c313fab
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 61 deletions.
32 changes: 3 additions & 29 deletions backend/api/decapod_api/views/v1/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from decapod_api import exceptions as http_exceptions
from decapod_api import validators
from decapod_api.views import generic
from decapod_api.views.v1 import misc
from decapod_common import exceptions as base_exceptions
from decapod_common import log
from decapod_common.models import execution
Expand Down Expand Up @@ -108,35 +109,8 @@ def post(self):
pc_id, pc_version
)

auth.AUTH.check_auth_permission(flask.g.token.user,
"playbook", config.playbook_id)
if config.cluster.time_deleted:
raise http_exceptions.CannotExecuteOnDeletedCluster(
config.cluster_id)

model = execution.ExecutionModel.create(config, self.initiator_id)
LOG.info(
"Created execution %s for playbook configuration %s of "
"version %s",
model.model_id, config.model_id, config.version
)

try:
tsk = task.PlaybookPluginTask(
config.playbook_id, config._id, model.model_id
)
tsk.create()
except Exception as exc:
LOG.error("Cannot create task for execution %s: %s",
model.model_id, exc)
model.state = execution.ExecutionState.failed
model.save()
raise
else:
LOG.info("Created task for execution %s: %s",
model.model_id, tsk._id)

return model
with misc.created_execution_model(config, self.initiator_id) as model:
return model

@auth.AUTH.require_authorization("api", "delete_execution")
@validators.with_model(execution.ExecutionModel)
Expand Down
95 changes: 95 additions & 0 deletions backend/api/decapod_api/views/v1/misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-


import contextlib

import flask

from decapod_api import auth
from decapod_api import exceptions as http_exceptions
from decapod_common import exceptions as base_exceptions
from decapod_common import log
from decapod_common.models import execution
from decapod_common.models import playbook_configuration
from decapod_common.models import task


LOG = log.getLogger(__name__)
"""Logger."""


@contextlib.contextmanager
def created_playbook_configuration_model(
name, playbook_id, cluster_model, servers, initiator_id, hints, *,
delete_on_fail=False):
try:
pcmodel = playbook_configuration.PlaybookConfigurationModel.create(
name=name,
playbook_id=playbook_id,
cluster=cluster_model,
servers=servers,
initiator_id=initiator_id,
hints=hints
)
except base_exceptions.UniqueConstraintViolationError as exc:
LOG.warning(
"Cannot create cluster %s (unique constraint "
"violation)", name)
raise http_exceptions.ImpossibleToCreateSuchModel() from exc
except base_exceptions.ClusterMustBeDeployedError as exc:
mid = cluster_model.model_id
LOG.warning(
"Attempt to create playbook configuration for not "
"deployed cluster %s", mid)
raise http_exceptions.ClusterMustBeDeployedError(mid) from exc

LOG.info("Playbook configuration %s (%s) created by %s",
name, pcmodel.model_id, initiator_id)

try:
yield pcmodel
except Exception as exc:
if delete_on_fail:
LOG.warning("Caught exception %s, delete playbook config %s",
exc, pcmodel.model_id)
pcmodel.delete()
raise


@contextlib.contextmanager
def created_execution_model(pcmodel, initiator_id, *, delete_on_fail=True):
auth.AUTH.check_auth_permission(flask.g.token.user,
"playbook", pcmodel.playbook_id)
if pcmodel.cluster.time_deleted:
raise http_exceptions.CannotExecuteOnDeletedCluster(
pcmodel.cluster_id)

model = execution.ExecutionModel.create(pcmodel, initiator_id)
LOG.info(
"Created execution %s for playbook configuration %s of "
"version %s",
model.model_id, pcmodel.model_id, pcmodel.version
)
try:
with created_task(pcmodel, model) as tsk:
LOG.info("Created task for execution %s: %s",
model.model_id, tsk._id)
yield model
except Exception as exc:
LOG.error("Cannot create task for execution %s: %s",
model.model_id, exc)
if delete_on_fail:
model.state = execution.ExecutionState.failed
model.save()

raise


@contextlib.contextmanager
def created_task(pcmodel, execution_model):
tsk = task.PlaybookPluginTask(
pcmodel.playbook_id, pcmodel._id, execution_model.model_id
)
tsk.create()

yield tsk
51 changes: 23 additions & 28 deletions backend/api/decapod_api/views/v1/playbook_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from decapod_api import exceptions as http_exceptions
from decapod_api import validators
from decapod_api.views import generic
from decapod_api.views.v1 import misc
from decapod_common import exceptions as base_exceptions
from decapod_common import log
from decapod_common import plugins
Expand Down Expand Up @@ -68,6 +69,7 @@
}
}
POST_SCHEMA = validators.create_data_schema(POST_SCHEMA, True)
POST_SCHEMA["properties"]["run"] = {"type": "boolean"}
"""Schema for the creating new playbook configuration."""

LOG = log.getLogger(__name__)
Expand Down Expand Up @@ -174,34 +176,27 @@ def post(self):
exc
)

try:
pcmodel = playbook_configuration.PlaybookConfigurationModel.create(
name=self.request_json["name"],
playbook_id=self.request_json["playbook_id"],
cluster=cluster_model,
servers=servers_for_playbook,
initiator_id=self.initiator_id,
hints=self.request_json["hints"]
)
except base_exceptions.UniqueConstraintViolationError as exc:
LOG.warning(
"Cannot create cluster %s (unique constraint "
"violation)",
self.request_json["name"]
)
raise http_exceptions.ImpossibleToCreateSuchModel() from exc
except base_exceptions.ClusterMustBeDeployedError as exc:
mid = cluster_model.model_id
LOG.warning(
"Attempt to create playbook configuration for not "
"deployed cluster %s", mid)
raise http_exceptions.ClusterMustBeDeployedError(mid) from exc

LOG.info("Playbook configuration %s (%s) created by %s",
self.request_json["name"], pcmodel.model_id,
self.initiator_id)

return pcmodel
run_after = self.request_json.get("run", False)
parameters = {
"name": self.request_json["name"],
"playbook_id": self.request_json["playbook_id"],
"cluster_model": cluster_model,
"servers": servers_for_playbook,
"initiator_id": self.initiator_id,
"hints": self.request_json["hints"]
}
with misc.created_playbook_configuration_model(
**parameters, delete_on_fail=run_after) as model:
response = model.make_api_structure()
response["data"]["created_execution_id"] = None

if run_after:
with misc.created_execution_model(
model, self.initiator_id) as exc_model:
response["data"]["created_execution_id"] = \
exc_model.model_id

return response

@auth.AUTH.require_authorization("api", "delete_playbook_configuration")
@validators.with_model(playbook_configuration.PlaybookConfigurationModel)
Expand Down
9 changes: 7 additions & 2 deletions decapodcli/decapodcli/playbook_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,20 @@ def get_version(playbook_configuration_id, version, client):
type=param_types.JSON,
help="JSON dump of hints parameter"
)
@click.option(
"--run-after",
is_flag=True,
help="Execute playbook configuration after create"
)
@decorators.command(playbook_configuration)
def create(name, playbook, cluster_id, server_ids, hints, client):
def create(name, playbook, cluster_id, server_ids, hints, run_after, client):
"""Create new playbook configuration."""

cluster_id = str(cluster_id)
server_ids = [str(item) for item in server_ids]

return client.create_playbook_configuration(
name, cluster_id, playbook, server_ids, hints
name, cluster_id, playbook, server_ids, hints, run_after
)


Expand Down
7 changes: 5 additions & 2 deletions decapodlib/decapodlib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,8 @@ def get_playbook_configuration_version(self, playbook_configuration_id,
return self._session.get(url, **kwargs)

def create_playbook_configuration(self, name, cluster_id, playbook_id,
server_ids, hints=None, **kwargs):
server_ids, hints=None, run_after=False,
**kwargs):
"""This method creates new playbook configuration model.
This method does ``POST /v1/playbook_configuration/`` endpoint
Expand Down Expand Up @@ -876,6 +877,7 @@ def create_playbook_configuration(self, name, cluster_id, playbook_id,
form of server model IDs.
:type server_ids: [:py:class:`str`, ...]
:param list hints: List of hints for playbook configuration.
:param bool run_after: Run playbook configuration after create.
:return: New cluster model.
:rtype: dict
:raises decapodlib.exceptions.DecapodError: if not possible to
Expand All @@ -890,7 +892,8 @@ def create_playbook_configuration(self, name, cluster_id, playbook_id,
"cluster_id": cluster_id,
"playbook_id": playbook_id,
"server_ids": list(set(server_ids)),
"hints": hints or []
"hints": hints or [],
"run": run_after
}

return self._session.post(url, json=payload, **kwargs)
Expand Down
36 changes: 36 additions & 0 deletions tests/api/views/v1/test_api_playbook_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from decapod_common import playbook_plugin
from decapod_common import plugins
from decapod_common.models import playbook_configuration
from decapod_common.models import role
from decapod_common.models import server


Expand Down Expand Up @@ -164,10 +165,45 @@ def test_create_new_playbook_configuration(
assert response.status_code == 200
for key in "name", "playbook_id":
assert response.json["data"][key] == valid_post_request[key]
assert response.json["data"]["created_execution_id"] is None

assert isinstance(response.json["data"]["configuration"], dict)


def test_create_and_run_new_playbook_configuration_nok(
sudo_client_v1, valid_post_request):
valid_post_request["run"] = True

items_before = sudo_client_v1.get("/v1/playbook_configuration/")
response = sudo_client_v1.post("/v1/playbook_configuration/",
data=valid_post_request)
items_after = sudo_client_v1.get("/v1/playbook_configuration/")

assert response.status_code == 403
assert items_before.json == items_after.json


def test_create_and_run_new_playbook_configuration_ok(
sudo_client_v1, sudo_role, valid_post_request):
valid_post_request["run"] = True

role.PermissionSet.add_permission(
"playbook", valid_post_request["playbook_id"])
sudo_role.add_permissions("playbook", [valid_post_request["playbook_id"]])
sudo_role.save()

response = sudo_client_v1.post("/v1/playbook_configuration/",
data=valid_post_request)
assert response.status_code == 200
assert response.json["data"]["created_execution_id"]

response2 = sudo_client_v1.get(
"/v1/execution/{0}/".format(
response.json["data"]["created_execution_id"]))
assert response2.status_code == 200
assert response2.json["data"]["state"] == "created"


def test_create_new_playbook_configuration_unknown_playbook(
sudo_client_v1, valid_post_request
):
Expand Down

0 comments on commit c313fab

Please sign in to comment.