# Lint as: python2, python3 # Copyright 2022 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import absolute_import from __future__ import division from __future__ import print_function import json import logging import os import requests import six from autotest_lib.client.common_lib import autotemp from autotest_lib.client.common_lib import error from autotest_lib.client.cros.update_engine import nebraska_wrapper class NebraskaService: """ Remotely sets up nebraska on the DUT. This service is different from `autotest_lib.client.cros.update_engine.nebraska_wrapper.NebraskaWrapper` in that it is used by server-only tests to remotely launch nebraska on the DUT. """ def __init__(self, test, host, payload_url=None, **props_to_override): """ Initializes the NebraskaService. @param test: Instance of the test using the service. @param host: The DUT we will be running on. @param payload_url: The payload that will be returned in responses for update requests. This can be a single URL string or a list of URLs to return multiple payload URLs (such as a platform payload + DLC payloads) in the responses. @param props_to_override: Dictionary of key/values to use in responses instead of the default values in payload_url's properties file. """ self._host = host self._test = test # _update_metadata_dir is the directory for storing the json metadata # files associated with the payloads. # _update_payloads_address is the address of the update server where # the payloads are staged. self._update_metadata_dir = None self._update_payloads_address = None if payload_url: # Normalize payload_url to be a list. if not isinstance(payload_url, list): payload_url = [payload_url] self._update_metadata_dir = self._host.get_tmp_dir() self._update_payloads_address = ''.join( payload_url[0].rpartition('/')[0:2]) # Download the metadata files and save them in a tempdir for general # use. for url in payload_url: self.get_payload_properties_file(url, self._update_metadata_dir, **props_to_override) def get_payload_properties_file(self, payload_url, target_dir, **kwargs): """ Downloads the payload properties file into a directory on the DUT. @param payload_url: The URL to the update payload file. @param target_dir: The directory on the DUT to download the file into. @param kwargs: A dictionary of key/values that needs to be overridden on the payload properties file. """ payload_props_url = payload_url + '.json' _, _, file_name = payload_props_url.rpartition('/') try: response = json.loads(requests.get(payload_props_url).text) # Override existing keys if any. for k, v in six.iteritems(kwargs): # Don't set default None values. We don't want to override good # values to None. if v is not None: response[k] = v self._write_remote_file(os.path.join(target_dir, file_name), json.dumps(response)) except (requests.exceptions.RequestException, IOError, ValueError) as err: raise error.TestError( 'Failed to get update payload properties: %s with error: %s' % (payload_props_url, err)) def start(self, **kwargs): """Launch nebraska on DUT.""" # Generate nebraska configuration. self._write_remote_file( nebraska_wrapper.NEBRASKA_CONFIG, json.dumps(self._create_startup_config(**kwargs)), ) logging.info('Start nebraska service') self._host.upstart_restart('nebraska') self._host.wait_for_service('nebraska') def stop(self): """Stop Nebraska service.""" logging.info('Stop nebraska service') self._host.upstart_stop('nebraska') self._host.run('rm', args=('-f', nebraska_wrapper.NEBRASKA_CONFIG)) def _create_startup_config(self, **kwargs): """ Creates a nebraska startup config file. If this file is present, nebraska can be started by upstart. @param kwargs: A dictionary of key/values for nebraska config options. See platform/dev/nebraska/nebraska.py for more info. @return: A dictionary of nebraska config options. """ conf = {} if self._update_metadata_dir: conf['update_metadata'] = self._update_metadata_dir if self._update_payloads_address: conf['update_payloads_address'] = self._update_payloads_address for k, v in six.iteritems(kwargs): conf[k] = v return conf def _create_remote_dir(self, remote_dir, owner=None): """ Create directory on DUT. @param remote_dir: The directory to create. @param owner: Set owner of the remote directory. """ permission = '1777' if owner: permission = '1770' self._host.run(['mkdir', '-p', '-m', permission, remote_dir]) if owner: self._host.run('chown', args=(owner, remote_dir)) def _write_remote_file(self, filepath, content, permission=None, owner=None): """ Write content to filepath on DUT. @param permission: set permission to 0xxx octal number of remote file. @param owner: set owner of remote file. """ tmpdir = autotemp.tempdir(unique_id='minios') tmp_path = os.path.join(tmpdir.name, os.path.basename(filepath)) with open(tmp_path, 'w') as f: f.write(content) if permission is not None: os.chmod(tmp_path, permission) self._create_remote_dir(os.path.dirname(filepath), owner) self._host.send_file(tmp_path, filepath, delete_dest=True) if owner is not None: self._host.run('chown', args=(owner, filepath)) tmpdir.clean()