2d201b94d7672c091ac2ccb140463b06967cd0d9
[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 GetSlivers(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         'interfaces': [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         'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
171                  'user':Parameter(str,"username for the XMPP server"),
172                  'password':Parameter(str,"username for the XMPP server"),
173                  },
174     }
175
176     def call(self, auth, node_id_or_hostname = None):
177         timestamp = int(time.time())
178
179         # Get node
180         if node_id_or_hostname is None:
181             if isinstance(self.caller, Node):
182                 node = self.caller
183             else:
184                 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
185         else:
186             nodes = Nodes(self.api, [node_id_or_hostname])
187             if not nodes:
188                 raise PLCInvalidArgument, "No such node"
189             node = nodes[0]
190
191             if node['peer_id'] is not None:
192                 raise PLCInvalidArgument, "Not a local node"
193
194         # Get interface information
195         interfaces = Interfaces(self.api, node['interface_ids'])
196
197         # Get node group information
198         nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
199         groups = nodegroups.keys()
200
201         # Get all (enabled) configuration files
202         all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
203         conf_files = {}
204
205         # Global configuration files are the default. If multiple
206         # entries for the same global configuration file exist, it is
207         # undefined which one takes precedence.
208         for conf_file in all_conf_files.values():
209             if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
210                 conf_files[conf_file['dest']] = conf_file
211         
212         # Node group configuration files take precedence over global
213         # ones. If a node belongs to multiple node groups for which
214         # the same configuration file is defined, it is undefined
215         # which one takes precedence.
216         for nodegroup in nodegroups.values():
217             for conf_file_id in nodegroup['conf_file_ids']:
218                 if conf_file_id in all_conf_files:
219                     conf_file = all_conf_files[conf_file_id]
220                     conf_files[conf_file['dest']] = conf_file
221         
222         # Node configuration files take precedence over node group
223         # configuration files.
224         for conf_file_id in node['conf_file_ids']:
225             if conf_file_id in all_conf_files:
226                 conf_file = all_conf_files[conf_file_id]
227                 conf_files[conf_file['dest']] = conf_file            
228
229         # Get all (enabled) initscripts
230         initscripts = InitScripts(self.api, {'enabled': True})  
231
232         # Get system slices
233         system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
234         system_slice_ids = system_slice_tags.keys()
235         
236         # Get nm-controller slices
237         # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
238         controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
239         controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
240         slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
241
242         slivers = get_slivers(self.api, auth, slice_ids, node)
243
244         # get the special accounts and keys needed for the node
245         # root
246         # site_admin
247         accounts = []
248         if False and 'site_id' not in node:
249             nodes = Nodes(self.api, node['node_id'])
250             node = nodes[0]
251
252         # used in conjunction with reduce to flatten lists, like in
253         # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
254         def reduce_flatten_list (x,y): return x+y
255
256         # power users are pis and techs
257         def get_site_power_user_keys(api,site_id_or_name):
258             site = Sites (api,site_id_or_name,['person_ids'])[0]
259             key_ids = reduce (reduce_flatten_list, 
260                               [ p['key_ids'] for p in \
261                                     Persons(api,{ 'person_id':site['person_ids'], 
262                                                   'enabled':True, '|role_ids' : [20, 40] }, 
263                                             ['key_ids']) ],
264                               [])
265             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
266
267         # all admins regardless of their site
268         def get_all_admin_keys(api):
269             key_ids = reduce (reduce_flatten_list, 
270                               [ p['key_ids'] for p in \
271                                     Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
272                                             ['key_ids']) ],
273                               [])
274             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
275
276         # 'site_admin' account setup
277         personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
278         accounts.append({'name':'site_admin','keys':personsitekeys})
279
280         # 'root' account setup on nodes from all 'admin' users
281         personsitekeys=get_all_admin_keys(self.api)
282         accounts.append({'name':'root','keys':personsitekeys})
283
284         node.update_last_contact()
285
286         # XMPP config for omf federation
287         try:
288             if not self.api.config.PLC_OMF_ENABLED:
289                 raise Exception,"OMF disabled"
290             xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
291                   'user':self.api.config.PLC_OMF_XMPP_USER,
292                   'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
293                   }
294         except:
295             xmpp={'server':None,'user':None,'password':None}
296
297         return {
298             'timestamp': timestamp,
299             'node_id': node['node_id'],
300             'hostname': node['hostname'],
301             'interfaces': interfaces,
302             'groups': groups,
303             'conf_files': conf_files.values(),
304             'initscripts': initscripts,
305             'slivers': slivers,
306             'accounts': accounts,
307             'xmpp':xmpp,
308             }