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 *
28 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
29 from cache_utils.decorators import cached
31 # XXX used to check if slice expiration time is sane
34 # slice_filter essentially contains the slice_ids for the relevant slices (on the node + system & delegated slices)
35 def get_slivers(api, caller, auth, slice_filter, node = None):
36 # Get slice information
37 slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
39 # Build up list of users and slice attributes
43 person_ids.update(slice['person_ids'])
44 slice_tag_ids.update(slice['slice_tag_ids'])
46 # Get user information
47 all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
49 # Build up list of keys
51 for person in all_persons.values():
52 key_ids.update(person['key_ids'])
54 # Get user account keys
55 all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
57 # Get slice attributes
58 all_slice_tags = SliceTags(api, slice_tag_ids).dict()
63 for person_id in slice['person_ids']:
64 if person_id in all_persons:
65 person = all_persons[person_id]
66 if not person['enabled']:
68 for key_id in person['key_ids']:
69 if key_id in all_keys:
70 key = all_keys[key_id]
71 keys += [{'key_type': key['key_type'],
76 # All (per-node and global) attributes for this slice
78 for slice_tag_id in slice['slice_tag_ids']:
79 if slice_tag_id in all_slice_tags:
80 slice_tags.append(all_slice_tags[slice_tag_id])
82 # Per-node sliver attributes take precedence over global
83 # slice attributes, so set them first.
84 # Then comes nodegroup slice attributes
85 # Followed by global slice attributes
86 sliver_attributes = []
89 for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
90 sliver_attributes.append(sliver_attribute['tagname'])
91 attributes.append({'tagname': sliver_attribute['tagname'],
92 'value': sliver_attribute['value']})
94 # set nodegroup slice attributes
95 for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
96 # Do not set any nodegroup slice attributes for
97 # which there is at least one sliver attribute
99 if slice_tag not in slice_tags:
100 attributes.append({'tagname': slice_tag['tagname'],
101 'value': slice_tag['value']})
103 for slice_tag in [ a for a in slice_tags if a['node_id'] is None ]:
104 # Do not set any global slice attributes for
105 # which there is at least one sliver attribute
107 if slice_tag['tagname'] not in sliver_attributes:
108 attributes.append({'tagname': slice_tag['tagname'],
109 'value': slice_tag['value']})
111 # XXX Sanity check; though technically this should be a system invariant
112 # checked with an assertion
113 if slice['expires'] > MAXINT: slice['expires']= MAXINT
115 # expose the slice vref as computed by GetSliceFamily
116 family = GetSliceFamily (api,caller).call(auth, slice['slice_id'])
119 'name': slice['name'],
120 'slice_id': slice['slice_id'],
121 'instantiation': slice['instantiation'],
122 'expires': slice['expires'],
124 'attributes': attributes,
125 'GetSliceFamily': family,
130 ### The pickle module, used in conjunction with caching has a restriction that it does not
131 ### work on "connection objects." It doesn't matter if the connection object has
132 ### an 'str' or 'repr' method, there is a taint check that throws an exception if
133 ### the pickled class is found to derive from a connection.
134 ### (To be moved to Method.py)
136 def sanitize_for_pickle (obj):
137 if (isinstance(obj, dict)):
139 for k in parent.keys(): parent[k] = sanitize_for_pickle (parent[k])
141 elif (isinstance(obj, list)):
143 parent = map(sanitize_for_pickle, parent)
148 class GetSlivers(Method):
150 Returns a struct containing information about the specified node
151 (or calling node, if called by a node and node_id_or_hostname is
152 not specified), including the current set of slivers bound to the
155 All of the information returned by this call can be gathered from
156 other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
157 function exists almost solely for the benefit of Node Manager.
160 roles = ['admin', 'node']
164 Mixed(Node.fields['node_id'],
165 Node.fields['hostname']),
169 'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
170 'node_id': Node.fields['node_id'],
171 'hostname': Node.fields['hostname'],
172 'interfaces': [Interface.fields],
173 'groups': [NodeGroup.fields['groupname']],
174 'conf_files': [ConfFile.fields],
175 'initscripts': [InitScript.fields],
177 'name': Parameter(str, "unix style account name", max = 254),
179 'key_type': Key.fields['key_type'],
180 'key': Key.fields['key']
184 'name': Slice.fields['name'],
185 'slice_id': Slice.fields['slice_id'],
186 'instantiation': Slice.fields['instantiation'],
187 'expires': Slice.fields['expires'],
189 'key_type': Key.fields['key_type'],
190 'key': Key.fields['key']
193 'tagname': SliceTag.fields['tagname'],
194 'value': SliceTag.fields['value']
197 # how to reach the xmpp server
198 'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
199 'user':Parameter(str,"username for the XMPP server"),
200 'password':Parameter(str,"username for the XMPP server"),
202 # we consider three policies (reservation-policy)
203 # none : the traditional way to use a node
204 # lease_or_idle : 0 or 1 slice runs at a given time
205 # lease_or_shared : 1 slice is running during a lease, otherwise all the slices come back
206 'reservation_policy': Parameter(str,"one among none, lease_or_idle, lease_or_shared"),
207 'leases': [ { 'slice_id' : Lease.fields['slice_id'],
208 't_from' : Lease.fields['t_from'],
209 't_until' : Lease.fields['t_until'],
213 def call(self, auth, node_id_or_hostname = None):
215 cache_opt = self.api.config.PLC_GETSLIVERS_CACHE
220 return self.cacheable_call(auth, node_id_or_hostname)
222 return self.raw_call(auth, node_id_or_hostname)
225 def cacheable_call(self, auth, node_id_or_hostname):
226 return self.raw_call(auth, node_id_or_hostname)
228 def raw_call(self, auth, node_id_or_hostname):
229 timestamp = int(time.time())
232 if node_id_or_hostname is None:
233 if isinstance(self.caller, Node):
236 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
238 nodes = Nodes(self.api, [node_id_or_hostname])
240 raise PLCInvalidArgument, "No such node"
243 if node['peer_id'] is not None:
244 raise PLCInvalidArgument, "Not a local node"
246 # Get interface information
247 interfaces = Interfaces(self.api, node['interface_ids'])
249 # Get node group information
250 nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
251 groups = nodegroups.keys()
253 # Get all (enabled) configuration files
254 all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
257 # Global configuration files are the default. If multiple
258 # entries for the same global configuration file exist, it is
259 # undefined which one takes precedence.
260 for conf_file in all_conf_files.values():
261 if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
262 conf_files[conf_file['dest']] = conf_file
264 # Node group configuration files take precedence over global
265 # ones. If a node belongs to multiple node groups for which
266 # the same configuration file is defined, it is undefined
267 # which one takes precedence.
268 for nodegroup in nodegroups.values():
269 for conf_file_id in nodegroup['conf_file_ids']:
270 if conf_file_id in all_conf_files:
271 conf_file = all_conf_files[conf_file_id]
272 conf_files[conf_file['dest']] = conf_file
274 # Node configuration files take precedence over node group
275 # configuration files.
276 for conf_file_id in node['conf_file_ids']:
277 if conf_file_id in all_conf_files:
278 conf_file = all_conf_files[conf_file_id]
279 conf_files[conf_file['dest']] = conf_file
281 # Get all (enabled) initscripts
282 initscripts = InitScripts(self.api, {'enabled': True})
285 system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
286 system_slice_ids = system_slice_tags.keys()
288 # Get nm-controller slices
289 # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
290 controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
291 controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
292 slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
294 slivers = get_slivers(self.api, self.caller, auth, slice_ids, node)
296 # get the special accounts and keys needed for the node
300 if False and 'site_id' not in node:
301 nodes = Nodes(self.api, node['node_id'])
304 # used in conjunction with reduce to flatten lists, like in
305 # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
306 def reduce_flatten_list (x,y): return x+y
308 # root users are users marked with the tag 'isrootonsite'. Hack for Mlab and other sites in which admins participate in diagnosing problems.
309 def get_site_root_user_keys(api,site_id_or_name):
310 site = Sites (api,site_id_or_name,['person_ids'])[0]
311 all_site_persons = site['person_ids']
312 all_site_person_tags = PersonTags(self.api,{'person_id':all_site_persons,'tagname':'isrootonsite'},['value','person_id'])
313 site_root_person_tags = filter(lambda r:r['value']=='true',all_site_person_tags)
314 site_root_person_ids = map(lambda r:r['person_id'],site_root_person_tags)
315 key_ids = reduce (reduce_flatten_list,
316 [ p['key_ids'] for p in \
317 Persons(api,{ 'person_id':site_root_person_ids,
318 'enabled':True, '|role_ids' : [20, 40] },
321 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
323 # power users are pis and techs
324 def get_site_power_user_keys(api,site_id_or_name):
325 site = Sites (api,site_id_or_name,['person_ids'])[0]
326 key_ids = reduce (reduce_flatten_list,
327 [ p['key_ids'] for p in \
328 Persons(api,{ 'person_id':site['person_ids'],
329 'enabled':True, '|role_ids' : [20, 40] },
332 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
334 # all admins regardless of their site
335 def get_all_admin_keys(api):
336 key_ids = reduce (reduce_flatten_list,
337 [ p['key_ids'] for p in \
338 Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] },
341 return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
343 # 'site_admin' account setup
344 personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
345 accounts.append({'name':'site_admin','keys':personsitekeys})
347 # 'root' account setup on nodes from all 'admin' users and ones marked with 'isrootonsite' for this site
348 siterootkeys=get_site_root_user_keys(self.api,node['site_id'])
349 personsitekeys=get_all_admin_keys(self.api)
350 personsitekeys.extend(siterootkeys)
352 accounts.append({'name':'root','keys':personsitekeys})
354 hrn = GetNodeHrn(self.api,self.caller).call(auth,node['node_id'])
356 # XMPP config for omf federation
358 if not self.api.config.PLC_OMF_ENABLED:
359 raise Exception,"OMF disabled"
360 xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
361 'user':self.api.config.PLC_OMF_XMPP_USER,
362 'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
365 xmpp={'server':None,'user':None,'password':None}
367 node.update_last_contact()
369 # expose leases & reservation policy
370 # in a first implementation we only support none and lease_or_idle
371 lease_exposed_fields = [ 'slice_id', 't_from', 't_until', 'name', ]
373 if node['node_type'] != 'reservable':
374 reservation_policy='none'
376 reservation_policy='lease_or_idle'
377 # expose the leases for the next 24 hours
378 leases = [ dict ( [ (k,l[k]) for k in lease_exposed_fields ] )
379 for l in Leases (self.api, {'node_id':node['node_id'],
380 'clip': (timestamp, timestamp+24*Duration.HOUR),
383 granularity=self.api.config.PLC_RESERVATION_GRANULARITY
386 'timestamp': timestamp,
387 'node_id': node['node_id'],
388 'hostname': node['hostname'],
389 'interfaces': interfaces,
391 'conf_files': conf_files.values(),
392 'initscripts': initscripts,
394 'accounts': accounts,
397 'reservation_policy': reservation_policy,
399 'lease_granularity': granularity,
402 sanitized_data = sanitize_for_pickle (raw_data)
403 return sanitized_data