예제 #1
0
    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()
예제 #2
0
    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")
예제 #3
0
    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")