Example #1
0
class ProxyInfo(Plugin):

    # HTTP proxy to use instead of the one specified in environment.
    http_proxy = String(required=False)

    # HTTPS proxy to use instead of the one specified in environment.
    https_proxy = String(required=False)

    def register(self, manager):
        super(ProxyInfo, self).register(manager)

        self.proxy = {}

        self._manager.reactor.call_on("report-gconf", self.report_gconf)
        self._manager.reactor.call_on("gather", self.gather, 100)

    def report_gconf(self, resources):
        proxy_pattern = re.compile(r"/system/(http_)?proxy/(?P<name>[^/]+)$")
        for resource in resources:
            match = proxy_pattern.match(resource["name"])
            if match:
                self.proxy[match.group("name")] = resource["value"]

    def gather(self):
        # Config has lowest precedence
        http_proxy = self.http_proxy
        https_proxy = self.https_proxy

        # Gconf has higher precedence
        proxy = self.proxy
        if proxy.get("use_http_proxy", False):
            if proxy.get("use_authentication", False):
                http_proxy = "http://%s:%s@%s:%s" % (
                    proxy["authentication_user"],
                    proxy["authentication_password"], proxy["host"],
                    proxy["port"])
            elif "host" in proxy:
                http_proxy = "http://%s:%s" % (proxy["host"], proxy["port"])

            if proxy.get("use_same_proxy", False):
                https_proxy = http_proxy
            elif "secure_host" in proxy:
                https_proxy = "https://%s:%s" % (proxy["secure_host"],
                                                 proxy["secure_port"])

        # Environment has highest precedence
        http_proxy = get_variable("http_proxy", http_proxy)
        https_proxy = get_variable("https_proxy", https_proxy)

        add_variable("http_proxy", http_proxy)
        add_variable("https_proxy", https_proxy)
Example #2
0
class ServerInfo(Plugin):

    transfer_server = String(default="cdimage.ubuntu.com")
    ntp_server = String(default="ntp.ubuntu.com")
    other_server = String(default="127.0.0.1")

    def register(self, manager):
        super(ServerInfo, self).register(manager)

        self._manager.reactor.call_on("gather", self.gather)

    def gather(self):
        add_variable("TRANSFER_SERVER", self.transfer_server)
        add_variable("NTP_SERVER", self.ntp_server)
        add_variable("OTHER_SERVER", self.ntp_server)
Example #3
0
class SubmissionInfo(Plugin):

    # Submission ID to exchange information with the server.
    submission_id = String(required=False)

    def register(self, manager):
        super(SubmissionInfo, self).register(manager)

        self._system_id = None

        for (rt, rh) in [("report", self.report),
                         ("report-system_id", self.report_system_id)]:
            self._manager.reactor.call_on(rt, rh)

    def report_system_id(self, system_id):
        self._system_id = system_id

    # TODO: report this upon gathering
    def report(self):
        submission_id = self.submission_id
        if not submission_id:
            if not self._system_id:
                return

            fingerprint = safe_md5sum()
            fingerprint.update(self._system_id)
            fingerprint.update(str(datetime.utcnow()))
            submission_id = fingerprint.hexdigest()

        message = submission_id
        logging.info("Submission ID: %s", message)
        self._manager.reactor.fire("report-submission_id", message)
Example #4
0
class ClientInfo(Plugin):

    # Name of the user agent
    name = String()

    # Version of the user agent
    version = String()

    def register(self, manager):
        super(ClientInfo, self).register(manager)

        self._manager.reactor.call_on("report", self.report)

    def report(self):
        message = {
            "name": self.name,
            "version": self.version}
        self._manager.reactor.fire("report-client", message)
Example #5
0
class IntroPrompt(Plugin):

    welcome_text = String(default=_("""\
Welcome to the official test suite for the Open Compute project.

Checkbox OCP provides numerous tests to validate your hardware operates 
within guidelines defined by opencompute-ready and/or opencompute-certified
compliance and interoperability scope. 

Once you are finished running the test suite, you can view a summary 
report for your system in the following location:

/home/<user>/.cache/checkbox/submission.xml.

*Checkbox-OCP is a product developed by Canonical with the 
help of members of Facebook, AVL, and the Open Compute project*""") + _("""

Warning: Some tests could cause your system to freeze \
or become unresponsive. Please save all your work \
and close all other running applications before \
beginning the testing process."""))

    def register(self, manager):
        super(IntroPrompt, self).register(manager)

        self._recover = False

        self._manager.reactor.call_on("begin-recover", self.begin_recover)

        # Introduction should be prompted last
        self._manager.reactor.call_on("prompt-begin", self.prompt_begin, 100)

    def begin_recover(self, recover):
        self._recover = recover

    def prompt_begin(self, interface):
        if interface.direction == PREV or not self._recover:
            self._recover = False
            interface.show_text(self.welcome_text, previous="")
Example #6
0
class LaunchpadExchange(Plugin):

    # Timeout value for each submission.
    timeout = Int(default=120)

    # URL where to send submissions.
    transport_url = String(default="https://launchpad.net/+hwdb/+submit")

    def register(self, manager):
        super(LaunchpadExchange, self).register(manager)

        self._headers = {"Referer": self.transport_url}
        self._form = {
            "field.private": "False",
            "field.contactable": "False",
            "field.live_cd": "False",
            "field.format": "VERSION_1",
            "field.actions.upload": "Upload",
            "field.submission_data": None,
            "field.system": None
        }

        for (rt, rh) in [("report-client", self.report_client),
                         ("report-datetime", self.report_datetime),
                         ("report-dpkg", self.report_dpkg),
                         ("report-lsb", self.report_lsb),
                         ("report-submission_id", self.report_submission_id),
                         ("report-system_id", self.report_system_id),
                         ("launchpad-email", self.launchpad_email),
                         ("launchpad-report", self.launchpad_report),
                         ("launchpad-exchange", self.launchpad_exchange)]:
            self._manager.reactor.call_on(rt, rh)

    def report_client(self, message):
        user_agent = "%s/%s" % (message["name"], message["version"])
        self._headers["User-Agent"] = user_agent

    def report_datetime(self, message):
        self._form["field.date_created"] = message

    def report_dpkg(self, resources):
        dpkg = resources[0]
        self._form["field.architecture"] = dpkg["architecture"]

    def report_lsb(self, resources):
        lsb = resources[0]
        self._form["field.distribution"] = lsb["distributor_id"]
        self._form["field.distroseries"] = lsb["release"]

    def report_submission_id(self, message):
        self._form["field.submission_key"] = message

    def report_system_id(self, message):
        self._form["field.system"] = message

    def launchpad_email(self, message):
        self._form["field.emailaddress"] = message

    def launchpad_report(self, report):
        self._launchpad_report = report

    def launchpad_exchange(self, interface):
        # Maybe on the next exchange...
        if not self._form["field.system"] \
           or self._form["field.submission_data"]:
            return

        # Compress and add payload to form
        payload = open(self._launchpad_report, "rb").read()
        compressed_payload = bz2.compress(payload)
        file = BytesIO(compressed_payload)
        file.name = "%s.xml.bz2" % gethostname()
        file.size = len(compressed_payload)
        self._form["field.submission_data"] = file

        if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
            logging.debug("Uncompressed payload length: %d", len(payload))

        # Encode form data
        try:
            form = FORM.coerce(self._form)
        except ValueError as e:
            self._manager.reactor.fire(
                "exchange-error", _("""\
Failed to process form: %s""" % e))
            return

        transport = HTTPTransport(self.transport_url)
        start_time = time.time()
        try:
            response = transport.exchange(form,
                                          self._headers,
                                          timeout=self.timeout)
        except Exception as error:
            self._manager.reactor.fire("exchange-error", str(error))
            return
        end_time = time.time()

        if not response:
            self._manager.reactor.fire(
                "exchange-error",
                _("""\
Failed to contact server. Please try
again or upload the following file name:
%s

directly to the system database:
https://launchpad.net/+hwdb/+submit""") %
                posixpath.abspath(self._launchpad_report))
            return
        elif response.status != 200:
            self._manager.reactor.fire(
                "exchange-error",
                _("""\
Failed to upload to server,
please try again later."""))
            return

        if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
            logging.debug("Response headers:\n%s",
                          pprint.pformat(response.getheaders()))

        header = response.getheader("x-launchpad-hwdb-submission")
        if not header:
            self._manager.reactor.fire(
                "exchange-error", _("Information not posted to Launchpad."))
        elif "Error" in header:
            # HACK: this should return a useful error message
            self._manager.reactor.fire("exchange-error", header)
            logging.error(header)
        else:
            text = response.read()
            self._manager.reactor.fire("exchange-success", text)
            logging.info("Sent %d bytes and received %d bytes in %s.",
                         file.size, len(text),
                         format_delta(end_time - start_time))
Example #7
0
import bz2
import logging
import posixpath

from gettext import gettext as _
from socket import gethostname
from io import BytesIO

from checkbox.lib.log import format_delta
from checkbox.lib.transport import HTTPTransport

from checkbox.properties import File, Int, Map, String
from checkbox.plugin import Plugin

FORM = Map({
    "field.private": String(),
    "field.contactable": String(),
    "field.format": String(),
    "field.actions.upload": String(),
    "field.architecture": String(),
    "field.date_created": String(),
    "field.distribution": String(),
    "field.distroseries": String(),
    "field.submission_key": String(),
    "field.system": String(),
    "field.emailaddress": String(),
    "field.live_cd": String(),
    "field.submission_data": File()
})

Example #8
0
class EnvironmentInfo(Plugin):

    routers = String(default="single")
    router_ssid = String(default="")
    router_psk = String(default="")

    wpa_bg_ssid = String(default="")
    wpa_bg_psk = String(default="")
    open_bg_ssid = String(default="")
    wpa_n_ssid = String(default="")
    wpa_n_psk = String(default="")
    open_n_ssid = String(default="")
    btdevaddr = String(default="")
    gsm_apn = String(default="")
    gsm_conn_name = String(default="")
    gsm_username = String(default="")
    gsm_password = String(default="")
    cdma_conn_name = String(default="")
    cdma_username = String(default="")
    cdma_password = String(default="")
    sources_list = String(default="/etc/apt/sources.list")
    repositories = String(default="")

    def register(self, manager):
        super(EnvironmentInfo, self).register(manager)

        self._manager.reactor.call_on("prompt-begin", self.prompt_begin, 100)

    def prompt_begin(self, interface):
        for key, value in get_variables(self).items():
            name = key.name.upper()
            if name not in os.environ:
                os.environ[name] = value.get()
Example #9
0
class LaunchpadPrompt(Plugin):

    # Email address used to sign in to Launchpad.
    email = String(required=False)

    # Default email address used for anonymous submissions.
    default_email = String(default="*****@*****.**")

    def register(self, manager):
        super(LaunchpadPrompt, self).register(manager)

        self.persist = None

        for (rt, rh) in [("begin-persist", self.begin_persist),
                         ("launchpad-report", self.launchpad_report),
                         ("prompt-exchange", self.prompt_exchange)]:
            self._manager.reactor.call_on(rt, rh)

    def begin_persist(self, persist):
        self.persist = persist.root_at("launchpad_prompt")

    def launchpad_report(self, report):
        self.report = report

    def prompt_exchange(self, interface):
        if self.persist and self.persist.has("email"):
            email = self.persist.get("email")
        else:
            email = self.email

        # Register temporary handler for exchange-error events
        errors = []

        def exchange_error(e):
            errors.append(e)

        event_id = self._manager.reactor.call_on("exchange-error",
                                                 exchange_error)

        while True:
            if errors or not self.email:
                for error in errors:
                    self._manager.reactor.fire("prompt-error", interface,
                                               error)

                url = "file://%s" % posixpath.abspath(self.report)

                # Ignore whether to submit to HEXR
                email = interface.show_entry(_("""\
The following report has been generated for submission to the Launchpad \
hardware database:

  [[%s|View Report]]

You can submit this information about your system by providing the email \
address you use to sign in to Launchpad. If you do not have a Launchpad \
account, please register here:

  https://launchpad.net/+login""") % url,
                                             email,
                                             label=_("Email") + ":")[0]

            if interface.direction == PREV:
                break

            if not email:
                email = self.default_email

            if not re.match(r"^\S+@\S+\.\S+$", email, re.I):
                errors.append(_("Email address must be in a proper format."))
                continue

            errors = []
            self._manager.reactor.fire("launchpad-email", email)
            interface.show_progress(
                _("Exchanging information with the server..."),
                self._manager.reactor.fire, "launchpad-exchange", interface)
            if not errors:
                break

        self._manager.reactor.cancel_call(event_id)
        if self.persist:
            self.persist.set("email", email)
Example #10
0
class SuitesPrompt(Plugin):

    deselect_warning = String(default=_("""\
Unselecting a test will invalidate your submission for Ubuntu Friendly. \
If you plan to participate in Ubuntu Friendly, please, select all tests. \
You can always skip individual tests if you don't have the needed equipment.\
"""))

    @property
    def persist(self):
        if self._persist is None:
            self._persist = Persist(backend=MemoryBackend())

        return self._persist.root_at("suites_prompt")

    def register(self, manager):
        super(SuitesPrompt, self).register(manager)

        self._depends = {}
        self._jobs = {}
        self._statuses = {}
        self._persist = None
        self._recover = False

        for (rt, rh) in [("begin-persist", self.begin_persist),
                         ("begin-recover", self.begin_recover),
                         ("report-suite", self.report_suite),
                         ("store-access", self.store_access)]:
            self._manager.reactor.call_on(rt, rh)

        for (rt, rh) in [("prompt-gather", self.prompt_gather),
                         ("report-suite", self.report_job),
                         ("report-test", self.report_job)]:
            self._manager.reactor.call_on(rt, rh, 100)

    def begin_persist(self, persist):
        self._persist = persist

    def begin_recover(self, recover):
        if recover in [CONTINUE_ANSWER, RERUN_ANSWER]:
            self._recover = True

        if not self._recover:
            self.persist.remove("default")

    def report_suite(self, suite):
        suite.setdefault("type", "suite")

    def store_access(self, store):
        self.store = store

    def report_job(self, job):
        if job.get("type") == "suite":
            attribute = "description"
        else:
            attribute = "name"

        if attribute in job:
            self._jobs[job["name"]] = job[attribute]
            if "suite" in job:
                self._depends[job["name"]] = [job["suite"]]
            if job.get("type") == "test":
                self._statuses[job["name"]] = job["status"]

    def prompt_gather(self, interface):
        # Resolve dependencies
        interface.show_progress_start(
            _("Gathering information from your system..."))
        resolver = Resolver()
        for key in self._jobs.keys():
            depends = self._depends.get(key, [])
            resolver.add(key, *depends)

        # Build options
        options = {}
        self._manager.reactor.fire("expose-msgstore")
        offset = self.store.get_pending_offset()
        self.store.set_pending_offset(0)
        messages = self.store.get_pending_messages()
        self.store.add_pending_offset(offset)
        tests = dict([(m["name"], m) for m in messages
                      if m.get("type") in ("test", "metric")])

        def walk_dependencies(job, all_dependencies):
            for dependency in resolver.get_dependencies(job)[:-1]:
                walk_dependencies(dependency, all_dependencies)
            all_dependencies.append(job)

        for job in resolver.get_dependents():
            suboptions = options
            dependencies = []
            walk_dependencies(job, dependencies)
            for dependency in dependencies:
                if dependency in tests:
                    value = tests[dependency]["status"]
                else:
                    value = self._statuses.get(dependency, {})
                suboptions = suboptions.setdefault(self._jobs[dependency],
                                                   value)

        # Build defaults
        defaults = self.persist.get("default")
        if defaults is None:
            defaults = copy.deepcopy(options)

        # Get results
        interface.show_progress_stop()
        defaults = interface.show_tree(
            _("Choose tests to run on your system:"), options, defaults,
            self.deselect_warning)
        self.persist.set("default", defaults)

        # Get tests to ignore
        def get_ignore_jobs(options, results):
            jobs = []
            if isinstance(options, dict):
                for k, v in options.items():
                    if v == UNINITIATED and k not in results:
                        jobs.append(k)
                    else:
                        jobs.extend(
                            get_ignore_jobs(options[k], results.get(k, {})))

            return jobs

        ignore_jobs = get_ignore_jobs(options, defaults)
        self._manager.reactor.fire("ignore-jobs", ignore_jobs)
Example #11
0
class HexrTransport(Plugin):
    """ This provides means for submitting test reports to hexr.canonical.com
        and/or certification.canonical.com
    """

    # URL where to send submissions.
    transport_url = String(default="https://hexr.canonical.com/checkbox/submit/")

    # Timeout value for each submission.
    timeout = Int(default=360)

    # Timeout value for each submission.
    max_tries = Int(default=5)

    # Header to identify the hardware ID.
    hardware_id_header = String(default="X_HARDWARE_ID")
    submit_to_hexr_header = String(default="X_SHARE_WITH_HEXR")

    html_link = Bool(default=True)

    def register(self, manager):
        super(HexrTransport, self).register(manager)

        self._headers = {}
        self._submission_filename = ""

        #I need to have two things in order to send the report:
        # - Filename of xml report (I get it on launchpad-report event)
        # - Hardware secure ID (I get it on report-hardware-id event)
        #
        # Fire after the report has been generated.
        self._manager.reactor.call_on("launchpad-report",
                                      self._on_get_filename)

        #This is just to get the secure_id when the report-hardware-id event
        #is fired.
        self._manager.reactor.call_on("report-hardware-id",
                                      self._on_report_hardware_id)
        self._manager.reactor.call_on("report-submit-to-hexr",
                                      self._on_report_submit_to_hexr)
        self._manager.reactor.call_on("hexr-exchange",
                                      self._on_hexr_exchange)

    def _on_report_hardware_id(self, hardware_id):
        self._headers[self.hardware_id_header] = hardware_id

    def _on_report_submit_to_hexr(self, submitToHexr):
        if submitToHexr:
            self._headers[self.submit_to_hexr_header] = 'True'
        else:
            self._headers[self.submit_to_hexr_header] = 'False'

    def _on_get_filename(self, filename):
        self._submission_filename = filename

    def _on_hexr_exchange(self, interface):
        #Ensure I have needed data!
        if not self._headers and not self._submission_filename:
            logging.debug("Not ready to submit to new cert website,"
                          "information missing")
            return

        try:
            submission_file = open(self._submission_filename, "rb")
            body = [("data", submission_file)]
        except (IOError, OSError, socket.error, HTTPException) as error:
            logging.debug(error)
            self._manager.reactor.fire("exchange-error", error)
            return

        #Pathetic attempt at slightly more robustness by retrying a few times
        #in case transmitting the submission fails due to network transient
        #glitches.

        for attempt in range(self.max_tries):
            (result, details) = self.submit_results(self.transport_url,
                                                    body,
                                                    self._headers,
                                                    self.timeout)
            if result:
                link = details
                if self.html_link:
                    link = "<a href='%s'>%s</a>" % (details, details)
                self._manager.reactor.fire("report-final-text", "Submission link: " + link)
                break
            else:
                if attempt + 1 >= self.max_tries:
                    retries_string = "I won't try again, sorry."
                else:
                    retries_string = "I will retry (try %d of %d)" % \
                                     (attempt + 1, self.max_tries)

                self._manager.reactor.fire("exchange-error",
                                           " ".join([details, retries_string]))
        #File needs to be closed :)
        submission_file.close()
        return

    def submit_results(self, transport_url, body, headers, timeout):
        transport = HTTPTransport(transport_url)
        start_time = time.time()
        submission_stat = os.fstat(body[0][1].fileno())
        submission_size = submission_stat.st_size

        try:
            response = transport.exchange(body, headers, timeout)
        except (IOError, OSError) as error:
            logging.debug(error)
            return (False, error)

        end_time = time.time()
        if not response:
            error = "Error contacting the server: %s." % transport_url
            logging.debug(error)
            return (False, error)
        elif response.status != 200:
            error = "Server returned unexpected status: %d. " % \
                    response.status
            logging.debug(error)
            return (False, error)
        else:
            #This is the only success block
            text = response.read().decode()
            status_url = json.loads(text)['url']
            if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
                logging.debug("Response headers:\n%s",
                              pprint.pformat(response.getheaders()))
                logging.debug("Response content:\n%s",
                              pprint.pformat(text))
            logging.info("Sent %d bytes and received %d bytes in %s.",
                         submission_size, len(text),
                         format_delta(end_time - start_time))
            return (True, status_url)
Example #12
0
class JobsInfo(Plugin):

    # Domain for internationalization
    domain = String(default="checkbox")

    # Space separated list of directories where job files are stored.
    directories = List(Path(),
                       default_factory=lambda: "%(checkbox_share)s/jobs")

    # List of jobs to blacklist
    blacklist = List(String(), default_factory=lambda: "")

    # Path to blacklist file
    blacklist_file = Path(required=False)

    # List of jobs to whitelist
    whitelist = List(String(), default_factory=lambda: "")

    # Path to whitelist file
    whitelist_file = Path(required=False)

    def register(self, manager):
        super(JobsInfo, self).register(manager)

        self.whitelist_patterns = self.get_patterns(self.whitelist,
                                                    self.whitelist_file)
        self.blacklist_patterns = self.get_patterns(self.blacklist,
                                                    self.blacklist_file)
        self.selected_jobs = defaultdict(list)

        self._missing_dependencies_report = ""

        self._manager.reactor.call_on("prompt-begin", self.prompt_begin)
        self._manager.reactor.call_on("gather", self.gather)
        if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
            self._manager.reactor.call_on("prompt-gather", self.post_gather,
                                          90)
        self._manager.reactor.call_on("report-job", self.report_job, -100)

    def prompt_begin(self, interface):
        """
        Capture interface object to use it later
        to display errors
        """
        self.interface = interface
        self.unused_patterns = (self.whitelist_patterns +
                                self.blacklist_patterns)

    def check_ordered_messages(self, messages):
        """Return whether the list of messages are ordered or not.
           Also populates a _missing_dependencies_report string variable
           with a report of any jobs that are required but not present
           in the whitelist."""
        names_so_far = set()
        all_names = set([message['name'] for message in messages])
        messages_ordered = True
        missing_dependencies = defaultdict(set)

        for message in messages:
            name = message["name"]
            for dependency in message.get("depends", []):
                if dependency not in names_so_far:
                    messages_ordered = False
                #Two separate checks :) we *could* save a negligible
                #bit of time by putting this inside the previous "if"
                #but we're not in *that* big a hurry.
                if dependency not in all_names:
                    missing_dependencies[name].add(dependency)
            names_so_far.add(name)

        #Now assemble the list of missing deps into a nice report
        jobs_and_missing_deps = [
            "{} required by {}".format(
                job_name, ", ".join(missing_dependencies[job_name]))
            for job_name in missing_dependencies
        ]
        self._missing_dependencies_report = "\n".join(jobs_and_missing_deps)

        return messages_ordered

    def get_patterns(self, strings, filename=None):
        """Return the list of strings as compiled regular expressions."""
        if filename:
            try:
                file = open(filename)
            except IOError as e:
                error_message = (_("Failed to open file '%s': %s") %
                                 (filename, e.strerror))
                logging.critical(error_message)
                sys.stderr.write("%s\n" % error_message)
                sys.exit(os.EX_NOINPUT)
            else:
                strings.extend([l.strip() for l in file.readlines()])

        return [
            re.compile(r"^%s$" % s) for s in strings
            if s and not s.startswith("#")
        ]

    def get_unique_messages(self, messages):
        """Return the list of messages without any duplicates, giving
        precedence to messages that are the longest.
        """
        unique_messages = []
        unique_indexes = {}
        for message in messages:
            name = message["name"]
            index = unique_indexes.get(name)
            if index is None:
                unique_indexes[name] = len(unique_messages)
                unique_messages.append(message)
            elif len(message) > len(unique_messages[index]):
                unique_messages[index] = message

        return unique_messages

    def gather(self):
        # Register temporary handler for report-message events
        messages = []

        def report_message(message):
            if self.whitelist_patterns:
                name = message["name"]
                names = [
                    name for p in self.whitelist_patterns if p.match(name)
                ]
                if not names:
                    return

            messages.append(message)

        # Set domain and message event handler
        old_domain = gettext.textdomain()
        gettext.textdomain(self.domain)
        event_id = self._manager.reactor.call_on("report-message",
                                                 report_message, 100)

        for directory in self.directories:
            self._manager.reactor.fire("message-directory", directory)

        for message in messages:
            self._manager.reactor.fire("report-job", message)

        # Unset domain and event handler
        self._manager.reactor.cancel_call(event_id)
        gettext.textdomain(old_domain)

        # Get unique messages from the now complete list
        messages = self.get_unique_messages(messages)

        # Apply whitelist ordering
        if self.whitelist_patterns:

            def key_function(obj):
                name = obj["name"]
                for pattern in self.whitelist_patterns:
                    if pattern.match(name):
                        return self.whitelist_patterns.index(pattern)

            messages = sorted(messages, key=key_function)

        if not self.check_ordered_messages(messages):
            #One of two things may have happened if we enter this code path.
            #Either the jobs are not in topological ordering,
            #Or they are in topological ordering but a dependency is
            #missing.
            old_message_names = [
                message["name"] + "\n" for message in messages
            ]
            resolver = Resolver(key_func=lambda m: m["name"])
            for message in messages:
                resolver.add(message, *message.get("depends", []))
            messages = resolver.get_dependents()

            if (self.whitelist_patterns and
                    logging.getLogger().getEffectiveLevel() <= logging.DEBUG):
                new_message_names = [
                    message["name"] + "\n" for message in messages
                ]
                #This will contain a report of out-of-order jobs.
                detailed_text = "".join(
                    difflib.unified_diff(old_message_names, new_message_names,
                                         "old whitelist", "new whitelist"))
                #First, we report missing dependencies, if any.
                if self._missing_dependencies_report:
                    primary = _("Dependencies are missing so some jobs "
                                "will not run.")
                    secondary = _("To fix this, close checkbox and add "
                                  "the missing dependencies to the "
                                  "whitelist.")
                    self._manager.reactor.fire(
                        "prompt-warning", self.interface, primary, secondary,
                        self._missing_dependencies_report)
                #If detailed_text is empty, it means the problem
                #was missing dependencies, which we already reported.
                #Otherwise, we also need to report reordered jobs here.
                if detailed_text:
                    primary = _("Whitelist not topologically ordered")
                    secondary = _("Jobs will be reordered to fix broken "
                                  "dependencies")
                    self._manager.reactor.fire("prompt-warning",
                                               self.interface, primary,
                                               secondary, detailed_text)

        self._manager.reactor.fire("report-jobs", messages)

    def post_gather(self, interface):
        """
        Verify that all patterns were used
        """
        if logging.getLogger().getEffectiveLevel() > logging.DEBUG:
            return

        orphan_test_cases = []
        for name, jobs in self.selected_jobs.items():
            is_test = any(job.get('type') == 'test' for job in jobs)
            has_suite = any(job.get('suite') for job in jobs)
            if is_test and not has_suite:
                orphan_test_cases.append(name)

        if orphan_test_cases:
            detailed_error = \
                ('Test cases not included in any test suite:\n'
                 '{0}\n\n'
                 'This might cause problems '
                 'when uploading test cases results.\n'
                 'Please make sure that the patterns you used are up-to-date\n'
                 .format('\n'.join(['- {0}'.format(tc)
                                    for tc in orphan_test_cases])))
            self._manager.reactor.fire(
                'prompt-warning', self.interface, 'Orphan test cases detected',
                "Some test cases aren't included "
                'in any test suite', detailed_error)

        if self.unused_patterns:
            detailed_error = \
                ('Unused patterns:\n'
                 '{0}\n\n'
                 "Please make sure that the patterns you used are up-to-date\n"
                 .format('\n'.join(['- {0}'.format(p.pattern[1:-1])
                                        for p in self.unused_patterns])))
            self._manager.reactor.fire(
                'prompt-warning', self.interface, 'Unused patterns',
                'Please make sure that the patterns '
                'you used are up-to-date', detailed_error)

    @coerce_arguments(job=job_schema)
    def report_job(self, job):
        name = job["name"]

        patterns = self.whitelist_patterns or self.blacklist_patterns
        if patterns:
            match = next((p for p in patterns if p.match(name)), None)
            if match:
                # Keep track of which patterns didn't match any job
                if match in self.unused_patterns:
                    self.unused_patterns.remove(match)
                self.selected_jobs[name].append(job)
            else:
                # Stop if job not in whitelist or in blacklist
                self._manager.reactor.stop()
Example #13
0
from checkbox.lib.resolver import Resolver

from checkbox.arguments import coerce_arguments
from checkbox.plugin import Plugin
from checkbox.properties import (
    Float,
    Int,
    List,
    Map,
    Path,
    String,
)

job_schema = Map({
    "plugin": String(),
    "name": String(),
    "type": String(required=False),
    "status": String(required=False),
    "suite": String(required=False),
    "description": String(required=False),
    "purpose": String(required=False),
    "steps": String(required=False),
    "info": String(required=False),
    "verification": String(required=False),
    "command": String(required=False),
    "depends": List(String(), required=False),
    "duration": Float(required=False),
    "environ": List(String(), required=False),
    "requires": List(String(), separator=r"\n", required=False),
    "resources": List(String(), required=False),
Example #14
0
class LaunchpadReport(Plugin):

    # Filename where submission information is cached.
    filename = Path(default="%(checkbox_data)s/submission.xml")

    # Prompt for place to save the submission file
    submission_path_prompt = String(default="")

    # XML Schema
    schema = Path(default="%(checkbox_share)s/report/hardware-1_0.rng")

    # XSL Stylesheet
    stylesheet = Path(default="%(checkbox_share)s/report/checkbox.xsl")

    def register(self, manager):
        super(LaunchpadReport, self).register(manager)

        self._report = {
            "summary": {
                "private": False,
                "contactable": False,
                "live_cd": False},
            "hardware": {},
            "software": {
                "packages": []},
            "questions": [],
            "context": []}

        for (rt, rh) in [
            ("report-attachments", self.report_attachments),
            ("report-client", self.report_client),
            ("report-cpuinfo", self.report_cpuinfo),
            ("report-datetime", self.report_datetime),
            ("report-dpkg", self.report_dpkg),
            ("report-lsb", self.report_lsb),
            ("report-package", self.report_package),
            ("report-uname", self.report_uname),
            ("report-system_id", self.report_system_id),
            ("report-suites", self.report_suites),
            ("report-review", self.report_review),
            ("report-tests", self.report_tests)]:
                self._manager.reactor.call_on(rt, rh)

        # Launchpad report should be generated last.
        self._manager.reactor.call_on("report", self.report, 100)

        #Ask where to put submission file
        self._manager.reactor.call_on("prompt-begin", self.prompt_begin, 110)

    def prompt_begin(self, interface):
        if self.submission_path_prompt:
            # Ignore whether to submit to HEXR
            new_filename = interface.show_entry(
                self.submission_path_prompt, self.filename)[0]
            if new_filename != "":
                self.filename = new_filename

    def report_attachments(self, attachments):
        for attachment in attachments:
            name = attachment["name"]
            if "sysfs_attachment" in name:
                self._report["hardware"]["sysfs-attributes"] = \
                    attachment["data"]

            elif "dmi_attachment" in name:
                self._report["hardware"]["dmi"] = attachment["data"]

            elif "udev_attachment" in name:
                self._report["hardware"]["udev"] = attachment["data"]

            elif (all(c in printable for c in attachment["data"]) and
                  attachment['status'] != 'unsupported'):
                self._report["context"].append({
                    "command": attachment["command"],
                    "data": attachment["data"]})

    def report_client(self, client):
        self._report["summary"]["client"] = client

    def report_cpuinfo(self, resources):
        cpuinfo = resources[0]
        processors = []
        for i in range(int(cpuinfo["count"])):
            cpuinfo = dict(cpuinfo)
            cpuinfo["name"] = i
            processors.append(cpuinfo)

        self._report["hardware"]["processors"] = processors

    def report_datetime(self, datetime):
        self._report["summary"]["date_created"] = datetime

    def report_dpkg(self, resources):
        dpkg = resources[0]
        self._report["summary"]["architecture"] = dpkg["architecture"]

    def report_lsb(self, resources):
        lsb = resources[0]
        self._report["software"]["lsbrelease"] = dict(lsb)
        self._report["summary"]["distribution"] = lsb["distributor_id"]
        self._report["summary"]["distroseries"] = lsb["release"]

    def report_package(self, resources):
        self._report["software"]["packages"] = resources

    def report_uname(self, resources):
        uname = resources[0]
        self._report["summary"]["kernel-release"] = (
            "{release} {version}".format(release=uname["release"],
                                         version=uname["version"]))

    def report_system_id(self, system_id):
        self._report["summary"]["system_id"] = system_id

    def report_tests(self, tests):
        self.tests = tests
        for test in tests:
            question = {
                "name": test["name"],
                "answer": test["status"],
                "comment": test.get("data", "")}
            self._report["questions"].append(question)

    def report(self):
        # Prepare the payload and attach it to the form
        stylesheet_path = os.path.join(
            os.path.dirname(self.filename),
            os.path.basename(self.stylesheet))
        report_manager = LaunchpadReportManager(
            "system", "1.0", stylesheet_path, self.schema)
        payload = report_manager.dumps(self._report).toprettyxml("")

        # Write the report
        stylesheet_data = open(self.stylesheet).read() % os.environ
        open(stylesheet_path, "w").write(stylesheet_data)
        directory = os.path.dirname(self.filename)
        safe_make_directory(directory)
        open(self.filename, "w").write(payload)

        # Validate the report
        if not report_manager.validate(payload):
            self._manager.reactor.fire("report-error", _("""\
The generated report seems to have validation errors,
so it might not be processed by Launchpad."""))

        self._manager.reactor.fire("launchpad-report", self.filename)

    def report_review(self, interface):
        """
        Show test report in the interface
        """
        report = {}

        def add_job(job):
            is_suite = 'type' in job and job['type'] == 'suite'
            if 'suite' in job:
                suite_name = job['suite']
                parent_node = add_job(self.suites[suite_name])

                if is_suite:
                    if job['description'] in parent_node:
                        return parent_node[job['description']]

                    node = {}
                    parent_node[job['description']] = node
                    return node
                parent_node[job['name']] = job
            else:
                if is_suite:
                    field = 'description'
                else:
                    field = 'name'

                if job[field] in report:
                    return report[job[field]]

                node = {}
                report[job[field]] = node
                return node

        for test in self.tests:
            add_job(test)

        try:
            interface.show_report("Test case results report", report)
        except NotImplementedError:
            # Silently ignore the interfaces that don't implement the method
            pass

    def report_suites(self, suites):
        """
        Get tests results and store it
        to display them later
        """
        self.suites = dict([(suite['name'], suite) for suite in suites])
Example #15
0
class UserInterface(Plugin):

    # Module where the user interface implementation is defined.
    interface_module = String(default="checkbox.user_interface")

    # Class implementing the UserInterface interface.
    interface_class = String(default="UserInterface")

    # HACK: this is only a temporary workaround to internationalize the
    # user interface title and should be eventually removed.
    gettext.textdomain("checkbox")

    # Title of the user interface
    title = String(default=_("System Testing"))

    # Path where data files are stored.
    data_path = Path(required=False)

    @property
    def persist(self):
        if self._persist is None:
            self._persist = Persist(backend=MemoryBackend())

        return self._persist.root_at("user_interface")

    def register(self, manager):
        super(UserInterface, self).register(manager)

        self._persist = None

        self._manager.reactor.call_on("prompt-begin", self.prompt_begin)
        self._manager.reactor.call_on("stop", self.save_persist)
        self._manager.reactor.call_on("begin-persist", self.begin_persist)
        self._manager.reactor.call_on("run", self.run)
        self._manager.reactor.call_on("launchpad-report",
                                      self.launchpad_report)

        self._manager.reactor.call_on("set-progress", self.set_progress)
        self._manager.reactor.call_on("prompt-job", self.update_status, 101)

    def update_status(self, interface, job):
        #The UI can choose to implement this method to get
        #information about each job that completes
        interface.update_status(job)

    def begin_persist(self, persist):
        self._persist = persist

    def prompt_begin(self, interface):
        self._interface.ui_flags = self.persist.get("ui_flags", {})

    def save_persist(self, *args):
        self.persist.set("ui_flags", self._interface.ui_flags)
        self.persist.save()

    def set_progress(self, progress):
        self._interface.progress = progress

    def run(self):
        interface_module = __import__(self.interface_module, None, None, [''])
        interface_class = getattr(interface_module, self.interface_class)
        interface = interface_class(self.title, self.data_path)
        self._interface = interface

        event_types = [
            "prompt-begin", "prompt-gather", "prompt-jobs", "prompt-report",
            "prompt-exchange", "prompt-finish"
        ]

        index = 0
        while index < len(event_types):
            event_type = event_types[index]
            self._manager.reactor.fire(event_type, interface)

            if interface.direction == PREV:
                if index > 0:
                    index -= 1
            else:
                index += 1

    def launchpad_report(self, launchpad_report):
        self._interface.report_url = "file://%s" % posixpath.abspath(
            launchpad_report)