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
23 from PLC.Accessors.Accessors_standard import *
27 os.environ['DJANGO_SETTINGS_MODULE']='plc_django_settings'
28 from cache_utils.decorators import cached
30 # XXX used to check if slice expiration time is sane
33 # slice_filter essentially contains the slice_ids for the relevant slices (on the node + system & delegated slices)
34 def get_slivers(api, caller, auth, slice_filter, node = None):
35 # Get slice information
36 slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
38 # Build up list of users and slice attributes
42 person_ids.update(slice['person_ids'])
43 slice_tag_ids.update(slice['slice_tag_ids'])
45 # Get user information
46 all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
48 # Build up list of keys
50 for person in all_persons.values():
51 key_ids.update(person['key_ids'])
53 # Get user account keys
54 all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
56 # Get slice attributes
57 all_slice_tags = SliceTags(api, slice_tag_ids).dict()
62 for person_id in slice['person_ids']:
63 if person_id in all_persons:
64 person = all_persons[person_id]
65 if not person['enabled']:
67 for key_id in person['key_ids']:
68 if key_id in all_keys:
69 key = all_keys[key_id]
70 keys += [{'key_type': key['key_type'],
75 # All (per-node and global) attributes for this slice
77 for slice_tag_id in slice['slice_tag_ids']:
78 if slice_tag_id in all_slice_tags:
79 slice_tags.append(all_slice_tags[slice_tag_id])
81 # Per-node sliver attributes take precedence over global
82 # slice attributes, so set them first.
83 # Then comes nodegroup slice attributes
84 # Followed by global slice attributes
85 sliver_attributes = []
88 for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
89 sliver_attributes.append(sliver_attribute['tagname'])
90 attributes.append({'tagname': sliver_attribute['tagname'],
91 'value': sliver_attribute['value']})
93 # set nodegroup slice attributes
94 for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
95 # Do not set any nodegroup slice attributes for
96 # which there is at least one sliver attribute
98 if slice_tag not in slice_tags:
99 attributes.append({'tagname': slice_tag['tagname'],
100 'value': slice_tag['value']})
102 for slice_tag in [ a for a in slice_tags if a['node_id'] is None ]:
103 # Do not set any global slice attributes for
104 # which there is at least one sliver attribute
106 if slice_tag['tagname'] not in sliver_attributes:
107 attributes.append({'tagname': slice_tag['tagname'],
108 'value': slice_tag['value']})
110 # XXX Sanity check; though technically this should be a system invariant
111 # checked with an assertion
112 if slice['expires'] > MAXINT: slice['expires']= MAXINT
114 # expose the slice vref as computed by GetSliceFamily
115 family = GetSliceFamily (api,caller).call(auth, slice['slice_id'])
118 'name': slice['name'],
119 'slice_id': slice['slice_id'],
120 'instantiation': slice['instantiation'],
121 'expires': slice['expires'],
123 'attributes': attributes,
124 'GetSliceFamily': family,
129 ### The pickle module, used in conjunction with caching has a restriction that it does not
130 ### work on "connection objects." It doesn't matter if the connection object has
131 ### an 'str' or 'repr' method, there is a taint check that throws an exception if
132 ### the pickled class is found to derive from a connection.
133 ### (To be moved to Method.py)
135 def sanitize_for_pickle (obj):
136 if (isinstance(obj, dict)):
138 for k in parent.keys(): parent[k] = sanitize_for_pickle (parent[k])
140 elif (isinstance(obj, list)):
142 parent = map(sanitize_for_pickle, parent)
147 class GetSlivers(Method):
149 Returns a struct containing information about the specified node
150 (or calling node, if called by a node and node_id_or_hostname is
151 not specified), including the current set of slivers bound to the
154 All of the information returned by this call can be gathered from
155 other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
156 function exists almost solely for the benefit of Node Manager.
159 roles = ['admin', 'node']
163 Mixed(Node.fields['node_id'],
164 Node.fields['hostname']),
168 'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
169 'node_id': Node.fields['node_id'],
170 'hostname': Node.fields['hostname'],
171 'interfaces': [Interface.fields],
172 'groups': [NodeGroup.fields['groupname']],
173 'conf_files': [ConfFile.fields],
174 'initscripts': [InitScript.fields],
176 'name': Parameter(str, "unix style account name", max = 254),
178 'key_type': Key.fields['key_type'],
179 'key': Key.fields['key']
183 'name': Slice.fields['name'],
184 'slice_id': Slice.fields['slice_id'],
185 'instantiation': Slice.fields['instantiation'],
186 'expires': Slice.fields['expires'],
188 'key_type': Key.fields['key_type'],
189 'key': Key.fields['key']
192 'tagname': SliceTag.fields['tagname'],
193 'value': SliceTag.fields['value']
196 # how to reach the xmpp server
197 'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
198 'user':Parameter(str,"username for the XMPP server"),
199 'password':Parameter(str,"username for the XMPP server"),
201 # we consider three policies (reservation-policy)
202 # none : the traditional way to use a node
203 # lease_or_idle : 0 or 1 slice runs at a given time
204 # lease_or_shared : 1 slice is running during a lease, otherwise all the slices come back
205 'reservation_policy': Parameter(str,"one among none, lease_or_idle, lease_or_shared"),
206 'leases': [ { 'slice_id' : Lease.fields['slice_id'],
207 't_from' : Lease.fields['t_from'],
208 't_until' : Lease.fields['t_until'],
212 def call(self, auth, node_id_or_hostname = None):
214 cache_opt = self.api.config.PLC_GETSLIVERS_CACHE
219 return self.cacheable_call(auth, node_id_or_hostname)
221 return self.raw_call(auth, node_id_or_hostname)
224 def cacheable_call(self, auth, node_id_or_hostname):
225 return self.raw_call(auth, node_id_or_hostname)
227 def raw_call(self, auth, node_id_or_hostname):
228 timestamp = int(time.time())
231 if node_id_or_hostname is None:
232 if isinstance(self.caller, Node):
235 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
237 nodes = Nodes(self.api, [node_id_or_hostname])
239 raise PLCInvalidArgument, "No such node"
242 if node['peer_id'] is not None:
243 raise PLCInvalidArgument, "Not a local node"
245 # Get interface information
246 interfaces = Interfaces(self.api, node['interface_ids'])
248 # Get node group information
249 nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
250 groups = nodegroups.keys()
252 # Get all (enabled) configuration files
253 all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
256 # Global configuration files are the default. If multiple
257 # entries for the same global configuration file exist, it is
258 # undefined which one takes precedence.
259 for conf_file in all_conf_files.values():
260 if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
261 conf_files[conf_file['dest']] = conf_file
263 # Node group configuration files take precedence over global
264 # ones. If a node belongs to multiple node groups for which
265 # the same configuration file is defined, it is undefined
266 # which one takes precedence.
267 for nodegroup in nodegroups.values():
268 for conf_file_id in nodegroup['conf_file_ids']:
269 if conf_file_id in all_conf_files:
270 conf_file = all_conf_files[conf_file_id]
271 conf_files[conf_file['dest']] = conf_file
273 # Node configuration files take precedence over node group
274 # configuration files.
275 for conf_file_id in node['conf_file_ids']:
276 if conf_file_id in all_conf_files:
277 conf_file = all_conf_files[conf_file_id]
278 conf_files[conf_file['dest']] = conf_file
280 # Get all (enabled) initscripts
281 initscripts = InitScripts(self.api, {'enabled': True})
284 system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
285 system_slice_ids = system_slice_tags.keys()
287 # Get nm-controller slices
288 # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
289 controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
290 controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
291 slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
293 slivers = get_slivers(self.api, self.caller, auth, slice_ids, node)
295 # get the special accounts and keys needed for the node
299 if False and 'site_id' not in node:
300 nodes = Nodes(self.api, node['node_id'])
303 # used in conjunction with reduce to flatten lists, like in
304 # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
305 def reduce_flatten_list (x,y): return x+y
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
332 personsitekeys=get_all_admin_keys(self.api)
333 accounts.append({'name':'root','keys':personsitekeys})
335 hrn = GetNodeHrn(self.api,self.caller).call(auth,node['node_id'])
337 # XMPP config for omf federation
339 if not self.api.config.PLC_OMF_ENABLED:
340 raise Exception,"OMF disabled"
341 xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
342 'user':self.api.config.PLC_OMF_XMPP_USER,
343 'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
346 xmpp={'server':None,'user':None,'password':None}
348 node.update_last_contact()
350 # expose leases & reservation policy
351 # in a first implementation we only support none and lease_or_idle
352 lease_exposed_fields = [ 'slice_id', 't_from', 't_until', 'name', ]
354 if node['node_type'] != 'reservable':
355 reservation_policy='none'
357 reservation_policy='lease_or_idle'
358 # expose the leases for the next 24 hours
359 leases = [ dict ( [ (k,l[k]) for k in lease_exposed_fields ] )
360 for l in Leases (self.api, {'node_id':node['node_id'],
361 'clip': (timestamp, timestamp+24*Duration.HOUR),
364 granularity=self.api.config.PLC_RESERVATION_GRANULARITY
367 'timestamp': timestamp,
368 'node_id': node['node_id'],
369 'hostname': node['hostname'],
370 'interfaces': interfaces,
372 'conf_files': conf_files.values(),
373 'initscripts': initscripts,
375 'accounts': accounts,
378 'reservation_policy': reservation_policy,
380 'lease_granularity': granularity,
383 sanitized_data = sanitize_for_pickle (raw_data)
384 return sanitized_data