ifstate 1.13.7__py3-none-any.whl → 2.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hooks/wrapper.sh ADDED
@@ -0,0 +1,50 @@
1
+ #!/bin/sh
2
+
3
+ # ifstate: wrapper to run hooks
4
+
5
+ # debugging
6
+ export IFS_VERBOSE=${verbose}
7
+ if [ "$$IFS_VERBOSE" = 1 ]; then
8
+ set -x
9
+ fi
10
+
11
+ # return codes
12
+ export IFS_RC_OK="${rc_ok}"
13
+ export IFS_RC_ERROR="${rc_error}"
14
+ export IFS_RC_STARTED="${rc_started}"
15
+ export IFS_RC_STOPPED="${rc_stopped}"
16
+ export IFS_RC_CHANGED="${rc_changed}"
17
+
18
+ # generic environment variables
19
+ export IFS_SCRIPT="${script}"
20
+ export IFS_RUNDIR="${rundir}"
21
+
22
+ export IFS_IFNAME="${ifname}"
23
+ export IFS_INDEX="${index}"
24
+ export IFS_NETNS="${netns}"
25
+ export IFS_VRF="${vrf}"
26
+
27
+ # hook arguments
28
+ ${args}
29
+
30
+ # run hook NetNS and VRF aware
31
+ if [ -z "$$IFS_NETNS" ]; then
32
+ if [ -z "$$IFS_VRF" ]; then
33
+ # just exec the script
34
+ exec "$$IFS_SCRIPT" "$$@"
35
+ else
36
+ # exec in VRF
37
+ exec ip vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
38
+ fi
39
+ else
40
+ if [ -z "$$IFS_VRF" ]; then
41
+ # exec in NetNS
42
+ exec ip netns exec "$$IFS_NETNS" "$$IFS_SCRIPT" "$$@"
43
+ else
44
+ # exec in NetNS->VRF
45
+ exec ip -n "$$IFS_NETNS" vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
46
+ fi
47
+ fi
48
+
49
+ # somthing gone wrong
50
+ return $$IFS_RC_ERROR
ifstate/ifstate.py CHANGED
@@ -21,6 +21,7 @@ import yaml
21
21
  class Actions():
22
22
  CHECK = "check"
23
23
  APPLY = "apply"
24
+ IDENTIFY = "identify"
24
25
  SHOW = "show"
25
26
  SHOWALL = "showall"
26
27
  VRRP = "vrrp"
@@ -31,6 +32,7 @@ class Actions():
31
32
  ACTIONS_HELP = {
32
33
  "CHECK" : "dry run update the network config",
33
34
  "APPLY" : "update the network config",
35
+ "IDENTIFY" : "show interface attributes available for identification",
34
36
  "SHOW" : "show running network config",
35
37
  "SHOWALL" : "show running network config (more settings)",
36
38
  "VRRP" : "run as keepalived notify script",
@@ -180,13 +182,16 @@ def main():
180
182
 
181
183
  ifslog = IfStateLogging(lvl, action=args.action)
182
184
 
183
- if args.action in [Actions.SHOW, Actions.SHOWALL]:
185
+ if args.action in [Actions.IDENTIFY, Actions.SHOW, Actions.SHOWALL]:
184
186
  # preserve dict order on python 3.7+
185
187
  if sys.version_info >= (3, 7):
186
188
  yaml.add_representer(
187
189
  dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
188
190
  ifs = IfState()
189
- print(yaml.dump(ifs.show(args.action == Actions.SHOWALL)))
191
+ if args.action == Actions.IDENTIFY:
192
+ print(yaml.dump(ifs.identify()))
193
+ else:
194
+ print(yaml.dump(ifs.show(args.action == Actions.SHOWALL)))
190
195
 
191
196
  ifslog.quit()
192
197
  exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ifstate
3
- Version: 1.13.7
3
+ Version: 2.0.0rc1
4
4
  Summary: Manage host interface settings in a declarative manner
5
5
  Home-page: https://ifstate.net/
6
6
  Author: Thomas Liske
@@ -0,0 +1,38 @@
1
+ hooks/wrapper.sh,sha256=ipCmvcadJbXTT6oUR7BIhT5uglITnHfiAdm44vydZuw,1101
2
+ ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ ifstate/ifstate.py,sha256=OYSQ437-y8oRhnTtudZnHgzIW3kVuyCW9U4mNeA_i54,9119
4
+ ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
5
+ ifstate/vrrp.py,sha256=FJ9b1eJseTtZFfknHU-xV68Qz7cPrRrc5PTcjUVj-fY,5953
6
+ ifstate-2.0.0rc1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
+ libifstate/__init__.py,sha256=riz2m6o4yfab3Ta8pbX_Rfbk658N9yDkZWkjJKOiraA,33048
8
+ libifstate/exception.py,sha256=5i59BZdl56J_sNJbyU9n6uHuUNJEyDOO4FJ-neDn9Ds,2608
9
+ libifstate/log.py,sha256=XVoZdwdQoWsjuupFIuG6OP0OrBpXpx7oqyAaUhQ-nJk,4553
10
+ libifstate/util.py,sha256=1CGVgaEj7L4At9FPWcDQLwSe027XSKoxFHIdkS5spSA,11354
11
+ libifstate/address/__init__.py,sha256=zIGM7UWXSvoijHMD06DfS2CDtiHpiJlqDgJG7Dl1IPE,3222
12
+ libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
13
+ libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
14
+ libifstate/bpf/map.py,sha256=cLHNMvRBDNW2yVCEf3z242_oRdU0HqVbFEYVkKXng0w,10818
15
+ libifstate/brport/__init__.py,sha256=NzdA8F4hr2se1bXKNnyKZbvOFlCWBq_cdjwsL1H0Y-o,2964
16
+ libifstate/fdb/__init__.py,sha256=9dpL5n8ct3CaA-z8I6ZEkD3yL6yWJQeq3fpIe9pc2zw,6486
17
+ libifstate/hook/__init__.py,sha256=OPUyhrr-eT1UNp8ryYoJyvi4WWqL2Ql33szir673Td4,7379
18
+ libifstate/link/__init__.py,sha256=QZggoC-bIscqwVedqVycaSqS1CmXB3Bx3m2FZei8Q_4,115
19
+ libifstate/link/base.py,sha256=dazZVPRMyXms-ICiL7VOCUXwZnqNr1X6R2NjTlm2nEU,34384
20
+ libifstate/link/physical.py,sha256=ectyXVchCHqWZR8qsjAD9667wjQbGW1OUKLQheTUFys,594
21
+ libifstate/link/tun.py,sha256=SWkscSAgIk3DWFPWHo9Cmokhj88rO1_sR7Z0Qlg2xGA,993
22
+ libifstate/link/veth.py,sha256=Sv5WZMMsefYFz9I0BQSZyKytCQYxv0vjwAnB7FYHR1A,2283
23
+ libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
24
+ libifstate/netns/__init__.py,sha256=4DWFsgkT84foKoE0ZIbCIAqezDZzOBf8Ka1OAZS_T9E,10164
25
+ libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
26
+ libifstate/parser/base.py,sha256=EV7uJYdVke3a2ghkUZ6ZeaIeVQAPVS4rFnYhebXeQm0,5932
27
+ libifstate/parser/yaml.py,sha256=MC0kmwqt3P45z61fb_wfUqoj0iZyhFYkdPyr0UqMSZA,1415
28
+ libifstate/routing/__init__.py,sha256=EvuS0GC5s7up92DYwSGhkohqhCQKPJpvs-alY4WAQws,25326
29
+ libifstate/sysctl/__init__.py,sha256=EF52CdOOkVSUFR2t21A99KlG1-PjsD4qOiceQC4eI24,3074
30
+ libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
31
+ libifstate/wireguard/__init__.py,sha256=9v9szQdPtAQ7vx4-_adnh-82WdLtg9meeEjlV-uA-9o,6705
32
+ libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
33
+ schema/2/ifstate.conf.schema.json,sha256=W47erPkAJi8BhBZJF_mLdZ1v7832W67BMGOx6KHzsw0,218580
34
+ ifstate-2.0.0rc1.dist-info/METADATA,sha256=9EggrnXIHU69cZWFHFTcyHJvwYbpzTiOJoxtV4rgJ_4,1600
35
+ ifstate-2.0.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ ifstate-2.0.0rc1.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
37
+ ifstate-2.0.0rc1.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
38
+ ifstate-2.0.0rc1.dist-info/RECORD,,
libifstate/__init__.py CHANGED
@@ -2,6 +2,7 @@ from libifstate.exception import LinkDuplicate, NetnsUnknown
2
2
  from libifstate.link.base import ethtool_path, Link
3
3
  from libifstate.address import Addresses
4
4
  from libifstate.fdb import FDB
5
+ from libifstate.hook import Hooks
5
6
  from libifstate.neighbour import Neighbours
6
7
  from libifstate.routing import Tables, Rules, RTLookups
7
8
  from libifstate.parser import Parser
@@ -35,7 +36,7 @@ except ModuleNotFoundError:
35
36
  pass
36
37
 
37
38
  from libifstate.netns import NetNameSpace, prepare_netns, LinkRegistry, get_netns_instances
38
- from libifstate.util import logger, IfStateLogging, LinkDependency
39
+ from libifstate.util import logger, root_iw, IfStateLogging, LinkDependency, IDENTIFY_LOOKUPS
39
40
  from libifstate.exception import FeatureMissingError, LinkCircularLinked, LinkNoConfigFound, ParserValidationError
40
41
  from ipaddress import ip_network, ip_interface
41
42
  from jsonschema import validate, ValidationError, FormatChecker
@@ -48,8 +49,7 @@ import json
48
49
  import errno
49
50
  import logging
50
51
 
51
- __version__ = "1.13.7"
52
-
52
+ __version__ = "2.0.0rc1"
53
53
 
54
54
  class IfState():
55
55
  def __init__(self):
@@ -61,6 +61,8 @@ class IfState():
61
61
  self.ignore = {}
62
62
  self.features = {
63
63
  'brport': True,
64
+ 'devicetree': os.access('/sys/firmware/devicetree', os.R_OK),
65
+ 'iw': root_iw is not None,
64
66
  'link': True,
65
67
  'sysctl': os.access('/proc/sys/net', os.R_OK),
66
68
  'ethtool': not ethtool_path is None,
@@ -81,7 +83,7 @@ class IfState():
81
83
  def update(self, ifstates, soft_schema):
82
84
  # check config schema
83
85
  schema = json.loads(pkgutil.get_data(
84
- "libifstate", "../schema/ifstate.conf.schema.json"))
86
+ "libifstate", "../schema/{}/ifstate.conf.schema.json".format(__version__.split('.')[0])))
85
87
  try:
86
88
  validate(ifstates, schema, format_checker=FormatChecker())
87
89
  except ValidationError as ex:
@@ -104,10 +106,10 @@ class IfState():
104
106
 
105
107
  # add interface defaults
106
108
  if 'defaults' in ifstates:
107
- self.defaults = ifstates['defaults']
109
+ self.defaults = ifstates['parameters']['defaults']
108
110
 
109
111
  # add ignore list items
110
- self.ignore.update(ifstates['ignore'])
112
+ self.ignore.update(ifstates['parameters']['ignore'])
111
113
  self.ipaddr_ignore = set()
112
114
  for ip in self.ignore.get('ipaddr', []):
113
115
  self.ipaddr_ignore.add(ip_network(ip))
@@ -116,7 +118,10 @@ class IfState():
116
118
  self.fdb_ignore = re.compile('|'.join(self.ignore.get('fdb')))
117
119
 
118
120
  # save cshaper profiles
119
- self.cshaper_profiles = ifstates['cshaper']
121
+ self.cshaper_profiles = ifstates['parameters']['cshaper']
122
+
123
+ # prepare hooks
124
+ self.hooks = Hooks(ifstates['parameters'].get('hooks', {}))
120
125
 
121
126
  # build link registry over all named netns
122
127
  self.link_registry = LinkRegistry(self.ignore.get('ifname', []), self.root_netns)
@@ -134,17 +139,15 @@ class IfState():
134
139
  self._update(self.namespaces[netns_name], netns_ifstates)
135
140
 
136
141
  def _update(self, netns, ifstates):
137
- # parse options
138
- if 'options' in ifstates:
139
- # parse global sysctl settings
140
- if 'sysctl' in ifstates['options']:
141
- for proto in ifstates['options']['sysctl'].keys():
142
- if proto in ['all', 'default']:
143
- netns.sysctl.add(
144
- proto, ifstates['options']['sysctl'][proto])
145
- else:
146
- netns.sysctl.add_global(
147
- proto, ifstates['options']['sysctl'][proto])
142
+ # parse network sysctl settings
143
+ if 'sysctl' in ifstates:
144
+ for proto in ifstates['sysctl'].keys():
145
+ if proto in ['all', 'default']:
146
+ netns.sysctl.add(
147
+ proto, ifstates['sysctl'][proto])
148
+ else:
149
+ netns.sysctl.add_global(
150
+ proto, ifstates['sysctl'][proto])
148
151
 
149
152
  # load BPF programs
150
153
  if 'bpf' in ifstates:
@@ -157,8 +160,7 @@ class IfState():
157
160
  netns.bpf_progs.add(name, config)
158
161
 
159
162
  # add interfaces from config
160
- for ifstate in ifstates['interfaces']:
161
- name = ifstate['name']
163
+ for name, ifstate in ifstates['interfaces'].items():
162
164
  kind = ifstate['link']['kind']
163
165
  defaults = self.get_defaults(
164
166
  ifname=name,
@@ -189,7 +191,7 @@ class IfState():
189
191
  link.update(ifstate['link'])
190
192
  if link:
191
193
  netns.links[name] = Link(self,
192
- netns, name, link, ethtool, ifstate.get('vrrp'), ifstate.get('brport'))
194
+ netns, name, link, ifstate.get('identify', {}), ethtool, ifstate.get('hooks', []), ifstate.get('vrrp'), ifstate.get('brport'))
193
195
  else:
194
196
  netns.links[name] = None
195
197
 
@@ -224,7 +226,7 @@ class IfState():
224
226
  netns.sysctl.add(name, ifstate['sysctl'])
225
227
 
226
228
  if 'cshaper' in ifstate:
227
- profile_name = ifstate['cshaper'].get(
229
+ profile_name = ifstate['parameters']['cshaper'].get(
228
230
  'profile', 'default')
229
231
  logger.debug('cshaper profile {} enabled'.format(profile_name),
230
232
  extra={'iface': name, 'netns': netns})
@@ -246,7 +248,7 @@ class IfState():
246
248
  'qdisc': cshaper_profile['ingress_qdisc'],
247
249
  }
248
250
  }
249
- ifb_state['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
251
+ ifb_state['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
250
252
  'ingress', 'unlimited')
251
253
 
252
254
  if 'vrrp' in ifstate:
@@ -279,10 +281,10 @@ class IfState():
279
281
  ]
280
282
  }
281
283
 
282
- ifstate['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
284
+ ifstate['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
283
285
  'egress', 'unlimited')
284
286
 
285
- del ifstate['cshaper']
287
+ del ifstate['parameters']['cshaper']
286
288
 
287
289
  if 'tc' in ifstate:
288
290
  netns.tc[name] = TC(
@@ -611,6 +613,8 @@ class IfState():
611
613
  if ifname in netns.wireguard:
612
614
  netns.wireguard[ifname].apply(do_apply)
613
615
 
616
+ self.hooks.apply(link, do_apply)
617
+
614
618
  def _apply_routing(self, do_apply, netns, by_vrrp, vrrp_type, vrrp_name, vrrp_state):
615
619
  if not netns.tables is None:
616
620
  netns.tables.apply(self.ignore.get('routes', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
@@ -618,6 +622,54 @@ class IfState():
618
622
  if not netns.rules is None:
619
623
  netns.rules.apply(self.ignore.get('rules', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
620
624
 
625
+ def identify(self):
626
+ root_config = self._identify_netns(self.root_netns)
627
+ netns_instances = get_netns_instances()
628
+ if len(netns_instances) > 0:
629
+ netns_identifies = {}
630
+ for netns in netns_instances:
631
+ netns_identify = self._identify_netns(netns)
632
+ if netns_identify is not None:
633
+ netns_identifies[netns.netns] = netns_identify
634
+
635
+ if netns_identifies:
636
+ return {**root_config, 'namespaces': netns_identifies}
637
+
638
+ return {**root_config}
639
+
640
+ def _identify_netns(self, netns):
641
+ ifs_links = []
642
+ for ipr_link in netns.ipr.get_links():
643
+ name = ipr_link.get_attr('IFLA_IFNAME')
644
+ # skip links on ignore list
645
+ if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
646
+ info = ipr_link.get_attr('IFLA_LINKINFO')
647
+ if info is None:
648
+ kind = None
649
+ else:
650
+ kind = info.get_attr('IFLA_INFO_KIND')
651
+
652
+ if kind is not None:
653
+ continue
654
+
655
+ ifs_link = {
656
+ 'name': name,
657
+ 'identify': {
658
+ },
659
+ }
660
+
661
+ for attr, lookup in IDENTIFY_LOOKUPS.items():
662
+ value = lookup(netns, ipr_link)
663
+ if value is not None:
664
+ ifs_link['identify'][attr] = value
665
+
666
+ ifs_links.append(ifs_link)
667
+
668
+ if ifs_links:
669
+ return {**{'interfaces': ifs_links}}
670
+ else:
671
+ return None
672
+
621
673
  def show(self, showall=False):
622
674
  if showall:
623
675
  defaults = deepcopy(Parser._default_ifstates)
@@ -625,7 +677,7 @@ class IfState():
625
677
  defaults = {}
626
678
 
627
679
  ipaddr_ignore = []
628
- for ip in Parser._default_ifstates['ignore']['ipaddr_builtin']:
680
+ for ip in Parser._default_ifstates['parameters']['ignore']['ipaddr_builtin']:
629
681
  ipaddr_ignore.append(ip_network(ip))
630
682
 
631
683
  root_config = self._show_netns(self.root_netns, showall, ipaddr_ignore)
@@ -645,7 +697,7 @@ class IfState():
645
697
  for ipr_link in netns.ipr.get_links():
646
698
  name = ipr_link.get_attr('IFLA_IFNAME')
647
699
  # skip links on ignore list
648
- if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['ignore']['ifname_builtin']):
700
+ if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
649
701
  ifs_link = {
650
702
  'name': name,
651
703
  'addresses': [],
@@ -687,15 +739,13 @@ class IfState():
687
739
  addr = ipr_link.get_attr('IFLA_ADDRESS')
688
740
  if not addr is None:
689
741
  ifs_link['link']['address'] = addr
690
- permaddr = netns.ipr.get_permaddr(name)
691
- if not permaddr is None:
692
- if addr is None:
693
- ifs_link['link']['addr'] = permaddr
694
- elif addr != permaddr:
695
- ifs_link['link']['permaddr'] = permaddr
696
- businfo = netns.ipr.get_businfo(name)
697
- if not businfo is None:
698
- ifs_link['link']['businfo'] = businfo
742
+
743
+ # add identify section for physical links
744
+ ifs_link['identify'] = {}
745
+ for attr, func in IDENTIFY_LOOKUPS.items():
746
+ value = func(netns, ipr_link)
747
+ if value:
748
+ ifs_link['identify'][attr] = value
699
749
 
700
750
  # add device group if not 0
701
751
  group = ipr_link.get_attr('IFLA_GROUP')
@@ -735,8 +785,8 @@ class IfState():
735
785
  ifs_links.append(ifs_link)
736
786
 
737
787
  routing = {
738
- 'routes': Tables(netns).show_routes(Parser._default_ifstates['ignore']['routes_builtin']),
739
- 'rules': Rules(netns).show_rules(Parser._default_ifstates['ignore']['rules_builtin']),
788
+ 'routes': Tables(netns).show_routes(Parser._default_ifstates['parameters']['ignore']['routes_builtin']),
789
+ 'rules': Rules(netns).show_rules(Parser._default_ifstates['parameters']['ignore']['rules_builtin']),
740
790
  }
741
791
 
742
792
  return {**{'interfaces': ifs_links, 'routing': routing}}
@@ -0,0 +1,195 @@
1
+ from libifstate.util import get_netns_run_dir, dump_yaml_file, slurp_yaml_file, RUN_BASE_DIR
2
+ from libifstate.util import logger
3
+
4
+ import logging
5
+ from pathlib import Path
6
+ import os
7
+ import pkgutil
8
+ from shlex import quote
9
+ import shutil
10
+ from string import Template
11
+ import subprocess
12
+ import tempfile
13
+
14
+
15
+ HOOK_DIR = '/etc/ifstate/hooks'
16
+ HOOK_WRAPPER = Template(pkgutil.get_data("libifstate", "../hooks/wrapper.sh").decode("utf-8"))
17
+
18
+ RC_OK = 0
19
+ RC_ERROR = 1
20
+ RC_STARTED = 2
21
+ # it is the same value, but there are two constants to make it more convinient
22
+ RC_STOPPED = RC_STARTED
23
+ RC_CHANGED = 3
24
+
25
+ def _open_perm(path, flags):
26
+ return os.open(path, flags, 0o600)
27
+
28
+ class Hook():
29
+ def __init__(self, name, script, provides=[], after=[]):
30
+ self.name = name
31
+
32
+ if script[0] == '/':
33
+ self.script = Path(script).as_posix()
34
+ else:
35
+ self.script = Path(HOOK_DIR, script).as_posix()
36
+
37
+ self.provides = provides
38
+ self.after = after
39
+
40
+ def start(self, link, args, run_dir, do_apply):
41
+ wrapper_fn = f"{run_dir}/wrapper.sh"
42
+
43
+ with open(wrapper_fn, "w", opener=_open_perm) as fh:
44
+ template_vars = {
45
+ 'verbose': 1 if logger.getEffectiveLevel() == logging.DEBUG else '',
46
+ 'script': self.script,
47
+ 'ifname': link.settings.get('ifname'),
48
+ 'index': link.idx,
49
+ 'netns': link.netns.netns or '',
50
+ 'vrf': '',
51
+ 'rundir': run_dir,
52
+ 'rc_ok': RC_OK,
53
+ 'rc_error': RC_ERROR,
54
+ 'rc_started': RC_STARTED,
55
+ 'rc_stopped': RC_STOPPED,
56
+ 'rc_changed': RC_CHANGED,
57
+ }
58
+
59
+ if link.get_if_attr('IFLA_INFO_SLAVE_KIND') == 'vrf':
60
+ template_vars['vrf'] = link.settings.get('master')
61
+
62
+ args_list = []
63
+ for k, v in args.items():
64
+ args_list.append(f'export IFS_ARG_{k.upper()}={quote(v)}')
65
+ template_vars['args'] = "\n".join(args_list)
66
+
67
+ try:
68
+ fh.write(HOOK_WRAPPER.substitute(template_vars))
69
+ except KeyError as ex:
70
+ logger.error("Failed to prepare wrapper for hook {}: variable {} unknown".format(self.name, str(ex)))
71
+ return
72
+ except ValueError as ex:
73
+ logger.error("Failed to prepare wrapper for hook {}: {}".format(self.name, str(ex)))
74
+ return
75
+
76
+ try:
77
+ if do_apply:
78
+ subprocess.run(['/bin/sh', wrapper_fn, "start"], timeout=3, check=True)
79
+ else:
80
+ subprocess.run(['/bin/sh', wrapper_fn, "check-start"], timeout=3, check=True)
81
+
82
+ return RC_OK
83
+ except (FileNotFoundError, PermissionError) as ex:
84
+ logger.error("Failed executing hook {}: {}".format(self.name, str(ex)))
85
+ return RC_ERROR
86
+ except subprocess.TimeoutExpired as ex:
87
+ logger.error("Running hook {} has timed out.".format(self.name))
88
+ return RC_ERROR
89
+ except subprocess.CalledProcessError as ex:
90
+ if ex.returncode < 0:
91
+ logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
92
+ return RC_ERROR
93
+
94
+ return ex.returncode
95
+
96
+ @staticmethod
97
+ def stop(link, hook_name, run_dir, do_apply):
98
+ wrapper_fn = f"{run_dir}/wrapper.sh"
99
+
100
+ try:
101
+ if do_apply:
102
+ subprocess.run(['/bin/sh', wrapper_fn, "stop"], timeout=3, check=True)
103
+ else:
104
+ subprocess.run(['/bin/sh', wrapper_fn, "check-stop"], timeout=3, check=True)
105
+
106
+ return RC_OK
107
+ except (FileNotFoundError, PermissionError) as ex:
108
+ logger.error("Failed executing hook {}: {}".format(hook_name, str(ex)))
109
+ return RC_ERROR
110
+ except subprocess.TimeoutExpired as ex:
111
+ logger.error("Running hook {} has timed out.".format(hook_name))
112
+ return RC_ERROR
113
+ except subprocess.CalledProcessError as ex:
114
+ if ex.returncode < 0:
115
+ logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
116
+ return RC_ERROR
117
+
118
+ assert(run_dir.startswith(RUN_BASE_DIR))
119
+
120
+ try:
121
+ shutil.rmtree(run_dir)
122
+ except FileNotFoundError:
123
+ pass
124
+ except OSError as err:
125
+ logger.error("Failed cleanup hook rundir {}: {}".format(run_dir, str(err)))
126
+
127
+ return ex.returncode
128
+
129
+ class Hooks():
130
+ def __init__(self, ifstate):
131
+ self.hooks = {}
132
+ for hook, opts in ifstate.items():
133
+ if 'script' in opts:
134
+ self.hooks[hook] = Hook(hook, **opts)
135
+ else:
136
+ self.hooks[hook] = Hook(hook, script=hook, **opts)
137
+
138
+ def apply(self, link, do_apply):
139
+ run_dir = get_netns_run_dir('hooks', link.netns, str(link.idx))
140
+
141
+ state_fn = f"{run_dir}/state"
142
+ old_state = slurp_yaml_file(state_fn, default=[])
143
+ run_state = []
144
+
145
+ # Stop any running hooks which should not run any more or have other
146
+ # parameters - hooks are identified by all of their settings. Keep
147
+ # the rundir for any hook already running.
148
+ for entry in old_state:
149
+ if not entry.get('hook') in link.hooks:
150
+ rc = Hook.stop(link, entry["hook"]["name"], entry["rundir"], do_apply)
151
+ if rc == RC_OK:
152
+ pass
153
+ elif rc == RC_STOPPED:
154
+ logger.log_del('hooks', '- {}'.format(entry["hook"]["name"]))
155
+ else:
156
+ run_state.append(entry)
157
+ logger.log_err('hooks', '! {}'.format(entry["hook"]["name"]))
158
+ else:
159
+ hook = next((hook for hook in link.hooks if hook == entry["hook"]), None)
160
+ # tepmorary keep mapping between running and configured hook dict
161
+ hook["__rundir"] = entry["rundir"]
162
+
163
+ try:
164
+ if not link.hooks:
165
+ return
166
+
167
+ for hook in link.hooks:
168
+ if not hook["name"] in self.hooks:
169
+ logger.warning("Hook {} for {} is unknown!".format(link.settings.get('ifname'), hook))
170
+ continue
171
+
172
+ hook_run_dir = hook.get("__rundir")
173
+ # hook is not running, yet
174
+ if hook_run_dir is None:
175
+ hook_run_dir = tempfile.mkdtemp(prefix="hook_", dir=run_dir)
176
+ # hook was already running
177
+ else:
178
+ del(hook["__rundir"])
179
+ rc = self.hooks[hook["name"]].start(link, hook.get('args', {}), hook_run_dir, do_apply)
180
+
181
+ if rc == RC_OK:
182
+ logger.log_ok('hooks', '= {}'.format(hook["name"]))
183
+ elif rc == RC_STARTED:
184
+ logger.log_add('hooks', '+ {}'.format(hook["name"]))
185
+ elif rc == RC_CHANGED:
186
+ logger.log_change('hooks', '~ {}'.format(hook["name"]))
187
+ else:
188
+ logger.log_err('hooks', '! {}'.format(hook["name"]))
189
+
190
+ run_state.append({
191
+ "hook": hook,
192
+ "rundir": hook_run_dir,
193
+ })
194
+ finally:
195
+ dump_yaml_file(state_fn, run_state)
@@ -1,5 +1,4 @@
1
1
  import libifstate.link.base
2
- import libifstate.link.dsa
3
2
  import libifstate.link.physical
4
3
  import libifstate.link.tun
5
4
  import libifstate.link.veth