"""
Copyright (c) 2011, 2012, Regents of the University of California
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
"""
"""
@author Stephen Dawson-Haggerty <stevedh@eecs.berkeley.edu>
"""
import os, sys
import uuid
import ConfigParser
import configobj
try:
import ordereddict
except ImportError:
import collections as ordereddict
import core
import util
import driver
import smapconf
import checkers
class SmapLoadError(core.SmapException):
"""An error was encountered loading a config file"""
def _save_path(conf, inst, path):
conf.add_section(path)
cur = inst.lookup(path)
if not path in inst.drivers:
conf.set(path, "type", cur.__class__.__name__)
else:
# if it's a driver, save the class name that it's loaded as
conf.set(path, "type", inst.drivers[path].driver.__class__.__module__ + \
'.' + inst.drivers[path].driver.__class__.__name__)
if hasattr(cur, "key"):
conf.set(path, "key", getattr(cur, "key"))
elif 'uuid' in cur:
conf.set(path, "uuid", cur['uuid'])
if conf.get(path, "type") == "Timeseries":
conf.set(path, "BufferSize", str(cur['Readings'].size))
for k, v in cur.iteritems():
if k in ['uuid', 'Readings', 'Proxy', 'Contents']: continue
for (name, value) in util.buildkv(k, v):
conf.set(path, name, value)
[docs]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)
[docs]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