def test_mem_is_range(self):
     '''Verify mem returns true for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "mem")
     self.assertTrue(is_range_criteria)
Exemple #2
0
def set_criteria(criteria, iname, dbn, table, append=False):
    """
    Set a manifest's record in the criteria database with the
    criteria provided.
    If append is True -- append ones that aren't already set for
    the manifest, and replace ones that are.
    if append is False -- completely remove all criteria already
    set for the manifest, and use only the criteria specified.
    """

    # Build a list of criteria nvpairs to update
    nvpairs = list()

    # we need to fill in the criteria or NULLs for each criteria the database
    # supports (so iterate over each criteria)
    for crit in AIdb.getCriteria(dbn.getQueue(),
                                 table=table,
                                 onlyUsed=False,
                                 strip=True):

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(dbn.getQueue(), crit)

        # Get the value from the manifest
        values = criteria[crit]

        # the criteria manifest didn't specify this criteria
        if values is None:
            # If we not appending criteria, then we must write in NULLs
            # for this criteria since we're removing all criteria not
            # specified.
            if not append:
                # if the criteria we're processing is a range criteria, fill in
                # NULL for two columns, MINcrit and MAXcrit
                if is_range_crit:
                    nvpairs.append("MIN" + crit + "=NULL")
                    nvpairs.append("MAX" + crit + "=NULL")
                # this is a single value
                else:
                    nvpairs.append(crit + "=NULL")

        # Else if this is a value criteria (not a range), insert the
        # value as a space-separated list of values in case a list of
        # values have been given.
        elif not is_range_crit:
            nvstr = crit + "='" + AIdb.sanitizeSQL(" ".join(values)) + "'"
            nvpairs.append(nvstr)

        # Else the values are a list this is a range criteria
        else:
            # Set the MIN column for this range criteria
            nvpairs.append("MIN" + crit + "=" +
                           AIdb.format_value(crit, values[0]))

            # Set the MAX column for this range criteria
            nvpairs.append("MAX" + crit + "=" +
                           AIdb.format_value(crit, values[1]))

    query = "UPDATE " + table + " SET " + ",".join(nvpairs) + \
            " WHERE name='" + iname + "'"

    # update the DB
    query = AIdb.DBrequest(query, commit=True)
    dbn.getQueue().put(query)
    query.waitAns()
    # in case there's an error call the response function (which
    # will print the error)
    query.getResponse()
 def test_arch_not_range(self):
     '''Verify arch returns false for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "arch")
     self.assertFalse(is_range_criteria)
 def test_ipv4_is_range(self):
     '''Verify ipv4 returns true for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "ipv4")
     self.assertTrue(is_range_criteria)
def sql_values_from_criteria(criteria, queue, table, gbl=False):
    ''' Given a criteria dictionary, for the indicated DB table
    and queue, return a tuple composed of lists whose elements can be used
    to construct SQLite clauses.  If gbl is true, build a clause that
    will affect all database records if criteria is missing - a global effect.
    Args:
        criteria - criteria dictionary
        queue - database queue
        table - database table
        gbl - if True, global
    Returns: a tuple for SQLite clauses respectively: WHERE, INTO, VALUES
    '''
    where = list()  # for WHERE clause
    intol = list()  # for INTO clause
    vals = list()  # for VALUES clause
    for crit in AIdb.getCriteria(queue, table, onlyUsed=False, strip=True):

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(queue, crit, table)

        # Get the value from the manifest
        values = criteria[crit]
        # the critera manifest didn't specify this criteria
        if values is None:
            # if the criteria we're processing is a range criteria, fill in
            # NULL for two columns, MINcrit and MAXcrit
            vals += ["NULL"]
            if is_range_crit:
                where += ["MIN" + crit + " IS NULL"]
                where += ["MAX" + crit + " IS NULL"]
                intol += ["MIN" + crit]
                intol += ["MAX" + crit]
                vals += ["NULL"]
            # this is a single value
            else:
                where += [crit + " IS NULL"]
                intol += [crit]
        # This is a value criteria (not a range).  'values' is a list
        # with one or more items.
        elif not is_range_crit:
            intol += [crit]
            val = AIdb.format_value(crit, " ".join(values))
            where += [crit + "=" + val]
            vals += [val]
        # Else this is a range criteria.  'values' is a two-item list
        else:
            # Set the MIN column for this range criteria
            if values[0] == 'unbounded':
                if not gbl:
                    where += ["MIN" + crit + " IS NULL"]
                    intol += ["MIN" + crit]
                    vals += ['NULL']
            else:
                intol += ["MIN" + crit]
                if crit == 'mac':
                    val = AIdb.format_value(crit,
                            verifyXML.checkMAC(values[0])).upper()
                    where += ["HEX(MIN" + crit + ")<=HEX(" + val + ")"]
                else:
                    val = AIdb.format_value(crit, values[0]).lower()
                    where += ["MIN" + crit + "<=" + val]
                vals += [val]
            # Set the MAX column for this range criteria
            if values[1] == 'unbounded':
                if not gbl:
                    where += ["MAX" + crit + " IS NULL"]
                    intol += ["MAX" + crit]
                    vals += ['NULL']
            else:
                intol += ["MAX" + crit]
                if crit == 'mac':
                    val = AIdb.format_value(crit,
                            verifyXML.checkMAC(values[1])).upper()
                    where += ["HEX(MAX" + crit + ")>=HEX(" + val + ")"]
                else:
                    val = AIdb.format_value(crit, values[1]).lower()
                    where += ["MAX" + crit + ">=" + val]
                vals += [val]
    return where, intol, vals
Exemple #6
0
def find_colliding_criteria(criteria, db, exclude_manifests=None):
    """
    Returns: A dictionary of colliding criteria with keys being manifest name
             and instance tuples and values being the DB column names which
             collided
    Args:    criteria - Criteria object holding the criteria that is to be
                        added/set for a manifest.
             db - AI_database object for the install service.
             exclude_manifests -A list of manifest names from DB to ignore.
                                This arg is passed in when we're calling this
                                function to find criteria collisions for an
                                already published manifest.
    Raises:  SystemExit if: criteria is not found in database
                            value is not valid for type (integer and
                            hexadecimal checks)
                            range is improper
    """
    class Fields(object):
        """
        Define convenience indexes
        """
        # manifest name is row index 0
        MANNAME = 0
        # manifest instance is row index 1
        MANINST = 1
        # criteria is row index 2 (when a single valued criteria)
        CRIT = 2
        # minimum criteria is row index 2 (when a range valued criteria)
        MINCRIT = 2
        # maximum criteria is row index 3 (when a range valued criteria)
        MAXCRIT = 3

    # collisions is a dictionary to hold keys of the form (manifest name,
    # instance) which will point to a comma-separated string of colliding
    # criteria
    collisions = dict()

    # verify each range criteria in the manifest is well formed and collect
    # collisions with database entries
    for crit in criteria:
        # gather this criteria's values from the manifest
        man_criterion = criteria[crit]

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(db.getQueue(), crit,
                                             AIdb.MANIFESTS_TABLE)

        # Process "value" criteria here (check if the criteria exists in
        # DB, and then find collisions)
        if not is_range_crit:
            # only check criteria in use in the DB
            if crit not in AIdb.getCriteria(db.getQueue(),
                                            onlyUsed=False,
                                            strip=False):
                raise SystemExit(
                    _("Error:\tCriteria %s is not a " + "valid criteria!") %
                    crit)

            # get all values in the database for this criteria (and
            # manifest/instance pairs for each value)
            db_criteria = AIdb.getSpecificCriteria(
                db.getQueue(),
                crit,
                provideManNameAndInstance=True,
                excludeManifests=exclude_manifests)

            # will iterate over a list of the form [manName, manInst, crit,
            # None]
            for row in db_criteria:
                # check if a value in the list of values to be added is equal
                # to a value in the list of values for this criteria for this
                # row
                for value in man_criterion:
                    if AIdb.is_in_list(crit, value, str(row[Fields.CRIT]),
                                       None):
                        # record manifest name, instance and criteria name
                        try:
                            collisions[row[Fields.MANNAME],
                                       row[Fields.MANINST]] += crit + ","
                        except KeyError:
                            collisions[row[Fields.MANNAME],
                                       row[Fields.MANINST]] = crit + ","

        # This is a range criteria.  (Check that ranges are valid, that
        # "unbounded" gets set to 0/+inf, ensure the criteria exists
        # in the DB, then look for collisions.)
        else:
            # Clean-up NULL's and change "unbounded"s to 0 and
            # really large numbers in case this Python does
            # not support IEEE754.  Note "unbounded"s are already
            # converted to lower case during manifest processing.
            if man_criterion[0] == "unbounded":
                man_criterion[0] = "0"
            if man_criterion[1] == "unbounded":
                man_criterion[1] = INFINITY
            if crit == "mac":
                # convert hex mac address (w/o colons) to a number
                try:
                    man_criterion[0] = long(str(man_criterion[0]).upper(), 16)
                    man_criterion[1] = long(str(man_criterion[1]).upper(), 16)
                except ValueError:
                    raise SystemExit(
                        _("Error:\tCriteria %s "
                          "is not a valid hexadecimal value") % crit)
            else:
                # this is a decimal value
                try:
                    man_criterion = [
                        long(str(man_criterion[0]).upper()),
                        long(str(man_criterion[1]).upper())
                    ]
                except ValueError:
                    raise SystemExit(
                        _("Error:\tCriteria %s "
                          "is not a valid integer value") % crit)

            # Check for a properly ordered range (with unbounded being 0 or
            # Inf.) but ensure both are not unbounded.
            # Check for:
            #       a range of zero to inf -- not a valid range
            #  and
            #       min > max -- range order reversed
            #
            if (man_criterion[0] == 0 and man_criterion[1] == long(INFINITY)):
                raise SystemExit(
                    _("Error:\tCriteria %s is not a valid range, "
                      "MIN and MAX unbounded.") % crit)

            if ((man_criterion[0] != 0 and man_criterion[1] != long(INFINITY))
                    and (long(man_criterion[0]) > long(man_criterion[1]))):
                raise SystemExit(
                    _("Error:\tCriteria %s is not a valid range, "
                      "MIN > MAX.") % crit)
            # check to see that this criteria exists in the database columns
            man_crit = AIdb.getCriteria(db.getQueue(),
                                        onlyUsed=False,
                                        strip=False)
            if 'MIN' + crit not in man_crit and 'MAX' + crit not in man_crit:
                raise SystemExit(
                    _("Error:\tCriteria %s is not a "
                      "valid criteria!") % crit)
            db_criteria = AIdb.getSpecificCriteria(
                db.getQueue(),
                'MIN' + crit,
                'MAX' + crit,
                provideManNameAndInstance=True,
                excludeManifests=exclude_manifests)

            # will iterate over a list of the form [manName, manInst, mincrit,
            # maxcrit]
            for row in db_criteria:
                # arbitrarily large number in case this Python does
                # not support IEEE754
                db_criterion = ["0", INFINITY]

                # now populate in valid database values (i.e. non-NULL values)
                if row[Fields.MINCRIT]:
                    db_criterion[0] = row[Fields.MINCRIT]
                if row[Fields.MAXCRIT]:
                    db_criterion[1] = row[Fields.MAXCRIT]
                if crit == "mac":
                    # use a hexadecimal conversion
                    db_criterion = [
                        long(str(db_criterion[0]), 16),
                        long(str(db_criterion[1]), 16)
                    ]
                else:
                    # these are decimal numbers
                    db_criterion = [
                        long(str(db_criterion[0])),
                        long(str(db_criterion[1]))
                    ]

                # these three criteria can determine if there's a range overlap
                if ((man_criterion[1] >= db_criterion[0]
                     and db_criterion[1] >= man_criterion[0])
                        or man_criterion[0] == db_criterion[1]):
                    # range overlap so record the collision
                    try:
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MIN" + crit + ","
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MAX" + crit + ","
                    except KeyError:
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] = "MIN" + crit + ","
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MAX" + crit + ","
    return collisions
 def test_arch_not_range(self):
     '''Verify arch returns false for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "arch")
     self.assertFalse(is_range_criteria)
Exemple #8
0
def sql_values_from_criteria(criteria, queue, table, gbl=False):
    ''' Given a criteria dictionary, for the indicated DB table
    and queue, return a tuple composed of lists whose elements can be used
    to construct SQLite clauses.  If gbl is true, build a clause that
    will affect all database records if criteria is missing - a global effect.
    Args:
        criteria - criteria dictionary
        queue - database queue
        table - database table
        gbl - if True, global
    Returns: a tuple for SQLite clauses respectively: WHERE, INTO, VALUES
    '''
    where = list()  # for WHERE clause
    intol = list()  # for INTO clause
    vals = list()  # for VALUES clause
    for crit in AIdb.getCriteria(queue, table, onlyUsed=False, strip=True):

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(queue, crit, table)

        # Get the value from the manifest
        values = criteria[crit]
        # the critera manifest didn't specify this criteria
        if values is None:
            # if the criteria we're processing is a range criteria, fill in
            # NULL for two columns, MINcrit and MAXcrit
            vals += ["NULL"]
            if is_range_crit:
                where += ["MIN" + crit + " IS NULL"]
                where += ["MAX" + crit + " IS NULL"]
                intol += ["MIN" + crit]
                intol += ["MAX" + crit]
                vals += ["NULL"]
            # this is a single value
            else:
                where += [crit + " IS NULL"]
                intol += [crit]
        # This is a value criteria (not a range).  'values' is a list
        # with one or more items.
        elif not is_range_crit:
            intol += [crit]
            val = AIdb.format_value(crit, " ".join(values))
            where += [crit + "=" + val]
            vals += [val]
        # Else this is a range criteria.  'values' is a two-item list
        else:
            # Set the MIN column for this range criteria
            if values[0] == 'unbounded':
                if not gbl:
                    where += ["MIN" + crit + " IS NULL"]
                    intol += ["MIN" + crit]
                    vals += ['NULL']
            else:
                intol += ["MIN" + crit]
                if crit == 'mac':
                    val = AIdb.format_value(crit, verifyXML.checkMAC(
                        values[0])).upper()
                    where += ["HEX(MIN" + crit + ")<=HEX(" + val + ")"]
                else:
                    val = AIdb.format_value(crit, values[0]).lower()
                    where += ["MIN" + crit + "<=" + val]
                vals += [val]
            # Set the MAX column for this range criteria
            if values[1] == 'unbounded':
                if not gbl:
                    where += ["MAX" + crit + " IS NULL"]
                    intol += ["MAX" + crit]
                    vals += ['NULL']
            else:
                intol += ["MAX" + crit]
                if crit == 'mac':
                    val = AIdb.format_value(crit, verifyXML.checkMAC(
                        values[1])).upper()
                    where += ["HEX(MAX" + crit + ")>=HEX(" + val + ")"]
                else:
                    val = AIdb.format_value(crit, values[1]).lower()
                    where += ["MAX" + crit + ">=" + val]
                vals += [val]
    return where, intol, vals
def verifyCriteriaDict(schema, criteria_dict, db, table=AIdb.MANIFESTS_TABLE):
    """
    Used for verifying and loading criteria from a dictionary of criteria.
    Args:       schema - path to schema file for criteria manifest.
                criteria_dict - dictionary of criteria to verify, in the form
                                of { criteria: value, criteria: value, ... }
                db - database object for install service
                table - database table, distinguishing manifests from profiles
    Raises IOError:
               * if the schema does not open
           ValueError:
                * if the criteria_dict dictionary is empty
                * if the XML is invalid according to the schema
           AssertionError:
                * if a value in the dictionary is empty
    Returns:    A valid XML DOM of the criteria and all MAC and IPV4 values
                are formatted according to verifyXML.prepValuesAndRanges().
    """
    schema = open(schema, 'r')

    if not criteria_dict:
        raise ValueError("Error:\tCriteria dictionary empty: %s\n" %
                         criteria_dict)

    root = lxml.etree.Element("ai_criteria_manifest")

    for name, value_or_range in criteria_dict.iteritems():

        if value_or_range is None:
            raise AssertionError(
                _("Error: Missing value for criteria "
                  "'%s'") % name)

        crit = lxml.etree.SubElement(root, "ai_criteria")
        crit.set("name", name)

        # If criteria is a range, split on "-" and add to
        # XML DOM as a range element.
        if AIdb.isRangeCriteria(db.getQueue(), name, table):
            # Split on "-"
            range_value = value_or_range.split('-', 1)

            # If there was only a single value, means user specified
            # this range criteria as a single value, add it as a single
            # value
            if len(range_value) == 1:
                value_elem = lxml.etree.SubElement(crit, "value")
                value_elem.text = value_or_range
            else:
                range_elem = lxml.etree.SubElement(crit, "range")
                range_elem.text = " ".join(range_value)
        else:
            value_elem = lxml.etree.SubElement(crit, "value")
            value_elem.text = value_or_range

    # Verify the generated criteria DOM
    root, errors = verifyXML.verifyRelaxNGManifest(
        schema, StringIO.StringIO(lxml.etree.tostring(root)))
    if errors:
        raise ValueError(
            _("Error: Criteria failed validation:\n\t%s") % errors.message)
    try:
        verifyXML.prepValuesAndRanges(root, db, table)
    except ValueError, err:
        raise ValueError(_("Error:\tCriteria error: %s") % err)
def find_colliding_criteria(criteria, db, exclude_manifests=None):
    """
    Returns: A dictionary of colliding criteria with keys being manifest name
             and instance tuples and values being the DB column names which
             collided
    Args:    criteria - Criteria object holding the criteria that is to be
                        added/set for a manifest.
             db - AI_database object for the install service.
             exclude_manifests -A list of manifest names from DB to ignore.
                                This arg is passed in when we're calling this
                                function to find criteria collisions for an
                                already published manifest.
    Raises:  SystemExit if: criteria is not found in database
                            value is not valid for type (integer and
                            hexadecimal checks)
                            range is improper
    """
    class Fields(object):
        """
        Define convenience indexes
        """
        # manifest name is row index 0
        MANNAME = 0
        # manifest instance is row index 1
        MANINST = 1
        # criteria is row index 2 (when a single valued criteria)
        CRIT = 2
        # minimum criteria is row index 2 (when a range valued criteria)
        MINCRIT = 2
        # maximum criteria is row index 3 (when a range valued criteria)
        MAXCRIT = 3

    # collisions is a dictionary to hold keys of the form (manifest name,
    # instance) which will point to a comma-separated string of colliding
    # criteria
    collisions = dict()

    # verify each range criteria in the manifest is well formed and collect
    # collisions with database entries
    for crit in criteria:
        # gather this criteria's values from the manifest
        man_criterion = criteria[crit]

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(db.getQueue(), crit,
            AIdb.MANIFESTS_TABLE)

        # Process "value" criteria here (check if the criteria exists in
        # DB, and then find collisions)
        if not is_range_crit:
            # only check criteria in use in the DB
            if crit not in AIdb.getCriteria(db.getQueue(),
                                            onlyUsed=False, strip=False):
                raise SystemExit(_("Error:\tCriteria %s is not a " +
                                   "valid criteria!") % crit)

            # get all values in the database for this criteria (and
            # manifest/instance pairs for each value)
            db_criteria = AIdb.getSpecificCriteria(
                db.getQueue(), crit,
                provideManNameAndInstance=True,
                excludeManifests=exclude_manifests)

            # will iterate over a list of the form [manName, manInst, crit,
            # None]
            for row in db_criteria:
                # check if a value in the list of values to be added is equal
                # to a value in the list of values for this criteria for this
                # row
                for value in man_criterion:
                    if AIdb.is_in_list(crit, value, str(row[Fields.CRIT]),
                        None):
                        # record manifest name, instance and criteria name
                        try:
                            collisions[row[Fields.MANNAME],
                                       row[Fields.MANINST]] += crit + ","
                        except KeyError:
                            collisions[row[Fields.MANNAME],
                                       row[Fields.MANINST]] = crit + ","

        # This is a range criteria.  (Check that ranges are valid, that
        # "unbounded" gets set to 0/+inf, ensure the criteria exists
        # in the DB, then look for collisions.)
        else:
            # Clean-up NULL's and change "unbounded"s to 0 and
            # really large numbers in case this Python does
            # not support IEEE754.  Note "unbounded"s are already
            # converted to lower case during manifest processing.
            if man_criterion[0] == "unbounded":
                man_criterion[0] = "0"
            if man_criterion[1] == "unbounded":
                man_criterion[1] = INFINITY
            if crit == "mac":
                # convert hex mac address (w/o colons) to a number
                try:
                    man_criterion[0] = long(str(man_criterion[0]).upper(), 16)
                    man_criterion[1] = long(str(man_criterion[1]).upper(), 16)
                except ValueError:
                    raise SystemExit(_("Error:\tCriteria %s "
                                       "is not a valid hexadecimal value") %
                                     crit)
            else:
                # this is a decimal value
                try:
                    man_criterion = [long(str(man_criterion[0]).upper()),
                                     long(str(man_criterion[1]).upper())]
                except ValueError:
                    raise SystemExit(_("Error:\tCriteria %s "
                                       "is not a valid integer value") % crit)

            # Check for a properly ordered range (with unbounded being 0 or
            # Inf.) but ensure both are not unbounded.
            # Check for:
            #       a range of zero to inf -- not a valid range
            #  and
            #       min > max -- range order reversed
            #
            if (man_criterion[0] == 0 and man_criterion[1] == long(INFINITY)):
                raise SystemExit(_("Error:\tCriteria %s is not a valid range, "
                                   "MIN and MAX unbounded.") % crit)

            if ((man_criterion[0] != 0 and
                 man_criterion[1] != long(INFINITY)) and
                (long(man_criterion[0]) > long(man_criterion[1]))):
                raise SystemExit(_("Error:\tCriteria %s is not a valid range, "
                                   "MIN > MAX.") % crit)
            # check to see that this criteria exists in the database columns
            man_crit = AIdb.getCriteria(db.getQueue(), onlyUsed=False,
                                        strip=False)
            if 'MIN' + crit not in man_crit and 'MAX' + crit not in man_crit:
                raise SystemExit(_("Error:\tCriteria %s is not a "
                                   "valid criteria!") % crit)
            db_criteria = AIdb.getSpecificCriteria(
                db.getQueue(), 'MIN' + crit, 'MAX' + crit,
                provideManNameAndInstance=True,
                excludeManifests=exclude_manifests)

            # will iterate over a list of the form [manName, manInst, mincrit,
            # maxcrit]
            for row in db_criteria:
                # arbitrarily large number in case this Python does
                # not support IEEE754
                db_criterion = ["0", INFINITY]

                # now populate in valid database values (i.e. non-NULL values)
                if row[Fields.MINCRIT]:
                    db_criterion[0] = row[Fields.MINCRIT]
                if row[Fields.MAXCRIT]:
                    db_criterion[1] = row[Fields.MAXCRIT]
                if crit == "mac":
                    # use a hexadecimal conversion
                    db_criterion = [long(str(db_criterion[0]), 16),
                                    long(str(db_criterion[1]), 16)]
                else:
                    # these are decimal numbers
                    db_criterion = [long(str(db_criterion[0])),
                                    long(str(db_criterion[1]))]

                # these three criteria can determine if there's a range overlap
                if((man_criterion[1] >= db_criterion[0] and
                   db_criterion[1] >= man_criterion[0]) or
                   man_criterion[0] == db_criterion[1]):
                    # range overlap so record the collision
                    try:
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MIN" + crit + ","
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MAX" + crit + ","
                    except KeyError:
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] = "MIN" + crit + ","
                        collisions[row[Fields.MANNAME],
                                   row[Fields.MANINST]] += "MAX" + crit + ","
    return collisions
def set_criteria(criteria, iname, dbn, table, append=False):
    """
    Set a manifest's record in the criteria database with the
    criteria provided.
    If append is True -- append ones that aren't already set for
    the manifest, and replace ones that are.
    if append is False -- completely remove all criteria already
    set for the manifest, and use only the criteria specified.
    """

    # Build a list of criteria nvpairs to update
    nvpairs = list()

    # we need to fill in the criteria or NULLs for each criteria the database
    # supports (so iterate over each criteria)
    for crit in AIdb.getCriteria(dbn.getQueue(), table=table, onlyUsed=False,
                                 strip=True):

        # Determine if this crit is a range criteria or not.
        is_range_crit = AIdb.isRangeCriteria(dbn.getQueue(), crit)

        # Get the value from the manifest
        values = criteria[crit]

        # the criteria manifest didn't specify this criteria
        if values is None:
            # If we not appending criteria, then we must write in NULLs
            # for this criteria since we're removing all criteria not
            # specified.
            if not append:
                # if the criteria we're processing is a range criteria, fill in
                # NULL for two columns, MINcrit and MAXcrit
                if is_range_crit:
                    nvpairs.append("MIN" + crit + "=NULL")
                    nvpairs.append("MAX" + crit + "=NULL")
                # this is a single value
                else:
                    nvpairs.append(crit + "=NULL")

        # Else if this is a value criteria (not a range), insert the
        # value as a space-separated list of values in case a list of
        # values have been given. 
        elif not is_range_crit:
            nvstr = crit + "='" + AIdb.sanitizeSQL(" ".join(values)) + "'"
            nvpairs.append(nvstr)

        # Else the values are a list this is a range criteria
        else:
            # Set the MIN column for this range criteria
            nvpairs.append("MIN" + crit + "=" +
                           AIdb.format_value(crit, values[0]))

            # Set the MAX column for this range criteria
            nvpairs.append("MAX" + crit + "=" +
                           AIdb.format_value(crit, values[1]))

    query = "UPDATE " + table + " SET " + ",".join(nvpairs) + \
            " WHERE name='" + iname + "'"

    # update the DB
    query = AIdb.DBrequest(query, commit=True)
    dbn.getQueue().put(query)
    query.waitAns()
    # in case there's an error call the response function (which
    # will print the error)
    query.getResponse()
def send_manifest(form_data, port=0, servicename=None,
        protocolversion=COMPATIBILITY_VERSION, no_default=False):
    '''Replies to the client with matching service for a service.
    
    Args
        form_data   - the postData passed in from the client request
        port        - the port of the old client
        servicename - the name of the service being used
        protocolversion - the version of the AI service RE: handshake
        no_default  - boolean flag to signify whether or not we should hand
                      back the default manifest and profiles if one cannot
                      be matched based on the client criteria.

    Returns
        None
    
    Raises
        None
    
    '''
    # figure out the appropriate path for the AI database,
    # and get service name if necessary.
    # currently service information is stored in a port directory.
    # When the cherrypy webserver new service directories should be
    # separated via service-name only.  Old services will still use
    # port numbers as the separation mechanism.
    path = None
    found_servicename = None
    service = None
    port = str(port)
    
    if servicename:
        service = AIService(servicename)
        path = service.database_path
    else:
        for name in config.get_all_service_names():
            if config.get_service_port(name) == port:
                found_servicename = name
                service = AIService(name)
                path = service.database_path
                break
    
    # Check to insure that a valid path was found
    if not path or not os.path.exists(path):
        print 'Content-Type: text/html'     # HTML is following
        print                               # blank line, end of headers
        if servicename:
            print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.'
        else:
            print '<pre><b>Error</b>:unable to find<i>', port + '</i>.'
        print 'Available services are:<p><ol><i>'
        hostname = socket.gethostname()
        for name in config.get_all_service_names():
            port = config.get_service_port(name)
            sys.stdout.write('<a href="http://%s:%d/cgi-bin/'
                   'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' %
                   (hostname, port, VERSION, name, name))
        print '</i></ol>Please select a service from the above list.'
        return

    if found_servicename:
        servicename = found_servicename

    # load to the AI database
    aisql = AIdb.DB(path)
    aisql.verifyDBStructure()

    # convert the form data into a criteria dictionary
    criteria = dict()
    orig_data = form_data
    while form_data:
        try:
            [key_value, form_data] = form_data.split(';', 1)
        except (ValueError, NameError, TypeError, KeyError):
            key_value = form_data
            form_data = ''
        try:
            [key, value] = key_value.split('=')
            criteria[key] = value
        except (ValueError, NameError, TypeError, KeyError):
            criteria = dict()

    # Generate templating dictionary from criteria
    template_dict = dict()
    for crit in criteria:
        template_dict["AI_" + crit.upper()] = \
                AIdb.formatValue(crit, criteria[crit], units=False)
            
    # find the appropriate manifest
    try:
        manifest = AIdb.findManifest(criteria, aisql)
    except StandardError as err:
        print 'Content-Type: text/html'     # HTML is following
        print                               # blank line, end of headers
        print '<pre><b>Error</b>:findManifest criteria<br>'
        print err, '<br>'
        print '<ol>servicename =', servicename
        print 'port        =', port
        print 'path        =', path
        print 'form_data   =', orig_data
        print 'criteria    =', criteria
        print 'servicename found by port =', found_servicename, '</ol>'
        print '</pre>'
        return

    # check if findManifest() returned a number equal to 0
    # (means we got no manifests back -- thus we serve the default if desired)
    if manifest is None and not no_default:
        manifest = service.get_default_manifest()

    # if we have a manifest to return, prepare its return
    if manifest is not None:
        try:
            # construct the fully qualified filename
            filename = os.path.abspath(os.path.join(service.manifest_dir,
                                                    manifest))
            # open and read the manifest
            with open(filename, 'rb') as mfp:
                manifest_str = mfp.read()
            # maintain compability with older AI client
            if servicename is None or \
                    float(protocolversion) < float(PROFILES_VERSION):
                content_type = mimetypes.types_map.get('.xml', 'text/plain')
                print 'Content-Length:', len(manifest_str) # Length of the file
                print 'Content-Type:', content_type        # XML is following
                print                              # blank line, end of headers
                print manifest_str
                logging.info('Manifest sent from %s.' % filename)
                return

        except OSError as err:
            print 'Content-Type: text/html'     # HTML is following
            print                               # blank line, end of headers
            print '<pre>'
            # report the internal error to error_log and requesting client
            sys.stderr.write(_('error:manifest (%s) %s\n') % \
                            (str(manifest), err))
            sys.stdout.write(_('error:manifest (%s) %s\n') % \
                            (str(manifest), err))
            print '</pre>'
            return

    # get AI service image path
    service = AIService(servicename)
    image_dir = service.image.path
    # construct object to contain MIME multipart message
    outermime = MIMEMultipart()
    client_msg = list()  # accumulate message output for AI client

    # If we have a manifest, attach it to the return message
    if manifest is not None:
        # add manifest as attachment
        msg = MIMEText(manifest_str, 'xml')
        # indicate manifest using special name
        msg.add_header('Content-Disposition', 'attachment',
                      filename=sc.AI_MANIFEST_ATTACHMENT_NAME)
        outermime.attach(msg)  # add manifest as an attachment

    # search for any profiles matching client criteria
    # formulate database query to profiles table
    q_str = "SELECT DISTINCT name, file FROM " + \
        AIdb.PROFILES_TABLE + " WHERE "
    nvpairs = list()  # accumulate criteria values from post-data
    # for all AI client criteria
    for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE,
                                 onlyUsed=False):
        if crit not in criteria:
            msgtxt = _("Warning: client criteria \"%s\" not provided in "
                       "request.  Setting value to NULL for profile lookup.") \
                       % crit
            client_msg += [msgtxt]
            logging.warn(msgtxt)
            # fetch only global profiles destined for all clients
            if AIdb.isRangeCriteria(aisql.getQueue(), crit,
                                    AIdb.PROFILES_TABLE):
                nvpairs += ["MIN" + crit + " IS NULL"]
                nvpairs += ["MAX" + crit + " IS NULL"]
            else:
                nvpairs += [crit + " IS NULL"]
            continue

        # prepare criteria value to add to query
        envval = AIdb.sanitizeSQL(criteria[crit])
        if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE):
            # If no default profiles are requested, then we mustn't allow
            # this criteria to be NULL.  It must match the client's given
            # value for this criteria.
            if no_default:
                if crit == "mac":
                    nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \
                        "'))"]

                    nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \
                        "'))"]
                else:
                    nvpairs += ["(MIN" + crit + "<='" + envval + "')"]
                    nvpairs += ["(MAX" + crit + ">='" + envval + "')"]
            else:
                if crit == "mac":
                    nvpairs += ["(MIN" + crit + " IS NULL OR "
                        "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))"]
                    nvpairs += ["(MAX" + crit + " IS NULL OR HEX(MAX" +
                        crit + ")>=HEX(X'" + envval + "'))"]
                else:
                    nvpairs += ["(MIN" + crit + " IS NULL OR MIN" +
                        crit + "<='" + envval + "')"]
                    nvpairs += ["(MAX" + crit + " IS NULL OR MAX" +
                        crit + ">='" + envval + "')"]
        else:
            # If no default profiles are requested, then we mustn't allow
            # this criteria to be NULL.  It must match the client's given
            # value for this criteria.
            #
            # Also, since this is a non-range criteria, the value stored
            # in the DB may be a whitespace separated list of single
            # values.  We use a special user-defined function in the
            # determine if the given criteria is in that textual list.
            if no_default:
                nvpairs += ["(is_in_list('" + crit + "', '" + envval + \
                    "', " + crit + ", 'None') == 1)"]
            else:
                nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \
                    "', '" + envval + "', " + crit + ", 'None') == 1)"]

    if len(nvpairs) > 0:
        q_str += " AND ".join(nvpairs)

        # issue database query
        logging.info("Profile query: " + q_str)
        query = AIdb.DBrequest(q_str)
        aisql.getQueue().put(query)
        query.waitAns()
        if query.getResponse() is None or len(query.getResponse()) == 0:
            msgtxt = _("No profiles found.")
            client_msg += [msgtxt]
            logging.info(msgtxt)
        else:
            for row in query.getResponse():
                profpath = row['file']
                profname = row['name']
                if profname is None:  # should not happen
                    profname = 'unnamed'
                try:
                    if profpath is None:
                        msgtxt = "Database record error - profile path is " \
                            "empty."
                        client_msg += [msgtxt]
                        logging.error(msgtxt)
                        continue
                    msgtxt = _('Processing profile %s') % profname
                    client_msg += [msgtxt]
                    logging.info(msgtxt)
                    with open(profpath, 'r') as pfp:
                        raw_profile = pfp.read()
                    # do any template variable replacement {{AI_xxx}}
                    tmpl_profile = sc.perform_templating(raw_profile,
                                                         template_dict)
                    # precautionary validation of profile, logging only
                    sc.validate_profile_string(tmpl_profile, image_dir,
                                               dtd_validation=True,
                                               warn_if_dtd_missing=True)
                except IOError as err:
                    msgtxt = _("Error:  I/O error: ") + str(err)
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    continue
                except OSError:
                    msgtxt = _("Error:  OS error on profile ") + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    continue
                except KeyError:
                    msgtxt = _('Error:  could not find criteria to substitute '
                        'in template: ') + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    logging.error('Profile with template substitution error:' +
                            raw_profile)
                    continue
                except lxml.etree.XMLSyntaxError as err:
                    # log validation error and proceed
                    msgtxt = _(
                            'Warning:  syntax error found in profile: ') \
                            + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    for error in err.error_log:
                        msgtxt = _('Error:  ') + error.message
                        client_msg += [msgtxt]
                        logging.error(msgtxt)
                    logging.info([_('Profile failing validation:  ') +
                                 lxml.etree.tostring(root)])
                # build MIME message and attach to outer MIME message
                msg = MIMEText(tmpl_profile, 'xml')
                # indicate in header that this is an attachment
                msg.add_header('Content-Disposition', 'attachment',
                               filename=profname)
                # attach this profile to the manifest and any other profiles
                outermime.attach(msg)
                msgtxt = _('Parsed and loaded profile: ') + profname
                client_msg += [msgtxt]
                logging.info(msgtxt)

    # any profiles and AI manifest have been attached to MIME message
    # specially format list of messages for display on AI client console
    if client_msg:
        outtxt = ''
        for msgtxt in client_msg:
            msgtxt = _('SC profile locator:') + msgtxt
            outtxt += str(msgtxt) + '\n'
        # add AI client console messages as single plain text attachment
        msg = MIMEText(outtxt, 'plain')  # create MIME message
        outermime.attach(msg)  # attach MIME message to response

    print outermime.as_string()  # send MIME-formatted message
Exemple #13
0
def set_criteria(criteria, manifest_name, db, append=False):
    """
    Set a manifest's record in the criteria database with the
    criteria provided.
    If append is True -- append ones that aren't already set for
    the manifest, and replace ones that are.
    if append is False -- completely remove all criteria already
    set for the manifest, and use only the criteria specified.
    """

    # Build a list of criteria nvpairs to update
    nvpairs = list()

    # we need to fill in the criteria or NULLs for each criteria the database
    # supports (so iterate over each criteria)
    for crit in AIdb.getCriteria(db.getQueue(), onlyUsed=False, strip=True):

        # Get the value from the manifest
        values = criteria[crit]

        # the critera manifest didn't specify this criteria
        if values is None:
            # If we not appending criteria, then we must write in NULLs
            # for this criteria since we're removing all criteria not
            # specified.
            if not append:
                # if the criteria we're processing is a range criteria, fill in
                # NULL for two columns, MINcrit and MAXcrit
                if AIdb.isRangeCriteria(db.getQueue(), crit):
                    nvpairs.append("MIN" + crit + "=NULL")
                    nvpairs.append("MAX" + crit + "=NULL")
                # this is a single value
                else:
                    nvpairs.append(crit + "=NULL")

        # this is a single criteria (not a range)
        elif isinstance(values, basestring):
            # translate "unbounded" to a database NULL
            if values == "unbounded":
                nvstr = crit + "=NULL"
            else:
                # use lower case for text strings
                nvstr = crit + "='" + AIdb.sanitizeSQL(str(values).lower()) \
                        + "'"
            nvpairs.append(nvstr)

        # Else the values are a list this is a range criteria
        else:
            # Set the MIN column for this range criteria
            nvpairs.append("MIN" + crit + "=" + format_value(crit, values[0]))

            # Set the MAX column for this range criteria
            nvpairs.append("MAX" + crit + "=" + format_value(crit, values[1]))

    query = "UPDATE manifests SET " + ",".join(nvpairs) + \
            " WHERE name='" + manifest_name + "'"

    # update the DB
    query = AIdb.DBrequest(query, commit=True)
    db.getQueue().put(query)
    query.waitAns()
    # in case there's an error call the response function (which
    # will print the error)
    query.getResponse()
def verifyCriteriaDict(schema, criteria_dict, db, table=AIdb.MANIFESTS_TABLE):
    """
    Used for verifying and loading criteria from a dictionary of criteria.
    Args:       schema - path to schema file for criteria manifest.
                criteria_dict - dictionary of criteria to verify, in the form
                                of { criteria: value, criteria: value, ... }
                db - database object for install service
                table - database table, distinguishing manifests from profiles
    Raises IOError:
               * if the schema does not open
           ValueError:
                * if the criteria_dict dictionary is empty
                * if the XML is invalid according to the schema
           AssertionError:
                * if a value in the dictionary is empty
    Returns:    A valid XML DOM of the criteria and all MAC and IPV4 values
                are formatted according to verifyXML.prepValuesAndRanges().
    """
    schema = open(schema, 'r')

    if not criteria_dict:
        raise ValueError("Error:\tCriteria dictionary empty: %s\n"
                         % criteria_dict)

    root = lxml.etree.Element("ai_criteria_manifest")

    for name, value_or_range in criteria_dict.iteritems():

        if value_or_range is None:
            raise AssertionError(_("Error: Missing value for criteria "
                                   "'%s'") % name)

        crit = lxml.etree.SubElement(root, "ai_criteria")
        crit.set("name", name)

        # If criteria is a range, split on "-" and add to
        # XML DOM as a range element.
        if AIdb.isRangeCriteria(db.getQueue(), name, table):
            # Split on "-"
            range_value = value_or_range.split('-', 1)

            # If there was only a single value, means user specified
            # this range criteria as a single value, add it as a single
            # value
            if len(range_value) == 1:
                value_elem = lxml.etree.SubElement(crit, "value")
                value_elem.text = value_or_range
            else:
                range_elem = lxml.etree.SubElement(crit, "range")
                range_elem.text = " ".join(range_value)
        else:
            value_elem = lxml.etree.SubElement(crit, "value")
            value_elem.text = value_or_range

    # Verify the generated criteria DOM
    root, errors = verifyXML.verifyRelaxNGManifest(schema,
                        StringIO.StringIO(lxml.etree.tostring(root)))
    if errors:
        raise ValueError(_("Error: Criteria failed validation:\n\t%s") %
                           errors.message)
    try:
        verifyXML.prepValuesAndRanges(root, db, table)
    except ValueError, err:
        raise ValueError(_("Error:\tCriteria error: %s") % err)
Exemple #15
0
 def test_ipv4_is_range(self):
     '''Verify ipv4 returns true for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "ipv4")
     self.assertTrue(is_range_criteria)
Exemple #16
0
def validate_criteria_from_user(criteria, dbo, table):
    ''' Validate profile criteria from dictionary containing command line input
    Args:    criteria - Criteria object holding the criteria that is to be
                        added/set for a manifest.
             dbo - AI_database object for the install service.
             table - name of database table
    Raises:  SystemExit if:
        - criteria is not found in database
        - value is not valid for type (integer and hexadecimal checks)
        - range is improper
    Returns: nothing
    '''
    # find all possible profile criteria expressed as DB table columns
    critlist = []
    # take criteria from generator
    for crit in AIdb.getCriteria(dbo.getQueue(),
                                 table,
                                 onlyUsed=False,
                                 strip=False):
        critlist.append(crit)
    # verify each range criteria is well formed
    for crit in criteria:
        # gather this criteria's values
        man_criterion = criteria[crit]
        # check "value" criteria here (check the criteria exists in DB
        if not AIdb.isRangeCriteria(dbo.getQueue(), crit, table):
            # only check criteria in use in the DB
            if crit not in critlist:
                raise SystemExit(
                    _("Error:\tCriteria %s is not a valid criteria!") % crit)
        # This is a range criteria.  (Check that ranges are valid, that
        # "unbounded" gets set to 0/+inf, ensure the criteria exists
        # in the DB
        else:
            # check for a properly ordered range (with unbounded being 0 or
            # Inf.)
            if man_criterion[0] != "unbounded" and \
                man_criterion[1] != "unbounded" and \
                man_criterion[0] > man_criterion[1]:  # Check min > max
                raise SystemExit(
                    _("Error:\tCriteria %s is not a valid range (MIN > MAX) ")
                    % crit)
            # Clean-up NULL's and changed "unbounded"s to 0 and
            # the maximum integer value
            # Note "unbounded"s are already converted to lower case during
            # input processing
            if man_criterion[0] == "unbounded":
                man_criterion[0] = "0"
            if man_criterion[1] == "unbounded":
                man_criterion[1] = str(sys.maxint)
            if crit == "mac":
                # convert hex mac address (w/o colons) to a number
                try:
                    man_criterion[0] = long(str(man_criterion[0]).upper(), 16)
                    man_criterion[1] = long(str(man_criterion[1]).upper(), 16)
                except ValueError:
                    raise SystemExit(
                        _("Error:\tCriteria %s is not a valid hexadecimal value"
                          ) % crit)
            else:  # this is a decimal value
                try:
                    man_criterion = [
                        long(str(man_criterion[0]).upper()),
                        long(str(man_criterion[1]).upper())
                    ]
                except ValueError:
                    raise SystemExit(
                        _("Error:\tCriteria %s is not a valid integer value") %
                        crit)
            # check to see that this criteria exists in the database columns
            if 'MIN' + crit not in critlist and 'MAX' + crit not in critlist:
                raise SystemExit(
                    _("Error:\tCriteria %s is not a valid criteria!") % crit)
Exemple #17
0
 def test_mem_is_range(self):
     '''Verify mem returns true for range'''
     is_range_criteria = AIdb.isRangeCriteria(self.files.database, "mem")
     self.assertTrue(is_range_criteria)
Exemple #18
0
def send_manifest(form_data,
                  port=0,
                  servicename=None,
                  protocolversion=COMPATIBILITY_VERSION,
                  no_default=False):
    '''Replies to the client with matching service for a service.
    
    Args
        form_data   - the postData passed in from the client request
        port        - the port of the old client
        servicename - the name of the service being used
        protocolversion - the version of the AI service RE: handshake
        no_default  - boolean flag to signify whether or not we should hand
                      back the default manifest and profiles if one cannot
                      be matched based on the client criteria.

    Returns
        None
    
    Raises
        None
    
    '''
    # figure out the appropriate path for the AI database,
    # and get service name if necessary.
    # currently service information is stored in a port directory.
    # When the cherrypy webserver new service directories should be
    # separated via service-name only.  Old services will still use
    # port numbers as the separation mechanism.
    path = None
    found_servicename = None
    service = None
    port = str(port)

    if servicename:
        service = AIService(servicename)
        path = service.database_path
    else:
        for name in config.get_all_service_names():
            if config.get_service_port(name) == port:
                found_servicename = name
                service = AIService(name)
                path = service.database_path
                break

    # Check to insure that a valid path was found
    if not path or not os.path.exists(path):
        print 'Content-Type: text/html'  # HTML is following
        print  # blank line, end of headers
        if servicename:
            print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.'
        else:
            print '<pre><b>Error</b>:unable to find<i>', port + '</i>.'
        print 'Available services are:<p><ol><i>'
        hostname = socket.gethostname()
        for name in config.get_all_service_names():
            port = config.get_service_port(name)
            sys.stdout.write(
                '<a href="http://%s:%d/cgi-bin/'
                'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' %
                (hostname, port, VERSION, name, name))
        print '</i></ol>Please select a service from the above list.'
        return

    if found_servicename:
        servicename = found_servicename

    # load to the AI database
    aisql = AIdb.DB(path)
    aisql.verifyDBStructure()

    # convert the form data into a criteria dictionary
    criteria = dict()
    orig_data = form_data
    while form_data:
        try:
            [key_value, form_data] = form_data.split(';', 1)
        except (ValueError, NameError, TypeError, KeyError):
            key_value = form_data
            form_data = ''
        try:
            [key, value] = key_value.split('=')
            criteria[key] = value
        except (ValueError, NameError, TypeError, KeyError):
            criteria = dict()

    # Generate templating dictionary from criteria
    template_dict = dict()
    for crit in criteria:
        template_dict["AI_" + crit.upper()] = \
                AIdb.formatValue(crit, criteria[crit], units=False)

    # find the appropriate manifest
    try:
        manifest = AIdb.findManifest(criteria, aisql)
    except StandardError as err:
        print 'Content-Type: text/html'  # HTML is following
        print  # blank line, end of headers
        print '<pre><b>Error</b>:findManifest criteria<br>'
        print err, '<br>'
        print '<ol>servicename =', servicename
        print 'port        =', port
        print 'path        =', path
        print 'form_data   =', orig_data
        print 'criteria    =', criteria
        print 'servicename found by port =', found_servicename, '</ol>'
        print '</pre>'
        return

    # check if findManifest() returned a number equal to 0
    # (means we got no manifests back -- thus we serve the default if desired)
    if manifest is None and not no_default:
        manifest = service.get_default_manifest()

    # if we have a manifest to return, prepare its return
    if manifest is not None:
        try:
            # construct the fully qualified filename
            filename = os.path.abspath(
                os.path.join(service.manifest_dir, manifest))
            # open and read the manifest
            with open(filename, 'rb') as mfp:
                manifest_str = mfp.read()
            # maintain compability with older AI client
            if servicename is None or \
                    float(protocolversion) < float(PROFILES_VERSION):
                content_type = mimetypes.types_map.get('.xml', 'text/plain')
                print 'Content-Length:', len(
                    manifest_str)  # Length of the file
                print 'Content-Type:', content_type  # XML is following
                print  # blank line, end of headers
                print manifest_str
                logging.info('Manifest sent from %s.' % filename)
                return

        except OSError as err:
            print 'Content-Type: text/html'  # HTML is following
            print  # blank line, end of headers
            print '<pre>'
            # report the internal error to error_log and requesting client
            sys.stderr.write(_('error:manifest (%s) %s\n') % \
                            (str(manifest), err))
            sys.stdout.write(_('error:manifest (%s) %s\n') % \
                            (str(manifest), err))
            print '</pre>'
            return

    # get AI service image path
    service = AIService(servicename)
    image_dir = service.image.path
    # construct object to contain MIME multipart message
    outermime = MIMEMultipart()
    client_msg = list()  # accumulate message output for AI client

    # If we have a manifest, attach it to the return message
    if manifest is not None:
        # add manifest as attachment
        msg = MIMEText(manifest_str, 'xml')
        # indicate manifest using special name
        msg.add_header('Content-Disposition',
                       'attachment',
                       filename=sc.AI_MANIFEST_ATTACHMENT_NAME)
        outermime.attach(msg)  # add manifest as an attachment

    # search for any profiles matching client criteria
    # formulate database query to profiles table
    q_str = "SELECT DISTINCT name, file FROM " + \
        AIdb.PROFILES_TABLE + " WHERE "
    nvpairs = list()  # accumulate criteria values from post-data
    # for all AI client criteria
    for crit in AIdb.getCriteria(aisql.getQueue(),
                                 table=AIdb.PROFILES_TABLE,
                                 onlyUsed=False):
        if crit not in criteria:
            msgtxt = _("Warning: client criteria \"%s\" not provided in "
                       "request.  Setting value to NULL for profile lookup.") \
                       % crit
            client_msg += [msgtxt]
            logging.warn(msgtxt)
            # fetch only global profiles destined for all clients
            if AIdb.isRangeCriteria(aisql.getQueue(), crit,
                                    AIdb.PROFILES_TABLE):
                nvpairs += ["MIN" + crit + " IS NULL"]
                nvpairs += ["MAX" + crit + " IS NULL"]
            else:
                nvpairs += [crit + " IS NULL"]
            continue

        # prepare criteria value to add to query
        envval = AIdb.sanitizeSQL(criteria[crit])
        if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE):
            # If no default profiles are requested, then we mustn't allow
            # this criteria to be NULL.  It must match the client's given
            # value for this criteria.
            if no_default:
                if crit == "mac":
                    nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \
                        "'))"]

                    nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \
                        "'))"]
                else:
                    nvpairs += ["(MIN" + crit + "<='" + envval + "')"]
                    nvpairs += ["(MAX" + crit + ">='" + envval + "')"]
            else:
                if crit == "mac":
                    nvpairs += [
                        "(MIN" + crit + " IS NULL OR "
                        "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))"
                    ]
                    nvpairs += [
                        "(MAX" + crit + " IS NULL OR HEX(MAX" + crit +
                        ")>=HEX(X'" + envval + "'))"
                    ]
                else:
                    nvpairs += [
                        "(MIN" + crit + " IS NULL OR MIN" + crit + "<='" +
                        envval + "')"
                    ]
                    nvpairs += [
                        "(MAX" + crit + " IS NULL OR MAX" + crit + ">='" +
                        envval + "')"
                    ]
        else:
            # If no default profiles are requested, then we mustn't allow
            # this criteria to be NULL.  It must match the client's given
            # value for this criteria.
            #
            # Also, since this is a non-range criteria, the value stored
            # in the DB may be a whitespace separated list of single
            # values.  We use a special user-defined function in the
            # determine if the given criteria is in that textual list.
            if no_default:
                nvpairs += ["(is_in_list('" + crit + "', '" + envval + \
                    "', " + crit + ", 'None') == 1)"]
            else:
                nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \
                    "', '" + envval + "', " + crit + ", 'None') == 1)"]

    if len(nvpairs) > 0:
        q_str += " AND ".join(nvpairs)

        # issue database query
        logging.info("Profile query: " + q_str)
        query = AIdb.DBrequest(q_str)
        aisql.getQueue().put(query)
        query.waitAns()
        if query.getResponse() is None or len(query.getResponse()) == 0:
            msgtxt = _("No profiles found.")
            client_msg += [msgtxt]
            logging.info(msgtxt)
        else:
            for row in query.getResponse():
                profpath = row['file']
                profname = row['name']
                if profname is None:  # should not happen
                    profname = 'unnamed'
                try:
                    if profpath is None:
                        msgtxt = "Database record error - profile path is " \
                            "empty."
                        client_msg += [msgtxt]
                        logging.error(msgtxt)
                        continue
                    msgtxt = _('Processing profile %s') % profname
                    client_msg += [msgtxt]
                    logging.info(msgtxt)
                    with open(profpath, 'r') as pfp:
                        raw_profile = pfp.read()
                    # do any template variable replacement {{AI_xxx}}
                    tmpl_profile = sc.perform_templating(
                        raw_profile, template_dict)
                    # precautionary validation of profile, logging only
                    sc.validate_profile_string(tmpl_profile,
                                               image_dir,
                                               dtd_validation=True,
                                               warn_if_dtd_missing=True)
                except IOError as err:
                    msgtxt = _("Error:  I/O error: ") + str(err)
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    continue
                except OSError:
                    msgtxt = _("Error:  OS error on profile ") + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    continue
                except KeyError:
                    msgtxt = _('Error:  could not find criteria to substitute '
                               'in template: ') + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    logging.error('Profile with template substitution error:' +
                                  raw_profile)
                    continue
                except lxml.etree.XMLSyntaxError as err:
                    # log validation error and proceed
                    msgtxt = _(
                            'Warning:  syntax error found in profile: ') \
                            + profpath
                    client_msg += [msgtxt]
                    logging.error(msgtxt)
                    for error in err.error_log:
                        msgtxt = _('Error:  ') + error.message
                        client_msg += [msgtxt]
                        logging.error(msgtxt)
                    logging.info([
                        _('Profile failing validation:  ') +
                        lxml.etree.tostring(root)
                    ])
                # build MIME message and attach to outer MIME message
                msg = MIMEText(tmpl_profile, 'xml')
                # indicate in header that this is an attachment
                msg.add_header('Content-Disposition',
                               'attachment',
                               filename=profname)
                # attach this profile to the manifest and any other profiles
                outermime.attach(msg)
                msgtxt = _('Parsed and loaded profile: ') + profname
                client_msg += [msgtxt]
                logging.info(msgtxt)

    # any profiles and AI manifest have been attached to MIME message
    # specially format list of messages for display on AI client console
    if client_msg:
        outtxt = ''
        for msgtxt in client_msg:
            msgtxt = _('SC profile locator:') + msgtxt
            outtxt += str(msgtxt) + '\n'
        # add AI client console messages as single plain text attachment
        msg = MIMEText(outtxt, 'plain')  # create MIME message
        outermime.attach(msg)  # attach MIME message to response

    print outermime.as_string()  # send MIME-formatted message
def validate_criteria_from_user(criteria, dbo, table):
    ''' Validate profile criteria from dictionary containing command line input
    Args:    criteria - Criteria object holding the criteria that is to be
                        added/set for a manifest.
             dbo - AI_database object for the install service.
             table - name of database table
    Raises:  SystemExit if:
        - criteria is not found in database
        - value is not valid for type (integer and hexadecimal checks)
        - range is improper
    Returns: nothing
    '''
    # find all possible profile criteria expressed as DB table columns
    critlist = []
    # take criteria from generator
    for crit in AIdb.getCriteria(dbo.getQueue(), table, onlyUsed=False,
                                 strip=False):
        critlist.append(crit)
    # verify each range criteria is well formed
    for crit in criteria:
        # gather this criteria's values
        man_criterion = criteria[crit]
        # check "value" criteria here (check the criteria exists in DB
        if not AIdb.isRangeCriteria(dbo.getQueue(), crit, table):
            # only check criteria in use in the DB
            if crit not in critlist:
                raise SystemExit(_(
                    "Error:\tCriteria %s is not a valid criteria!") % crit)
        # This is a range criteria.  (Check that ranges are valid, that
        # "unbounded" gets set to 0/+inf, ensure the criteria exists
        # in the DB
        else:
            # check for a properly ordered range (with unbounded being 0 or
            # Inf.)
            if man_criterion[0] != "unbounded" and \
                man_criterion[1] != "unbounded" and \
                man_criterion[0] > man_criterion[1]:  # Check min > max
                raise SystemExit(_(
                    "Error:\tCriteria %s is not a valid range (MIN > MAX) ")
                    % crit)
            # Clean-up NULL's and changed "unbounded"s to 0 and
            # the maximum integer value
            # Note "unbounded"s are already converted to lower case during
            # input processing
            if man_criterion[0] == "unbounded":
                man_criterion[0] = "0"
            if man_criterion[1] == "unbounded":
                man_criterion[1] = str(sys.maxint)
            if crit == "mac":
                # convert hex mac address (w/o colons) to a number
                try:
                    man_criterion[0] = long(str(man_criterion[0]).upper(), 16)
                    man_criterion[1] = long(str(man_criterion[1]).upper(), 16)
                except ValueError:
                    raise SystemExit(_(
                        "Error:\tCriteria %s is not a valid hexadecimal value")
                                % crit)
            else:  # this is a decimal value
                try:
                    man_criterion = [long(str(man_criterion[0]).upper()),
                                     long(str(man_criterion[1]).upper())]
                except ValueError:
                    raise SystemExit(_(
                        "Error:\tCriteria %s is not a valid integer value") %
                            crit)
            # check to see that this criteria exists in the database columns
            if 'MIN' + crit not in critlist and 'MAX' + crit not in critlist:
                raise SystemExit(_(
                    "Error:\tCriteria %s is not a valid criteria!") % crit)