def add_timeseries(self, path, *args, **kwargs): """Add a timeseries to the smap server at the given path. This will generate a UUID for the timeseries. direct form :param path a Timeseries instance simple form :param args[0] is a uuid instance, or a key to generate a uuid with by combining it with the root uuid. :param args[1] and kwargs are arguments passed to the Timeseries constructor. Therefore you have to include at least the UnitofMeasure :param boolean replace: (kwarg) replace an existing timeseries at that path instead of throwing an exception :param boolean recurse: recursively create parent collections instead of thrwoing an exception. Default is True. :raises: :py:class:`SmapException` if the parent isn't a collection or the path already exists. """ replace = kwargs.pop('replace', False) recurse = kwargs.pop('recurse', True) klass = kwargs.pop('klass', Timeseries) if len(args) == 0 or \ not ITimeseries.providedBy(args[0]) and not IActuator.providedBy(args[0]): if len(args) == 2: if not isinstance(args[0], uuid.UUID): id = self.uuid(args[0], namespace=kwargs.get('namespace', None)) else: id = args[0] args = args[1:] elif len(args) == 1: id = self.uuid(util.norm_path(path), kwargs.get('namespace', None)) else: id = self.uuid(util.norm_path(path)) # raise SmapException("SmapInstance.add_timeseries may only be called " # "with two or three arguments") kwargs.pop('namespace', None) timeseries = klass(id, *args, **kwargs) if id != args[0]: setattr(timeseries, "key", args[0]) else: timeseries = args[0] path = util.split_path(path) if recurse: self._add_parents(path) parent = self.get_collection(util.join_path(path[:-1])) if not replace and util.join_path(path) in self.OBJS_PATH: raise SmapException("add_timeseries: path " + str(path) + " exists!") if not parent: raise SmapException("add_timeseries: parent is not a collection!") parent.add_child(path[-1]) # place the new timeseries into the uuid and path tables self.OBJS_UUID[timeseries['uuid']] = timeseries self.OBJS_PATH[util.join_path(path)] = timeseries timeseries.inst = self setattr(timeseries, 'path', util.join_path(path)) if not self.loading: self.reports.update_subscriptions() return timeseries
def dump(inst, file): """Dump an existing :py:class:`~smap.core.SmapInstance` object to a conf file :param smap.Core.SmapInstance inst: the object to dump :param string file: config filename :raises IOError: if writing to the file fails """ conf = ConfigParser.ConfigParser('', ordereddict.OrderedDict) conf.optionxform = str q = ['/'] while len(q) > 0: cur = inst.get_collection(q[0]) if cur and cur.has_key('Contents') and not q[0] in inst.drivers: for child in cur['Contents']: q.append(util.norm_path(q[0] + '/' + child)) _save_path(conf, inst, q[0]) if conf.get(q[0], 'type') == 'Timeseries': for k, v in core.Timeseries.DEFAULTS.iteritems(): if conf.has_option(q[0], k) and \ conf.get(q[0], k) == str(v): conf.remove_option(q[0], k) q.pop(0) with open(file, 'w') as fp: conf.write(fp)
def publish(self, path, val, prepend=False): """Publish a new reading to the stream identified by a path. Not thread safe. """ path = util.norm_path(path) for sub in self.subscribers: if path in sub['Topics']: sub['PendingData'].add(path, val)
def _lookup_r(self, id, pred=None): """Lookup recursively in the resource hierarchy, starting with the resource identifed by "id". Returns a list of elements for which "pred" returns True""" rv = {} q = [id] root_path = getattr(self.lookup(id), 'path') while len(q) > 0: cur = self.lookup(q.pop(0)) if ICollection.providedBy(cur): for child in cur['Contents']: q.append(getattr(cur, 'path') + '/' + child) if cur and (not pred or pred(cur)): rvpath = util.norm_path(getattr(cur, 'path')[len(root_path):]) rv[rvpath] = cur return rv
def __init__(self, path, inst=None, description=None, *args): """ :param string path: the path where the collection will be added :param SmapInstance inst: the containing :py:class:`SmapInstance` object :param string description: the contents of the sMAP description field :raise SmapSchemaException: if the resulting object does not validate """ self.inst = inst setattr(self, 'path', util.norm_path(path)) if len(args) == 1 and isinstance(args[0], dict): dict.__init__(self, args[0]) else: self.__setitem__("Contents", []) if not schema.validate("Collection", self): raise SmapSchemaException("Error instantiating Collection: " "invalid parameter")
def reporting_map(rpt, col_cb, ts_cb): q = ['/'] while len(q) > 0: cur_path = util.norm_path(q.pop(0)) cur = rpt.get(cur_path, None) if not cur: continue if 'Contents' in cur: for pc in cur['Contents']: q.append(cur_path + '/' + pc) col_cb(cur_path, cur) else: ts_cb(cur_path, cur) del rpt[cur_path] for p, v in rpt.iteritems(): if 'Contents' in v: col_cb(p, v) else: ts_cb(p, v)
def __join_id(self, id): if util.is_string(id) and id.startswith('/'): return util.norm_path(self.__attach_point + '/' + id) else: return id
def load(file, sections=[], **instargs): """Create a sMAP instance based on the representation stored in a file. The configuration file contains sections which refer to either reporting instances, or paths in the sMAP heirarchy. Any section whose name starts with ``/`` is treated as a resource name; sections starting with ``report`` are treated as reports. The file must contain at least one section named ``/``, which must contain a ``uuid`` key to set the root identifier for the source. :param string file: filename of the configuration file :param instargs: arguments passed to the :py:class:`~smap.core.SmapInstance` constructor. :return smap.core.SmapInstance: the created instancev :raise smap.loader.SmapLoadError: an error is encountered processing the file :raise smap.core.SmapError: some other error is encountered validating the loaded object """ found = None for l in ['', os.getcwd(), sys.prefix]: path = os.path.join(l, file) if os.path.isfile(path): found = path if not found: raise Exception("Config file %s not found." % file) print "Loading config file:", found conf = configobj.ConfigObj(found, indent_type=' ') # if there's a server section, override the default server # configuration with that if 'server' in conf: smapconf.SERVER = util.dict_merge(smapconf.SERVER, dict(((k.lower(), v) for (k, v) in conf['server'].iteritems()))) if 'logging' in conf: smapconf.LOGGING = util.dict_merge(smapconf.LOGGING, dict(((k.lower(), v) for (k, v) in conf['logging'].iteritems()))) # we need the root to have a uuid inst = core.SmapInstance(conf['/']['uuid'], **instargs) inst.loading = True reports = [] for s in conf: print "Loading section", s if s.startswith('report'): resource = conf[s].get('ReportResource', '/+') format = conf[s].get('Format', 'json') max_age = conf[s].get('MaxAge', None) max_age = int(max_age) if max_age != None else None dest = [conf[s]['ReportDeliveryLocation']] for i in xrange(0, 10): if 'ReportDeliveryLocation%i' % i in conf[s]: dest.append(conf[s]['ReportDeliveryLocation%i' % i]) reportinst = { 'ReportDeliveryLocation' : dest, 'ReportResource' : resource, 'Format': format, 'uuid' : inst.uuid(s), 'MaxAge' : max_age, } for o in ['MinPeriod', 'MaxPeriod']: if o in conf[s]: reportinst[o] = conf[s][o] for o in ['ClientCertificateFile', 'ClientPrivateKeyFile', 'CAFile']: if o in conf[s]: reportinst[i] = os.path.expanduser(conf[s][o]) reports.append(reportinst) continue elif not s.startswith('/'): # path sections must start with a '/' # other sections might be present and could be parsed by # other parts of the program print "Warning: skipping section", s, "since it does not begin with a '/'" continue elif len(sections) and not util.norm_path(s) in sections: # skip all but the listed sections if we were asked to continue s = util.norm_path(s) # build the UUID for the item props = util.build_recursive(dict(conf[s].items())) id = None if 'uuid' in conf[s]: key = None id = uuid.UUID(conf[s]['uuid']) elif 'key' in conf[s]: key = conf[s]['key'] else: # default to the path if key = s if key: id = inst.uuid(key) # raise SmapLoadError("Every config file section must have a uuid or a key!") # create the timeseries or collection if (s == '/' or conf[s].get("type", None) == 'Collection' or inst.get_collection(s) != None): if s == '/': c = inst.get_collection('/') elif inst.get_collection(s) != None: # sometimes you will have collections created twice, # for instance if a driver creates it and then we want # to tag it with metadata c = inst.get_collection(s) else: c = core.Collection(s, inst) inst.add_collection(s, c) elif conf[s].get("type", "Timeseries") == "Timeseries": if inst.get_timeseries(s) != None: c = inst.get_timeseries(s) else: try: props['Properties']['UnitofMeasure'] except KeyError: raise SmapLoadError("A Timeseries must have at least " "the Properites/UnitofMeasure key") # the Timeseries uses defaults if the conf file doesn't # contain the right sections. c = core.Timeseries(id, props['Properties']['UnitofMeasure'], data_type=props['Properties'].get('ReadingType', core.Timeseries.DEFAULTS['Properties/ReadingType']), timezone=props['Properties'].get('Timezone', core.Timeseries.DEFAULTS['Properties/Timezone']), buffersz=int(props.get('BufferSize', core.Timeseries.DEFAULTS['BufferSize']))) inst.add_timeseries(s, c) else: if not id: raise SmapLoadError("A driver must have a key or uuid to generate a namespace") # load a new driver manager layer newdrv = driver.SmapDriver.get_driver(inst, conf[s]['type'], s, id) # create a collection and add it at the attachment point c = inst.get_collection(s) if not c: c = core.Collection(s, inst) inst.add_collection(s, c) # Add config file specified checkers for the driver check = checkers.get(inst, newdrv, conf[s]) if check: inst.checkers.append(check) # get the driver to add its points newdrv.setup(conf[s]) # Metadata and Description are shared between both Collections # and Timeseries if props.has_key('Metadata'): # the driver may have added metadata; however config file # metadata overrides it c['Metadata'] = util.dict_merge(c.get('Metadata', {}), props['Metadata']) if props.has_key('Description'): c['Description'] = props['Description'] if key: setattr(c, 'key', key) # since the sections could come in any order, update the reporting # instance to make sure all the topics are set right. for reportinst in reports: if not inst.reports.update_report(reportinst): inst.reports.add_report(reportinst) inst.reports.update_subscriptions() inst.loading = False return inst
def load(file, sections=[], **instargs): """Create a sMAP instance based on the representation stored in a file. The configuration file contains sections which refer to either reporting instances, or paths in the sMAP heirarchy. Any section whose name starts with ``/`` is treated as a resource name; sections starting with ``report`` are treated as reports. The file must contain at least one section named ``/``, which must contain a ``uuid`` key to set the root identifier for the source. :param string file: filename of the configuration file :param instargs: arguments passed to the :py:class:`~smap.core.SmapInstance` constructor. :return smap.core.SmapInstance: the created instancev :raise smap.loader.SmapLoadError: an error is encountered processing the file :raise smap.core.SmapError: some other error is encountered validating the loaded object """ found = None for l in ['', os.getcwd(), sys.prefix]: path = os.path.join(l, file) if os.path.isfile(path): found = path if not found: raise Exception("Config file %s not found." % file) print "Loading config file:", found conf = configobj.ConfigObj(found, indent_type=' ') # if there's a server section, override the default server # configuration with that if 'server' in conf: smapconf.SERVER = util.dict_merge( smapconf.SERVER, dict(((k.lower(), v) for (k, v) in conf['server'].iteritems()))) # we need the root to have a uuid inst = core.SmapInstance(conf['/']['uuid'], **instargs) inst.loading = True reports = [] for s in conf: print "Loading section", s if s.startswith('report'): resource = conf[s].get('ReportResource', '/+') format = conf[s].get('Format', 'json') max_age = conf[s].get('MaxAge', None) max_age = int(max_age) if max_age != None else None dest = [conf[s]['ReportDeliveryLocation']] for i in xrange(0, 10): if 'ReportDeliveryLocation%i' % i in conf[s]: dest.append(conf[s]['ReportDeliveryLocation%i' % i]) reportinst = { 'ReportDeliveryLocation': dest, 'ReportResource': resource, 'Format': format, 'uuid': inst.uuid(s), 'MaxAge': max_age, } for o in ['MinPeriod', 'MaxPeriod']: if o in conf[s]: reportinst[o] = conf[s][o] for o in [ 'ClientCertificateFile', 'ClientPrivateKeyFile', 'CAFile' ]: if o in conf[s]: reportinst[i] = os.path.expanduser(conf[s][o]) reports.append(reportinst) continue elif not s.startswith('/'): # path sections must start with a '/' # other sections might be present and could be parsed by # other parts of the program print "Warning: skipping section", s, "since it does not begin with a '/'" continue elif len(sections) and not util.norm_path(s) in sections: # skip all but the listed sections if we were asked to continue s = util.norm_path(s) # build the UUID for the item props = util.build_recursive(dict(conf[s].items())) id = None if 'uuid' in conf[s]: key = None id = uuid.UUID(conf[s]['uuid']) elif 'key' in conf[s]: key = conf[s]['key'] else: # default to the path if key = s if key: id = inst.uuid(key) # raise SmapLoadError("Every config file section must have a uuid or a key!") # create the timeseries or collection if (s == '/' or conf[s].get("type", None) == 'Collection' or inst.get_collection(s) != None): if s == '/': c = inst.get_collection('/') elif inst.get_collection(s) != None: # sometimes you will have collections created twice, # for instance if a driver creates it and then we want # to tag it with metadata c = inst.get_collection(s) else: c = core.Collection(s, inst) inst.add_collection(s, c) elif conf[s].get("type", "Timeseries") == "Timeseries": if inst.get_timeseries(s) != None: c = inst.get_timeseries(s) else: try: props['Properties']['UnitofMeasure'] except KeyError: raise SmapLoadError("A Timeseries must have at least " "the Properites/UnitofMeasure key") # the Timeseries uses defaults if the conf file doesn't # contain the right sections. c = core.Timeseries( id, props['Properties']['UnitofMeasure'], data_type=props['Properties'].get( 'ReadingType', core.Timeseries.DEFAULTS['Properties/ReadingType']), timezone=props['Properties'].get( 'Timezone', core.Timeseries.DEFAULTS['Properties/Timezone']), buffersz=int( props.get('BufferSize', core.Timeseries.DEFAULTS['BufferSize']))) inst.add_timeseries(s, c) else: if not id: raise SmapLoadError( "A driver must have a key or uuid to generate a namespace") # load a new driver manager layer newdrv = driver.SmapDriver.get_driver(inst, conf[s]['type'], s, id) # create a collection and add it at the attachment point c = inst.get_collection(s) if not c: c = core.Collection(s, inst) inst.add_collection(s, c) # Add config file specified checkers for the driver check = checkers.get(inst, newdrv, conf[s]) if check: inst.checkers.append(check) # get the driver to add its points newdrv.setup(conf[s]) # Metadata and Description are shared between both Collections # and Timeseries if props.has_key('Metadata'): # the driver may have added metadata; however config file # metadata overrides it c['Metadata'] = util.dict_merge(c.get('Metadata', {}), props['Metadata']) if props.has_key('Description'): c['Description'] = props['Description'] if key: setattr(c, 'key', key) # since the sections could come in any order, update the reporting # instance to make sure all the topics are set right. for reportinst in reports: if not inst.reports.update_report(reportinst): inst.reports.add_report(reportinst) inst.reports.update_subscriptions() inst.loading = False return inst