-
Notifications
You must be signed in to change notification settings - Fork 95
/
__init__.py
183 lines (152 loc) · 6.13 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#
# -*- coding: utf-8 -*-
#
# This file is part of reclass (http://github.com/madduck/reclass)
#
# Copyright © 2007–13 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#
import time
import types
import re
import sys
import fnmatch
import shlex
from reclass.datatypes import Entity, Classes
from reclass.errors import MappingFormatError
def _get_timestamp():
return time.strftime('%c')
def vvv(msg):
print >>sys.stderr, msg
pass
class NodeStorageBase(object):
def __init__(self, nodes_uri, classes_uri, class_mappings):
self._nodes_uri = nodes_uri
self._classes_uri = classes_uri
self._classes_cache = {}
self._class_mappings = class_mappings
nodes_uri = property(lambda self: self._nodes_uri)
classes_uri = property(lambda self: self._classes_uri)
class_mappings = property(lambda self: self._class_mappings)
def _match_regexp(self, key, nodename):
return re.search(key, nodename)
def _match_glob(self, key, nodename):
return fnmatch.fnmatchcase(nodename, key)
def _shlex_split(self, instr):
lexer = shlex.shlex(instr, posix=True)
lexer.whitespace_split = True
lexer.commenters = ''
regexp = False
if instr[0] == '/':
lexer.quotes += '/'
lexer.escapedquotes += '/'
regexp = True
try:
key = lexer.get_token()
except ValueError, e:
raise MappingFormatError('Error in mapping "{0}": missing closing '
'quote (or slash)'.format(instr))
if regexp:
key = '/{0}/'.format(key)
return key, list(lexer)
def _populate_with_class_mappings(self, nodename):
if not self.class_mappings:
return Entity(name='empty')
c = Classes()
for mapping in self.class_mappings:
matched = False
key, klasses = self._shlex_split(mapping)
if key[0] == ('/'):
matched = self._match_regexp(key[1:-1], nodename)
if matched:
for klass in klasses:
c.append_if_new(matched.expand(klass))
else:
if self._match_glob(key, nodename):
for klass in klasses:
c.append_if_new(klass)
return Entity(classes=c,
name='class mappings for node {0}'.format(nodename))
def _get_storage_name(self):
raise NotImplementedError, "Storage class does not have a name"
def _get_node(self, name, merge_base=None):
raise NotImplementedError, "Storage class not implement node entity retrieval"
def _get_class(self, name):
raise NotImplementedError, "Storage class not implement class entity retrieval"
def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None):
if seen is None:
seen = {}
if merge_base is None:
merge_base = Entity(name='empty (@{0})'.format(nodename))
for klass in entity.classes.as_list():
if klass not in seen:
try:
class_entity = self._classes_cache[klass]
except KeyError, e:
class_entity = self._get_class(klass)
self._classes_cache[klass] = class_entity
descent = self._recurse_entity(class_entity, seen=seen,
nodename=nodename)
# on every iteration, we merge the result of the recursive
# descent into what we have so far…
merge_base.merge(descent)
seen[klass] = True
# … and finally, we merge what we have at this level into the
# result of the iteration, so that elements at the current level
# overwrite stuff defined by parents
merge_base.merge(entity)
return merge_base
def _nodeinfo(self, nodename):
node_entity = self._get_node(nodename)
merge_base = self._populate_with_class_mappings(node_entity.name)
ret = self._recurse_entity(node_entity, merge_base,
nodename=node_entity.name)
ret.interpolate()
return ret
def _nodeinfo_as_dict(self, nodename, entity):
ret = {'__reclass__' : {'node': entity.name, 'name': nodename,
'uri': entity.uri,
'timestamp': _get_timestamp()
},
}
ret.update(entity.as_dict())
return ret
def nodeinfo(self, nodename):
return self._nodeinfo_as_dict(nodename, self._nodeinfo(nodename))
def _list_inventory(self):
raise NotImplementedError, "Storage class does not implement inventory listing"
def inventory(self):
entities = self._list_inventory()
nodes = {}
applications = {}
classes = {}
for f, nodeinfo in entities.iteritems():
d = nodes[f] = self._nodeinfo_as_dict(f, nodeinfo)
for a in d['applications']:
if a in applications:
applications[a].append(f)
else:
applications[a] = [f]
for c in d['classes']:
if c in classes:
classes[c].append(f)
else:
classes[c] = [f]
return {'__reclass__' : {'timestamp': _get_timestamp()},
'nodes': nodes,
'classes': classes,
'applications': applications
}
class StorageBackendLoader(object):
def __init__(self, storage_type):
self._name = 'reclass.storage.' + storage_type
try:
self._module = __import__(self._name, globals(), locals(), self._name)
except ImportError:
raise NotImplementedError
def load(self, attr='ExternalNodeStorage'):
klass = getattr(self._module, attr, None)
if klass is None:
raise AttributeError, \
'Storage backend class {0} does not export "{1}"'.format(self._name, klass)
return klass