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 from PLC.Accessors.Accessors_standard import *
26 # XXX used to check if slice expiration time is sane
29 # slice_filter essentially contains the slice_ids for the relevant slices (on the node + system & delegated slices)
30 def get_slivers(api, caller, auth, slice_filter, node = None):
31 # Get slice information
32 slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
34 # Build up list of users and slice attributes
38 person_ids.update(slice['person_ids'])
39 slice_tag_ids.update(slice['slice_tag_ids'])
41 # Get user information
42 all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
44 # Build up list of keys
46 for person in all_persons.values():
47 key_ids.update(person['key_ids'])
49 # Get user account keys
50 all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
52 # Get slice attributes
53 all_slice_tags = SliceTags(api, slice_tag_ids).dict()
58 for person_id in slice['person_ids']:
59 if person_id in all_persons:
60 person = all_persons[person_id]
61 if not person['enabled']:
63 for key_id in person['key_ids']:
64 if key_id in all_keys:
65 key = all_keys[key_id]
66 keys += [{'key_type': key['key_type'],
71 # All (per-node and global) attributes for this slice
73 for slice_tag_id in slice['slice_tag_ids']:
74 if slice_tag_id in all_slice_tags:
75 slice_tags.append(all_slice_tags[slice_tag_id])
77 # Per-node sliver attributes take precedence over global
78 # slice attributes, so set them first.
79 # Then comes nodegroup slice attributes
80 # Followed by global slice attributes
81 sliver_attributes = []
84 for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
85 sliver_attributes.append(sliver_attribute['tagname'])
86 attributes.append({'tagname': sliver_attribute['tagname'],
87 'value': sliver_attribute['value']})
89 # set nodegroup slice attributes
90 for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
91 # Do not set any nodegroup slice attributes for
92 # which there is at least one sliver attribute
94 if slice_tag not in slice_tags:
95 attributes.append({'tagname': slice_tag['tagname'],
96 'value': slice_tag['value']})
98 for slice_tag in [ a for a in slice_tags if a['node_id'] is None ]:
99 # Do not set any global slice attributes for
100 # which there is at least one sliver attribute
102 if slice_tag['tagname'] not in sliver_attributes:
103 attributes.append({'tagname': slice_tag['tagname'],
104 'value': slice_tag['value']})
106 # XXX Sanity check; though technically this should be a system invariant
107 # checked with an assertion
108 if slice['expires'] > MAXINT: slice['expires']= MAXINT
110 # expose the slice vref as computed by GetSliceFamily
111 family = GetSliceFamily (api,caller).call(auth, slice['slice_id'])
114 'name': slice['name'],
115 'slice_id': slice['slice_id'],
116 'instantiation': slice['instantiation'],
117 'expires': slice['expires'],
119 'attributes': attributes,
120 'GetSliceFamily': family,
125 ### The pickle module, used in conjunction with caching has a restriction that it does not
126 ### work on "connection objects." It doesn't matter if the connection object has
127 ### an 'str' or 'repr' method, there is a taint check that throws an exception if
128 ### the pickled class is found to derive from a connection.
129 ### (To be moved to Method.py)
131 def sanitize_for_pickle (obj):
132 if (isinstance(obj, dict)):
134 for k in parent.keys(): parent[k] = sanitize_for_pickle (parent[k])
136 elif (isinstance(obj, list)):
138 parent = map(sanitize_for_pickle, parent)
143 class GetSlivers(Method):
145 Returns a struct containing information about the specified node
146 (or calling node, if called by a node and node_id_or_hostname is
147 not specified), including the current set of slivers bound to the
150 All of the information returned by this call can be gathered from
151 other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
152 function exists almost solely for the benefit of Node Manager.
155 roles = ['admin', 'node']
159 Mixed(Node.fields['node_id'],
160 Node.fields['hostname']),
164 'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
165 'node_id': Node.fields['node_id'],
166 'hostname': Node.fields['hostname'],
167 'interfaces': [Interface.fields],
168 'groups': [NodeGroup.fields['groupname']],
169 'conf_files': [ConfFile.fields],
170 'initscripts': [InitScript.fields],
172 'name': Parameter(str, "unix style account name", max = 254),
174 'key_type': Key.fields['key_type'],
175 'key': Key.fields['key']
179 'name': Slice.fields['name'],
180 'slice_id': Slice.fields['slice_id'],
181 'instantiation': Slice.fields['instantiation'],
182 'expires': Slice.fields['expires'],
184 'key_type': Key.fields['key_type'],
185 'key': Key.fields['key']
188 'tagname': SliceTag.fields['tagname'],
189 'value': SliceTag.fields['value']
192 # how to reach the xmpp server
193 'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
194 'user':Parameter(str,"username for the XMPP server"),
195 'password':Parameter(str,"username for the XMPP server"),
197 # we consider three policies (reservation-policy)
198 # none : the traditional way to use a node
199 # lease_or_idle : 0 or 1 slice runs at a given time
200 # lease_or_shared : 1 slice is running during a lease, otherwise all the slices come back
201 'reservation_policy': Parameter(str,"one among none, lease_or_idle, lease_or_shared"),
202 'leases': [ { 'slice_id' : Lease.fields['slice_id'],
203 't_from' : Lease.fields['t_from'],
204 't_until' : Lease.fields['t_until'],
208 def call(self, auth, node_id_or_hostname = None):
209 return self.raw_call(auth, node_id_or_hostname)
212 def raw_call(self, auth, node_id_or_hostname):
213 timestamp = int(time.time())
216 if node_id_or_hostname is None:
217 if isinstance(self.caller, Node):
220 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
222 nodes = Nodes(self.api, [node_id_or_hostname])
224 raise PLCInvalidArgument, "No such node"
227 if node['peer_id'] is not None:
228 raise PLCInvalidArgument, "Not a local node"
230 # Get interface information
231 interfaces = Interfaces(self.api, node['interface_ids'])
233 # Get node group information
234 nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
235 groups = nodegroups.keys()
237 # Get all (enabled) configuration files
238 all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
241 # Global configuration files are the default. If multiple
242 # entries for the same global configuration file exist, it is
243 # undefined which one takes precedence.
244 for conf_file in all_conf_files.values():
245 if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
246 conf_files[conf_file['dest']] = conf_file
248 # Node group configuration files take precedence over global
249 # ones. If a node belongs to multiple node groups for which
250 # the same configuration file is defined, it is undefined
251 # which one takes precedence.
252 for nodegroup in nodegroups.values():
253 for conf_file_id in nodegroup['conf_file_ids']:
254 if conf_file_id in all_conf_files:
255 conf_file = all_conf_files[conf_file_id]
256 conf_files[conf_file['dest']] = conf_file
258 # Node configuration files take precedence over node group
259 # configuration files.
260 for conf_file_id in node['conf_file_ids']:
261 if conf_file_id in all_conf_files:
262 conf_file = all_conf_files[conf_file_id]
263 conf_files[conf_file['dest']] = conf_file
265 # Get all (enabled) initscripts
266 initscripts = InitScripts(self.api, {'enabled': True})
269 system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
270 system_slice_ids = system_slice_tags.keys()
272 # Get nm-controller slices
273 # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
274 controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
275 controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
276 slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
278 slivers = get_slivers(self.api, self.caller, auth, slice_ids, node)
280 # get the special accounts and keys needed for the node
284 if False and 'site_id' not in node:
285 nodes = Nodes(self.api, node['node_id'])
288 # used in conjunction with reduce to flatten lists, like in
289 # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
290 def reduce_flatten_list (x,y): return x+y
292 # root users are users marked with the tag 'isrootonsite'. Hack for Mlab and other sites in which admins participate in diagnosing problems.
293 def get_site_root_user_keys(api,site_id_or_name):
294 site = Sites (api,site_id_or_name,['person_ids'])[0]
295 all_site_persons = site['person_ids']
296 all_site_person_tags = PersonTags(self.api,{'person_id':all_site_persons,'tagname':'isrootonsite'},['value','person_id'])
297 site_root_person_tags = filter(lambda r:r['value']=='true',all_site_person_tags)
298 site_root_person_ids = map(lambda r:r['person_id'],site_root_person_tags)
299 key_ids = reduce (reduce_flatten_list,
300 [ p['key_ids'] for p in \
301 Persons(api,{ 'person_id':site_root_person_ids,
302 'enabled':True, '|role_ids' : [20, 40] },
305 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
307 # power users are pis and techs
308 def get_site_power_user_keys(api,site_id_or_name):
309 site = Sites (api,site_id_or_name,['person_ids'])[0]
310 key_ids = reduce (reduce_flatten_list,
311 [ p['key_ids'] for p in \
312 Persons(api,{ 'person_id':site['person_ids'],
313 'enabled':True, '|role_ids' : [20, 40] },
316 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
318 # all admins regardless of their site
319 def get_all_admin_keys(api):
320 key_ids = reduce (reduce_flatten_list,
321 [ p['key_ids'] for p in \
322 Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] },
325 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
327 # 'site_admin' account setup
328 personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
329 accounts.append({'name':'site_admin','keys':personsitekeys})
331 # 'root' account setup on nodes from all 'admin' users and ones marked with 'isrootonsite' for this site
332 siterootkeys=get_site_root_user_keys(self.api,node['site_id'])
333 personsitekeys=get_all_admin_keys(self.api)
334 personsitekeys.extend(siterootkeys)
336 accounts.append({'name':'root','keys':personsitekeys})
338 hrn = GetNodeHrn(self.api,self.caller).call(auth,node['node_id'])
340 # XMPP config for omf federation
342 if not self.api.config.PLC_OMF_ENABLED:
343 raise Exception,"OMF disabled"
344 xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
345 'user':self.api.config.PLC_OMF_XMPP_USER,
346 'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
349 xmpp={'server':None,'user':None,'password':None}
351 node.update_last_contact()
353 # expose leases & reservation policy
354 # in a first implementation we only support none and lease_or_idle
355 lease_exposed_fields = [ 'slice_id', 't_from', 't_until', 'name', ]
357 if node['node_type'] != 'reservable':
358 reservation_policy='none'
360 reservation_policy='lease_or_idle'
361 # expose the leases for the next 24 hours
362 leases = [ dict ( [ (k,l[k]) for k in lease_exposed_fields ] )
363 for l in Leases (self.api, {'node_id':node['node_id'],
364 'clip': (timestamp, timestamp+24*Duration.HOUR),
367 granularity=self.api.config.PLC_RESERVATION_GRANULARITY
370 'timestamp': timestamp,
371 'node_id': node['node_id'],
372 'hostname': node['hostname'],
373 'interfaces': interfaces,
375 'conf_files': conf_files.values(),
376 'initscripts': initscripts,
378 'accounts': accounts,
381 'reservation_policy': reservation_policy,
383 'lease_granularity': granularity,
386 sanitized_data = sanitize_for_pickle (raw_data)
387 return sanitized_data