287 print 'criteria =', criteria |
297 print 'criteria =', criteria |
288 print 'servicename found by port =', str(found_servicename), '</ol>' |
298 print 'servicename found by port =', str(found_servicename), '</ol>' |
289 print '</pre>' |
299 print '</pre>' |
290 return |
300 return |
291 |
301 |
292 if str(manifest).isdigit() and manifest > 0: |
|
293 web_page = \ |
|
294 E.HTML( |
|
295 E.HEAD( |
|
296 E.TITLE(_("Error!")) |
|
297 ), |
|
298 E.BODY( |
|
299 E.P(_("Criteria indeterminate -- this " |
|
300 "should not happen! Got %s matches.") % |
|
301 str(manifest)) |
|
302 ) |
|
303 ) |
|
304 print "Content-Type: text/html" # HTML is following |
|
305 print # blank line, end of headers |
|
306 print lxml.etree.tostring(web_page, pretty_print=True) |
|
307 return |
|
308 |
|
309 # check if findManifest() returned a number equal to 0 |
302 # check if findManifest() returned a number equal to 0 |
310 # (means we got no manifests back -- thus we serve the default) |
303 # (means we got no manifests back -- thus we serve the default if desired) |
311 elif manifest == 0: |
304 if manifest is None and not no_default: |
312 manifest = get_default(servicename) |
305 manifest = get_default(servicename) |
313 |
306 |
314 # findManifest() returned the name of the manifest to serve |
307 # if we have a manifest to return, prepare its return |
315 # (or it is now set to default.xml) |
308 if manifest is not None: |
316 try: |
309 try: |
317 # construct the fully qualified filename |
310 # construct the fully qualified filename |
318 path = os.path.join(os.path.dirname(path), AI_DATA) |
311 path = os.path.join(os.path.dirname(path), AI_DATA) |
319 filename = os.path.abspath(os.path.join(path, manifest)) |
312 filename = os.path.abspath(os.path.join(path, manifest)) |
320 # open and read the manifest |
313 # open and read the manifest |
321 with open(filename, 'rb') as mfp: |
314 with open(filename, 'rb') as mfp: |
322 manifest_str = mfp.read() |
315 manifest_str = mfp.read() |
323 # maintain compability with older AI client |
316 # maintain compability with older AI client |
324 if servicename is None or \ |
317 if servicename is None or \ |
325 float(protocolversion) < float(PROFILES_VERSION): |
318 float(protocolversion) < float(PROFILES_VERSION): |
326 content_type = mimetypes.types_map.get('.xml', 'text/plain') |
319 content_type = mimetypes.types_map.get('.xml', 'text/plain') |
327 print 'Content-Length:', len(manifest_str) # Length of the file |
320 print 'Content-Length:', len(manifest_str) # Length of the file |
328 print 'Content-Type:', content_type # XML is following |
321 print 'Content-Type:', content_type # XML is following |
329 print # blank line, end of headers |
322 print # blank line, end of headers |
330 print manifest_str |
323 print manifest_str |
331 logging.info('Manifest sent from %s.' % filename) |
324 logging.info('Manifest sent from %s.' % filename) |
|
325 return |
|
326 |
|
327 except OSError, err: |
|
328 print 'Content-Type: text/html' # HTML is following |
|
329 print # blank line, end of headers |
|
330 print '<pre>' |
|
331 # report the internal error to error_log and requesting client |
|
332 sys.stderr.write(_('error:manifest (%s) %s\n') % (str(manifest), err)) |
|
333 sys.stdout.write(_('error:manifest (%s) %s\n') % (str(manifest), err)) |
|
334 print '</pre>' |
332 return |
335 return |
333 |
336 |
334 except OSError, err: |
|
335 print 'Content-Type: text/html' # HTML is following |
|
336 print # blank line, end of headers |
|
337 print '<pre>' |
|
338 # report the internal error to error_log and requesting client |
|
339 sys.stderr.write(_('error:manifest (%s) %s\n') % (str(manifest), err)) |
|
340 sys.stdout.write(_('error:manifest (%s) %s\n') % (str(manifest), err)) |
|
341 print '</pre>' |
|
342 return |
|
343 |
337 |
344 # get AI service image path |
338 # get AI service image path |
345 service_info = get_service_info(servicename) |
339 service_info = get_service_info(servicename) |
346 # construct object to contain MIME multipart message |
340 # construct object to contain MIME multipart message |
347 outermime = MIMEMultipart() |
341 outermime = MIMEMultipart() |
348 client_msg = list() # accumulate message output for AI client |
342 client_msg = list() # accumulate message output for AI client |
349 # add manifest as attachment |
343 |
350 msg = MIMEText(manifest_str, 'xml') |
344 # If we have a manifest, attach it to the return message |
351 # indicate manifest using special name |
345 if manifest is not None: |
352 msg.add_header('Content-Disposition', 'attachment', |
346 # add manifest as attachment |
353 filename=sc.AI_MANIFEST_ATTACHMENT_NAME) |
347 msg = MIMEText(manifest_str, 'xml') |
354 outermime.attach(msg) # add manifest as an attachment |
348 # indicate manifest using special name |
|
349 msg.add_header('Content-Disposition', 'attachment', |
|
350 filename=sc.AI_MANIFEST_ATTACHMENT_NAME) |
|
351 outermime.attach(msg) # add manifest as an attachment |
|
352 |
355 |
353 |
356 # search for any profiles matching client criteria |
354 # search for any profiles matching client criteria |
357 # formulate database query to profiles table |
355 # formulate database query to profiles table |
358 q_str = "SELECT DISTINCT name, file FROM " + \ |
356 q_str = "SELECT DISTINCT name, file FROM " + \ |
359 AIdb.PROFILES_TABLE + " WHERE " |
357 AIdb.PROFILES_TABLE + " WHERE " |
360 nvpairs = list() # accumulate criteria values from post-data |
358 nvpairs = list() # accumulate criteria values from post-data |
361 # for all AI client criteria |
359 # for all AI client criteria |
362 for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, |
360 for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, |
363 onlyUsed=False): |
361 onlyUsed=False): |
364 if crit not in criteria: |
362 if crit not in criteria: |
365 msgtxt = _("Warning: expected client criteria \"%s\" " \ |
363 msgtxt = _("Warning: client criteria \"%s\" not provided in " |
366 "missing from post-data. Profiles may be missing.") \ |
364 "request. Setting value to NULL for profile lookup.") \ |
367 % crit |
365 % crit |
368 client_msg += [msgtxt] |
366 client_msg += [msgtxt] |
369 logging.warn(msgtxt) |
367 logging.warn(msgtxt) |
370 # fetch only global profiles destined for all clients |
368 # fetch only global profiles destined for all clients |
371 if AIdb.isRangeCriteria(aisql.getQueue(), crit, |
369 if AIdb.isRangeCriteria(aisql.getQueue(), crit, |
373 nvpairs += ["MIN" + crit + " IS NULL"] |
371 nvpairs += ["MIN" + crit + " IS NULL"] |
374 nvpairs += ["MAX" + crit + " IS NULL"] |
372 nvpairs += ["MAX" + crit + " IS NULL"] |
375 else: |
373 else: |
376 nvpairs += [crit + " IS NULL"] |
374 nvpairs += [crit + " IS NULL"] |
377 continue |
375 continue |
|
376 |
378 # prepare criteria value to add to query |
377 # prepare criteria value to add to query |
379 envval = AIdb.sanitizeSQL(criteria[crit]) |
378 envval = AIdb.sanitizeSQL(criteria[crit]) |
380 if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): |
379 if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): |
381 if crit == "mac": |
380 # If no default profiles are requested, then we mustn't allow |
382 nvpairs += ["(MIN" + crit + " IS NULL OR " |
381 # this criteria to be NULL. It must match the client's given |
383 "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))"] |
382 # value for this criteria. |
384 nvpairs += ["(MAX" + crit + " IS NULL OR HEX(MAX" + |
383 if no_default: |
|
384 if crit == "mac": |
|
385 nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \ |
|
386 "'))"] |
|
387 |
|
388 nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \ |
|
389 "'))"] |
|
390 else: |
|
391 nvpairs += ["(MIN" + crit + "<='" + envval + "')"] |
|
392 nvpairs += ["(MAX" + crit + ">='" + envval + "')"] |
|
393 else: |
|
394 if crit == "mac": |
|
395 nvpairs += ["(MIN" + crit + " IS NULL OR " |
|
396 "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))"] |
|
397 nvpairs += ["(MAX" + crit + " IS NULL OR HEX(MAX" + |
385 crit + ")>=HEX(X'" + envval + "'))"] |
398 crit + ")>=HEX(X'" + envval + "'))"] |
386 else: |
399 else: |
387 nvpairs += ["(MIN" + crit + " IS NULL OR MIN" + |
400 nvpairs += ["(MIN" + crit + " IS NULL OR MIN" + |
388 crit + "<='" + envval + "')"] |
401 crit + "<='" + envval + "')"] |
389 nvpairs += ["(MAX" + crit + " IS NULL OR MAX" + |
402 nvpairs += ["(MAX" + crit + " IS NULL OR MAX" + |
390 crit + ">='" + envval + "')"] |
403 crit + ">='" + envval + "')"] |
391 else: |
404 else: |
392 nvpairs += ["(" + crit + " IS NULL OR " + |
405 # If no default profiles are requested, then we mustn't allow |
393 crit + "='" + envval + "')"] |
406 # this criteria to be NULL. It must match the client's given |
394 q_str += " AND ".join(nvpairs) |
407 # value for this criteria. |
395 |
408 # |
396 # issue database query |
409 # Also, since this is a non-range criteria, the value stored |
397 logging.info("Profile query: " + q_str) |
410 # in the DB may be a whitespace separated list of single |
398 query = AIdb.DBrequest(q_str) |
411 # values. We use a special user-defined function in the |
399 aisql.getQueue().put(query) |
412 # determine if the given criteria is in that textual list. |
400 query.waitAns() |
413 if no_default: |
401 if query.getResponse() is None or len(query.getResponse()) == 0: |
414 nvpairs += ["(is_in_list('" + crit + "', '" + envval + "', " + \ |
402 msgtxt = _("No profiles found.") |
415 crit + ", 'None') == 1)"] |
403 client_msg += [msgtxt] |
416 else: |
404 logging.info(msgtxt) |
417 nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \ |
405 else: |
418 "', '" + envval + "', " + crit + ", 'None') == 1)"] |
406 for row in query.getResponse(): |
419 |
407 profpath = row['file'] |
420 if len(nvpairs) > 0: |
408 profname = row['name'] |
421 q_str += " AND ".join(nvpairs) |
409 if profname is None: # should not happen |
422 |
410 profname = 'unnamed' |
423 # issue database query |
411 try: |
424 logging.info("Profile query: " + q_str) |
412 if profpath is None: |
425 query = AIdb.DBrequest(q_str) |
413 msgtxt = "Database record error - profile path is empty." |
426 aisql.getQueue().put(query) |
|
427 query.waitAns() |
|
428 if query.getResponse() is None or len(query.getResponse()) == 0: |
|
429 msgtxt = _("No profiles found.") |
|
430 client_msg += [msgtxt] |
|
431 logging.info(msgtxt) |
|
432 else: |
|
433 for row in query.getResponse(): |
|
434 profpath = row['file'] |
|
435 profname = row['name'] |
|
436 if profname is None: # should not happen |
|
437 profname = 'unnamed' |
|
438 try: |
|
439 if profpath is None: |
|
440 msgtxt = "Database record error - profile path is empty." |
|
441 client_msg += [msgtxt] |
|
442 logging.error(msgtxt) |
|
443 continue |
|
444 msgtxt = _('Processing profile %s') % profname |
|
445 client_msg += [msgtxt] |
|
446 logging.info(msgtxt) |
|
447 with open(profpath, 'r') as pfp: |
|
448 raw_profile = pfp.read() |
|
449 # do any template variable replacement {{AI_xxx}} |
|
450 tmpl_profile = sc.perform_templating(raw_profile, |
|
451 validate_only=False) |
|
452 # precautionary validation or profile, logging only |
|
453 sc.validate_profile_string(tmpl_profile, service_info[2], |
|
454 dtd_validation=True, |
|
455 warn_if_dtd_missing=True) |
|
456 except IOError, err: |
|
457 msgtxt = _("Error: I/O error: ") + str(err) |
414 client_msg += [msgtxt] |
458 client_msg += [msgtxt] |
415 logging.error(msgtxt) |
459 logging.error(msgtxt) |
416 continue |
460 continue |
417 msgtxt = _('Processing profile %s') % profname |
461 except OSError: |
|
462 msgtxt = _("Error: OS error on profile ") + profpath |
|
463 client_msg += [msgtxt] |
|
464 logging.error(msgtxt) |
|
465 continue |
|
466 except KeyError: |
|
467 msgtxt = _('Error: could not find criteria to substitute in ' |
|
468 'template: ') + profpath |
|
469 client_msg += [msgtxt] |
|
470 logging.error(msgtxt) |
|
471 logging.error('Profile with template substitution error:' + |
|
472 raw_profile) |
|
473 continue |
|
474 except lxml.etree.XMLSyntaxError, err: |
|
475 # log validation error and proceed |
|
476 msgtxt = _( |
|
477 'Warning: syntax error found in profile: ') \ |
|
478 + profpath |
|
479 client_msg += [msgtxt] |
|
480 logging.error(msgtxt) |
|
481 for error in err.error_log: |
|
482 msgtxt = _('Error: ') + error.message |
|
483 client_msg += [msgtxt] |
|
484 logging.error(msgtxt) |
|
485 logging.info([_('Profile failing validation: ') + |
|
486 lxml.etree.tostring(root)]) |
|
487 # build MIME message and attach to outer MIME message |
|
488 msg = MIMEText(tmpl_profile, 'xml') |
|
489 # indicate in header that this is an attachment |
|
490 msg.add_header('Content-Disposition', 'attachment', |
|
491 filename=profname) |
|
492 # attach this profile to the manifest and any other profiles |
|
493 outermime.attach(msg) |
|
494 msgtxt = _('Parsed and loaded profile: ') + profname |
418 client_msg += [msgtxt] |
495 client_msg += [msgtxt] |
419 logging.info(msgtxt) |
496 logging.info(msgtxt) |
420 with open(profpath, 'r') as pfp: |
|
421 raw_profile = pfp.read() |
|
422 # do any template variable replacement {{AI_xxx}} |
|
423 tmpl_profile = sc.perform_templating(raw_profile, |
|
424 validate_only=False) |
|
425 # precautionary validation or profile, logging only |
|
426 sc.validate_profile_string(tmpl_profile, service_info[2], |
|
427 dtd_validation=True, |
|
428 warn_if_dtd_missing=True) |
|
429 except IOError, err: |
|
430 msgtxt = _("Error: I/O error: ") + str(err) |
|
431 client_msg += [msgtxt] |
|
432 logging.error(msgtxt) |
|
433 continue |
|
434 except OSError: |
|
435 msgtxt = _("Error: OS error on profile ") + profpath |
|
436 client_msg += [msgtxt] |
|
437 logging.error(msgtxt) |
|
438 continue |
|
439 except KeyError: |
|
440 msgtxt = _('Error: could not find criteria to substitute in ' |
|
441 'template: ') + profpath |
|
442 client_msg += [msgtxt] |
|
443 logging.error(msgtxt) |
|
444 logging.error('Profile with template substitution error:' + |
|
445 raw_profile) |
|
446 continue |
|
447 except lxml.etree.XMLSyntaxError, err: |
|
448 # log validation error and proceed |
|
449 msgtxt = _( |
|
450 'Warning: syntax error found in profile: ') \ |
|
451 + profpath |
|
452 client_msg += [msgtxt] |
|
453 logging.error(msgtxt) |
|
454 for error in err.error_log: |
|
455 msgtxt = _('Error: ') + error.message |
|
456 client_msg += [msgtxt] |
|
457 logging.error(msgtxt) |
|
458 logging.info([_('Profile failing validation: ') + |
|
459 lxml.etree.tostring(root)]) |
|
460 # build MIME message and attach to outer MIME message |
|
461 msg = MIMEText(tmpl_profile, 'xml') |
|
462 # indicate in header that this is an attachment |
|
463 msg.add_header('Content-Disposition', 'attachment', |
|
464 filename=profname) |
|
465 # attach this profile to the manifest and any other profiles |
|
466 outermime.attach(msg) |
|
467 msgtxt = _('Parsed and loaded profile: ') + profname |
|
468 client_msg += [msgtxt] |
|
469 logging.info(msgtxt) |
|
470 |
497 |
471 # any profiles and AI manifest have been attached to MIME message |
498 # any profiles and AI manifest have been attached to MIME message |
472 # specially format list of messages for display on AI client console |
499 # specially format list of messages for display on AI client console |
473 if client_msg: |
500 if client_msg: |
474 outtxt = '' |
501 outtxt = '' |