Adding help and background class atrributes to ResourceManager
[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, ResourceState, \
22         reschedule_delay
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
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     def 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).discover()
185
186     def 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).provision()
228
229     def 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(reschedule_delay, self.deploy)
236         elif not chan or chan.state < ResourceState.READY:
237             self.ec.schedule(reschedule_delay, self.deploy)
238         else:
239             # Verify if the interface exists in node. If not, configue
240             # if yes, load existing configuration
241             try:
242                 self.discover()
243                 self.provision()
244             except:
245                 self.fail()
246                 raise
247
248             super(LinuxInterface, self).deploy()
249
250     def release(self):
251         tear_down = self.get("tearDown")
252         if tear_down:
253             self.execute(tear_down)
254
255         super(LinuxInterface, self).release()
256
257     def valid_connection(self, guid):
258         # TODO: Validate!
259         return True
260
261     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
262         self.set("deviceName", devname)
263         self.set("mac", mac)
264         self.set("ip4", ip4)
265         self.set("mask4", mask4)
266         self.set("ip6", ip6)
267         self.set("mask6", mask6)
268
269         # set the following without validating or triggering hooks
270         attr = self._attrs["up"]
271         attr._value = up
272         attr = self._attrs["mtu"]
273
274     def add_set_hooks(self):
275         attrup = self._attrs["up"]
276         attrup.set_hook = self.set_hook_up
277
278         attrmtu = self._attrs["mtu"]
279         attrmtu.set_hook = self.set_hook_mtu
280
281     def set_hook_up(self, oldval, newval):
282         if oldval == newval:
283             return oldval
284
285         # configure interface up
286         if newval == True:
287             cmd = "ifup %s" % self.get("deviceName")
288         elif newval == False:
289             cmd = "ifdown %s" % self.get("deviceName")
290
291         (out, err), proc = self.node.execute(cmd, sudo = True)
292
293         if err and proc.poll():
294             msg = "Error setting interface up/down using command '%s' " % cmd
295             self.error(msg, err, out)
296             return oldval
297         
298         return newval
299
300     def set_hook_mtu(self, oldval, newval):
301         if oldval == newval:
302             return oldval
303
304         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
305
306         (out, err), proc = self.node.execute(cmd, sudo = True)
307
308         if err and proc.poll():
309             msg = "Error setting interface MTU using command '%s' " % cmd
310             self.error(msg, err, out)
311             return  oldval
312         
313         return newval
314