]> git.proxmox.com Git - mirror_ifupdown2.git/blob - addons/addressvirtual.py
Virtual interfaces for VRR bounced during ifreload -a
[mirror_ifupdown2.git] / addons / addressvirtual.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6
7 from ifupdown.iface import *
8 from ifupdownaddons.modulebase import moduleBase
9 from ifupdownaddons.iproute2 import iproute2
10 import ifupdown.statemanager as statemanager
11 import ifupdown.rtnetlink_api as rtnetlink_api
12 from ipaddr import IPNetwork
13 import logging
14 import os
15 import glob
16
17 class addressvirtual(moduleBase):
18 """ ifupdown2 addon module to configure virtual addresses """
19
20 _modinfo = {'mhelp' : 'address module configures virtual addresses for ' +
21 'interfaces. It creates a macvlan interface for ' +
22 'every mac ip address-virtual line',
23 'attrs' : {
24 'address-virtual' :
25 { 'help' : 'bridge router virtual mac and ip',
26 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.254/24 11.0.1.254/24']}
27 }}
28
29
30 def __init__(self, *args, **kargs):
31 moduleBase.__init__(self, *args, **kargs)
32 self.ipcmd = None
33 self._bridge_fdb_query_cache = {}
34
35 def _is_supported(self, ifaceobj):
36 if ifaceobj.get_attr_value_first('address-virtual'):
37 return True
38 return False
39
40 def _get_macvlan_prefix(self, ifaceobj):
41 return '%s-v' %ifaceobj.name[0:13].replace('.', '-')
42
43 def _add_addresses_to_bridge(self, ifaceobj, hwaddress):
44 # XXX: batch the addresses
45 if '.' in ifaceobj.name:
46 (bridgename, vlan) = ifaceobj.name.split('.')
47 if self.ipcmd.bridge_is_vlan_aware(bridgename):
48 [self.ipcmd.bridge_fdb_add(bridgename, addr,
49 vlan) for addr in hwaddress]
50 elif self.ipcmd.is_bridge(ifaceobj.name):
51 [self.ipcmd.bridge_fdb_add(ifaceobj.name, addr)
52 for addr in hwaddress]
53
54 def _remove_addresses_from_bridge(self, ifaceobj, hwaddress):
55 # XXX: batch the addresses
56 if '.' in ifaceobj.name:
57 (bridgename, vlan) = ifaceobj.name.split('.')
58 if self.ipcmd.bridge_is_vlan_aware(bridgename):
59 for addr in hwaddress:
60 try:
61 self.ipcmd.bridge_fdb_del(bridgename, addr, vlan)
62 except Exception, e:
63 self.logger.debug("%s: %s" %(ifaceobj.name, str(e)))
64 pass
65 elif self.ipcmd.is_bridge(ifaceobj.name):
66 for addr in hwaddress:
67 try:
68 self.ipcmd.bridge_fdb_del(ifaceobj.name, addr)
69 except Exception, e:
70 self.logger.debug("%s: %s" %(ifaceobj.name, str(e)))
71 pass
72
73 def _get_bridge_fdbs(self, bridgename, vlan):
74 fdbs = self._bridge_fdb_query_cache.get(bridgename)
75 if not fdbs:
76 fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename)
77 if not fdbs:
78 return
79 self._bridge_fdb_query_cache[bridgename] = fdbs
80 return fdbs.get(vlan)
81
82 def _check_addresses_in_bridge(self, ifaceobj, hwaddress):
83 """ If the device is a bridge, make sure the addresses
84 are in the bridge """
85 if '.' in ifaceobj.name:
86 (bridgename, vlan) = ifaceobj.name.split('.')
87 if self.ipcmd.bridge_is_vlan_aware(bridgename):
88 fdb_addrs = self._get_bridge_fdbs(bridgename, vlan)
89 if not fdb_addrs or hwaddress not in fdb_addrs:
90 return False
91 return True
92
93 def _fix_connected_route(self, ifaceobj, vifacename, addr):
94 #
95 # XXX: Hack to make sure the primary address
96 # is the first in the routing table.
97 #
98 # We use `ip route get` on the vrr network to see which
99 # device the kernel returns. if it is the mac vlan device,
100 # flap the macvlan device to adjust the routing table entry.
101 #
102 # flapping the macvlan device makes sure the macvlan
103 # connected route goes through delete + add, hence adjusting
104 # the order in the routing table.
105 #
106 try:
107 self.logger.info('%s: checking route entry ...' %ifaceobj.name)
108 ip = IPNetwork(addr)
109 route_prefix = '%s/%d' %(ip.network, ip.prefixlen)
110
111 dev = self.ipcmd.ip_route_get_dev(route_prefix)
112 if dev and dev == vifacename:
113 self.logger.info('%s: preferred routing entry ' %ifaceobj.name +
114 'seems to be of the macvlan dev %s'
115 %vifacename +
116 ' .. flapping macvlan dev to fix entry.')
117 self.ipcmd.link_down(vifacename)
118 self.ipcmd.link_up(vifacename)
119 except Exception, e:
120 self.logger.debug('%s: fixing route entry failed (%s)'
121 %str(e))
122 pass
123
124 def _get_macs_from_old_config(self, ifaceobj=None):
125 """ This method returns a list of the mac addresses
126 in the address-virtual attribute for the bridge. """
127 maclist = []
128 saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name)
129 if not saved_ifaceobjs:
130 return maclist
131 # we need the old saved configs from the statemanager
132 for oldifaceobj in saved_ifaceobjs:
133 if not oldifaceobj.get_attr_value('address-virtual'):
134 continue
135 for av in oldifaceobj.get_attr_value('address-virtual'):
136 macip = av.split()
137 if len(macip) < 2:
138 self.logger.debug("%s: incorrect old address-virtual attrs '%s'"
139 %(oldifaceobj.name, av))
140 continue
141 maclist.append(macip[0])
142 return maclist
143
144 def _apply_address_config(self, ifaceobj, address_virtual_list):
145 purge_existing = False if self.PERFMODE else True
146
147 hwaddress = []
148 self.ipcmd.batch_start()
149 av_idx = 0
150 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
151 for av in address_virtual_list:
152 av_attrs = av.split()
153 if len(av_attrs) < 2:
154 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
155 %(ifaceobj.name, av))
156 av_idx += 1
157 continue
158
159 # Create a macvlan device on this device and set the virtual
160 # router mac and ip on it
161 link_created = False
162 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
163 if not self.ipcmd.link_exists(macvlan_ifacename):
164 rtnetlink_api.rtnl_api.create_macvlan(macvlan_ifacename,
165 ifaceobj.name)
166 link_created = True
167 mac = av_attrs[0]
168 ips = av_attrs[1:]
169 if mac != 'None':
170 mac = mac.lower()
171 # customer could have used UPPERCASE for MAC
172 self.ipcmd.link_set_hwaddress(macvlan_ifacename, mac)
173 hwaddress.append(mac)
174 self.ipcmd.addr_add_multiple(macvlan_ifacename, ips,
175 purge_existing)
176 # If link existed before, flap the link
177 if not link_created:
178 self._fix_connected_route(ifaceobj, macvlan_ifacename,
179 ips[0])
180 av_idx += 1
181 self.ipcmd.batch_commit()
182
183 # check the statemanager for old configs.
184 # We need to remove only the previously configured FDB entries
185 oldmacs = self._get_macs_from_old_config(ifaceobj)
186 # get a list of fdbs in old that are not in new config meaning they should
187 # be removed since they are gone from the config
188 removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress]
189 self._remove_addresses_from_bridge(ifaceobj, removed_macs)
190 # if ifaceobj is a bridge and bridge is a vlan aware bridge
191 # add the vid to the bridge
192 self._add_addresses_to_bridge(ifaceobj, hwaddress)
193
194 def _remove_running_address_config(self, ifaceobj):
195 if not self.ipcmd.link_exists(ifaceobj.name):
196 return
197 hwaddress = []
198 self.ipcmd.batch_start()
199 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
200 for macvlan_ifacename in glob.glob("/sys/class/net/%s*" %macvlan_prefix):
201 macvlan_ifacename = os.path.basename(macvlan_ifacename)
202 if not self.ipcmd.link_exists(macvlan_ifacename):
203 continue
204 hwaddress.append(self.ipcmd.link_get_hwaddress(macvlan_ifacename))
205 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
206 # XXX: Also delete any fdb addresses. This requires, checking mac address
207 # on individual macvlan interfaces and deleting the vlan from that.
208 self.ipcmd.batch_commit()
209 if any(hwaddress):
210 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
211
212 def _remove_address_config(self, ifaceobj, address_virtual_list=None):
213 if not address_virtual_list:
214 self._remove_running_address_config(ifaceobj)
215 return
216
217 if not self.ipcmd.link_exists(ifaceobj.name):
218 return
219 hwaddress = []
220 self.ipcmd.batch_start()
221 av_idx = 0
222 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
223 for av in address_virtual_list:
224 av_attrs = av.split()
225 if len(av_attrs) < 2:
226 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
227 %(ifaceobj.name, av))
228 av_idx += 1
229 continue
230
231 # Delete the macvlan device on this device
232 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
233 self.ipcmd.link_delete(os.path.basename(macvlan_ifacename))
234 if av_attrs[0] != 'None':
235 hwaddress.append(av_attrs[0])
236 av_idx += 1
237 self.ipcmd.batch_commit()
238 self._remove_addresses_from_bridge(ifaceobj, hwaddress)
239
240 def _up(self, ifaceobj):
241 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
242 if not address_virtual_list:
243 # XXX: address virtual is not present. In which case,
244 # delete stale macvlan devices.
245 self._remove_address_config(ifaceobj, address_virtual_list)
246 return
247
248 if not self.ipcmd.link_exists(ifaceobj.name):
249 return
250 self._apply_address_config(ifaceobj, address_virtual_list)
251
252 def _down(self, ifaceobj):
253 try:
254 self._remove_address_config(ifaceobj,
255 ifaceobj.get_attr_value('address-virtual'))
256 except Exception, e:
257 self.log_warn(str(e))
258
259 def _query_check(self, ifaceobj, ifaceobjcurr):
260 address_virtual_list = ifaceobj.get_attr_value('address-virtual')
261 if not address_virtual_list:
262 return
263 if not self.ipcmd.link_exists(ifaceobj.name):
264 return
265 av_idx = 0
266 macvlan_prefix = self._get_macvlan_prefix(ifaceobj)
267 for address_virtual in address_virtual_list:
268 av_attrs = address_virtual.split()
269 if len(av_attrs) < 2:
270 self.logger.warn("%s: incorrect address-virtual attrs '%s'"
271 %(ifaceobj.name, address_virtual))
272 av_idx += 1
273 continue
274
275 # Check if the macvlan device on this interface
276 macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx)
277 if not self.ipcmd.link_exists(macvlan_ifacename):
278 ifaceobjcurr.update_config_with_status('address-virtual',
279 '', 1)
280 av_idx += 1
281 continue
282 # Check mac and ip address
283 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
284 raddrs = self.ipcmd.addr_get(macvlan_ifacename)
285 if not raddrs or not rhwaddress:
286 ifaceobjcurr.update_config_with_status('address-virtual', '', 1)
287 av_idx += 1
288 continue
289 raddrs = raddrs.keys()
290 if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and
291 self._check_addresses_in_bridge(ifaceobj, av_attrs[0])):
292 ifaceobjcurr.update_config_with_status('address-virtual',
293 address_virtual, 0)
294 else:
295 raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs))
296 ifaceobjcurr.update_config_with_status('address-virtual',
297 raddress_virtual, 1)
298 av_idx += 1
299 return
300
301 def _query_running(self, ifaceobjrunning):
302 macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning)
303 address_virtuals = glob.glob("/sys/class/net/%s*" %macvlan_prefix)
304 for av in address_virtuals:
305 macvlan_ifacename = os.path.basename(av)
306 rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename)
307 raddress = self.ipcmd.addr_get(macvlan_ifacename)
308 if not raddress:
309 self.logger.warn('%s: no running addresses'
310 %ifaceobjrunning.name)
311 raddress = []
312 ifaceobjrunning.update_config('address-virtual',
313 '%s %s' %(rhwaddress, ''.join(raddress)))
314 return
315
316 _run_ops = {'up' : _up,
317 'down' : _down,
318 'query-checkcurr' : _query_check,
319 'query-running' : _query_running}
320
321 def get_ops(self):
322 """ returns list of ops supported by this module """
323 return self._run_ops.keys()
324
325 def _init_command_handlers(self):
326 if not self.ipcmd:
327 self.ipcmd = iproute2(**self.get_flags())
328
329 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
330 """ run vlan configuration on the interface object passed as argument
331
332 Args:
333 **ifaceobj** (object): iface object
334
335 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
336 'query-running'
337 Kwargs:
338 **query_ifaceobj** (object): query check ifaceobject. This is only
339 valid when op is 'query-checkcurr'. It is an object same as
340 ifaceobj, but contains running attribute values and its config
341 status. The modules can use it to return queried running state
342 of interfaces. status is success if the running state is same
343 as user required state in ifaceobj. error otherwise.
344 """
345 if ifaceobj.type == ifaceType.BRIDGE_VLAN:
346 return
347 op_handler = self._run_ops.get(operation)
348 if not op_handler:
349 return
350 self._init_command_handlers()
351 if operation == 'query-checkcurr':
352 op_handler(self, ifaceobj, query_ifaceobj)
353 else:
354 op_handler(self, ifaceobj)