def script_url_to_source(callingUrl): parse_url = lib_util.survol_urlparse(callingUrl) query = parse_url.query params = parse_qs(query) xidParam = params['xid'][0] # sys.stdout.write("script_url_to_source xidParam=%s\n"%xidParam) (entity_type,entity_id,entity_host) = lib_util.ParseXid(xidParam) # sys.stdout.write("script_url_to_source entity_id=%s\n"%entity_id) entity_id_dict = lib_util.SplitMoniker(entity_id) # sys.stdout.write("entity_id_dict=%s\n"%str(entity_id_dict)) # parse_url.path=/LocalExecution/sources_types/Win32_UserAccount/Win32_NetUserGetInfo.py # This is a very simple method to differentiate local from remote scripts if parse_url.path.startswith(lib_util.prefixLocalExecution): # This also chops the leading slash. pathScript = parse_url.path[len(lib_util.prefixLocalExecution) + 1:] objSource = SourceLocal(pathScript,entity_type,**entity_id_dict) # Note: This should be True: parse_url.netloc.startswith("LOCAL_MODE") else: objSource = SourceRemote(callingUrl,entity_type,**entity_id_dict) return objSource
def GetId(self): sys.stderr.write("GetId m_entity_type=%s m_entity_id=%s\n" % ( self.m_entity_type, str( self.m_entity_id ) ) ) try: # If this is a top-level url, no object type, therefore no id. if self.m_entity_type == "": return "" splitKV = lib_util.SplitMoniker(self.m_entity_id) sys.stderr.write("GetId splitKV=%s\n" % ( str( splitKV ) ) ) # If this class is defined in our ontology, then we know the first property. entOnto = lib_util.OntologyClassKeys(self.m_entity_type) if entOnto: keyFirst = entOnto[0] # Only if this mandatory key is in the dict. try: return splitKV[keyFirst] except KeyError: # This is a desperate case... pass # Returns the first value but this is not reliable at all. for key in splitKV: return splitKV[key] except KeyError: pass # If no parameters although one was requested. self.EditionMode() return ""
def entity_id_to_instance(agent_url, class_name, entity_id): """This receives the URL of an object, its class and the moniker. It splits the moniker in key-value pairs. These are used to create a CIM object with key-value pairs transformed in attributes. Example: xid="CIM_Process.Handle=2092" BEWARE: Some arguments should be decoded from Base64.""" xid_dict = lib_util.SplitMoniker(entity_id) new_instance = create_CIM_class(agent_url, class_name, **xid_dict) return new_instance
def WmiReadWithQuery(cgiEnv, connWmi, className): """ Maybe reading with the moniker does not work because not all properties. This splits the moniker into key value paris, and uses a WQL query. """ splitMonik = lib_util.SplitMoniker(cgiEnv.m_entity_id) aQry = lib_util.SplitMonikToWQL(splitMonik, className) try: return connWmi.query(aQry) except Exception: exc = sys.exc_info()[1] lib_common.ErrorMessageHtml("Query=%s Caught:%s" % (aQry, str(exc)))
def get_instance_bag_of_words(self): # TODO: And the host ? bagOfWords = set(self.__class__.__name__) # This is the minimal set of words. dictIds = lib_util.SplitMoniker( self.m_entity_id ) for keyId in dictIds: bagOfWords.add(keyId) valId = dictIds[keyId] bagOfWords.add(valId) # TODO: Call AddInfo() return bagOfWords
def _define_class_in_ontology(url_node): """This takes the class from an Url and defines it in the RDF ontology. This returns the class name as a string.""" entity_label, class_name, entity_id = lib_naming.ParseEntityUri( url_node) # This could be: ("http://the_host", "http://primhillcomputers.com/survol/____Information", "HTTP url") if not class_name: return None # TODO: Define base classes with rdfs:subClassOf / RDFS.subClassOf # "base_class" and "class_description' ??? # A class name with the WMI namespace might be produced with this kind of URL: # "http://www.primhillcomputers.com/survol#root\CIMV2:CIM_Process" class_name = class_name.replace("\\", "%5C") assert isinstance(class_name, str) if class_name not in map_classes: if class_name == "": raise Exception("No class name for url=%s type=%s" % (str(url_node), str(type(url_node)))) # Maybe this CIM class is not defined as an RDFS class. # This function might also filter duplicate and redundant insertions. lib_util.AppendClassSurvolOntology(class_name, map_classes, map_attributes) # The entity_id is a concatenation of CIM property-value paris, and define an unique object. # They are different of the triples, but might overlap. entity_id_dict = lib_util.SplitMoniker(entity_id) for predicate_key in entity_id_dict: if predicate_key not in map_attributes: # This function might also filter a duplicate and redundant insertion. lib_util.append_property_survol_ontology( predicate_key, "CIM key predicate %s" % predicate_key, class_name, None, map_attributes) # This value is explicitly added to the node. predicate_value = entity_id_dict[predicate_key] new_grph.add((url_node, lib_properties.MakeProp(predicate_key), rdflib.Literal(predicate_value))) # This adds a triple specifying that this node belongs to this RDFS class. lib_kbase.add_node_to_rdfs_class(new_grph, url_node, class_name, entity_label) return class_name
def entity_to_label(entity_type, entity_ids_concat, force_entity_ip_addr): """ This returns the label of an URL which is a script plus CGI arguments defining an object. For an association, we might have: entity_id=Dependent=root/cimv2:LMI_StorageExtent.CreationClassName="LMI_StorageExtent",SystemCreationClassName="PG_ComputerSystem" Antecedent=root/cimv2:LMI_DiskDrive.CreationClassName="LMI_DiskDrive",DeviceID="/dev/sda" This is not easy to manage but avoids ambiguities. """ # Specific case of objtypes.py if not entity_ids_concat: return entity_type # TODO: Robust logic as long as the value does not contain an '=' sign. split_kv = lib_util.SplitMoniker(entity_ids_concat) # Now build the array of values in the ontology order. onto_keys = lib_util.OntologyClassKeys(entity_type) # Default value if key is missing. entity_ids_arr = [split_kv.get(key_onto, key_onto + "?") for key_onto in onto_keys] if force_entity_ip_addr: entity_label = _entity_array_to_alias(entity_type, entity_ids_arr, force_entity_ip_addr) else: entity_label = _entity_array_to_label(entity_type, entity_ids_arr) # There might be extra properties which are not in our ontology. # This happens if duplicates from WBEM or WMI. MAKE THIS FASTER ? # Both must be sets, otherwise unsupported operation. # TODO: This set could be created once and for all. But the original order must be kept. set_onto_keys = set(onto_keys) # This appends the keys which are not part of the normal ontology, therefore bring extra information. # This is rather slow and should normally not happen. for ext_prp_key, ext_prp_val in split_kv.items(): if not ext_prp_key in set_onto_keys: entity_label += " %s=%s" % (ext_prp_key, ext_prp_val) return entity_label
def EntityToLabel(entity_type, entity_ids_concat, entity_host, force_entity_ip_addr): # sys.stderr.write("EntityToLabel entity_id=%s entity_type=%s\n" % ( entity_ids_concat, entity_type ) ) # Specific case of objtypes.py if not entity_ids_concat: return entity_type # TODO: Robust logic as long as the value does ot contain an '=' sign. splitKV = lib_util.SplitMoniker(entity_ids_concat) # Now build the array of values in the ontology order. ontoKeys = lib_util.OntologyClassKeys(entity_type) # Default value if key is missing. entity_ids_arr = [ splitKV.get(keyOnto, keyOnto + "?") for keyOnto in ontoKeys ] if force_entity_ip_addr: entity_label = EntityArrToAlias(entity_type, entity_ids_arr, entity_host, force_entity_ip_addr) else: entity_label = EntityArrToLabel(entity_type, entity_ids_arr, entity_host) # sys.stderr.write("EntityToLabel entity_label=%s\n" % entity_label ) # There might be extra properties which are not in our ontology. # This happens if duplicates from WBEM or WMI. MAKE THIS FASTER ? # Both must be sets, otherwise unsupported operation. # TODO: This set could be created once and for all. But the original order must be kept. setOntoKeys = set(ontoKeys) # This appends the keys which are not part of the normal ontology, therefore bring extra information. # This is rather slow and should normally not happen. for (extPrpKey, extPrpVal) in splitKV.items(): if not extPrpKey in setOntoKeys: entity_label += " %s=%s" % (extPrpKey, extPrpVal) return entity_label
if className == "": lib_common.ErrorMessageHtml("No class name. entity_id=%s" % entity_id) grph = cgiEnv.GetGraph() conn = lib_wbem.WbemConnection(cimomUrl) rootNode = lib_util.EntityClassNode(className, nameSpace, cimomUrl, "WBEM") klaDescrip = lib_wbem.WbemClassDescription(conn, className, nameSpace) if not klaDescrip: klaDescrip = "Undefined class %s %s" % (nameSpace, className) grph.add( (rootNode, pc.property_information, lib_common.NodeLiteral(klaDescrip))) splitMonik = lib_util.SplitMoniker(cgiEnv.m_entity_id) sys.stderr.write("entity_wbem.py nameSpace=%s className=%s cimomUrl=%s\n" % (nameSpace, className, cimomUrl)) # This works: # conn = pywbem.WBEMConnection("http://192.168.0.17:5988",("pegasus","toto")) # conn.ExecQuery("WQL","select * from CIM_System","root/cimv2") # conn.ExecQuery("WQL",'select * from CIM_Process where Handle="4125"',"root/cimv2") # # select * from CIM_Directory or CIM_DataFile does not return anything. # If ExecQuery is not supported like on OpenPegasus, try to build one instance. def WbemPlainExecQuery(conn, className, splitMonik, nameSpace): aQry = lib_util.SplitMonikToWQL(splitMonik, className)
def __init__(self, parameters = {}, can_process_remote = False ): # TODO: This value is read again in OutCgiRdf, we could save time by making this object global. sys.stderr.write( "CgiEnv parameters=%s\n" % ( str(parameters) ) ) # TODO: When running from cgiserver.py, and if QUERY_STRING is finished by a dot ".", this dot # TODO: is removed. Workaround: Any CGI variable added after. # TODO: Also: Several slashes "/" are merged into one. # TODO: Example: "xid=http://192.168.1.83:5988/." becomes "xid=http:/192.168.1.83:5988/" # TODO: ... or "xx.py?xid=smbshr.Id=////WDMyCloudMirror///rchateau" become "xx.py?xid=smbshr.Id=/WDMyCloudMirror/rchateau" # TODO: Replace by "xid=http:%2F%2F192.168.1.83:5988/." # Maybe a bad collapsing of URL ? # sys.stderr.write("QUERY_STRING=%s\n" % os.environ['QUERY_STRING'] ) mode = lib_util.GuessDisplayMode() # Contains the optional arguments, needed by calling scripts. self.m_parameters = parameters self.m_parameterized_links = dict() # When in merge mode, the display parameters must be stored in a place accessible by the graph. docModuAll = GetCallingModuleDoc() # Take only the first non-empty line. See lib_util.FromModuleToDoc() docModuSplit = docModuAll.split("\n") self.m_page_title = docModuSplit[0] # Title page contains __doc__ plus object label. callingUrl = lib_util.RequestUri() self.m_calling_url = callingUrl sys.stderr.write("CgiEnv m_page_title=%s m_calling_url=%s\n"%(self.m_page_title,self.m_calling_url)) parsedEntityUri = lib_naming.ParseEntityUri(callingUrl,longDisplay=False,force_entity_ip_addr=None) if parsedEntityUri[2]: # If there is an object to display. # Practically, we are in the script "entity.py" and the single doc string is "Overview" fullTitle = parsedEntityUri[0] self.m_page_title += " " + fullTitle # We assume there is an object, and therefore a class and its description. entity_class = parsedEntityUri[1] # Similar code in objtypes.py entity_module = lib_util.GetEntityModule(entity_class) entDoc = entity_module.__doc__ # The convention is the first line treated as a title. if entDoc: self.m_page_title += "\n" + entDoc # If we can talk to a remote host to get the desired values. # Global CanProcessRemote has precedence over parameter can_process_remote # whcih should probably be deprecated, although they do not have exactly the same role: # * Global CanProcessRemote is used by entity.py to display scripts which have this capability. # * Parameter can_process_remote is used to inform, at execution time, of this capability. # Many scripts are not enumerated by entity.py so a global CanProcessRemote is not necessary. # For clarity, it might be fine to replace the parameter can_process_remote by the global value. # There cannot be nasty consequences except that some scripts might not be displayed # when they should be, and vice-versa. try: globalCanProcessRemote = globals()["CanProcessRemote"] except KeyError: globalCanProcessRemote = False if can_process_remote != globalCanProcessRemote: # sys.stderr.write("INCONSISTENCY CanProcessRemote\n") # ... which is not an issue. can_process_remote = True self.m_can_process_remote = can_process_remote self.m_arguments = cgi.FieldStorage() (self.m_entity_type,self.m_entity_id,self.m_entity_host) = self.GetXid() sys.stderr.write("CgiEnv m_entity_type=%s m_entity_id=%s m_entity_host=%s\n"%(self.m_entity_type,self.m_entity_id,self.m_entity_host)) self.m_entity_id_dict = lib_util.SplitMoniker(self.m_entity_id) # Depending on the caller module, maybe the arguments should be 64decoded. See "sql/query". # As the entity type is available, it is possible to import it and check if it encodes it arguments. # See presence of source_types.sql.query.DecodeCgiArg(keyWord,cgiArg) for example. # This is probably too generous to indicate a local host. self.TestRemoteIfPossible(can_process_remote) # TODO: HOW WILL WE RESTORE THE ORIGINAL DISPLAY MODE ? if mode == "edit": self.EditionMode()
def __init__(self, parameters=None, can_process_remote=False, layout_style="", collapsed_properties=None): # It is possible to run these scripts as CGI scripts, so this transforms # command line arguments into CGI arguments. This is very helpful for debugging. # The HTTP server can set the logging level with the environment variable SURVOL_LOGGING_LEVEL. try: logging_level = os.environ["SURVOL_LOGGING_LEVEL"] logging.getLogger().setLevel(logging_level) logging.info("logging_level set with SURVOL_LOGGING_LEVEL=%s" % logging_level) except KeyError: logging.info("logging_level is not forced with SURVOL_LOGGING_LEVEL.") lib_command_line.command_line_to_cgi_args() assert "QUERY_STRING" in os.environ # Some limitations of cgiserver.py and Python2: # TODO: When running from cgiserver.py, and if QUERY_STRING is finished by a dot ".", this dot # TODO: is removed. Workaround: Any CGI variable added after. # TODO: Also: Several slashes "/" are merged into one. # TODO: Example: "xid=http://192.168.1.83:5988/." becomes "xid=http:/192.168.1.83:5988/" # TODO: ... or "xx.py?xid=smbshr.Id=////WDMyCloudMirror///jsmith" ... # TODO: ... becomes "xx.py?xid=smbshr.Id=/WDMyCloudMirror/jsmith" # TODO: Replace by "xid=http:%2F%2F192.168.1.83:5988/." mode = lib_util.GuessDisplayMode() logging.debug("mode=%s" % mode) # Contains the optional arguments of the script, entered as CGI arguments.. self.m_parameters = parameters if parameters else {} self.m_parameterized_links = dict() self.m_layout_style = layout_style self.m_collapsed_properties = collapsed_properties if collapsed_properties else [] # When in merge mode, the display parameters must be stored in a place accessible by the graph. doc_modu_all = _get_calling_module_doc() # Take only the first non-empty line. See lib_util.FromModuleToDoc() self.m_page_title, self.m_page_subtitle = lib_util.SplitTextTitleRest(doc_modu_all) # Title page contains __doc__ plus object label. # Example: REQUEST_URI=/Survol/survol/print_environment_variables.py # This does NOT contain the host and the port, which implies a confusion if severl Survol agents # use the same database. It makes sense, because the result should not depend in the agent. self.m_calling_url = lib_util.RequestUri() self.m_url_without_mode = lib_util.url_mode_replace(self.m_calling_url, "") full_title, entity_class, entity_id, entity_host = lib_naming.parse_entity_uri_with_host( self.m_calling_url, long_display=False, force_entity_ip_addr=None) # Here, the commas separating the CGI arguments are intact, but the commas in the arguments are encoded. entity_id_dict = lib_util.SplitMoniker(entity_id) self._concatenate_entity_documentation(full_title, entity_class, entity_id) # Global CanProcessRemote has precedence over parameter can_process_remote # which should probably be deprecated, although they do not have exactly the same role: # * Global CanProcessRemote is used by entity.py to display scripts which have this capability. # * Parameter can_process_remote is used to inform, at execution time, of this capability. # Many scripts are not enumerated by entity.py so a global CanProcessRemote is not necessary. # For clarity, it might be fine to replace the parameter can_process_remote by the global value. # There cannot be nasty consequences except that some scripts might not be displayed # when they should be, and vice-versa. try: globalCanProcessRemote = globals()["CanProcessRemote"] except KeyError: globalCanProcessRemote = False if can_process_remote != globalCanProcessRemote: # "INCONSISTENCY CanProcessRemote ... which is not an issue. can_process_remote = True self.m_can_process_remote = can_process_remote self.m_arguments = cgi.FieldStorage() self.m_entity_type = entity_class self.m_entity_id = entity_id self.m_entity_host = entity_host self.m_entity_id_dict = entity_id_dict self._create_or_get_graph() # Depending on the caller module, maybe the arguments should be 64decoded. See "sql/query". # As the entity type is available, it is possible to import it and check if it encodes it arguments. # See presence of source_types.sql.query.DecodeCgiArg(keyWord,cgiArg) for example. # This is probably too generous to indicate a local host. self.test_remote_if_possible(can_process_remote) if mode == "edit": self.enter_edition_mode() logging.critical("Should not be here because the HTML form is displayed.") assert False # Scripts which can run as events feeders must have their name starting with "events_feeder_". # This allows to use CGI programs as events genetors not written in Python. # TODO: Using the script name is enough, the module is not necessary. full_script_path, _, _ = self.m_calling_url.partition("?") script_basename = os.path.basename(full_script_path) daemonizable_script = os.path.basename(script_basename).startswith("events_feeder_") if not daemonizable_script: # This would be absurd to have a normal CGI script started in this mode. assert mode != "daemon", "Script is not an events generator:" + self.m_calling_url # Runs as usual as a CGI script. The script will fill the graph. return # The events graph must be specified because, from here, everything will access the events graph. set_events_credentials() # Maybe this is in the daemon. if mode == "daemon": # Just runs as usual. At the end of the script, OutCgiRdf will write the RDF graph in the events. # Here, this process is started by the supervisor process; It is not started by the HTTP server, # in CGI or WSGI. return try: # This may throw "[Errno 111] Connection refused" is_daemon_running = lib_daemon.is_events_feeder_daemon_running(self.m_url_without_mode) except Exception as exc: # Then display the content in snapshot mode, which is better than nothing. self.report_error_message("Cannot start daemon, caught:%s\n" % exc) logging.error("Cannot start daemon: When getting daemon status, caught:%s" % exc) return if not is_daemon_running: # This is the case of a daemonizable script, normally run. # TODO: Slight ambiguity here: The daemon could be intentionally stopped, and the user # TODO: would like to see the existing events stored in the persistent triplestore, # TODO: without restarting the daemon. We do not know how to do this yet. lib_daemon.start_events_feeder_daemon(self.m_url_without_mode) # After that, whether the daemon dedicated to the script and its parameters is started or not, # the script is then executed in normal, snapshot mode, as a CGI script. else: # Events are probably stored in the big events graph. The host and port are not used in the URL. lib_kbase.read_events_to_graph(self.m_url_without_mode, self.m_graph) # TODO: The layout parameters and any other display parameters of the calling script # TODO: must be in the constructor. # TODO: This, because the rest of the script is not executed. self.OutCgiRdf() # The rest of the script must not be executed because daemon scripts are organised so that # when the daemon is started, it writes all events in the database, to be read by the same script # run in CGI or WSGI. # The snapshot part of a daemon script is executed only when the deamon is not started. logging.info("Events are read from the events database because the deamon is running.") if _is_wsgi(): logging.info("Leaving the execution of the script run in a WSGI server.") # This is not an error. else: logging.info("Exiting the process of the script run in snapshot mode and CGI server.") # This raises SystemExit which can be handled. exit(0)
def Main(): cgiEnv = lib_common.CgiEnv(can_process_remote = True) entity_id = cgiEnv.GetId() DEBUG("entity_id=%s", entity_id) if entity_id == "": lib_common.ErrorMessageHtml("No entity_id") # Just the path, shorter than cgiEnv.get_parameters("xid") cimomUrl = cgiEnv.GetHost() nameSpace, className = cgiEnv.get_namespace_type() DEBUG("entity_wbem.py cimomUrl=%s nameSpace=%s className=%s", cimomUrl,nameSpace,className) if nameSpace == "": nameSpace = "root/cimv2" INFO("Setting namespace to default value\n") if className == "": lib_common.ErrorMessageHtml("No class name. entity_id=%s" % entity_id) grph = cgiEnv.GetGraph() conn = lib_wbem.WbemConnection(cimomUrl) rootNode = lib_util.EntityClassNode( className, nameSpace, cimomUrl, "WBEM" ) klaDescrip = lib_wbem.WbemClassDescription(conn,className,nameSpace) if not klaDescrip: klaDescrip = "Undefined class %s %s" % ( nameSpace, className ) grph.add( ( rootNode, pc.property_information, lib_common.NodeLiteral(klaDescrip ) ) ) splitMonik = lib_util.SplitMoniker( cgiEnv.m_entity_id ) DEBUG("entity_wbem.py nameSpace=%s className=%s cimomUrl=%s",nameSpace,className,cimomUrl) # This works: # conn = pywbem.WBEMConnection("http://192.168.0.17:5988",("pegasus","toto")) # conn.ExecQuery("WQL","select * from CIM_System","root/cimv2") # conn.ExecQuery("WQL",'select * from CIM_Process where Handle="4125"',"root/cimv2") # # select * from CIM_Directory or CIM_DataFile does not return anything. instLists = WbemPlainExecQuery( conn, className, splitMonik, nameSpace ) DEBUG("entity_wbem.py instLists=%s",str(instLists)) if instLists is None: instLists = WbemNoQueryOneInst( conn, className, splitMonik, nameSpace ) if instLists is None: instLists = WbemNoQueryFilterInstances( conn, className, splitMonik, nameSpace ) # TODO: Some objects are duplicated. # 'CSCreationClassName' CIM_UnitaryComputerSystem Linux_ComputerSystem # 'CreationClassName' PG_UnixProcess TUT_UnixProcess numInsts = len(instLists) # If there are duplicates, adds a property which we hope is different. propDiscrim = "CreationClassName" # TODO!! WHAT OF THIS IS NOT THE RIGHT ORDER ??? # Remove the double-quotes around the argument. WHAT IF THEY ARE NOT THERE ?? # arrVals = [ ChopEnclosingParentheses( splitMonik[qryKey] ) for qryKey in splitMonik ] for anInst in instLists: # TODO: Use the right accessor for better performance. # On peut peut etre mettre tout ca dans une fonction sauf l execution de la query. dictInst = dict(anInst) # This differentiates several instance with the same properties. if numInsts > 1: # TODO: Should check if this property is different for all instances !!! withExtraArgs = { propDiscrim : dictInst[ propDiscrim ] } allArgs = splitMonik.copy() allArgs.update(withExtraArgs) dictProps = allArgs else: dictProps = splitMonik hostOnly = lib_util.EntHostToIp(cimomUrl) if lib_util.IsLocalAddress(hostOnly): uriInst = lib_common.gUriGen.UriMakeFromDict(className, dictProps) else: uriInst = lib_common.RemoteBox(hostOnly).UriMakeFromDict(className, dictProps) grph.add( ( rootNode, lib_common.MakeProp(className), uriInst ) ) AddNamespaceLink(grph, rootNode, nameSpace, cimomUrl, className) # None properties are not printed. for inameKey in dictInst: # Do not print twice values which are in the name. if inameKey in splitMonik: continue inameVal = dictInst[inameKey] # TODO: If this is a reference, create a Node !!!!!!! if not inameVal is None: grph.add( ( uriInst, lib_common.MakeProp(inameKey), lib_common.NodeLiteral(inameVal) ) ) # TODO: Should call Associators(). Same for References(). cgiEnv.OutCgiRdf()