def test_mem_formatValue(self):
     '''Ensure that memory criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINmem', self.mem)
     self.assertEqual(fmt.split(' ')[0], self.mem)
     self.assertEqual(fmt.split(' ')[1], 'MB')
     fmt = AIdb.formatValue('MINmem', self.mem, units=False)
     self.assertEqual(fmt, self.mem)
Ejemplo n.º 2
0
 def test_mem_formatValue(self):
     '''Ensure that memory criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINmem', self.mem)
     self.assertEqual(fmt.split(' ')[0], self.mem)
     self.assertEqual(fmt.split(' ')[1], 'MB')
     fmt = AIdb.formatValue('MINmem', self.mem, units=False)
     self.assertEqual(fmt, self.mem)
Ejemplo n.º 3
0
 def test_other_formatValue(self):
     '''Ensure that formatValue does nothing with all other criteria'''
     fmt = AIdb.formatValue('arch', self.arch)
     self.assertEqual(fmt, self.arch)
     fmt = AIdb.formatValue('platform', self.platform)
     self.assertEqual(fmt, self.platform)
     fmt = AIdb.formatValue('cpu', self.cpu)
     self.assertEqual(fmt, self.cpu)
 def test_other_formatValue(self):
     '''Ensure that formatValue does nothing with all other criteria'''
     fmt = AIdb.formatValue('arch', self.arch)
     self.assertEqual(fmt, self.arch)
     fmt = AIdb.formatValue('platform', self.platform)
     self.assertEqual(fmt, self.platform)
     fmt = AIdb.formatValue('cpu', self.cpu)
     self.assertEqual(fmt, self.cpu)
def remove_client_dhcp_config(client_id):
    '''
    If a local DHCP server is running, remove any client configuration for
    this client from its configuration. If not, inform end-user that the
    client-service binding should no longer be referenced in the DHCP
    configuration.
    '''
    server = dhcp.DHCPServer()
    if server.is_configured():
        # A local DHCP server is configured. Check for a host entry and remove
        # it if found.
        mac_address = client_id[2:]
        mac_address = AIdb.formatValue('mac', mac_address)
        if server.host_is_configured(mac_address):
            print cw(_("Removing host entry '%s' from local DHCP "
                       "configuration.") % mac_address)
            server.remove_host(mac_address)

            if server.is_online():
                try:
                    server.control('restart')
                except dhcp.DHCPServerError as err:
                    print >> sys.stderr, cw(_("Unable to restart the DHCP "
                                              "SMF service: %s" % err))
                    return
    else:
        # No local DHCP configuration, inform user that it needs to be
        # unconfigured elsewhere.
        print cw(_("No local DHCP configuration found. Unless it will be "
                   "reused, the bootfile '%s' may be removed from the DHCP "
                   "configuration\n" % client_id))
Ejemplo n.º 6
0
def remove_client_dhcp_config(client_id):
    '''
    If a local DHCP server is running, remove any client configuration for
    this client from its configuration. If not, inform end-user that the
    client-service binding should no longer be referenced in the DHCP
    configuration.
    '''
    server = dhcp.DHCPServer()
    if server.is_configured():
        # A local DHCP server is configured. Check for a host entry and remove
        # it if found.
        mac_address = client_id[2:]
        mac_address = AIdb.formatValue('mac', mac_address)
        if server.host_is_configured(mac_address):
            print cw(
                _("Removing host entry '%s' from local DHCP "
                  "configuration.") % mac_address)
            server.remove_host(mac_address)

            if server.is_online():
                try:
                    server.control('restart')
                except dhcp.DHCPServerError as err:
                    print >> sys.stderr, cw(
                        _("Unable to restart the DHCP "
                          "SMF service: %s" % err))
                    return
    else:
        # No local DHCP configuration, inform user that it needs to be
        # unconfigured elsewhere.
        print cw(
            _("No local DHCP configuration found. Unless it will be "
              "reused, the bootfile '%s' may be removed from the DHCP "
              "configuration\n" % client_id))
Ejemplo n.º 7
0
def find_clients(lservices, sname=None):
    """
    find_clients() returns a dictionary that contains a list of
    dictionaries.

    The service name is the key for the main dictionary and the
    client, image path, and arch are members of the subdictionary,
    as follows:

        {
          'service1': [
                { 'ipath':<path1>, 'client':<client1>, 'arch': <arch>},
                ....
                      ],
          ....
        }

    Args
        lservices = config.get_all_service_props()
        sname - service name, if only interesetd in clients of a
                specific service

    Returns
        dictionary of a list of dictionaries

    Raises
        None

    """
    sdict = dict()
    for servicename in lservices.keys():
        if sname and sname != servicename:
            continue
        try:
            service = AIService(servicename)
        except VersionError as version_err:
            warn_version(version_err)
            continue
        arch = which_arch(service)
        image_path = [service.image.path]
        client_info = config.get_clients(servicename)
        for clientkey in client_info:
            # strip off the leading '01' and reinsert ':'s
            client = AIdb.formatValue('mac', clientkey[2:])
            tdict = {'client': client, 'ipath': image_path, 'arch': arch}
            if servicename in sdict:  # existing service name
                slist = sdict[servicename]
                slist.extend([tdict])
                sdict[servicename] = slist
            else:  # new service name key
                sdict[servicename] = [tdict]
    return sdict
Ejemplo n.º 8
0
def find_clients(lservices, sname=None):
    """
    find_clients() returns a dictionary that contains a list of
    dictionaries.

    The service name is the key for the main dictionary and the
    client, image path, and arch are members of the subdictionary,
    as follows:

        {
          'service1': [
                { 'ipath':<path1>, 'client':<client1>, 'arch': <arch>},
                ....
                      ],
          ....
        }

    Args
        lservices = config.get_all_service_props()
        sname - service name, if only interesetd in clients of a
                specific service

    Returns
        dictionary of a list of dictionaries

    Raises
        None

    """
    sdict = dict()
    for servicename in lservices.keys():
        if sname and sname != servicename:
            continue
        try:
            service = AIService(servicename)
        except VersionError as version_err:
            warn_version(version_err)
            continue
        arch = which_arch(service)
        image_path = [service.image.path]
        client_info = config.get_clients(servicename)
        for clientkey in client_info:
            # strip off the leading '01' and reinsert ':'s
            client = AIdb.formatValue('mac', clientkey[2:])
            tdict = {'client': client, 'ipath': image_path, 'arch': arch}
            if servicename in sdict:  # existing service name
                slist = sdict[servicename]
                slist.extend([tdict])
                sdict[servicename] = slist
            else:  # new service name key
                sdict[servicename] = [tdict]
    return sdict
Ejemplo n.º 9
0
    def get_criteria_info(mancriteria):
        """
        Iterates over the criteria which consists of a dictionary with
        possibly arch, min memory, max memory, min ipv4, max ipv4, min mac, 
        max mac, cpu, platform, min network and max network converting it 
        into a dictionary with on arch, mem, ipv4, mac, cpu, platform, 
        network.  Any min/max attributes are stored as a range within the 
        new dictionary.

        Args
            criteria = dictionary of the criteria

        Returns
            dictionary of combinded min/max and other criteria
            maximum criteria width

        Raises
            None
        """
        tdict = {
            'arch': '',
            'mem': '',
            'ipv4': '',
            'mac': '',
            'platform': '',
            'network': '',
            'cpu': ''
        }
        twidth = 0
        for key in mancriteria.keys():
            if mancriteria[key] is None or mancriteria[key] == '':
                continue  # no criteria for instance key
            twidth = max(twidth, len(key.lstrip('MAX').lstrip('MIN')))
            svalue = AIdb.formatValue(key, mancriteria[key])
            if key.find('MAX') == 0 or key.find('MIN') == 0:
                tkey = key[3:]  # strip off the MAX or MIN for a new keyname
                if tdict[tkey] != '':  # dealing with range
                    if tdict[tkey] != svalue:
                        if max(svalue, tdict[tkey]) == svalue:
                            tdict[tkey] = tdict[tkey] + ' - ' + svalue
                        else:
                            tdict[tkey] = svalue + ' - ' + tdict[tkey]
                else:  # first value, not range yet
                    tdict[tkey] = svalue
            else:  # not a range manifest criteria
                tdict[key] = svalue

        return tdict, twidth
def list_manifests(service):
    '''Replies to the client with criteria list for a service.
       The output should be similar to installadm list.

    Args
        service - the name of the service being listed

    Returns
        None

    Raises
        None
    '''
    print 'Content-Type: text/html'     # HTML is following
    print                               # blank line, end of headers
    print '<html>'
    print '<head>'
    sys.stdout.write('<title>%s %s</title>' %
                     (_('Manifest list for'), service))
    print '</head><body>'

    port = 0
    try:
        smf.AISCF(FMRI="system/install/server")
    except KeyError:
        # report the internal error to error_log and requesting client
        sys.stderr.write(_("error:The system does not have the "
                           "system/install/server SMF service."))
        sys.stdout.write(_("error:The system does not have the "
                           "system/install/server SMF service."))
        return
    services = config.get_all_service_names()
    if not services:
        # report the error to the requesting client only
        sys.stdout.write(_('error:no services on this server.\n'))
        return

    found = False
    if config.is_service(service):
        service_ctrl = AIService(service)
        found = True

        # assume new service setup
        path = service_ctrl.database_path
        if os.path.exists(path):
            try:
                aisql = AIdb.DB(path)
                aisql.verifyDBStructure()
            except StandardError as err:
                # report the internal error to error_log and
                # requesting client
                sys.stderr.write(_('error:AI database access '
                                   'error\n%s\n') % err)
                sys.stdout.write(_('error:AI database access '
                                   'error\n%s\n') % err)
                return

            # generate the list of criteria for the criteria table header
            criteria_header = E.TR()
            for crit in AIdb.getCriteria(aisql.getQueue(), strip=False):
                criteria_header.append(E.TH(crit))

            # generate the manifest rows for the criteria table body
            names = AIdb.getManNames(aisql.getQueue())
            table_body = E.TR()
            allcrit = AIdb.getCriteria(aisql.getQueue(), strip=False)
            colspan = str(max(len(list(allcrit)), 1))
            for manifest in names:

                # iterate through each manifest (and instance)
                for instance in range(0,
                        AIdb.numInstances(manifest, aisql.getQueue())):

                    table_body.append(E.TR())
                    # print the manifest name only once (from instance 0)
                    if instance == 0:
                        href = '../' + service + '/' + manifest
                        row = str(AIdb.numInstances(manifest,
                                                    aisql.getQueue()))
                        table_body.append(E.TD(
                                   E.A(manifest, href=href, rowspan=row)))
                    else:
                        table_body.append(E.TD())

                    crit_pairs = AIdb.getManifestCriteria(manifest,
                                                          instance,
                                                          aisql.getQueue(),
                                                          onlyUsed=True,
                                                          humanOutput=True)

                    # crit_pairs is an SQLite3 row object which doesn't
                    # support iteritems(), etc.
                    for crit in crit_pairs.keys():
                        formatted_val = AIdb.formatValue(crit,
                                                         crit_pairs[crit])
                        # if we do not get back a valid value ensure a
                        # hyphen is printed (prevents "" from printing)
                        if formatted_val and crit_pairs[crit]:
                            table_body.append(E.TD(formatted_val,
                                                   align="center"))
                        else:
                            table_body.append(E.TD(lxml.etree.Entity("nbsp"),
                                                   align="center"))

            # print the default manifest at the end of the table,
            # which has the same colspan as the Criteria List label
            else:
                href = '../' + service + '/default.xml'
                table_body.append(E.TR(E.TD(E.A("Default", href=href)),
                                       E.TD(lxml.etree.Entity("nbsp"),
                                            colspan=colspan,
                                            align="center")))
            web_page = E.HTML(
                         E.HEAD(
                            E.TITLE(_("OmniOS Automated "
                                      "Installation Webserver"))
                            ),
                         E.BODY(
                            E.H1(_("Welcome to the OmniOS "
                                   "Automated Installation webserver!")),
                            E.P(_("Service '%s' has the following "
                                "manifests available, served to clients "
                                "matching required criteria.") % service),
                            E.TABLE(
                               E.TR(
                                    E.TH(_("Manifest"), rowspan="2"),
                                    E.TH(_("Criteria List"),
                                        colspan=colspan)),
                                    criteria_header,
                                    table_body,
                                    border="1", align="center"),
                          )
                     )
            print lxml.etree.tostring(web_page, pretty_print=True)

    # service is not found, provide available services on host
    if not found:
        sys.stdout.write(_('Service <i>%s</i> not found.  ') % service)
        sys.stdout.write(_('Available services are:<p><ol><i>'))
        host = socket.gethostname()
        for service_name in config.get_all_service_names():
            # assume new service setup
            port = config.get_service_port(service_name)
            sys.stdout.write('<a href="http://%s:%d/cgi-bin/'
                   'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' %
                   (host, port, VERSION, service_name, service_name))
        sys.stdout.write('</i></ol>%s' % _('Please select a service '
                   'from the above list.'))

    print '</body></html>'
Ejemplo n.º 11
0
def get_criteria_info(crit_dict):
    """
    Iterates over the criteria which consists of a dictionary with
    possibly arch, min memory, max memory, min ipv4, max ipv4, min mac,
    max mac, cpu, platform, min network, max network and zonename converting
    it into a dictionary with arch, mem, ipv4, mac, cpu, platform, network
    and zonename.  Any min/max attributes are stored as a range within the
    new dictionary.

    Args
        crit_dict = dictionary of the criteria

    Returns
        dictionary of combined min/max and other criteria, formatted
           with possible endings such as MB
        maximum criteria width

    Raises
        None
    """

    if crit_dict is None:
        return dict(), 0

    # tdict values are formatted strings, with possible endings
    # such as MB.
    tdict = dict()

    crit_width = 0
    for key in crit_dict.keys():
        if key == 'service':
            continue
        is_range_crit = key.startswith('MIN') or key.startswith('MAX')
        # strip off the MAX or MIN for a new keyname
        if is_range_crit:
            keyname = key[3:]  # strip off the MAX or MIN for a new keyname
        else:
            keyname = key
        tdict.setdefault(keyname, '')
        db_value = crit_dict[key]
        if not db_value and not is_range_crit:
            # For non-range (value) criteria, None means it isn't set.
            # For range criteria, None means unbounded if the other
            # criteria in the MIN/MAX pair is set.
            continue    # value criteria not set
        crit_width = max(crit_width, len(keyname))
        fmt_value = AIdb.formatValue(key, db_value)
        if is_range_crit:
            if not db_value:
                fmt_value = "unbounded"
            if tdict[keyname] != '':
                if tdict[keyname] != fmt_value:  # dealing with range
                    if key.startswith('MAX'):
                        tdict[keyname] = tdict[keyname] + ' - ' + fmt_value
                    else:
                        tdict[keyname] = fmt_value + ' - ' + tdict[keyname]
                elif tdict[keyname] == "unbounded":
                    # MIN and MAX both unbounded, which means neither is
                    # set in db. Clear tdict value.
                    tdict[keyname] = ''   # no values for range, reset tdict
            else:  # first value, not range yet
                tdict[keyname] = fmt_value
                # if the partner MIN/MAX criterion is not set in the db,
                # handle now because otherwise it won't be processed.
                if key.startswith('MIN'):
                    if 'MAX' + keyname not in crit_dict.keys():
                        if fmt_value == "unbounded":
                            tdict[keyname] = ''
                        else:
                            tdict[keyname] = tdict[keyname] + ' - unbounded'
                else:
                    if 'MIN' + keyname not in crit_dict.keys():
                        if fmt_value == "unbounded":
                            tdict[keyname] = ''
                        else:
                            tdict[keyname] = 'unbounded - ' + tdict[keyname]
        else:
            tdict[keyname] = fmt_value

    return tdict, crit_width
Ejemplo n.º 12
0
def setup_x86_client(service, mac_address, bootargs=''):
    ''' Set up an x86 client

    Creates a relative symlink from the <svcname>'s bootfile to
        /etc/netboot/<client_id>
    Creates /etc/netboot/menu.lst.<client_id> boot configuration file
    Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file

    Arguments:
              image_path - directory path to AI image
              mac_address - client MAC address (as formed by
                            MACAddress class, i.e., 'ABABABABABAB')
              bootargs = bootargs of client (x86)
    Returns: Nothing

    '''
    # create a client-identifier (01 + MAC ADDRESS)
    client_id = "01" + mac_address

    menulst = os.path.join(service.config_dir, grub.MENULST)
    client_menulst = _menulst_path(client_id)

    # copy service's menu.lst file to menu.lst.<client_id>
    try:
        shutil.copy(menulst, client_menulst)
    except IOError as err:
        print >> sys.stderr, cw(
            _("Unable to copy grub menu.lst file: %s") % err.strerror)
        return

    # create a symlink from the boot directory to the sevice's bootfile.
    # note this must be relative from the boot directory.
    bootfile, pxegrub_path = _pxegrub_path(client_id)
    os.symlink("./" + service.dhcp_bootfile, pxegrub_path)

    clientinfo = {config.FILES: [client_menulst, pxegrub_path]}

    # if the client specifies bootargs, use them. Otherwise, inherit
    # the bootargs specified in the service (do nothing)
    if bootargs:
        grub.update_bootargs(client_menulst, service.bootargs, bootargs)
        clientinfo[config.BOOTARGS] = bootargs

    config.add_client_info(service.name, client_id, clientinfo)

    # Configure DHCP for this client if the configuration is local, otherwise
    # suggest the configuration addition. Note we only need to do this for
    # x86-based clients, not SPARC.
    server = dhcp.DHCPServer()
    if server.is_configured():
        # We'll need the actual hardware ethernet address for the DHCP entry,
        # rather than the non-delimited string that 'mac_address' is.
        full_mac = AIdb.formatValue('mac', mac_address)
        try:
            print cw(
                _("Adding host entry for %s to local DHCP configuration.") %
                full_mac)
            server.add_host(full_mac, bootfile)
        except dhcp.DHCPServerError as err:
            print cw(
                _("Unable to add host (%s) to DHCP configuration: %s") %
                (full_mac, err))
            return

        if server.is_online():
            try:
                server.control('restart')
            except dhcp.DHCPServerError as err:
                print >> sys.stderr, cw(
                    _("\nUnable to restart the DHCP SMF "
                      "service: %s\n" % err))
                return
        else:
            print cw(
                _("\nLocal DHCP configuration complete, but the DHCP "
                  "server SMF service is offline. To enable the "
                  "changes made, enable: %s.\nPlease see svcadm(1M) "
                  "for further information.\n") % dhcp.DHCP_SERVER_IPV4_SVC)
    else:
        # No local DHCP, tell the user all about their boot configuration
        valid_nets = list(com.get_valid_networks())
        if valid_nets:
            server_ip = valid_nets[0]

        print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, bootfile))

        if len(valid_nets) > 1:
            print cw(
                _("\nNote: determined more than one IP address "
                  "configured for use with AI. Please ensure the above "
                  "'Boot server IP' is correct.\n"))
Ejemplo n.º 13
0
    def index(self):
        """ The server's main page """

        # generate the list of criteria for the criteria table header
        criteriaHeader = E.TR()
        for crit in AIdb.getCriteria(self.AISQL.getQueue(), strip=False):
            criteriaHeader.append(E.TH(crit))

        # generate the manifest rows for the criteria table body
        names = AIdb.getManNames(self.AISQL.getQueue())
        tableBody = E.TR()
        for manifest in names:

            # iterate through each manifest (and instance)
            for instance in range(0,
                    AIdb.numInstances(manifest, self.AISQL.getQueue())):

                tableBody.append(E.TR())
                # print the manifest name only once (key off instance 0)
                if instance == 0:
                    tableBody.append(
                        E.TD(E.A(manifest,
                                 href="/manifests/" + manifest,
                                 rowspan=str(AIdb.numInstances(manifest,
                                    self.AISQL.getQueue())))
                            )
                    )
                else:
                    tableBody.append(E.TD())
                critPairs = AIdb.getManifestCriteria(manifest, instance,
                                                     self.AISQL.getQueue(),
                                                     onlyUsed=True,
                                                     humanOutput=True)
                # critPairs is an SQLite3 row object which doesn't support
                # iteritems(), etc.
                for crit in critPairs.keys():
                    formatted_val = AIdb.formatValue(crit, critPairs[crit])
                    # if we do not get back a valid value ensure a hyphen is
                    # printed (this prevents "" from printing)
                    if formatted_val and critPairs[crit]:
                        tableBody.append(E.TD(formatted_val, align="center"))
                    else:
                        tableBody.append(E.TD(lxml.etree.Entity("nbsp"),
                                              align="center"))

        # print the default manifest at the end of the table
        else:
            tableBody.append(
                             E.TR(
                                  E.TD(
                                       E.A("Default",
                                           href="/manifests/default.xml")),
                                  E.TD(lxml.etree.Entity("nbsp"),
                                       colspan=str(max(len(list(
                                       AIdb.getCriteria(self.AISQL.getQueue(),
                                       strip=False))), 1)),
                                       align="center")
                             )
            )
        web_page = \
                E.HTML(
                       E.HEAD(
                              E.TITLE(_("%s A/I Webserver") % _DISTRIBUTION)
                       ),
                       E.BODY(
                              E.H1(_("Welcome to the %s A/I "
                                     "webserver!") % _DISTRIBUTION),
                              E.P(_("This server has the following "
                                    "manifests available, served to clients "
                                    "matching required criteria.")),
                              E.TABLE(
                                      E.TR(
                                           E.TH(_("Manifest"), rowspan="2"),
                                           E.TH(_("Criteria List"),
                                                colspan=str(max(len(list(
                                                AIdb.getCriteria(self.AISQL.\
                                                getQueue(),
                                                strip=False))), 1)))
                                      ),
                                      criteriaHeader,
                                      tableBody,
                                      border="1", align="center"
                              ),
                       )
                )
        return lxml.etree.tostring(web_page, pretty_print=True)
 def test_network_formatValue(self):
     '''Ensure that ipv4 network criteria is formatted appropriately'''
     fmt = AIdb.formatValue('network', self.network)
     self.assertTrue(self.is_ipv4(fmt))
 def test_mac_formatValue(self):
     '''Ensure that mac criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINmac', self.mac)
     for k, bits in enumerate(fmt.split(':')):
         self.assertEqual(bits, self.mac[k * 2:(k * 2) + 2])
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 test_ipv4_formatValue(self):
     '''Ensure that ipv4 criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINipv4', self.ipv4)
     self.assertTrue(self.is_ipv4(fmt))
Ejemplo n.º 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
Ejemplo n.º 19
0
def setup_x86_client(service,
                     mac_address,
                     bootargs='',
                     suppress_dhcp_msgs=False):
    ''' Set up an x86 client

    Creates relative symlink(s) in /etc/netboot::
        <client_id>.<archtype> -> ./<svcname>/<bootfile_path>
        e.g., 01223344223344.bios -> ./mysvc/boot/grub/pxegrub
    Creates /etc/netboot/<cfg_file>.<client_id> boot configuration file
    Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file

    Arguments:
              service - the AIService to associate with client
              mac_address - client MAC address (as formed by
                            MACAddress class, i.e., 'ABABABABABAB')
              bootargs = bootargs of client (x86)
              suppress_dhcp_msgs - if True, suppresses output of DHCP
                                   configuration messages
    Returns: Nothing

    '''
    # create a client-identifier (01 + MAC ADDRESS)
    client_id = "01" + mac_address
    clientinfo = dict()

    svcgrub = grubcfg(service.name,
                      path=service.image.path,
                      config_dir=service.config_dir)

    # Call setup_client - it will return netconfig_files, config_files,
    # and tuples, e.g.:
    # netconfig_files:  ['/etc/netboot/menu.lst.01234234234234']
    # config_files:  ['/etc/netboot/menu.conf.01234234234234']
    # boot_tuples:  [('00:00', 'bios', 'mysvc/boot/grub/pxegrub2'),
    #                ('00:07', 'uefi', 'mysvc/boot/grub/grub2netx64.efi')]
    # If the client specifies bootargs, use them. Otherwise, inherit
    # the bootargs specified in the service.
    netconfig_files, config_files, boot_tuples = \
        svcgrub.setup_client(mac_address, bootargs=bootargs,
                             service_bootargs=service.bootargs)

    # update the bootargs in the service .config file
    if bootargs:
        clientinfo[config.BOOTARGS] = bootargs

    # keep track of client files to delete when client is removed
    client_files = list(netconfig_files)
    client_files.extend(config_files)

    # create symlink(s) from the boot directory to the sevice's bootfile(s).
    # note these must be relative from the boot directory.
    for arch, archtype, relpath in boot_tuples:
        # name of symlink is /etc/netboot/<clientid>.<archtype>
        bootfile_symlink = _bootfile_path(client_id, archtype)
        dotpath = './' + relpath
        logging.debug('creating symlink %s->%s', bootfile_symlink, dotpath)
        os.symlink(dotpath, bootfile_symlink)
        client_files.append(bootfile_symlink)

        # if this is archtype bios, create <clientid> symlink to
        # <clientid>.bios for backward compatibility with existing dhcp
        # servers.
        if archtype == 'bios':
            clientid_path = os.path.join(com.BOOT_DIR, client_id)
            dot_client_arch = './' + client_id + '.' + archtype
            logging.debug('creating symlink %s->%s', clientid_path,
                          dot_client_arch)
            os.symlink(dot_client_arch, clientid_path)

    logging.debug('adding client_files to .config: %s', client_files)
    clientinfo[config.FILES] = client_files
    config.add_client_info(service.name, client_id, clientinfo)

    # Configure DHCP for this client if the configuration is local, otherwise
    # suggest the configuration addition. Note we only need to do this for
    # x86-based clients, not SPARC.
    server = dhcp.DHCPServer()
    if server.is_configured():
        # We'll need the actual hardware ethernet address for the DHCP entry,
        # rather than the non-delimited string that 'mac_address' is.
        full_mac = AIdb.formatValue('mac', mac_address)
        try:
            if not suppress_dhcp_msgs:
                print cw(
                    _("Adding host entry for %s to local DHCP "
                      "configuration.") % full_mac)
            server.add_option_arch()
            server.add_host(full_mac, boot_tuples)
        except dhcp.DHCPServerError as err:
            print cw(
                _("Unable to add host (%(mac)s) to DHCP "
                  "configuration: %(error)s") % {
                      'mac': full_mac,
                      'error': err
                  })
            return

        if server.is_online():
            try:
                server.control('restart')
            except dhcp.DHCPServerError as err:
                print >> sys.stderr, cw(
                    _("\nUnable to restart the DHCP SMF "
                      "service: %s\n" % err))
                return
        elif not suppress_dhcp_msgs:
            print cw(
                _("\nLocal DHCP configuration complete, but the DHCP "
                  "server SMF service is offline. To enable the "
                  "changes made, enable: %s.\nPlease see svcadm(1M) "
                  "for further information.\n") % dhcp.DHCP_SERVER_IPV4_SVC)
    else:
        # No local DHCP, tell the user all about their boot configuration
        valid_nets = list(com.get_valid_networks())
        if valid_nets:
            server_ip = valid_nets[0]

        if not suppress_dhcp_msgs:
            boofile_text = '\n'
            for archval, archtype, relpath in boot_tuples:
                bootfilename = client_id + '.' + archtype
                boofile_text = (boofile_text + '\t' + archtype +
                                ' clients (arch ' + archval + '):  ' +
                                bootfilename + '\n')
            print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, boofile_text))

            if len(valid_nets) > 1:
                print cw(
                    _("\nNote: determined more than one IP address "
                      "configured for use with AI. Please ensure the "
                      "above 'Boot server IP' is correct.\n"))
Ejemplo n.º 20
0
def find_sparc_clients(lservices, sname = None):
    """
    find_sparc_clients() searches /etc/netboot for all clients and
    returns a dictionary that contains a list of dictionaries.  

    The service name is the key for the main dictionary and the 
    client and image path are members of the subdictionary.  The 
    dictionary will look something like:

        { 
            'service1': [
                        { 'ipath':<path1>, 'client':<client1> },
                        ....
                        ],
            ....
        }

    The information is spread out across a couple of different 
    files within the server.  The Client is embedded within a
    directory path (/etc/netboot/<IP Network>/01<client>).  The
    Image Path is in the wanboot.conf file pointed to by the 
    Client path.  The Service Name is contained in the install.conf
    file pointed to by the Image Path.

    We first get the IP addresses for the host.  Then while only
    using IPv4 address we iterate over the client directories within
    /etc/netboot/<IP Network> to get the Image Path and Service Name.
    The client and image path are then added to the named service
    dictionary list.

    Args
        lservices = 
        sname = 

    Returns
        dictionary of a list of dictionaries

    Raises
        None
    """

    installconf = 'install.conf'
    wanbootconf = 'wanboot.conf'

    def get_image_path(lpath):
        """
        gets the Image Path for the client pointed to by lpath.
        The Image Path on Sparc is stored in the wanboot.conf file.

        Args
            lpath = path for directory which contains wanboot.conf file

        Returns
            image path for client

        Raises
            None
        """
        try:
            confpath = os.path.join(lpath, wanbootconf)
            sinfo = os.stat(confpath)
            fp = open(confpath)
            fstr = fp.read(sinfo.st_size)
            fp.close()
        except (OSError, IOError):
            sys.stderr.write("%s: error: while accessing wanboot.conf file" % \
                            sys.argv[0])
            return

        start = fstr.find('boot_file=')+len('boot_file=')
        end = fstr[start:].find('/platform')

        return fstr[start:start+end]

    def get_service_name(lpath):
        """
        gets the Service Name for the client from the lpath pointed
        to by the wanboot.conf file.  The Service Name is in the
        Image Path install.conf file.

        Args
            lpath = path to directory containing install.conf file

        Returns
            install service for client

        Raises
            None
        """
        try:
            confpath = os.path.join(lpath, installconf)
            sinfo = os.stat(confpath)
            fp = open(confpath)
            fstr = fp.read(sinfo.st_size)
            fp.close()
        except (OSError, IOError):
            sys.stderr.write("%s: error: while accessing "
                             "install.conf file\n" % \
                             sys.argv[0])
            return 

        start = fstr.find('install_service=')+len('install_service=')
        end = fstr[start:].find('\n')

        return fstr[start:start+end]

    # start of find_sparc_clients
    if not os.path.exists('/etc/netboot'):
        return {}

    sdict = {}
    hostname = socket.getfqdn()
    ipaddr = socket.gethostbyname(hostname)
    # get the Network IP path for the host
    end = ipaddr.rfind('.')
    path = os.path.join('/etc/netboot', ipaddr[:end]+'.0')

    try:
        for clientdir in os.listdir(path):
            # strip off the 01 in the clientdir
            client = AIdb.formatValue('mac', clientdir[2:])

            # get the Image from the clientdir/wanboot.conf file
            ipath = get_image_path(os.path.join(path, clientdir))

            if not os.path.exists(ipath):
                continue 

            # get the service name from the ipath/install.conf file
            servicename = get_service_name(ipath)

            # store the client and image path in the 
            # dictionary under the service name.  First
            # check to see if the service name key
            # already exists.
            if servicename in lservices and \
              (not sname or servicename == sname):
                tdict = {'client':client, 'ipath':[ipath], 'arch':'Sparc'}
                if sdict.has_key(servicename):  # existing service name key
                    slist = sdict[servicename]
                    slist.extend([tdict])
                    sdict[servicename] = slist
                else: # new service name key
                    sdict[servicename] = [tdict]
    except OSError:
        return {} 
       
    return sdict
Ejemplo n.º 21
0
 def test_ipv4_formatValue(self):
     '''Ensure that ipv4 criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINipv4', self.ipv4)
     self.assertTrue(self.is_ipv4(fmt))
Ejemplo n.º 22
0
 def test_network_formatValue(self):
     '''Ensure that ipv4 network criteria is formatted appropriately'''
     fmt = AIdb.formatValue('network', self.network)
     self.assertTrue(self.is_ipv4(fmt))
Ejemplo n.º 23
0
def find_x86_clients(lservices, sname = None):
    """
    find_x86_clients() searches TFTPDir for all clients and
    returns a dictionary that contains a list of dictionaries.  

    The service name is the key for the main dictionary and the 
    client and image path are members of the subdictionary.  The 
    dictionary will look something like:

        { 
            'service1': [
                        { 'ipath':<path1>, 'client':<client1> },
                        ....
                        ],
            ....
        }

    The information is contained within the menu.lst.01<client>
    file.  Though not the best approach architecturally it is 
    the only method currently available.

    We first get the TFTProot directory.  Then iterate over the 
    files within the TFTProot directory to get the client menu 
    which contains the Image Path and Service Name.  The client 
    and image path are then added to the named service dictionary 
    list.
    """
    def get_menu_info(path):
        """
        Reads TFTPboot/menu.list file pointed to by 'path' via
        GrubMenu class in installadm_common.py.  Getting the 
        install path from the install_media field, service name 	
        from install_service
        
        Args
            path = path for the menu.list.01<client> file.

        Returns
            a dictionary of services made up of a list of tuples of 
                port, install path
            
        Raises
            None
        """
        iaddr = 'install_svc_address='
        iserv = 'install_service='
        imedia = 'install_media=http://'

        rdict = {}
        menu = com.GrubMenu(file_name=path)
        entries = menu.entries[0]
#        for entries in menu.entries:
        if entries:
            if menu[entries].has_key('kernel$'):
                mdict = menu[entries]['kernel$']
                start = mdict.find(iserv)+len(iserv)
                service = mdict[start:].split(',')[0]
                start = mdict.find(iaddr)+len(iaddr)
                port = mdict[start:].split(',')[0].split(':')[-1]
                start = mdict.find(imedia)+len(imedia)
                pstart = mdict[start:].split(',')[0].find('/')
                path = mdict[start:].split(',')[0][pstart:]
                if rdict.has_key(service):
                    tlist = rdict[service]
                    tlist.extend([(port, path)])
                    rdict[service] = tlist
                else:
                    rdict[service] = [(port, path)]

        return rdict

    # start of find_x86_clients
    sdict = {}
    tftp_dir = com.find_TFTP_root()
    if tftp_dir and os.path.exists(tftp_dir):
        for filenames in os.listdir(tftp_dir):
            if filenames.find("menu.lst.01") >= 0:
                path = os.path.join(tftp_dir, filenames)
                pservices = get_menu_info(path)
                tdict = {'client':'', 'ipath':'', 'arch':'x86'}
                for servicename in pservices:
                    if servicename in lservices and \
                      (not sname or servicename == sname):
                        client = AIdb.formatValue('mac', filenames[11:])
                        tdict['client'] = client
                        # create a list of image_paths for the client
                        ipath = []
                        for tup in pservices[servicename]:
                            ipath.insert(0, tup[1])
                        tdict['ipath'] = ipath
                        if sdict.has_key(servicename): # existing service name
                            slist = sdict[servicename]
                            slist.extend([tdict])
                            sdict[servicename] = slist
                        else: # new service name key
                            sdict[servicename] = [tdict]
    return sdict
Ejemplo n.º 24
0
def get_criteria_info(crit_dict):
    """
    Iterates over the criteria which consists of a dictionary with
    possibly arch, min memory, max memory, min ipv4, max ipv4, min mac,
    max mac, cpu, platform, min network, max network and zonename converting
    it into a dictionary with arch, mem, ipv4, mac, cpu, platform, network
    and zonename.  Any min/max attributes are stored as a range within the
    new dictionary.

    Args
        crit_dict = dictionary of the criteria

    Returns
        dictionary of combined min/max and other criteria, formatted
           with possible endings such as MB
        maximum criteria width

    Raises
        None
    """

    if crit_dict is None:
        return dict(), 0

    # tdict values are formatted strings, with possible endings
    # such as MB.
    tdict = dict()

    crit_width = 0
    for key in crit_dict.keys():
        if key == 'service':
            continue
        is_range_crit = key.startswith('MIN') or key.startswith('MAX')
        # strip off the MAX or MIN for a new keyname
        if is_range_crit:
            keyname = key[3:]  # strip off the MAX or MIN for a new keyname
        else:
            keyname = key
        tdict.setdefault(keyname, '')
        db_value = crit_dict[key]
        if not db_value and not is_range_crit:
            # For non-range (value) criteria, None means it isn't set.
            # For range criteria, None means unbounded if the other
            # criteria in the MIN/MAX pair is set.
            continue  # value criteria not set
        crit_width = max(crit_width, len(keyname))
        fmt_value = AIdb.formatValue(key, db_value)
        if is_range_crit:
            if not db_value:
                fmt_value = "unbounded"
            if tdict[keyname] != '':
                if tdict[keyname] != fmt_value:  # dealing with range
                    if key.startswith('MAX'):
                        tdict[keyname] = tdict[keyname] + ' - ' + fmt_value
                    else:
                        tdict[keyname] = fmt_value + ' - ' + tdict[keyname]
                elif tdict[keyname] == "unbounded":
                    # MIN and MAX both unbounded, which means neither is
                    # set in db. Clear tdict value.
                    tdict[keyname] = ''  # no values for range, reset tdict
            else:  # first value, not range yet
                tdict[keyname] = fmt_value
                # if the partner MIN/MAX criterion is not set in the db,
                # handle now because otherwise it won't be processed.
                if key.startswith('MIN'):
                    if 'MAX' + keyname not in crit_dict.keys():
                        if fmt_value == "unbounded":
                            tdict[keyname] = ''
                        else:
                            tdict[keyname] = tdict[keyname] + ' - unbounded'
                else:
                    if 'MIN' + keyname not in crit_dict.keys():
                        if fmt_value == "unbounded":
                            tdict[keyname] = ''
                        else:
                            tdict[keyname] = 'unbounded - ' + tdict[keyname]
        else:
            tdict[keyname] = fmt_value

    return tdict, crit_width
def setup_x86_client(service, mac_address, bootargs=''):
    ''' Set up an x86 client

    Creates a relative symlink from the <svcname>'s bootfile to
        /etc/netboot/<client_id>
    Creates /etc/netboot/menu.lst.<client_id> boot configuration file
    Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file

    Arguments:
              image_path - directory path to AI image
              mac_address - client MAC address (as formed by
                            MACAddress class, i.e., 'ABABABABABAB')
              bootargs = bootargs of client (x86)
    Returns: Nothing

    '''
    # create a client-identifier (01 + MAC ADDRESS)
    client_id = "01" + mac_address

    menulst = os.path.join(service.config_dir, grub.MENULST)
    client_menulst = _menulst_path(client_id)

    # copy service's menu.lst file to menu.lst.<client_id>
    try:
        shutil.copy(menulst, client_menulst)
    except IOError as err:
        print >> sys.stderr, cw(_("Unable to copy grub menu.lst file: %s") %
           err.strerror)
        return

    # create a symlink from the boot directory to the sevice's bootfile.
    # note this must be relative from the boot directory.
    bootfile, pxegrub_path = _pxegrub_path(client_id)
    os.symlink("./" + service.dhcp_bootfile, pxegrub_path)

    clientinfo = {config.FILES: [client_menulst, pxegrub_path]}

    # if the client specifies bootargs, use them. Otherwise, inherit
    # the bootargs specified in the service (do nothing)
    if bootargs:
        grub.update_bootargs(client_menulst, service.bootargs, bootargs)
        clientinfo[config.BOOTARGS] = bootargs

    config.add_client_info(service.name, client_id, clientinfo)

    # Configure DHCP for this client if the configuration is local, otherwise
    # suggest the configuration addition. Note we only need to do this for
    # x86-based clients, not SPARC.
    server = dhcp.DHCPServer()
    if server.is_configured():
        # We'll need the actual hardware ethernet address for the DHCP entry,
        # rather than the non-delimited string that 'mac_address' is.
        full_mac = AIdb.formatValue('mac', mac_address)
        try:
            print cw(_("Adding host entry for %s to local DHCP configuration.")
                       % full_mac)
            server.add_host(full_mac, bootfile)
        except dhcp.DHCPServerError as err:
            print cw(_("Unable to add host (%s) to DHCP configuration: %s") %
                      (full_mac, err))
            return

        if server.is_online():
            try:
                server.control('restart')
            except dhcp.DHCPServerError as err:
                print >> sys.stderr, cw(_("\nUnable to restart the DHCP SMF "
                                          "service: %s\n" % err))
                return
        else:
            print cw(_("\nLocal DHCP configuration complete, but the DHCP "
                       "server SMF service is offline. To enable the "
                       "changes made, enable: %s.\nPlease see svcadm(1M) "
                       "for further information.\n") % 
                       dhcp.DHCP_SERVER_IPV4_SVC)
    else:
        # No local DHCP, tell the user all about their boot configuration
        valid_nets = list(com.get_valid_networks())
        if valid_nets:
            server_ip = valid_nets[0]

        print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, bootfile))

        if len(valid_nets) > 1:
            print cw(_("\nNote: determined more than one IP address "
                       "configured for use with AI. Please ensure the above "
                       "'Boot server IP' is correct.\n"))
Ejemplo n.º 26
0
 def test_arch_formatValue(self):
     '''Ensure that ipv4 criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINipv4', self.ipv4)
     for octet in fmt.split('.'):
         self.assertEqual(octet, str(int(octet)))
Ejemplo n.º 27
0
def list_manifests(service):
    '''Replies to the client with criteria list for a service.
       The output should be similar to installadm list.

    Args
        service - the name of the service being listed

    Returns
        None

    Raises
        None
    '''
    print 'Content-Type: text/html'  # HTML is following
    print  # blank line, end of headers
    print '<html>'
    print '<head>'
    sys.stdout.write('<title>%s %s</title>' %
                     (_('Manifest list for'), service))
    print '</head><body>'

    port = 0
    try:
        smf.AISCF(FMRI="system/install/server")
    except KeyError:
        # report the internal error to error_log and requesting client
        sys.stderr.write(
            _("error:The system does not have the "
              "system/install/server SMF service."))
        sys.stdout.write(
            _("error:The system does not have the "
              "system/install/server SMF service."))
        return
    services = config.get_all_service_names()
    if not services:
        # report the error to the requesting client only
        sys.stdout.write(_('error:no services on this server.\n'))
        return

    found = False
    if config.is_service(service):
        service_ctrl = AIService(service)
        found = True

        # assume new service setup
        path = service_ctrl.database_path
        if os.path.exists(path):
            try:
                aisql = AIdb.DB(path)
                aisql.verifyDBStructure()
            except StandardError as err:
                # report the internal error to error_log and
                # requesting client
                sys.stderr.write(
                    _('error:AI database access '
                      'error\n%s\n') % err)
                sys.stdout.write(
                    _('error:AI database access '
                      'error\n%s\n') % err)
                return

            # generate the list of criteria for the criteria table header
            criteria_header = E.TR()
            for crit in AIdb.getCriteria(aisql.getQueue(), strip=False):
                criteria_header.append(E.TH(crit))

            # generate the manifest rows for the criteria table body
            names = AIdb.getManNames(aisql.getQueue())
            table_body = E.TR()
            allcrit = AIdb.getCriteria(aisql.getQueue(), strip=False)
            colspan = str(max(len(list(allcrit)), 1))
            for manifest in names:

                # iterate through each manifest (and instance)
                for instance in range(
                        0, AIdb.numInstances(manifest, aisql.getQueue())):

                    table_body.append(E.TR())
                    # print the manifest name only once (from instance 0)
                    if instance == 0:
                        href = '../' + service + '/' + manifest
                        row = str(AIdb.numInstances(manifest,
                                                    aisql.getQueue()))
                        table_body.append(
                            E.TD(E.A(manifest, href=href, rowspan=row)))
                    else:
                        table_body.append(E.TD())

                    crit_pairs = AIdb.getManifestCriteria(manifest,
                                                          instance,
                                                          aisql.getQueue(),
                                                          onlyUsed=True,
                                                          humanOutput=True)

                    # crit_pairs is an SQLite3 row object which doesn't
                    # support iteritems(), etc.
                    for crit in crit_pairs.keys():
                        formatted_val = AIdb.formatValue(
                            crit, crit_pairs[crit])
                        # if we do not get back a valid value ensure a
                        # hyphen is printed (prevents "" from printing)
                        if formatted_val and crit_pairs[crit]:
                            table_body.append(
                                E.TD(formatted_val, align="center"))
                        else:
                            table_body.append(
                                E.TD(lxml.etree.Entity("nbsp"),
                                     align="center"))

            # print the default manifest at the end of the table,
            # which has the same colspan as the Criteria List label
            else:
                href = '../' + service + '/default.xml'
                table_body.append(
                    E.TR(
                        E.TD(E.A("Default", href=href)),
                        E.TD(lxml.etree.Entity("nbsp"),
                             colspan=colspan,
                             align="center")))
            web_page = E.HTML(
                E.HEAD(E.TITLE(_("OmniOS Automated "
                                 "Installation Webserver"))),
                E.BODY(
                    E.H1(
                        _("Welcome to the OmniOS "
                          "Automated Installation webserver!")),
                    E.P(
                        _("Service '%s' has the following "
                          "manifests available, served to clients "
                          "matching required criteria.") % service),
                    E.TABLE(E.TR(E.TH(_("Manifest"), rowspan="2"),
                                 E.TH(_("Criteria List"), colspan=colspan)),
                            criteria_header,
                            table_body,
                            border="1",
                            align="center"),
                ))
            print lxml.etree.tostring(web_page, pretty_print=True)

    # service is not found, provide available services on host
    if not found:
        sys.stdout.write(_('Service <i>%s</i> not found.  ') % service)
        sys.stdout.write(_('Available services are:<p><ol><i>'))
        host = socket.gethostname()
        for service_name in config.get_all_service_names():
            # assume new service setup
            port = config.get_service_port(service_name)
            sys.stdout.write(
                '<a href="http://%s:%d/cgi-bin/'
                'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' %
                (host, port, VERSION, service_name, service_name))
        sys.stdout.write('</i></ol>%s' % _('Please select a service '
                                           'from the above list.'))

    print '</body></html>'
Ejemplo n.º 28
0
 def test_mac_formatValue(self):
     '''Ensure that mac criteria is formatted appropriately'''
     fmt = AIdb.formatValue('MINmac', self.mac)
     for k, bits in enumerate(fmt.split(':')):
         self.assertEqual(bits, self.mac[k * 2:(k * 2) + 2])