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