3 from PLC.Faults import *
4 from PLC.Method import Method
5 from PLC.Parameter import Parameter, Mixed
6 from PLC.Filter import Filter
7 from PLC.Auth import Auth
8 from PLC.Nodes import Node, Nodes
9 from PLC.Interfaces import Interface, Interfaces
10 from PLC.NodeGroups import NodeGroup, NodeGroups
11 from PLC.ConfFiles import ConfFile, ConfFiles
12 from PLC.Slices import Slice, Slices
13 from PLC.Persons import Person, Persons
14 from PLC.Sites import Sites
15 from PLC.Roles import Roles
16 from PLC.Keys import Key, Keys
17 from PLC.SliceTags import SliceTag, SliceTags
18 from PLC.InitScripts import InitScript, InitScripts
19 from PLC.Leases import Lease, Leases
20 from PLC.Timestamp import Duration
21 from PLC.Methods.GetSliceFamily import GetSliceFamily
22 from PLC.PersonTags import PersonTag,PersonTags
24 # XXX used to check if slice expiration time is sane
27 # slice_filter essentially contains the slice_ids for the relevant slices (on the node + system & delegated slices)
28 def get_slivers(api, caller, auth, slice_filter, node = None):
29 # Get slice information
30 slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
32 # Build up list of users and slice attributes
36 person_ids.update(slice['person_ids'])
37 slice_tag_ids.update(slice['slice_tag_ids'])
39 # Get user information
40 all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
42 # Build up list of keys
44 for person in all_persons.values():
45 key_ids.update(person['key_ids'])
47 # Get user account keys
48 all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
50 # Get slice attributes
51 all_slice_tags = SliceTags(api, slice_tag_ids).dict()
56 for person_id in slice['person_ids']:
57 if person_id in all_persons:
58 person = all_persons[person_id]
59 if not person['enabled']:
61 for key_id in person['key_ids']:
62 if key_id in all_keys:
63 key = all_keys[key_id]
64 keys += [{'key_type': key['key_type'],
69 # All (per-node and global) attributes for this slice
71 for slice_tag_id in slice['slice_tag_ids']:
72 if slice_tag_id in all_slice_tags:
73 slice_tags.append(all_slice_tags[slice_tag_id])
75 # Per-node sliver attributes take precedence over global
76 # slice attributes, so set them first.
77 # Then comes nodegroup slice attributes
78 # Followed by global slice attributes
79 sliver_attributes = []
82 for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
83 sliver_attributes.append(sliver_attribute['tagname'])
84 attributes.append({'tagname': sliver_attribute['tagname'],
85 'value': sliver_attribute['value']})
87 # set nodegroup slice attributes
88 for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
89 # Do not set any nodegroup slice attributes for
90 # which there is at least one sliver attribute
92 if slice_tag['tagname'] not in sliver_attributes:
93 sliver_attributes.append(slice_tag['tagname'])
94 attributes.append({'tagname': slice_tag['tagname'],
95 'value': slice_tag['value']})
97 for slice_tag in [ a for a in slice_tags if a['node_id'] is None and a['nodegroup_id'] is None ]:
98 # Do not set any global slice attributes for
99 # which there is at least one sliver attribute
101 if slice_tag['tagname'] not in sliver_attributes:
102 attributes.append({'tagname': slice_tag['tagname'],
103 'value': slice_tag['value']})
105 # XXX Sanity check; though technically this should be a system invariant
106 # checked with an assertion
107 if slice['expires'] > MAXINT: slice['expires']= MAXINT
109 # expose the slice vref as computed by GetSliceFamily
110 family = GetSliceFamily (api,caller).call(auth, slice['slice_id'])
113 'name': slice['name'],
114 'slice_id': slice['slice_id'],
115 'instantiation': slice['instantiation'],
116 'expires': slice['expires'],
118 'attributes': attributes,
119 'GetSliceFamily': family,
124 ### The pickle module, used in conjunction with caching has a restriction that it does not
125 ### work on "connection objects." It doesn't matter if the connection object has
126 ### an 'str' or 'repr' method, there is a taint check that throws an exception if
127 ### the pickled class is found to derive from a connection.
128 ### (To be moved to Method.py)
130 def sanitize_for_pickle (obj):
131 if (isinstance(obj, dict)):
133 for k in parent.keys(): parent[k] = sanitize_for_pickle (parent[k])
135 elif (isinstance(obj, list)):
137 parent = map(sanitize_for_pickle, parent)
142 class GetSlivers(Method):
144 Returns a struct containing information about the specified node
145 (or calling node, if called by a node and node_id_or_hostname is
146 not specified), including the current set of slivers bound to the
149 All of the information returned by this call can be gathered from
150 other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
151 function exists almost solely for the benefit of Node Manager.
154 roles = ['admin', 'node']
158 Mixed(Node.fields['node_id'],
159 Node.fields['hostname']),
163 'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
164 'node_id': Node.fields['node_id'],
165 'hostname': Node.fields['hostname'],
166 'interfaces': [Interface.fields],
167 'groups': [NodeGroup.fields['groupname']],
168 'conf_files': [ConfFile.fields],
169 'initscripts': [InitScript.fields],
171 'name': Parameter(str, "unix style account name", max = 254),
173 'key_type': Key.fields['key_type'],
174 'key': Key.fields['key']
178 'name': Slice.fields['name'],
179 'slice_id': Slice.fields['slice_id'],
180 'instantiation': Slice.fields['instantiation'],
181 'expires': Slice.fields['expires'],
183 'key_type': Key.fields['key_type'],
184 'key': Key.fields['key']
187 'tagname': SliceTag.fields['tagname'],
188 'value': SliceTag.fields['value']
191 # how to reach the xmpp server
192 'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
193 'user':Parameter(str,"username for the XMPP server"),
194 'password':Parameter(str,"username for the XMPP server"),
196 # we consider three policies (reservation-policy)
197 # none : the traditional way to use a node
198 # lease_or_idle : 0 or 1 slice runs at a given time
199 # lease_or_shared : 1 slice is running during a lease, otherwise all the slices come back
200 'reservation_policy': Parameter(str,"one among none, lease_or_idle, lease_or_shared"),
201 'leases': [ { 'slice_id' : Lease.fields['slice_id'],
202 't_from' : Lease.fields['t_from'],
203 't_until' : Lease.fields['t_until'],
207 def call(self, auth, node_id_or_hostname = None):
208 return self.raw_call(auth, node_id_or_hostname)
211 def raw_call(self, auth, node_id_or_hostname):
212 timestamp = int(time.time())
215 if node_id_or_hostname is None:
216 if isinstance(self.caller, Node):
219 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
221 nodes = Nodes(self.api, [node_id_or_hostname])
223 raise PLCInvalidArgument, "No such node"
226 if node['peer_id'] is not None:
227 raise PLCInvalidArgument, "Not a local node"
229 # Get interface information
230 interfaces = Interfaces(self.api, node['interface_ids'])
232 # Get node group information
233 nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
234 groups = nodegroups.keys()
236 # Get all (enabled) configuration files
237 all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
240 # Global configuration files are the default. If multiple
241 # entries for the same global configuration file exist, it is
242 # undefined which one takes precedence.
243 for conf_file in all_conf_files.values():
244 if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
245 conf_files[conf_file['dest']] = conf_file
247 # Node group configuration files take precedence over global
248 # ones. If a node belongs to multiple node groups for which
249 # the same configuration file is defined, it is undefined
250 # which one takes precedence.
251 for nodegroup in nodegroups.values():
252 for conf_file_id in nodegroup['conf_file_ids']:
253 if conf_file_id in all_conf_files:
254 conf_file = all_conf_files[conf_file_id]
255 conf_files[conf_file['dest']] = conf_file
257 # Node configuration files take precedence over node group
258 # configuration files.
259 for conf_file_id in node['conf_file_ids']:
260 if conf_file_id in all_conf_files:
261 conf_file = all_conf_files[conf_file_id]
262 conf_files[conf_file['dest']] = conf_file
264 # Get all (enabled) initscripts
265 initscripts = InitScripts(self.api, {'enabled': True})
268 system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
269 system_slice_ids = system_slice_tags.keys()
271 # Get nm-controller slices
272 # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
273 controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
274 controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
275 slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
277 slivers = get_slivers(self.api, self.caller, auth, slice_ids, node)
279 # get the special accounts and keys needed for the node
283 if False and 'site_id' not in node:
284 nodes = Nodes(self.api, node['node_id'])
287 # used in conjunction with reduce to flatten lists, like in
288 # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
289 def reduce_flatten_list (x,y): return x+y
291 # root users are users marked with the tag 'isrootonsite'. Hack for Mlab and other sites in which admins participate in diagnosing problems.
292 def get_site_root_user_keys(api,site_id_or_name):
293 site = Sites (api,site_id_or_name,['person_ids'])[0]
294 all_site_persons = site['person_ids']
295 all_site_person_tags = PersonTags(self.api,{'person_id':all_site_persons,'tagname':'isrootonsite'},['value','person_id'])
296 site_root_person_tags = filter(lambda r:r['value']=='true',all_site_person_tags)
297 site_root_person_ids = map(lambda r:r['person_id'],site_root_person_tags)
298 key_ids = reduce (reduce_flatten_list,
299 [ p['key_ids'] for p in \
300 Persons(api,{ 'person_id':site_root_person_ids,
301 'enabled':True, '|role_ids' : [20, 40] },
304 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
306 # power users are pis and techs
307 def get_site_power_user_keys(api,site_id_or_name):
308 site = Sites (api,site_id_or_name,['person_ids'])[0]
309 key_ids = reduce (reduce_flatten_list,
310 [ p['key_ids'] for p in \
311 Persons(api,{ 'person_id':site['person_ids'],
312 'enabled':True, '|role_ids' : [20, 40] },
315 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
317 # all admins regardless of their site
318 def get_all_admin_keys(api):
319 key_ids = reduce (reduce_flatten_list,
320 [ p['key_ids'] for p in \
321 Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] },
324 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
326 # 'site_admin' account setup
327 personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
328 accounts.append({'name':'site_admin','keys':personsitekeys})
330 # 'root' account setup on nodes from all 'admin' users and ones marked with 'isrootonsite' for this site
331 siterootkeys=get_site_root_user_keys(self.api,node['site_id'])
332 personsitekeys=get_all_admin_keys(self.api)
333 personsitekeys.extend(siterootkeys)
335 accounts.append({'name':'root','keys':personsitekeys})
337 hrn = GetNodeHrn(self.api,self.caller).call(auth,node['node_id'])
339 # XMPP config for omf federation
341 if not self.api.config.PLC_OMF_ENABLED:
342 raise Exception,"OMF disabled"
343 xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
344 'user':self.api.config.PLC_OMF_XMPP_USER,
345 'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
348 xmpp={'server':None,'user':None,'password':None}
350 node.update_last_contact()
352 # expose leases & reservation policy
353 # in a first implementation we only support none and lease_or_idle
354 lease_exposed_fields = [ 'slice_id', 't_from', 't_until', 'name', ]
356 if node['node_type'] != 'reservable':
357 reservation_policy='none'
359 reservation_policy='lease_or_idle'
360 # expose the leases for the next 24 hours
361 leases = [ dict ( [ (k,l[k]) for k in lease_exposed_fields ] )
362 for l in Leases (self.api, {'node_id':node['node_id'],
363 'clip': (timestamp, timestamp+24*Duration.HOUR),
366 granularity=self.api.config.PLC_RESERVATION_GRANULARITY
369 'timestamp': timestamp,
370 'node_id': node['node_id'],
371 'hostname': node['hostname'],
372 'interfaces': interfaces,
374 'conf_files': conf_files.values(),
375 'initscripts': initscripts,
377 'accounts': accounts,
380 'reservation_policy': reservation_policy,
382 'lease_granularity': granularity,
385 sanitized_data = sanitize_for_pickle (raw_data)
386 return sanitized_data