Replace dumper with an actual YAML emitter
This commit is contained in:
@ -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
|
||||
|
170
vpp/vppapi.py
170
vpp/vppapi.py
@ -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
|
||||
|
||||
for numtags in [ 0, 1, 2 ]:
|
||||
for idx, iface in self.cache['interfaces'].items():
|
||||
if iface.sub_number_of_tags != numtags:
|
||||
continue
|
||||
|
||||
if iface.interface_dev_type=='Loopback':
|
||||
if iface.sub_id > 0:
|
||||
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']:
|
||||
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:
|
||||
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]
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 bridge.sw_if_details:
|
||||
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:
|
||||
self.logger.info(" Members: " + ' '.join(members))
|
||||
bridge['interfaces'] = members
|
||||
bridge['mtu'] = mtu
|
||||
config['bridgedomains'][bridge_name] = bridge
|
||||
|
||||
def dump_interfaces(self):
|
||||
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_id > 0:
|
||||
self.logger.info(" Encapsulation: %s" % (self.get_encapsulation(iface)))
|
||||
|
||||
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))
|
||||
|
||||
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]))
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
|
||||
pass
|
||||
return config
|
||||
|
3
vppcfg
3
vppcfg
@ -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:
|
||||
|
Reference in New Issue
Block a user