def spawn(self): """Start a microVM as a daemon or in a screen session.""" # pylint: disable=subprocess-run-check self._jailer.setup() self._api_socket = self._jailer.api_socket_path() self._api_session = Session() self.actions = Actions(self._api_socket, self._api_session) self.boot = BootSource(self._api_socket, self._api_session) self.drive = Drive(self._api_socket, self._api_session) self.logger = Logger(self._api_socket, self._api_session) self.machine_cfg = MachineConfigure( self._api_socket, self._api_session ) self.metrics = Metrics(self._api_socket, self._api_session) self.mmds = MMDS(self._api_socket, self._api_session) self.network = Network(self._api_socket, self._api_session) self.vsock = Vsock(self._api_socket, self._api_session) jailer_param_list = self._jailer.construct_param_list() # When the daemonize flag is on, we want to clone-exec into the # jailer rather than executing it via spawning a shell. Going # forward, we'll probably switch to this method for running # Firecracker in general, because it represents the way it's meant # to be run by customers (together with CLONE_NEWPID flag). # # We have to use an external tool for CLONE_NEWPID, because # 1) Python doesn't provide a os.clone() interface, and # 2) Python's ctypes libc interface appears to be broken, causing # our clone / exec to deadlock at some point. if self._jailer.daemonize: if self.bin_cloner_path: cmd = [self.bin_cloner_path] + \ [self._jailer_binary_path] + \ jailer_param_list _p = run(cmd, stdout=PIPE, stderr=PIPE, check=True) # Terrible hack to make the tests fail when starting the # jailer fails with a panic. This is needed because we can't # get the exit code of the jailer. In newpid_clone.c we are # not waiting for the process and we always return 0 if the # clone was successful (which in most cases will be) and we # don't do anything if the jailer was not started # successfully. if _p.stderr.decode().strip(): raise Exception(_p.stderr.decode()) self.jailer_clone_pid = int(_p.stdout.decode().rstrip()) else: # This code path is not used at the moment, but I just feel # it's nice to have a fallback mechanism in place, in case # we decide to offload PID namespacing to the jailer. _pid = os.fork() if _pid == 0: os.execv( self._jailer_binary_path, [self._jailer_binary_path] + jailer_param_list ) self.jailer_clone_pid = _pid else: # Delete old screen log if any. try: os.unlink(self.SCREEN_LOGFILE) except OSError: pass # Log screen output to SCREEN_LOGFILE # This file will collect any output from 'screen'ed Firecracker. start_cmd = 'screen -L -Logfile {logfile} '\ '-dmS {session} {binary} {params}' start_cmd = start_cmd.format( logfile=self.SCREEN_LOGFILE, session=self._session_name, binary=self._jailer_binary_path, params=' '.join(jailer_param_list) ) run(start_cmd, shell=True, check=True) # Build a regex object to match (number).session_name regex_object = re.compile( r'([0-9]+)\.{}'.format(self._session_name)) # Run 'screen -ls' in a retry_call loop, 30 times with a one # second delay between calls. # If the output of 'screen -ls' matches the regex object, it will # return the PID. Otherwise a RuntimeError will be raised. screen_pid = retry_call( utils.search_output_from_cmd, fkwargs={ "cmd": 'screen -ls', "find_regex": regex_object }, exceptions=RuntimeError, tries=30, delay=1).group(1) self.jailer_clone_pid = open('/proc/{0}/task/{0}/children' .format(screen_pid) ).read().strip() # Configure screen to flush stdout to file. flush_cmd = 'screen -S {session} -X colon "logfile flush 0^M"' run(flush_cmd.format(session=self._session_name), shell=True, check=True) # Wait for the jailer to create resources needed, and Firecracker to # create its API socket. # We expect the jailer to start within 80 ms. However, we wait for # 1 sec since we are rechecking the existence of the socket 5 times # and leave 0.2 delay between them. if 'no-api' not in self._jailer.extra_args: self._wait_create()
def spawn(self, create_logger=True, log_file='log_fifo', log_level='Info'): """Start a microVM as a daemon or in a screen session.""" # pylint: disable=subprocess-run-check self._jailer.setup() self._api_socket = self._jailer.api_socket_path() self._api_session = Session() self.actions = Actions(self._api_socket, self._api_session) self.balloon = Balloon(self._api_socket, self._api_session) self.boot = BootSource(self._api_socket, self._api_session) self.desc_inst = DescribeInstance(self._api_socket, self._api_session) self.drive = Drive(self._api_socket, self._api_session) self.logger = Logger(self._api_socket, self._api_session) self.machine_cfg = MachineConfigure( self._api_socket, self._api_session ) self.metrics = Metrics(self._api_socket, self._api_session) self.mmds = MMDS(self._api_socket, self._api_session) self.network = Network(self._api_socket, self._api_session) self.vm = Vm(self._api_socket, self._api_session) self.vsock = Vsock(self._api_socket, self._api_session) self.init_snapshot_api() if create_logger: log_fifo_path = os.path.join(self.path, log_file) log_fifo = log_tools.Fifo(log_fifo_path) self.create_jailed_resource(log_fifo.path, create_jail=True) # The default value for `level`, when configuring the # logger via cmd line, is `Warning`. We set the level # to `Info` to also have the boot time printed in fifo. self.jailer.extra_args.update({'log-path': log_file, 'level': log_level}) self.start_console_logger(log_fifo) jailer_param_list = self._jailer.construct_param_list() # When the daemonize flag is on, we want to clone-exec into the # jailer rather than executing it via spawning a shell. Going # forward, we'll probably switch to this method for running # Firecracker in general, because it represents the way it's meant # to be run by customers (together with CLONE_NEWPID flag). # # We have to use an external tool for CLONE_NEWPID, because # 1) Python doesn't provide a os.clone() interface, and # 2) Python's ctypes libc interface appears to be broken, causing # our clone / exec to deadlock at some point. if self._jailer.daemonize: self.daemonize_jailer(jailer_param_list) else: # Delete old screen log if any. try: os.unlink(self.SCREEN_LOGFILE) except OSError: pass # Log screen output to SCREEN_LOGFILE # This file will collect any output from 'screen'ed Firecracker. start_cmd = 'screen -L -Logfile {logfile} '\ '-dmS {session} {binary} {params}'.format( logfile=self.SCREEN_LOGFILE, session=self._session_name, binary=self._jailer_binary_path, params=' '.join(jailer_param_list)) utils.run_cmd(start_cmd) # Build a regex object to match (number).session_name regex_object = re.compile( r'([0-9]+)\.{}'.format(self._session_name)) # Run 'screen -ls' in a retry_call loop, 30 times with a one # second delay between calls. # If the output of 'screen -ls' matches the regex object, it will # return the PID. Otherwise a RuntimeError will be raised. screen_pid = retry_call( utils.search_output_from_cmd, fkwargs={ "cmd": 'screen -ls', "find_regex": regex_object }, exceptions=RuntimeError, tries=30, delay=1).group(1) self.jailer_clone_pid = int(open('/proc/{0}/task/{0}/children' .format(screen_pid) ).read().strip()) # Configure screen to flush stdout to file. flush_cmd = 'screen -S {session} -X colon "logfile flush 0^M"' utils.run_cmd(flush_cmd.format(session=self._session_name)) # Wait for the jailer to create resources needed, and Firecracker to # create its API socket. # We expect the jailer to start within 80 ms. However, we wait for # 1 sec since we are rechecking the existence of the socket 5 times # and leave 0.2 delay between them. if 'no-api' not in self._jailer.extra_args: self._wait_create() if create_logger: self.check_log_message("Running Firecracker")
def spawn( self, create_logger=True, log_file="log_fifo", log_level="Info", use_ramdisk=False, ): """Start a microVM as a daemon or in a screen session.""" # pylint: disable=subprocess-run-check self._jailer.setup(use_ramdisk=use_ramdisk) self._api_socket = self._jailer.api_socket_path() self._api_session = Session() self.actions = Actions(self._api_socket, self._api_session) self.balloon = Balloon(self._api_socket, self._api_session) self.boot = BootSource(self._api_socket, self._api_session) self.desc_inst = DescribeInstance(self._api_socket, self._api_session) self.full_cfg = FullConfig(self._api_socket, self._api_session) self.logger = Logger(self._api_socket, self._api_session) self.version = InstanceVersion(self._api_socket, self._fc_binary_path, self._api_session) self.machine_cfg = MachineConfigure(self._api_socket, self._api_session, self.firecracker_version) self.metrics = Metrics(self._api_socket, self._api_session) self.mmds = MMDS(self._api_socket, self._api_session) self.network = Network(self._api_socket, self._api_session) self.snapshot = SnapshotHelper(self._api_socket, self._api_session) self.drive = Drive(self._api_socket, self._api_session, self.firecracker_version) self.vm = Vm(self._api_socket, self._api_session) self.vsock = Vsock(self._api_socket, self._api_session) if create_logger: log_fifo_path = os.path.join(self.path, log_file) log_fifo = log_tools.Fifo(log_fifo_path) self.create_jailed_resource(log_fifo.path, create_jail=True) # The default value for `level`, when configuring the # logger via cmd line, is `Warning`. We set the level # to `Info` to also have the boot time printed in fifo. self.jailer.extra_args.update({ "log-path": log_file, "level": log_level }) self.start_console_logger(log_fifo) if self.metadata_file: if os.path.exists(self.metadata_file): LOG.debug("metadata file exists, adding as a jailed resource") self.create_jailed_resource(self.metadata_file, create_jail=True) self.jailer.extra_args.update( {"metadata": os.path.basename(self.metadata_file)}) jailer_param_list = self._jailer.construct_param_list() # When the daemonize flag is on, we want to clone-exec into the # jailer rather than executing it via spawning a shell. Going # forward, we'll probably switch to this method for running # Firecracker in general, because it represents the way it's meant # to be run by customers (together with CLONE_NEWPID flag). # # We have to use an external tool for CLONE_NEWPID, because # 1) Python doesn't provide os.clone() interface, and # 2) Python's ctypes libc interface appears to be broken, causing # our clone / exec to deadlock at some point. if self._jailer.daemonize: self.daemonize_jailer(jailer_param_list) else: # This file will collect any output from 'screen'ed Firecracker. self._screen_log = self.SCREEN_LOGFILE.format(self._session_name) screen_pid, binary_pid = utils.start_screen_process( self._screen_log, self._session_name, self._jailer_binary_path, jailer_param_list, ) self._screen_pid = screen_pid self.jailer_clone_pid = binary_pid # Wait for the jailer to create resources needed, and Firecracker to # create its API socket. # We expect the jailer to start within 80 ms. However, we wait for # 1 sec since we are rechecking the existence of the socket 5 times # and leave 0.2 delay between them. if "no-api" not in self._jailer.extra_args: self._wait_create() if create_logger: self.check_log_message("Running Firecracker")