1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2022 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li# 6*9c5db199SXin Li# Expects to be run in an environment with sudo and no interactive password 7*9c5db199SXin Li# prompt, such as within the Chromium OS development chroot. 8*9c5db199SXin Li"""Host class for GSC devboard connected host.""" 9*9c5db199SXin Li 10*9c5db199SXin Liimport contextlib 11*9c5db199SXin Liimport logging 12*9c5db199SXin Litry: 13*9c5db199SXin Li import docker 14*9c5db199SXin Liexcept ImportError: 15*9c5db199SXin Li logging.info("Docker API is not installed in this environment") 16*9c5db199SXin Li 17*9c5db199SXin LiDOCKER_IMAGE = "gcr.io/satlab-images/gsc_dev_board:release" 18*9c5db199SXin Li 19*9c5db199SXin LiSATLAB_DOCKER_HOST = 'tcp://192.168.231.1:2375' 20*9c5db199SXin LiLOCAL_DOCKER_HOST = 'tcp://127.0.0.1:2375' 21*9c5db199SXin LiDEFAULT_DOCKER_HOST = 'unix:///var/run/docker.sock' 22*9c5db199SXin Li 23*9c5db199SXin LiDEFAULT_SERVICE_PORT = 39999 24*9c5db199SXin Li 25*9c5db199SXin LiULTRADEBUG = '18d1:0304' 26*9c5db199SXin Li 27*9c5db199SXin Li 28*9c5db199SXin Liclass GSCDevboardHost(object): 29*9c5db199SXin Li """ 30*9c5db199SXin Li A host that is physically connected to a GSC devboard. 31*9c5db199SXin Li 32*9c5db199SXin Li It could either be a SDK workstation (chroot) or a SatLab box. 33*9c5db199SXin Li """ 34*9c5db199SXin Li 35*9c5db199SXin Li def _initialize(self, 36*9c5db199SXin Li hostname, 37*9c5db199SXin Li service_debugger_serial=None, 38*9c5db199SXin Li service_ip=None, 39*9c5db199SXin Li service_port=DEFAULT_SERVICE_PORT, 40*9c5db199SXin Li *args, 41*9c5db199SXin Li **dargs): 42*9c5db199SXin Li """Construct a GSCDevboardHost object. 43*9c5db199SXin Li 44*9c5db199SXin Li @hostname: Name of the devboard host, will be used in future to look up 45*9c5db199SXin Li the debugger serial, not currently used. 46*9c5db199SXin Li @service_debugger_serial: debugger connected to devboard, defaults to 47*9c5db199SXin Li the first one found on the container. 48*9c5db199SXin Li @service_ip: devboard service ip, default is to start a new container. 49*9c5db199SXin Li @service_port: devboard service port, defaults to 39999. 50*9c5db199SXin Li """ 51*9c5db199SXin Li 52*9c5db199SXin Li # Use docker host from environment or by probing a list of candidates. 53*9c5db199SXin Li self._client = None 54*9c5db199SXin Li try: 55*9c5db199SXin Li self._client = docker.from_env() 56*9c5db199SXin Li logging.info("Created docker host from env") 57*9c5db199SXin Li except NameError: 58*9c5db199SXin Li raise NameError('Please install docker using ' 59*9c5db199SXin Li '"autotest/files/utils/install_docker_chroot.sh"') 60*9c5db199SXin Li except docker.errors.DockerException: 61*9c5db199SXin Li docker_host = None 62*9c5db199SXin Li candidate_hosts = [ 63*9c5db199SXin Li SATLAB_DOCKER_HOST, DEFAULT_DOCKER_HOST, LOCAL_DOCKER_HOST 64*9c5db199SXin Li ] 65*9c5db199SXin Li for h in candidate_hosts: 66*9c5db199SXin Li try: 67*9c5db199SXin Li c = docker.DockerClient(base_url=h, timeout=2) 68*9c5db199SXin Li c.close() 69*9c5db199SXin Li docker_host = h 70*9c5db199SXin Li break 71*9c5db199SXin Li except docker.errors.DockerException: 72*9c5db199SXin Li pass 73*9c5db199SXin Li if docker_host is not None: 74*9c5db199SXin Li self._client = docker.DockerClient(base_url=docker_host, 75*9c5db199SXin Li timeout=300) 76*9c5db199SXin Li else: 77*9c5db199SXin Li raise ValueError('Invalid DOCKER_HOST, ensure dockerd is' 78*9c5db199SXin Li ' running.') 79*9c5db199SXin Li logging.info("Using docker host at %s", docker_host) 80*9c5db199SXin Li 81*9c5db199SXin Li self._satlab = False 82*9c5db199SXin Li # GSCDevboardHost should only be created on Satlab or localhost, so 83*9c5db199SXin Li # assume Satlab if a drone container is running. 84*9c5db199SXin Li if len(self._client.containers.list(filters={'name': 'drone'})) > 0: 85*9c5db199SXin Li logging.info("In Satlab") 86*9c5db199SXin Li self._satlab = True 87*9c5db199SXin Li 88*9c5db199SXin Li self._service_debugger_serial = service_debugger_serial 89*9c5db199SXin Li self._service_ip = service_ip 90*9c5db199SXin Li self._service_port = service_port 91*9c5db199SXin Li logging.info("Using service port %s", self._service_port) 92*9c5db199SXin Li 93*9c5db199SXin Li self._docker_network = 'default_satlab' if self._satlab else 'host' 94*9c5db199SXin Li self._docker_container = None 95*9c5db199SXin Li 96*9c5db199SXin Li serials = self._list_debugger_serials() 97*9c5db199SXin Li if len(serials) == 0: 98*9c5db199SXin Li raise ValueError('No debuggers found') 99*9c5db199SXin Li logging.info("Available debuggers: [%s]", ', '.join(serials)) 100*9c5db199SXin Li 101*9c5db199SXin Li if self._service_debugger_serial is None: 102*9c5db199SXin Li self._service_debugger_serial = serials[0] 103*9c5db199SXin Li else: 104*9c5db199SXin Li if self._service_debugger_serial not in serials: 105*9c5db199SXin Li raise ValueError( 106*9c5db199SXin Li '%s debugger not found in [%s]' % 107*9c5db199SXin Li (self._service_debugger_serial, ', '.join(serials))) 108*9c5db199SXin Li logging.info("Using debugger %s", self._service_debugger_serial) 109*9c5db199SXin Li self._docker_container_name = "gsc_dev_board_{}".format( 110*9c5db199SXin Li self._service_debugger_serial) 111*9c5db199SXin Li 112*9c5db199SXin Li def _list_debugger_serials(self): 113*9c5db199SXin Li """List all attached debuggers.""" 114*9c5db199SXin Li 115*9c5db199SXin Li c = self._client.containers.run(DOCKER_IMAGE, 116*9c5db199SXin Li remove=True, 117*9c5db199SXin Li privileged=True, 118*9c5db199SXin Li name='list_debugger_serial', 119*9c5db199SXin Li hostname='list_debugger_serial', 120*9c5db199SXin Li detach=True, 121*9c5db199SXin Li volumes=["/dev:/hostdev"], 122*9c5db199SXin Li command=['sleep', '5']) 123*9c5db199SXin Li 124*9c5db199SXin Li res, output = c.exec_run(['lsusb', '-v', '-d', ULTRADEBUG], 125*9c5db199SXin Li stderr=False, 126*9c5db199SXin Li privileged=True) 127*9c5db199SXin Li c.kill() 128*9c5db199SXin Li if res != 0: 129*9c5db199SXin Li return [] 130*9c5db199SXin Li output = output.decode("utf-8").split('\n') 131*9c5db199SXin Li serials = [ 132*9c5db199SXin Li l.strip().split(' ')[-1] for l in output 133*9c5db199SXin Li if l.strip()[:7] == 'iSerial' 134*9c5db199SXin Li ] 135*9c5db199SXin Li return serials 136*9c5db199SXin Li 137*9c5db199SXin Li @contextlib.contextmanager 138*9c5db199SXin Li def service_context(self): 139*9c5db199SXin Li """Service context manager that provides the service endpoint.""" 140*9c5db199SXin Li self.start_service() 141*9c5db199SXin Li try: 142*9c5db199SXin Li yield "{}:{}".format(self.service_ip, self.service_port) 143*9c5db199SXin Li finally: 144*9c5db199SXin Li self.stop_service() 145*9c5db199SXin Li 146*9c5db199SXin Li def start_service(self): 147*9c5db199SXin Li """Starts service if needed.""" 148*9c5db199SXin Li if self._docker_container is not None: 149*9c5db199SXin Li return 150*9c5db199SXin Li 151*9c5db199SXin Li if self._service_ip: 152*9c5db199SXin Li # Assume container was manually started if service_ip was set 153*9c5db199SXin Li logging.info("Skip start_service due to set service_ip") 154*9c5db199SXin Li return 155*9c5db199SXin Li 156*9c5db199SXin Li #TODO(b/215767105): Pull image onto Satlab box if not present. 157*9c5db199SXin Li 158*9c5db199SXin Li environment = { 159*9c5db199SXin Li 'DEVBOARDSVC_PORT': self._service_port, 160*9c5db199SXin Li 'DEBUGGER_SERIAL': self._service_debugger_serial 161*9c5db199SXin Li } 162*9c5db199SXin Li start_cmd = ['/opt/gscdevboard/start_devboardsvc.sh'] 163*9c5db199SXin Li 164*9c5db199SXin Li # Stop any leftover containers 165*9c5db199SXin Li try: 166*9c5db199SXin Li c = self._client.containers.get(self._docker_container_name) 167*9c5db199SXin Li c.kill() 168*9c5db199SXin Li except docker.errors.NotFound: 169*9c5db199SXin Li pass 170*9c5db199SXin Li 171*9c5db199SXin Li self._client.containers.run(DOCKER_IMAGE, 172*9c5db199SXin Li remove=True, 173*9c5db199SXin Li privileged=True, 174*9c5db199SXin Li name=self._docker_container_name, 175*9c5db199SXin Li hostname=self._docker_container_name, 176*9c5db199SXin Li network=self._docker_network, 177*9c5db199SXin Li cap_add=["NET_ADMIN"], 178*9c5db199SXin Li detach=True, 179*9c5db199SXin Li volumes=["/dev:/hostdev"], 180*9c5db199SXin Li environment=environment, 181*9c5db199SXin Li command=start_cmd) 182*9c5db199SXin Li 183*9c5db199SXin Li # A separate containers.get call is needed to capture network attributes 184*9c5db199SXin Li self._docker_container = self._client.containers.get( 185*9c5db199SXin Li self._docker_container_name) 186*9c5db199SXin Li 187*9c5db199SXin Li def stop_service(self): 188*9c5db199SXin Li """Stops service by killing the container.""" 189*9c5db199SXin Li if self._docker_container is None: 190*9c5db199SXin Li return 191*9c5db199SXin Li self._docker_container.kill() 192*9c5db199SXin Li self._docker_container = None 193*9c5db199SXin Li 194*9c5db199SXin Li @property 195*9c5db199SXin Li def service_port(self): 196*9c5db199SXin Li """Return service port (local to the container host).""" 197*9c5db199SXin Li return self._service_port 198*9c5db199SXin Li 199*9c5db199SXin Li @property 200*9c5db199SXin Li def service_ip(self): 201*9c5db199SXin Li """Return service ip (local to the container host).""" 202*9c5db199SXin Li if self._service_ip is not None: 203*9c5db199SXin Li return self._service_ip 204*9c5db199SXin Li 205*9c5db199SXin Li if self._docker_network == 'host': 206*9c5db199SXin Li return '127.0.0.1' 207*9c5db199SXin Li else: 208*9c5db199SXin Li if self._docker_container is None: 209*9c5db199SXin Li return '' 210*9c5db199SXin Li else: 211*9c5db199SXin Li settings = self._docker_container.attrs['NetworkSettings'] 212*9c5db199SXin Li return settings['Networks'][self._docker_network]['IPAddress'] 213