xref: /aosp_15_r20/external/autotest/site_utils/cloud_console_client.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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