98fd4256b8671bd072ed16ce34582a8077faa693
[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 from PLC.Accessors.Accessors_standard import *
24
25 # XXX used to check if slice expiration time is sane
26 MAXINT =  2L**31-1
27
28 def get_slivers(api, auth, 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 [ a for a in slice_tags if a['node_id'] == node['node_id'] ]:
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 [ a for a in slice_tags if a['nodegroup_id'] in node['nodegroup_ids'] ]:
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 [ a for a in slice_tags if a['node_id'] is None ]:
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         # expose the slice vref as computed by GetSliceFamily
109         family = GetSliceFamily (api).call(auth, slice['slice_id'])
110
111         slivers.append({
112             'name': slice['name'],
113             'slice_id': slice['slice_id'],
114             'instantiation': slice['instantiation'],
115             'expires': slice['expires'],
116             'keys': keys,
117             'attributes': attributes,
118             'GetSliceFamily': family,
119             })
120
121     return slivers
122
123 class GetSlivers(Method):
124     """
125     Returns a struct containing information about the specified node
126     (or calling node, if called by a node and node_id_or_hostname is
127     not specified), including the current set of slivers bound to the
128     node.
129
130     All of the information returned by this call can be gathered from
131     other calls, e.g. GetNodes, GetInterfaces, GetSlices, etc. This
132     function exists almost solely for the benefit of Node Manager.
133     """
134
135     roles = ['admin', 'node']
136
137     accepts = [
138         Auth(),
139         Mixed(Node.fields['node_id'],
140               Node.fields['hostname']),
141         ]
142
143     returns = {
144         'timestamp': Parameter(int, "Timestamp of this call, in seconds since UNIX epoch"),
145         'node_id': Node.fields['node_id'],
146         'hostname': Node.fields['hostname'],
147         'interfaces': [Interface.fields],
148         'groups': [NodeGroup.fields['groupname']],
149         'conf_files': [ConfFile.fields],
150         'initscripts': [InitScript.fields],
151         'accounts': [{
152             'name': Parameter(str, "unix style account name", max = 254),
153             'keys': [{
154                 'key_type': Key.fields['key_type'],
155                 'key': Key.fields['key']
156             }],
157             }],
158         'slivers': [{
159             'name': Slice.fields['name'],
160             'slice_id': Slice.fields['slice_id'],
161             'instantiation': Slice.fields['instantiation'],
162             'expires': Slice.fields['expires'],
163             'keys': [{
164                 'key_type': Key.fields['key_type'],
165                 'key': Key.fields['key']
166             }],
167             'attributes': [{
168                 'tagname': SliceTag.fields['tagname'],
169                 'value': SliceTag.fields['value']
170             }]
171         }],
172         'xmpp': {'server':Parameter(str,"hostname for the XMPP server"),
173                  'user':Parameter(str,"username for the XMPP server"),
174                  'password':Parameter(str,"username for the XMPP server"),
175                  },
176     }
177
178     def call(self, auth, node_id_or_hostname = None):
179         timestamp = int(time.time())
180
181         # Get node
182         if node_id_or_hostname is None:
183             if isinstance(self.caller, Node):
184                 node = self.caller
185             else:
186                 raise PLCInvalidArgument, "'node_id_or_hostname' not specified"
187         else:
188             nodes = Nodes(self.api, [node_id_or_hostname])
189             if not nodes:
190                 raise PLCInvalidArgument, "No such node"
191             node = nodes[0]
192
193             if node['peer_id'] is not None:
194                 raise PLCInvalidArgument, "Not a local node"
195
196         # Get interface information
197         interfaces = Interfaces(self.api, node['interface_ids'])
198
199         # Get node group information
200         nodegroups = NodeGroups(self.api, node['nodegroup_ids']).dict('groupname')
201         groups = nodegroups.keys()
202
203         # Get all (enabled) configuration files
204         all_conf_files = ConfFiles(self.api, {'enabled': True}).dict()
205         conf_files = {}
206
207         # Global configuration files are the default. If multiple
208         # entries for the same global configuration file exist, it is
209         # undefined which one takes precedence.
210         for conf_file in all_conf_files.values():
211             if not conf_file['node_ids'] and not conf_file['nodegroup_ids']:
212                 conf_files[conf_file['dest']] = conf_file
213         
214         # Node group configuration files take precedence over global
215         # ones. If a node belongs to multiple node groups for which
216         # the same configuration file is defined, it is undefined
217         # which one takes precedence.
218         for nodegroup in nodegroups.values():
219             for conf_file_id in nodegroup['conf_file_ids']:
220                 if conf_file_id in all_conf_files:
221                     conf_file = all_conf_files[conf_file_id]
222                     conf_files[conf_file['dest']] = conf_file
223         
224         # Node configuration files take precedence over node group
225         # configuration files.
226         for conf_file_id in node['conf_file_ids']:
227             if conf_file_id in all_conf_files:
228                 conf_file = all_conf_files[conf_file_id]
229                 conf_files[conf_file['dest']] = conf_file            
230
231         # Get all (enabled) initscripts
232         initscripts = InitScripts(self.api, {'enabled': True})  
233
234         # Get system slices
235         system_slice_tags = SliceTags(self.api, {'tagname': 'system', 'value': '1'}).dict('slice_id')
236         system_slice_ids = system_slice_tags.keys()
237         
238         # Get nm-controller slices
239         # xxx Thierry: should these really be exposed regardless of their mapping to nodes ?
240         controller_and_delegated_slices = Slices(self.api, {'instantiation': ['nm-controller', 'delegated']}, ['slice_id']).dict('slice_id')
241         controller_and_delegated_slice_ids = controller_and_delegated_slices.keys()
242         slice_ids = system_slice_ids + controller_and_delegated_slice_ids + node['slice_ids']
243
244         slivers = get_slivers(self.api, auth, slice_ids, node)
245
246         # get the special accounts and keys needed for the node
247         # root
248         # site_admin
249         accounts = []
250         if False and 'site_id' not in node:
251             nodes = Nodes(self.api, node['node_id'])
252             node = nodes[0]
253
254         # used in conjunction with reduce to flatten lists, like in
255         # reduce ( reduce_flatten_list, [ [1] , [2,3] ], []) => [ 1,2,3 ]
256         def reduce_flatten_list (x,y): return x+y
257
258         # power users are pis and techs
259         def get_site_power_user_keys(api,site_id_or_name):
260             site = Sites (api,site_id_or_name,['person_ids'])[0]
261             key_ids = reduce (reduce_flatten_list, 
262                               [ p['key_ids'] for p in \
263                                     Persons(api,{ 'person_id':site['person_ids'], 
264                                                   'enabled':True, '|role_ids' : [20, 40] }, 
265                                             ['key_ids']) ],
266                               [])
267             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
268
269         # all admins regardless of their site
270         def get_all_admin_keys(api):
271             key_ids = reduce (reduce_flatten_list, 
272                               [ p['key_ids'] for p in \
273                                     Persons(api, {'peer_id':None, 'enabled':True, '|role_ids':[10] }, 
274                                             ['key_ids']) ],
275                               [])
276             return [ key['key'] for key in Keys (api, key_ids) if key['key_type']=='ssh']
277
278         # 'site_admin' account setup
279         personsitekeys=get_site_power_user_keys(self.api,node['site_id'])
280         accounts.append({'name':'site_admin','keys':personsitekeys})
281
282         # 'root' account setup on nodes from all 'admin' users
283         personsitekeys=get_all_admin_keys(self.api)
284         accounts.append({'name':'root','keys':personsitekeys})
285
286         hrn = GetNodeHrn(self.api).call(auth,node['node_id'])
287
288         # XMPP config for omf federation
289         try:
290             if not self.api.config.PLC_OMF_ENABLED:
291                 raise Exception,"OMF disabled"
292             xmpp={'server':self.api.config.PLC_OMF_XMPP_SERVER,
293                   'user':self.api.config.PLC_OMF_XMPP_USER,
294                   'password':self.api.config.PLC_OMF_XMPP_PASSWORD,
295                   }
296         except:
297             xmpp={'server':None,'user':None,'password':None}
298
299         node.update_last_contact()
300
301         return {
302             'timestamp': timestamp,
303             'node_id': node['node_id'],
304             'hostname': node['hostname'],
305             'interfaces': interfaces,
306             'groups': groups,
307             'conf_files': conf_files.values(),
308             'initscripts': initscripts,
309             'slivers': slivers,
310             'accounts': accounts,
311             'xmpp':xmpp,
312             'hrn':hrn,
313             }