This change, while substantial, is mostly organizational. Currently, connections, sources, triggers, and reporters are discrete concepts, and yet are related by virtue of the fact that the ConnectionRegistry is used to instantiate each of them. The method used to instantiate them is called "_getDriver", in recognition that behind each "trigger", etc., which appears in the config file, there is a class in the zuul.trigger hierarchy implementing the driver for that trigger. Connections also specify a "driver" in the config file. In this change, we redefine a "driver" as a single class that organizes related connections, sources, triggers and reporters. The connection, source, trigger, and reporter interfaces still exist. A driver class is responsible for indicating which of those interfaces it supports and instantiating them when asked to do so. Zuul instantiates a single instance of each driver class it knows about (currently hardcoded, but in the future, we will be able to easily ask entrypoints for these). That instance will be retained for the life of the Zuul server process. When Zuul is (re-)configured, it asks the driver instances to create new connection, source, trigger, reporter instances as necessary. For instance, a user may specify a connection that uses the "gerrit" driver, and the ConnectionRegistry would call getConnection() on the Gerrit driver instance. This is done for two reasons: first, it allows us to organize all of the code related to interfacing with an external system together. All of the existing connection, source, trigger, and reporter classes are moved as follows: zuul.connection.FOO -> zuul.driver.FOO.FOOconnection zuul.source.FOO -> zuul.driver.FOO.FOOsource zuul.trigger.FOO -> zuul.driver.FOO.FOOtrigger zuul.reporter.FOO -> zuul.driver.FOO.FOOreporter For instance, all of the code related to interfacing with Gerrit is now is zuul.driver.gerrit. Second, the addition of a single, long-lived object associated with each of these systems allows us to better support some types of interfaces. For instance, the Zuul trigger maintains a list of events it is required to emit -- this list relates to a tenant as a whole rather than individual pipelines or triggers. The timer trigger maintains a single scheduler instance for all tenants, but must be able to add or remove cron jobs based on an individual tenant being reconfigured. The global driver instance for each of these can be used to accomplish this. As a result of using the driver interface to create new connection, source, trigger and reporter instances, the connection setup in ConnectionRegistry is much simpler, and can easily be extended with entrypoints in the future. The existing tests of connections, sources, triggers, and reporters which only tested that they could be instantiated and have names have been removed, as there are functional tests which cover them. Change-Id: Ib2f7297d81f7a003de48f799dc1b09e82d4894bcchanges/49/408849/6
parent
7f238a2991
commit
e511d2f6c4
@ -1,46 +0,0 @@
|
||||
# Copyright 2014 Rackspace Australia
|
||||
#
|
||||
# Licensed 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 logging
|
||||
import testtools
|
||||
|
||||
import zuul.reporter
|
||||
|
||||
|
||||
class TestSMTPReporter(testtools.TestCase):
|
||||
log = logging.getLogger("zuul.test_reporter")
|
||||
|
||||
def setUp(self):
|
||||
super(TestSMTPReporter, self).setUp()
|
||||
|
||||
def test_reporter_abc(self):
|
||||
# We only need to instantiate a class for this
|
||||
reporter = zuul.reporter.smtp.SMTPReporter({}) # noqa
|
||||
|
||||
def test_reporter_name(self):
|
||||
self.assertEqual('smtp', zuul.reporter.smtp.SMTPReporter.name)
|
||||
|
||||
|
||||
class TestGerritReporter(testtools.TestCase):
|
||||
log = logging.getLogger("zuul.test_reporter")
|
||||
|
||||
def setUp(self):
|
||||
super(TestGerritReporter, self).setUp()
|
||||
|
||||
def test_reporter_abc(self):
|
||||
# We only need to instantiate a class for this
|
||||
reporter = zuul.reporter.gerrit.GerritReporter(None) # noqa
|
||||
|
||||
def test_reporter_name(self):
|
||||
self.assertEqual('gerrit', zuul.reporter.gerrit.GerritReporter.name)
|
@ -1,51 +0,0 @@
|
||||
# Copyright 2014 Rackspace Australia
|
||||
#
|
||||
# Licensed 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 logging
|
||||
import testtools
|
||||
|
||||
import zuul.trigger
|
||||
|
||||
|
||||
class TestGerritTrigger(testtools.TestCase):
|
||||
log = logging.getLogger("zuul.test_trigger")
|
||||
|
||||
def test_trigger_abc(self):
|
||||
# We only need to instantiate a class for this
|
||||
zuul.trigger.gerrit.GerritTrigger({})
|
||||
|
||||
def test_trigger_name(self):
|
||||
self.assertEqual('gerrit', zuul.trigger.gerrit.GerritTrigger.name)
|
||||
|
||||
|
||||
class TestTimerTrigger(testtools.TestCase):
|
||||
log = logging.getLogger("zuul.test_trigger")
|
||||
|
||||
def test_trigger_abc(self):
|
||||
# We only need to instantiate a class for this
|
||||
zuul.trigger.timer.TimerTrigger({})
|
||||
|
||||
def test_trigger_name(self):
|
||||
self.assertEqual('timer', zuul.trigger.timer.TimerTrigger.name)
|
||||
|
||||
|
||||
class TestZuulTrigger(testtools.TestCase):
|
||||
log = logging.getLogger("zuul.test_trigger")
|
||||
|
||||
def test_trigger_abc(self):
|
||||
# We only need to instantiate a class for this
|
||||
zuul.trigger.zuultrigger.ZuulTrigger({})
|
||||
|
||||
def test_trigger_name(self):
|
||||
self.assertEqual('zuul', zuul.trigger.zuultrigger.ZuulTrigger.name)
|
@ -0,0 +1,167 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
|
||||
class Driver(object):
|
||||
"""A Zuul Driver.
|
||||
|
||||
A Driver is an extension component of Zuul that supports
|
||||
interfacing with a remote system. It can support any of the
|
||||
following interfaces:
|
||||
|
||||
* Connection
|
||||
* Source
|
||||
* Trigger
|
||||
* Reporter
|
||||
|
||||
Drivers supporting each of these interfaces must implement some of
|
||||
the following methods, as appropriate.
|
||||
|
||||
Zuul will create a single instance of each Driver (which will be
|
||||
shared by all tenants), and this instance will persist for the
|
||||
life of the process. The Driver class may therefore manage any
|
||||
global state used by all connections.
|
||||
|
||||
The class or instance attribute **name** must be provided as a string.
|
||||
|
||||
"""
|
||||
|
||||
name = None
|
||||
|
||||
def getConnection(self, name, config):
|
||||
"""Create and return a new Connection object.
|
||||
|
||||
Required if this driver implements the Connection interface.
|
||||
|
||||
This method will be called once for each connection specified
|
||||
in zuul.conf. The resultant object should be responsible for
|
||||
establishing any long-lived connections to remote systems. If
|
||||
Zuul is reconfigured, all existing connections will be stopped
|
||||
and this method will be called again for any new connections
|
||||
which should be created.
|
||||
|
||||
When a connection is specified in zuul.conf with a name, that
|
||||
name is used here when creating the connection, and it is also
|
||||
used in the layout to attach triggers and reporters to the
|
||||
named connection. If the Driver does not utilize a connection
|
||||
(because it does not interact with a remote system), do not
|
||||
implement this method and Zuul will automatically associate
|
||||
triggers and reporters with the name of the Driver itself
|
||||
where it would normally expect the name of a connection.
|
||||
|
||||
:arg str name: The name of the connection. This is the name
|
||||
supplied in the zuul.conf file where the connection is
|
||||
configured.
|
||||
:arg dict config: The configuration information supplied along
|
||||
with the connection in zuul.conf.
|
||||
|
||||
:returns: A new Connection object.
|
||||
:rtype: Connection
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getTrigger(self, connection, config=None):
|
||||
"""Create and return a new Connection object.
|
||||
|
||||
Required if this driver implements the Trigger interface.
|
||||
|
||||
:arg Connection connection: The Connection object associated
|
||||
with the trigger (as previously returned by getConnection)
|
||||
or None.
|
||||
:arg dict config: The configuration information supplied along
|
||||
with the trigger in the layout.
|
||||
|
||||
:returns: A new Trigger object.
|
||||
:rtype: Trigger
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getSource(self, connection):
|
||||
"""Create and return a new Source object.
|
||||
|
||||
Required if this driver implements the Source interface.
|
||||
|
||||
:arg Connection connection: The Connection object associated
|
||||
with the source (as previously returned by getConnection).
|
||||
|
||||
:returns: A new Source object.
|
||||
:rtype: Source
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getReporter(self, connection, config=None):
|
||||
"""Create and return a new Reporter object.
|
||||
|
||||
Required if this driver implements the Reporter interface.
|
||||
|
||||
:arg Connection connection: The Connection object associated
|
||||
with the reporter (as previously returned by getConnection)
|
||||
or None.
|
||||
:arg dict config: The configuration information supplied along
|
||||
with the reporter in the layout.
|
||||
|
||||
:returns: A new Reporter object.
|
||||
:rtype: Reporter
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getTriggerSchema(self):
|
||||
"""Get the schema for this driver's trigger.
|
||||
|
||||
Required if this driver implements the Trigger interface.
|
||||
|
||||
:returns: A voluptuous schema.
|
||||
:rtype: dict or Schema
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getReporterSchema(self):
|
||||
"""Get the schema for this driver's reporter.
|
||||
|
||||
Required if this driver implements the Reporter interface.
|
||||
|
||||
:returns: A voluptuous schema.
|
||||
:rtype: dict or Schema
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def reconfigure(self, tenant):
|
||||
"""Called when a tenant is reconfigured.
|
||||
|
||||
When Zuul performs a reconfiguration for a tenant, this method
|
||||
is called with the tenant (including the new layout
|
||||
configuration) as an argument. The driver may establish any
|
||||
global resources needed by the tenant at this point.
|
||||
|
||||
:arg Tenant tenant: The tenant which has been reconfigured.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def registerScheduler(self, scheduler):
|
||||
"""Register the scheduler with the driver.
|
||||
|
||||
This method is called once during initialization to allow the
|
||||
driver to store a handle to the running scheduler.
|
||||
|
||||
:arg Scheduler scheduler: The current running scheduler.
|
||||
|
||||
"""
|
||||
pass
|
@ -0,0 +1,40 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# Licensed 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 gerritconnection
|
||||
import gerrittrigger
|
||||
import gerritsource
|
||||
import gerritreporter
|
||||
|
||||
|
||||
class GerritDriver(object):
|
||||
name = 'gerrit'
|
||||
|
||||
def getConnection(self, name, config):
|
||||
return gerritconnection.GerritConnection(self, name, config)
|
||||
|
||||
def getTrigger(self, connection, config=None):
|
||||
return gerrittrigger.GerritTrigger(self, connection, config)
|
||||
|
||||
def getSource(self, connection):
|
||||
return gerritsource.GerritSource(self, connection)
|
||||
|
||||
def getReporter(self, connection, config=None):
|
||||
return gerritreporter.GerritReporter(self, connection, config)
|
||||
|
||||
def getTriggerSchema(self):
|
||||
return gerrittrigger.getSchema()
|
||||
|
||||
def getReporterSchema(self):
|
||||
return gerritreporter.getSchema()
|
@ -0,0 +1,94 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# Licensed 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 logging
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from zuul.model import TriggerEvent
|
||||
import timertrigger
|
||||
|
||||
|
||||
class TimerDriver(object):
|
||||
name = 'timer'
|
||||
|
||||
log = logging.getLogger("zuul.Timer")
|
||||
|
||||
def __init__(self):
|
||||
self.apsched = BackgroundScheduler()
|
||||
self.apsched.start()
|
||||
self.tenant_jobs = {}
|
||||
|
||||
def registerScheduler(self, scheduler):
|
||||
self.sched = scheduler
|
||||
|
||||
def reconfigure(self, tenant):
|
||||
self._removeJobs(tenant)
|
||||
self._addJobs(tenant)
|
||||
|
||||
def _removeJobs(self, tenant):
|
||||
jobs = self.tenant_jobs.get(tenant.name, [])
|
||||
for job in jobs:
|
||||
job.remove()
|
||||
|
||||
def _addJobs(self, tenant):
|
||||
jobs = []
|
||||
self.tenant_jobs[tenant.name] = jobs
|
||||
for pipeline in tenant.layout.pipelines:
|
||||
for ef in pipeline.manager.event_filters:
|
||||
if not isinstance(ef.trigger, timertrigger.TimerTrigger):
|
||||
continue
|
||||
for timespec in ef.timespecs:
|
||||
parts = timespec.split()
|
||||
if len(parts) < 5 or len(parts) > 6:
|
||||
self.log.error(
|
||||
"Unable to parse time value '%s' "
|
||||
"defined in pipeline %s" % (
|
||||
timespec,
|
||||
pipeline.name))
|
||||
continue
|
||||
minute, hour, dom, month, dow = parts[:5]
|
||||
if len(parts) > 5:
|
||||
second = parts[5]
|
||||
else:
|
||||
second = None
|
||||
trigger = CronTrigger(day=dom, day_of_week=dow, hour=hour,
|
||||
minute=minute, second=second)
|
||||
|
||||
job = self.apsched.add_job(
|
||||
self._onTrigger, trigger=trigger,
|
||||
args=(tenant, pipeline.name, timespec,))
|
||||
jobs.append(job)
|
||||
|
||||
def _onTrigger(self, tenant, pipeline_name, timespec):
|
||||
for project in tenant.layout.projects.values():
|
||||
event = TriggerEvent()
|
||||
event.type = 'timer'
|
||||
event.timespec = timespec
|
||||
event.forced_pipeline = pipeline_name
|
||||
event.project_name = project.name
|
||||
self.log.debug("Adding event %s" % event)
|
||||
self.sched.addEvent(event)
|
||||
|
||||
def stop(self):
|
||||
self.apsched.shutdown()
|
||||
|
||||
def getTrigger(self, connection_name):
|
||||
return timertrigger.TimerTrigger(self)
|
||||
|
||||
def getTriggerSchema(self):
|
||||
return timertrigger.getSchema()
|
@ -0,0 +1,46 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed 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 voluptuous as v
|
||||
|
||||
from zuul.model import EventFilter
|
||||
from zuul.trigger import BaseTrigger
|
||||
|
||||
|
||||
class TimerTrigger(BaseTrigger):
|
||||
name = 'timer'
|
||||
|
||||
def getEventFilters(self, trigger_conf):
|
||||
def toList(item):
|
||||
if not item:
|
||||
return []
|
||||
if isinstance(item, list):
|
||||
return item
|
||||
return [item]
|
||||
|
||||
efilters = []
|
||||
for trigger in toList(trigger_conf):
|
||||
f = EventFilter(trigger=self,
|
||||
types=['timer'],
|
||||
timespecs=toList(trigger['time']))
|
||||
|
||||
efilters.append(f)
|
||||
|
||||
return efilters
|
||||
|
||||
|
||||
def getSchema():
|
||||
timer_trigger = {v.Required('time'): str}
|
||||
return timer_trigger
|
@ -0,0 +1,111 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# Licensed 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 logging
|
||||
|
||||
from zuul.model import TriggerEvent
|
||||
|
||||
import zuultrigger
|
||||
|
||||
PARENT_CHANGE_ENQUEUED = 'parent-change-enqueued'
|
||||
PROJECT_CHANGE_MERGED = 'project-change-merged'
|
||||
|
||||
|
||||
class ZuulDriver(object):
|
||||
name = 'zuul'
|
||||
log = logging.getLogger("zuul.ZuulTrigger")
|
||||
|
||||
def __init__(self):
|
||||
self.tenant_events = {}
|
||||
|
||||
def registerScheduler(self, scheduler):
|
||||
self.sched = scheduler
|
||||
|
||||
def reconfigure(self, tenant):
|
||||
events = set()
|
||||
self.tenant_events[tenant.name] = events
|
||||
for pipeline in tenant.layout.pipelines.values():
|
||||
for ef in pipeline.manager.event_filters:
|
||||
if not isinstance(ef.trigger, zuultrigger.ZuulTrigger):
|
||||
continue
|
||||
if PARENT_CHANGE_ENQUEUED in ef._types:
|
||||
events.add(PARENT_CHANGE_ENQUEUED)
|
||||
elif PROJECT_CHANGE_MERGED in ef._types:
|
||||
events.add(PROJECT_CHANGE_MERGED)
|
||||
|
||||
def onChangeMerged(self, tenant, change, source):
|
||||
# Called each time zuul merges a change
|
||||
if PROJECT_CHANGE_MERGED in self.tenant_events[tenant.name]:
|
||||
try:
|
||||
self._createProjectChangeMergedEvents(change, source)
|
||||
except Exception:
|
||||
self.log.exception(
|
||||
"Unable to create project-change-merged events for "
|
||||
"%s" % (change,))
|
||||
|
||||
def onChangeEnqueued(self, tenant, change, pipeline):
|
||||
self.log.debug("onChangeEnqueued %s", self.tenant_events[tenant.name])
|
||||
# Called each time a change is enqueued in a pipeline
|
||||
if PARENT_CHANGE_ENQUEUED in self.tenant_events[tenant.name]:
|
||||
try:
|
||||
self._createParentChangeEnqueuedEvents(change, pipeline)
|
||||
except Exception:
|
||||
self.log.exception(
|
||||
"Unable to create parent-change-enqueued events for "
|
||||
"%s in %s" % (change, pipeline))
|
||||
|
||||
def _createProjectChangeMergedEvents(self, change, source):
|
||||
changes = source.getProjectOpenChanges(
|
||||
change.project)
|
||||
for open_change in changes:
|
||||
self._createProjectChangeMergedEvent(open_change)
|
||||
|
||||
def _createProjectChangeMergedEvent(self, change):
|
||||
event = TriggerEvent()
|
||||
event.type = PROJECT_CHANGE_MERGED
|
||||
event.trigger_name = self.name
|
||||
event.project_name = change.project.name
|
||||
event.change_number = change.number
|
||||
event.branch = change.branch
|
||||
event.change_url = change.url
|
||||
event.patch_number = change.patchset
|
||||
event.refspec = change.refspec
|
||||
self.sched.addEvent(event)
|
||||
|
||||
def _createParentChangeEnqueuedEvents(self, change, pipeline):
|
||||
self.log.debug("Checking for changes needing %s:" % change)
|
||||
if not hasattr(change, 'needed_by_changes'):
|
||||
self.log.debug(" Changeish does not support dependencies")
|
||||
return
|
||||
for needs in change.needed_by_changes:
|
||||
self._createParentChangeEnqueuedEvent(needs, pipeline)
|
||||
|
||||
def _createParentChangeEnqueuedEvent(self, change, pipeline):
|
||||
event = TriggerEvent()
|
||||
event.type = PARENT_CHANGE_ENQUEUED
|
||||
event.trigger_name = self.name
|
||||
event.pipeline_name = pipeline.name
|
||||
event.project_name = change.project.name
|
||||
event.change_number = change.number
|
||||
event.branch = change.branch
|
||||
event.change_url = change.url
|
||||
event.patch_number = change.patchset
|
||||
event.refspec = change.refspec
|
||||
self.sched.addEvent(event)
|
||||
|
||||
def getTrigger(self, connection_name, config=None):
|
||||
return zuultrigger.ZuulTrigger(self, config)
|
||||
|
||||
def getTriggerSchema(self):
|
||||
return zuultrigger.getSchema()
|
@ -0,0 +1,77 @@
|
||||
# Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed 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 logging
|
||||
import voluptuous as v
|
||||
from zuul.model import EventFilter
|
||||
from zuul.trigger import BaseTrigger
|
||||
|
||||
|
||||
class ZuulTrigger(BaseTrigger):
|
||||
name = 'zuul'
|
||||
log = logging.getLogger("zuul.ZuulTrigger")
|
||||
|
||||
def __init__(self, connection, config=None):
|
||||
super(ZuulTrigger, self).__init__(connection, config)
|
||||
self._handle_parent_change_enqueued_events = False
|
||||
self._handle_project_change_merged_events = False
|
||||
|
||||
def getEventFilters(self, trigger_conf):
|
||||
def toList(item):
|
||||
if not item:
|
||||
return []
|
||||
if isinstance(item, list):
|
||||
return item
|
||||
return [item]
|
||||
|
||||
efilters = []
|
||||
for trigger in toList(trigger_conf):
|
||||
f = EventFilter(
|
||||
trigger=self,
|
||||
types=toList(trigger['event']),
|
||||
pipelines=toList(trigger.get('pipeline')),
|
||||
required_approvals=(
|
||||
toList(trigger.get('require-approval'))
|
||||
),
|
||||
reject_approvals=toList(
|
||||
trigger.get('reject-approval')
|
||||
),
|
||||
)
|
||||
efilters.append(f)
|
||||
|
||||
return efilters
|
||||
|
||||
|
||||
def getSchema():
|
||||
def toList(x):
|
||||
return v.Any([x], x)
|
||||
|
||||
approval = v.Schema({'username': str,
|
||||
'email-filter': str,
|
||||
'email': str,
|
||||
'older-than': str,
|
||||
'newer-than': str,
|
||||
}, extra=True)
|
||||
|
||||
zuul_trigger = {
|
||||
v.Required('event'):
|
||||
toList(v.Any('parent-change-enqueued',
|
||||
'project-change-merged')),
|
||||
'pipeline': toList(str),
|
||||
'require-approval': toList(approval),
|
||||
'reject-approval': toList(approval),
|
||||
}
|
||||
|
||||
return zuul_trigger
|
@ -1,93 +0,0 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
import logging
|
||||
import voluptuous as v
|
||||
from zuul.model import EventFilter, TriggerEvent
|
||||
from zuul.trigger import BaseTrigger
|
||||
|
||||
|
||||
class TimerTrigger(BaseTrigger):
|
||||
name = 'timer'
|
||||
log = logging.getLogger("zuul.Timer")
|
||||
|
||||
def __init__(self, trigger_config={}, connection=None):
|
||||
super(TimerTrigger, self).__init__(trigger_config, connection)
|
||||
self.apsched = BackgroundScheduler()
|
||||
self.apsched.start()
|
||||
|
||||
def _onTrigger(self, pipeline_name, timespec):
|
||||
for project in self.sched.layout.projects.values():
|
||||
event = TriggerEvent()
|
||||
event.type = 'timer'
|
||||
event.timespec = timespec
|
||||
event.forced_pipeline = pipeline_name
|
||||
event.project_name = project.name
|
||||
self.log.debug("Adding event %s" % event)
|
||||
self.connection.sched.addEvent(event)
|
||||
|
||||
def stop(self):
|
||||
self.apsched.shutdown()
|
||||
|
||||
def getEventFilters(self, trigger_conf):
|
||||
def toList(item):
|
||||
if not item:
|
||||
return []
|
||||
if isinstance(item, list):
|
||||
return item
|
||||
return [item]
|
||||
|
||||
efilters = []
|
||||
for trigger in toList(trigger_conf):
|
||||
f = EventFilter(trigger=self,
|
||||
types=['timer'],
|
||||
timespecs=toList(trigger['time']))
|
||||
|
||||
efilters.append(f)
|
||||
|
||||
return efilters
|
||||
|
||||
def postConfig(self, pipeline):
|
||||
for job in self.apsched.get_jobs():
|
||||
job.remove()
|
||||
for ef in pipeline.manager.event_filters:
|
||||
if ef.trigger != self:
|
||||
continue
|
||||
for timespec in ef.timespecs:
|
||||
parts = timespec.split()
|
||||
if len(parts) < 5 or len(parts) > 6:
|
||||
self.log.error(
|
||||
"Unable to parse time value '%s' "
|
||||
"defined in pipeline %s" % (
|
||||
timespec,
|
||||
pipeline.name))
|
||||
continue
|
||||
minute, hour, dom, month, dow = parts[:5]
|
||||
if len(parts) > 5:
|
||||
second = parts[5]
|
||||
else:
|
||||
second = None
|
||||
trigger = CronTrigger(day=dom, day_of_week=dow, hour=hour,
|
||||
minute=minute, second=second)
|
||||
|
||||
self.apsched.add_job(self._onTrigger, trigger=trigger,
|
||||
args=(pipeline.name, timespec,))
|
||||
|
||||
|
||||
def getSchema():
|
||||
timer_trigger = {v.Required('time'): str}
|
||||
return timer_trigger
|
@ -1,147 +0,0 @@
|
||||
# Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||