Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 1 | # 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 | |
| 7 | from __future__ import print_function |
| 8 | |
| 9 | import base64 |
| 10 | import logging |
| 11 | |
| 12 | import common |
| 13 | |
| 14 | from autotest_lib.client.common_lib import global_config |
| 15 | from autotest_lib.client.common_lib import utils |
| 16 | from autotest_lib.server.hosts import moblab_host |
| 17 | from autotest_lib.site_utils import pubsub_utils |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 18 | from autotest_lib.site_utils import cloud_console_pb2 as cpcon |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 19 | |
| 20 | |
| 21 | _PUBSUB_TOPIC = global_config.global_config.get_config_value( |
| 22 | 'CROS', 'cloud_notification_topic', default=None) |
| 23 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 24 | # Current notification version. |
| 25 | CURRENT_MESSAGE_VERSION = '1' |
| 26 | |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 27 | # Test upload pubsub notification attributes |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 28 | LEGACY_ATTR_VERSION = 'version' |
| 29 | LEGACY_ATTR_GCS_URI = 'gcs_uri' |
| 30 | LEGACY_ATTR_MOBLAB_MAC = 'moblab_mac_address' |
| 31 | LEGACY_ATTR_MOBLAB_ID = 'moblab_id' |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 32 | # the message data for new test result notification. |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 33 | LEGACY_TEST_OFFLOAD_MESSAGE = 'NEW_TEST_RESULT' |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 34 | |
| 35 | |
| 36 | def 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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 45 | def _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 | |
| 55 | def _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 Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 65 | class 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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 87 | def send_log(self, msg, level=None, session_id=None): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 88 | """Sends a log message to the remote console. |
| 89 | |
| 90 | @param msg: The log message. |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 91 | @param level: The logging level. |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 92 | @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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 99 | def send_alert(self, msg, level=None, session_id=None): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 100 | """Sends an alert to the remote console. |
| 101 | |
| 102 | @param msg: The alert message. |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 103 | @param level: The logging level. |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 104 | @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 |
| 123 | def _create_pubsub_client(credential): |
| 124 | return pubsub_utils.PubSubClient(credential) |
| 125 | |
| 126 | |
| 127 | class 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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 145 | def _create_message(self, data, msg_attributes): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 146 | """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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 153 | message = {} |
| 154 | if data: |
| 155 | message['data'] = data |
| 156 | if msg_attributes: |
| 157 | message['attributes'] = msg_attributes |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 158 | return message |
| 159 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 160 | def _create_message_attributes(self, message_type_enum): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 161 | """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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 166 | @param message_type_enum The message type enum. |
| 167 | |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 168 | @returns: A pubsub messsage attribute map. |
| 169 | """ |
| 170 | msg_attributes = {} |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 171 | 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 Haddow | a4b55dd | 2018-02-28 14:34:59 -0800 | [diff] [blame] | 176 | utils.get_moblab_serial_number()) |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 177 | msg_attributes[_get_attribute_name(cpcon.ATTR_MOBLAB_ID)] = ( |
| 178 | utils.get_moblab_id()) |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 179 | return msg_attributes |
| 180 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 181 | def _create_test_job_offloaded_message(self, gcs_uri): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 182 | """Construct a test result notification. |
| 183 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 184 | TODO(ntang): switch LEGACY to new message format. |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 185 | @param gcs_uri: The test result Google Cloud Storage URI. |
| 186 | |
| 187 | @returns The notification message. |
| 188 | """ |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 189 | 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 Haddow | a4b55dd | 2018-02-28 14:34:59 -0800 | [diff] [blame] | 193 | utils.get_moblab_serial_number()) |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 194 | msg_attributes[LEGACY_ATTR_MOBLAB_ID] = utils.get_moblab_id() |
| 195 | msg_attributes[LEGACY_ATTR_GCS_URI] = gcs_uri |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 196 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 197 | return self._create_message(data, msg_attributes) |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 198 | |
| 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 Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 209 | message = self._create_test_job_offloaded_message(gcs_uri) |
| 210 | return self._publish_notification(message) |
| 211 | |
| 212 | |
| 213 | def _publish_notification(self, message): |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 214 | msg_ids = self._pubsub_client.publish_notifications( |
| 215 | self._pubsub_topic, [message]) |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 216 | |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 217 | if msg_ids: |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 218 | logging.debug('Successfully sent out a notification') |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 219 | return True |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 220 | logging.warning('Failed to send notification %s', str(message)) |
Michael Tang | 0f553bd | 2017-06-16 17:38:45 -0700 | [diff] [blame] | 221 | return False |
| 222 | |
Michael Tang | e8bc959 | 2017-07-06 10:59:32 -0700 | [diff] [blame] | 223 | 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) |