def test_arch_no_exclude(self): '''Verify arch query string with no exclude option''' criteria = "arch" queue = self.files.database.getQueue() AIdb.getSpecificCriteria(queue, criteria) expect_query = "SELECT arch FROM manifests WHERE arch IS NOT NULL" self.assertEquals(expect_query, self.mockquery.query)
def test_no_match_fqhn_value(self): ''' Test that we don't match a hostname with fqhn in the list''' value = "foo" value_list = "bar foo.com bum" matched = AIdb.match_hostname(value, value_list, 1) self.assertFalse(AIdb.match_hostname(value, value_list, 0)) self.assertEqual(len(matched), 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)
def check_published_manifest(service, dbn, manifest_name): """ Used for checking that a manifest is already published in the install service specified. Checks to make sure manifest exists in the install service's DB, and that the manifest also exists in the install service's published files area. Args: service - service object for service dbn - dbn object of install service to check against. manifest_name - name of manifest to check. Postconditions: None Returns: True if manifest exists in install service False if manifest does not exist. """ # Check if manifest exists in the service's criteria DB. if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(dbn.getQueue()): print(_("Error: install service does not contain the specified " "manifest: %s") % manifest_name) return False # Check if manifest file exists in the service's published area. published_path = os.path.join(service.manifest_dir, manifest_name) if not os.path.exists(published_path): print(_("Error: manifest missing from published area: %s") % published_path) return False return True
def test_match_fqhn_value(self): ''' Test that we match a fqhn value ''' value = "foo.com" value_list = "foo bar bum" matched = AIdb.match_hostname(value, value_list, 1) self.assertTrue(AIdb.match_hostname(value, value_list, 0)) self.assertEqual(matched, "foo")
def validate_internal(profile_list, database, table, image_dir): ''' given a list of profile files and the profile database and table, validate the list of profiles Args: profile_list - list of profile path names database - name of database table - name of database table image_dir - path of service image, used to locate service_bundle Returns True if all profiles are valid, return False if any are invalid ''' # Open the database dbn = AIdb.DB(database, commit=True) dbn.verifyDBStructure() isvalid = True queue = dbn.getQueue() if not profile_list: profile_list = [None] for profile in profile_list: qstr = "SELECT name, file FROM %s WHERE name = %s" % \ (table, AIdb.format_value('name', profile)) query = AIdb.DBrequest(qstr, commit=True) queue.put(query) query.waitAns() # check response, if failure, getResponse prints error if query.getResponse() is None: # database error return False # give up if len(query.getResponse()) == 0: print >> sys.stderr, \ _('No profiles in database with basename ') + profile isvalid = False continue # to the next profile for response in query.getResponse(): if not df.validate_file(response[0], response[1], image_dir): isvalid = False return isvalid
def delete_profiles(profs, dbo, table): ''' deletes all database entries matching user's command line options Args: profs - list of profiles to delete by name dbo - database object table - database table name Returns: True if any errors encountered, False otherwise Exceptions: none ''' # if any serious errors encountered, set exit status has_errors = False queue = dbo.getQueue() # Build a list of criteria for WHERE clause db_cols = [u'rowid'] + [u'file'] # loop through all profiles from command line and delete them for profile_name in profs: query_str = "SELECT " + ", ".join(db_cols) + " FROM " + table + \ " WHERE name=" + AIdb.format_value('name', profile_name) logging.debug("query=" + query_str) query = AIdb.DBrequest(query_str, commit=True) queue.put(query) query.waitAns() # check response, if failure, getResponse prints error rsp = query.getResponse() if rsp is None: has_errors = True continue if len(rsp) == 0: print >> sys.stderr, _("\tProfile %s not found.") % profile_name has_errors = True continue # delete database record and any accompanying internal profile file for response in rsp: deldict = dict() iresponse = iter(response) for crit in db_cols: deldict[crit] = next(iresponse) query_str = "DELETE FROM %s WHERE rowid=%d" % \ (table, deldict['rowid']) delquery = AIdb.DBrequest(query_str, commit=True) queue.put(delquery) delquery.waitAns() # check response, if failure, getResponse prints error if delquery.getResponse() is None: has_errors = True continue print >> sys.stderr, _("\tDeleted profile %s.") % profile_name # delete static (internal) files only if deldict['file'] is None or \ not deldict['file'].startswith(sc.INTERNAL_PROFILE_DIRECTORY): continue try: os.unlink(deldict['file']) except OSError, (errno, errmsg): if errno != ENOENT: # does not exist print >> sys.stderr, _( "Error (%s): Problem deleting %s (%s): %s") \ % (errno, profile_name, deldict['file'], errmsg) has_errors = True continue
def check_published_manifest(service, dbn, manifest_name): """ Used for checking that a manifest is already published in the install service specified. Checks to make sure manifest exists in the install service's DB, and that the manifest also exists in the install service's published files area. Args: service - service object for service dbn - dbn object of install service to check against. manifest_name - name of manifest to check. Postconditions: None Returns: True if manifest exists in install service False if manifest does not exist. """ # Check if manifest exists in the service's criteria DB. if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(dbn.getQueue()): print( _("Error: install service does not contain the specified " "manifest: %s") % manifest_name) return False # Check if manifest file exists in the service's published area. published_path = os.path.join(service.manifest_dir, manifest_name) if not os.path.exists(published_path): print( _("Error: manifest missing from published area: %s") % published_path) return False return True
def test_MAXipv4(self): '''Verify single MAX query string ''' criteria = "MAXipv4" queue = self.files.database.getQueue() AIdb.getSpecificCriteria(queue, criteria) expect_query = "SELECT MAXipv4 FROM manifests WHERE MAXipv4 IS " + \ "NOT NULL" self.assertEquals(expect_query, self.mockquery.query)
def test_arch_exclude(self): '''Verify arch query string with exclude option''' criteria = "arch" queue = self.files.database.getQueue() AIdb.getSpecificCriteria(queue, criteria, excludeManifests=["suexml"]) expect_query = "SELECT arch FROM manifests WHERE arch IS NOT NULL " + \ "AND name IS NOT 'suexml'" self.assertEquals(expect_query, self.mockquery.query)
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_MIN_MAXmem(self): '''Verify mem range query string ''' criteria = "MINmem" criteria2 = "MAXmem" queue = self.files.database.getQueue() AIdb.getSpecificCriteria(queue, criteria, criteria2=criteria2) expect_query = "SELECT MINmem, MAXmem FROM manifests WHERE " + \ "(MINmem IS NOT NULL OR MAXmem IS NOT NULL)" self.assertEquals(expect_query, self.mockquery.query)
def test_MIN_MAXmac(self): '''Verify mac range query string ''' criteria = "MINmac" criteria2 = "MAXmac" queue = self.files.database.getQueue() AIdb.getSpecificCriteria(queue, criteria, criteria2=criteria2) expect_query = "SELECT HEX(MINmac), HEX(MAXmac) FROM manifests " + \ "WHERE (MINmac IS NOT NULL OR MAXmac IS NOT NULL)" self.assertEquals(expect_query, self.mockquery.query)
def send_needed_criteria(port): '''Replies to the old client with the needed criteria Args port - the originating port for the old client Returns None Raises None ''' # Establish the service SQL database based upon the # port number for the service path = os.path.join(com.AI_SERVICE_DIR_PATH, str(port), 'AI.db') if os.path.exists(path): try: aisql = AIdb.DB(path) aisql.verifyDBStructure() except StandardError as err: # internal error, record the error in the server error_log sys.stderr.write(_('error:AI database access error\n%s\n') % err) # report the error to the requesting client print "Content-Type: text/html" # HTML is following print # blank line, end of headers sys.stdout.write(_("error:AI database access error\n%s\n") % err) sys.exit(1) else: # not an internal error, report to the requesting client only print "Content-Type: text/html" # HTML is following print # blank line, end of headers print _("Error:unable to determine criteria " "for service associated with port"), port return # build the required criteria list xml = lxml.etree.Element("CriteriaList") # old version number version_value = lxml.etree.Element("Version") version_value.attrib["Number"] = COMPATIBILITY_VERSION xml.append(version_value) # pull the required criteria from the SQL database for crit in AIdb.getCriteria(aisql.getQueue(), strip=True): tag = lxml.etree.Element("Criteria") tag.attrib["Name"] = crit xml.append(tag) xmlstr = lxml.etree.tostring(xml, pretty_print=True) # report the results print "Content-Length:", len(xmlstr) # Length of XML reply print "Content-Type: text/xml" # XML is following print # blank line, end of headers print xmlstr
def is_name_in_table(name, queue, table): ''' Determine if profile already registered for service and same basename Args: name - profile name queue - database queue for profiles table - profile table name Returns True if any records are found, False if no records found ''' query_str = "SELECT * FROM %s WHERE name='%s'" % \ (table, AIdb.sanitizeSQL(name)) query = AIdb.DBrequest(query_str) queue.put(query) query.waitAns() return len(query.getResponse()) > 0
def do_export_profile(options): ''' Export a profile. ''' save_errno = 0 # Open the database aisql = AIdb.DB(options.service.database_path, commit=True) aisql.verifyDBStructure() queue = aisql.getQueue() for pname in options.pnames: # sanitize and format for SELECT fmtname = AIdb.format_value('name', pname) q_str = "SELECT file FROM " + AIdb.PROFILES_TABLE + \ " WHERE name=" + fmtname query = AIdb.DBrequest(q_str) queue.put(query) query.waitAns() # check response, if failure, getResponse prints error if query.getResponse() is None: continue if len(query.getResponse()) == 0: print >> sys.stderr, _("Profile %s not found.") % fmtname continue for row in query.getResponse(): profpath = row['file'] if options.output_isdir: output_name = "/".join([options.output_name, pname]) else: output_name = options.output_name if output_name == SCREEN and options.file_count > 1: display_file_header(_("profile: ") + pname) try: shutil.copyfile(profpath, output_name) except IOError as err: print >> sys.stderr, _( "Error exporting profile: " "%(error)s: %(file)s") % ({ "error": err.strerror, "file": err.filename }) save_errno = err.errno print return save_errno
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))
def __init__(self, sname): ''' Reads profile names associated with this service from database, creates objects for them and stores these objects into instance attributes ''' ServicePrintObject.__init__(self, sname) self.profiles = list() self.max_profman_len = 0 self.max_crit_len = 0 try: # Read all profiles for this service for name in AIdb.getNames(self.aiqueue, AIdb.PROFILES_TABLE): # Record the longest profile name if self.max_profman_len < len(name): self.max_profman_len = len(name) profile = ProfilePrintObject(self.aiqueue, name) # Record the longest criteria in this service if profile.get_max_crit_len() > self.max_crit_len: self.max_crit_len = profile.get_max_crit_len() self.profiles.append(profile) except StandardError as err: sys.stderr.write(_('Error: AI database access error\n%s\n') % err) raise
def __init__(self, data_loc): self.base_dir = data_loc if os.path.exists(os.path.join(self.base_dir, 'AI.db')): self.AISQL = AIdb.DB(os.path.join(self.base_dir, 'AI.db')) else: raise SystemExit(_("Error:\tNo AI.db database")) self.AISQL.verifyDBStructure()
def test_building_query_str(self): ''' test that we get reasonable query str ''' cri_list = ['MINipv4', 'MAXipv4', 'arch', 'cpu', 'platform', 'MINmac', 'MAXmac', 'MINmem', 'MAXmem', 'MINnetwork', 'MAXnetwork'] # Artificially small list to test filter functionality. all_cri_list = ['arch'] my_crit_dict = { 'ipv4': '020025224125', 'arch': 'i86pc', 'platform': 'myplatform', 'cpu': 'i386', 'network': '010000002000', 'mem': '2048', 'mac': 'aabbccddeeff' } query_str = AIdb.build_query_str(my_crit_dict, cri_list, all_cri_list) self.assertTrue(query_str.startswith("SELECT name")) self.assertTrue("FROM manifests WHERE " in query_str) self.assertTrue("MAXmem >= 2048 OR MAXmem IS NULL" in query_str) self.assertTrue("MINmem <= 2048 OR MINmem IS NULL" in query_str) self.assertTrue("MAXipv4 >= 020025224125" in query_str) self.assertTrue("MINipv4 <= 020025224125" in query_str) self.assertTrue("MAXnetwork >= 010000002000" in query_str) self.assertTrue("MINnetwork <= 010000002000" in query_str) self.assertTrue("HEX(MINmac) <= HEX(x'aabbccddeeff'" in query_str) self.assertTrue("HEX(MAXmac) >= HEX(x'aabbccddeeff'" in query_str) self.assertTrue("is_in_list('arch', 'i86pc', arch, 'None')" \ in query_str) self.assertTrue("is_in_list('platform', 'myplatform', platform, " + "'None')" in query_str) self.assertTrue("NOT ((arch IS NULL)" in query_str) self.assertFalse("(cpu IS NULL)" in query_str) self.assertTrue(query_str.endswith("LIMIT 1"))
def do_delete_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: delete profiles per command line ''' # check for authorization and euid try: check_auth_and_euid(PROFILE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(cmd_options) # get AI service directory, database name service = AIService(options.service_name) dbname = service.database_path # Open the database aisql = AIdb.DB(dbname, commit=True) aisql.verifyDBStructure() # delete profiles per command line errs = delete_profiles(options.profile_name, aisql, AIdb.PROFILES_TABLE) if errs: sys.exit(1)
def __init__(self, sname): ''' Opens database for given service and sets database request queue ''' self.name = sname try: self.service = AIService(sname) except VersionError as err: warn_version(err) raise path = self.service.database_path if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() self.aiqueue = maisql.getQueue() except StandardError as err: sys.stderr.write( _('Error: AI database access error\n%s\n') % err) raise else: sys.stderr.write( _('Error: unable to locate AI database for "%s" ' 'on server\n') % sname) # I can't read from service database and I should raise an error # for this condition. raise StandardError
def do_delete_manifest(cmd_options=None): ''' Delete a manifest from an install service. ''' # check for authorization and euid try: check_auth_and_euid(MANIFEST_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(cmd_options) if not os.path.exists(os.path.join(options.svcdir_path, "AI.db")): raise SystemExit(_("Error: Need a valid AI service directory")) aisql = AIdb.DB(os.path.join(options.svcdir_path, 'AI.db'), commit=True) aisql.verifyDBStructure() try: delete_manifest_from_db(aisql, (options.manifest_name, options.instance), options.service_name, options.svcdir_path) except ValueError as error: raise SystemExit(error)
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))
def __init__(self, aiqueue, name, instance, dbtable): ''' Reads criteria from database and stores them into class attribute Args: aiqueue = database request queue name = either the name of the manifest or the name of the profile to which this set of criteria belongs instance = instance number dbtable = database table, distinguishing manifests from profiles Assumed to be one of AIdb.MANIFESTS_TABLE or AIdb.PROFILES_TABLE ''' # Set to True if there is at least one non-empty criteria self.has_crit = False # Store criteria in dictionary self.crit = dict() # Initialize length of the longest criteria to length of 'None' word self.max_crit_len = len(_('None')) # We need non-human output to be able to distiguish empty criteria criteria = AIdb.getTableCriteria(name, instance, aiqueue, dbtable, humanOutput=False, onlyUsed=True) if criteria is not None: for key in criteria.keys(): if criteria[key] is not None: self.has_crit = True break if self.has_crit: # We need criteria in human readable form to be able to # print it hrcrit = AIdb.getTableCriteria(name, instance, aiqueue, dbtable, humanOutput=True, onlyUsed=True) # convert criteria into printable values self.get_criteria_info(hrcrit)
def add_profile(criteria, profile_name, profile_file, queue, table): """ Set a profile record in the database with the criteria provided. Args: criteria - criteria object profile_name - name of profile to add profile_file - path of profile to add queue - database request queue table - profile table in database Returns: True if successful, false otherwise Effects: database record added stored resulting profile in internal profile directory """ # get lists prepared for SQLite WHERE, INSERT VALUES from command line (wherel, insertl, valuesl) = \ sc.sql_values_from_criteria(criteria, queue, table) # clear any profiles exactly matching the criteria wherel += ["name=" + AIdb.format_value('name', profile_name)] q_str = "DELETE FROM " + table + " WHERE " + " AND ".join(wherel) query = AIdb.DBrequest(q_str, commit=True) queue.put(query) query.waitAns() if query.getResponse() is None: return False # add profile to database insertl += ["name"] valuesl += [AIdb.format_value('name', profile_name)] insertl += ["file"] valuesl += [AIdb.format_value('name', profile_file)] q_str = "INSERT INTO " + table + "(" + ", ".join(insertl) + \ ") VALUES (" + ", ".join(valuesl) + ")" query = AIdb.DBrequest(q_str, commit=True) queue.put(query) query.waitAns() if query.getResponse() is None: return False print >> sys.stderr, _('Profile %s added to database.') % profile_name return True
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
def test_ipv4_match(self): ''' test manifest match on ipv4 ''' my_crit_dict = { 'ipv4': '020000000025', 'arch': 'i86pc', 'platform': 'myplatform', 'cpu': 'i386', 'network': '010000002000', 'mem': '2048', 'mac': 'aabbccddeef0' } manifest = AIdb.findManifest(my_crit_dict, self.aidb) self.assertEqual(manifest, "ipv4_max_unbound")
def test_cpu_match(self): ''' test manifest match on cpu ''' my_crit_dict = { 'ipv4': '010000000225', 'arch': 'sparc', 'platform': 'otherplatform', 'cpu': 'i386', 'network': '010000002000', 'mem': '2048', 'mac': 'aabbccddeef0' } manifest = AIdb.findManifest(my_crit_dict, self.aidb) self.assertEqual(manifest, "cpu_man")
def test_manifest_nomatch(self): ''' test that findManifest returns 0 for no matching manifest ''' my_crit_dict = { 'ipv4': '010000000225', 'arch': 'sparc', 'platform': 'otherplatform', 'cpu': 'sun4v', 'network': '010000002100', 'mem': '3000', 'mac': 'bbbbccddeef0' } manifest = AIdb.findManifest(my_crit_dict, self.aidb) self.assertEquals(manifest, None)
def test_mem_match(self): ''' test manifest match on mem ''' my_crit_dict = { 'ipv4': '010000000225', 'arch': 'sparc', 'platform': 'otherplatform', 'cpu': 'sun4v', 'network': '010000002100', 'mem': '2048', 'mac': 'bbbbccddeef0' } manifest = AIdb.findManifest(my_crit_dict, self.aidb) self.assertEqual(manifest, "mem_min_unbound")
def test_unique_criteria_match(self): ''' test manifest match on mac and ipv4 value ''' my_crit_dict = { 'ipv4': '020000000025', 'arch': 'i86pc', 'platform': 'myplatform', 'cpu': 'i386', 'network': '010000002000', 'mem': '2048', 'mac': 'aabbccddeeff' } manifest = AIdb.findManifest(my_crit_dict, self.aidb) self.assertEqual(manifest, "mac_ipv4_man")
def do_export_profile(options): ''' Export a profile. ''' save_errno = 0 # Open the database aisql = AIdb.DB(options.service.database_path, commit=True) aisql.verifyDBStructure() queue = aisql.getQueue() for pname in options.pnames: # sanitize and format for SELECT fmtname = AIdb.format_value('name', pname) q_str = "SELECT file FROM " + AIdb.PROFILES_TABLE + \ " WHERE name=" + fmtname query = AIdb.DBrequest(q_str) queue.put(query) query.waitAns() # check response, if failure, getResponse prints error if query.getResponse() is None: continue if len(query.getResponse()) == 0: print >> sys.stderr, _("Profile %s not found.") % fmtname continue for row in query.getResponse(): profpath = row['file'] if options.output_isdir: output_name = "/".join([options.output_name, pname]) else: output_name = options.output_name if output_name == SCREEN and options.file_count > 1: display_file_header(_("profile: ") + pname) try: shutil.copyfile(profpath, output_name) except IOError as err: print >> sys.stderr, _("Error exporting profile: " "%(error)s: %(file)s") % ( {"error": err.strerror, "file": err.filename}) save_errno = err.errno print return save_errno
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 test_match_case_sensitive(self): ''' Test that we match a value based on a case senstive criteria ''' value = "FoO" value_list = "bar FoO blah" self.assertTrue(AIdb.is_in_list('zonename', value, value_list, None))
def test_no_match_case_sensitive(self): ''' Test that we don't match a value on the wrong case.''' value = "FoO" value_list = "bar foo blah" self.assertFalse(AIdb.is_in_list('zonename', value, value_list, None))
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"))
def get_manifest_or_profile_names(services, dbtable): """ Iterate through the services retrieving all the stored manifest or profile names. Args services = dictionary of service properties dbtable = database table, distinguishing manifests from profiles Returns a dictionary of service manifests or profiles within a list: { servicename1: [ [name, has_criteria (boolean), {crit:value, ... }], ... ], ... } the width of the longest service name (swidth) the width of the longest manifest name (mwidth) the width of the longest criteria (cwidth) Raises None """ swidth = 0 mwidth = 0 cwidth = 0 sdict = dict() for sname in sorted(services.keys()): try: service = AIService(sname) except VersionError as err: warn_version(err) continue path = service.database_path if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() aiqueue = maisql.getQueue() swidth = max(len(sname), swidth) if not AIdb.tableExists(aiqueue, dbtable): continue for name in AIdb.getNames(aiqueue, dbtable): mwidth = max(len(name), mwidth) tdict = dict() if dbtable == 'manifests': instances = AIdb.numInstances(name, aiqueue) for instance in range(0, instances): criteria = AIdb.getTableCriteria(name, instance, aiqueue, dbtable, humanOutput=False, onlyUsed=True) has_criteria = False if criteria is not None: for key in criteria.keys(): if criteria[key] is not None: has_criteria = True break if has_criteria: # We need criteria in human readable form hrcrit = AIdb.getTableCriteria(name, instance, aiqueue, dbtable, humanOutput=True, onlyUsed=True) tdict, twidth = get_criteria_info(hrcrit) cwidth = max(twidth, cwidth) else: criteria = AIdb.getTableCriteria(name, None, aiqueue, dbtable, humanOutput=False, onlyUsed=True) has_criteria = False if criteria is not None: for key in criteria.keys(): if criteria[key] is not None: has_criteria = True break if sname in sdict: slist = sdict[sname] slist.append([name, has_criteria, tdict]) sdict[sname] = slist else: sdict[sname] = [[name, has_criteria, tdict]] except StandardError as err: sys.stderr.write(_('Error: AI database access error\n%s\n') % err) continue else: sys.stderr.write(_('Error: unable to locate AI database for "%s" ' 'on server\n') % sname) continue return sdict, swidth, mwidth, cwidth
def prepValuesAndRanges(criteriaRoot, database, table=AIdb.MANIFESTS_TABLE): # ============================================================================= """ Processes criteria manifest data already read into memory but before it is stored in the AI database. Does the following: - When a criterion range of one is given by a single <value>, morph that value so it can be stored in the database as a <range>. - Pad the digit strings of MAC addresses so that the six values between the colons are two hex digits each. - Pad the digit strings of IP addresses so that the four values between the dots are three digits each and between 0 and 255. - Strip off colons from MAC addresses and dots from IPv4 addresses. Args: - criteriaRoot: Tree root for criteria manifest. This is where data is checked / modified. - database: Used to find which criteria are range criteria. Returns: Nothing. However, data may be checked and modified per above. Raises: - Exception: ValueError - a range criteria provided as a list of values - a non-range criteria provided as a range - Exceptions raised by database calls, and calls to - checkIPv4() - checkMAC() """ # ============================================================================= # Find from the database which criteria are range criteria. # Range criteria named xxx have names bounds values MINxxx and MAXxxx. # Assume that MINxxx is a good enough check. # All criteria names in database are stored as lower case, except # for their "MIN" and "MAX" prefixes. range_crit = list() for crit_name in AIdb.getCriteria(database.getQueue(), table, onlyUsed=False, strip=False): if (crit_name.startswith("MIN")): range_crit.append(crit_name.replace("MIN", "", 1)) # Loop through all criteria elements. for crit in criteriaRoot.findall('.//ai_criteria'): crit_name = crit.attrib['name'].lower() val_range = crit.getchildren()[0] # <range>'s here are a single element with a single # string containing two space-separated values for MIN and MAX # <value>'s here are a single element with a single # string containing one value or a space-separated list of values. value_list = val_range.text.split() num_values = len(value_list) # val_range.tag will be either 'value' or 'range'. # This is syntactically validated by the schema. if val_range.tag == "value": # Allow a list of values to be provided with the 'value' tag. # However, for criteria that can be provided as a range, we # currently do not support lists for them. if num_values > 1 and crit_name in range_crit: raise ValueError("Criteria '" + crit_name + "' is not " "supported to be provided as a list of values") else: # For ranges, make sure it is indeed a range criteria if crit_name not in range_crit: raise ValueError("Criteria '" + crit_name + "' can not " "be passed as a range pair") # For value criteria, there is no need to do anything to store # single value into val_range.text. It is already there. # Just check architecture and cpu values because it is common to # confuse i86pc and i386 or sparc and sun4v if crit_name == "arch": for one_value in value_list: try: checkArch(one_value) except ValueError as err: # Just print warning print >> sys.stderr, err elif crit_name == "cpu": for one_value in value_list: try: checkCPU(one_value) except ValueError as err: # Just print warning print >> sys.stderr, err # # For some types supported by range criteria, some additional # format checking is needed. Also, range criteria that are passed # as single values need to be split into a range where min=max. # Current criterion is a range criterion. if range_crit.count(crit_name) > 0: # Each value will have already been checked against the # schema. IPv4 values will be 4 numbers ranging from # 0-255, separated by dots. MAC values will be 6 hex # numbers ranging from 0-FF, separated by colons. # There may be one or two values. new_values = "" for one_value in value_list: # Space between (range) values. if new_values != "": new_values += " " # Handle "unbounded" keyword; and pass lowercase lowered_value = one_value.lower() if lowered_value == "unbounded": new_values += lowered_value # Handle IPv4 addressses. elif crit_name == "ipv4" or crit_name == "network": new_values += checkIPv4(one_value) # Handle MAC addresses. elif crit_name == "mac": new_values += checkMAC(one_value) # Handle everything else by passing through. else: new_values += one_value # Single values which come in under a "value" # tag but represent a range (e.g. a single ipv4 # value) are "converted" into the form a range # value pair would take (a single string # consisting of two items) where # the min value = max value. if val_range.tag == "value": # Change to a range. # Set min = max = value. val_range.tag = "range" val_range.text = \ new_values + " " + new_values elif val_range.tag == "range": # values will be a list of 2 already. val_range.text = new_values
def insert_SQL(files): """ Ensures all data is properly sanitized and formatted, then inserts it into the database Args: None Returns: None """ query = "INSERT INTO manifests VALUES(" # add the manifest name to the query string query += "'" + AIdb.sanitizeSQL(files.manifest_name) + "'," # check to see if manifest name is already in database (affects instance # number) if AIdb.sanitizeSQL(files.manifest_name) in \ AIdb.getManNames(files.database.getQueue()): # database already has this manifest name get the number of # instances instance = AIdb.numInstances(AIdb.sanitizeSQL(files.manifest_name), files.database.getQueue()) # this a new manifest else: instance = 0 # actually add the instance to the query string query += str(instance) + "," # 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(files.database.getQueue(), onlyUsed=False, strip=False): # for range values trigger on the MAX criteria (skip the MIN's # arbitrary as we handle rows in one pass) if crit.startswith('MIN'): continue # get the values from the manifest values = files.criteria[crit.replace('MAX', '', 1)] # If the critera manifest didn't specify this criteria, fill in NULLs if values is None: # use the criteria name to determine if this is a range if crit.startswith('MAX'): query += "NULL,NULL," # this is a single value else: query += "NULL," # Else if this is a value criteria (not a range), insert the value # as a space-separated list of values which will account for the case # where a list of values have been given. elif not crit.startswith('MAX'): # Join the values of the list with a space separator. query += "'" + AIdb.sanitizeSQL(" ".join(values)) + "'," # else values is a range else: for value in values: # translate "unbounded" to a database NULL if value == "unbounded": query += "NULL," # we need to deal with mac addresses specially being # hexadecimal elif crit.endswith("mac"): # need to insert with hex operand x'<val>' # use an upper case string for hex values query += "x'" + AIdb.sanitizeSQL(str(value).upper()) + \ "'," else: query += AIdb.sanitizeSQL(str(value).upper()) + "," # strip trailing comma and close parentheses query = query[:-1] + ")" # update the database query = AIdb.DBrequest(query, commit=True) files.database.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)
def get_mfest_or_profile_criteria(sname, services, dbtable): """ Iterate through all the manifests or profiles for the named service (sname) pointed to by the SCF service. Args sname = service name services = config.get_all_service_props() dbtable = database table, distinguishing manifests from profiles Assumed to be one of AIdb.MANIFESTS_TABLE or AIdb.PROFILES_TABLE Returns a dictionary of the criteria for the named service within a list: { servicename1:[ { 'arch':arch1, 'mem':memory1, 'ipv4':ipaddress1, 'mac':macaddr1, 'platform':platform1, 'network':network1, 'cpu':cpu1, 'zonename':z1 }, ... ] } * Note1: platform, network and cpu are currently not-implemented upstream. * Note2: could simply use a list of dictionaries but implemented as a dictionary of a list of dictionary which will allow for multiple services to be listed at the same time. width of longest manifest or profile name width of longest criteria Raises None """ sdict = dict() width = 0 cwidth = 0 # ensure the named service is in our service dictionary. lservices = services.keys() if sname in lservices: try: path = AIService(sname).database_path except VersionError as version_err: warn_version(version_err) return sdict, width, cwidth if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() aiqueue = maisql.getQueue() if dbtable == AIdb.MANIFESTS_TABLE: for name in AIdb.getNames(aiqueue, dbtable): sdict[name] = list() instances = AIdb.numInstances(name, aiqueue) for instance in range(0, instances): width = max(len(name), width) criteria = AIdb.getManifestCriteria(name, instance, aiqueue, humanOutput=True, onlyUsed=True) if criteria: tdict, twidth = get_criteria_info(criteria) cwidth = max(twidth, cwidth) sdict[name].append(tdict) elif dbtable == AIdb.PROFILES_TABLE: for name in AIdb.getNames(aiqueue, dbtable): sdict[name] = list() criteria = AIdb.getProfileCriteria(name, aiqueue, humanOutput=True, onlyUsed=True) width = max(len(name), width) tdict, twidth = get_criteria_info(criteria) cwidth = max(twidth, cwidth) sdict[name].append(tdict) else: raise ValueError("Invalid value for dbtable: %s" % dbtable) except StandardError as err: sys.stderr.write(_('Error: AI database access error\n%s\n') % err) sys.exit(1) else: sys.stderr.write(_('Error: unable to locate AI database on server ' 'for %s\n') % sname) sys.exit(1) return sdict, width, cwidth