Adding PlanetLab resources
[nepi.git] / src / nepi / resources / linux / interface.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, Types, Flags
21 from nepi.execution.resource import ResourceManager, clsinit, ResourceState
22 from nepi.resources.linux.node import LinuxNode
23 from nepi.resources.linux.channel import LinuxChannel
24
25 import collections
26 import logging
27 import os
28 import random
29 import re
30 import tempfile
31 import time
32
33 # TODO: UP, MTU attributes!
34
35 reschedule_delay = "0.5s"
36
37 @clsinit
38 class LinuxInterface(ResourceManager):
39     _rtype = "LinuxInterface"
40
41     @classmethod
42     def _register_attributes(cls):
43         ip4 = Attribute("ip4", "IPv4 Address",
44                 flags = Flags.ExecReadOnly)
45
46         ip6 = Attribute("ip6", "IPv6 Address",
47                 flags = Flags.ExecReadOnly)
48
49         mac = Attribute("mac", "MAC Address",
50                 flags = Flags.ExecReadOnly)
51
52         mask4 = Attribute("mask4", "IPv4 network mask",
53                 flags = Flags.ExecReadOnly)
54
55         mask6 = Attribute("mask6", "IPv6 network mask",
56                 type = Types.Integer,
57                 flags = Flags.ExecReadOnly)
58
59         mtu = Attribute("mtu", "Maximum transmition unit for device",
60             type = Types.Integer)
61
62         devname = Attribute("deviceName", 
63                 "Name of the network interface (e.g. eth0, wlan0, etc)",
64                 flags = Flags.ExecReadOnly)
65
66         up = Attribute("up", "Link up", type = Types.Bool)
67
68         tear_down = Attribute("tearDown", "Bash script to be executed before " + \
69                 "releasing the resource",
70                 flags = Flags.ExecReadOnly)
71
72         cls._register_attribute(ip4)
73         cls._register_attribute(ip6)
74         cls._register_attribute(mac)
75         cls._register_attribute(mask4)
76         cls._register_attribute(mask6)
77         cls._register_attribute(mtu)
78         cls._register_attribute(devname)
79         cls._register_attribute(up)
80         cls._register_attribute(tear_down)
81
82     def __init__(self, ec, guid):
83         super(LinuxInterface, self).__init__(ec, guid)
84         self._configured = False
85
86         self._logger = logging.getLogger("LinuxInterface")
87         
88         self.add_set_hooks()
89
90     def log_message(self, msg):
91         return " guid %d - host %s - %s " % (self.guid, 
92                 self.node.get("hostname"), msg)
93
94     @property
95     def node(self):
96         node = self.get_connected(LinuxNode.rtype())
97         if node: return node[0]
98         return None
99
100     @property
101     def channel(self):
102         chan = self.get_connected(LinuxChannel.rtype())
103         if chan: return chan[0]
104         return None
105
106     def discover(self):
107         devname = self.get("deviceName")
108         ip4 = self.get("ip4")
109         ip6 = self.get("ip4")
110         mac = self.get("mac")
111         mask4 = self.get("mask4")
112         mask6 = self.get("mask6")
113         mtu = self.get("mtu")
114
115         # Get current interfaces information
116         (out, err), proc = self.node.execute("ifconfig", sudo = True)
117
118         if err and proc.poll():
119             msg = " Error retrieving interface information "
120             self.error(msg, out, err)
121             raise RuntimeError, "%s - %s - %s" % (msg, out, err)
122         
123         # Check if an interface is found matching the RM attributes
124         ifaces = out.split("\n\n")
125
126         for i in ifaces:
127             m = re.findall("(\w+)\s+Link\s+encap:\w+(\s+HWaddr\s+(([0-9a-fA-F]{2}:?){6}))?(\s+inet\s+addr:((\d+\.?){4}).+Mask:(\d+\.\d+\.\d+\.\d+))?(.+inet6\s+addr:\s+([0-9a-fA-F:.]+)/(\d+))?(.+(UP))?(.+MTU:(\d+))?", i, re.DOTALL)
128             
129             m = m[0]
130             dn = m[0]
131             mc = m[2]
132             i4 = m[5]
133             msk4 = m[7]
134             i6 = m[9]
135             msk6 = m[10]
136             up = True if m[12] else False
137             mu = m[14]
138
139             self.debug("Found interface %(devname)s with MAC %(mac)s,"
140                     "IPv4 %(ipv4)s %(mask4)s IPv6 %(ipv6)s/%(mask6)s %(up)s %(mtu)s" % ({
141                 'devname': dn,
142                 'mac': mc,
143                 'ipv4': i4,
144                 'mask4': msk4,
145                 'ipv6': i6,
146                 'mask6': msk6,
147                 'up': up,
148                 'mtu': mu
149                 }) )
150
151             # If the user didn't provide information we take the first 
152             # interface that is UP
153             if not devname and not ip4 and not ip6 and up:
154                 self._configured = True
155                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
156                 break
157
158             # If the user provided ipv4 or ipv6 matching that of an interface
159             # load the interface info
160             if (ip4 and ip4 == i4) or (ip6 and ip6 == i6):
161                 self._configured = True
162                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
163                 break
164
165             # If the user provided the device name we load the associated info
166             if devname and devname == dn:
167                 if ((ip4 and ip4 == i4) and (ipv6 and ip6 == i6)) or \
168                         not (ip4 or ip6):
169                     self._configured = True
170                
171                 # If the user gave a different ip than the existing, asume ip 
172                 # needs to be changed
173                 i4 = ip4 or i4
174                 i6 = ip6 or i6
175                 mu = mtu or mu 
176
177                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
178                 break
179        
180         if not self.get("deviceName"):
181             msg = "Unable to resolve interface "
182             self.error(msg)
183             raise RuntimeError, msg
184
185         super(LinuxInterface, self).discover()
186
187     def provision(self):
188         devname = self.get("deviceName")
189         ip4 = self.get("ip4")
190         ip6 = self.get("ip4")
191         mac = self.get("mac")
192         mask4 = self.get("mask4")
193         mask6 = self.get("mask6")
194         mtu = self.get("mtu")
195
196         # Must configure interface if configuration is required
197         if not self._configured:
198             cmd = "ifconfig %s" % devname
199
200             if ip4 and mask4:
201                 cmd += " %(ip4)s netmask %(mask4)s broadcast %(bcast)s up" % ({
202                     'ip4': ip4,
203                     'mask4': mask4,
204                     'bcast': bcast})
205             if mtu:
206                 cmd += " mtu %d " % mtu
207
208             (out, err), proc = self.node.execute(cmd, sudo = True)
209
210             if err and proc.poll():
211                 msg = "Error configuring interface with command '%s'" % cmd
212                 self.error(msg, out, err)
213                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
214
215             if ip6 and mask6:
216                 cmd = "ifconfig %(devname)s inet6 add %(ip6)s/%(mask6)d" % ({
217                         'devname': devname,
218                         'ip6': ip6,
219                         'mask6': mask6})
220
221             (out, err), proc = self.node.execute(cmd, sudo = True)
222
223             if err and proc.poll():
224                 msg = "Error seting ipv6 for interface using command '%s' " % cmd
225                 self.error(msg, out, err)
226                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
227
228         super(LinuxInterface, self).provision()
229
230     def deploy(self):
231         # Wait until node is provisioned
232         node = self.node
233         chan = self.channel
234
235         if not node or node.state < ResourceState.PROVISIONED:
236             self.ec.schedule(reschedule_delay, self.deploy)
237         elif not chan or chan.state < ResourceState.READY:
238             self.ec.schedule(reschedule_delay, self.deploy)
239         else:
240             # Verify if the interface exists in node. If not, configue
241             # if yes, load existing configuration
242             try:
243                 self.discover()
244                 self.provision()
245             except:
246                 self._state = ResourceState.FAILED
247                 raise
248
249             super(LinuxInterface, self).deploy()
250
251     def release(self):
252         tear_down = self.get("tearDown")
253         if tear_down:
254             self.execute(tear_down)
255
256         super(LinuxInterface, self).release()
257
258     def valid_connection(self, guid):
259         # TODO: Validate!
260         return True
261
262     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
263         self.set("deviceName", devname)
264         self.set("mac", mac)
265         self.set("ip4", ip4)
266         self.set("mask4", mask4)
267         self.set("ip6", ip6)
268         self.set("mask6", mask6)
269
270         # set the following without validating or triggering hooks
271         attr = self._attrs["up"]
272         attr._value = up
273         attr = self._attrs["mtu"]
274
275     def add_set_hooks(self):
276         attrup = self._attrs["up"]
277         attrup.set_hook = self.set_hook_up
278
279         attrmtu = self._attrs["mtu"]
280         attrmtu.set_hook = self.set_hook_mtu
281
282     def set_hook_up(self, oldval, newval):
283         if oldval == newval:
284             return oldval
285
286         # configure interface up
287         if newval == True:
288             cmd = "ifup %s" % self.get("deviceName")
289         elif newval == False:
290             cmd = "ifdown %s" % self.get("deviceName")
291
292         (out, err), proc = self.node.execute(cmd, sudo = True)
293
294         if err and proc.poll():
295             msg = "Error setting interface up/down using command '%s' " % cmd
296             self.error(msg, err, out)
297             return oldval
298         
299         return newval
300
301     def set_hook_mtu(self, oldval, newval):
302         if oldval == newval:
303             return oldval
304
305         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
306
307         (out, err), proc = self.node.execute(cmd, sudo = True)
308
309         if err and proc.poll():
310             msg = "Error setting interface MTU using command '%s' " % cmd
311             self.error(msg, err, out)
312             return  oldval
313         
314         return newval
315