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