Example #1
0
    def set(self, field, value):
        """
        Set a field in the database

        :param str field: name of the field to set
        :param str value: value to stored; None to remove that field
        """
        if value != None:
            assert isinstance(value, basestring)
            assert len(value) < 1023
        location = os.path.join(self.location, field)
        if value == None:
            try:
                os.unlink(location)
            except OSError as e:
                if e.errno == errno.ENOENT:
                    pass
                else:
                    raise
        else:
            # New location, add a unique thing to it so there is no
            # collision if more than one process is trying to modify
            # at the same time; they can override each other, that's
            # ok--the last one wins.
            location_new = location + "-%s-%s-%s" \
                % (os.getpid(), self.uuid_seed, time.time())
            commonl.rm_f(location_new)
            os.symlink(value, location_new)
            os.rename(location_new, location)
Example #2
0
 def delete_file(self, target, _who, args, _files, user_path):
     file_path = self.arg_get(args, 'file_path', str)
     file_path_final, rw = self._validate_file_path(target, file_path,
                                                    user_path)
     if not rw:
         raise PermissionError(f"{file_path}: is a read only location")
     commonl.rm_f(file_path_final)
     return dict()
Example #3
0
    def _power_off_do(self):
        # Make sure the pidfile is removed, somehow it fails to do so
        pid = self._power_get()
        if pid != None:
            commonl.process_terminate(pid, pidfile=self.pidfile, tag="QEMU: ")

        self.fsdb.set("qemu-cmdline", None)
        commonl.rm_f(self.pidfile + ".qmp")
        console_out_fname = os.path.join(self.state_dir, "console-1.log")
        commonl.rm_f(console_out_fname)
Example #4
0
    def delete_file(self, _target, _who, args, _files, user_path):
        file_path = self.arg_get(args, 'file_path', basestring)
        if os.path.isabs(file_path):
            raise RuntimeError(
                "%s: trying to delete a file from an area that is not allowed"
                % file_path)

        file_path_final = self._validate_file_path(file_path, user_path)
        commonl.rm_f(file_path_final)
        return dict()
Example #5
0
    def _power_off_do(self):
        # Make sure the pidfile is removed, somehow it fails to do so
        pid = self._power_get()
        if pid != None:
            commonl.process_terminate(pid, pidfile = self.pidfile,
                                      tag = "QEMU: ")

        self.fsdb.set("qemu-cmdline", None)
        commonl.rm_f(self.pidfile + ".qmp")
        console_out_fname = os.path.join(self.state_dir, "console-1.log")
        commonl.rm_f(console_out_fname)
Example #6
0
 def on(self, target, component):
     # open serial port to set the baud rate, then ncat gets
     # started and it keeps the setting; default is 9600 8n1 no
     # flow control, so we explicitly set what the device needs 115200.
     with serial.Serial(self.serial_device, 115200) as f:
         self.stdin = f
         kws = dict(target.kws)
         kws['name'] = 'ncat'
         kws['component'] = component
         commonl.rm_f(
             os.path.join(target.state_dir, f"{component}-ncat.socket"))
         ttbl.power.daemon_c.on(self, target, component)
Example #7
0
File: tt_qemu.py Project: intel/tcf
    def _power_off_do_bsp(self, bsp):
        # Make sure the pidfile is removed, somehow it fails to do so
        pid = self._power_get_bsp(bsp)
        if pid != None:
            commonl.process_terminate(pid, pidfile = self.pidfile[bsp],
                                      tag = "QEMU[%s]: " % bsp)

        self.fsdb.set("qemu-cmdline-%s" % bsp, None)
        commonl.rm_f(self.pidfile[bsp] + ".qmp")
        console_out_fname = os.path.join(
            self.state_dir, "console-%s.log" % bsp)
        commonl.rm_f(console_out_fname)
Example #8
0
    def _power_off_do_bsp(self, bsp):
        # Make sure the pidfile is removed, somehow it fails to do so
        pid = self._power_get_bsp(bsp)
        if pid != None:
            commonl.process_terminate(pid,
                                      pidfile=self.pidfile[bsp],
                                      tag="QEMU[%s]: " % bsp)

        self.fsdb.set("qemu-cmdline-%s" % bsp, None)
        commonl.rm_f(self.pidfile[bsp] + ".qmp")
        console_out_fname = os.path.join(self.state_dir,
                                         "console-%s.log" % bsp)
        commonl.rm_f(console_out_fname)
Example #9
0
    def _healthcheck(self):
        # not much we can do here without knowing what the interfaces
        # can do, we can start and stop them, they might fail to start
        # since they might need the target to be powered on
        target = self.target
        testcase = target.testcase

        for i in range(10):
            name = "cert%d" % i
            with testcase.subcase(name):
                with testcase.subcase("creation"):
                    self.get(name)
                    target.report_pass("creation works")

                with testcase.subcase("check-exists"):
                    l = self.list()
                    if name in l:
                        target.report_pass(f"created '{name}' listed")
                    else:
                        target.report_fail(f"created '{name}' not listed")

                with testcase.subcase("save"):
                    self.get(name, save=True)
                    target.report_pass(f"save worked")

                with testcase.subcase("save_key"):
                    commonl.rm_f(f"{target.tmpdir}.{name}.key")
                    self.get(name, key_path=f"{target.tmpdir}.{name}.key")
                    if os.path.isfile(f"{target.tmpdir}.{name}.key"):
                        target.report_pass(f"save key worked")
                    else:
                        target.report_fail(f"save key: no file?")

                with testcase.subcase("save_cert"):
                    commonl.rm_f(f"{target.tmpdir}.{name}.cert")
                    self.get(name, cert_path=f"{target.tmpdir}.{name}.cert")
                    if os.path.isfile(f"{target.tmpdir}.{name}.cert"):
                        target.report_pass(f"save cert worked")
                    else:
                        target.report_fail(f"save cert: no file?")

                with testcase.subcase("removal"):
                    self.remove(name)
                    target.report_pass("removal works")

                with testcase.subcase("check-removed"):
                    l = self.list()
                    if name in l:
                        target.report_fail(f"removed '{name}' is still listed")
                    else:
                        target.report_pass(f"removed '{name}' not listed")
Example #10
0
    def flush(self, testcase, run_name, buffers_poll, buffers, results):
        if 'collateral' in buffers_poll:
            # write the collateral images, which basically have
            # squares drawn on the icons we were asked to look for--we
            # marked the squares in detect()--we wrote one square per
            # expectation per polled image
            collateral_img = buffers_poll['collateral']
            # so we can draw all the detections on the same screenshot
            collateral_filename = \
                testcase.report_file_prefix \
                + "%s.detected.png" % run_name
            cv2.imwrite(collateral_filename, collateral_img)
            del buffers_poll['collateral']
            del collateral_img

        if not results:
            # if we have no results about this expectation, it
            # means we missed it, so record a miss for reference

            # First generate collateral for the screenshot, if still
            # not recorded
            collateral_missed_filename = buffers_poll.get('collateral_missed',
                                                          None)
            if not collateral_missed_filename:
                collateral_missed_filename = \
                    testcase.report_file_prefix \
                    + "%s.missed.%s.png" % (run_name, self.poll_context())
                screenshots = buffers_poll.get('screenshots', [ ])
                if not screenshots:
                    self.target.report_info(
                        "%s/%s: no screenshot collateral, "
                        "since no captures where done"
                        % (run_name, self.name))
                    return
                last_screenshot = screenshots[-1]
                commonl.rm_f(collateral_missed_filename)
                shutil.copy(last_screenshot, collateral_missed_filename)
                buffers_poll['collateral_missed'] = collateral_missed_filename

            # lastly, symlink the specific missed expectation to the
            # screenshot--remember we might be sharing the screenshot
            # for many expectations
            collateral_filename = \
                testcase.report_file_prefix \
                + "%s.missed.%s.%s.png" % (
                    run_name, self.poll_context(), self.name)
            # make sure we symlink in the same directory
            commonl.rm_f(collateral_filename)
            os.symlink(os.path.basename(collateral_missed_filename),
                       collateral_filename)
Example #11
0
 def _local_preexec_fn():
     self._qemu_preexec_nw()
     # Open file descriptors for stdout/stderr to a
     # logfile, because -D is not really working. We
     # need it to check startup errors for things that
     # need a retry.
     commonl.rm_f(errfname)
     logfd = os.open(errfname,
                     # O_CREAT: Always a new file, so
                     # we can check for errors and not
                     # get confused with previous runs
                     os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644)
     os.dup2(logfd, 1)
     os.dup2(logfd, 2)
     os.close(logfd)
Example #12
0
 def _local_preexec_fn():
     self._qemu_preexec_nw()
     # Open file descriptors for stdout/stderr to a
     # logfile, because -D is not really working. We
     # need it to check startup errors for things that
     # need a retry.
     commonl.rm_f(errfname)
     logfd = os.open(errfname,
                     # O_CREAT: Always a new file, so
                     # we can check for errors and not
                     # get confused with previous runs
                     os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644)
     os.dup2(logfd, 1)
     os.dup2(logfd, 2)
     os.close(logfd)
Example #13
0
 def __init__(self, userid, fail_if_new=False, roles=None):
     path = self.create_filename(userid)
     self.userid = userid
     if not os.path.isdir(path) and fail_if_new == False:
         commonl.rm_f(path)  # cleanup, just in case
         commonl.makedirs_p(path)
     try:
         self.fsdb = ttbl.fsdb_symlink_c(path)
     except (AssertionError, ttbl.fsdb_c.exception) as e:
         if fail_if_new:
             raise self.user_not_existant_e("%s: no such user" % userid)
     self.fsdb.set('userid', userid)
     if roles:
         assert isinstance(roles, list)
         for role in roles:
             self.role_add(role)
Example #14
0
def known_user_list():
    # this now is a HACK, FIXME, repeats a lot of code, but when
    # the user database is moved to fsdb it will be cleaned up
    user_path = os.path.join(User.state_dir, "_user_*")
    l = []
    for path in glob.glob(user_path):
        try:
            # FIXME: ugly hack, fix when we have the cache
            fsdb = ttbl.fsdb_symlink_c(path)
            userid = fsdb.get('userid', None)
            if userid:
                l.append(userid)
        except Exception as e:  # FIXME: move to invalid_e
            logging.warning("cannot load user DB '%s': %s", path, e)
            # Wipe the file, it might have errors--it might be not
            # a file, so wipe hard
            commonl.rm_f(path)
    return l
Example #15
0
    def start(self, target, capturer, path):
        if self.macaddr != None:  # got a MAC address! use it
            macaddr = self.macaddr
        else:  # got to extract the MAC address
            # no interconnect given, let's take the ONLY one there should be
            interconnects = target.tags['interconnects']
            if self.ic_name == None:
                if len(interconnects) > 1:
                    raise RuntimeError(
                        f"CONFIG BUG: {target.id} declares {len(interconnects)}"
                        f" interconnects but tcpdump capturer wasn't configured "
                        f" to select which one to capture from")
                ic_name = list(interconnects.keys())[0]
                macaddr = target.tags['interconnects'][ic_name]['mac_addr']
            elif '%(' in self.ic_name:
                # we have a templated interconnect name
                # this will be expanded by generic_stream.start()
                # might be sth like interconnects.%(FIELD)s-%(FIELD2)s.mac_addr
                macaddr = f'interconnects.{self.ic_name}.mac_addr'
            else:
                # we have a fixed interconnect name
                if self.ic_name not in interconnects:
                    raise RuntimeError(
                        f"capture/{capturer}: CONFIG BUG: tcpdump_c"
                        f" configured to use interconnect "
                        f" '{self.ic_name}';"
                        f" target {target.id} is not connected to it")
                macaddr = target.tags['interconnects'][
                    self.ic_name]['mac_addr']

        # set this for generic_stream.start() -> note this will be
        # available in templates as _impl.macaddr
        self.kws['macaddr'] = macaddr
        # this matches what generic_stream.start() will do
        stream_filename = os.path.join(path, f"{capturer}{self.extension}")
        commonl.rm_f(stream_filename)
        r = generic_stream.start(self, target, capturer, path)
        commonl.verify_timeout(f"{target.id}/capture/{capturer}:tcpdump", 5,
                               os.path.isfile, stream_filename)
        return r
Example #16
0
    def tb_state_save(self, filepath):
        """Save cookies in *path* so they can be loaded by when the object is
        created.

        :param path: Filename where to save to
        :type path: str

        """
        url_safe = commonl.file_name_make_safe(self._url)
        if not os.path.isdir(filepath):
            logger.warning("%s: created state storage directory", filepath)
            os.mkdir(filepath)
        fname = filepath + "/cookies-%s.pickle" % url_safe
        if self.cookies == {}:
            logger.debug("%s: state deleted (no cookies)", self._url)
            commonl.rm_f(fname)
        else:
            with os.fdopen(os.open(fname, os.O_CREAT | os.O_WRONLY, 0o600),
                           "w") as f, \
                    self.lock:
                cPickle.dump(self.cookies, f, protocol=2)
                logger.debug("%s: state saved %s", self._url,
                             pprint.pformat(self.cookies))
Example #17
0
 def _known_hosts_wipe(self):
     # wipe known_hosts, to avoid key issues
     commonl.rm_f(self._known_hosts_path())
Example #18
0
    def _qemu_launch(self, bsp, kws):
        gdb_tcp_port = commonl.tcp_port_assigner(
            1, port_range=ttbl.config.tcp_port_range)
        self.fsdb.set("debug-%s-gdb-tcp-port" % bsp, "%s" % gdb_tcp_port)
        console_out_fname = os.path.join(self.state_dir,
                                         "console-%s.log" % bsp)
        errfname = os.path.join(self.state_dir, "%s-stderr.log" % bsp)
        try:
            # Make sure we wipe the PID file -- sometimes a pidfile is
            # left over and it seems to override it, so the reading
            # becomes corrupt
            commonl.rm_f(self.pidfile[bsp])
            qemu_cmdline = \
                (self.qemu_cmdlines[bsp] % kws).split() \
                + [
                    # Don't add -daemonize! This way this is part of
                    # the process tree and killed when we kill the
                    # parent process
                    # We use QMP to find the PTY assigned
                    "-qmp", "unix:%s.qmp,server,nowait" % self.pidfile[bsp],
                    # Always start in debug mode -- this way the
                    # whole thing is stopped until we unleash it
                    # with QMP; this allows us to first start
                    # daemons that we might need to start
                    "-S",
                    "-pidfile", self.pidfile[bsp],
                    "-gdb", "tcp:0.0.0.0:%d" % gdb_tcp_port,
                ]
            self.fsdb.set("qemu-cmdline-%s" % bsp, qemu_cmdline[0])
        except KeyError as e:
            msg = "bad QEMU command line specification: " \
                  "uninitialized key %s" % e
            self.log.error(msg)
            commonl.raise_from(RuntimeError(msg), e)
        self.log.debug("QEMU cmdline %s" % " ".join(qemu_cmdline))
        self.tags['bsps'][bsp]['cmdline'] = " ".join(qemu_cmdline)
        try:
            _preexec_fn = getattr(self, "qemu_preexec_fn", None)

            def _local_preexec_fn():
                if _preexec_fn:
                    _preexec_fn()
                    # Open file descriptors for stdout/stderr to a
                    # logfile, because -D is not really working. We
                    # need it to check startup errors for things that
                    # need a retry.
                commonl.rm_f(errfname)
                logfd = os.open(
                    errfname,
                    # O_CREAT: Always a new file, so
                    # we can check for errors and not
                    # get confused with previous runs
                    os.O_WRONLY | os.O_EXCL | os.O_CREAT,
                    0o0644)
                os.dup2(logfd, 1)
                os.dup2(logfd, 2)
                os.close(logfd)

            p = subprocess.Popen(qemu_cmdline,
                                 shell=False,
                                 cwd=self.state_dir,
                                 close_fds=True,
                                 preexec_fn=_local_preexec_fn)
            self.log.debug("QEMU %s: console @ %s" % (bsp, console_out_fname))
            # Give it a few secs to start, the pidfile has been
            # deleted before starting -- note 4 was found by
            # ad-hoc experimentation, sometimes depending on system load it
            # takes more or less.
            timeout = 10
            ts0 = time.time()
            while True:
                if time.time() - ts0 > timeout:
                    lines = []
                    with open(errfname) as f:
                        count = 0
                        for line in f:
                            lines.append("log: " + line)
                            if count > 5:
                                lines.append("log: ...")
                                break
                    raise RuntimeError("QEMU %s: did not start after %.0fs\n"
                                       "%s" % (bsp, timeout, "\n".join(lines)))
                try:
                    if self._qmp_running(bsp):
                        # FIXME: race condition
                        ttbl.daemon_pid_add(p.pid)
                        return True
                except RuntimeError as e:
                    self.log.warning("QEMU %s: can't read QMP: %s" %
                                     (bsp, str(e)))
                    # fall through, let it retry
                # Check errors during startup
                with open(errfname, "r") as logf:
                    causes_for_retry = [
                        # bah, race condition: since we chose the
                        # port we wanted to use and until we
                        # started using it someone took it. Retry
                        'Failed to bind socket: Address already in use',
                    ]
                    for line in logf:
                        for cause in causes_for_retry:
                            if cause in line:
                                self.log.info(
                                    "QEMU %s: retrying because found in "
                                    "logfile: %s", bsp, cause)
                                return False
                time.sleep(0.25)
                # nothing runs after this, either it returns or raises
        except (OSError, ValueError) as e:
            self.log.debug("QEMU %s: launch failure: %s", bsp, e)
            raise
Example #19
0
    def on(self, target, component):
        stderrf_name = os.path.join(target.state_dir,
                                    component + "-" + self.name + ".stderr")

        kws = dict(target.kws)
        kws.update(self.kws)
        kws['component'] = component
        # render the real commandline against kws
        _cmdline = []
        for i in self.cmdline:
            # some older Linux distros complain if this string is unicode
            _cmdline.append(str(i % kws))

        target.log.info("%s: command line: %s" %
                        (component, " ".join(_cmdline)))
        if self.env_add:
            env = dict(os.environ)
            env.update(self.env_add)
        else:
            env = os.environ
        pidfile = self.pidfile % kws
        commonl.rm_f(pidfile)
        stderrf = open(stderrf_name, "w+")
        try:
            p = subprocess.Popen(_cmdline,
                                 env=env,
                                 cwd=target.state_dir,
                                 stderr=stderrf,
                                 bufsize=0,
                                 shell=False,
                                 universal_newlines=False)
            if self.mkpidfile:
                with open(pidfile, "w+") as pidf:
                    pidf.write("%s" % p.pid)
        except TypeError as e:
            # This happens on misconfiguration
            ## TypeError: execve() arg 3 contains a non-string value
            if 'execve() arg 3' in str(e):
                target.log.exception(
                    "Ensure environment settings are not set to None", e)
            if 'execve()' in str(e):
                target.log.exception("Possible target misconfiguration: %s", e)
                count = 0
                for i in _cmdline:
                    target.log.error("cmdline %d: [%s] %s", count,
                                     type(i).__name__, i)
                    count += 1
                for key, val in env.iteritems():
                    target.log.error("env %s: [%s] %s", key,
                                     type(val).__name__, val)
            raise
        except OSError as e:
            raise self.start_e("%s: %s failed to start: %s" %
                               (component, self.name, e))

        del stderrf  # we don't care for this file here
        if self.precheck_wait:
            time.sleep(self.precheck_wait)
        pid = commonl.process_started(pidfile, self.path,
                                      component + "-" + self.name, target.log,
                                      self.verify, (_cmdline, ))
        if pid == None:
            raise self.start_e("%s: %s failed to start" %
                               (component, self.name))
        ttbl.daemon_pid_add(pid)
Example #20
0
    def _report(self, level, alevel, ulevel, _tc, tag, message, attachments):
        """
        Report data to log files for a possible failure report later

        We don't even check the levels, we log everything here by
        INFO > 2.

        We report to the file ``TAG LEVEL CODE MESSAGE`` which we'll
        parse later to generate the report.
        """
        # This is what marks all the testcase runs being done, so we
        # can use it to wrap things up.
        if _tc == tcfl.tc.tc_global:
            if message.startswith("COMPLETION"):
                self._finalize(_tc)
            return
        # Okie, this is a hack -- this means this is a testcase, but
        # we need something better.
        if getattr(_tc, "skip_reports", False) == True:
            return  # We don't operate on the global reporter fake TC
        # Note we open the file for every thing we report -- we can be
        # running *A LOT* of stuff in parallel and run out of file
        # descriptors.
        if tag == "INFO" and level > 2:
            return
        code = self._get_code()
        if not code in self.fs:
            f = codecs.open(os.path.join(_tc.tmpdir,
                                         "report-" + code + ".txt"),
                            "w",
                            encoding='utf-8',
                            errors='ignore')
            self.fs[code] = f.name
        else:
            f = codecs.open(self.fs[code],
                            "a+b",
                            encoding='utf-8',
                            errors='ignore')

        # Extract the target name where this message came from (if the
        # reporter is a target)
        if isinstance(_tc, tcfl.tc.target_c):
            tgname = " @" + _tc.fullid + _tc.bsp_suffix()
        else:
            tgname = " @local"
        with contextlib.closing(f):
            # Remove the ticket from the ident string, as it will be
            # the same for all and makes no sense to have it.
            ident = tcfl.msgid_c.ident()
            if ident.startswith(_tc._ident):
                ident = ident[len(_tc._ident):]
            if ident == "":
                # If empty, give it a to snip token that we'll replace
                # later in mkreport
                ident = "<snip>"
            _prefix = "%s %d %s%s\t" % (tag, level, ident, tgname)
            self._write(f, u"%s %s\n" % (_prefix, message))
            if attachments != None:
                assert isinstance(attachments, dict)
                for key, attachment in attachments.iteritems():
                    self._report_f_attachment(f, _prefix, key, attachment)
            f.flush()
            # FIXME: this is an unsmokable mess and needs to be fixed.
            # will defer cleaning up until we move the whole reporting
            # to be done with a Jinja2 template so the report paths
            # are fully split.
            if self.junit and message.startswith("COMPLETION") \
               or message.startswith("COMPLETION failed") \
               or message.startswith("COMPLETION error") \
               or message.startswith("COMPLETION blocked") \
               or message.startswith("COMPLETION skipped") \
               or (message.startswith("COMPLETION passed")
                   and (
                       _tc.tag_get('report_always', (False, ))[0] == True
                       or self.text_report_pass
                   )):
                self._mkreport(tag, code, _tc, message, f)
                # Wipe the file, it might have errors--it might be not
                # a file, so wipe hard
                commonl.rm_f(self.fs[code])
                del self.fs[code]
Example #21
0
File: tt_qemu.py Project: intel/tcf
    def _qemu_launch(self, bsp, kws):
        gdb_tcp_port = commonl.tcp_port_assigner(
            1, port_range = ttbl.config.tcp_port_range)
        self.fsdb.set("debug-%s-gdb-tcp-port" % bsp, "%s" % gdb_tcp_port)
        console_out_fname = os.path.join(
            self.state_dir, "console-%s.log" % bsp)
        errfname = os.path.join(
            self.state_dir, "%s-stderr.log" % bsp)
        try:
            # Make sure we wipe the PID file -- sometimes a pidfile is
            # left over and it seems to override it, so the reading
            # becomes corrupt
            commonl.rm_f(self.pidfile[bsp])
            qemu_cmdline = \
                (self.qemu_cmdlines[bsp] % kws).split() \
                + [
                    # Don't add -daemonize! This way this is part of
                    # the process tree and killed when we kill the
                    # parent process
                    # We use QMP to find the PTY assigned
                    "-qmp", "unix:%s.qmp,server,nowait" % self.pidfile[bsp],
                    # Always start in debug mode -- this way the
                    # whole thing is stopped until we unleash it
                    # with QMP; this allows us to first start
                    # daemons that we might need to start
                    "-S",
                    "-pidfile", self.pidfile[bsp],
                    "-gdb", "tcp:0.0.0.0:%d" % gdb_tcp_port,
                ]
            self.fsdb.set("qemu-cmdline-%s" % bsp, qemu_cmdline[0])
        except KeyError as e:
            msg = "bad QEMU command line specification: " \
                  "uninitialized key %s" % e
            self.log.error(msg)
            commonl.raise_from(RuntimeError(msg), e)
        self.log.debug("QEMU cmdline %s" % " ".join(qemu_cmdline))
        self.tags['bsps'][bsp]['cmdline'] = " ".join(qemu_cmdline)
        try:
            _preexec_fn = getattr(self, "qemu_preexec_fn", None)
            def _local_preexec_fn():
                if _preexec_fn:
                    _preexec_fn()
                    # Open file descriptors for stdout/stderr to a
                    # logfile, because -D is not really working. We
                    # need it to check startup errors for things that
                    # need a retry.
                commonl.rm_f(errfname)
                logfd = os.open(errfname,
                                # O_CREAT: Always a new file, so
                                # we can check for errors and not
                                # get confused with previous runs
                                os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644)
                os.dup2(logfd, 1)
                os.dup2(logfd, 2)
                os.close(logfd)

            p = subprocess.Popen(qemu_cmdline,
                                 shell = False, cwd = self.state_dir,
                                 close_fds = True,
                                 preexec_fn = _local_preexec_fn)
            self.log.debug("QEMU %s: console @ %s" % (bsp, console_out_fname))
            # Give it a few secs to start, the pidfile has been
            # deleted before starting -- note 4 was found by
            # ad-hoc experimentation, sometimes depending on system load it
            # takes more or less.
            timeout = 10
            ts0 = time.time()
            while True:
                if time.time() - ts0 > timeout:
                    lines = []
                    with open(errfname) as f:
                        count = 0
                        for line in f:
                            lines.append("log: " + line)
                            if count > 5:
                                lines.append("log: ...")
                                break
                    raise RuntimeError("QEMU %s: did not start after %.0fs\n"
                                       "%s" % (bsp, timeout, "\n".join(lines)))
                try:
                    if self._qmp_running(bsp):
                        # FIXME: race condition
                        ttbl.daemon_pid_add(p.pid)
                        return True
                except RuntimeError as e:
                    self.log.warning("QEMU %s: can't read QMP: %s"
                                     % (bsp, str(e)))
                    # fall through, let it retry
                # Check errors during startup
                with open(errfname, "r") as logf:
                    causes_for_retry = [
                        # bah, race condition: since we chose the
                        # port we wanted to use and until we
                        # started using it someone took it. Retry
                        'Failed to bind socket: Address already in use',
                    ]
                    for line in logf:
                        for cause in causes_for_retry:
                            if cause in line:
                                self.log.info(
                                    "QEMU %s: retrying because found in "
                                    "logfile: %s", bsp, cause)
                                return False
                time.sleep(0.25)
                # nothing runs after this, either it returns or raises
        except (OSError, ValueError) as e:
            self.log.debug("QEMU %s: launch failure: %s", bsp, e)
            raise
Example #22
0
    def on(self, target, _component):
        # Bring up the lower network interface; lower is called
        # whatever (if it is a physical device) or _bNAME; bring it
        # up, make it promiscuous
        mode = self._get_mode(target)
        if mode == 'vlan':
            # our lower is a physical device, our upper is a device
            # which till tag for eth vlan %(vlan)
            ifname = commonl.if_find_by_mac(target.tags['mac_addr'],
                                            physical=True)
            commonl.if_remove_maybe("b%(id)s" % target.kws)
            kws = dict(target.kws)
            kws['ifname'] = ifname
            subprocess.check_call(
                "/usr/sbin/ip link add"
                " link %(ifname)s name b%(id)s"
                " type vlan id %(vlan)s"
                #" protocol VLAN_PROTO"
                #" reorder_hdr on|off"
                #" gvrp on|off mvrp on|off loose_binding on|off"
                % kws,
                shell=True)
            subprocess.check_call(  # bring lower up
                "/usr/sbin/ip link set dev %s up promisc on" % ifname,
                shell=True)
        elif mode == 'physical':
            ifname = commonl.if_find_by_mac(target.tags['mac_addr'])
            subprocess.check_call(  # bring lower up
                "/usr/sbin/ip link set dev %s up promisc on" % ifname,
                shell=True)
            self._if_rename(target)
        elif mode == 'virtual':
            # We do not have a physical device, a bridge, to serve as
            # lower
            commonl.if_remove_maybe("_b%(id)s" % target.kws)
            subprocess.check_call("/usr/sbin/ip link add"
                                  "  name _b%(id)s"
                                  "  type bridge" % target.kws,
                                  shell=True)
            subprocess.check_call("/usr/sbin/ip link add"
                                  "  link _b%(id)s name b%(id)s"
                                  "  type macvlan mode bridge; " % target.kws,
                                  shell=True)
            subprocess.check_call(  # bring lower up
                "/usr/sbin/ip link set"
                "  dev _b%(id)s"
                "  up promisc on" % target.kws,
                shell=True)
        else:
            raise AssertionError("Unknown mode %s" % mode)

        # Configure the IP addresses for the top interface
        subprocess.check_call(  # clean up existing address
            "/usr/sbin/ip add flush dev b%(id)s " % target.kws,
            shell=True)
        subprocess.check_call(  # add IPv6
            # if this fails, check Network Manager hasn't disabled ipv6
            # sysctl -a | grep disable_ipv6 must show all to 0
            "/usr/sbin/ip addr add"
            "  %(ipv6_addr)s/%(ipv6_prefix_len)s dev b%(id)s " % target.kws,
            shell=True)
        subprocess.check_call(  # add IPv4
            "/usr/sbin/ip addr add"
            "  %(ipv4_addr)s/%(ipv4_prefix_len)d"
            "  dev b%(id)s" % target.kws,
            shell=True)

        # Bring up the top interface, which sets up ther outing
        subprocess.check_call(
            "/usr/sbin/ip link set dev b%(id)s up promisc on" % target.kws,
            shell=True)

        target.fsdb.set('power_state', 'on')

        # Start tcpdump on the network?
        #
        # The value of the tcpdump property, if not None, is the
        # filename we'll capture to.
        tcpdump = target.fsdb.get('tcpdump')
        if tcpdump:
            assert not os.path.sep in tcpdump \
                and tcpdump != "" \
                and tcpdump != os.path.pardir \
                and tcpdump != os.path.curdir, \
                "Bad filename for TCP dump capture '%s' specified as " \
                " value to property *tcpdump*: must not include" % tcpdump
            # per ttbd:make_ticket(), colon splits the real username
            # from the ticket
            owner = target.owner_get().split(":")[0]
            assert owner, "BUG? target not owned on power on?"
            capfile = os.path.join(target.files_path, owner, tcpdump)
            # Because it is in the user's area,
            # we assume the user knows what he is doing to overwrite it,
            # so we'll remove any first
            commonl.rm_f(capfile)
            pidfile = os.path.join(target.state_dir, "tcpdump.pid")
            logfile = os.path.join(target.state_dir, "tcpdump.log")
            cmdline = [
                "/usr/sbin/tcpdump", "-U", "-i",
                "_b%(id)s" % target.kws, "-w", capfile
            ]
            try:
                logf = open(logfile, "a")
                target.log.info("Starting tcpdump with: %s", " ".join(cmdline))
                p = subprocess.Popen(cmdline,
                                     shell=False,
                                     cwd=target.state_dir,
                                     close_fds=True,
                                     stdout=logf,
                                     stderr=subprocess.STDOUT)
            except OSError as e:
                raise RuntimeError("tcpdump failed to start: %s" % e)
            ttbl.daemon_pid_add(p.pid)  # FIXME: race condition if it died?
            with open(pidfile, "w") as pidfilef:
                pidfilef.write("%d" % p.pid)

            pid = commonl.process_started(  # Verify it started
                pidfile,
                "/usr/sbin/tcpdump",
                verification_f=os.path.exists,
                verification_f_args=(capfile, ),
                timeout=20,
                tag="tcpdump",
                log=target.log)
            if pid == None:
                raise RuntimeError("tcpdump failed to start after 5s")
Example #23
0
    def on(self, target, _component):
        ic = target  # Note the rename (target -> ic)

        # Create records for each target that we know will connect to
        # this interconnect, place them in the directory TARGET/dnsmasq.hosts
        dirname = os.path.join(ic.state_dir, "dnsmasq.hosts")
        shutil.rmtree(dirname, ignore_errors=True)
        commonl.makedirs_p(dirname)
        tftp_dirname = os.path.join(ic.state_dir, "tftp.root")
        shutil.rmtree(tftp_dirname, ignore_errors=True)
        commonl.makedirs_p(tftp_dirname, 0o0775)
        ttbl.pxe.setup_tftp_root(tftp_dirname)  # creates the dir
        commonl.rm_f(os.path.join(ic.state_dir, "dnsmasq.log"))

        # Find the targets that connect to this interconnect and
        # collect their IPv4/6/MAC addresses to create the record and
        # DHCP info; in theory we wouldn't need to create the host
        # info, as the DHCP host info would do it--doesn't hurt
        # FIXME: parallelize for many
        dhcp_hosts = collections.defaultdict(dict)
        for target in ttbl.config.targets.values():
            interconnects = target.tags.get('interconnects', {})
            # iterate interconnects this thing connects to
            for interconnect_id, interconnect in interconnects.items():
                if interconnect_id != ic.id:
                    continue
                addrs = []
                mac_addr = interconnect.get('mac_addr', None)
                if mac_addr:
                    dhcp_hosts[target]['mac_addr'] = mac_addr
                ipv4_addr = interconnect.get('ipv4_addr', None)
                if ipv4_addr:
                    dhcp_hosts[target]['ipv4_addr'] = ipv4_addr
                    addrs.append(ipv4_addr)
                ipv6_addr = interconnect.get('ipv6_addr', None)
                if ipv6_addr:
                    dhcp_hosts[target]['ipv6_addr'] = ipv6_addr
                    addrs.append(ipv6_addr)
                if addrs:
                    # Create a file for each target that will connect to
                    # this interconnect
                    with open(os.path.join(dirname, target.id), "w+") as f:
                        for addr in addrs:
                            f.write("%s\t%s %s.%s\n" %
                                    (addr, target.id, target.id, ic.id))
        # Create a configuration file
        #
        # configl has all the options with template values which we
        # expand later.
        with open(os.path.join(ic.state_dir, "dnsmasq.conf"), "w+") as f:

            configl = [
                "no-hosts",  # only files in...
                "hostsdir=%(path)s/dnsmasq.hosts",  # ..this dir
                # we are defining a domain .NETWORKNAME
                "domain=%(id)s",
                "local=/%(id)s/",
                # serve only on the in the interface for this network;
                # listen-address not needed since we specify
                # interface--having a hard time making listen-address
                # only work anyway
                # FIXME: hardcoded to knowing the network interface
                #        name is called bTARGET
                "interface=b%(id)s",
                # need to use this so we only bind to our
                # interface and we can run multiple dnsmasqa and coexists
                # with whichever are in the system
                "bind-interfaces",
                "except-interface=lo",
                # if a plain name (w/o domain name) is not found in the
                # local database, do not forward it upstream
                "domain-needed",
                # Needs an A record "%(ipv4_addr)s %(id)s", created in on()
                # DISABLED: unknown why, this messes up resolution of
                # plain names
                # auth-server=%(id)s,b%(id)s",
                "auth-zone=%(id)s,b%(id)s",
                "dhcp-authoritative",
                # Enable TFTP server to STATEDIR/tftp.root
                "enable-tftp",
                "tftp-root=%(path)s/tftp.root",
                # all files TFTP is to send have to be owned by the
                # user running it (the same one running this daemon)
                "tftp-secure",
                # logging -- can be accessed with a console, see class doc
                "log-dhcp",
                "log-facility=%(path)s/dnsmasq.log",
            ]

            # Add stuff based on having ipv4/6 support
            #
            # dhcp-range activates the DHCP server
            # host-record creates a record for the host that
            # represents the domain zone; but not sure it is working
            # all right.
            addrs = []
            ic_ipv4_addr = ic.kws.get('ipv4_addr', None)
            if ic_ipv4_addr:
                addrs.append(ic_ipv4_addr)
                # IPv4 server address so we can do auth-server
                configl.append("host-record=%(id)s,%(ipv4_addr)s")
                # we let DNSMASQ figure out the range from the
                # configuration of the network interface and we only
                # allow (static) the ones set below with dhcp-host
                configl.append("dhcp-range=%(ipv4_addr)s,static")

            ic_ipv6_addr = ic.kws.get('ipv6_addr', None)
            if ic_ipv6_addr:
                addrs.append(ic_ipv6_addr)
                # IPv6 server address so we can do auth-server
                configl.append("host-record=%(id)s,[%(ipv6_addr)s]")
                # FIXME: while this is working, it is still not giving
                # the IPv6 address we hardcoded in the doc :/
                ipv6_prefix_len = ic.kws['ipv6_prefix_len']
                network = ipaddress.IPv6Network(str(ic_ipv6_addr + "/" +
                                                    str(ipv6_prefix_len)),
                                                strict=False)
                configl.append(
                    "dhcp-range=%s,%s,%s" %
                    (ic_ipv6_addr, network.broadcast_address, ipv6_prefix_len))

            # Create A record for the server/ domain
            # this is a separat file in DIRNAME/dnsmasq.hosts/NAME
            if addrs:
                configl.append("listen-address=" + ",".join(addrs))
                with open(os.path.join(dirname, ic.id), "w+") as hf:
                    for addr in addrs:
                        hf.write("%s\t%s\n" % (addr, ic.id))

            for config in configl:
                f.write(config % ic.kws + "\n")

            # For each target we know can connect, create a dhcp-host entry
            for target, data in dhcp_hosts.items():
                infol = [
                    # we set a tag after the host name to match a
                    # host-specific dhcp-option line to it
                    "set:" + target.id,
                    data['mac_addr']
                ]
                if 'ipv4_addr' in data:
                    infol.append(data['ipv4_addr'])
                if 'ipv6_addr' in data:
                    # IPv6 addr in [ADDR] format, per man page
                    infol.append("[" + data['ipv6_addr'] + "]")
                infol.append(target.id)
                infol.append("infinite")
                f.write("dhcp-host=" + ",".join(infol) + "\n")
                # next fields can be in the target or fall back to the
                # values from the interconnect
                kws = target.kws
                bsps = target.tags.get('bsps', {}).keys()
                if bsps:
                    # take the first BSP in sort order...yeah, not a
                    # good plan
                    bsp = sorted(bsps)[0]
                    kws['bsp'] = bsp
                ttbl.pxe.tag_get_from_ic_target(kws, 'pos_http_url_prefix', ic,
                                                target)
                ttbl.pxe.tag_get_from_ic_target(kws, 'pos_nfs_server', ic,
                                                target)
                ttbl.pxe.tag_get_from_ic_target(kws, 'pos_nfs_path', ic,
                                                target)

                # FIXME: this is very confusing here, since it is what
                # ttbl.pxe.pos_cmdline_opts is relaying on in a way
                # and we'd need a way to make it machine specific too;
                # as well, in some places like for pos_mode==pxe this
                # is all set in the server sides, while the client in
                # tcfl.pos has a lot of it in the client side; we need
                # a unified source.

                f.write(
                    "dhcp-option=tag:%(id)s,option:root-path,%(pos_nfs_server)s:%(pos_nfs_path)s,soft,nfsvers=4\n"
                    % kws)

                # If the target declares a BSP (at this point of the
                # game, it should), figure out which architecture is
                # so we can point it to the right file.
                if bsp:
                    # try ARCH or efi-ARCH
                    # override with anything the target declares in config
                    arch = None
                    boot_filename = None
                    if 'pos_tftp_boot_filename' in target.tags:
                        boot_filename = target.tags['pos_tftp_boot_filename']
                    elif bsp in ttbl.pxe.architectures:
                        arch = ttbl.pxe.architectures[bsp]
                        arch_name = bsp
                        boot_filename = arch_name + "/" + arch.get(
                            'boot_filename', None)
                    elif "efi-" + bsp in ttbl.pxe.architectures:
                        arch_name = "efi-" + bsp
                        arch = ttbl.pxe.architectures[arch_name]
                        boot_filename = arch_name + "/" + arch.get(
                            'boot_filename', None)
                    if boot_filename:
                        f.write("dhcp-option=tag:%(id)s," % kws +
                                "option:bootfile-name," + boot_filename + "\n")
                    if ic_ipv4_addr:
                        f.write("dhcp-option=tag:%(id)s," % kws +
                                "option:tftp-server," + ic_ipv4_addr + "\n")
                    if ic_ipv6_addr:
                        f.write("dhcp-option=tag:%(id)s," % kws +
                                "option:tftp-server," + ic_ipv4_addr + "\n")
                    else:
                        raise RuntimeError(
                            "%s: TFTP/PXE boot mode selected, but no boot"
                            " filename can be guessed for arch/BSP %s/%s;"
                            " declare tag pos_tftp_boot_filename?" %
                            (target.id, arch_name, bsp))

        # note the rename we did target -> ic
        ttbl.power.daemon_c.on(self, ic, _component)
Example #24
0
File: report.py Project: intel/tcf
    def _report(self, level, alevel, ulevel, _tc, tag, message, attachments):
        """
        Report data to log files for a possible failure report later

        We don't even check the levels, we log everything here by
        INFO <= 4.

        We report to the file ``TAG LEVEL CODE MESSAGE`` which we'll
        parse later to generate the report.
        """
        # This is what marks all the testcase runs being done, so we
        # can use it to wrap things up.
        if _tc == tcfl.tc.tc_global:
            return
        # Okie, this is a hack -- this means this is a testcase, but
        # we need something better.
        if getattr(_tc, "skip_reports", False) == True:
            return	# We don't operate on the global reporter fake TC
        # Note we open the file for every thing we report -- we can be
        # running *A LOT* of stuff in parallel and run out of file
        # descriptors.
        if tag == "INFO" and level > 4:
            return
        code = self._get_code()
        if not code in self.fs:
            f = codecs.open(
                os.path.join(_tc.tmpdir, "report-" + code + ".txt"),
                "w", encoding = 'utf-8', errors = 'ignore')
            self.fs[code] = f.name
        else:
            f = codecs.open(self.fs[code], "a+b",
                            encoding = 'utf-8', errors = 'ignore')

        # Extract the target name where this message came from (if the
        # reporter is a target)
        if isinstance(_tc, tcfl.tc.target_c):
            tgname = " @" + _tc.fullid + _tc.bsp_suffix()
        else:
            tgname = " @local"
        with contextlib.closing(f):
            # Remove the ticket from the ident string, as it will be
            # the same for all and makes no sense to have it.
            ident = tcfl.msgid_c.ident()
            if ident.startswith(_tc._ident):
                ident = ident[len(_tc._ident):]
            if ident == "":
                # If empty, give it a to snip token that we'll replace
                # later in mkreport
                ident = "<snip>"
            _prefix = "%s %d %s%s\t" % (tag, level, ident, tgname)
            self._write(f, u"%s %s\n" % (_prefix, message))
            if attachments != None:
                assert isinstance(attachments, dict)
                for key, attachment in attachments.iteritems():
                    self._report_f_attachment(f, _prefix, key, attachment)
            f.flush()
            # This is an indication that the testcase is done and we
            # can generate final reports
            if message.startswith("COMPLETION "):
                self._mkreport(tag, code, _tc, message)
                # Wipe the file, it might have errors--it might be not
                # a file, so wipe hard
                commonl.rm_f(self.fs[code])
                del self.fs[code]
Example #25
0
 def delete_file(self, _target, _who, args, _files, user_path):
     file_path = self._arg_get(args, 'file_path')
     file_path_final = self._validate_file_path(file_path, user_path)
     commonl.rm_f(file_path_final)
     return dict()
Example #26
0
    def _report(self, level, alevel, ulevel, _tc, tag, message, attachments):
        """
        Report data to log files for a possible failure report later

        We don't even check the levels, we log everything here by
        INFO <= 4.

        We report to the file ``TAG LEVEL CODE MESSAGE`` which we'll
        parse later to generate the report.
        """
        # This is what marks all the testcase runs being done, so we
        # can use it to wrap things up.
        if _tc == tcfl.tc.tc_global:
            return
        # Okie, this is a hack -- this means this is a testcase, but
        # we need something better.
        if getattr(_tc, "skip_reports", False) == True:
            return  # We don't operate on the global reporter fake TC
        # Note we open the file for every thing we report -- we can be
        # running *A LOT* of stuff in parallel and run out of file
        # descriptors.
        if tag == "INFO" and level > 4:
            return
        code = self._get_code()
        if not code in self.fs:
            f = codecs.open(os.path.join(_tc.tmpdir,
                                         "report-" + code + ".txt"),
                            "w",
                            encoding='utf-8',
                            errors='replace')
            self.fs[code] = f.name
        else:
            f = codecs.open(self.fs[code],
                            "a+b",
                            encoding='utf-8',
                            errors='replace')

        # Extract the target name where this message came from (if the
        # reporter is a target)
        if isinstance(_tc, tcfl.tc.target_c):
            tgname = " @" + _tc.fullid + _tc.bsp_suffix()
        else:
            tgname = " @local"
        with contextlib.closing(f):
            # Remove the ticket from the ident string, as it will be
            # the same for all and makes no sense to have it.
            ident = self.ident_simplify(tcfl.msgid_c.ident(),
                                        _tc.kws.get('runid', ''),
                                        _tc.kws.get('tc_hash', ""))
            if ident == "":
                # If empty, give it a to snip token that we'll replace
                # later in mkreport
                ident = "<snip>"
            _prefix = "%s %d %s%s\t" % (tag, level, ident, tgname)
            self._write(f, u"%s %s\n" % (_prefix, _mkutf8(message)))
            if attachments != None:
                assert isinstance(attachments, dict)
                for key, attachment in attachments.iteritems():
                    self._report_f_attachment(f, _prefix, key, attachment)
            f.flush()
            # This is an indication that the testcase is done and we
            # can generate final reports
            if message.startswith("COMPLETION "):
                self._mkreport(tag, code, _tc, message)
                # Wipe the file, it might have errors--it might be not
                # a file, so wipe hard
                commonl.rm_f(self.fs[code])
                del self.fs[code]
Example #27
0
 def _client_wipe(self, name, cert_client_path):
     # wipe without complaining if not there
     for extension in self.client_extensions:
         commonl.rm_f(os.path.join(cert_client_path,
                                   name + "." + extension))
Example #28
0
    def _qemu_console_on(self, target, component):
        # Run steps that are needed when we power on WRT to the
        # consoles

        console_interface = getattr(target, "console", None)
        if console_interface == None:  # no console interface? skip
            return

        # set the generation for the consoles, so clients now the
        # output is new
        consolel = list(target.console.impls.keys())
        for console in consolel:
            ttbl.console.generation_set(target, console)

        #
        # Find out which PTS nodes have been allocated by QEMU
        #
        # If we have declared any consoles using PTYs (preferred
        # method), a PTS pair has been allocated by the kernel and now
        # we need to find which one is it, so the console can write to
        # it. The generic console implementation expects a file in the
        # target state directory called console-NAME.write where to
        # write to.
        #
        # We'll symlink console-NAME.write -> /dev/pts/XYZ
        #
        # This function uses QMP to query QEMU for all the chardevs,
        # then goes over each finding the ones that are using
        # PTYs. Those that match a declared console implementation in
        # the target.console interface will be symlinked.
        #

        with qmp_c(os.path.join(target.state_dir, "qemu.qmp")) as qmp:
            # This will return a list such as:
            #
            ## [
            ##     {"frontend-open": true, "filename": "gdb", "label": "#chr034"},
            ##     {"frontend-open": true, "filename": "vc", "label": "parallel0"},
            ##     {"frontend-open": true, "filename": "disconnected:tcp:0.0.0.0:62949,server", "label": "gdb"},
            ##     {"frontend-open": true, "filename": "unix:.../qemu.qmp,server", "label": "compat_monitor0"}
            ##     {"frontend-open": true, "filename": "pty:/dev/pts/5", "label": "ttyS0"},
            ## ]
            r = qmp.command("query-chardev")
            for d in r:
                # Each entry is a dictionary with a bunch of fields,
                # for which we are interested in the *label* and
                # *filename* fields.
                #
                ## {
                ##     "frontend-open": true,
                ##     "filename": "pty:/dev/pts/5",
                ##     "label": "ttyS0"
                ## }
                #
                # if no label or filename, log an error--malformed; if
                # no pty or not declared as a console for ttbd, log an
                # info, we just don't care about it.
                label = d.get('label', None)
                if label == None:
                    target.log.error(
                        "QEMU:%s/console: ignoring entry missing label: %s",
                        component, json.dumps(d, skipkeys=True))
                    continue
                filename = d.get('filename', None)
                if filename == None:
                    target.log.error(
                        "QEMU:%s/console: ignoring entry '%s': missing filename",
                        component, label)
                    continue
                if not filename.startswith("pty:"):
                    target.log.info(
                        "QEMU:%s/console: ignoring entry '%s': no pty (%s)",
                        component, label, filename)
                    continue
                _, pts_name = filename.split(":", 1)
                console_impl = console_interface.impls.get(label, None)
                if console_impl == None:
                    target.log.debug(
                        "QEMU:%s/console: ignoring entry '%s': no console declared for it",
                        component, label)
                    continue
                write_filename = os.path.join(target.state_dir,
                                              "console-" + label + ".write")
                target.log.info("QEMU/%s/console: '%s' uses PTS %s", component,
                                label, pts_name)
                commonl.rm_f(write_filename)
                os.symlink(pts_name, write_filename)