Replace dumper with an actual YAML emitter

This commit is contained in:
Pim van Pelt
2022-04-03 21:37:48 +00:00
parent 53a7935168
commit a4a91d1f5e
3 changed files with 150 additions and 96 deletions

View File

@ -110,15 +110,20 @@ fully valid! For a full write up of the syntax and semantic validation, see
### vppcfg dump
The purpose of the **dump** module is to connect to the VPP dataplane, and retrieve its
state, printing most information found in the INFO logs. Although it does contact VPP, it
state, emitting the configuration as a YAML file. Although it does contact VPP, it
will perform *readonly* operations and never manipulate state in the dataplane, so it
should be safe to run.
There are no flags to the dump command. It will return 0 if the connection to VPP was
established and its state successfully dumped to the logs, and non-zero otherwise.
If the flag `-o/--output` is given, the resulting YAML is written to that filename, but
if it is not given, the output will be written to stdout. It will return 0 if the connection
to VPP was established and its state successfully dumped to the logs, and non-zero otherwise.
Use of the **dump** command can be done even if the dataplane was configured outside of
`vppcfg`, in other words, the following can be done:
`vppcfg`, although some non-supported scenarios (for example, sub-interfaces on loopbacks)
will be flagged as warnings. If warnings or errors are reported, the YAML file cannot be
assumed safe. Conversely, if no warnings/errors are logged, the resulting YAML should be
a good representation of the dataplane state, as far as `vppcfg` is concerned. A good way
to confirm that is to subsequently run the output file back into `vppcfg check`.
```
$ vppcfg dump || echo "Not a hoopy frood"
@ -132,23 +137,47 @@ DBGvpp# set interface ip address GigabitEthernet3/0/0.100 2001:db8:1::1/64
DBGvpp# create bridge-domain 10
DBGvpp# set interface l2 bridge HundredGigabitEthernet12/0/0 10
$ vppcfg dump
$ vppcfg dump -o vpp.yaml
[INFO ] vppcfg.vppapi.connect: VPP version is 22.06-rc0~320-g8f60318ac
[INFO ] vppcfg.vppapi.dump_phys: GigabitEthernet3/0/0 idx=1
[INFO ] vppcfg.vppapi.dump_phys: GigabitEthernet3/0/1 idx=2
[INFO ] vppcfg.vppapi.dump_phys: HundredGigabitEthernet12/0/0 idx=3
[INFO ] vppcfg.vppapi.dump_phys: HundredGigabitEthernet12/0/1 idx=4
[INFO ] vppcfg.vppapi.dump_interfaces: local0 idx=0 type=local mac=00:00:00:00:00:00 mtu=0 flags=0
[INFO ] vppcfg.vppapi.dump_interfaces: GigabitEthernet3/0/0 idx=1 type=dpdk mac=00:25:90:0c:05:00 mtu=9000 flags=2
[INFO ] vppcfg.vppapi.dump_interfaces: GigabitEthernet3/0/1 idx=2 type=dpdk mac=00:25:90:0c:05:01 mtu=9000 flags=2
[INFO ] vppcfg.vppapi.dump_interfaces: HundredGigabitEthernet12/0/0 idx=3 type=dpdk mac=b4:96:91:b3:b1:10 mtu=8996 flags=0
[INFO ] vppcfg.vppapi.dump_interfaces: HundredGigabitEthernet12/0/1 idx=4 type=dpdk mac=b4:96:91:b3:b1:11 mtu=8996 flags=0
[INFO ] vppcfg.vppapi.dump_interfaces: GigabitEthernet3/0/0.100 idx=5 type=dpdk mac=00:00:00:00:00:00 mtu=0 flags=2
[INFO ] vppcfg.vppapi.dump_interfaces: Encapsulation: dot1q 100 exact-match
[INFO ] vppcfg.vppapi.dump_interfaces: L3: 2001:db8:1::1/64
[INFO ] vppcfg.vppapi.dump_subints: GigabitEthernet3/0/0.100 tags=1 idx=5 encap=dot1q 100 exact-match
[INFO ] vppcfg.vppapi.dump_bridgedomains: BridgeDomain10
[INFO ] vppcfg.vppapi.dump_bridgedomains: Members: HundredGigabitEthernet12/0/0
[INFO ] vppcfg.vppapi.write: Wrote YAML config to vpp.yaml
$ cat vpp.yaml
bondethernets: {}
bridgedomains:
bd10:
description: ''
interfaces:
- HundredGigabitEthernet12/0/0
mtu: 8996
interfaces:
GigabitEthernet3/0/0:
description: ''
mtu: 9000
sub-interfaces:
100:
addresses:
- 2001:db8:1::1/64
description: ''
encapsulation:
dot1q: 100
exact-match: true
mtu: 9000
GigabitEthernet3/0/1:
description: ''
mtu: 9000
HundredGigabitEthernet12/0/0:
description: ''
mtu: 8996
HundredGigabitEthernet12/0/1:
description: ''
mtu: 8996
loopbacks: {}
vxlan_tunnels: {}
$ vppcfg check -c vpp.yaml
[INFO ] root.main: Loading configfile vpp.yaml
[INFO ] vppcfg.config.valid_config: Configuration validated successfully
[INFO ] root.main: Configuration is valid
```
### vppcfg plan

View File

@ -5,9 +5,11 @@ interface metadata.
from vpp_papi import VPPApiClient
import os
import sys
import fnmatch
import logging
import socket
import yaml
class VPPApi():
def __init__(self, address='/run/vpp/api.sock', clientname='vppcfg'):
@ -211,18 +213,6 @@ class VPPApi():
self.cache_read = True
return self.cache_read
def get_encapsulation(self, iface):
""" Return a string with the encapsulation of a subint """
encap = "dot1q"
if iface.sub_if_flags & 8:
encap = "dot1ad"
encap += " %d" % iface.sub_outer_vlan_id
if iface.sub_inner_vlan_id> 0:
encap += " inner-dot1q %d" % iface.sub_inner_vlan_id
if iface.sub_if_flags & 16:
encap += " exact-match"
return encap
def phys_exist(self, ifname_list):
""" Return True if all interfaces in the `ifname_list` exist as physical interface names
in VPP. Return False otherwise."""
@ -271,73 +261,107 @@ class VPPApiDumper(VPPApi):
def __init__(self, address='/run/vpp/api.sock', clientname='vppcfg'):
VPPApi.__init__(self, address, clientname)
def dump(self):
self.dump_phys()
self.dump_interfaces()
self.dump_subints()
self.dump_bridgedomains()
def write(self, outfile):
if outfile and outfile == '-':
fh = sys.stdout
outfile = "(stdout)"
else:
fh = open(outfile, 'w')
def dump_phys(self):
phys = self.get_phys()
for ifname in phys:
iface = self.cache['interface_names'][ifname]
self.logger.info("%s idx=%d" % (iface.interface_name, iface.sw_if_index))
config = self.cache_to_config()
def dump_subints(self):
subints = self.get_qinx_interfaces()
for ifname in subints:
iface = self.cache['interface_names'][ifname]
self.logger.info("%s tags=2 idx=%d encap=%s" % (iface.interface_name, iface.sw_if_index, self.get_encapsulation(iface)))
print(yaml.dump(config), file=fh)
subints = self.get_dot1x_interfaces()
for ifname in subints:
iface = self.cache['interface_names'][ifname]
self.logger.info("%s tags=1 idx=%d encap=%s" % (iface.interface_name, iface.sw_if_index, self.get_encapsulation(iface)))
if fh is not sys.stdout:
fh.close()
self.logger.info("Wrote YAML config to %s" % (outfile))
def dump_bridgedomains(self):
for bd_id, bridge in self.cache['bridgedomains'].items():
self.logger.info("BridgeDomain%d" % (bridge.bd_id))
if bridge.bvi_sw_if_index > 0 and bridge.bvi_sw_if_index < 2**32-1 :
self.logger.info(" BVI: " + self.cache['interfaces'][bridge.bvi_sw_if_index].interface_name)
def cache_to_config(self):
config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {} }
for idx, iface in self.cache['bondethernets'].items():
bond = {"description": ""}
if iface.sw_if_index in self.cache['bondethernet_members']:
bond['interfaces'] = [self.cache['interfaces'][x].interface_name for x in self.cache['bondethernet_members'][iface.sw_if_index]]
config['bondethernets'][iface.interface_name] = bond
members = []
for member in bridge.sw_if_details:
members.append(self.cache['interfaces'][member.sw_if_index].interface_name)
if len(members) > 0:
self.logger.info(" Members: " + ' '.join(members))
def dump_interfaces(self):
for numtags in [ 0, 1, 2 ]:
for idx, iface in self.cache['interfaces'].items():
self.logger.info("%s idx=%d type=%s mac=%s mtu=%d flags=%d" % (iface.interface_name,
iface.sw_if_index, iface.interface_dev_type, iface.l2_address,
iface.mtu[0], iface.flags))
if iface.interface_dev_type=='bond' and iface.sub_id == 0 and iface.sw_if_index in self.cache['bondethernet_members']:
members = [self.cache['interfaces'][x].interface_name for x in self.cache['bondethernet_members'][iface.sw_if_index]]
self.logger.info(" Members: %s" % ' '.join(members))
if iface.interface_dev_type=="VXLAN":
vxlan = self.cache['vxlan_tunnels'][iface.sw_if_index]
self.logger.info(" VXLAN: %s:%d -> %s:%d VNI %d" % (vxlan.src_address, vxlan.src_port,
vxlan.dst_address, vxlan.dst_port, vxlan.vni))
if iface.sub_number_of_tags != numtags:
continue
if iface.interface_dev_type=='Loopback':
if iface.sub_id > 0:
self.logger.info(" Encapsulation: %s" % (self.get_encapsulation(iface)))
self.logger.warning("Refusing to export sub-interfaces of loopback devices (%s)" % iface.interface_name)
continue
loop = {"description": ""}
loop['mtu'] = iface.mtu[0]
if iface.sw_if_index in self.cache['lcps']:
lcp = self.cache['lcps'][iface.sw_if_index]
tap_name = self.cache['interfaces'][lcp.host_sw_if_index].interface_name
tap_idx = lcp.host_sw_if_index
self.logger.info(" LCP: %s (tap=%s idx=%d netns=%s)" % (lcp.host_if_name, tap_name, tap_idx, lcp.namespace))
loop['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name
if iface.sw_if_index in self.cache['interface_addresses']:
if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0:
self.logger.info(" L3: %s" % ' '.join(self.cache['interface_addresses'][iface.sw_if_index]))
loop['addresses'] = self.cache['interface_addresses'][iface.sw_if_index]
config['loopbacks'][iface.interface_name] = loop
elif iface.interface_dev_type in ['bond', 'VXLAN', 'dpdk']:
i = {"description": "" }
if iface.sw_if_index in self.cache['lcps']:
i['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name
if iface.sw_if_index in self.cache['interface_addresses']:
if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0:
i['addresses'] = self.cache['interface_addresses'][iface.sw_if_index]
if iface.sw_if_index in self.cache['l2xcs']:
l2xc = self.cache['l2xcs'][iface.sw_if_index]
self.logger.info(" L2XC: %s" % self.cache['interfaces'][l2xc.tx_sw_if_index].interface_name)
i['l2xc'] = self.cache['interfaces'][l2xc.tx_sw_if_index].interface_name
i['mtu'] = iface.mtu[0]
if iface.sub_number_of_tags == 0:
config['interfaces'][iface.interface_name] = i
continue
for bd_id, bridge in self.cache['bridgedomains'].items():
if bridge.bvi_sw_if_index == iface.sw_if_index:
self.logger.info(" BVI: BridgeDomain%d" % (bd_id))
encap = {}
if iface.sub_if_flags&8:
encap['dot1ad'] = iface.sub_outer_vlan_id
else:
encap['dot1q'] = iface.sub_outer_vlan_id
if iface.sub_inner_vlan_id > 0:
encap['inner-dot1q'] = iface.sub_inner_vlan_id
encap['exact-match'] = bool(iface.sub_if_flags&16)
i['encapsulation'] = encap
pass
sup_iface = self.cache['interfaces'][iface.sup_sw_if_index]
if iface.mtu[0] > 0:
i['mtu'] = iface.mtu[0]
else:
i['mtu'] = sup_iface.mtu[0]
if not 'sub-interfaces' in config['interfaces'][sup_iface.interface_name]:
config['interfaces'][sup_iface.interface_name]['sub-interfaces'] = {}
config['interfaces'][sup_iface.interface_name]['sub-interfaces'][iface.sub_id] = i
for idx, iface in self.cache['vxlan_tunnels'].items():
vpp_iface = self.cache['interfaces'][iface.sw_if_index]
vxlan = { "description": "",
"vni": int(iface.vni),
"local": str(iface.src_address),
"remote": str(iface.dst_address) }
config['vxlan_tunnels'][vpp_iface.interface_name] = vxlan
for idx, iface in self.cache['bridgedomains'].items():
# self.logger.info("%d: %s" % (idx, iface))
bridge_name = "bd%d" % idx
mtu = 1500
bridge = {"description": ""}
bvi = None
if iface.bvi_sw_if_index != 2**32-1:
bvi = self.cache['interfaces'][iface.bvi_sw_if_index]
mtu = bvi.mtu[0]
bridge['bvi'] = bvi.interface_name
members = []
for member in iface.sw_if_details:
if bvi and bvi.interface_name == self.cache['interfaces'][member.sw_if_index].interface_name == bvi.interface_name:
continue
members.append(self.cache['interfaces'][member.sw_if_index].interface_name)
mtu = self.cache['interfaces'][member.sw_if_index].mtu[0]
if len(members) > 0:
bridge['interfaces'] = members
bridge['mtu'] = mtu
config['bridgedomains'][bridge_name] = bridge
return config

3
vppcfg
View File

@ -40,6 +40,7 @@ def main():
check_p.add_argument('-c', '--config', dest='config', required=True, type=str, help="""YAML configuration file for vppcfg""")
dump_p = subparsers.add_parser('dump', help="dump current running VPP configuration (VPP readonly)")
dump_p.add_argument('-o', '--output', dest='outfile', required=False, default='-', type=str, help="""Output file for YAML config, default stdout""")
plan_p = subparsers.add_parser('plan', help="plan changes from current VPP dataplane to target config (VPP readonly)")
plan_p.add_argument('-s', '--schema', dest='schema', type=str, help="""YAML schema validation file, default to use built-in""")
@ -68,7 +69,7 @@ def main():
if not d.readconfig():
logging.error("Could not retrieve config from VPP")
sys.exit(-7)
d.dump()
d.write(args.outfile)
sys.exit(0)
try: