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 +50 -0
- ifstate/ifstate.py +7 -2
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc1.dist-info}/METADATA +1 -1
- ifstate-2.0.0rc1.dist-info/RECORD +38 -0
- libifstate/__init__.py +88 -38
- libifstate/hook/__init__.py +195 -0
- libifstate/link/__init__.py +0 -1
- libifstate/link/base.py +23 -31
- libifstate/link/physical.py +2 -2
- libifstate/link/tun.py +2 -2
- libifstate/link/veth.py +2 -2
- libifstate/netns/__init__.py +40 -5
- libifstate/parser/base.py +107 -84
- libifstate/routing/__init__.py +62 -16
- libifstate/util.py +146 -147
- libifstate/wireguard/__init__.py +16 -16
- schema/2/ifstate.conf.schema.json +4398 -0
- ifstate-1.13.7.dist-info/RECORD +0 -37
- libifstate/link/dsa.py +0 -10
- schema/ifstate.conf.schema.json +0 -4259
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc1.dist-info}/WHEEL +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc1.dist-info}/entry_points.txt +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc1.dist-info}/top_level.txt +0 -0
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
|
-
|
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)
|
@@ -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__ = "
|
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
|
138
|
-
if '
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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)
|