]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown/iface.py
Make sure an interface is down (in the bond case, its slaves are also
[mirror_ifupdown2.git] / ifupdown / iface.py
CommitLineData
a6f80f0e 1#!/usr/bin/python
3e8ee54f 2#
904908bc 3# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
3e8ee54f 4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6# iface --
7# interface object
8#
62ddec8b 9
10"""ifupdown2 network interface object
11
904908bc 12It is modeled based on the 'iface' section in /etc/network/interfaces
62ddec8b 13file. But can be extended to include any other network interface format
62ddec8b 14"""
15
cca03c30 16from collections import OrderedDict
d08d5f54 17import logging
2cd06f78 18import json
a6f80f0e 19
41febf89 20class ifaceType():
2da58137
RP
21 UNKNOWN = 0x0
22 IFACE = 0x1
23 BRIDGE_VLAN = 0x2
41febf89 24
0a3bee28
ST
25
26class ifaceRole():
27 """ ifaceRole is used to classify the ifaceobj.role of
28 MASTER or SLAVE where there is a bond or bridge
29 with bond-slaves or bridge-ports. A bond in a bridge
30 is both a master and slave (0x3)
31 """
32 UNKNOWN = 0x0
33 SLAVE = 0x1
34 MASTER = 0x2
35
a9ab1b4f 36class ifaceLinkKind():
0a3bee28
ST
37 """ ifaceLlinkKind is used to identify interfaces
38 in the ifaceobj.link_kind attribute. Dependents of the bridge or
39 bond have an ifaceobj.role attribute of SLAVE and the bridge or
40 bond itself has ifaceobj.role of MASTER.
41 """
a9ab1b4f
RP
42 UNKNOWN = 0x0
43 BRIDGE = 0x1
44 BOND = 0x2
0a3bee28
ST
45 VLAN = 0x4
46 VXLAN = 0x8
7f67f3e5 47 BRIDGE_VLAN_AWARE = 0x10
65e0c276
RP
48 BRIDGE_PORT = 0x20
49 BOND_SLAVE = 0x40
a9ab1b4f 50
c416da6a 51class ifaceLinkType():
a070c90e 52 LINK_UNKNOWN = 0x0
c416da6a
RP
53 LINK_SLAVE = 0x1
54 LINK_MASTER = 0x2
a070c90e 55 LINK_NA = 0x3
c416da6a 56
45ca0b6d
RP
57class ifaceDependencyType():
58 """ Indicates type of dependency.
59
60 This class enumerates types of dependency relationships
61 between interfaces.
62
63 iface dependency relationships can be classified
64 into:
65 - link
66 - master/slave
67
68 In a 'link' dependency relationship, dependency can be shared
69 between interfaces. example: swp1.100 and
70 swp1.200 can both have 'link' swp1. swp1 is also a dependency
71 of swp1.100 and swp1.200. As you can see dependency
72 swp1 is shared between swp1.100 and swp1.200.
73
74 In a master/slave relationship like bridge and
75 its ports: eg: bridge br0 and its ports swp1 and swp2.
76 dependency swp1 and swp2 cannot be shared with any other
77 interface with the same dependency relationship.
78 ie, swp1 and swp2 cannot be in a slave relationship
79 with another interface. Understanding the dependency type is
80 required for any semantic checks between dependencies.
81
82 """
83 UNKNOWN = 0x0
84 LINK = 0x1
85 MASTER_SLAVE = 0x2
86
a6f80f0e 87class ifaceStatus():
62ddec8b 88 """Enumerates iface status """
89
a6f80f0e 90 UNKNOWN = 0x1
91 SUCCESS = 0x2
92 ERROR = 0x3
10720a53 93 NOTFOUND = 0x4
a6f80f0e 94
95 @classmethod
96 def to_str(cls, state):
97 if state == cls.UNKNOWN:
98 return 'unknown'
99 elif state == cls.SUCCESS:
100 return 'success'
101 elif state == cls.ERROR:
102 return 'error'
739f665b 103 elif state == cls.NOTFOUND:
69f58278 104 return 'notfound'
a6f80f0e 105
106 @classmethod
107 def from_str(cls, state_str):
108 if state_str == 'unknown':
109 return cls.UNKNOWN
110 elif state_str == 'success':
111 return cls.SUCCESS
112 elif state_str == 'error':
113 return cls.ERROR
114
115class ifaceState():
62ddec8b 116 """Enumerates iface state """
a6f80f0e 117
a6f80f0e 118 UNKNOWN = 0x1
119 NEW = 0x2
120 PRE_UP = 0x3
121 UP = 0x4
122 POST_UP = 0x5
123 PRE_DOWN = 0x6
124 DOWN = 0x7
125 POST_DOWN = 0x8
126
d08d5f54 127 # Pseudo states
128 QUERY_CHECKCURR = 0x9
129 QUERY_RUNNING = 0xa
130
a6f80f0e 131 @classmethod
132 def to_str(cls, state):
133 if state == cls.UNKNOWN:
134 return 'unknown'
135 elif state == cls.NEW:
136 return 'new'
137 elif state == cls.PRE_UP:
138 return 'pre-up'
139 elif state == cls.UP:
140 return 'up'
141 elif state == cls.POST_UP:
142 return 'post-up'
143 elif state == cls.PRE_DOWN:
144 return 'pre-down'
d08d5f54 145 elif state == cls.DOWN:
146 return 'down'
a6f80f0e 147 elif state == cls.POST_DOWN:
148 return 'post-down'
d08d5f54 149 elif state == cls.QUERY_CHECKCURR:
150 return 'query-checkcurr'
151 elif state == cls.QUERY_RUNNING:
152 return 'query-running'
a6f80f0e 153
154 @classmethod
155 def from_str(cls, state_str):
156 if state_str == 'unknown':
157 return cls.UNKNOWN
158 elif state_str == 'new':
159 return cls.NEW
160 elif state_str == 'pre-up':
161 return cls.PRE_UP
162 elif state_str == 'up':
163 return cls.UP
164 elif state_str == 'post-up':
165 return cls.POST_UP
166 elif state_str == 'pre-down':
167 return cls.PRE_DOWN
d08d5f54 168 elif state_str == 'down':
169 return cls.DOWN
a6f80f0e 170 elif state_str == 'post-down':
171 return cls.POST_DOWN
d08d5f54 172 elif state_str == 'query-checkcurr':
173 return cls.QUERY_CHECKCURR
174 elif state_str == 'query-running':
175 return cls.QUERY_RUNNING
a6f80f0e 176
d08d5f54 177class ifaceJsonEncoder(json.JSONEncoder):
178 def default(self, o):
83c1f241 179 retconfig = {}
180 if o.config:
3dcc1d0e 181 retconfig = dict((k, (v[0] if len(v) == 1 else v))
182 for k,v in o.config.items())
75730152 183 return OrderedDict({'name' : o.name,
184 'addr_method' : o.addr_method,
185 'addr_family' : o.addr_family,
186 'auto' : o.auto,
83c1f241 187 'config' : retconfig})
d08d5f54 188
3dcc1d0e 189class ifaceJsonDecoder():
190 @classmethod
191 def json_to_ifaceobj(cls, ifaceattrdict):
192 ifaceattrdict['config'] = OrderedDict([(k, (v if isinstance(v, list)
193 else [v]))
194 for k,v in ifaceattrdict.get('config',
195 OrderedDict()).items()])
196 return iface(attrsdict=ifaceattrdict)
197
a6f80f0e 198class iface():
904908bc 199 """ ifupdown2 iface object class
62ddec8b 200
201 Attributes:
904908bc
RP
202 **name** Name of the interface
203
204 **addr_family** Address family eg, inet, inet6. Can be None to
205 indicate both address families
206
207 **addr_method** Address method eg, static, manual or None for
208 static address method
209
210 **config** dictionary of config lines for this interface
211
212 **state** Configuration state of an interface as defined by
213 ifaceState
214
215 **status** Configuration status of an interface as defined by
216 ifaceStatus
217
218 **flags** Internal flags used by iface processing
219
220 **priv_flags** private flags owned by module using this class
221
4c773918
ST
222 **module_flags** module flags owned by module using this class
223
904908bc
RP
224 **refcnt** reference count, indicating number of interfaces
225 dependent on this iface
226
227 **lowerifaces** list of interface names lower to this interface or
228 this interface depends on
229
230 **upperifaces** list of interface names upper to this interface or
231 the interfaces that depend on this interface
232
233 **auto** True if interface belongs to the auto class
234
235 **classes** List of classes the interface belongs to
236
237 **env** shell environment the interface needs during
238 execution
239
240 **raw_config** raw interface config from file
62ddec8b 241 """
242
31a5f4c3 243 # flag to indicate that the object was created from pickled state
73c27592
RP
244 _PICKLED = 0x00000001
245 HAS_SIBLINGS = 0x00000010
246 IFACERANGE_ENTRY = 0x00000100
247 IFACERANGE_START = 0x00001000
a6f80f0e 248
37c0543d 249 version = '0.1'
250
3dcc1d0e 251 def __init__(self, attrsdict={}):
252 self._set_attrs_from_dict(attrsdict)
62ddec8b 253 self._config_status = {}
2c0ad8b3 254 """dict with config status of iface attributes"""
a6f80f0e 255 self.state = ifaceState.NEW
2c0ad8b3 256 """iface state (of type ifaceState) """
a6f80f0e 257 self.status = ifaceStatus.UNKNOWN
2c0ad8b3 258 """iface status (of type ifaceStatus) """
e1601369
RP
259 self.status_str = None
260 """iface status str (string representing the status) """
a6f80f0e 261 self.flags = 0x0
2c0ad8b3 262 """iface flags """
37c0543d 263 self.priv_flags = 0x0
4c773918
ST
264 """iface module flags dictionary with module name: flags"""
265 self.module_flags = {}
2c0ad8b3 266 """iface priv flags. can be used by the external object manager """
a6f80f0e 267 self.refcnt = 0
2c0ad8b3 268 """iface refcnt (incremented for each dependent this interface has) """
41febf89 269 self.lowerifaces = None
2c0ad8b3 270 """lower iface list (in other words: slaves of this interface """
f3215127 271 self.upperifaces = None
2c0ad8b3 272 """upper iface list (in other words: master of this interface """
a6f80f0e 273 self.classes = []
2c0ad8b3 274 """interface classes this iface belongs to """
a6f80f0e 275 self.env = None
2c0ad8b3 276 """environment variable dict required for this interface to run"""
62ddec8b 277 self.raw_config = []
2c0ad8b3 278 """interface config/attributes in raw format (eg: as it appeared in the interfaces file)"""
a6f80f0e 279 self.linkstate = None
2c0ad8b3 280 """linkstate of the interface"""
41febf89
RP
281 self.type = ifaceType.UNKNOWN
282 """interface type"""
283 self.priv_data = None
0a3bee28 284 self.role = ifaceRole.UNKNOWN
2da58137 285 self.realname = None
a070c90e 286 self.link_type = ifaceLinkType.LINK_UNKNOWN
c65c2ccd 287 self.link_kind = ifaceLinkKind.UNKNOWN
a6f80f0e 288
45ca0b6d
RP
289 # The below attribute is used to disambiguate between various
290 # types of dependencies
291 self.dependency_type = ifaceDependencyType.UNKNOWN
0532f0a3 292 self.blacklisted = False
45ca0b6d 293
3dcc1d0e 294 def _set_attrs_from_dict(self, attrdict):
295 self.auto = attrdict.get('auto', False)
296 self.name = attrdict.get('name')
297 self.addr_family = attrdict.get('addr_family')
298 self.addr_method = attrdict.get('addr_method')
299 self.config = attrdict.get('config', OrderedDict())
300
a6f80f0e 301 def inc_refcnt(self):
2c0ad8b3
RP
302 """ increment refcnt of the interface. Usually used to indicate that
303 it has dependents """
a6f80f0e 304 self.refcnt += 1
305
306 def dec_refcnt(self):
2c0ad8b3
RP
307 """ decrement refcnt of the interface. Usually used to indicate that
308 it has lost its dependent """
a6f80f0e 309 self.refcnt -= 1
310
cca03c30 311 def is_config_present(self):
2c0ad8b3
RP
312 """ returns true if the interface has user provided config,
313 false otherwise """
62ddec8b 314 addr_method = self.addr_method
2cd06f78 315 if addr_method and addr_method in ['dhcp', 'dhcp6', 'loopback']:
316 return True
fe0a57d3 317 if not self.config:
cca03c30 318 return False
fe0a57d3 319 else:
320 return True
cca03c30 321
3e8ee54f 322 def set_class(self, classname):
2c0ad8b3 323 """ appends class to the interfaces class list """
a6f80f0e 324 self.classes.append(classname)
325
31a5f4c3 326 def set_state_n_status(self, state, status):
2c0ad8b3 327 """ sets state and status of an interface """
31a5f4c3 328 self.state = state
329 self.status = status
330
a6f80f0e 331 def set_flag(self, flag):
332 self.flags |= flag
333
334 def clear_flag(self, flag):
335 self.flags &= ~flag
336
f3215127 337 def add_to_upperifaces(self, upperifacename):
2c0ad8b3 338 """ add to the list of upperifaces """
f3215127 339 if self.upperifaces:
340 if upperifacename not in self.upperifaces:
341 self.upperifaces.append(upperifacename)
342 else:
343 self.upperifaces = [upperifacename]
344
a6f80f0e 345 def get_attr_value(self, attr_name):
2c0ad8b3 346 """ add to the list of upperifaces """
62ddec8b 347 return self.config.get(attr_name)
a6f80f0e 348
349 def get_attr_value_first(self, attr_name):
2c0ad8b3 350 """ get first value of the specified attr name """
62ddec8b 351 attr_value_list = self.config.get(attr_name)
fe0a57d3 352 if attr_value_list:
a6f80f0e 353 return attr_value_list[0]
a6f80f0e 354 return None
355
679e6567
RP
356 def get_attrs_value_first(self, attrs):
357 """ get first value of the first attr in the list.
358 Useful when you have multiple attrs representing the
359 same thing.
360 """
361 for attr in attrs:
362 attr_value_list = self.config.get(attr)
363 if attr_value_list:
364 return attr_value_list[0]
365 return None
366
a6f80f0e 367 def get_attr_value_n(self, attr_name, attr_index):
2c0ad8b3 368 """ get n'th value of the specified attr name """
62ddec8b 369 attr_value_list = self.config.get(attr_name)
fe0a57d3 370 if attr_value_list:
a6f80f0e 371 try:
372 return attr_value_list[attr_index]
373 except:
374 return None
a6f80f0e 375 return None
376
62ddec8b 377 @property
a6f80f0e 378 def get_env(self):
2c0ad8b3 379 """ get shell environment variables the interface must execute in """
fe0a57d3 380 if not self.env:
a6f80f0e 381 self.generate_env()
382 return self.env
383
a6f80f0e 384 def generate_env(self):
2c0ad8b3
RP
385 """ generate shell environment variables dict interface must execute
386 in. This is used to support legacy ifupdown scripts
387 """
a6f80f0e 388 env = {}
62ddec8b 389 config = self.config
390 env['IFACE'] = self.name
a6f80f0e 391 for attr, attr_value in config.items():
392 attr_env_name = 'IF_%s' %attr.upper()
393 env[attr_env_name] = attr_value[0]
fe0a57d3 394 if env:
62ddec8b 395 self.env = env
a6f80f0e 396
739f665b 397 def update_config(self, attr_name, attr_value):
2c0ad8b3 398 """ add attribute name and value to the interface config """
62ddec8b 399 self.config.setdefault(attr_name, []).append(attr_value)
739f665b 400
d8e3554d
RP
401 def replace_config(self, attr_name, attr_value):
402 """ add attribute name and value to the interface config """
403 self.config[attr_name] = [attr_value]
404
405 def delete_config(self, attr_name):
406 """ add attribute name and value to the interface config """
407 try:
408 del self.config[attr_name]
409 except:
410 pass
411
739f665b 412 def update_config_dict(self, attrdict):
413 self.config.update(attrdict)
414
415 def update_config_with_status(self, attr_name, attr_value, attr_status=0):
2c0ad8b3
RP
416 """ add attribute name and value to the interface config and also
417 update the config_status dict with status of this attribute config """
69f58278 418 if not attr_value:
a6f80f0e 419 attr_value = ''
62ddec8b 420 self.config.setdefault(attr_name, []).append(attr_value)
421 self._config_status.setdefault(attr_name, []).append(attr_status)
69f58278 422 # set global iface state
e1601369 423 if attr_status == 1:
62ddec8b 424 self.status = ifaceStatus.ERROR
425 elif self.status != ifaceStatus.ERROR:
69f58278 426 # Not already error, mark success
62ddec8b 427 self.status = ifaceStatus.SUCCESS
69f58278 428
a070c90e 429 def check_n_update_config_with_status_many(self, ifaceobjorig, attr_names,
e1601369
RP
430 attr_status=0):
431 # set multiple attribute status to zero
432 # also updates status only if the attribute is present
433 for attr_name in attr_names:
a070c90e
RP
434 if not ifaceobjorig.get_attr_value_first(attr_name):
435 continue
e1601369
RP
436 self.config.setdefault(attr_name, []).append('')
437 self._config_status.setdefault(attr_name, []).append(attr_status)
438
69f58278 439 def get_config_attr_status(self, attr_name, idx=0):
2c0ad8b3
RP
440 """ get status of a attribute config on this interface.
441
442 Looks at the iface _config_status dict"""
62ddec8b 443 return self._config_status.get(attr_name, [])[idx]
69f58278 444
ca3f4fc7 445 def compare(self, dstiface):
2c0ad8b3 446 """ compares iface object with iface object passed as argument
ca3f4fc7 447
448 Returns True if object self is same as dstiface and False otherwise """
449
450 if self.name != dstiface.name: return False
84ca006f 451 if self.type != dstiface.type: return False
ca3f4fc7 452 if self.addr_family != dstiface.addr_family: return False
453 if self.addr_method != dstiface.addr_method: return False
454 if self.auto != dstiface.auto: return False
455 if self.classes != dstiface.classes: return False
acf92644
RP
456 if len(self.config) != len(dstiface.config):
457 return False
37c0543d 458 if any(True for k in self.config if k not in dstiface.config):
ca3f4fc7 459 return False
37c0543d 460 if any(True for k,v in self.config.items()
ca3f4fc7 461 if v != dstiface.config.get(k)): return False
462 return True
d08d5f54 463
e1601369 464
d08d5f54 465 def __getstate__(self):
466 odict = self.__dict__.copy()
467 del odict['state']
468 del odict['status']
f3215127 469 del odict['lowerifaces']
c798b0f4 470 del odict['upperifaces']
d08d5f54 471 del odict['refcnt']
62ddec8b 472 del odict['_config_status']
5c721925 473 del odict['flags']
474 del odict['priv_flags']
4c773918 475 del odict['module_flags']
62ddec8b 476 del odict['raw_config']
5c721925 477 del odict['linkstate']
478 del odict['env']
c416da6a 479 del odict['link_type']
a9ab1b4f 480 del odict['link_kind']
0a3bee28 481 del odict['role']
45ca0b6d 482 del odict['dependency_type']
0532f0a3 483 del odict['blacklisted']
d08d5f54 484 return odict
485
486 def __setstate__(self, dict):
487 self.__dict__.update(dict)
62ddec8b 488 self._config_status = {}
d08d5f54 489 self.state = ifaceState.NEW
490 self.status = ifaceStatus.UNKNOWN
491 self.refcnt = 0
5c721925 492 self.flags = 0
f3215127 493 self.lowerifaces = None
c798b0f4 494 self.upperifaces = None
d08d5f54 495 self.linkstate = None
5c721925 496 self.env = None
0a3bee28 497 self.role = ifaceRole.UNKNOWN
5c721925 498 self.priv_flags = 0
4c773918 499 self.module_flags = {}
62ddec8b 500 self.raw_config = []
501 self.flags |= self._PICKLED
f3b69969 502 self.link_type = ifaceLinkType.LINK_NA
a9ab1b4f 503 self.link_kind = ifaceLinkKind.UNKNOWN
45ca0b6d 504 self.dependency_type = ifaceDependencyType.UNKNOWN
0532f0a3 505 self.blacklisted = False
3dcc1d0e 506
a6f80f0e 507 def dump_raw(self, logger):
508 indent = ' '
53b00224 509 if self.auto:
510 print 'auto %s' %self.name
62ddec8b 511 print (self.raw_config[0])
512 for i in range(1, len(self.raw_config)):
53b00224 513 print(indent + self.raw_config[i])
a6f80f0e 514
a6f80f0e 515 def dump(self, logger):
516 indent = '\t'
62ddec8b 517 logger.info(self.name + ' : {')
518 logger.info(indent + 'family: %s' %self.addr_family)
519 logger.info(indent + 'method: %s' %self.addr_method)
31a5f4c3 520 logger.info(indent + 'flags: %x' %self.flags)
a6f80f0e 521 logger.info(indent + 'state: %s'
62ddec8b 522 %ifaceState.to_str(self.state))
a6f80f0e 523 logger.info(indent + 'status: %s'
62ddec8b 524 %ifaceStatus.to_str(self.status))
525 logger.info(indent + 'refcnt: %d' %self.refcnt)
526 d = self.lowerifaces
fe0a57d3 527 if d:
f3215127 528 logger.info(indent + 'lowerdevs: %s' %str(d))
a6f80f0e 529 else:
f3215127 530 logger.info(indent + 'lowerdevs: None')
739f665b 531
a6f80f0e 532 logger.info(indent + 'config: ')
62ddec8b 533 config = self.config
fe0a57d3 534 if config:
a6f80f0e 535 logger.info(indent + indent + str(config))
536 logger.info('}')
537
86fc62e2 538 def dump_pretty(self, with_status=False,
2da58137 539 successstr='success', errorstr='error',
fac4138b 540 unknownstr='unknown', use_realname=False):
a6f80f0e 541 indent = '\t'
cca03c30 542 outbuf = ''
b48ff1a9
RP
543 if use_realname and self.realname:
544 name = '%s' %self.realname
2da58137 545 else:
b48ff1a9 546 name = '%s' %self.name
62ddec8b 547 if self.auto:
2da58137 548 outbuf += 'auto %s\n' %name
5b4d3044 549 ifaceline = ''
cb46a208 550 if self.type == ifaceType.BRIDGE_VLAN:
5b4d3044 551 ifaceline += 'vlan %s' %name
cb46a208 552 else:
5b4d3044 553 ifaceline += 'iface %s' %name
62ddec8b 554 if self.addr_family:
5b4d3044 555 ifaceline += ' %s' %self.addr_family
62ddec8b 556 if self.addr_method:
5b4d3044 557 ifaceline += ' %s' %self.addr_method
2cd06f78 558 if with_status:
5b4d3044 559 status_str = None
e1601369
RP
560 if (self.status == ifaceStatus.ERROR or
561 self.status == ifaceStatus.NOTFOUND):
562 if self.status_str:
5b4d3044
RP
563 ifaceline += ' (%s)' %self.status_str
564 status_str = errorstr
86fc62e2 565 elif self.status == ifaceStatus.SUCCESS:
5b4d3044
RP
566 status_str = successstr
567 if status_str:
568 outbuf += '{0:65} {1:>8}'.format(ifaceline, status_str) + '\n'
569 else:
570 outbuf += ifaceline + '\n'
2cd06f78 571 if self.status == ifaceStatus.NOTFOUND:
e1601369
RP
572 outbuf = (outbuf.encode('utf8')
573 if isinstance(outbuf, unicode) else outbuf)
2cd06f78 574 print outbuf + '\n'
575 return
5b4d3044
RP
576 else:
577 outbuf += ifaceline + '\n'
62ddec8b 578 config = self.config
fe0a57d3 579 if config:
eab25b7c 580 for cname, cvaluelist in config.items():
69f58278 581 idx = 0
eab25b7c 582 for cv in cvaluelist:
69f58278 583 if with_status:
86fc62e2 584 s = self.get_config_attr_status(cname, idx)
e1601369 585 if s == -1:
5b4d3044 586 status_str = unknownstr
e1601369 587 elif s == 1:
5b4d3044 588 status_str = errorstr
86fc62e2 589 elif s == 0:
5b4d3044
RP
590 status_str = successstr
591 outbuf += (indent + '{0:55} {1:>10}'.format(
592 '%s %s' %(cname, cv), status_str)) + '\n'
69f58278 593 else:
594 outbuf += indent + '%s %s\n' %(cname, cv)
595 idx += 1
3dcc1d0e 596 if with_status:
597 outbuf = (outbuf.encode('utf8')
598 if isinstance(outbuf, unicode) else outbuf)
a6f80f0e 599 print outbuf