Updating the discover method of PlanetLab node
[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 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState, \
22         reschedule_delay
23 from nepi.resources.linux.node import LinuxNode
24 from nepi.resources.planetlab.plcapi import PLCAPIFactory 
25 from nepi.util.timefuncs import tnow, tdiff, tdiffsec, stabsformat
26
27
28 @clsinit_copy
29 class PlanetlabNode(LinuxNode):
30     _rtype = "PlanetlabNode"
31
32     @classmethod
33     def _register_attributes(cls):
34         ip = Attribute("ip", "PlanetLab host public IP address",
35                 flags = Flags.ReadOnly)
36
37         pl_url = Attribute("plcApiUrl", "URL of PlanetLab PLCAPI host (e.g. www.planet-lab.eu or www.planet-lab.org) ",
38                 default = "www.planet-lab.eu",
39                 flags = Flags.Credential)
40
41         pl_ptn = Attribute("plcApiPattern", "PLC API service regexp pattern (e.g. https://%(hostname)s:443/PLCAPI/ ) ",
42                 default = "https://%(hostname)s:443/PLCAPI/",
43                 flags = Flags.ExecReadOnly)
44     
45         pl_user = Attribute("pluser", "PlanetLab account user, as the one to authenticate in the website) ",
46                 flags = Flags.Credential)
47
48         pl_password = Attribute("password", "PlanetLab account password, as the one to authenticate in the website) ",
49                 flags = Flags.Credential)
50
51         city = Attribute("city",
52                 "Constrain location (city) during resource discovery. May use wildcards.",
53                 flags = Flags.Filter)
54
55         country = Attribute("country",
56                 "Constrain location (country) during resource discovery. May use wildcards.",
57                 flags = Flags.Filter)
58
59         region = Attribute("region",
60                 "Constrain location (region) during resource discovery. May use wildcards.",
61                 flags = Flags.Filter)
62
63         architecture = Attribute("architecture",
64                 "Constrain architecture during resource discovery.",
65                 type = Types.Enumerate,
66                 allowed = ["x86_64",
67                             "i386"],
68                 flags = Flags.Filter)
69
70         operating_system = Attribute("operatingSystem",
71                 "Constrain operating system during resource discovery.",
72                 type = Types.Enumerate,
73                 allowed =  ["f8",
74                             "f12",
75                             "f14",
76                             "centos",
77                             "other"],
78                 flags = Flags.Filter)
79
80         site = Attribute("site",
81                 "Constrain the PlanetLab site this node should reside on.",
82                 type = Types.Enumerate,
83                 allowed = ["PLE",
84                             "PLC",
85                             "PLJ"],
86                 flags = Flags.Filter)
87
88         min_reliability = Attribute("minReliability",
89                 "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
90                 type = Types.Double,
91                 range = (1, 100),
92                 flags = Flags.Filter)
93
94         max_reliability = Attribute("maxReliability",
95                 "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
96                 type = Types.Double,
97                 range = (1, 100),
98                 flags = Flags.Filter)
99
100         min_bandwidth = Attribute("minBandwidth",
101                 "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
102                 type = Types.Double,
103                 range = (0, 2**31),
104                 flags = Flags.Filter)
105
106         max_bandwidth = Attribute("maxBandwidth",
107                 "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
108                 type = Types.Double,
109                 range = (0, 2**31),
110                 flags = Flags.Filter)
111
112         min_load = Attribute("minLoad",
113                 "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
114                 type = Types.Double,
115                 range = (0, 2**31),
116                 flags = Flags.Filter)
117
118         max_load = Attribute("maxLoad",
119                 "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
120                 type = Types.Double,
121                 range = (0, 2**31),
122                 flags = Flags.Filter)
123
124         min_cpu = Attribute("minCpu",
125                 "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
126                 type = Types.Double,
127                 range = (0, 100),
128                 flags = Flags.Filter)
129
130         max_cpu = Attribute("maxCpu",
131                 "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
132                 type = Types.Double,
133                 range = (0, 100),
134                 flags = Flags.Filter)
135
136         timeframe = Attribute("timeframe",
137                 "Past time period in which to check information about the node. Values are year,month, week, latest",
138                 default = "week",
139                 type = Types.Enumerate,
140                 allowed = ["latest",
141                             "week",
142                             "month",
143                             "year"],
144                  flags = Flags.Filter)
145
146         cls._register_attribute(ip)
147         cls._register_attribute(pl_url)
148         cls._register_attribute(pl_ptn)
149         cls._register_attribute(pl_user)
150         cls._register_attribute(pl_password)
151         cls._register_attribute(site)
152         cls._register_attribute(city)
153         cls._register_attribute(country)
154         cls._register_attribute(region)
155         cls._register_attribute(architecture)
156         cls._register_attribute(operating_system)
157         cls._register_attribute(min_reliability)
158         cls._register_attribute(max_reliability)
159         cls._register_attribute(min_bandwidth)
160         cls._register_attribute(max_bandwidth)
161         cls._register_attribute(min_load)
162         cls._register_attribute(max_load)
163         cls._register_attribute(min_cpu)
164         cls._register_attribute(max_cpu)
165         cls._register_attribute(timeframe)
166
167     def __init__(self, ec, guid):
168         super(PlanetlabNode, self).__init__(ec, guid)
169
170         self._plapi = None
171     
172     @property
173     def plapi(self):
174         if not self._plapi:
175             pl_user = self.get("pluser")
176             pl_pass = self.get("password")
177             pl_url = self.get("plcApiUrl")
178             pl_ptn = self.get("plcApiPattern")
179
180             self._plapi =  PLCAPIFactory.get_api(pl_user, pl_pass, pl_url,
181                     pl_ptn)
182             
183         return self._plapi
184
185     #def discover(self):
186     #    hostname = self.get("hostname")
187     #    if hostname: 
188     #        node_id = self.check_alive_and_active(hostname=hostname)
189     #    else:
190     #        from random import choice
191     #        nodes = self.filter_based_on_attributes()
192     #        nodes_alive = self.check_alive_and_active(nodes)
193     #        while in_blkl:
194     #            node_id = choice(nodes_alive)
195                 
196
197     #    self._discover_time = tnow()
198     #    self._state = ResourceState.DISCOVERED
199     #    return node_id
200
201     #def provision(self):
202
203     def filter_based_on_attributes(self):
204         # Map attributes with tagnames of PL
205         timeframe = self.get("timeframe")[0]
206         attr_to_tags = {
207             'city' : 'city',
208             'country' : 'country',
209             'region' : 'region',
210             'architecture' : 'arch',
211             'operatingSystem' : 'fcdistro',
212             #'site' : 'pldistro',
213             'minReliability' : 'reliability%s' % timeframe,
214             'maxReliability' : 'reliability%s' % timeframe,
215             'minBandwidth' : 'bw%s' % timeframe,
216             'maxBandwidth' : 'bw%s' % timeframe,
217             'minLoad' : 'load%s' % timeframe,
218             'maxLoad' : 'load%s' % timeframe,
219             'minCpu' : 'cpu%s' % timeframe,
220             'maxCpu' : 'cpu%s' % timeframe,
221         }
222         
223         nodes_id = []
224         filters = {}
225         for attr_name, attr_obj in self._attrs.iteritems():
226             attr_value = self.get(attr_name)
227             print nodes_id
228             if attr_value is not None and attr_obj.flags == 8 and not 'min' in attr_name \
229                 and not 'max' in attr_name and attr_name != 'timeframe':
230                 attr_tag = attr_to_tags[attr_name]
231                 filters['tagname'] = attr_tag
232                 filters['value'] = attr_value
233                 node_tags = self.plapi.get_node_tags(filters)
234                 if node_tags is not None:
235                     if len(nodes_id) == 0:
236                         for node_tag in node_tags:
237                             nodes_id.append(node_tag['node_id'])
238                     else:
239                         nodes_id_tmp = []
240                         for node_tag in node_tags:
241                             if node_tag['node_id'] in nodes_id:
242                                 nodes_id_tmp.append(node_tag['node_id'])
243                         if len(nodes_id_tmp):
244                             nodes_id = set(nodes_id) & set(nodes_id_tmp)
245                         else:
246                             self.fail()
247                 else:
248                     self.fail()
249             elif attr_value is not None and attr_obj.flags == 8 and ('min' or 'max') in attr_name:
250                 attr_tag = attr_to_tags[attr_name]
251                 filters['tagname'] = attr_tag
252                 node_tags = self.plapi.get_node_tags(filters)
253                 if node_tags is not None:
254                     if len(nodes_id) == 0:
255                         for node_tag in node_tags:
256                             if 'min' in attr_name and node_tag['value'] != 'n/a' and \
257                                 float(node_tag['value']) > attr_value:
258                                 nodes_id.append(node_tag['node_id'])
259                             elif 'max' in attr_name and node_tag['value'] != 'n/a' and \
260                                 float(node_tag['value']) < attr_value:
261                                 nodes_id.append(node_tag['node_id'])
262                     else:
263                         nodes_id_tmp = []
264                         for node_tag in node_tags:
265                             if 'min' in attr_name and node_tag['value'] != 'n/a' and \
266                                 float(node_tag['value']) > attr_value and \
267                                 node_tag['node_id'] in nodes_id:
268                                 nodes_id_tmp.append(node_tag['node_id'])
269                             elif 'max' in attr_name and node_tag['value'] != 'n/a' and \
270                                 float(node_tag['value']) < attr_value and \
271                                 node_tag['node_id'] in nodes_id:
272                                 nodes_id_tmp.append(node_tag['node_id'])
273                         if len(nodes_id_tmp):
274                             nodes_id = set(nodes_id) & set(nodes_id_tmp)
275                         else:
276                             self.fail()
277
278         return nodes_id
279                     
280     def check_alive_and_active(self, nodes_id=None, hostname=None):
281         if nodes_id is None and hostname is None:
282             msg = "Specify nodes_id or hostname"
283             raise RuntimeError, msg
284         if nodes_id is not None and hostname is not None:
285             msg = "Specify either nodes_id or hostname"
286             raise RuntimeError, msg
287
288         # check node alive
289         import time
290         filters = dict()
291         filters['run_level'] = 'boot'
292         filters['boot_state'] = 'boot'
293         filters['node_type'] = 'regular' 
294         filters['>last_contact'] =  int(time.time()) - 2*3600
295         if nodes_id:
296             filters['node_id'] = list(nodes_id)
297             alive_nodes_id = self.plapi.get_nodes(filters, fields=['node_id'])
298         elif hostname:
299             filters['hostname'] = hostname
300             alive_nodes_id = self.plapi.get_nodes(filters, fields=['node_id'])
301         if len(alive_nodes_id) == 0:
302             self.fail()
303         else:
304             nodes_id = list()
305             for node_id in alive_nodes_id:
306                 nid = node_id['node_id']
307                 nodes_id.append(nid)
308             return nodes_id
309
310 # ip = self.plapi.get_interfaces({'node_id':nid}, fields=['ip'])
311 # self.set('ip', ip[0]['ip'])
312 #de hostname para que provision haga add_node_slice, check que ip coincide con hostname
313
314
315     def fail(self):
316         msg = "Discovery failed. No candidates found for node"
317         self.error(msg)
318         raise RuntimeError, msg
319            
320                         
321     def valid_connection(self, guid):
322         # TODO: Validate!
323         return True
324
325 #    def blacklist(self):
326 #        # TODO!!!!
327 #        self.warn(" Blacklisting malfunctioning node ")
328 #        #import util
329 #        #util.appendBlacklist(self.hostname)
330