def watchdog(type, message, variables = [], severity = WATCHDOG_NOTICE, \ link = None): """ Log a system message. @param type The category to which this message belongs. @param message The message to store in the log. See t() for documentation on how message and variables interact. Keep message translatable by not concatenating dynamic values into it! @param variables Array of variables to replace in the message on display or NULL if message is already translated or not possible to translate. @param severity The severity of the message, as per RFC 3164 @param link A link to associate with the message. @see watchdog_severity_levels() """ php.static(watchdog, 'in_error_state', False) # It is possible that the error handling will itself trigger an error. # In that case, we could # end up in an infinite loop. To avoid that, we implement a simple # static semaphore. if (not watchdog.in_error_state): watchdog.in_error_state = True # Prepare the fields to be logged log_message = { 'type': type, 'message': message, 'variables': variables, 'severity': severity, 'link': link, 'user': lib_appglobals.user, 'request_uri': lib_appglobals.base_root + request_uri(), 'referer': php.SERVER['HTTP_REFERER'], 'ip': ip_address(), 'timestamp': REQUEST_TIME } # Call the logging hooks to log/process the message for plugin_ in lib_plugin.implements('watchdog', True): lib_plugin.invoke(plugin_, 'watchdog', log_message) watchdog.in_error_state = False
def watchdog(type, message, variables=[], severity=WATCHDOG_NOTICE, link=None): """ Log a system message. @param type The category to which this message belongs. @param message The message to store in the log. See t() for documentation on how message and variables interact. Keep message translatable by not concatenating dynamic values into it! @param variables Array of variables to replace in the message on display or NULL if message is already translated or not possible to translate. @param severity The severity of the message, as per RFC 3164 @param link A link to associate with the message. @see watchdog_severity_levels() """ php.static(watchdog, "in_error_state", False) # It is possible that the error handling will itself trigger an error. # In that case, we could # end up in an infinite loop. To avoid that, we implement a simple # static semaphore. if not watchdog.in_error_state: watchdog.in_error_state = True # Prepare the fields to be logged log_message = { "type": type, "message": message, "variables": variables, "severity": severity, "link": link, "user": lib_appglobals.user, "request_uri": lib_appglobals.base_root + request_uri(), "referer": php.SERVER["HTTP_REFERER"], "ip": ip_address(), "timestamp": REQUEST_TIME, } # Call the logging hooks to log/process the message for plugin_ in lib_plugin.implements("watchdog", True): lib_plugin.invoke(plugin_, "watchdog", log_message) watchdog.in_error_state = False
def registry_cache_hook_implementations(hook, write_to_persistent_cache=False): """ Save hook implementations cache. @param hook Array with the hook name and list of plugins that implement it. @param write_to_persistent_cache Whether to write to the persistent cache. """ php.static(registry_cache_hook_implementations, "implementations", {}) if hook: # Newer is always better, so overwrite anything that's come before. registry_cache_hook_implementations.implementations[hook["hook"]] = hook["plugins"] if write_to_persistent_cache == True: # Only write this to cache if the implementations data we are going to cache # is different to what we loaded earlier in the request. if registry_cache_hook_implementations.implementations != lib_plugin.implements(): cache_set("hooks", registry_cache_hook_implementations.implementations, "cache_registry")
def registry_cache_hook_implementations(hook, \ write_to_persistent_cache = False): """ Save hook implementations cache. @param hook Array with the hook name and list of plugins that implement it. @param write_to_persistent_cache Whether to write to the persistent cache. """ php.static(registry_cache_hook_implementations, 'implementations', {}) if (hook): # Newer is always better, so overwrite anything that's come before. registry_cache_hook_implementations.implementations[hook['hook']] = \ hook['plugins'] if (write_to_persistent_cache == True): # Only write this to cache if the implementations data we are going to cache # is different to what we loaded earlier in the request. if (registry_cache_hook_implementations.implementations != \ lib_plugin.implements()): cache_set('hooks', registry_cache_hook_implementations.implementations, \ 'cache_registry')
class RPCLogging(Plugin): """This is a JSON-RPC service that provides logging functionality. As well as exposing rpc methods, it also provides configuration functions. """ _jsonrpcName = "logging" implements(IRPCService) def __init__(self, env): #setup logs from config file self.env = env self.config = env.config self.log = env.log # make sure the directory exists for the default log path path = self.config.get('logging', {}).get('default', {}).get('path', None) if path != None: path = os.path.dirname(path) if not os.path.exists(path): os.makedirs(path) self.logs = { } # mapping between lognames and the file handler instance we auto create for log in self.config.get('logging', {}).get('logs', {}): self.setup_log(log) @serviceProcedure( summary="Logs a message to the named logger with a given log level.", params=[String('name'), Number('level'), String('message')], ret=None) def log(self, name, level, message): """Logs a message to the named logger with a given log level.""" if not isinstance(name, (str, unicode)) and \ not isinstance(level, (int, float)) and \ not isinstance(message, (str, unicode)): # this method does not return so just finish return log = logging.getLogger(name) if log.handlers == []: self.setup_log(name) log.log(level, message) def setup_log(self, name): cfg = self.config.get('logging', {}) logs = cfg.get('logs', {}) loggercfg = None fmtcfg = None default = False if logs.has_key(name): loggercfg = logs[name].get('logger', None) fmtcfg = logs[name].get('formatter', None) if loggercfg == None: loggercfg = cfg.get('default', {}).get('logger', {}) default = True if fmtcfg == None: fmtcfg = cfg.get('default', {}).get('formatter', {}) if default: fname = os.path.join(loggercfg.get('path', '.'), '%s.log' % name) level = loggercfg.get('level', 30) else: fname = loggercfg.get('path', None) if fname == None: fname = os.path.join( cfg.get('default', {}).get('logger', {}).get('path', 'logs'), '%s.log' % name) else: # make sure the directory for the file exists path = os.path.dirname(fname) if not os.path.exists(path): os.makedirs(path) level = loggercfg.get('level', None) if level == None: level = cfg.get('default', {}).get('logger', {}).get('level', 30) mode = loggercfg.get('mode', 'a') if loggercfg.get('type', 0) == 0: handler = logging.FileHandler(fname, mode) else: maxbytes = loggercfg.get('maxbytes', 4096) buckupcount = loggercfg.get('buckupcount', 3) handler = logging.handlers.RotatingFileHandler( fname, mode, maxbytes, backupcount) fmt = fmtcfg.get('format', '%(asctime)s %(levelname)-8s %(message)s') datefmt = fmtcfg.get('dateformat', None) if datefmt == None: formatter = logging.Formatter(fmt) else: formatter = logging.Formatter(fmt, datefmt) handler.setFormatter(formatter) handler.setLevel(level) self.logs[name] = handler log = logging.getLogger(name) log.addHandler(handler) def reset_config(self): """Call this after the config has been changed to reconfigure the logs.""" for name, hdlr in self.logs.iteritems(): log = logging.getLogger(name) log.removeHandler(hdlr) self.setup_log(name)
class Update(Plugin): """This is a JSON-RPC service that provides updating functionality.""" _jsonrpcName = "update" implements(IRPCService, IDownloadManipulator) #=============================================================================== # service methods #=============================================================================== @serviceProcedure( summary= "returns true if the system supplies a given update, false otherwise.", params=[String('name')], ret=Boolean()) def hasUpdate(self, name): """returns true if the system supplies a given update, false otherwise. @param name: string containing the name of the update to check. @return: True if the system supplies the update, False otherwise. """ return (self.getUpdateMetadata(name) != None) @serviceProcedure(summary="Returns the version of the update system.", ret=Array()) def systemVersion(self): """Returns the version of the update system. @return: An Array containing 2 numbers, the major and minor version numbers of the update system. """ return _VERSION @serviceProcedure( summary= "Returns a string representation of the update systems version.", ret=String()) def systemVersionString(self): """Returns a string representation of the update systems version. @return: a string representation of the update systems version. """ return "%d.%d" % tuple(_VERSION) @serviceProcedure( summary= "Returns a string representation of the update systems version.", params=[String('update')], ret=Array()) def version(self, update): """Returns the numeric version of a given update. @param update: the name of the update. @return: An Array containing 2 numbers, with the major and minor version numbers of the update. """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) else: return meta['version'] @serviceProcedure( summary= "Returns the string representation of the version of a given update.", params=[String('update')], ret=String()) def versionString(self, update): """Returns the string representation of the version of a given update. @param update: The name of the update. @return: A string containing the version of the update. """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) else: return '%d.%d' % tuple(meta['version'])\ @serviceProcedure(summary="Returns the meta data of a given update.", params=[String('update')], ret=Object()) def metaData(self, update): """Returns the meta data of a given update. @param update: The name of the update. @return: An Object containging the meta data with the following attributes:: name: string <package name>, version: [major, minor] <update version>, timestamp: number <timestamp of update creation>, description: string <description>, (optional) files: [ { file: string <file name>, dlpath: string <download path>, path: string <install path>, (optional default local root directory) }, ... ] """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) out = {} out['name'] = meta['name'] out['version'] = meta['version'] out['timestamp'] = meta['timestamp'] if meta.had_key('description'): out['description'] = meta['description'] files = [] dlroot = self.config.get('update', {}).get('dlroot', 'update').strip('/') for f in meta['files']: tmp = {} tmp['file'] = f['file'] tmp['dlpath'] = '/%s/%s/%s' % (dlroot, update, f['file']) if f.has_key('path'): tmp['path'] = f['path'] files.append(tmp) out['files'] = files return out @serviceProcedure( summary="Returns the creation timestamp of a given update.", params=[String('update')], ret=Number()) def timestamp(self, update): """Returns the creation timestamp of a given update. @param update: The name of the update @return: A float with the updates timestamp in seconds since Epoch (January 1, 1970). """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) return meta['timestamp'] @serviceProcedure( summary= "Returns the creation timestamp of a given update as an ISO 8601 Format string.", params=[String('update')], ret=String()) def timestampString(self, update): """Returns the creation timestamp of a given update as an ISO 8601 Format string. @param update: The name of the update. @return: The creation timestamp as an ISO Formated string. """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) return time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(meta['timestamp'])) @serviceProcedure(summary="Returns the description of a given update.", params=[String('update')], ret=String()) def description(self, update): """Returns the description of a given update. @param update: The name of the update. @return: The updates description or an empty string if no description is given. """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) return meta.get('description', '') @serviceProcedure( summary="Returns an array of file meta data for a given update.", params=[String('update')], ret=Array()) def files(self, update): """Returns an array of file meta data for a given update. @param update: The name of the update. @return: An Array of Objects with members:: file: string <file name>, dlpath: string <download path>, path: string <install path>, (optional default local root directory) """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) files = [] dlroot = self.config.get('update', {}).get('dlroot', 'update').strip('/') for f in meta['files']: tmp = {} tmp['file'] = f['file'] tmp['dlpath'] = '/%s/%s/%s' % (dlroot, update, f['file']) if f.has_key('path'): tmp['path'] = f['path'] files.append(tmp) return files @serviceProcedure( summary="Returns the meta data for file within a given update.", params=[String('update'), String('filename')], ret=Object()) def fileMetaData(self, update, filename): """Returns the meta data for file within a given update. @param update: The name of the update. @param filename: The file to retrieve the meta data for. @return: An Objects with members:: file: string <file name>, dlpath: string <download path>, path: string <install path>, (optional default local root directory) """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) dlroot = self.config.get('update', {}).get('dlroot', 'update').strip('/') for f in meta['files']: if f['file'] == filename: tmp = {} tmp['file'] = f['file'] tmp['dlpath'] = '/%s/%s/%s' % (dlroot, update, f['file']) if f.has_key('path'): tmp['path'] = f['path'] return tmp else: raise ApplicationError('update %s does not contain file %s' % (update, filename)) @serviceProcedure( summary="Returns the install path for a file within a given update.", params=[String('update'), String('filename')], ret=String()) def filePath(self, update, filename): """Returns the install path for a file within a given update. @param update: The name of the update. @param filename: The file to retrieve the meta data for. @return: A string containing the install path for the file. """ meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) for f in meta['files']: if f['file'] == filename: path = '' if f.has_key('path'): path = f['path'] return path else: raise ApplicationError('update %s does not contain file %s' % (update, filename)) #=============================================================================== # IDownloadManipulator Methods #=============================================================================== def download(self, head, tail, rootpath): """Fetch the data to be sent. This should return a string with the data to be returned. @param head: A string with the requested path that precedes tail. @param tail: the requested path relative to rootpath. @param rootpath: A string with the local rootpath for the path of the request. """ tail_ = tail.lstrip('/') tmp = tail_.split('/') update = tmp[0] path = '/'.join(tmp[1:]) meta = self.getUpdateMetadata(update) if meta == None: raise ApplicationError('unknown update %s' % update) for f in meta['files']: if f['file'] == path: file_meta = f else: raise IOError('File %s is missing from update %s' % (path, meta['name'])) # is this a file system based update if meta['is_dir'] == True: data = open(os.path.join(meta['path'], path)).read() else: # the update is an archive arch = zipfile.ZipFile(meta['path'], 'r', zipfile.ZIP_DEFLATED) data = arch.read(path) hash = hashlib.sha1() hash.update(data) if file_meta['hash'] != hash.hexdigest(): self.log.debug( 'file (%s) sha1 hash does not match update configuration.' % path) raise ApplicationError( 'file (%s) sha1 hash does not match update configuration.' % path) return data def handles(self, head, tail): """Utility method to tell if the plugin handles a particular request. This will allow a plugin to only handle graphic files by only returning true if the file extension is .jpg, .png, etc. @param head: A string with the requested path that precedes tail. @param tail: the requested path relative to rootpath. @return: True if this plugin will handle the request, False otherwise. """ return True # we handle all requests #=============================================================================== # Utility Methods #=============================================================================== def getUpdateMetadata(self, update): """This method returns the metadata for an update. @param update: string containing the name of the update to fetch the metadata for. @return: a dictionary containing Update metadata, if the update exists and is enabled, None otherwise. """ path = '' for update_cfg in self.config.get('update', {}).get('updates', {}): if update_cfg['name'] == update: if not update_cfg.get('enabled', True): return None path = update_cfg['path'] if not os.path.exists(path): return None break else: root = self.config.get('update', {}).get('rootpath', 'updates') for i in os.listdir(root): if i == update: path = os.path.join(root, i) break else: return None # have a path to a possible update, first check if its a file system update # or an archive update if os.path.isdir(path): update_path = os.path.join(path, 'update') if not os.path.exists(update_path): self.log.debug( 'update %s does not have an update configuration file' % update) return None try: update_meta = load(open(filename), Loader=Loader) except: # the CLoader has problems with throwing exceptions so call the python version here to get a meaningful exception try: load(open(filename)) except YAMLError, e: self.log.debug( 'update %s has invalid configuration file: %s' % (update, str(e))) return None update_meta['is_dir'] = True else:
class FileTransfer(Plugin): """This is a JSON-RPC service that provides download and uploading capabilities.""" _jsonrpcName = "file" implements(IRPCService) download_plugins = ExtensionPoint(IDownloadManipulator) upload_plugins = ExtensionPoint(IUploadManipulator) listdir_plugins = ExtensionPoint(IListDir) @serviceProcedure(summary="This method is used to request a file from the server.", params=[String('file'), Boolean('compress')], ret=Object()) def download(self, file, compress = False): """This method is used to request a file from the server. @param file: path of the file to download. @param compress: if True the contents of the file will be compressed with zlib before sending. @return: a JSON-RPC Object with keys data, crc, and compressed. """ cfg = getConfig(file) dl_plugins = dict([[x.__class__.__name__, x] for x in self.download_plugins]) for plugin in cfg['plugins']: if dl_plugins.has_key(plugin): plugin = dl_plugins[plugin] if plugin.handles(cfg['head'], cfg['tail']): data = plugin.download(cfg['head'], cfg['tail'], cfg['rootpath']) break else: self.log.debug("FileTransfer.download: Skipping plugin %s, not found" % plugin) else: # default download handling, just send the file path = os.path.normpath(os.path.join(cfg['rootpath'], cfg['tail'])) if not os.path.exists(path): raise ApplicationError('IOError: No such file or directory: %s' % file) if not os.isfile(path): raise ApplicationError('IOError: %s is a directory' % file) data = open(path, 'rb').read() # compute the crc of the data crc = crc16(data) # compress the data if so requested if compress: data = zlib.compress(data) # encode the data in base64 data = base64.b64encode(data) return {'data': data, 'crc': crc} @serviceProcedure(summary="This method uploads a file to the server.", params=[String('data'), Number('crc'), Boolean('compress')], ret=None) def upload(self, filename, data, crc, compressed = False): """This method uploads a file to the server. @param filename: file name of the file to download. @param data: the contents of the file being uploaded in base64 format. @param crc: the CRC-16-IBM (CRC16) Cyclic Redundancy Check for the data. @param compressed: if True the data will be decompressed with zlib. """ # decode the base64 encoded string data_ = base64.b64decode(data) # decompress the data if needed if compressed: data_ = zlib.decompress(data) # make sure we recieved what we were expecting by computing the crc value if crc16(data_) != crc: self.log.debug('FileTransfer.upload: crc values did not match for request %s' % filename) raise JSONRPCAssertionError('crc value does not match') cfg = getConfig(file) ul_plugins = dict([[x.__class__.__name__, x] for x in self.upload_plugins]) for plugin in cfg['plugins']: if ul_plugins.has_key(plugin): plugin = ul_plugins[plugin] if plugin.handles(cfg['head'], cfg['tail']): plugin.upload(cfg['head'], cfg['tail'], data_, cfg['rootpath']) break else: self.log.debug("FileTransfer.upload: Skipping plugin %s, not found" % plugin) else: # default upload handling, just save the data path = os.path.normpath(os.path.join(cfg['rootpath'], cfg['tail'])) if not os.path.exists(path): raise ApplicationError('IOError: No such file or directory: %s' % filename) if not os.isfile(path): raise ApplicationError('IOError: %s is a directory' % filename) f = open(path, 'wb') f.write(data_) f.close() @serviceProcedure(summary="Returns a directory listing of the given path.", params=[String('path')], ret=Array()) def listDir(self, path): """Returns a directory listing of the given path. @param dir: the directory to list the contents of. @return: An Array of Objects each one listing the contents of the directory. Each Object has the keys name, size, dir (if true object is a directory), and readonly (if true object is read-only). """ cfg = getConfig(path) list_plugins = dict([[x.__class__.__name__, x] for x in self.listdir_plugins]) for plugin in cfg['plugins']: if list_plugins.has_key(plugin): plugin = list_plugins[plugin] data = plugin.listDir(cfg['head'], cfg['tail'], cfg['rootpath']) break else: self.log.debug("FileTransfer.listDir: Skipping plugin %s, not found" % plugin) else: # default listDir handling path = os.path.normpath(os.path.join(cfg['rootpath'], cfg['tail'])) if not os.path.exists(path): raise ApplicationError('IOError: No such file or directory: %s' % path) if not os.isdir(path): raise ApplicationError('IOError: %s is not a directory' % path) out = [] for i in os.listdir(path): name = i size = os.path.getsize(os.path.join(path, i)) isdir = os.path.isdir(os.path.join(path, i)) readonly = os.access(os.path.join(path, i), os.W_OK) out.append(makeDirectoryEntry(name, size, isdir, readonly)) return out def getConfig(self, path): """Get the configuration for the supplied path. @param path: the requested file path. @return: A dictionary with the configuration values for the given path. It has the keys rootpath, readonly and plugin from the directory configuration as well as the keys head and tail that contain the configuration path and the remainder of the path relative to rootpath. """ tpath = path if tpath == '': tpath = '/' cfg = self.config.get('file', {}) pathcfg = {} head = path tail = '' for p in cfg.get('directories', {}): prefix = os.path.commonprefix((tpath, p)) if prefix != '' and len(p) == len(prefix): pathcfg = cfg['directories'][p] head = prefix tail = tpath[len(prefix):] if tail[0] == '/': tail = tail[1:] break out = {} if pathcfg.has_key('rootpath'): out['rootpath'] = pathcfg['rootpath'] else: out['rootpath'] = cfg.get('rootpath', 'servdocs') head = '/' tail = tpath[1:] if not os.path.isabs(out['rootpath']): out['rootpath'] = os.path.abspath(out['rootpath']) out['readonly'] = pathcfg.get('readonly', False) out['plugins'] = pathcfg.get('plugins', []) out['head'] = head out['tail'] = tail return out