A series of synchronization fixes:
[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 sith %s" % (guid,
108                             node.make_filter_description())
109         
110         # Now do the backtracking search for a suitable solution
111         # First with existing slice nodes
112         reqs = []
113         nodes = []
114         for guid, node in self._elements.iteritems():
115             if isinstance(node, self._node.Node) and node._node_id is None:
116                 # Try existing nodes first
117                 # If we have only one candidate, simply use it
118                 candidates = node.find_candidates(
119                     filter_slice_id = self.slice_id)
120                 reqs.append(candidates)
121                 nodes.append(node)
122         
123         if nodes and reqs:
124             try:
125                 solution = resourcealloc.alloc(reqs)
126             except resourcealloc.ResourceAllocationError:
127                 # Failed, try again with all nodes
128                 reqs = []
129                 for node in nodes:
130                     candidates = node.find_candidates()
131                     reqs.append(candidates)
132                 
133                 solution = resourcealloc.alloc(reqs)
134                 to_provision.update(solution)
135             
136             # Do assign nodes
137             for node, node_id in zip(nodes, solution):
138                 node.assign_node_id(node_id)
139
140     def do_provisioning(self):
141         if self._to_provision:
142             # Add new nodes to the slice
143             cur_nodes = self.plapi.GetSlices(self.slicename, ['node_ids'])[0]['node_ids']
144             new_nodes = list(set(cur_nodes) | self._to_provision)
145             self.plapi.UpdateSlice(self.slicename, nodes=new_nodes)
146
147         # cleanup
148         del self._to_provision
149
150     def set(self, guid, name, value, time = TIME_NOW):
151         super(TestbedController, self).set(guid, name, value, time)
152         # TODO: take on account schedule time for the task
153         element = self._elements[guid]
154         if element:
155             setattr(element, name, value)
156
157             if hasattr(element, 'refresh'):
158                 # invoke attribute refresh hook
159                 element.refresh()
160
161     def get(self, guid, name, time = TIME_NOW):
162         value = super(TestbedController, self).get(guid, name, time)
163         # TODO: take on account schedule time for the task
164         factory_id = self._create[guid]
165         factory = self._factories[factory_id]
166         if factory.box_attributes.is_attribute_design_only(name):
167             return value
168         element = self._elements.get(guid)
169         try:
170             return getattr(element, name)
171         except KeyError, AttributeError:
172             return value
173
174     def get_address(self, guid, index, attribute='Address'):
175         index = int(index)
176
177         # try the real stuff
178         iface = self._elements.get(guid)
179         if iface and index == 0:
180             if attribute == 'Address':
181                 return iface.address
182             elif attribute == 'NetPrefix':
183                 return iface.netprefix
184             elif attribute == 'Broadcast':
185                 return iface.broadcast
186
187         # if all else fails, query box
188         return super(TestbedController, self).get_address(guid, index, attribute)
189
190     def action(self, time, guid, action):
191         raise NotImplementedError
192
193     def shutdown(self):
194         for trace in self._traces.itervalues():
195             trace.close()
196         for element in self._elements.itervalues():
197             # invoke cleanup hooks
198             if hasattr(element, 'cleanup'):
199                 element.cleanup()
200         for element in self._elements.itervalues():
201             # invoke destroy hooks
202             if hasattr(element, 'destroy'):
203                 element.destroy()
204         self._elements.clear()
205         self._traces.clear()
206
207     def trace(self, guid, trace_id, attribute='value'):
208         app = self._elements[guid]
209
210         if attribute == 'value':
211             path = app.sync_trace(self.home_directory, trace_id)
212             if path:
213                 fd = open(path, "r")
214                 content = fd.read()
215                 fd.close()
216             else:
217                 content = None
218         elif attribute == 'path':
219             content = app.remote_trace_path(trace_id)
220         else:
221             content = None
222         return content
223
224     def follow_trace(self, trace_id, trace):
225         self._traces[trace_id] = trace
226     
227     def _make_generic(self, parameters, kind):
228         app = kind(self.plapi)
229
230         # Note: there is 1-to-1 correspondence between attribute names
231         #   If that changes, this has to change as well
232         for attr,val in parameters.iteritems():
233             setattr(app, attr, val)
234
235         return app
236
237     def _make_node(self, parameters):
238         node = self._make_generic(parameters, self._node.Node)
239
240         # If emulation is enabled, we automatically need
241         # some vsys interfaces and packages
242         if node.emulation:
243             node.required_vsys.add('ipfw-be')
244             node.required_packages.add('ipfwslice')
245
246         return node
247
248     def _make_node_iface(self, parameters):
249         return self._make_generic(parameters, self._interfaces.NodeIface)
250
251     def _make_tun_iface(self, parameters):
252         return self._make_generic(parameters, self._interfaces.TunIface)
253
254     def _make_tap_iface(self, parameters):
255         return self._make_generic(parameters, self._interfaces.TapIface)
256
257     def _make_netpipe(self, parameters):
258         return self._make_generic(parameters, self._interfaces.NetPipe)
259
260     def _make_internet(self, parameters):
261         return self._make_generic(parameters, self._interfaces.Internet)
262
263     def _make_application(self, parameters):
264         return self._make_generic(parameters, self._app.Application)
265
266     def _make_dependency(self, parameters):
267         return self._make_generic(parameters, self._app.Dependency)
268
269     def _make_nepi_dependency(self, parameters):
270         return self._make_generic(parameters, self._app.NepiDependency)
271
272     def _make_ns3_dependency(self, parameters):
273         return self._make_generic(parameters, self._app.NS3Dependency)
274