applied the except and raise fixers to the master branch to close the gap with py3
[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 version 2 as
7 #    published by the Free Software Foundation;
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
18
19 from nepi.execution.attribute import Attribute, Types, Flags
20 from nepi.execution.resource import ResourceManager, clsinit_copy, \
21         ResourceState
22 from nepi.resources.linux.node import LinuxNode
23 from nepi.resources.linux.channel import LinuxChannel
24
25 import collections
26 import os
27 import random
28 import re
29 import tempfile
30 import time
31
32 # TODO: 
33 #     - check UP, MTU attributes!
34 #     - clean up code and test!
35
36 @clsinit_copy
37 class LinuxInterface(ResourceManager):
38     _rtype = "linux::Interface"
39     _help = "Controls network devices on Linux hosts through the ifconfig tool"
40     _platform = "linux"
41
42     @classmethod
43     def _register_attributes(cls):
44         ip4 = Attribute("ip4", "IPv4 Address",
45               flags = Flags.Design)
46
47         ip6 = Attribute("ip6", "IPv6 Address",
48                 flags = Flags.Design)
49
50         mac = Attribute("mac", "MAC Address",
51                 flags = Flags.Design)
52
53         mask4 = Attribute("mask4", "IPv4 network mask",
54                 flags = Flags.Design)
55
56         mask6 = Attribute("mask6", "IPv6 network mask",
57                 type = Types.Integer,
58                 flags = Flags.Design)
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.Design)
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.Design)
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.get_rtype())
96         if node: return node[0]
97         return None
98
99     @property
100     def channel(self):
101         chan = self.get_connected(LinuxChannel.get_rtype())
102         if chan: return chan[0]
103         return None
104
105     def do_discover(self):
106         devname = self.get("deviceName")
107         ip4 = self.get("ip4")
108         ip6 = self.get("ip4")
109         mac = self.get("mac")
110         mask4 = self.get("mask4")
111         mask6 = self.get("mask6")
112         mtu = self.get("mtu")
113
114         # Get current interfaces information
115         (out, err), proc = self.node.execute("ifconfig", sudo = True, tty = True)
116
117         if err and proc.poll():
118             msg = " Error retrieving interface information "
119             self.error(msg, out, err)
120             raise RuntimeError("%s - %s - %s" % (msg, out, err))
121         
122         # Check if an interface is found matching the RM attributes
123         ifaces = out.split("\n\n")
124
125         for i in ifaces:
126             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)
127             
128             m = m[0]
129             dn = m[0]
130             mc = m[2]
131             i4 = m[5]
132             msk4 = m[7]
133             i6 = m[9]
134             msk6 = m[10]
135             up = True if m[12] else False
136             mu = m[14]
137
138             self.debug("Found interface %(devname)s with MAC %(mac)s,"
139                     "IPv4 %(ipv4)s %(mask4)s IPv6 %(ipv6)s/%(mask6)s %(up)s %(mtu)s" % ({
140                 'devname': dn,
141                 'mac': mc,
142                 'ipv4': i4,
143                 'mask4': msk4,
144                 'ipv6': i6,
145                 'mask6': msk6,
146                 'up': up,
147                 'mtu': mu
148                 }) )
149
150             # If the user didn't provide information we take the first 
151             # interface that is UP
152             if not devname and not ip4 and not ip6 and up:
153                 self._configured = True
154                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
155                 break
156
157             # If the user provided ipv4 or ipv6 matching that of an interface
158             # load the interface info
159             if (ip4 and ip4 == i4) or (ip6 and ip6 == i6):
160                 self._configured = True
161                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
162                 break
163
164             # If the user provided the device name we load the associated info
165             if devname and devname == dn:
166                 if ((ip4 and ip4 == i4) and (ipv6 and ip6 == i6)) or \
167                         not (ip4 or ip6):
168                     self._configured = True
169                
170                 # If the user gave a different ip than the existing, asume ip 
171                 # needs to be changed
172                 i4 = ip4 or i4
173                 i6 = ip6 or i6
174                 mu = mtu or mu 
175
176                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
177                 break
178        
179         if not self.get("deviceName"):
180             msg = "Unable to resolve interface "
181             self.error(msg)
182             raise RuntimeError(msg)
183
184         super(LinuxInterface, self).do_discover()
185
186     def do_provision(self):
187         devname = self.get("deviceName")
188         ip4 = self.get("ip4")
189         ip6 = self.get("ip4")
190         mac = self.get("mac")
191         mask4 = self.get("mask4")
192         mask6 = self.get("mask6")
193         mtu = self.get("mtu")
194
195         # Must configure interface if configuration is required
196         if not self._configured:
197             cmd = "ifconfig %s" % devname
198
199             if ip4 and mask4:
200                 cmd += " %(ip4)s netmask %(mask4)s broadcast %(bcast)s up" % ({
201                     'ip4': ip4,
202                     'mask4': mask4,
203                     'bcast': bcast})
204             if mtu:
205                 cmd += " mtu %d " % mtu
206
207             (out, err), proc = self.node.execute(cmd, sudo = True)
208
209             if err and proc.poll():
210                 msg = "Error configuring interface with command '%s'" % cmd
211                 self.error(msg, out, err)
212                 raise RuntimeError("%s - %s - %s" % (msg, out, err))
213
214             if ip6 and mask6:
215                 cmd = "ifconfig %(devname)s inet6 add %(ip6)s/%(mask6)d" % ({
216                         'devname': devname,
217                         'ip6': ip6,
218                         'mask6': mask6})
219
220             (out, err), proc = self.node.execute(cmd, sudo = True)
221
222             if err and proc.poll():
223                 msg = "Error seting ipv6 for interface using command '%s' " % cmd
224                 self.error(msg, out, err)
225                 raise RuntimeError("%s - %s - %s" % (msg, out, err))
226
227         super(LinuxInterface, self).do_provision()
228
229     def do_deploy(self):
230         # Wait until node is provisioned
231         node = self.node
232         chan = self.channel
233
234         if not node or node.state < ResourceState.PROVISIONED:
235             self.ec.schedule(self.reschedule_delay, self.deploy)
236         elif not chan or chan.state < ResourceState.READY:
237             self.ec.schedule(self.reschedule_delay, self.deploy)
238         else:
239             # Verify if the interface exists in node. If not, configue
240             # if yes, load existing configuration
241             self.do_discover()
242             self.do_provision()
243
244             super(LinuxInterface, self).do_deploy()
245
246     def do_release(self):
247         tear_down = self.get("tearDown")
248         if tear_down:   
249             self.execute(tear_down)
250
251         super(LinuxInterface, self).do_release()
252
253     def valid_connection(self, guid):
254         # TODO: Validate!
255         return True
256
257     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
258         self.set("deviceName", devname)
259         self.set("mac", mac)
260         self.set("ip4", ip4)
261         self.set("mask4", mask4)
262         self.set("ip6", ip6)
263         self.set("mask6", mask6)
264
265         # set the following without validating or triggering hooks
266         attr = self._attrs["up"]
267         attr._value = up
268         attr = self._attrs["mtu"]
269         attr._value = mtu 
270
271     def add_set_hooks(self):
272         attrup = self._attrs["up"]
273         attrup.set_hook = self.set_hook_up
274
275         attrmtu = self._attrs["mtu"]
276         attrmtu.set_hook = self.set_hook_mtu
277
278     def set_hook_up(self, oldval, newval):
279         if self.state == ResourceState.NEW or oldval == newval:
280             return oldval
281
282         # configure interface up
283         if newval == True:
284             cmd = "ifup %s" % self.get("deviceName")
285         elif newval == False:
286             cmd = "ifdown %s" % self.get("deviceName")
287
288         (out, err), proc = self.node.execute(cmd, sudo = True)
289
290         if err and proc.poll():
291             msg = "Error setting interface up/down using command '%s' " % cmd
292             self.error(msg, err, out)
293             return oldval
294         
295         return newval
296
297     def set_hook_mtu(self, oldval, newval):
298         if self.state == ResourceState.NEW or oldval == newval:
299             return oldval
300
301         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
302
303         (out, err), proc = self.node.execute(cmd, sudo = True)
304
305         if err and proc.poll():
306             msg = "Error setting interface MTU using command '%s' " % cmd
307             self.error(msg, err, out)
308             return  oldval
309         
310         return newval
311