~checkbox-dev/checkbox-legacy/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#
# This file is part of Checkbox.
#
# Copyright 2013 Canonical Ltd.
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.

#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
#
import logging
import os
import pprint
import socket
import time
import json

from gettext import gettext as _
from http.client import HTTPException
from string import printable

from checkbox.lib.log import format_delta
from checkbox.lib.transport import HTTPTransport
from checkbox.plugin import Plugin
from checkbox.properties import Bool, Int, String


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)


factory = HexrTransport