Home | History | Annotate | Download | only in command_processor
      1 #
      2 # Copyright (C) 2018 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the 'License');
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an 'AS IS' BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 
     17 import httplib2
     18 import logging
     19 import os
     20 import socket
     21 import threading
     22 import time
     23 
     24 from googleapiclient import errors
     25 from google.protobuf import text_format
     26 
     27 from host_controller import common
     28 from host_controller.command_processor import base_command_processor
     29 from host_controller.console_argument_parser import ConsoleArgumentError
     30 from host_controller.tradefed import remote_operation
     31 
     32 from vti.test_serving.proto import TestLabConfigMessage_pb2 as LabCfgMsg
     33 from vti.test_serving.proto import TestScheduleConfigMessage_pb2 as SchedCfgMsg
     34 
     35 
     36 class CommandConfig(base_command_processor.BaseCommandProcessor):
     37     """Command processor for config command.
     38 
     39     Attributes:
     40         arg_parser: ConsoleArgumentParser object, argument parser.
     41         console: cmd.Cmd console object.
     42         command: string, command name which this processor will handle.
     43         command_detail: string, detailed explanation for the command.
     44         schedule_thread: dict containing threading.Thread instances(s) that
     45                          update schedule info regularly.
     46     """
     47 
     48     command = "config"
     49     command_detail = "Specifies a global config type to monitor."
     50 
     51     def UpdateConfig(self, account_id, branch, targets, config_type, method):
     52         """Updates the global configuration data.
     53 
     54         Args:
     55             account_id: string, Partner Android Build account_id to use.
     56             branch: string, branch to grab the artifact from.
     57             targets: string, a comma-separate list of build target product(s).
     58             config_type: string, config type (`prod` or `test').
     59             method: string, HTTP method for fetching.
     60         """
     61 
     62         self.console._build_provider["pab"].Authenticate()
     63         for target in targets.split(","):
     64             listed_builds = self.console._build_provider["pab"].GetBuildList(
     65                 account_id=account_id,
     66                 branch=branch,
     67                 target=target,
     68                 page_token="",
     69                 max_results=1,
     70                 method="GET")
     71 
     72             if listed_builds and len(listed_builds) > 0:
     73                 listed_build = listed_builds[0]
     74                 if listed_build["successful"]:
     75                     device_images, test_suites, artifacts, configs = self.console._build_provider[
     76                         "pab"].GetArtifact(
     77                             account_id=account_id,
     78                             branch=branch,
     79                             target=target,
     80                             artifact_name=(
     81                                 "vti-global-config-%s.zip" % config_type),
     82                             build_id=listed_build["build_id"],
     83                             method=method)
     84                     base_path = os.path.dirname(configs[config_type])
     85                     schedules_pbs = []
     86                     lab_pbs = []
     87                     for root, dirs, files in os.walk(base_path):
     88                         for config_file in files:
     89                             full_path = os.path.join(root, config_file)
     90                             try:
     91                                 if config_file.endswith(".schedule_config"):
     92                                     with open(full_path, "r") as fd:
     93                                         context = fd.read()
     94                                         sched_cfg_msg = SchedCfgMsg.ScheduleConfigMessage(
     95                                         )
     96                                         text_format.Merge(
     97                                             context, sched_cfg_msg)
     98                                         schedules_pbs.append(sched_cfg_msg)
     99                                         print sched_cfg_msg.manifest_branch
    100                                 elif config_file.endswith(".lab_config"):
    101                                     with open(full_path, "r") as fd:
    102                                         context = fd.read()
    103                                         lab_cfg_msg = LabCfgMsg.LabConfigMessage(
    104                                         )
    105                                         text_format.Merge(context, lab_cfg_msg)
    106                                         lab_pbs.append(lab_cfg_msg)
    107                             except text_format.ParseError as e:
    108                                 print("ERROR: Config parsing error %s" % e)
    109                     self.console._vti_endpoint_client.UploadScheduleInfo(
    110                         schedules_pbs)
    111                     self.console._vti_endpoint_client.UploadLabInfo(lab_pbs)
    112 
    113     def UpdateConfigLoop(self, account_id, branch, target, config_type, method,
    114                          update_interval):
    115         """Regularly updates the global configuration.
    116 
    117         Args:
    118             account_id: string, Partner Android Build account_id to use.
    119             branch: string, branch to grab the artifact from.
    120             targets: string, a comma-separate list of build target product(s).
    121             config_type: string, config type (`prod` or `test').
    122             method: string, HTTP method for fetching.
    123             update_interval: int, number of seconds before repeating
    124         """
    125         thread = threading.currentThread()
    126         while getattr(thread, 'keep_running', True):
    127             try:
    128                 self.UpdateConfig(account_id, branch, target, config_type,
    129                                   method)
    130             except (socket.error, remote_operation.RemoteOperationException,
    131                     httplib2.HttpLib2Error, errors.HttpError) as e:
    132                 logging.exception(e)
    133             time.sleep(update_interval)
    134 
    135     # @Override
    136     def SetUp(self):
    137         """Initializes the parser for config command."""
    138         self.schedule_thread = {}
    139         self.arg_parser.add_argument(
    140             "--update",
    141             choices=("single", "start", "stop", "list"),
    142             default="start",
    143             help="Update build info")
    144         self.arg_parser.add_argument(
    145             "--id",
    146             default=None,
    147             help="session ID only required for 'stop' update command")
    148         self.arg_parser.add_argument(
    149             "--interval",
    150             type=int,
    151             default=60,
    152             help="Interval (seconds) to repeat build update.")
    153         self.arg_parser.add_argument(
    154             "--config-type",
    155             choices=("prod", "test"),
    156             default="prod",
    157             help="Whether it's for prod")
    158         self.arg_parser.add_argument(
    159             "--branch",
    160             required=True,
    161             help="Branch to grab the artifact from.")
    162         self.arg_parser.add_argument(
    163             "--target",
    164             required=True,
    165             help="a comma-separate list of build target product(s).")
    166         self.arg_parser.add_argument(
    167             "--account_id",
    168             default=common._DEFAULT_ACCOUNT_ID,
    169             help="Partner Android Build account_id to use.")
    170         self.arg_parser.add_argument(
    171             '--method',
    172             default='GET',
    173             choices=('GET', 'POST'),
    174             help='Method for fetching')
    175 
    176     # @Override
    177     def Run(self, arg_line):
    178         """Updates global config."""
    179         args = self.arg_parser.ParseLine(arg_line)
    180         if args.update == "single":
    181             self.UpdateConfig(args.account_id, args.branch, args.target,
    182                               args.config_type, args.method)
    183         elif args.update == "list":
    184             print("Running config update sessions:")
    185             for id in self.schedule_thread:
    186                 print("  ID %d", id)
    187         elif args.update == "start":
    188             if args.interval <= 0:
    189                 raise ConsoleArgumentError("update interval must be positive")
    190             # do not allow user to create new
    191             # thread if one is currently running
    192             if args.id is None:
    193                 if not self.schedule_thread:
    194                     args.id = 1
    195                 else:
    196                     args.id = max(self.schedule_thread) + 1
    197             else:
    198                 args.id = int(args.id)
    199             if args.id in self.schedule_thread and not hasattr(
    200                     self.schedule_thread[args.id], 'keep_running'):
    201                 print('config update already running. '
    202                       'run config --update=stop --id=%s first.' % args.id)
    203                 return
    204             self.schedule_thread[args.id] = threading.Thread(
    205                 target=self.UpdateConfigLoop,
    206                 args=(
    207                     args.account_id,
    208                     args.branch,
    209                     args.target,
    210                     args.config_type,
    211                     args.method,
    212                     args.interval,
    213                 ))
    214             self.schedule_thread[args.id].daemon = True
    215             self.schedule_thread[args.id].start()
    216         elif args.update == "stop":
    217             if args.id is None:
    218                 print("--id must be set for stop")
    219             else:
    220                 self.schedule_thread[int(args.id)].keep_running = False
    221 
    222     def Help(self):
    223         base_command_processor.BaseCommandProcessor.Help(self)
    224         print("Sample: schedule --target=aosp_sailfish-userdebug "
    225               "--branch=git_oc-release")
    226