[ews-build] Buildbot should include builder_display_name in the build events
[WebKit-https.git] / Tools / BuildSlaveSupport / ews-build / events.py
1 # Copyright (C) 2019 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 import datetime
24 import json
25 import time
26
27 from buildbot.util import service
28 from twisted.internet import defer
29 from twisted.internet import reactor
30 from twisted.internet.defer import succeed
31 from twisted.python import log
32 from twisted.web.client import Agent
33 from twisted.web.http_headers import Headers
34 from twisted.web.iweb import IBodyProducer
35 from zope.interface import implements
36
37
38 class JSONProducer(object):
39     """
40     Perform JSON asynchronously as to not lock the buildbot main event loop
41     """
42     implements(IBodyProducer)
43
44     def __init__(self, data):
45         try:
46             self.body = json.dumps(data, default=self.json_serialize_datetime)
47         except TypeError:
48             self.body = ''
49         self.length = len(self.body)
50
51     def startProducing(self, consumer):
52         if self.body:
53             consumer.write(self.body)
54         return succeed(None)
55
56     def pauseProducing(self):
57         pass
58
59     def stopProducing(self):
60         pass
61
62     def json_serialize_datetime(self, obj):
63         """
64         Serializing buildbot dates into UNIX epoch timestamps.
65         """
66         if isinstance(obj, datetime.datetime):
67             return int(time.mktime(obj.timetuple()))
68
69         raise TypeError("Type %s not serializable" % type(obj))
70
71
72 class Events(service.BuildbotService):
73
74     EVENT_SERVER_ENDPOINT = 'http://ews.webkit-uat.org/results/'
75
76     def __init__(self, type_prefix='', name='Events'):
77         """
78         Initialize the Events Plugin. Sends data to event server on specific buildbot events.
79         :param type_prefix: [optional] prefix we want to add to the 'type' field on the json we send
80          to event server. (i.e. ews-build, where 'ews-' is the prefix.
81         :return: Events Object
82         """
83         service.BuildbotService.__init__(self, name=name)
84
85         if type_prefix and not type_prefix.endswith("-"):
86             type_prefix += "-"
87         self.type_prefix = type_prefix
88
89     def sendData(self, data):
90         agent = Agent(reactor)
91         body = JSONProducer(data)
92
93         agent.request('POST', self.EVENT_SERVER_ENDPOINT, Headers({'Content-Type': ['application/json']}), body)
94
95     def getBuilderName(self, build):
96         if not (build and 'properties' in build):
97             return ''
98
99         return build.get('properties').get('buildername')[0]
100
101     def getPatchID(self, build):
102         if not (build and 'properties' in build):
103             return None
104
105         return build.get('properties').get('patch_id')[0]
106
107     @defer.inlineCallbacks
108     def buildStarted(self, key, build):
109         if not build.get('properties'):
110             build['properties'] = yield self.master.db.builds.getBuildProperties(build.get('buildid'))
111
112         builder = yield self.master.db.builders.getBuilder(build.get('builderid'))
113         builder_display_name = builder.get('description')
114
115         data = {
116             "type": self.type_prefix + "build",
117             "status": "started",
118             "patch_id": self.getPatchID(build),
119             "build_id": build.get('buildid'),
120             "builder_id": build.get('builderid'),
121             "number": build.get('number'),
122             "result": build.get('results'),
123             "started_at": build.get('started_at'),
124             "complete_at": build.get('complete_at'),
125             "state_string": build.get('state_string'),
126             "builder_name": self.getBuilderName(build),
127             "builder_display_name": builder_display_name,
128         }
129
130         self.sendData(data)
131
132     @defer.inlineCallbacks
133     def buildFinished(self, key, build):
134         if not build.get('properties'):
135             build['properties'] = yield self.master.db.builds.getBuildProperties(build.get('buildid'))
136         if not build.get('steps'):
137             build['steps'] = yield self.master.db.steps.getSteps(build.get('buildid'))
138
139         builder = yield self.master.db.builders.getBuilder(build.get('builderid'))
140         builder_display_name = builder.get('description')
141
142         data = {
143             "type": self.type_prefix + "build",
144             "status": "finished",
145             "patch_id": self.getPatchID(build),
146             "build_id": build.get('buildid'),
147             "builder_id": build.get('builderid'),
148             "number": build.get('number'),
149             "result": build.get('results'),
150             "started_at": build.get('started_at'),
151             "complete_at": build.get('complete_at'),
152             "state_string": build.get('state_string'),
153             "builder_name": self.getBuilderName(build),
154             "builder_display_name": builder_display_name,
155             "steps": build.get('steps'),
156         }
157
158         self.sendData(data)
159
160     def stepStarted(self, key, step):
161         data = {
162             "type": self.type_prefix + "step",
163             "status": "started",
164             "step_id": step.get('stepid'),
165             "build_id": step.get('buildid'),
166             "result": step.get('results'),
167             "state_string": step.get('state_string'),
168             "started_at": step.get('started_at'),
169             "complete_at": step.get('complete_at'),
170         }
171
172         self.sendData(data)
173
174     def stepFinished(self, key, step):
175         data = {
176             "type": self.type_prefix + "step",
177             "status": "finished",
178             "step_id": step.get('stepid'),
179             "build_id": step.get('buildid'),
180             "result": step.get('results'),
181             "state_string": step.get('state_string'),
182             "started_at": step.get('started_at'),
183             "complete_at": step.get('complete_at'),
184         }
185
186         self.sendData(data)
187
188     @defer.inlineCallbacks
189     def startService(self):
190         yield service.BuildbotService.startService(self)
191
192         startConsuming = self.master.mq.startConsuming
193
194         self._buildStartedConsumer = yield startConsuming(self.buildStarted, ('builds', None, 'new'))
195         self._buildCompleteConsumer = yield startConsuming(self.buildFinished, ('builds', None, 'finished'))
196         self._stepStartedConsumer = yield startConsuming(self.stepStarted, ('steps', None, 'started'))
197         self._stepFinishedConsumer = yield startConsuming(self.stepFinished, ('steps', None, 'finished'))
198
199     def stopService(self):
200         self._buildStartedConsumer.stopConsuming()
201         self._buildCompleteConsumer.stopConsuming()
202         self._stepStartedConsumer.stopConsuming()
203         self._stepFinishedConsumer.stopConsuming()