expose reservation policy
[plcapi.git] / PLC / Methods / GetSlivers.py
1 # $Id$
2 # $URL$
3 import time
4
5 from PLC.Faults import *
6 from PLC.Method import Method
7 from PLC.Parameter import Parameter, Mixed
8 from PLC.Filter import Filter
9 from PLC.Auth import Auth
10 from PLC.Nodes import Node, Nodes
11 from PLC.Interfaces import Interface, Interfaces
12 from PLC.NodeGroups import NodeGroup, NodeGroups
13 from PLC.ConfFiles import ConfFile, ConfFiles
14 from PLC.Slices import Slice, Slices
15 from PLC.Persons import Person, Persons
16 from PLC.Sites import Sites
17 from PLC.Roles import Roles
18 from PLC.Keys import Key, Keys
19 from PLC.SliceTags import SliceTag, SliceTags
20 from PLC.InitScripts import InitScript, InitScripts
21 from PLC.Leases import Lease, Leases
22 from PLC.Timestamp import Duration
23 from PLC.Methods.GetSliceFamily import GetSliceFamily
24
25 from PLC.Accessors.Accessors_standard import *
26
27 # XXX used to check if slice expiration time is sane
28 MAXINT =  2L**31-1
29
30 def get_slivers(api, 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'])
33
34     # Build up list of users and slice attributes
35     person_ids = set()
36     slice_tag_ids = set()
37     for slice in slices:
38         person_ids.update(slice['person_ids'])
39         slice_tag_ids.update(slice['slice_tag_ids'])
40
41     # Get user information
42     all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
43
44     # Build up list of keys
45     key_ids = set()
46     for person in all_persons.values():
47         key_ids.update(person['key_ids'])
48
49     # Get user account keys
50     all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
51
52     # Get slice attributes
53     all_slice_tags = SliceTags(api, slice_tag_ids).dict()
54
55     slivers = []
56     for slice in slices:
57         keys = []
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']:
62                     continue
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'],
67                                   'key': key['key']}]
68
69         attributes = []
70
71         # All (per-node and global) attributes for this slice
72         slice_tags = []
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])
76
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 = []
82
83         if node is not None:
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']})
88
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
93                 # already set.
94                 if slice_tag not in slice_tags:
95                     attributes.append({'tagname': slice_tag['tagname'],
96                                    'value': slice_tag['value']})
97
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
101             # already set.
102             if slice_tag['tagname'] not in sliver_attributes:
103                 attributes.append({'tagname': slice_tag['tagname'],
104                                    'value': slice_tag['value']})
105
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
109
110         # expose the slice vref as computed by GetSliceFamily
111         family = GetSliceFamily (api).call(auth, slice['slice_id'])
112
113         slivers.append({
114             'name': slice['name'],
115             'slice_id': slice['slice_id'],
116             'instantiation': slice['instantiation'],
117             'expires': slice['expires'],
118             'keys': keys,
119             'attributes': attributes,
120             'GetSliceFamily': family,
121             })
122
123     return slivers
124
125 class GetSlivers(Method):
126     """
127     Returns a struct containing information about the specified node
128     (or calling node, if called by a node and node_id_or_hostname is
129     not specified), including the current set of slivers bound to the
130     node.
131
132     All of the information returned by this call can be gathered from
133     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
134     function exists almost solely for the benefit of Node Manager.
135     """
136
137     roles = ['admin', 'node']
138
139     accepts = [
140         Auth(),
141         Mixed(Node.fields['node_id'],
142               Node.fields['hostname']),
143         ]
144
145     returns = {
146         'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
147         'node_id': Node.fields['node_id'],
148         'hostname': Node.fields['hostname'],
149         'interfaces': [Interface.fields],
150         'groups': [NodeGroup.fields['groupname']],
151         'conf_files': [ConfFile.fields],
152         'initscripts': [InitScript.fields],
153         'accounts': [{
154             'name': Parameter(str, "unix style account name", max = 254),
155             'keys': [{
156                 'key_type': Key.fields['key_type'],
157                 'key': Key.fields['key']
158             }],
159             }],
160         'slivers': [{
161             'name': Slice.fields['name'],
162             'slice_id': Slice.fields['slice_id'],
163             'instantiation': Slice.fields['instantiation'],
164             'expires': Slice.fields['expires'],
165             'keys': [{
166                 'key_type': Key.fields['key_type'],
167                 'key': Key.fields['key']
168             }],
169             'attributes': [{
170                 'tagname': SliceTag.fields['tagname'],
171                 'value': SliceTag.fields['value']
172             }]
173         }],
174         # how to reach the xmpp server
175         'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
176                  'user':Parameter(str,"username for the XMPP server"),
177                  'password':Parameter(str,"username for the XMPP server"),
178                  },
179         # we consider three policies (reservation-policy)
180         # none : the traditional way to use a node
181         # lease_or_idle : 0 or 1 slice runs at a given time
182         # lease_or_shared : 1 slice is running during a lease, otherwise all the slices come back
183         'reservation_policy': Parameter(str,"one among none, lease_or_idle, lease_or_shared"),
184         'leases': [  { 'slice_id' : Lease.fields['slice_id'],
185                        't_from' : Lease.fields['t_from'],
186                        't_until' : Lease.fields['t_until'],
187                        }],
188     }
189
190     def call(self, auth, node_id_or_hostname = None):
191         timestamp = int(time.time())
192
193         # Get node
194         if node_id_or_hostname is None:
195             if isinstance(self.caller, Node):
196                 node = self.caller
197             else:
198                 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
199         else:
200             nodes = Nodes(self.api, [node_id_or_hostname])
201             if not nodes:
202                 raise PLCInvalidArgument, "No such node"
203             node = nodes[0]
204
205             if node['peer_id'] is not None:
206                 raise PLCInvalidArgument, "Not a local node"
207
208         # Get interface information
209         interfaces = Interfaces(self.api, node['interface_ids'])
210
211         # Get node group information
212         nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
213         groups = nodegroups.keys()
214
215         # Get all (enabled) configuration files
216         all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
217         conf_files = {}
218
219         # Global configuration files are the default. If multiple
220         # entries for the same global configuration file exist, it is
221         # undefined which one takes precedence.
222         for conf_file in all_conf_files.values():
223             if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
224                 conf_files[conf_file['dest']] = conf_file
225         
226         # Node group configuration files take precedence over global
227         # ones. If a node belongs to multiple node groups for which
228         # the same configuration file is defined, it is undefined
229         # which one takes precedence.
230         for nodegroup in nodegroups.values():
231             for conf_file_id in nodegroup['conf_file_ids']:
232                 if conf_file_id in all_conf_files:
233                     conf_file = all_conf_files[conf_file_id]
234                     conf_files[conf_file['dest']] = conf_file
235         
236         # Node configuration files take precedence over node group
237         # configuration files.
238         for conf_file_id in node['conf_file_ids']:
239             if conf_file_id in all_conf_files:
240                 conf_file = all_conf_files[conf_file_id]
241                 conf_files[conf_file['dest']] = conf_file            
242
243         # Get all (enabled) initscripts
244         initscripts = InitScripts(self.api, {'enabled': True})  
245
246         # Get system slices
247         system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
248         system_slice_ids = system_slice_tags.keys()
249         
250         # Get nm-controller slices
251         # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
252         controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
253         controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
254         slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
255
256         slivers = get_slivers(self.api, auth, slice_ids, node)
257
258         # get the special accounts and keys needed for the node
259         # root
260         # site_admin
261         accounts = []
262         if False and 'site_id' not in node:
263             nodes = Nodes(self.api, node['node_id'])
264             node = nodes[0]
265
266         # used in conjunction with reduce to flatten lists, like in
267         # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
268         def reduce_flatten_list (x,y): return x+y
269
270         # power users are pis and techs
271         def get_site_power_user_keys(api,site_id_or_name):
272             site = Sites (api,site_id_or_name,['person_ids'])[0]
273             key_ids = reduce (reduce_flatten_list, 
274                               [ p['key_ids'] for p in \
275                                     Persons(api,{ 'person_id':site['person_ids'], 
276                                                   'enabled':True, '|role_ids' : [20, 40] }, 
277                                             ['key_ids']) ],
278                               [])
279             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
280
281         # all admins regardless of their site
282         def get_all_admin_keys(api):
283             key_ids = reduce (reduce_flatten_list, 
284                               [ p['key_ids'] for p in \
285                                     Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
286                                             ['key_ids']) ],
287                               [])
288             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
289
290         # 'site_admin' account setup
291         personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
292         accounts.append({'name':'site_admin','keys':personsitekeys})
293
294         # 'root' account setup on nodes from all 'admin' users
295         personsitekeys=get_all_admin_keys(self.api)
296         accounts.append({'name':'root','keys':personsitekeys})
297
298         hrn = GetNodeHrn(self.api).call(auth,node['node_id'])
299
300         # XMPP config for omf federation
301         try:
302             if not self.api.config.PLC_OMF_ENABLED:
303                 raise Exception,"OMF disabled"
304             xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
305                   'user':self.api.config.PLC_OMF_XMPP_USER,
306                   'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
307                   }
308         except:
309             xmpp={'server':None,'user':None,'password':None}
310
311         node.update_last_contact()
312
313         # expose leases & reservation policy
314         # in a first implementation we only support none and lease_or_idle
315         lease_exposed_fields = [ 'slice_id', 't_from', 't_until', ]
316         leases=None
317         if node['node_type'] != 'reservable':
318             reservation_policy='none'
319         else:
320             reservation_policy='lease_or_idle'
321             # expose the leases for the next 24 hours
322             leases = [ dict ( [ (k,l[k]) for k in lease_exposed_fields ] ) 
323                        for l in Leases (self.api, {'node_id':node['node_id'],
324                                                    'clip': (timestamp, timestamp+24*Duration.HOUR),
325                                                    '-SORT': 't_from',
326                                                    }) ]
327
328         return {
329             'timestamp': timestamp,
330             'node_id': node['node_id'],
331             'hostname': node['hostname'],
332             'interfaces': interfaces,
333             'groups': groups,
334             'conf_files': conf_files.values(),
335             'initscripts': initscripts,
336             'slivers': slivers,
337             'accounts': accounts,
338             'xmpp':xmpp,
339             'hrn':hrn,
340             'reservation_policy': reservation_policy,
341             'leases':leases,
342             }