Migrating trigger functionality

Migrating trigger functionality to work with the app and
workflow resources instead of the plan and pipeline resources.

Change-Id: I5674c6dfc4e7d7813480cb8c9108012a5838dd22
Closes-Bug: #1495693
This commit is contained in:
Devdatta Kulkarni 2015-09-17 17:22:55 -05:00
parent 4d469df2d9
commit 706b1e1253
12 changed files with 96 additions and 99 deletions

View File

@ -16,6 +16,8 @@ import requests
import yaml
from functionaltests.api import base
from functionaltests.api.common import apputils
assembly_data = {'name': 'test_assembly',
'description': 'desc assembly'}
@ -53,8 +55,15 @@ class TestTriggerController(base.TestCase):
self.assertIsNotNone(trigger_uri)
return uuid, trigger_uri
def _create_app(self):
data = apputils.get_sample_data()
resp = self.client.create_app(data=data)
bdy = json.loads(resp.body)
trigger_uri = bdy['trigger_uri']
return trigger_uri
def test_trigger_post(self):
assembly_uuid, plan_uuid, trigger_uri = self._create_assembly()
trigger_uri = self._create_app()
# Using requests instead of self.client to test unauthenticated request
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
@ -64,8 +73,6 @@ class TestTriggerController(base.TestCase):
resp = requests.post(trigger_uri, data=body)
self.assertEqual(resp.status_code, 202)
self._delete_assembly(assembly_uuid, plan_uuid)
def test_trigger_post_with_empty_body(self):
assembly_uuid, plan_uuid, trigger_uri = self._create_assembly()
# Using requests instead of self.client to test unauthenticated request

View File

@ -19,30 +19,11 @@ from tempest_lib import exceptions as tempest_exceptions
import yaml
from functionaltests.api import base
from functionaltests.api.common import apputils
class TestAppController(base.TestCase):
def _get_sample_data(self):
data = dict()
data["name"] = "test_app_1"
data["description"] = "descp"
data["languagepack"] = "python"
data["trigger_actions"] = ["test", "build", "deploy"]
data["ports"] = [80]
source = {}
source['repository'] = "https://github.com"
source['revision'] = "master"
data["source"] = source
workflow = {}
workflow["test_cmd"] = "./unit_tests.sh"
workflow["run_cmd"] = "python app.py"
data["workflow_config"] = workflow
return data
def _assert_app_data(self, actual, expected):
self.assertEqual(actual["name"], expected["name"])
self.assertEqual(actual["description"], expected["description"])
@ -71,13 +52,13 @@ class TestAppController(base.TestCase):
self.client.delete_created_apps()
def test_app_create(self):
data = self._get_sample_data()
data = apputils.get_sample_data()
resp = self.client.create_app(data=data)
self.assertEqual(resp.status, 201)
def test_app_create_bad_port_data(self):
try:
bad_data = self._get_sample_data()
bad_data = apputils.get_sample_data()
bad_data["ports"][0] = -1
self.client.create_plan(data=bad_data)
except tempest_exceptions.BadRequest:
@ -89,7 +70,7 @@ class TestAppController(base.TestCase):
headers={'content-type': 'application/json'})
def test_app_patch(self):
data = self._get_sample_data()
data = apputils.get_sample_data()
create_resp = self.client.create_app(data=data)
self.assertEqual(create_resp.status, 201)
@ -116,7 +97,7 @@ class TestAppController(base.TestCase):
self.assertEqual(app_body["source"]["repository"], "newrepo")
def test_app_get(self):
data = self._get_sample_data()
data = apputils.get_sample_data()
create_resp = self.client.create_app(data=data)
self.assertEqual(create_resp.status, 201)
id = create_resp.id
@ -129,7 +110,7 @@ class TestAppController(base.TestCase):
self._assert_app_data(yaml_data, data)
def test_apps_get_all(self):
data = self._get_sample_data()
data = apputils.get_sample_data()
create_resp = self.client.create_app(data)
self.assertEqual(create_resp.status, 201)
resp, body = self.client.get(
@ -141,7 +122,7 @@ class TestAppController(base.TestCase):
self.assertEqual(filtered[0]['id'], id)
def test_app_delete(self):
data = self._get_sample_data()
data = apputils.get_sample_data()
create_resp = self.client.create_app(data)
self.assertEqual(create_resp.status, 201)
id = create_resp.id

View File

@ -106,7 +106,6 @@ class AppsController(rest.RestController):
new_app = handler.create(app_data)
created_app = app.App.from_db_model(new_app, pecan.request.host_url)
return created_app
@exception.wrap_wsme_pecan_controller_exception

View File

@ -47,10 +47,12 @@ class App(api_types.Base):
workflow_config = {wtypes.text: wtypes.text}
trigger_uuid = wtypes.text
trigger_actions = [wtypes.text]
trigger_uri = wtypes.text
trust_id = wtypes.text
trust_user = wtypes.text
app_url = wtypes.text
status = wtypes.text
repo_token = wtypes.text
def __init__(self, *args, **kwargs):
super(App, self).__init__(*args, **kwargs)
@ -67,6 +69,8 @@ class App(api_types.Base):
json = m.as_dict()
json['type'] = m.__tablename__
json['uri'] = '%s/v1/apps/%s' % (host_url, m.id)
json['trigger_uri'] = ('%s/v1/triggers/%s' %
(host_url, m.trigger_uuid))
return cls(**(json))
def as_dict(self, db_model):

View File

@ -18,8 +18,7 @@ from oslo_config import cfg
import pecan
from pecan import rest
from solum.api.handlers import pipeline_handler
from solum.api.handlers import plan_handler
from solum.api.handlers import app_handler
from solum.common import exception
from solum.openstack.common import log as logging
@ -47,6 +46,7 @@ class TriggerController(rest.RestController):
status_url = None
collab_url = None
workflow = None
try:
query = query_dict(pecan.request.query_string)
if 'workflow' in query:
@ -96,11 +96,11 @@ class TriggerController(rest.RestController):
raise exception.BadRequest(reason=info_msg)
try:
handler = plan_handler.PlanHandler(None)
handler = app_handler.AppHandler(None)
handler.trigger_workflow(trigger_id, commit_sha, status_url,
collab_url, workflow=workflow)
except exception.ResourceNotFound:
handler = pipeline_handler.PipelineHandler(None)
handler.trigger_workflow(trigger_id)
except exception.ResourceNotFound as e:
LOG.error("Incorrect trigger url.")
raise e
pecan.response.status = 202

View File

@ -78,7 +78,9 @@ class WorkflowsController(rest.RestController):
data.source = app_model.source
wf_data = data.as_dict(workflow.Workflow)
return workflow.Workflow.from_db_model(handler.create(wf_data),
return workflow.Workflow.from_db_model(handler.create(wf_data,
commit_sha='',
status_url=''),
pecan.request.host_url)
@exception.wrap_pecan_controller_exception

View File

@ -15,6 +15,9 @@
import uuid
from solum.api.handlers import handler
from solum.api.handlers import workflow_handler
from solum.common import exception
from solum.common import keystone_utils
from solum import objects
from solum.openstack.common import log as logging
@ -62,6 +65,10 @@ class AppHandler(handler.Handler):
db_obj.project_id = self.context.tenant
db_obj.deleted = False
# create a delegation trust_id\token, if required
db_obj.trust_id = keystone_utils.create_delegation_token(self.context)
db_obj.trust_user = self.context.user_name
db_obj.name = data.get('name')
db_obj.description = data.get('description')
db_obj.languagepack = data.get('languagepack')
@ -69,14 +76,53 @@ class AppHandler(handler.Handler):
db_obj.ports = data.get('ports')
db_obj.source = data.get('source')
db_obj.workflow_config = data.get('workflow_config')
db_obj.trigger_uuid = data.get('trigger_uuid')
db_obj.trigger_uuid = str(uuid.uuid4())
db_obj.trigger_actions = data.get('trigger_actions')
db_obj.trust_id = data.get('trust_id')
db_obj.trust_user = data.get('trust_user')
db_obj.create(self.context)
return db_obj
def trigger_workflow(self, trigger_id, commit_sha='',
status_url=None, collab_url=None, workflow=None):
"""Get trigger by trigger id and start git workflow associated."""
# Note: self.context will be None at this point as this is a
# non-authenticated request.
app_obj = objects.registry.App.get_by_trigger_id(None, trigger_id)
# get the trust context and authenticate it.
try:
self.context = keystone_utils.create_delegation_context(
app_obj, self.context)
self.context.tenant = app_obj.project_id
self.context.user = app_obj.user_id
self.context.user_name = app_obj.trust_user
except exception.AuthorizationFailure as auth_ex:
LOG.warn(auth_ex)
return
# TODO(devkulkarni): Call repo_utils.verify_artifact
# as we are calling it in the plan_handler to verify
# the collaborator
self._build_artifact(app_obj, commit_sha=commit_sha,
status_url=status_url, wf=workflow)
def _build_artifact(self, app, commit_sha='',
status_url=None, wf=None):
if wf is None:
wf = ['unittest', 'build', 'deploy']
wfhand = workflow_handler.WorkflowHandler(self.context)
wfdata = {
'app_id': app.id,
'name': "%s" % app.name,
'description': '',
'source': app.source,
'config': app.workflow_config,
'actions': wf
}
wfhand.create(wfdata, commit_sha=commit_sha, status_url=status_url)
def get_all(self):
"""Return all apps."""
all_apps = objects.registry.AppList.get_all(self.context)

View File

@ -64,7 +64,7 @@ class WorkflowHandler(handler.Handler):
db_obj = objects.registry.Workflow.get_by_uuid(self.context, id)
db_obj.destroy(self.context)
def create(self, data):
def create(self, data, commit_sha, status_url):
"""Create a new workflow."""
db_obj = objects.registry.Workflow()
db_obj.id = str(uuid.uuid4())

View File

@ -18,19 +18,16 @@ import mock
from oslo_config import cfg
from solum.api.controllers.v1 import trigger
from solum.common import exception
from solum.tests import base
from solum.tests import fakes
@mock.patch('pecan.request', new_callable=fakes.FakePecanRequest)
@mock.patch('pecan.response', new_callable=fakes.FakePecanResponse)
@mock.patch('solum.api.controllers.v1.trigger.plan_handler'
'.PlanHandler')
@mock.patch('solum.api.controllers.v1.trigger.pipeline_handler'
'.PipelineHandler')
@mock.patch('solum.api.controllers.v1.trigger.app_handler'
'.AppHandler')
class TestTriggerController(base.BaseTestCase):
def test_trigger_post_with_empty_body(self, pipe_mock, assem_mock,
def test_trigger_post_with_empty_body(self, assem_mock,
resp_mock, request_mock):
obj = trigger.TriggerController()
obj.post('test_id')
@ -38,7 +35,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_on_github_webhook(self, pipe_mock, assem_mock,
def test_trigger_post_on_github_webhook(self, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
@ -53,7 +50,7 @@ class TestTriggerController(base.BaseTestCase):
tw.assert_called_once_with('test_id', 'asdf', expected_st_url, None,
workflow=None)
def test_trigger_post_on_github_comment_webhook(self, pipe_mock,
def test_trigger_post_on_github_comment_webhook(self,
assem_mock, resp_mock,
request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -78,7 +75,6 @@ class TestTriggerController(base.BaseTestCase):
@mock.patch('httplib2.Http.request')
def test_trigger_post_on_mismatch_comment_pub_repo(self, http_mock,
pipe_mock,
assem_mock, resp_mock,
request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -102,7 +98,6 @@ class TestTriggerController(base.BaseTestCase):
@mock.patch('httplib2.Http.request')
def test_trigger_post_on_valid_comment_pub_repo(self, http_mock,
pipe_mock,
assem_mock, resp_mock,
request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -127,7 +122,7 @@ class TestTriggerController(base.BaseTestCase):
tw.assert_called_once_with('test_id', 'asdf', expected_st_url,
expected_clb_url, workflow=None)
def test_trigger_post_on_comment_missing_login(self, pipe_mock,
def test_trigger_post_on_comment_missing_login(self,
assem_mock, resp_mock,
request_mock):
cfg.CONF.api.rebuild_phrase = "solum retry tests"
@ -148,7 +143,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_on_wrong_github_webhook(self, pipe_mock, assem_mock,
def test_trigger_post_on_wrong_github_webhook(self, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
@ -161,7 +156,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_on_unknown_git_webhook(self, pipe_mock, assem_mock,
def test_trigger_post_on_unknown_git_webhook(self, assem_mock,
resp_mock, request_mock):
body_dict = {"pull_request": {"head": {"sha": "asdf"}}}
request_mock.body = json.dumps(body_dict)
@ -171,7 +166,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_on_non_github_webhook(self, pipe_mock, assem_mock,
def test_trigger_post_on_non_github_webhook(self, assem_mock,
resp_mock, request_mock):
body_dict = {"sender": {"url": "https://non-github.com"},
"pull_request": {"head": {"sha": "asdf"}}}
@ -182,7 +177,7 @@ class TestTriggerController(base.BaseTestCase):
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_on_github_ping_webhook(self, pipe_mock, assem_mock,
def test_trigger_post_on_github_ping_webhook(self, assem_mock,
resp_mock, request_mock):
body_dict = {"sender": {"url": "https://api.github.com"},
"zen": "Keep it logically awesome."}
@ -191,41 +186,4 @@ class TestTriggerController(base.BaseTestCase):
obj.post('test_id')
self.assertEqual(501, resp_mock.status)
tw = assem_mock.return_value.trigger_workflow
assert not tw.called
def test_trigger_post_pipeline(self, pipe_mock, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': 'asdf'}},
'repository': {'statuses_url': status_url}}
request_mock.body = json.dumps(body_dict)
obj = trigger.TriggerController()
assem_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
obj.post('test_id')
self.assertEqual(202, resp_mock.status)
tw = pipe_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id')
def test_trigger_post_none(self, pipe_mock, assem_mock,
resp_mock, request_mock):
status_url = 'https://api.github.com/repos/u/r/statuses/{sha}'
body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': 'asdf'}},
'repository': {'statuses_url': status_url}}
request_mock.body = json.dumps(body_dict)
expected_st_url = 'https://api.github.com/repos/u/r/statuses/asdf'
obj = trigger.TriggerController()
assem_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
pipe_mock.return_value.trigger_workflow.side_effect = (
exception.ResourceNotFound(name='trigger', id='test_id'))
obj.post('test_id')
self.assertEqual(404, resp_mock.status)
tw = assem_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id', 'asdf', expected_st_url, None,
workflow=None)
tw = pipe_mock.return_value.trigger_workflow
tw.assert_called_once_with('test_id')
assert not tw.called

View File

@ -51,7 +51,8 @@ class TestAppHandler(base.BaseTestCase):
get_by_uuid.assert_called_once_with(self.ctx, 'test_id')
self.assertTrue(db_obj.destroy.called)
def test_app_create(self, mock_registry):
@mock.patch('solum.common.clients.OpenStackClients.keystone')
def test_app_create(self, mock_kc, mock_registry):
data = {'name': 'fakeapp',
'description': 'fake app for testing'}
db_obj = fakes.FakeApp()

View File

@ -91,8 +91,7 @@ class TestWorkflowHandler(base.BaseTestCase):
handler = workflow_handler.WorkflowHandler(self.ctx)
res = handler.create(workflow_data)
res = handler.create(workflow_data, commit_sha='', status_url='')
self.assertEqual(wf_obj, res)
git_info = {
'source_url': app_obj.source['repository'],

View File

@ -46,14 +46,14 @@ def _get_solum_client():
def main(args):
client = _get_solum_client()
plan = client.plans.find(name_or_id=args.app)
app = client.apps.find(name_or_id=args.app)
status_url = 'https://api.github.com/repos/{repo}/statuses/{sha}'.format(
repo=args.repo, sha=args.sha)
body_dict = {'sender': {'url': 'https://api.github.com'},
'pull_request': {'head': {'sha': args.sha}},
'repository': {'statuses_url': status_url}}
body = json.dumps(body_dict)
trigger_uri = plan.trigger_uri
trigger_uri = app.trigger_uri
if args.workflow:
trigger_uri = "%s?workflow=%s" % (trigger_uri, args.workflow)
resp = requests.post(trigger_uri, data=body)