Ticket #22: complitid
[nepi.git] / src / nepi / testbeds / planetlab / execute.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from constants import TESTBED_ID
5 from nepi.core import testbed_impl
6 from nepi.util.constants import TIME_NOW
7 import os
8 import time
9 import resourcealloc
10
11 class TestbedController(testbed_impl.TestbedController):
12     def __init__(self, testbed_version):
13         super(TestbedController, self).__init__(TESTBED_ID, testbed_version)
14         self._home_directory = None
15         self.slicename = None
16         self._traces = dict()
17
18         import node, interfaces, application
19         self._node = node
20         self._interfaces = interfaces
21         self._app = application
22
23     @property
24     def home_directory(self):
25         return self._home_directory
26
27     @property
28     def plapi(self):
29         if not hasattr(self, '_plapi'):
30             import plcapi
31
32             if self.authUser:
33                 self._plapi = plcapi.PLCAPI(
34                     username = self.authUser,
35                     password = self.authString,
36                     hostname = self.plcHost,
37                     urlpattern = self.plcUrl
38                     )
39             else:
40                 # anonymous access - may not be enough for much
41                 self._plapi = plcapi.PLCAPI()
42         return self._plapi
43
44     @property
45     def slice_id(self):
46         if not hasattr(self, '_slice_id'):
47             slices = self.plapi.GetSlices(self.slicename, fields=('slice_id',))
48             if slices:
49                 self._slice_id = slices[0]['slice_id']
50             else:
51                 # If it wasn't found, don't remember this failure, keep trying
52                 return None
53         return self._slice_id
54
55     def do_setup(self):
56         self._home_directory = self._attributes.\
57             get_attribute_value("homeDirectory")
58         self.slicename = self._attributes.\
59             get_attribute_value("slice")
60         self.authUser = self._attributes.\
61             get_attribute_value("authUser")
62         self.authString = self._attributes.\
63             get_attribute_value("authPass")
64         self.sliceSSHKey = self._attributes.\
65             get_attribute_value("sliceSSHKey")
66         self.plcHost = self._attributes.\
67             get_attribute_value("plcHost")
68         self.plcUrl = self._attributes.\
69             get_attribute_value("plcUrl")
70         super(TestbedController, self).do_setup()
71
72     def do_preconfigure(self):
73         # Perform resource discovery if we don't have
74         # specific resources assigned yet
75         self.do_resource_discovery()
76
77         # Create PlanetLab slivers
78         self.do_provisioning()
79
80         # Configure elements per XML data
81         super(TestbedController, self).do_preconfigure()
82
83     def do_resource_discovery(self):
84         to_provision = self._to_provision = set()
85         
86         # Initial algo:
87         #   look for perfectly defined nodes
88         #   (ie: those with only one candidate)
89         for guid, node in self._elements.iteritems():
90             if isinstance(node, self._node.Node) and node._node_id is None:
91                 # Try existing nodes first
92                 # If we have only one candidate, simply use it
93                 candidates = node.find_candidates(
94                     filter_slice_id = self.slice_id)
95                 if len(candidates) == 1:
96                     node.assign_node_id(iter(candidates).next())
97                 else:
98                     # Try again including unassigned nodes
99                     candidates = node.find_candidates()
100                     if len(candidates) > 1:
101                         continue
102                     if len(candidates) == 1:
103                         node_id = iter(candidates).next()
104                         node.assign_node_id(node_id)
105                         to_provision.add(node_id)
106                     elif not candidates:
107                         raise RuntimeError, "Cannot assign resources for node %s, no candidates" % (guid,)
108         
109         # Now do the backtracking search for a suitable solution
110         # First with existing slice nodes
111         reqs = []
112         nodes = []
113         for guid, node in self._elements.iteritems():
114             if isinstance(node, self._node.Node) and node._node_id is None:
115                 # Try existing nodes first
116                 # If we have only one candidate, simply use it
117                 candidates = node.find_candidates(
118                     filter_slice_id = self.slice_id)
119                 reqs.append(candidates)
120                 nodes.append(node)
121         
122         if nodes and reqs:
123             try:
124                 solution = resourcealloc.alloc(reqs)
125             except resourcealloc.ResourceAllocationError:
126                 # Failed, try again with all nodes
127                 reqs = []
128                 for node in nodes:
129                     candidates = node.find_candidates()
130                     reqs.append(candidates)
131                 
132                 solution = resourcealloc.alloc(reqs)
133                 to_provision.update(solution)
134             
135             # Do assign nodes
136             for node, node_id in zip(nodes, solution):
137                 node.assign_node_id(node_id)
138
139     def do_provisioning(self):
140         if self._to_provision:
141             # Add new nodes to the slice
142             cur_nodes = self.plapi.GetSlices(self.slicename, ['node_ids'])[0]['node_ids']
143             new_nodes = list(set(cur_nodes) | self._to_provision)
144             self.plapi.UpdateSlice(self.slicename, nodes=new_nodes)
145
146         # cleanup
147         del self._to_provision
148
149     def set(self, guid, name, value, time = TIME_NOW):
150         super(TestbedController, self).set(guid, name, value, time)
151         # TODO: take on account schedule time for the task
152         element = self._elements[guid]
153         if element:
154             setattr(element, name, value)
155
156             if hasattr(element, 'refresh'):
157                 # invoke attribute refresh hook
158                 element.refresh()
159
160     def get(self, guid, name, time = TIME_NOW):
161         value = super(TestbedController, self).get(guid, name, time)
162         # TODO: take on account schedule time for the task
163         factory_id = self._create[guid]
164         factory = self._factories[factory_id]
165         if factory.box_attributes.is_attribute_design_only(name):
166             return value
167         element = self._elements.get(guid)
168         try:
169             return getattr(element, name)
170         except KeyError, AttributeError:
171             return value
172
173     def get_address(self, guid, index, attribute='Address'):
174         index = int(index)
175
176         # try the real stuff
177         iface = self._elements.get(guid)
178         if iface and index == 0:
179             if attribute == 'Address':
180                 return iface.address
181             elif attribute == 'NetPrefix':
182                 return iface.netprefix
183             elif attribute == 'Broadcast':
184                 return iface.broadcast
185
186         # if all else fails, query box
187         return super(TestbedController, self).get_address(guid, index, attribute)
188
189     def action(self, time, guid, action):
190         raise NotImplementedError
191
192     def shutdown(self):
193         for trace in self._traces.values():
194             trace.close()
195         for element in self._elements.values():
196             # invoke cleanup hooks
197             if hasattr(element, 'cleanup'):
198                 element.cleanup()
199
200     def trace(self, guid, trace_id, attribute='value'):
201         app = self._elements[guid]
202
203         if attribute == 'value':
204             path = app.sync_trace(self.home_directory, trace_id)
205             if path:
206                 fd = open(path, "r")
207                 content = fd.read()
208                 fd.close()
209             else:
210                 content = None
211         elif attribute == 'path':
212             content = app.remote_trace_path(trace_id)
213         else:
214             content = None
215         return content
216
217     def follow_trace(self, trace_id, trace):
218         self._traces[trace_id] = trace
219     
220     def _make_generic(self, parameters, kind):
221         app = kind(self.plapi)
222
223         # Note: there is 1-to-1 correspondence between attribute names
224         #   If that changes, this has to change as well
225         for attr,val in parameters.iteritems():
226             setattr(app, attr, val)
227
228         return app
229
230     def _make_node(self, parameters):
231         node = self._make_generic(parameters, self._node.Node)
232
233         # If emulation is enabled, we automatically need
234         # some vsys interfaces and packages
235         if node.emulation:
236             node.required_vsys.add('ipfw-be')
237             node.required_packages.add('ipfwslice')
238
239         return node
240
241     def _make_node_iface(self, parameters):
242         return self._make_generic(parameters, self._interfaces.NodeIface)
243
244     def _make_tun_iface(self, parameters):
245         return self._make_generic(parameters, self._interfaces.TunIface)
246
247     def _make_tap_iface(self, parameters):
248         return self._make_generic(parameters, self._interfaces.TapIface)
249
250     def _make_netpipe(self, parameters):
251         return self._make_generic(parameters, self._interfaces.NetPipe)
252
253     def _make_internet(self, parameters):
254         return self._make_generic(parameters, self._interfaces.Internet)
255
256     def _make_application(self, parameters):
257         return self._make_generic(parameters, self._app.Application)
258
259     def _make_dependency(self, parameters):
260         return self._make_generic(parameters, self._app.Dependency)
261
262     def _make_nepi_dependency(self, parameters):
263         return self._make_generic(parameters, self._app.NepiDependency)
264
265     def _make_ns3_dependency(self, parameters):
266         return self._make_generic(parameters, self._app.NS3Dependency)
267