better support for sliver urns
[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             
122             id = ":".join(map(str, [slice['slice_id'], node_id]))
123             xrn = Xrn(slice_urn, id=id).get_urn()
124             xrn.set_authority(self.driver.hrn)
125             sliver_urn = xrn.get_urn()
126             sliver = Sliver({'sliver_id': sliver_urn,
127                              'name': slice['name'],
128                              'type': 'plab-vserver', 
129                              'tags': []})
130             slivers[node_id]= sliver
131
132         # sort sliver attributes by node id    
133         tags = self.driver.shell.GetSliceTags({'slice_tag_id': slice['slice_tag_ids']})
134         for tag in tags:
135             # most likely a default/global sliver attribute (node_id == None)
136             if tag['node_id'] not in slivers:
137                 sliver_urn = Xrn(slice_urn, id = slice['slice_id']).get_urn()
138                 sliver = Sliver({'sliver_id': sliver_urn,
139                                  'name': slice['name'],
140                                  'type': 'plab-vserver',
141                                  'tags': []})
142                 slivers[tag['node_id']] = sliver
143             slivers[tag['node_id']]['tags'].append(tag)
144         
145         return (slice, slivers)
146
147     def get_nodes_and_links(self, slice_xrn, slice=None,slivers=[], options={}):
148         # if we are dealing with a slice that has no node just return 
149         # and empty list    
150         if slice_xrn:
151             if not slice or not slice['node_ids']:
152                 return ([],[])
153
154         filter = {}
155         tags_filter = {}
156         if slice and 'node_ids' in slice and slice['node_ids']:
157             filter['node_id'] = slice['node_ids']
158             tags_filter=filter.copy()
159
160         geni_available = options.get('geni_available')    
161         if geni_available == True:
162             filter['boot_state'] = 'boot'     
163         
164         filter.update({'peer_id': None})
165         nodes = self.driver.shell.GetNodes(filter)
166         
167         # get the granularity in second for the reservation system
168         grain = self.driver.shell.GetLeaseGranularity()
169        
170         site_ids = []
171         interface_ids = []
172         tag_ids = []
173         nodes_dict = {}
174         for node in nodes:
175             site_ids.append(node['site_id'])
176             interface_ids.extend(node['interface_ids'])
177             tag_ids.extend(node['node_tag_ids'])
178             nodes_dict[node['node_id']] = node
179  
180         # get sites
181         sites_dict  = self.get_sites({'site_id': site_ids}) 
182         # get interfaces
183         interfaces = self.get_interfaces({'interface_id':interface_ids}) 
184         # get tags
185         node_tags = self.get_node_tags(tags_filter)
186         # get initscripts
187         pl_initscripts = self.get_pl_initscripts()
188         
189         links = self.get_links(sites_dict, nodes_dict, interfaces)
190
191         rspec_nodes = []
192         for node in nodes:
193             # skip whitelisted nodes
194             if node['slice_ids_whitelist']:
195                 if not slice or slice['slice_id'] not in node['slice_ids_whitelist']:
196                     continue
197             rspec_node = Node()
198             # xxx how to retrieve site['login_base']
199             site_id=node['site_id']
200             site=sites_dict[site_id]
201             rspec_node['component_id'] = hostname_to_urn(self.driver.hrn, site['login_base'], node['hostname'])
202             rspec_node['component_name'] = node['hostname']
203             rspec_node['component_manager_id'] = Xrn(self.driver.hrn, 'authority+cm').get_urn()
204             rspec_node['authority_id'] = hrn_to_urn(PlXrn.site_hrn(self.driver.hrn, site['login_base']), 'authority+sa')
205             # do not include boot state (<available> element) in the manifest rspec
206             if not slice:     
207                 rspec_node['boot_state'] = node['boot_state']
208             rspec_node['exclusive'] = 'false'
209             rspec_node['hardware_types'] = [HardwareType({'name': 'plab-pc'}),
210                                             HardwareType({'name': 'pc'})]
211             # only doing this because protogeni rspec needs
212             # to advertise available initscripts 
213             rspec_node['pl_initscripts'] = pl_initscripts.values()
214              # add site/interface info to nodes.
215             # assumes that sites, interfaces and tags have already been prepared.
216             site = sites_dict[node['site_id']]
217             if site['longitude'] and site['latitude']:  
218                 location = Location({'longitude': site['longitude'], 'latitude': site['latitude'], 'country': 'unknown'})
219                 rspec_node['location'] = location
220             # Granularity
221             granularity = Granularity({'grain': grain})
222             rspec_node['granularity'] = granularity
223
224             rspec_node['interfaces'] = []
225             if_count=0
226             for if_id in node['interface_ids']:
227                 interface = Interface(interfaces[if_id]) 
228                 interface['ipv4'] = interface['ip']
229                 interface['component_id'] = PlXrn(auth=self.driver.hrn, 
230                                                   interface='node%s:eth%s' % (node['node_id'], if_count)).get_urn()
231                 # interfaces in the manifest need a client id
232                 if slice:
233                     interface['client_id'] = "%s:%s" % (node['node_id'], if_id)            
234                 rspec_node['interfaces'].append(interface)
235                 if_count+=1
236
237             tags = [PLTag(node_tags[tag_id]) for tag_id in node['node_tag_ids']]
238             rspec_node['tags'] = tags
239             if node['node_id'] in slivers:
240                 # add sliver info
241                 sliver = slivers[node['node_id']]
242                 rspec_node['sliver_id'] = sliver['sliver_id']
243                 rspec_node['client_id'] = node['hostname']
244                 rspec_node['slivers'] = [sliver]
245                 
246                 # slivers always provide the ssh service
247                 login = Login({'authentication': 'ssh-keys', 'hostname': node['hostname'], 'port':'22', 'username': sliver['name']})
248                 service = Services({'login': login})
249                 rspec_node['services'] = [service]
250             rspec_nodes.append(rspec_node)
251         return (rspec_nodes, links)
252              
253
254     def get_leases(self, slice=None, options={}):
255         
256         now = int(time.time())
257         filter={}
258         filter.update({'clip':now})
259         if slice:
260            filter.update({'name':slice['name']})
261         return_fields = ['lease_id', 'hostname', 'site_id', 'name', 't_from', 't_until']
262         leases = self.driver.shell.GetLeases(filter)
263         grain = self.driver.shell.GetLeaseGranularity()
264
265         site_ids = []
266         for lease in leases:
267             site_ids.append(lease['site_id'])
268
269         # get sites
270         sites_dict  = self.get_sites({'site_id': site_ids}) 
271   
272         rspec_leases = []
273         for lease in leases:
274
275             rspec_lease = Lease()
276             
277             # xxx how to retrieve site['login_base']
278             site_id=lease['site_id']
279             site=sites_dict[site_id]
280
281             rspec_lease['lease_id'] = lease['lease_id']
282             rspec_lease['component_id'] = hostname_to_urn(self.driver.hrn, site['login_base'], lease['hostname'])
283             slice_hrn = slicename_to_hrn(self.driver.hrn, lease['name'])
284             slice_urn = hrn_to_urn(slice_hrn, 'slice')
285             rspec_lease['slice_id'] = slice_urn
286             rspec_lease['start_time'] = lease['t_from']
287             rspec_lease['duration'] = (lease['t_until'] - lease['t_from']) / grain
288             rspec_leases.append(rspec_lease)
289         return rspec_leases
290
291     
292     def list_resources(self, version = None, options={}):
293
294         version_manager = VersionManager()
295         version = version_manager.get_version(version)
296         rspec_version = version_manager._get_version(version.type, version.version, 'ad')
297         rspec = RSpec(version=rspec_version, user_options=options)
298         
299         if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'leases':
300            nodes, links = self.get_nodes_and_links(options=options)
301            rspec.version.add_nodes(nodes)
302            rspec.version.add_links(links)
303         return rspec.toxml()
304
305     def describe(self, urns, version=None, options={}):
306         version_manager = VersionManager()
307         version = version_manager.get_version(version)
308         rspec_version = version_manager._get_version(version.type, version.version, 'manifest')
309         rspec = RSpec(version=version, user_options=options)
310         slice, slivers = self.get_slice_and_slivers(urns)
311         if slice and 'expires' in slice:
312             rspec.xml.set('expires',  datetime_to_string(utcparse(slice['expires'])))
313        
314         if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'leases':
315            nodes, links = self.get_nodes_and_links(urns, slice, slivers, options)
316            rspec.version.add_nodes(nodes)
317            rspec.version.add_links(links)
318            # add sliver defaults
319            default_sliver = slivers.get(None, [])
320            if default_sliver:
321               default_sliver_attribs = default_sliver.get('tags', [])
322               for attrib in default_sliver_attribs:
323                   rspec.version.add_default_sliver_attribute(attrib['tagname'], attrib['value'])
324         
325
326         if not options.get('list_leases') or options.get('list_leases') and options['list_leases'] != 'resources':
327            leases = self.get_leases(slice)
328            rspec.version.add_leases(leases)
329         
330         result = {'geni_urn': '',
331                   'geni_rspec': rspec.toxml(),
332                   'geni_slivers': []}
333
334         return result