* Automatic provisioning
[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 import os
7 import time
8
9 class TestbedController(testbed_impl.TestbedController):
10     def __init__(self, testbed_version):
11         super(TestbedController, self).__init__(TESTBED_ID, testbed_version)
12         self._home_directory = None
13         self.slicename = None
14         self._traces = dict()
15         
16         import node, interfaces, application
17         self._node = node
18         self._interfaces = interfaces
19         self._app = application
20
21     @property
22     def home_directory(self):
23         return self._home_directory
24     
25     @property
26     def plapi(self):
27         if not hasattr(self, '_plapi'):
28             import plcapi
29             
30             if self.authUser:
31                 self._plapi = plcapi.PLCAPI(
32                     username = self.authUser,
33                     password = self.authString)
34             else:
35                 # anonymous access - may not be enough for much
36                 self._plapi = plcapi.PLCAPI()
37         return self._plapi
38     
39     @property
40     def slice_id(self):
41         if not hasattr(self, '_slice_id'):
42             slices = self.plapi.GetSlices(self.slicename, fields=('slice_id',))
43             if slices:
44                 self._slice_id = slices[0]['slice_id']
45             else:
46                 # If it wasn't found, don't remember this failure, keep trying
47                 return None
48         return self._slice_id
49
50     def do_setup(self):
51         self._home_directory = self._attributes.\
52             get_attribute_value("homeDirectory")
53         self.slicename = self._attributes.\
54             get_attribute_value("slice")
55         self.authUser = self._attributes.\
56             get_attribute_value("authUser")
57         self.authString = self._attributes.\
58             get_attribute_value("authPass")
59         self.sliceSSHKey = self._attributes.\
60             get_attribute_value("sliceSSHKey")
61
62     def do_preconfigure(self):
63         # Perform resource discovery if we don't have
64         # specific resources assigned yet
65         self.do_resource_discovery()
66         
67         # Create PlanetLab slivers
68         self.do_provisioning()
69         
70         # Configure elements per XML data
71         super(TestbedController, self).do_preconfigure()
72     
73     def do_resource_discovery(self):
74         # Do what?
75         
76         # Provisional algo:
77         #   look for perfectly defined nodes
78         #   (ie: those with only one candidate)
79         to_provision = self._to_provision = set()
80         for guid, node in self._elements.iteritems():
81             if isinstance(node, self._node.Node) and node._node_id is None:
82                 # Try existing nodes first
83                 # If we have only one candidate, simply use it
84                 candidates = node.find_candidates(
85                     filter_slice_id = self.slice_id)
86                 if len(candidates) == 1:
87                     node.assign_node_id(iter(candidates).next())
88                 else:
89                     # Try again including unassigned nodes
90                     candidates = node.find_candidates()
91                     if len(candidates) > 1:
92                         raise RuntimeError, "Cannot assign resources for node %s, too many candidates" % (guid,)
93                     if len(candidates) == 1:
94                         node_id = iter(candidates).next()
95                         node.assign_node_id(node_id)
96                         to_provision.add(node_id)
97                     elif not candidates:
98                         raise RuntimeError, "Cannot assign resources for node %s, no candidates" % (guid,)
99
100     def do_provisioning(self):
101         if self._to_provision:
102             # Add new nodes to the slice
103             cur_nodes = self.plapi.GetSlices(self.slicename, ['node_ids'])[0]['node_ids']
104             new_nodes = list(set(cur_nodes) | self._to_provision)
105             self.plapi.UpdateSlice(self.slicename, nodes=new_nodes)
106     
107         # cleanup
108         del self._to_provision
109     
110
111     def set(self, time, guid, name, value):
112         super(TestbedController, self).set(time, guid, name, value)
113         # TODO: take on account schedule time for the task 
114         element = self._elements[guid]
115         if element:
116             setattr(element, name, value)
117
118     def get(self, time, guid, name):
119         # TODO: take on account schedule time for the task
120         element = self._elements.get(guid)
121         if element:
122             try:
123                 if hasattr(element, name):
124                     # Runtime attribute
125                     return getattr(element, name)
126                 else:
127                     # Try design-time attributes
128                     return self.box_get(time, guid, name)
129             except KeyError, AttributeError:
130                 return None
131
132     def get_route(self, guid, index, attribute):
133         # TODO: fetch real data from planetlab
134         try:
135             return self.box_get_route(guid, int(index), attribute)
136         except KeyError, AttributeError:
137             return None
138
139     def get_address(self, guid, index, attribute='Address'):
140         index = int(index)
141         
142         # try the real stuff
143         iface = self._elements.get(guid)
144         if iface and index == 0:
145             if attribute == 'Address':
146                 return iface.address
147             elif attribute == 'NetPrefix':
148                 return iface.netprefix
149             elif attribute == 'Broadcast':
150                 return iface.broadcast
151         
152         # if all else fails, query box
153         try:
154             return self.box_get_address(guid, index, attribute)
155         except KeyError, AttributeError:
156             return None
157
158
159     def action(self, time, guid, action):
160         raise NotImplementedError
161
162     def shutdown(self):
163         for trace in self._traces.values():
164             trace.close()
165         for element in self._elements.values():
166             pass
167             #element.destroy()
168
169     def trace(self, guid, trace_id, attribute='value'):
170         app = self._elements[guid]
171         
172         if attribute == 'value':
173             path = app.sync_trace(self.home_directory, trace_id)
174             if path:
175                 fd = open(path, "r")
176                 content = fd.read()
177                 fd.close()
178             else:
179                 content = None
180         elif attribute == 'path':
181             content = app.remote_trace_path(trace_id)
182         else:
183             content = None
184         return content
185         
186     def follow_trace(self, trace_id, trace):
187         self._traces[trace_id] = trace
188
189     def _make_node(self, parameters):
190         node = self._node.Node(self.plapi)
191         
192         # Note: there is 1-to-1 correspondence between attribute names
193         #   If that changes, this has to change as well
194         for attr,val in parameters.iteritems():
195             setattr(node, attr, val)
196         
197         return node
198     
199     def _make_node_iface(self, parameters):
200         iface = self._interfaces.NodeIface(self.plapi)
201         
202         # Note: there is 1-to-1 correspondence between attribute names
203         #   If that changes, this has to change as well
204         for attr,val in parameters.iteritems():
205             setattr(iface, attr, val)
206         
207         return iface
208     
209     def _make_tun_iface(self, parameters):
210         iface = self._interfaces.TunIface(self.plapi)
211         
212         # Note: there is 1-to-1 correspondence between attribute names
213         #   If that changes, this has to change as well
214         for attr,val in parameters.iteritems():
215             setattr(iface, attr, val)
216         
217         return iface
218     
219     def _make_internet(self, parameters):
220         return self._interfaces.Internet(self.plapi)
221     
222     def _make_application(self, parameters):
223         app = self._app.Application(self.plapi)
224         
225         # Note: there is 1-to-1 correspondence between attribute names
226         #   If that changes, this has to change as well
227         for attr,val in parameters.iteritems():
228             setattr(app, attr, val)
229         
230         return app
231         
232
233