5a129cad30e1e314f4ecc7a927207bba51da1a62
[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.Methods.GetSliceFamily import GetSliceFamily
22
23 # XXX used to check if slice expiration time is sane
24 MAXINT =  2L**31-1
25
26 def get_slivers(api, auth, slice_filter, node = None):
27     # Get slice information
28     slices = Slices(api, slice_filter, ['slice_id', 'name', 'instantiation', 'expires', 'person_ids', 'slice_tag_ids'])
29
30     # Build up list of users and slice attributes
31     person_ids = set()
32     slice_tag_ids = set()
33     for slice in slices:
34         person_ids.update(slice['person_ids'])
35         slice_tag_ids.update(slice['slice_tag_ids'])
36
37     # Get user information
38     all_persons = Persons(api, {'person_id':person_ids,'enabled':True}, ['person_id', 'enabled', 'key_ids']).dict()
39
40     # Build up list of keys
41     key_ids = set()
42     for person in all_persons.values():
43         key_ids.update(person['key_ids'])
44
45     # Get user account keys
46     all_keys = Keys(api, key_ids, ['key_id', 'key', 'key_type']).dict()
47
48     # Get slice attributes
49     all_slice_tags = SliceTags(api, slice_tag_ids).dict()
50
51     slivers = []
52     for slice in slices:
53         keys = []
54         for person_id in slice['person_ids']:
55             if person_id in all_persons:
56                 person = all_persons[person_id]
57                 if not person['enabled']:
58                     continue
59                 for key_id in person['key_ids']:
60                     if key_id in all_keys:
61                         key = all_keys[key_id]
62                         keys += [{'key_type': key['key_type'],
63                                   'key': key['key']}]
64
65         attributes = []
66
67         # All (per-node and global) attributes for this slice
68         slice_tags = []
69         for slice_tag_id in slice['slice_tag_ids']:
70             if slice_tag_id in all_slice_tags:
71                 slice_tags.append(all_slice_tags[slice_tag_id])
72
73         # Per-node sliver attributes take precedence over global
74         # slice attributes, so set them first.
75         # Then comes nodegroup slice attributes
76         # Followed by global slice attributes
77         sliver_attributes = []
78
79         if node is not None:
80             for sliver_attribute in [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
81                 sliver_attributes.append(sliver_attribute['tagname'])
82                 attributes.append({'tagname': sliver_attribute['tagname'],
83                                    'value': sliver_attribute['value']})
84
85             # set nodegroup slice attributes
86             for slice_tag in [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
87                 # Do not set any nodegroup slice attributes for
88                 # which there is at least one sliver attribute
89                 # already set.
90                 if slice_tag not in slice_tags:
91                     attributes.append({'tagname': slice_tag['tagname'],
92                                    'value': slice_tag['value']})
93
94         for slice_tag in [ a for a in slice_tags if a['node_id'] is None ]:
95             # Do not set any global slice attributes for
96             # which there is at least one sliver attribute
97             # already set.
98             if slice_tag['tagname'] not in sliver_attributes:
99                 attributes.append({'tagname': slice_tag['tagname'],
100                                    'value': slice_tag['value']})
101
102         # XXX Sanity check; though technically this should be a system invariant
103         # checked with an assertion
104         if slice['expires'] > MAXINT:  slice['expires']= MAXINT
105
106         # expose the slice vref as computed by GetSliceFamily
107         family = GetSliceFamily (api).call(auth, slice['slice_id'])
108
109         slivers.append({
110             'name': slice['name'],
111             'slice_id': slice['slice_id'],
112             'instantiation': slice['instantiation'],
113             'expires': slice['expires'],
114             'keys': keys,
115             'attributes': attributes,
116             'GetSliceFamily': family,
117             })
118
119     return slivers
120
121 class v43GetSlivers(Method):
122     """
123     Returns a struct containing information about the specified node
124     (or calling node, if called by a node and node_id_or_hostname is
125     not specified), including the current set of slivers bound to the
126     node.
127
128     All of the information returned by this call can be gathered from
129     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
130     function exists almost solely for the benefit of Node Manager.
131     """
132
133     roles = ['admin', 'node']
134
135     accepts = [
136         Auth(),
137         Mixed(Node.fields['node_id'],
138               Node.fields['hostname']),
139         ]
140
141     returns = {
142         'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
143         'node_id': Node.fields['node_id'],
144         'hostname': Node.fields['hostname'],
145         'networks': [Interface.fields],
146         'groups': [NodeGroup.fields['groupname']],
147         'conf_files': [ConfFile.fields],
148         'initscripts': [InitScript.fields],
149         'accounts': [{
150             'name': Parameter(str, "unix style account name", max = 254),
151             'keys': [{
152                 'key_type': Key.fields['key_type'],
153                 'key': Key.fields['key']
154             }],
155             }],
156         'slivers': [{
157             'name': Slice.fields['name'],
158             'slice_id': Slice.fields['slice_id'],
159             'instantiation': Slice.fields['instantiation'],
160             'expires': Slice.fields['expires'],
161             'keys': [{
162                 'key_type': Key.fields['key_type'],
163                 'key': Key.fields['key']
164             }],
165             'attributes': [{
166                 'tagname': SliceTag.fields['tagname'],
167                 'value': SliceTag.fields['value']
168             }]
169         }]
170     }
171
172     def call(self, auth, node_id_or_hostname = None):
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         # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
234         controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
235         controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
236         slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
237
238         slivers = get_slivers(self.api, auth, slice_ids, node)
239
240         # get the special accounts and keys needed for the node
241         # root
242         # site_admin
243         accounts = []
244         if False and 'site_id' not in node:
245             nodes = Nodes(self.api, node['node_id'])
246             node = nodes[0]
247
248         # used in conjunction with reduce to flatten lists, like in
249         # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
250         def reduce_flatten_list (x,y): return x+y
251
252         # power users are pis and techs
253         def get_site_power_user_keys(api,site_id_or_name):
254             site = Sites (api,site_id_or_name,['person_ids'])[0]
255             key_ids = reduce (reduce_flatten_list, 
256                               [ p['key_ids'] for p in \
257                                     Persons(api,{ 'person_id':site['person_ids'], 
258                                                   'enabled':True, '|role_ids' : [20, 40] }, 
259                                             ['key_ids']) ],
260                               [])
261             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
262
263         # all admins regardless of their site
264         def get_all_admin_keys(api):
265             key_ids = reduce (reduce_flatten_list, 
266                               [ p['key_ids'] for p in \
267                                     Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
268                                             ['key_ids']) ],
269                               [])
270             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
271
272         # 'site_admin' account setup
273         personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
274         accounts.append({'name':'site_admin','keys':personsitekeys})
275
276         # 'root' account setup on nodes from all 'admin' users
277         personsitekeys=get_all_admin_keys(self.api)
278         accounts.append({'name':'root','keys':personsitekeys})
279
280         node.update_last_contact()
281
282         return {
283             'timestamp': timestamp,
284             'node_id': node['node_id'],
285             'hostname': node['hostname'],
286             'networks': networks,
287             'groups': groups,
288             'conf_files': conf_files.values(),
289             'initscripts': initscripts,
290             'slivers': slivers,
291             'accounts': accounts
292             }
293
294 class v42GetSlivers(v43GetSlivers):
295     """
296     Legacy wrapper for v43GetSlivers.
297     """
298
299     def call(self, auth, node_id_or_hostname = None):
300         result = v43GetSlivers.call(self,auth,node_id_or_hostname)
301         networks = result['networks']
302
303         for i in range(0,len(networks)):
304             network = networks[i]
305             if network.has_key("interface_id"):
306                 network['nodenetwork_id']=network['interface_id']
307             if network.has_key("interface_tag_ids"):
308                 network['nodenetwork_setting_ids']=network['interface_tag_ids']
309             networks[i]=network
310
311         result['networks']=networks
312         return result
313
314 class GetSlivers(v42GetSlivers):
315     """
316     Returns a struct containing information about the specified node
317     (or calling node, if called by a node and node_id_or_hostname is
318     not specified), including the current set of slivers bound to the
319     node.
320
321     All of the information returned by this call can be gathered from
322     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
323     function exists almost solely for the benefit of Node Manager.
324     """
325
326     pass