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)
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)
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)
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)
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="")
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))
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() })
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()
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)
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)
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)
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()
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),
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])
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)