leverages new Filter features to gather all admins and site power users
[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
22 # XXX used to check if slice expiration time is sane
23 MAXINT =  2L**31-1
24
25 def get_slivers(api, slice_filter, node = None):
26     # Get slice information
27     slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
28
29     # Build up list of users and slice attributes
30     person_ids = set()
31     slice_tag_ids = set()
32     for slice in slices:
33         person_ids.update(slice['person_ids'])
34         slice_tag_ids.update(slice['slice_tag_ids'])
35
36     # Get user information
37     all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
38
39     # Build up list of keys
40     key_ids = set()
41     for person in all_persons.values():
42         key_ids.update(person['key_ids'])
43
44     # Get user account keys
45     all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
46
47     # Get slice attributes
48     all_slice_tags = SliceTags(api, slice_tag_ids).dict()
49
50     slivers = []
51     for slice in slices:
52         keys = []
53         for person_id in slice['person_ids']:
54             if person_id in all_persons:
55                 person = all_persons[person_id]
56                 if not person['enabled']:
57                     continue
58                 for key_id in person['key_ids']:
59                     if key_id in all_keys:
60                         key = all_keys[key_id]
61                         keys += [{'key_type': key['key_type'],
62                                   'key': key['key']}]
63
64         attributes = []
65
66         # All (per-node and global) attributes for this slice
67         slice_tags = []
68         for slice_tag_id in slice['slice_tag_ids']:
69             if slice_tag_id in all_slice_tags:
70                 slice_tags.append(all_slice_tags[slice_tag_id])
71
72         # Per-node sliver attributes take precedence over global
73         # slice attributes, so set them first.
74         # Then comes nodegroup slice attributes
75         # Followed by global slice attributes
76         sliver_attributes = []
77
78         if node is not None:
79             for sliver_attribute in filter(lambda a: a['node_id'] == node['node_id'], slice_tags):
80                 sliver_attributes.append(sliver_attribute['tagname'])
81                 attributes.append({'tagname': sliver_attribute['tagname'],
82                                    'value': sliver_attribute['value']})
83
84             # set nodegroup slice attributes
85             for slice_tag in filter(lambda a: a['nodegroup_id'] in node['nodegroup_ids'], slice_tags):
86                 # Do not set any nodegroup slice attributes for
87                 # which there is at least one sliver attribute
88                 # already set.
89                 if slice_tag not in slice_tags:
90                     attributes.append({'tagname': slice_tag['tagname'],
91                                    'value': slice_tag['value']})
92
93         for slice_tag in filter(lambda a: a['node_id'] is None, slice_tags):
94             # Do not set any global slice attributes for
95             # which there is at least one sliver attribute
96             # already set.
97             if slice_tag['tagname'] not in sliver_attributes:
98                 attributes.append({'tagname': slice_tag['tagname'],
99                                    'value': slice_tag['value']})
100
101         # XXX Sanity check; though technically this should be a system invariant
102         # checked with an assertion
103         if slice['expires'] > MAXINT:  slice['expires']= MAXINT
104
105         slivers.append({
106             'name': slice['name'],
107             'slice_id': slice['slice_id'],
108             'instantiation': slice['instantiation'],
109             'expires': slice['expires'],
110             'keys': keys,
111             'attributes': attributes
112             })
113
114     return slivers
115
116 class v43GetSlivers(Method):
117     """
118     Returns a struct containing information about the specified node
119     (or calling node, if called by a node and node_id_or_hostname is
120     not specified), including the current set of slivers bound to the
121     node.
122
123     All of the information returned by this call can be gathered from
124     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
125     function exists almost solely for the benefit of Node Manager.
126     """
127
128     roles = ['admin', 'node']
129
130     accepts = [
131         Auth(),
132         Mixed(Node.fields['node_id'],
133               Node.fields['hostname']),
134         ]
135
136     returns = {
137         'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
138         'node_id': Node.fields['node_id'],
139         'hostname': Node.fields['hostname'],
140         'networks': [Interface.fields],
141         'groups': [NodeGroup.fields['groupname']],
142         'conf_files': [ConfFile.fields],
143         'initscripts': [InitScript.fields],
144         'accounts': [{
145             'name': Parameter(str, "unix style account name", max = 254),
146             'keys': [{
147                 'key_type': Key.fields['key_type'],
148                 'key': Key.fields['key']
149             }],
150             }],
151         'slivers': [{
152             'name': Slice.fields['name'],
153             'slice_id': Slice.fields['slice_id'],
154             'instantiation': Slice.fields['instantiation'],
155             'expires': Slice.fields['expires'],
156             'keys': [{
157                 'key_type': Key.fields['key_type'],
158                 'key': Key.fields['key']
159             }],
160             'attributes': [{
161                 'tagname': SliceTag.fields['tagname'],
162                 'value': SliceTag.fields['value']
163             }]
164         }]
165     }
166
167     def call(self, auth, node_id_or_hostname = None):
168         timestamp = int(time.time())
169
170         # Get node
171         if node_id_or_hostname is None:
172             if isinstance(self.caller, Node):
173                 node = self.caller
174             else:
175                 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
176         else:
177             nodes = Nodes(self.api, [node_id_or_hostname])
178             if not nodes:
179                 raise PLCInvalidArgument, "No such node"
180             node = nodes[0]
181
182             if node['peer_id'] is not None:
183                 raise PLCInvalidArgument, "Not a local node"
184
185         # Get interface information
186         networks = Interfaces(self.api, node['interface_ids'])
187
188         # Get node group information
189         nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
190         groups = nodegroups.keys()
191
192         # Get all (enabled) configuration files
193         all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
194         conf_files = {}
195
196         # Global configuration files are the default. If multiple
197         # entries for the same global configuration file exist, it is
198         # undefined which one takes precedence.
199         for conf_file in all_conf_files.values():
200             if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
201                 conf_files[conf_file['dest']] = conf_file
202         
203         # Node group configuration files take precedence over global
204         # ones. If a node belongs to multiple node groups for which
205         # the same configuration file is defined, it is undefined
206         # which one takes precedence.
207         for nodegroup in nodegroups.values():
208             for conf_file_id in nodegroup['conf_file_ids']:
209                 if conf_file_id in all_conf_files:
210                     conf_file = all_conf_files[conf_file_id]
211                     conf_files[conf_file['dest']] = conf_file
212         
213         # Node configuration files take precedence over node group
214         # configuration files.
215         for conf_file_id in node['conf_file_ids']:
216             if conf_file_id in all_conf_files:
217                 conf_file = all_conf_files[conf_file_id]
218                 conf_files[conf_file['dest']] = conf_file            
219
220         # Get all (enabled) initscripts
221         initscripts = InitScripts(self.api, {'enabled': True})  
222
223         # Get system slices
224         system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
225         system_slice_ids = system_slice_tags.keys()
226         
227         # Get nm-controller slices
228         controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
229         controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
230         slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
231
232         slivers = get_slivers(self.api, slice_ids, node)
233
234         # get the special accounts and keys needed for the node
235         # root
236         # site_admin
237         accounts = []
238         if False and 'site_id' not in node:
239             nodes = Nodes(self.api, node['node_id'])
240             node = nodes[0]
241
242         # used in conjunction with reduce to flatten lists, like in
243         # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
244         def reduce_flatten_list (x,y): return x+y
245
246         # power users are pis and techs
247         def get_site_power_user_keys(api,site_id_or_name):
248             site = Sites (api,site_id_or_name,['person_ids'])[0]
249             key_ids = reduce (reduce_flatten_list, 
250                               [ p['key_ids'] for p in \
251                                     Persons(api,{ 'person_id':site['person_ids'], 
252                                                   'enabled':True, '|role_ids' : [20, 40] }, 
253                                             ['key_ids']) ],
254                               [])
255             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
256
257         # all admins regardless of their site
258         def get_all_admin_keys(api):
259             key_ids = reduce (reduce_flatten_list, 
260                               [ p['key_ids'] for p in \
261                                     Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
262                                             ['key_ids']) ],
263                               [])
264             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
265
266         # 'site_admin' account setup
267         personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
268         accounts.append({'name':'site_admin','keys':personsitekeys})
269
270         # 'root' account setup on nodes from all 'admin' users
271         personsitekeys=get_all_admin_keys(self.api)
272         accounts.append({'name':'root','keys':personsitekeys})
273
274         node.update_last_contact()
275
276         return {
277             'timestamp': timestamp,
278             'node_id': node['node_id'],
279             'hostname': node['hostname'],
280             'networks': networks,
281             'groups': groups,
282             'conf_files': conf_files.values(),
283             'initscripts': initscripts,
284             'slivers': slivers,
285             'accounts': accounts
286             }
287
288 class v42GetSlivers(v43GetSlivers):
289     """
290     Legacy wrapper for v43GetSlivers.
291     """
292
293     def call(self, auth, node_id_or_hostname = None):
294         result = v43GetSlivers.call(self,auth,node_id_or_hostname)
295         networks = result['networks']
296
297         for i in range(0,len(networks)):
298             network = networks[i]
299             if network.has_key("interface_id"):
300                 network['nodenetwork_id']=network['interface_id']
301             if network.has_key("interface_tag_ids"):
302                 network['nodenetwork_setting_ids']=network['interface_tag_ids']
303             networks[i]=network
304
305         result['networks']=networks
306         return result
307
308 class GetSlivers(v42GetSlivers):
309     """
310     Returns a struct containing information about the specified node
311     (or calling node, if called by a node and node_id_or_hostname is
312     not specified), including the current set of slivers bound to the
313     node.
314
315     All of the information returned by this call can be gathered from
316     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
317     function exists almost solely for the benefit of Node Manager.
318     """
319
320     pass