Adding PlanetLab resources
[nepi.git] / src / nepi / resources / planetlab / node.py
1 """
2     NEPI, a framework to manage network experiments
3     Copyright (C) 2013 INRIA
4
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 """
19
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState
22 from nepi.resources.linux.node import LinuxNode
23
24 from nepi.resources.planetlab.plcapi import PLCAPIFactory 
25
26 import logging
27
28 reschedule_delay = "0.5s"
29
30 @clsinit_copy
31 class PlanetlabNode(LinuxNode):
32     _rtype = "PlanetLabNode"
33
34     @classmethod
35     def _register_attributes(cls):
36         cls._remove_attribute("username")
37
38         ip = Attribute("ip", "PlanetLab host public IP address",
39                 flags = Flags.ReadOnly)
40
41         slicename = Attribute("slice", "PlanetLab slice name",
42                 flags = Flags.Credential)
43
44         pl_url = Attribute("plcApiUrl", "URL of PlanetLab PLCAPI host (e.g. www.planet-lab.eu or www.planet-lab.org) ",
45                 default = "www.planet-lab.eu",
46                 flags = Flags.Credential)
47
48         pl_ptn = Attribute("plcApiPattern", "PLC API service regexp pattern (e.g. https://%(hostname)s:443/PLCAPI/ ) ",
49                 default = "https://%(hostname)s:443/PLCAPI/",
50                 flags = Flags.ExecReadOnly)
51
52         city = Attribute("city",
53                 "Constrain location (city) during resource discovery. May use wildcards.",
54                 flags = Flags.Filter)
55
56         country = Attribute("country",
57                 "Constrain location (country) during resource discovery. May use wildcards.",
58                 flags = Flags.Filter)
59
60         region = Attribute("region",
61                 "Constrain location (region) during resource discovery. May use wildcards.",
62                 flags = Flags.Filter)
63
64         architecture = Attribute("architecture",
65                 "Constrain architecture during resource discovery.",
66                 type = Types.Enumerate,
67                 allowed = ["x86_64",
68                             "i386"],
69                 flags = Flags.Filter)
70
71         operating_system = Attribute("operatingSystem",
72                 "Constrain operating system during resource discovery.",
73                 type = Types.Enumerate,
74                 allowed =  ["f8",
75                             "f12",
76                             "f14",
77                             "centos",
78                             "other"],
79                 flags = Flags.Filter)
80
81         site = Attribute("site",
82                 "Constrain the PlanetLab site this node should reside on.",
83                 type = Types.Enumerate,
84                 allowed = ["PLE",
85                             "PLC",
86                             "PLJ"],
87                 flags = Flags.Filter)
88
89         min_reliability = Attribute("minReliability",
90                 "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
91                 type = Types.Double,
92                 range = (1, 100),
93                 flags = Flags.Filter)
94
95         max_reliability = Attribute("maxReliability",
96                 "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
97                 type = Types.Double,
98                 range = (1, 100),
99                 flags = Flags.Filter)
100
101         min_bandwidth = Attribute("minBandwidth",
102                 "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
103                 type = Types.Double,
104                 range = (0, 2**31),
105                 flags = Flags.Filter)
106
107         max_bandwidth = Attribute("maxBandwidth",
108                 "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
109                 type = Types.Double,
110                 range = (0, 2**31),
111                 flags = Flags.Filter)
112
113         min_load = Attribute("minLoad",
114                 "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
115                 type = Types.Double,
116                 range = (0, 2**31),
117                 flags = Flags.Filter)
118
119         max_load = Attribute("maxLoad",
120                 "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
121                 type = Types.Double,
122                 range = (0, 2**31),
123                 flags = Flags.Filter)
124
125         min_cpu = Attribute("minCpu",
126                 "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
127                 type = Types.Double,
128                 range = (0, 100),
129                 flags = Flags.Filter)
130
131         max_cpu = Attribute("maxCpu",
132                 "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
133                 type = Types.Double,
134                 range = (0, 100),
135                 flags = Flags.Filter)
136
137         timeframe = Attribute("timeframe",
138                 "Past time period in which to check information about the node. Values are year,month, week, latest",
139                 default = "week",
140                 type = Types.Enumerate,
141                 allowed = ["latest",
142                             "week",
143                             "month",
144                             "year"],
145                  flags = Flags.Filter)
146
147         cls._register_attribute(ip)
148         cls._register_attribute(slicename)
149         cls._register_attribute(pl_url)
150         cls._register_attribute(pl_ptn)
151         cls._register_attribute(city)
152         cls._register_attribute(country)
153         cls._register_attribute(region)
154         cls._register_attribute(architecture)
155         cls._register_attribute(operating_system)
156         cls._register_attribute(min_reliability)
157         cls._register_attribute(max_reliability)
158         cls._register_attribute(min_bandwidth)
159         cls._register_attribute(max_bandwidth)
160         cls._register_attribute(min_load)
161         cls._register_attribute(max_load)
162         cls._register_attribute(min_cpu)
163         cls._register_attribute(max_cpu)
164         cls._register_attribute(timeframe)
165
166     def __init__(self, ec, guid):
167         super(PLanetLabNode, self).__init__(ec, guid)
168
169         self._plapi = None
170
171         self._logger = logging.getLogger("PlanetLabNode")
172     
173     @property
174     def plapi(self):
175         if not self._plapi:
176             slicename = self.get("slice")
177             pl_pass = self.get("password")
178             pl_url = self.get("plcApiUrl")
179             pl_ptn = self.get("plcApiPattern")
180
181             self._plapi =  PLCAPIFactory.get_api(slicename, pl_pass, pl_url,
182                     pl_ptn)
183             
184         return self._plapi
185
186     @property
187     def os(self):
188         if self._os:
189             return self._os
190
191         if (not self.get("hostname") or not self.get("username")):
192             msg = "Can't resolve OS, insufficient data "
193             self.error(msg)
194             raise RuntimeError, msg
195
196         (out, err), proc = self.execute("cat /etc/issue", with_lock = True)
197
198         if err and proc.poll():
199             msg = "Error detecting OS "
200             self.error(msg, out, err)
201             raise RuntimeError, "%s - %s - %s" %( msg, out, err )
202
203         if out.find("Fedora release 12") == 0:
204             self._os = "f12"
205         elif out.find("Fedora release 14") == 0:
206             self._os = "f14"
207         else:
208             msg = "Unsupported OS"
209             self.error(msg, out)
210             raise RuntimeError, "%s - %s " %( msg, out )
211
212         return self._os
213
214     @property
215     def localhost(self):
216         return False
217
218     def discover(self):
219         # Get the list of nodes that match the filters
220
221
222         # find one that 
223         if not self.is_alive():
224             self._state = ResourceState.FAILED
225             msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
226             self.error(msg)
227             raise RuntimeError, msg
228
229         if self.get("cleanProcesses"):
230             self.clean_processes()
231
232         if self.get("cleanHome"):
233             self.clean_home()
234        
235         self.mkdir(self.node_home)
236
237         super(PlanetlabNode, self).discover()
238
239     def provision(self):
240         if not self.is_alive():
241             self._state = ResourceState.FAILED
242             msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
243             self.error(msg)
244             raise RuntimeError, msg
245
246         if self.get("cleanProcesses"):
247             self.clean_processes()
248
249         if self.get("cleanHome"):
250             self.clean_home()
251        
252         self.mkdir(self.node_home)
253
254         super(PlanetlabNode, self).provision()
255
256     def deploy(self):
257         if self.state == ResourceState.NEW:
258             try:
259                self.discover()
260                if self.state == ResourceState.DISCOVERED:
261                    self.provision()
262             except:
263                 self._state = ResourceState.FAILED
264                 raise
265
266         if self.state != ResourceState.PROVISIONED:
267            self.ec.schedule(reschedule_delay, self.deploy)
268
269         super(PlanetlabNode, self).deploy()
270
271     def valid_connection(self, guid):
272         # TODO: Validate!
273         return True
274
275     def clean_processes(self, killer = False):
276         self.info("Cleaning up processes")
277     
278         # Hardcore kill
279         cmd = ("sudo -S killall python tcpdump || /bin/true ; " +
280             "sudo -S killall python tcpdump || /bin/true ; " +
281             "sudo -S kill $(ps -N -T -o pid --no-heading | grep -v $PPID | sort) || /bin/true ; " +
282             "sudo -S killall -u root || /bin/true ; " +
283             "sudo -S killall -u root || /bin/true ; ")
284
285         out = err = ""
286         (out, err), proc = self.execute(cmd, retry = 1, with_lock = True) 
287             
288     def is_alive(self):
289         if self.localhost:
290             return True
291
292         out = err = ""
293         try:
294             # TODO: FIX NOT ALIVE!!!!
295             (out, err), proc = self.execute("echo 'ALIVE' || (echo 'NOTALIVE') >&2", retry = 5, 
296                     with_lock = True)
297         except:
298             import traceback
299             trace = traceback.format_exc()
300             msg = "Unresponsive host  %s " % err
301             self.error(msg, out, trace)
302             return False
303
304         if out.strip().startswith('ALIVE'):
305             return True
306         else:
307             msg = "Unresponsive host "
308             self.error(msg, out, err)
309             return False
310
311     def blacklist(self):
312         # TODO!!!!
313         self.warn(" Blacklisting malfunctioning node ")
314         #import util
315         #util.appendBlacklist(self.hostname)
316