1*9c5db199SXin Li# Copyright 2016 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Li"""ChromeOS Parnter Concole remote actions.""" 6*9c5db199SXin Li 7*9c5db199SXin Lifrom __future__ import print_function 8*9c5db199SXin Li 9*9c5db199SXin Liimport base64 10*9c5db199SXin Liimport logging 11*9c5db199SXin Li 12*9c5db199SXin Liimport common 13*9c5db199SXin Li 14*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config 15*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils 16*9c5db199SXin Lifrom autotest_lib.server.hosts import moblab_host 17*9c5db199SXin Lifrom autotest_lib.site_utils import pubsub_utils 18*9c5db199SXin Lifrom autotest_lib.site_utils import cloud_console_pb2 as cpcon 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Li_PUBSUB_TOPIC = global_config.global_config.get_config_value( 22*9c5db199SXin Li 'CROS', 'cloud_notification_topic', default=None) 23*9c5db199SXin Li 24*9c5db199SXin Li# Current notification version. 25*9c5db199SXin LiCURRENT_MESSAGE_VERSION = '1' 26*9c5db199SXin Li 27*9c5db199SXin Li# Test upload pubsub notification attributes 28*9c5db199SXin LiLEGACY_ATTR_VERSION = 'version' 29*9c5db199SXin LiLEGACY_ATTR_GCS_URI = 'gcs_uri' 30*9c5db199SXin LiLEGACY_ATTR_MOBLAB_MAC = 'moblab_mac_address' 31*9c5db199SXin LiLEGACY_ATTR_MOBLAB_ID = 'moblab_id' 32*9c5db199SXin Li# the message data for new test result notification. 33*9c5db199SXin LiLEGACY_TEST_OFFLOAD_MESSAGE = 'NEW_TEST_RESULT' 34*9c5db199SXin Li 35*9c5db199SXin Li 36*9c5db199SXin Lidef is_cloud_notification_enabled(): 37*9c5db199SXin Li """Checks if cloud pubsub notification is enabled. 38*9c5db199SXin Li 39*9c5db199SXin Li @returns: True if cloud pubsub notification is enabled. Otherwise, False. 40*9c5db199SXin Li """ 41*9c5db199SXin Li return global_config.global_config.get_config_value( 42*9c5db199SXin Li 'CROS', 'cloud_notification_enabled', type=bool, default=False) 43*9c5db199SXin Li 44*9c5db199SXin Li 45*9c5db199SXin Lidef _get_message_type_name(message_type_enum): 46*9c5db199SXin Li """Gets the message type name from message type enum. 47*9c5db199SXin Li 48*9c5db199SXin Li @param message_type_enum: The message type enum. 49*9c5db199SXin Li 50*9c5db199SXin Li @return The corresponding message type name as string, or 'MSG_UNKNOWN'. 51*9c5db199SXin Li """ 52*9c5db199SXin Li return cpcon.MessageType.Name(message_type_enum) 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Lidef _get_attribute_name(attribute_enum): 56*9c5db199SXin Li """Gets the message attribute name from attribte enum. 57*9c5db199SXin Li 58*9c5db199SXin Li @param attribute_enum: The attribute enum. 59*9c5db199SXin Li 60*9c5db199SXin Li @return The corresponding attribute name as string, or 'ATTR_INVALID'. 61*9c5db199SXin Li """ 62*9c5db199SXin Li return cpcon.MessageAttribute.Name(attribute_enum) 63*9c5db199SXin Li 64*9c5db199SXin Li 65*9c5db199SXin Liclass CloudConsoleClient(object): 66*9c5db199SXin Li """The remote interface to the Cloud Console.""" 67*9c5db199SXin Li def send_heartbeat(self): 68*9c5db199SXin Li """Sends a heartbeat. 69*9c5db199SXin Li 70*9c5db199SXin Li @returns True if the notification is successfully sent. 71*9c5db199SXin Li Otherwise, False. 72*9c5db199SXin Li """ 73*9c5db199SXin Li pass 74*9c5db199SXin Li 75*9c5db199SXin Li def send_event(self, event_type=None, event_data=None): 76*9c5db199SXin Li """Sends an event notification to the remote console. 77*9c5db199SXin Li 78*9c5db199SXin Li @param event_type: The event type that is defined in the protobuffer 79*9c5db199SXin Li file 'cloud_console.proto'. 80*9c5db199SXin Li @param event_data: The event data. 81*9c5db199SXin Li 82*9c5db199SXin Li @returns True if the notification is successfully sent. 83*9c5db199SXin Li Otherwise, False. 84*9c5db199SXin Li """ 85*9c5db199SXin Li pass 86*9c5db199SXin Li 87*9c5db199SXin Li def send_test_job_offloaded_message(self, gcs_uri): 88*9c5db199SXin Li """Sends a test job offloaded message to the remote console. 89*9c5db199SXin Li 90*9c5db199SXin Li @param gcs_uri: The test result Google Cloud Storage URI. 91*9c5db199SXin Li 92*9c5db199SXin Li @returns True if the notification is successfully sent. 93*9c5db199SXin Li Otherwise, False. 94*9c5db199SXin Li """ 95*9c5db199SXin Li pass 96*9c5db199SXin Li 97*9c5db199SXin Li 98*9c5db199SXin Li# Make it easy to mock out 99*9c5db199SXin Lidef _create_pubsub_client(credential): 100*9c5db199SXin Li return pubsub_utils.PubSubClient(credential) 101*9c5db199SXin Li 102*9c5db199SXin Li 103*9c5db199SXin Liclass PubSubBasedClient(CloudConsoleClient): 104*9c5db199SXin Li """A Cloud PubSub based implementation of the CloudConsoleClient interface. 105*9c5db199SXin Li """ 106*9c5db199SXin Li def __init__( 107*9c5db199SXin Li self, 108*9c5db199SXin Li credential=moblab_host.MOBLAB_SERVICE_ACCOUNT_LOCATION, 109*9c5db199SXin Li pubsub_topic=_PUBSUB_TOPIC): 110*9c5db199SXin Li """Constructor. 111*9c5db199SXin Li 112*9c5db199SXin Li @param credential: The service account credential filename. Default to 113*9c5db199SXin Li '/home/moblab/.service_account.json'. 114*9c5db199SXin Li @param pubsub_topic: The cloud pubsub topic name to use. 115*9c5db199SXin Li """ 116*9c5db199SXin Li super(PubSubBasedClient, self).__init__() 117*9c5db199SXin Li self._pubsub_client = _create_pubsub_client(credential) 118*9c5db199SXin Li self._pubsub_topic = pubsub_topic 119*9c5db199SXin Li 120*9c5db199SXin Li 121*9c5db199SXin Li def _create_message(self, data, msg_attributes): 122*9c5db199SXin Li """Creates a cloud pubsub notification object. 123*9c5db199SXin Li 124*9c5db199SXin Li @param data: The message data as a string. 125*9c5db199SXin Li @param msg_attributes: The message attribute map. 126*9c5db199SXin Li 127*9c5db199SXin Li @returns: A pubsub message object with data and attributes. 128*9c5db199SXin Li """ 129*9c5db199SXin Li message = {} 130*9c5db199SXin Li if data: 131*9c5db199SXin Li message['data'] = data 132*9c5db199SXin Li if msg_attributes: 133*9c5db199SXin Li message['attributes'] = msg_attributes 134*9c5db199SXin Li return message 135*9c5db199SXin Li 136*9c5db199SXin Li def _create_message_attributes(self, message_type_enum): 137*9c5db199SXin Li """Creates a cloud pubsub notification message attribute map. 138*9c5db199SXin Li 139*9c5db199SXin Li Fills in the version, moblab mac address, and moblab id information 140*9c5db199SXin Li as attributes. 141*9c5db199SXin Li 142*9c5db199SXin Li @param message_type_enum The message type enum. 143*9c5db199SXin Li 144*9c5db199SXin Li @returns: A pubsub messsage attribute map. 145*9c5db199SXin Li """ 146*9c5db199SXin Li msg_attributes = {} 147*9c5db199SXin Li msg_attributes[_get_attribute_name(cpcon.ATTR_MESSAGE_TYPE)] = ( 148*9c5db199SXin Li _get_message_type_name(message_type_enum)) 149*9c5db199SXin Li msg_attributes[_get_attribute_name(cpcon.ATTR_MESSAGE_VERSION)] = ( 150*9c5db199SXin Li CURRENT_MESSAGE_VERSION) 151*9c5db199SXin Li msg_attributes[_get_attribute_name(cpcon.ATTR_MOBLAB_MAC_ADDRESS)] = ( 152*9c5db199SXin Li utils.get_moblab_serial_number()) 153*9c5db199SXin Li msg_attributes[_get_attribute_name(cpcon.ATTR_MOBLAB_ID)] = ( 154*9c5db199SXin Li utils.get_moblab_id()) 155*9c5db199SXin Li return msg_attributes 156*9c5db199SXin Li 157*9c5db199SXin Li def _create_test_job_offloaded_message(self, gcs_uri): 158*9c5db199SXin Li """Construct a test result notification. 159*9c5db199SXin Li 160*9c5db199SXin Li TODO(ntang): switch LEGACY to new message format. 161*9c5db199SXin Li @param gcs_uri: The test result Google Cloud Storage URI. 162*9c5db199SXin Li 163*9c5db199SXin Li @returns The notification message. 164*9c5db199SXin Li """ 165*9c5db199SXin Li data = base64.b64encode(LEGACY_TEST_OFFLOAD_MESSAGE) 166*9c5db199SXin Li msg_attributes = {} 167*9c5db199SXin Li msg_attributes[LEGACY_ATTR_VERSION] = CURRENT_MESSAGE_VERSION 168*9c5db199SXin Li msg_attributes[LEGACY_ATTR_MOBLAB_MAC] = ( 169*9c5db199SXin Li utils.get_moblab_serial_number()) 170*9c5db199SXin Li msg_attributes[LEGACY_ATTR_MOBLAB_ID] = utils.get_moblab_id() 171*9c5db199SXin Li msg_attributes[LEGACY_ATTR_GCS_URI] = gcs_uri 172*9c5db199SXin Li 173*9c5db199SXin Li return self._create_message(data, msg_attributes) 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Li def send_test_job_offloaded_message(self, gcs_uri): 177*9c5db199SXin Li """Notify the cloud console a test job is offloaded. 178*9c5db199SXin Li 179*9c5db199SXin Li @param gcs_uri: The test result Google Cloud Storage URI. 180*9c5db199SXin Li 181*9c5db199SXin Li @returns True if the notification is successfully sent. 182*9c5db199SXin Li Otherwise, False. 183*9c5db199SXin Li """ 184*9c5db199SXin Li logging.info('Notification on gcs_uri %s', gcs_uri) 185*9c5db199SXin Li message = self._create_test_job_offloaded_message(gcs_uri) 186*9c5db199SXin Li return self._publish_notification(message) 187*9c5db199SXin Li 188*9c5db199SXin Li 189*9c5db199SXin Li def _publish_notification(self, message): 190*9c5db199SXin Li msg_ids = self._pubsub_client.publish_notifications( 191*9c5db199SXin Li self._pubsub_topic, [message]) 192*9c5db199SXin Li 193*9c5db199SXin Li if msg_ids: 194*9c5db199SXin Li logging.debug('Successfully sent out a notification') 195*9c5db199SXin Li return True 196*9c5db199SXin Li logging.warning('Failed to send notification %s', str(message)) 197*9c5db199SXin Li return False 198*9c5db199SXin Li 199*9c5db199SXin Li def send_heartbeat(self): 200*9c5db199SXin Li """Sends a heartbeat. 201*9c5db199SXin Li 202*9c5db199SXin Li @returns True if the heartbeat notification is successfully sent. 203*9c5db199SXin Li Otherwise, False. 204*9c5db199SXin Li """ 205*9c5db199SXin Li logging.info('Sending a heartbeat') 206*9c5db199SXin Li 207*9c5db199SXin Li event = cpcon.Heartbeat() 208*9c5db199SXin Li # Don't sent local timestamp for now. 209*9c5db199SXin Li data = event.SerializeToString() 210*9c5db199SXin Li try: 211*9c5db199SXin Li attributes = self._create_message_attributes( 212*9c5db199SXin Li cpcon.MSG_MOBLAB_HEARTBEAT) 213*9c5db199SXin Li message = self._create_message(data, attributes) 214*9c5db199SXin Li except ValueError: 215*9c5db199SXin Li logging.exception('Failed to create message.') 216*9c5db199SXin Li return False 217*9c5db199SXin Li return self._publish_notification(message) 218*9c5db199SXin Li 219*9c5db199SXin Li def send_event(self, event_type=None, event_data=None): 220*9c5db199SXin Li """Sends an event notification to the remote console. 221*9c5db199SXin Li 222*9c5db199SXin Li @param event_type: The event type that is defined in the protobuffer 223*9c5db199SXin Li file 'cloud_console.proto'. 224*9c5db199SXin Li @param event_data: The event data. 225*9c5db199SXin Li 226*9c5db199SXin Li @returns True if the notification is successfully sent. 227*9c5db199SXin Li Otherwise, False. 228*9c5db199SXin Li """ 229*9c5db199SXin Li logging.info('Send an event.') 230*9c5db199SXin Li if not event_type: 231*9c5db199SXin Li logging.info('Failed to send event without a type.') 232*9c5db199SXin Li return False 233*9c5db199SXin Li 234*9c5db199SXin Li event = cpcon.RemoteEventMessage() 235*9c5db199SXin Li if event_data: 236*9c5db199SXin Li event.data = event_data 237*9c5db199SXin Li else: 238*9c5db199SXin Li event.data = '' 239*9c5db199SXin Li event.type = event_type 240*9c5db199SXin Li data = event.SerializeToString() 241*9c5db199SXin Li try: 242*9c5db199SXin Li attributes = self._create_message_attributes( 243*9c5db199SXin Li cpcon.MSG_MOBLAB_REMOTE_EVENT) 244*9c5db199SXin Li message = self._create_message(data, attributes) 245*9c5db199SXin Li except ValueError: 246*9c5db199SXin Li logging.exception('Failed to create message.') 247*9c5db199SXin Li return False 248*9c5db199SXin Li return self._publish_notification(message) 249