blob: 343abefd805ac81bf8ed6bfb23fb8363e15cb52e [file] [log] [blame]
Michael Tang0f553bd2017-06-16 17:38:45 -07001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Chrome OS Parnter Concole remote actions."""
6
7from __future__ import print_function
8
9import base64
10import logging
11
12import common
13
14from autotest_lib.client.common_lib import global_config
15from autotest_lib.client.common_lib import utils
16from autotest_lib.server.hosts import moblab_host
17from autotest_lib.site_utils import pubsub_utils
Michael Tange8bc9592017-07-06 10:59:32 -070018from autotest_lib.site_utils import cloud_console_pb2 as cpcon
Michael Tang0f553bd2017-06-16 17:38:45 -070019
20
21_PUBSUB_TOPIC = global_config.global_config.get_config_value(
22 'CROS', 'cloud_notification_topic', default=None)
23
Michael Tange8bc9592017-07-06 10:59:32 -070024# Current notification version.
25CURRENT_MESSAGE_VERSION = '1'
26
Michael Tang0f553bd2017-06-16 17:38:45 -070027# Test upload pubsub notification attributes
Michael Tange8bc9592017-07-06 10:59:32 -070028LEGACY_ATTR_VERSION = 'version'
29LEGACY_ATTR_GCS_URI = 'gcs_uri'
30LEGACY_ATTR_MOBLAB_MAC = 'moblab_mac_address'
31LEGACY_ATTR_MOBLAB_ID = 'moblab_id'
Michael Tang0f553bd2017-06-16 17:38:45 -070032# the message data for new test result notification.
Michael Tange8bc9592017-07-06 10:59:32 -070033LEGACY_TEST_OFFLOAD_MESSAGE = 'NEW_TEST_RESULT'
Michael Tang0f553bd2017-06-16 17:38:45 -070034
35
36def is_cloud_notification_enabled():
37 """Checks if cloud pubsub notification is enabled.
38
39 @returns: True if cloud pubsub notification is enabled. Otherwise, False.
40 """
41 return global_config.global_config.get_config_value(
42 'CROS', 'cloud_notification_enabled', type=bool, default=False)
43
44
Michael Tange8bc9592017-07-06 10:59:32 -070045def _get_message_type_name(message_type_enum):
46 """Gets the message type name from message type enum.
47
48 @param message_type_enum: The message type enum.
49
50 @return The corresponding message type name as string, or 'MSG_UNKNOWN'.
51 """
52 return cpcon.MessageType.Name(message_type_enum)
53
54
55def _get_attribute_name(attribute_enum):
56 """Gets the message attribute name from attribte enum.
57
58 @param attribute_enum: The attribute enum.
59
60 @return The corresponding attribute name as string, or 'ATTR_INVALID'.
61 """
62 return cpcon.MessageAttribute.Name(attribute_enum)
63
64
Michael Tang0f553bd2017-06-16 17:38:45 -070065class CloudConsoleClient(object):
66 """The remote interface to the Cloud Console."""
67 def send_heartbeat(self):
68 """Sends a heartbeat.
69
70 @returns True if the notification is successfully sent.
71 Otherwise, False.
72 """
73 pass
74
75 def send_event(self, event_type=None, event_data=None):
76 """Sends an event notification to the remote console.
77
78 @param event_type: The event type that is defined in the protobuffer
79 file 'cloud_console.proto'.
80 @param event_data: The event data.
81
82 @returns True if the notification is successfully sent.
83 Otherwise, False.
84 """
85 pass
86
Michael Tange8bc9592017-07-06 10:59:32 -070087 def send_log(self, msg, level=None, session_id=None):
Michael Tang0f553bd2017-06-16 17:38:45 -070088 """Sends a log message to the remote console.
89
90 @param msg: The log message.
Michael Tange8bc9592017-07-06 10:59:32 -070091 @param level: The logging level.
Michael Tang0f553bd2017-06-16 17:38:45 -070092 @param session_id: The current session id.
93
94 @returns True if the notification is successfully sent.
95 Otherwise, False.
96 """
97 pass
98
Michael Tange8bc9592017-07-06 10:59:32 -070099 def send_alert(self, msg, level=None, session_id=None):
Michael Tang0f553bd2017-06-16 17:38:45 -0700100 """Sends an alert to the remote console.
101
102 @param msg: The alert message.
Michael Tange8bc9592017-07-06 10:59:32 -0700103 @param level: The logging level.
Michael Tang0f553bd2017-06-16 17:38:45 -0700104 @param session_id: The current session id.
105
106 @returns True if the notification is successfully sent.
107 Otherwise, False.
108 """
109 pass
110
111 def send_test_job_offloaded_message(self, gcs_uri):
112 """Sends a test job offloaded message to the remote console.
113
114 @param gcs_uri: The test result Google Cloud Storage URI.
115
116 @returns True if the notification is successfully sent.
117 Otherwise, False.
118 """
119 pass
120
121
122# Make it easy to mock out
123def _create_pubsub_client(credential):
124 return pubsub_utils.PubSubClient(credential)
125
126
127class PubSubBasedClient(CloudConsoleClient):
128 """A Cloud PubSub based implementation of the CloudConsoleClient interface.
129 """
130 def __init__(
131 self,
132 credential=moblab_host.MOBLAB_SERVICE_ACCOUNT_LOCATION,
133 pubsub_topic=_PUBSUB_TOPIC):
134 """Constructor.
135
136 @param credential: The service account credential filename. Default to
137 '/home/moblab/.service_account.json'.
138 @param pubsub_topic: The cloud pubsub topic name to use.
139 """
140 super(PubSubBasedClient, self).__init__()
141 self._pubsub_client = _create_pubsub_client(credential)
142 self._pubsub_topic = pubsub_topic
143
144
Michael Tange8bc9592017-07-06 10:59:32 -0700145 def _create_message(self, data, msg_attributes):
Michael Tang0f553bd2017-06-16 17:38:45 -0700146 """Creates a cloud pubsub notification object.
147
148 @param data: The message data as a string.
149 @param msg_attributes: The message attribute map.
150
151 @returns: A pubsub message object with data and attributes.
152 """
Michael Tange8bc9592017-07-06 10:59:32 -0700153 message = {}
154 if data:
155 message['data'] = data
156 if msg_attributes:
157 message['attributes'] = msg_attributes
Michael Tang0f553bd2017-06-16 17:38:45 -0700158 return message
159
Michael Tange8bc9592017-07-06 10:59:32 -0700160 def _create_message_attributes(self, message_type_enum):
Michael Tang0f553bd2017-06-16 17:38:45 -0700161 """Creates a cloud pubsub notification message attribute map.
162
163 Fills in the version, moblab mac address, and moblab id information
164 as attributes.
165
Michael Tange8bc9592017-07-06 10:59:32 -0700166 @param message_type_enum The message type enum.
167
Michael Tang0f553bd2017-06-16 17:38:45 -0700168 @returns: A pubsub messsage attribute map.
169 """
170 msg_attributes = {}
Michael Tange8bc9592017-07-06 10:59:32 -0700171 msg_attributes[_get_attribute_name(cpcon.ATTR_MESSAGE_TYPE)] = (
172 _get_message_type_name(message_type_enum))
173 msg_attributes[_get_attribute_name(cpcon.ATTR_MESSAGE_VERSION)] = (
174 CURRENT_MESSAGE_VERSION)
175 msg_attributes[_get_attribute_name(cpcon.ATTR_MOBLAB_MAC_ADDRESS)] = (
Keith Haddowa4b55dd2018-02-28 14:34:59 -0800176 utils.get_moblab_serial_number())
Michael Tange8bc9592017-07-06 10:59:32 -0700177 msg_attributes[_get_attribute_name(cpcon.ATTR_MOBLAB_ID)] = (
178 utils.get_moblab_id())
Michael Tang0f553bd2017-06-16 17:38:45 -0700179 return msg_attributes
180
Michael Tange8bc9592017-07-06 10:59:32 -0700181 def _create_test_job_offloaded_message(self, gcs_uri):
Michael Tang0f553bd2017-06-16 17:38:45 -0700182 """Construct a test result notification.
183
Michael Tange8bc9592017-07-06 10:59:32 -0700184 TODO(ntang): switch LEGACY to new message format.
Michael Tang0f553bd2017-06-16 17:38:45 -0700185 @param gcs_uri: The test result Google Cloud Storage URI.
186
187 @returns The notification message.
188 """
Michael Tange8bc9592017-07-06 10:59:32 -0700189 data = base64.b64encode(LEGACY_TEST_OFFLOAD_MESSAGE)
190 msg_attributes = {}
191 msg_attributes[LEGACY_ATTR_VERSION] = CURRENT_MESSAGE_VERSION
192 msg_attributes[LEGACY_ATTR_MOBLAB_MAC] = (
Keith Haddowa4b55dd2018-02-28 14:34:59 -0800193 utils.get_moblab_serial_number())
Michael Tange8bc9592017-07-06 10:59:32 -0700194 msg_attributes[LEGACY_ATTR_MOBLAB_ID] = utils.get_moblab_id()
195 msg_attributes[LEGACY_ATTR_GCS_URI] = gcs_uri
Michael Tang0f553bd2017-06-16 17:38:45 -0700196
Michael Tange8bc9592017-07-06 10:59:32 -0700197 return self._create_message(data, msg_attributes)
Michael Tang0f553bd2017-06-16 17:38:45 -0700198
199
200 def send_test_job_offloaded_message(self, gcs_uri):
201 """Notify the cloud console a test job is offloaded.
202
203 @param gcs_uri: The test result Google Cloud Storage URI.
204
205 @returns True if the notification is successfully sent.
206 Otherwise, False.
207 """
208 logging.info('Notification on gcs_uri %s', gcs_uri)
Michael Tange8bc9592017-07-06 10:59:32 -0700209 message = self._create_test_job_offloaded_message(gcs_uri)
210 return self._publish_notification(message)
211
212
213 def _publish_notification(self, message):
Michael Tang0f553bd2017-06-16 17:38:45 -0700214 msg_ids = self._pubsub_client.publish_notifications(
215 self._pubsub_topic, [message])
Michael Tange8bc9592017-07-06 10:59:32 -0700216
Michael Tang0f553bd2017-06-16 17:38:45 -0700217 if msg_ids:
Michael Tange8bc9592017-07-06 10:59:32 -0700218 logging.debug('Successfully sent out a notification')
Michael Tang0f553bd2017-06-16 17:38:45 -0700219 return True
Michael Tange8bc9592017-07-06 10:59:32 -0700220 logging.warning('Failed to send notification %s', str(message))
Michael Tang0f553bd2017-06-16 17:38:45 -0700221 return False
222
Michael Tange8bc9592017-07-06 10:59:32 -0700223 def send_heartbeat(self):
224 """Sends a heartbeat.
225
226 @returns True if the heartbeat notification is successfully sent.
227 Otherwise, False.
228 """
229 logging.info('Sending a heartbeat')
230
231 event = cpcon.Heartbeat()
232 # Don't sent local timestamp for now.
233 data = event.SerializeToString()
234 try:
235 attributes = self._create_message_attributes(
236 cpcon.MSG_MOBLAB_HEARTBEAT)
237 message = self._create_message(data, attributes)
238 except ValueError:
239 logging.exception('Failed to create message.')
240 return False
241 return self._publish_notification(message)
242
243 def send_event(self, event_type=None, event_data=None):
244 """Sends an event notification to the remote console.
245
246 @param event_type: The event type that is defined in the protobuffer
247 file 'cloud_console.proto'.
248 @param event_data: The event data.
249
250 @returns True if the notification is successfully sent.
251 Otherwise, False.
252 """
253 logging.info('Send an event.')
254 if not event_type:
255 logging.info('Failed to send event without a type.')
256 return False
257
258 event = cpcon.RemoteEventMessage()
259 if event_data:
260 event.data = event_data
261 else:
262 event.data = ''
263 event.type = event_type
264 data = event.SerializeToString()
265 try:
266 attributes = self._create_message_attributes(
267 cpcon.MSG_MOBLAB_REMOTE_EVENT)
268 message = self._create_message(data, attributes)
269 except ValueError:
270 logging.exception('Failed to create message.')
271 return False
272 return self._publish_notification(message)