merged ex_shutdown into nepi-3-dev
[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 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.execution.attribute import Attribute, Types, Flags
21 from nepi.execution.resource import ResourceManager, clsinit_copy, \
22         ResourceState, reschedule_delay, failtrap
23 from nepi.resources.linux.node import LinuxNode
24 from nepi.resources.linux.channel import LinuxChannel
25
26 import collections
27 import os
28 import random
29 import re
30 import tempfile
31 import time
32
33 # TODO: UP, MTU attributes!
34
35
36 @clsinit_copy
37 class LinuxInterface(ResourceManager):
38     _rtype = "LinuxInterface"
39     _help = "Controls network devices on Linux hosts through the ifconfig tool"
40     _backend = "linux"
41
42     @classmethod
43     def _register_attributes(cls):
44         ip4 = Attribute("ip4", "IPv4 Address",
45               flags = Flags.ExecReadOnly)
46
47         ip6 = Attribute("ip6", "IPv6 Address",
48                 flags = Flags.ExecReadOnly)
49
50         mac = Attribute("mac", "MAC Address",
51                 flags = Flags.ExecReadOnly)
52
53         mask4 = Attribute("mask4", "IPv4 network mask",
54                 flags = Flags.ExecReadOnly)
55
56         mask6 = Attribute("mask6", "IPv6 network mask",
57                 type = Types.Integer,
58                 flags = Flags.ExecReadOnly)
59
60         mtu = Attribute("mtu", "Maximum transmition unit for device",
61                 type = Types.Integer)
62
63         devname = Attribute("deviceName", 
64                 "Name of the network interface (e.g. eth0, wlan0, etc)",
65                 flags = Flags.ExecReadOnly)
66
67         up = Attribute("up", "Link up", type = Types.Bool)
68
69         tear_down = Attribute("tearDown", "Bash script to be executed before " + \
70                 "releasing the resource",
71                 flags = Flags.ExecReadOnly)
72
73         cls._register_attribute(ip4)
74         cls._register_attribute(ip6)
75         cls._register_attribute(mac)
76         cls._register_attribute(mask4)
77         cls._register_attribute(mask6)
78         cls._register_attribute(mtu)
79         cls._register_attribute(devname)
80         cls._register_attribute(up)
81         cls._register_attribute(tear_down)
82
83     def __init__(self, ec, guid):
84         super(LinuxInterface, self).__init__(ec, guid)
85         self._configured = False
86         
87         self.add_set_hooks()
88
89     def log_message(self, msg):
90         return " guid %d - host %s - %s " % (self.guid, 
91                 self.node.get("hostname"), msg)
92
93     @property
94     def node(self):
95         node = self.get_connected(LinuxNode.rtype())
96         if node: return node[0]
97         return None
98
99     @property
100     def channel(self):
101         chan = self.get_connected(LinuxChannel.rtype())
102         if chan: return chan[0]
103         return None
104
105     @failtrap
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, tty = 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     @failtrap
188     def provision(self):
189         devname = self.get("deviceName")
190         ip4 = self.get("ip4")
191         ip6 = self.get("ip4")
192         mac = self.get("mac")
193         mask4 = self.get("mask4")
194         mask6 = self.get("mask6")
195         mtu = self.get("mtu")
196
197         # Must configure interface if configuration is required
198         if not self._configured:
199             cmd = "ifconfig %s" % devname
200
201             if ip4 and mask4:
202                 cmd += " %(ip4)s netmask %(mask4)s broadcast %(bcast)s up" % ({
203                     'ip4': ip4,
204                     'mask4': mask4,
205                     'bcast': bcast})
206             if mtu:
207                 cmd += " mtu %d " % mtu
208
209             (out, err), proc = self.node.execute(cmd, sudo = True)
210
211             if err and proc.poll():
212                 msg = "Error configuring interface with command '%s'" % cmd
213                 self.error(msg, out, err)
214                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
215
216             if ip6 and mask6:
217                 cmd = "ifconfig %(devname)s inet6 add %(ip6)s/%(mask6)d" % ({
218                         'devname': devname,
219                         'ip6': ip6,
220                         'mask6': mask6})
221
222             (out, err), proc = self.node.execute(cmd, sudo = True)
223
224             if err and proc.poll():
225                 msg = "Error seting ipv6 for interface using command '%s' " % cmd
226                 self.error(msg, out, err)
227                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
228
229         super(LinuxInterface, self).provision()
230
231     @failtrap
232     def deploy(self):
233         # Wait until node is provisioned
234         node = self.node
235         chan = self.channel
236
237         if not node or node.state < ResourceState.PROVISIONED:
238             self.ec.schedule(reschedule_delay, self.deploy)
239         elif not chan or chan.state < ResourceState.READY:
240             self.ec.schedule(reschedule_delay, self.deploy)
241         else:
242             # Verify if the interface exists in node. If not, configue
243             # if yes, load existing configuration
244             self.discover()
245             self.provision()
246
247             super(LinuxInterface, self).deploy()
248
249     def release(self):
250         try:
251             tear_down = self.get("tearDown")
252             if tear_down:   
253                 self.execute(tear_down)
254         except:
255             import traceback
256             err = traceback.format_exc()
257             self.error(err)
258
259         super(LinuxInterface, self).release()
260
261     def valid_connection(self, guid):
262         # TODO: Validate!
263         return True
264
265     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
266         self.set("deviceName", devname)
267         self.set("mac", mac)
268         self.set("ip4", ip4)
269         self.set("mask4", mask4)
270         self.set("ip6", ip6)
271         self.set("mask6", mask6)
272
273         # set the following without validating or triggering hooks
274         attr = self._attrs["up"]
275         attr._value = up
276         attr = self._attrs["mtu"]
277
278     def add_set_hooks(self):
279         attrup = self._attrs["up"]
280         attrup.set_hook = self.set_hook_up
281
282         attrmtu = self._attrs["mtu"]
283         attrmtu.set_hook = self.set_hook_mtu
284
285     def set_hook_up(self, oldval, newval):
286         if oldval == newval:
287             return oldval
288
289         # configure interface up
290         if newval == True:
291             cmd = "ifup %s" % self.get("deviceName")
292         elif newval == False:
293             cmd = "ifdown %s" % self.get("deviceName")
294
295         (out, err), proc = self.node.execute(cmd, sudo = True)
296
297         if err and proc.poll():
298             msg = "Error setting interface up/down using command '%s' " % cmd
299             self.error(msg, err, out)
300             return oldval
301         
302         return newval
303
304     def set_hook_mtu(self, oldval, newval):
305         if oldval == newval:
306             return oldval
307
308         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
309
310         (out, err), proc = self.node.execute(cmd, sudo = True)
311
312         if err and proc.poll():
313             msg = "Error setting interface MTU using command '%s' " % cmd
314             self.error(msg, err, out)
315             return  oldval
316         
317         return newval
318