Merge branch 'master' of ssh://git.onelab.eu/git/sfa
[sfa.git] / sfa / planetlab / plaggregate.py
1 #!/usr/bin/python
2 from sfa.util.xrn import Xrn, hrn_to_urn, urn_to_hrn
3 from sfa.util.sfatime import utcparse, datetime_to_string
4 from sfa.util.sfalogging import logger
5
6 from sfa.rspecs.rspec import RSpec
7 from sfa.rspecs.elements.hardware_type import HardwareType
8 from sfa.rspecs.elements.node import Node
9 from sfa.rspecs.elements.link import Link
10 from sfa.rspecs.elements.sliver import Sliver
11 from sfa.rspecs.elements.login import Login
12 from sfa.rspecs.elements.location import Location
13 from sfa.rspecs.elements.interface import Interface
14 from sfa.rspecs.elements.services import Services
15 from sfa.rspecs.elements.pltag import PLTag
16 from sfa.rspecs.elements.lease import Lease
17 from sfa.rspecs.elements.granularity import Granularity
18 from sfa.rspecs.version_manager import VersionManager
19
20 from sfa.planetlab.plxrn import PlXrn, hostname_to_urn, hrn_to_pl_slicename, slicename_to_hrn
21 from sfa.planetlab.vlink import get_tc_rate
22 from sfa.planetlab.topology import Topology
23
24 import time
25
26 class PlAggregate:
27
28     def __init__(self, driver):
29         self.driver = driver
30  
31     def get_sites(self, filter={}):
32         sites = {}
33         for site in self.driver.shell.GetSites(filter):
34             sites[site['site_id']] = site
35         return sites
36
37     def get_interfaces(self, filter={}):
38         interfaces = {}
39         for interface in self.driver.shell.GetInterfaces(filter):
40             iface = Interface()
41             if interface['bwlimit']:
42                 interface['bwlimit'] = str(int(interface['bwlimit'])/1000)
43             interfaces[interface['interface_id']] = interface
44         return interfaces
45
46     def get_links(self, sites, nodes, interfaces):
47         
48         topology = Topology() 
49         links = []
50         for (site_id1, site_id2) in topology:
51             site_id1 = int(site_id1)
52             site_id2 = int(site_id2)
53             link = Link()
54             if not site_id1 in sites or site_id2 not in sites:
55                 continue
56             site1 = sites[site_id1]
57             site2 = sites[site_id2]
58             # get hrns
59             site1_hrn = self.driver.hrn + '.' + site1['login_base']
60             site2_hrn = self.driver.hrn + '.' + site2['login_base']
61
62             for s1_node_id in site1['node_ids']:
63                 for s2_node_id in site2['node_ids']:
64                     if s1_node_id not in nodes or s2_node_id not in nodes:
65                         continue
66                     node1 = nodes[s1_node_id]
67                     node2 = nodes[s2_node_id]
68                     # set interfaces
69                     # just get first interface of the first node
70                     if1_xrn = PlXrn(auth=self.driver.hrn, interface='node%s:eth0' % (node1['node_id']))
71                     if1_ipv4 = interfaces[node1['interface_ids'][0]]['ip']
72                     if2_xrn = PlXrn(auth=self.driver.hrn, interface='node%s:eth0' % (node2['node_id']))
73                     if2_ipv4 = interfaces[node2['interface_ids'][0]]['ip']
74
75                     if1 = Interface({'component_id': if1_xrn.urn, 'ipv4': if1_ipv4} )
76                     if2 = Interface({'component_id': if2_xrn.urn, 'ipv4': if2_ipv4} )
77
78                     # set link
79                     link = Link({'capacity': '1000000', 'latency': '0', 'packet_loss': '0', 'type': 'ipv4'})
80                     link['interface1'] = if1
81                     link['interface2'] = if2
82                     link['component_name'] = "%s:%s" % (site1['login_base'], site2['login_base'])
83                     link['component_id'] = PlXrn(auth=self.driver.hrn, interface=link['component_name']).get_urn()
84                     link['component_manager_id'] =  hrn_to_urn(self.driver.hrn, 'authority+am')
85                     links.append(link)
86
87         return links
88
89     def get_node_tags(self, filter={}):
90         node_tags = {}
91         for node_tag in self.driver.shell.GetNodeTags(filter):
92             node_tags[node_tag['node_tag_id']] = node_tag
93         return node_tags
94
95     def get_pl_initscripts(self, filter={}):
96         pl_initscripts = {}
97         filter.update({'enabled': True})
98         for initscript in self.driver.shell.GetInitScripts(filter):
99             pl_initscripts[initscript['initscript_id']] = initscript
100         return pl_initscripts
101
102
103     def get_slice_and_slivers(self, slice_xrn):
104         """
105         Returns a dict of slivers keyed on the sliver's node_id
106         """
107         slivers = {}
108         slice = None
109         if not slice_xrn:
110             return (slice, slivers)
111         slice_urn = hrn_to_urn(slice_xrn, 'slice')
112         slice_hrn, _ = urn_to_hrn(slice_xrn)
113         slice_name = hrn_to_pl_slicename(slice_hrn)
114         slices = self.driver.shell.GetSlices(slice_name)
115         if not slices:
116             return (slice, slivers)
117         slice = slices[0]
118
119         # sort slivers by node id    
120         for node_id in slice['node_ids']:
121             sliver_xrn = Xrn(slice_urn, type='sliver', id=node_id)
122             sliver_xrn.set_authority(self.driver.hrn)
123             sliver = Sliver({'sliver_id': sliver_xrn.urn,
124                              'name': slice['name'],
125                              'type': 'plab-vserver', 
126                              'tags': []})
127             slivers[node_id]= sliver
128
129         # sort sliver attributes by node id    
130         tags = self.driver.shell.GetSliceTags({'slice_tag_id': slice['slice_tag_ids']})
131         for tag in tags:
132             # most likely a default/global sliver attribute (node_id == None)
133             if tag['node_id'] not in slivers:
134                 sliver_xrn = Xrn(slice_urn, type='sliver', id=tag['node_id'])
135                 sliver_xrn.set_authority(self.driver.hrn)
136                 sliver = Sliver({'sliver_id': sliver_xrn.urn,
137                                  'name': slice['name'],
138                                  'type': 'plab-vserver',
139                                  'tags': []})
140                 slivers[tag['node_id']] = sliver
141             slivers[tag['node_id']]['tags'].append(tag)
142         
143         return (slice, slivers)
144
145     def get_nodes_and_links(self, slice_xrn, slice=None,slivers=[], options={}):
146         # if we are dealing with a slice that has no node just return 
147         # and empty list    
148         if slice_xrn:
149             if not slice or not slice['node_ids']:
150                 return ([],[])
151
152         filter = {}
153         tags_filter = {}
154         if slice and 'node_ids' in slice and slice['node_ids']:
155             filter['node_id'] = slice['node_ids']
156             tags_filter=filter.copy()
157
158         geni_available = options.get('geni_available')    
159         if geni_available == True:
160             filter['boot_state'] = 'boot'     
161         
162         filter.update({'peer_id': None})
163         nodes = self.driver.shell.GetNodes(filter)
164         
165         # get the granularity in second for the reservation system
166         grain = self.driver.shell.GetLeaseGranularity()
167        
168         site_ids = []
169         interface_ids = []
170         tag_ids = []
171         nodes_dict = {}
172         for node in nodes:
173             site_ids.append(node['site_id'])
174             interface_ids.extend(node['interface_ids'])
175             tag_ids.extend(node['node_tag_ids'])
176             nodes_dict[node['node_id']] = node
177  
178         # get sites
179         sites_dict  = self.get_sites({'site_id': site_ids}) 
180         # get interfaces
181         interfaces = self.get_interfaces({'interface_id':interface_ids}) 
182         # get tags
183         node_tags = self.get_node_tags(tags_filter)
184         # get initscripts
185         pl_initscripts = self.get_pl_initscripts()
186         
187         links = self.get_links(sites_dict, nodes_dict, interfaces)
188
189         rspec_nodes = []
190         for node in nodes:
191             # skip whitelisted nodes
192             if node['slice_ids_whitelist']:
193                 if not slice or slice['slice_id'] not in node['slice_ids_whitelist']:
194                     continue
195             rspec_node = Node()
196             # xxx how to retrieve site['login_base']
197             site_id=node['site_id']
198             site=sites_dict[site_id]
199             rspec_node['component_id'] = hostname_to_urn(self.driver.hrn, site['login_base'], node['hostname'])
200             rspec_node['component_name'] = node['hostname']
201             rspec_node['component_manager_id'] = Xrn(self.driver.hrn, 'authority+cm').get_urn()
202             rspec_node['authority_id'] = hrn_to_urn(PlXrn.site_hrn(self.driver.hrn, site['login_base']), 'authority+sa')
203             # do not include boot state (<available> element) in the manifest rspec
204             if not slice:     
205                 rspec_node['boot_state'] = node['boot_state']
206
207             #add the exclusive tag to distinguish between Shared and Reservable nodes
208             if node['node_type'] == 'reservable':
209                 rspec_node['exclusive'] = 'true'
210             else:
211                 rspec_node['exclusive'] = 'false'
212
213             rspec_node['hardware_types'] = [HardwareType({'name': 'plab-pc'}),
214                                             HardwareType({'name': 'pc'})]
215             # only doing this because protogeni rspec needs
216             # to advertise available initscripts 
217             rspec_node['pl_initscripts'] = pl_initscripts.values()
218              # add site/interface info to nodes.
219             # assumes that sites, interfaces and tags have already been prepared.
220             site = sites_dict[node['site_id']]
221             if site['longitude'] and site['latitude']:  
222                 location = Location({'longitude': site['longitude'], 'latitude': site['latitude'], 'country': 'unknown'})
223                 rspec_node['location'] = location
224             # Granularity
225             granularity = Granularity({'grain': grain})
226             rspec_node['granularity'] = granularity
227
228             rspec_node['interfaces'] = []
229             if_count=0
230             for if_id in node['interface_ids']:
231                 interface = Interface(interfaces[if_id]) 
232                 interface['ipv4'] = interface['ip']
233                 interface['component_id'] = PlXrn(auth=self.driver.hrn, 
234                                                   interface='node%s:eth%s' % (node['node_id'], if_count)).get_urn()
235                 # interfaces in the manifest need a client id
236                 if slice:
237                     interface['client_id'] = "%s:%s" % (node['node_id'], if_id)            
238                 rspec_node['interfaces'].append(interface)
239                 if_count+=1
240
241             tags = [PLTag(node_tags[tag_id]) for tag_id in node['node_tag_ids']\
242                     if tag_id in node_tags]
243             rspec_node['tags'] = tags
244             if node['node_id'] in slivers:
245                 # add sliver info
246                 sliver = slivers[node['node_id']]
247                 rspec_node['sliver_id'] = sliver['sliver_id']
248                 rspec_node['slivers'] = [sliver]
249                 for tag in sliver['tags']:
250                     if tag['tagname'] == 'client_id':
251                          rspec_node['client_id'] = tag['value']
252                 
253                 # slivers always provide the ssh service
254                 login = Login({'authentication': 'ssh-keys', 'hostname': node['hostname'], 'port':'22', 'username': sliver['name']})
255                 service = Services({'login': login})
256                 rspec_node['services'] = [service]
257             rspec_nodes.append(rspec_node)
258         return (rspec_nodes, links)
259              
260
261     def get_leases(self, slice_xrn=None, slice=None, options={}):
262         
263         if slice_xrn and not slice:
264             return []
265
266         now = int(time.time())
267         filter={}
268         filter.update({'clip':now})
269         if slice:
270            filter.update({'name':slice['name']})
271         return_fields = ['lease_id', 'hostname', 'site_id', 'name', 't_from', 't_until']
272         leases = self.driver.shell.GetLeases(filter)
273         grain = self.driver.shell.GetLeaseGranularity()
274
275         site_ids = []
276         for lease in leases:
277             site_ids.append(lease['site_id'])
278
279         # get sites
280         sites_dict  = self.get_sites({'site_id': site_ids}) 
281   
282         rspec_leases = []
283         for lease in leases:
284
285             rspec_lease = Lease()
286             
287             # xxx how to retrieve site['login_base']
288             site_id=lease['site_id']
289             site=sites_dict[site_id]
290
291             #rspec_lease['lease_id'] = lease['lease_id']
292             rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn, site['login_base'], lease['hostname'])
293             if slice_xrn:
294                 slice_urn = slice_xrn
295                 slice_hrn = urn_to_hrn(slice_urn)
296             else:
297                 slice_hrn = slicename_to_hrn(self.driver.hrn, lease['name'])
298                 slice_urn = hrn_to_urn(slice_hrn, 'slice')
299             rspec_lease['slice_id'] = slice_urn
300             rspec_lease['start_time'] = lease['t_from']
301             rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) / grain
302             rspec_leases.append(rspec_lease)
303         return rspec_leases
304
305     
306     def get_rspec(self, slice_xrn=None, version = None, options={}):
307
308         version_manager = VersionManager()
309         version = version_manager.get_version(version)
310         if not slice_xrn:
311             rspec_version = version_manager._get_version(version.type, version.version, 'ad')
312         else:
313             rspec_version = version_manager._get_version(version.type, version.version, 'manifest')
314
315         slice, slivers = self.get_slice_and_slivers(slice_xrn)
316         rspec = RSpec(version=rspec_version, user_options=options)
317         if slice and 'expires' in slice:
318             rspec.xml.set('expires',  datetime_to_string(utcparse(slice['expires'])))
319
320         if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'leases':
321             if slice_xrn and not slivers:
322                 nodes, links = [], []
323             else:
324                 nodes, links = self.get_nodes_and_links(slice_xrn, slice, slivers, options)
325             rspec.version.add_nodes(nodes)
326             rspec.version.add_links(links)
327             # add sliver defaults
328             default_sliver = slivers.get(None, [])
329             if default_sliver:
330                 default_sliver_attribs = default_sliver.get('tags', [])
331                 for attrib in default_sliver_attribs:
332                     logger.info(attrib)
333                     rspec.version.add_default_sliver_attribute(attrib['tagname'], attrib['value'])
334         
335         if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'resources':
336            leases = self.get_leases(slice_xrn, slice)
337            rspec.version.add_leases(leases)
338
339         return rspec.toxml()
340
341