Pin-Yen Lin | 3f96b75 | 2020-04-28 15:48:49 +0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright 2020 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Multicast service to spawn uftp server""" |
| 7 | |
Pin-Yen Lin | 576bbca | 2020-06-02 15:45:45 +0800 | [diff] [blame] | 8 | import argparse |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 9 | import logging |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 10 | import os |
Pin-Yen Lin | a996ae9 | 2020-06-01 14:53:42 +0800 | [diff] [blame] | 11 | import time |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 12 | |
| 13 | from cros.factory.utils import json_utils |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 14 | from cros.factory.utils import process_utils |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 15 | |
| 16 | |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 17 | MCAST_CONFIG_NAME = 'multicast_config.json' |
| 18 | UMPIRE_CONFIG_NAME = 'active_umpire.json' |
| 19 | UMPIRE_DIR = '/var/db/factory/umpire' |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 20 | UFTP_PATH = '/usr/bin/uftp' |
| 21 | |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 22 | |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 23 | class UftpProcess: |
| 24 | CC_TYPE = 'tfmcc' # TCP friendly multicast congestion control. |
| 25 | LOG_LEVEL = '0' |
| 26 | ROBUST_FACTOR = '50' # The number of announcements sent before file transfer. |
| 27 | TTL = '10' |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 28 | |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 29 | def __init__(self, file_path, multicast_addr, status_file_path, interface): |
| 30 | """Constructor of a UFTP process wrapper. |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 31 | |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 32 | Args: |
| 33 | file_path: The file path to be transferred. |
| 34 | multicast_addr: The multicast address for sending announcement messages. |
| 35 | status_file_path: The path for logging file transfer status. |
| 36 | interface: The network interface name or IP to send the data from. |
| 37 | """ |
| 38 | self.file_path = file_path |
| 39 | self.multicast_addr = multicast_addr |
| 40 | self.status_file_path = status_file_path |
| 41 | self.interface = interface |
| 42 | |
| 43 | self.Spawn() |
| 44 | |
| 45 | def Spawn(self): |
| 46 | addr, port = self.multicast_addr.split(':') |
| 47 | |
| 48 | # "-u" defines the the UDP source port, while "-p" defines the UDP |
| 49 | # destination port. |
| 50 | cmd = [ |
| 51 | UFTP_PATH, '-M', addr, '-t', self.TTL, '-u', port, '-p', port, '-x', |
| 52 | self.LOG_LEVEL, '-S', self.status_file_path, '-C', self.CC_TYPE, '-s', |
| 53 | self.ROBUST_FACTOR |
| 54 | ] |
| 55 | |
| 56 | if self.interface != '': |
| 57 | cmd += ['-I', self.interface] |
| 58 | |
| 59 | cmd += [self.file_path] |
| 60 | |
| 61 | self.process = process_utils.Spawn(cmd, stderr=process_utils.PIPE) |
| 62 | |
| 63 | def RespawnIfDied(self): |
| 64 | ANNOUNCE_TIMED_OUT_RETCODE = 7 |
| 65 | if self.process is not None and self.process.poll() is not None: |
| 66 | # Skip announce timed out message. |
| 67 | if self.process.returncode != ANNOUNCE_TIMED_OUT_RETCODE: |
| 68 | logging.error(self.process.stderr.read()) |
| 69 | self.Spawn() |
| 70 | |
| 71 | def Kill(self): |
| 72 | self.process.kill() |
| 73 | self.process.wait() |
| 74 | |
| 75 | |
| 76 | def IsUmpireEnabled(project): |
| 77 | """Return True if corresponding Umpire container is running.""" |
| 78 | container_name = 'umpire_%s' % project |
| 79 | |
| 80 | container_list = process_utils.CheckOutput( |
| 81 | ['docker', 'ps', '--all', '--format', '{{.Names}}'], |
| 82 | encoding='utf-8').splitlines() |
| 83 | return container_name in container_list |
| 84 | |
| 85 | |
| 86 | def ScanUftpArgsFromConfig(mcast_config, resource_dir, log_dir): |
| 87 | """Scan an Umpire config to extract UFTP arguments for the project. |
| 88 | |
| 89 | Args: |
| 90 | mcast_config: The multicast config file generated by multicast service. |
| 91 | The content of multicast config is similar to Umpire payload |
| 92 | config file, but has an additional "multicast" key for |
| 93 | multicast information. |
| 94 | resource_dir: The Umpire resource directory. |
| 95 | log_dir: The log directory for UFTP server. |
| 96 | """ |
| 97 | scanned_args = [] |
| 98 | |
| 99 | mcast_addr = mcast_config['multicast'] |
| 100 | uftp_interface = mcast_config['multicast'].get('server_ip', '') |
| 101 | |
| 102 | for component in mcast_addr: |
| 103 | if component == 'server_ip': |
| 104 | continue |
| 105 | for part in mcast_addr[component]: |
| 106 | file_name = mcast_config[component][part] |
| 107 | |
| 108 | uftp_file_path = os.path.join(resource_dir, file_name) |
| 109 | uftp_mcast_addr = mcast_addr[component][part] |
| 110 | uftp_status_file_path = os.path.join(log_dir, 'uftp_%s.log' % file_name) |
| 111 | |
| 112 | scanned_args += [(uftp_file_path, uftp_mcast_addr, uftp_status_file_path, |
| 113 | uftp_interface)] |
| 114 | |
| 115 | return scanned_args |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 116 | |
Pin-Yen Lin | 3f96b75 | 2020-04-28 15:48:49 +0800 | [diff] [blame] | 117 | |
| 118 | def Main(): |
Pin-Yen Lin | 576bbca | 2020-06-02 15:45:45 +0800 | [diff] [blame] | 119 | parser = argparse.ArgumentParser() |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 120 | parser.add_argument('-l', '--log-dir', help='path to Umpire log directory', |
| 121 | required=True) |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 122 | |
Pin-Yen Lin | 576bbca | 2020-06-02 15:45:45 +0800 | [diff] [blame] | 123 | args = parser.parse_args() |
| 124 | |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 125 | active_process = {} |
| 126 | active_config = {} |
Pin-Yen Lin | a996ae9 | 2020-06-01 14:53:42 +0800 | [diff] [blame] | 127 | |
| 128 | while True: |
Pin-Yen Lin | 5887511 | 2021-01-15 16:51:59 +0800 | [diff] [blame] | 129 | scanned_config = {} |
| 130 | scanned_args = {} |
| 131 | |
| 132 | for project in os.listdir(UMPIRE_DIR): |
| 133 | project_path = os.path.join(UMPIRE_DIR, project) |
| 134 | try: |
| 135 | umpire_config = json_utils.LoadFile( |
| 136 | os.path.join(project_path, UMPIRE_CONFIG_NAME)) |
| 137 | mcast_enabled = umpire_config['services']['multicast']['active'] |
| 138 | mcast_config = json_utils.LoadFile( |
| 139 | os.path.join(project_path, MCAST_CONFIG_NAME)) |
| 140 | except Exception: |
| 141 | continue |
| 142 | |
| 143 | if not mcast_enabled or not IsUmpireEnabled(project): |
| 144 | continue |
| 145 | |
| 146 | resource_dir = os.path.join(project_path, 'resources') |
| 147 | |
| 148 | scanned_config[project] = mcast_config |
| 149 | scanned_args[project] = ScanUftpArgsFromConfig(mcast_config, resource_dir, |
| 150 | args.log_dir) |
| 151 | |
| 152 | for project in scanned_config: |
| 153 | if scanned_config[project] != active_config.get(project): |
| 154 | active_config[project] = scanned_config[project] |
| 155 | for proc in active_process.get(project, []): |
| 156 | proc.Kill() |
| 157 | |
| 158 | active_process[project] = [] |
| 159 | for uftp_args in scanned_args[project]: |
| 160 | active_process[project] += [UftpProcess(*uftp_args)] |
| 161 | else: |
| 162 | for proc in active_process[project]: |
| 163 | proc.RespawnIfDied() |
| 164 | |
| 165 | for project in set(active_config) - set(scanned_config): |
| 166 | active_config.pop(project) |
| 167 | for proc in active_process[project]: |
| 168 | proc.Kill() |
| 169 | active_process.pop(project) |
| 170 | |
Pin-Yen Lin | a996ae9 | 2020-06-01 14:53:42 +0800 | [diff] [blame] | 171 | time.sleep(1) |
Pin-Yen Lin | 57d659f | 2020-04-28 15:59:28 +0800 | [diff] [blame] | 172 | |
Pin-Yen Lin | 3f96b75 | 2020-04-28 15:48:49 +0800 | [diff] [blame] | 173 | |
| 174 | if __name__ == '__main__': |
| 175 | Main() |