~launchpad-pqm/launchpad/devel

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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
Submissions to the hardware database
====================================

The hardware database client collects information from various sources,
and submits it in an HTTP POST request to the hardware database server.
The POST data consists of the following fields:

  * date_created (see hwdb.txt)
  * format (see hwdb.txt)
  * private (see hwdb.txt)
  * contactable (see hwdb.txt)
  * submission_key (see hwdb.txt)
  * emailaddress (see hwdb.txt)
  * distribution: The distribution name. The value should match a value
    in the SQL table Distribution, column name.
  * distroseries: The distroseries version. The value should match a value
    in the SQL table distroseries, column version
  * processorfamily: The name of the processorfamily. The value should
    match a value in the SQL table Processorfamily, column name.
  * system: The system name as returned by HAL (system.vendor, system.product)
  * submission_data: An XML file containing the collected data. This file is
    simply stored as a Librarian file, and parsed later by a cron job.

    >>> from zope.component import getUtility
    >>> from StringIO import StringIO
    >>> from lp.hardwaredb.interfaces.hwdb import IHWDBApplication
    >>> data = StringIO('some data.')
    >>> data.filename = 'hardware-info'
    >>> form={'field.date_created':    u'2007-08-01',
    ...       'field.format':          u'VERSION_1',
    ...       'field.private':         u'',
    ...       'field.contactable':     u'',
    ...       'field.submission_key':  u'unique-id-1',
    ...       'field.emailaddress':    u'test@canonical.com',
    ...       'field.distribution':    u'ubuntu',
    ...       'field.distroseries':    u'5.04',
    ...       'field.architecture':    u'i386',
    ...       'field.system':          u'HP 6543',
    ...       'field.submission_data': data,
    ...       'field.actions.upload':  u'Upload'}

    >>> app = getUtility(IHWDBApplication)
    >>> submit_view = create_initialized_view(app, name='+submit', form=form)
    >>> submit_view.errors
    []

The request created an entry in the HWDBSubmissions table.

    >>> from lp.hardwaredb.interfaces.hwdb import IHWSubmissionSet
    >>> submission_set = getUtility(IHWSubmissionSet)
    >>> submission = submission_set.getBySubmissionKey(u'unique-id-1')
    >>> submission.date_created, submission.format.name
    (datetime.datetime(2007, 8, 1, 0, 0, tzinfo=<UTC>), 'VERSION_1')
    >>> submission.private, submission.contactable
    (False, False)
    >>> submission.submission_key
    u'unique-id-1'
    >>> submission.system_fingerprint.fingerprint
    u'HP 6543'

The submitted data is stored in raw_submission.

    >>> import transaction
    >>> transaction.commit()
    >>> submission.raw_submission.read()
    'some data.'

A reference to distroarchseries is created for the fields distribution,
distroseries, architecture.

    >>> submission.distroarchseries.distroseries.distribution.name
    u'ubuntu'
    >>> submission.distroarchseries.distroseries.version
    u'5.04'
    >>> submission.distroarchseries.architecturetag
    u'i386'

Each submission must have a distinct submission_key, hence an attempt to submit
identical data a second time leads to an error.

    >>> submit_view = create_initialized_view(app, name='+submit', form=form)
    >>> for error in submit_view.errors:
    ...     print error.doc()
    Submission key already exists.

If the field distribution contains a name which is not known in the
Launchpad database, the distroarchseries field is None.

    >>> form['field.submission_key'] = u'unique-id-2'
    >>> invalid_form = form.copy()
    >>> invalid_form['field.distribution'] = 'no distribution'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> submission = submission_set.getBySubmissionKey(u'unique-id-2')
    >>> print submission.distroarchseries
    None

If distribution is known, but distroseries or architecture are unknown
to Launchpad, we refuse the submissions.

    >>> form['field.submission_key'] = u'unique-id-3'
    >>> invalid_form = form.copy()
    >>> invalid_form['field.distroseries'] = 'no release'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> print submission_set.getBySubmissionKey(u'unique-id-3')
    None

    >>> form['field.submission_key'] = u'unique-id-4'
    >>> invalid_form = form.copy()
    >>> invalid_form['field.architecture'] = 'no architecture'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> print submission_set.getBySubmissionKey(u'unique-id-4')
    None

The `date_created` field must contain a valid date.

    >>> form['field.submission_key'] = u'unique-id-5'
    >>> invalid_form = form.copy()
    >>> invalid_form['field.date_created'] = '2007-05-35'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> for error in submit_view.errors:
    ...     print error.doc()
    Invalid datetime data

The `format` field must contain a value that matches the DBEnumeratedType
HWDBSubmissionFormat.

    >>> invalid_form = form.copy()
    >>> invalid_form['field.format'] = u'VERSION_42'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> for error in submit_view.errors:
    ...     print error.doc()
    Invalid value

The field `submission_key` may contain only ASCII data.

    >>> invalid_form = form.copy()
    >>> invalid_form['field.submission_key'] = u'wrong id \x81'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> for error in submit_view.errors:
    ...     print error.doc()
    Invalid textual data

The field `emailaddress` must contain a formally valid email address.

    >>> invalid_form = form.copy()
    >>> invalid_form['field.emailaddress'] = u'beeblebrox'
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=invalid_form)
    >>> for error in submit_view.errors:
    ...     print error.doc()
    Invalid email address

All fields are required.  With normal form processing, it's impossible not to
have values for field.format, field.private, or field.contactable because
those widgets are checkboxes and menus.

    >>> for field in (
    ...     'field.date_created', 'field.submission_key',
    ...     'field.emailaddress', 'field.distribution',
    ...     'field.distroseries', 'field.architecture',
    ...     'field.system', 'field.submission_data',
    ...     ):
    ...     invalid_form = form.copy()
    ...     del invalid_form[field]
    ...     invalid_form[field] = u''
    ...     submit_view = create_initialized_view(
    ...         app, name='+submit', form=invalid_form)
    ...     print field
    ...     for error in submit_view.errors:
    ...         field_name = error.field_name
    ...         print '   ', field_name, submit_view.getFieldError(field_name)
    field.date_created
        date_created Required input is missing.
    field.submission_key
        submission_key Required input is missing.
    field.emailaddress
        emailaddress Required input is missing.
    field.distribution
        distribution Required input is missing.
    field.distroseries
        distroseries Required input is missing.
    field.architecture
        architecture Required input is missing.
    field.system
        system Required input is missing.
    field.submission_data
        submission_data Required input is missing.

Teams can be owners of submissions.

    >>> import os
    >>> from lp.services.config import config
    >>> team_form = form.copy()
    >>> team_form['field.emailaddress'] = 'support@ubuntu.com'
    >>> team_form['field.submission_key'] = u'unique-id-68'
    >>> valid_sample_data_path = os.path.join(
    ...     config.root,
    ...     'lib/lp/hardwaredb/scripts/tests/'
    ...     'simple_valid_hwdb_submission.xml')
    >>> valid_sample_data = StringIO(open(valid_sample_data_path).read())
    >>> valid_sample_data.filename = 'simple_valid_hwdb_submission.xml'
    >>> team_form['field.submission_data'] = valid_sample_data
    >>> submit_view = create_initialized_view(
    ...     app, name='+submit', form=team_form)
    >>> submission = submission_set.getBySubmissionKey(u'unique-id-68')
    >>> submission.owner.displayname
    u'Ubuntu Team'


Submission Processing
---------------------

Submissions are processed by the cronscript process-hwdb-submissions.py.
This script processes all submissions with the status SUBMITTED, checks
the validity of each submission, populates the HWDB tables with data
from a submission and sets the submission state to VALID. If any error
occurs when a submission is processed, its status is set to INVALID and
according error is logged. For details, see
lp/hardwaredb/scripts/tests/test_hwdb_submission_processing.py.

We have currently three unprocessed submissions in the database, one
submission with the status PROCESSED and no submissions with the status
INVALID.

    >>> from lp.hardwaredb.interfaces.hwdb import (
    ...     HWSubmissionProcessingStatus)
    >>> new_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED)
    >>> for submission in new_submissions:
    ...     print submission.submission_key, submission.status.title
    test_submission_id_1 Submitted
    unique-id-1 Submitted
    unique-id-68 Submitted
    >>> processed_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED)
    >>> for submission in processed_submissions:
    ...     print submission.submission_key, submission.status.title
    sample-submission Processed
    >>> invalid_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID)
    >>> print invalid_submissions.count()
    0

The script process-hwdb-submissions.py takes the optional parameter
-m or --max-submissions, so let's process just the first of these three
submissions. We don't have a Librarian file for this submission, so
let's add one. Let's add invalid data in order to see how invalid
submissions are processed.

    >>> from lp.services.librarianserver.testing.server import fillLibrarianFile
    >>> submission = submission_set.getBySubmissionKey('test_submission_id_1')
    >>> fillLibrarianFile(
    ...     submission.raw_submission.id, 'nonsense')

    # Commit the current transaction so that the script sees the
    # recently added data.
    >>> import transaction
    >>> transaction.commit()

    # Run the script.
    >>> from lp.testing.script import run_script
    >>> returnvalue, out, err = run_script(
    ...     'cronscripts/process-hwdb-submissions.py', ['-m1'])
    >>> returnvalue
    0
    >>> print err
    INFO    Creating lockfile: /var/lock/launchpad-hwdbsubmissions.lock
    ERROR   Parsing submission test_submission_id_1: syntax error:
    line 1, column 0
    INFO    Processed 0 valid and 1 invalid HWDB submissions
    <BLANKLINE>
    >>> print out
    <BLANKLINE>

Submission "test_submission_id_1" has now the state INVALID; the other
submissions are unchanged.

    >>> new_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED)
    >>> for submission in new_submissions:
    ...     print submission.submission_key, submission.status.title
    unique-id-1 Submitted
    unique-id-68 Submitted
    >>> processed_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED)
    >>> for submission in processed_submissions:
    ...     print submission.submission_key, submission.status.title
    sample-submission Processed
    >>> invalid_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID)
    >>> for submission in invalid_submissions:
    ...     print submission.submission_key, submission.status.title
    test_submission_id_1 Invalid submission

From the remaining two not yet processed submissions, one has invalid
data, the other submission is valid.

    >>> returnvalue, out, err = run_script(
    ...     'cronscripts/process-hwdb-submissions.py')
    >>> returnvalue
    0
    >>> print err
    INFO    Creating lockfile: /var/lock/launchpad-hwdbsubmissions.lock
    ERROR   Parsing submission unique-id-1: syntax error: line 1, column 0
    INFO    Processed 1 valid and 1 invalid HWDB submissions
    >>> print out
    <BLANKLINE>

Now we have one valid, two invalid and no unprocessed submissions.

    >>> # We must start a new transaction in order to see the effects
    >>> # the script had on the database.
    >>> transaction.commit()
    >>> new_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED)
    >>> print new_submissions.count()
    0
    >>> processed_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED)
    >>> for submission in processed_submissions:
    ...     print submission.submission_key, submission.status.title
    sample-submission Processed
    unique-id-68 Processed

    >>> invalid_submissions = submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID)
    >>> for submission in invalid_submissions:
    ...     print submission.submission_key, submission.status.title
    test_submission_id_1 Invalid submission
    unique-id-1 Invalid submission

Larger numbers of submissions can be processed too. Add enough submissions
that scripts.hwdbsubmissions.ProcessingLoop is called at least twice.

    >>> form['field.submission_data'] = valid_sample_data
    >>> for serial in range(80):
    ...     form['field.submission_key'] = u'submission-%i' % serial
    ...     submit_view = create_initialized_view(
    ...         app, name='+submit', form=form)

Now we have 80 new submissions and three submissions that were processed
in previous tests.

    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED).count()
    80
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED).count()
    2
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID).count()
    2
    >>> transaction.commit()

Let's leave some submissions unprocessed in order to check if the
processing loop terminates properly, when the parameter "-m" is given.

    >>> returnvalue, out, err = run_script(
    ...     'cronscripts/process-hwdb-submissions.py', ['-m60'])
    >>> returnvalue
    0
    >>> print err
    INFO    Creating lockfile: /var/lock/launchpad-hwdbsubmissions.lock
    INFO    Processed 60 valid and 0 invalid HWDB submissions
    >>> print out
    <BLANKLINE>
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED).count()
    20
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED).count()
    62
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID).count()
    2

Let's add more subscription so that we have more than max_chunk_size
unprocessed submissions and process all of them.

    >>> for serial in range(80, 160):
    ...     form['field.submission_key'] = u'submission-%i' % serial
    ...     submit_view = create_initialized_view(
    ...         app, name='+submit', form=form)

    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED).count()
    100
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED).count()
    62
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID).count()
    2
    >>> transaction.commit()
    >>> returnvalue, out, err = run_script(
    ...     'cronscripts/process-hwdb-submissions.py')
    >>> returnvalue
    0
    >>> print err
    INFO    Creating lockfile: /var/lock/launchpad-hwdbsubmissions.lock
    INFO    Processed 100 valid and 0 invalid HWDB submissions
    >>> print out
    <BLANKLINE>
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.SUBMITTED).count()
    0
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.PROCESSED).count()
    162
    >>> print submission_set.getByStatus(
    ...     HWSubmissionProcessingStatus.INVALID).count()
    2