Changing reschedule_delay internals
[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
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: 
34 #     - check UP, MTU attributes!
35 #     - clean up code and test!
36
37 @clsinit_copy
38 class LinuxInterface(ResourceManager):
39     _rtype = "LinuxInterface"
40     _help = "Controls network devices on Linux hosts through the ifconfig tool"
41     _backend = "linux"
42
43     @classmethod
44     def _register_attributes(cls):
45         ip4 = Attribute("ip4", "IPv4 Address",
46               flags = Flags.Design)
47
48         ip6 = Attribute("ip6", "IPv6 Address",
49                 flags = Flags.Design)
50
51         mac = Attribute("mac", "MAC Address",
52                 flags = Flags.Design)
53
54         mask4 = Attribute("mask4", "IPv4 network mask",
55                 flags = Flags.Design)
56
57         mask6 = Attribute("mask6", "IPv6 network mask",
58                 type = Types.Integer,
59                 flags = Flags.Design)
60
61         mtu = Attribute("mtu", "Maximum transmition unit for device",
62                 type = Types.Integer)
63
64         devname = Attribute("deviceName", 
65                 "Name of the network interface (e.g. eth0, wlan0, etc)",
66                 flags = Flags.Design)
67
68         up = Attribute("up", "Link up", type = Types.Bool)
69
70         tear_down = Attribute("tearDown", "Bash script to be executed before " + \
71                 "releasing the resource",
72                 flags = Flags.Design)
73
74         cls._register_attribute(ip4)
75         cls._register_attribute(ip6)
76         cls._register_attribute(mac)
77         cls._register_attribute(mask4)
78         cls._register_attribute(mask6)
79         cls._register_attribute(mtu)
80         cls._register_attribute(devname)
81         cls._register_attribute(up)
82         cls._register_attribute(tear_down)
83
84     def __init__(self, ec, guid):
85         super(LinuxInterface, self).__init__(ec, guid)
86         self._configured = False
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.get_rtype())
97         if node: return node[0]
98         return None
99
100     @property
101     def channel(self):
102         chan = self.get_connected(LinuxChannel.get_rtype())
103         if chan: return chan[0]
104         return None
105
106     def do_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).do_discover()
186
187     def do_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).do_provision()
229
230     def do_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(self.reschedule_delay, self.deploy)
237         elif not chan or chan.state < ResourceState.READY:
238             self.ec.schedule(self.reschedule_delay, self.deploy)
239         else:
240             # Verify if the interface exists in node. If not, configue
241             # if yes, load existing configuration
242             self.do_discover()
243             self.do_provision()
244
245             super(LinuxInterface, self).do_deploy()
246
247     def do_release(self):
248         tear_down = self.get("tearDown")
249         if tear_down:   
250             self.execute(tear_down)
251
252         super(LinuxInterface, self).do_release()
253
254     def valid_connection(self, guid):
255         # TODO: Validate!
256         return True
257
258     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
259         self.set("deviceName", devname)
260         self.set("mac", mac)
261         self.set("ip4", ip4)
262         self.set("mask4", mask4)
263         self.set("ip6", ip6)
264         self.set("mask6", mask6)
265
266         # set the following without validating or triggering hooks
267         attr = self._attrs["up"]
268         attr._value = up
269         attr = self._attrs["mtu"]
270         attr._value = mtu 
271
272     def add_set_hooks(self):
273         attrup = self._attrs["up"]
274         attrup.set_hook = self.set_hook_up
275
276         attrmtu = self._attrs["mtu"]
277         attrmtu.set_hook = self.set_hook_mtu
278
279     def set_hook_up(self, oldval, newval):
280         if self.state == ResourceState.NEW or oldval == newval:
281             return oldval
282
283         # configure interface up
284         if newval == True:
285             cmd = "ifup %s" % self.get("deviceName")
286         elif newval == False:
287             cmd = "ifdown %s" % self.get("deviceName")
288
289         (out, err), proc = self.node.execute(cmd, sudo = True)
290
291         if err and proc.poll():
292             msg = "Error setting interface up/down using command '%s' " % cmd
293             self.error(msg, err, out)
294             return oldval
295         
296         return newval
297
298     def set_hook_mtu(self, oldval, newval):
299         if self.state == ResourceState.NEW or oldval == newval:
300             return oldval
301
302         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
303
304         (out, err), proc = self.node.execute(cmd, sudo = True)
305
306         if err and proc.poll():
307             msg = "Error setting interface MTU using command '%s' " % cmd
308             self.error(msg, err, out)
309             return  oldval
310         
311         return newval
312