* First draft of Ubuntu 20.04 Vagrantfile and scripts to install 2021-Mar version of open source P4 development tools. * Add more tracing output of what files have been installed at each step * Don't do behavioral-model install_deps.sh before installing PI This is an experiment to see if the end result will be able to run tutorials basic exercise using Python3 only on an Ubuntu 20.04 system. Just before this commit, `vagrant up` resulted in a system that failed to run the basic exercise, because python3 failed to import google.grpc (if I recall correctly -- it may have been a different google.<something> Python3 module name). * Add missing patch file * Fix copy and paste mistake * Add missing patch file * Change how protobuf Python3 module files are installed * Correct a few desktop icon file names, and add clean.sh script * Enhance clean.sh script, and add README for manual steps in creating a VM * Changes to try to always use Python3, never Python2, in tutorials * Update README steps for preparing a VM * More additions to README on steps to create a single file VM image * Add empty-disk-block zeroing to clean.sh script * Also install PTF * Update versions of P4 dev tool source code to 2021-Apr-05 This includes a change to p4lang/PI that allows P4Runtime API clients to send the shortest byte sequences necessary to encode integer values, which I want for a PTF test that I have recently created. * Update README for 2021-Apr-05 version of VM image * Resolve Python 3 compatibility issues Most of the Python 2 to 3 code translation changes were automated with the 2to3 tool. Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org> * Update commit SHAs for 4 p4lang repos to latest as of 2021-May-04 * Update Ubuntu 20.04 README.md for how I created 2021-May-04 version of VM * mycontroller: Use Python 3 shebang line Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org> * Update Ubuntu 20.04 README.md for how I created 2021-Jun-01 version of VM * Update commit SHAs for 4 p4lang repos to latest as of 2021-Jul-07 * Update Ubuntu 20.04 README.md for how I created 2021-Jul-07 version of VM * Update commit SHAs for 4 p4lang repos to latest as of 2021-Aug-01 * Update Ubuntu 20.04 README.md for how I created 2021-Aug-01 version of VM * Update commit SHAs for 4 p4lang repos to latest as of 2021-Sep-07 * Update Ubuntu 20.04 README.md for how I created 2021-Sep-07 version of VM Co-authored-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
234 lines
8.6 KiB
Python
Executable File
234 lines
8.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2017-present Open Networking Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
from . import bmv2
|
|
from . import helper
|
|
|
|
|
|
def error(msg):
|
|
print(' - ERROR! ' + msg, file=sys.stderr)
|
|
|
|
def info(msg):
|
|
print(' - ' + msg, file=sys.stdout)
|
|
|
|
|
|
class ConfException(Exception):
|
|
pass
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='P4Runtime Simple Controller')
|
|
|
|
parser.add_argument('-a', '--p4runtime-server-addr',
|
|
help='address and port of the switch\'s P4Runtime server (e.g. 192.168.0.1:50051)',
|
|
type=str, action="store", required=True)
|
|
parser.add_argument('-d', '--device-id',
|
|
help='Internal device ID to use in P4Runtime messages',
|
|
type=int, action="store", required=True)
|
|
parser.add_argument('-p', '--proto-dump-file',
|
|
help='path to file where to dump protobuf messages sent to the switch',
|
|
type=str, action="store", required=True)
|
|
parser.add_argument("-c", '--runtime-conf-file',
|
|
help="path to input runtime configuration file (JSON)",
|
|
type=str, action="store", required=True)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not os.path.exists(args.runtime_conf_file):
|
|
parser.error("File %s does not exist!" % args.runtime_conf_file)
|
|
workdir = os.path.dirname(os.path.abspath(args.runtime_conf_file))
|
|
with open(args.runtime_conf_file, 'r') as sw_conf_file:
|
|
program_switch(addr=args.p4runtime_server_addr,
|
|
device_id=args.device_id,
|
|
sw_conf_file=sw_conf_file,
|
|
workdir=workdir,
|
|
proto_dump_fpath=args.proto_dump_file)
|
|
|
|
|
|
def check_switch_conf(sw_conf, workdir):
|
|
required_keys = ["p4info"]
|
|
files_to_check = ["p4info"]
|
|
target_choices = ["bmv2"]
|
|
|
|
if "target" not in sw_conf:
|
|
raise ConfException("missing key 'target'")
|
|
target = sw_conf['target']
|
|
if target not in target_choices:
|
|
raise ConfException("unknown target '%s'" % target)
|
|
|
|
if target == 'bmv2':
|
|
required_keys.append("bmv2_json")
|
|
files_to_check.append("bmv2_json")
|
|
|
|
for conf_key in required_keys:
|
|
if conf_key not in sw_conf or len(sw_conf[conf_key]) == 0:
|
|
raise ConfException("missing key '%s' or empty value" % conf_key)
|
|
|
|
for conf_key in files_to_check:
|
|
real_path = os.path.join(workdir, sw_conf[conf_key])
|
|
if not os.path.exists(real_path):
|
|
raise ConfException("file does not exist %s" % real_path)
|
|
|
|
|
|
def program_switch(addr, device_id, sw_conf_file, workdir, proto_dump_fpath):
|
|
sw_conf = json_load_byteified(sw_conf_file)
|
|
try:
|
|
check_switch_conf(sw_conf=sw_conf, workdir=workdir)
|
|
except ConfException as e:
|
|
error("While parsing input runtime configuration: %s" % str(e))
|
|
return
|
|
|
|
info('Using P4Info file %s...' % sw_conf['p4info'])
|
|
p4info_fpath = os.path.join(workdir, sw_conf['p4info'])
|
|
p4info_helper = helper.P4InfoHelper(p4info_fpath)
|
|
|
|
target = sw_conf['target']
|
|
|
|
info("Connecting to P4Runtime server on %s (%s)..." % (addr, target))
|
|
|
|
if target == "bmv2":
|
|
sw = bmv2.Bmv2SwitchConnection(address=addr, device_id=device_id,
|
|
proto_dump_file=proto_dump_fpath)
|
|
else:
|
|
raise Exception("Don't know how to connect to target %s" % target)
|
|
|
|
try:
|
|
sw.MasterArbitrationUpdate()
|
|
|
|
if target == "bmv2":
|
|
info("Setting pipeline config (%s)..." % sw_conf['bmv2_json'])
|
|
bmv2_json_fpath = os.path.join(workdir, sw_conf['bmv2_json'])
|
|
sw.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
|
|
bmv2_json_file_path=bmv2_json_fpath)
|
|
else:
|
|
raise Exception("Should not be here")
|
|
|
|
if 'table_entries' in sw_conf:
|
|
table_entries = sw_conf['table_entries']
|
|
info("Inserting %d table entries..." % len(table_entries))
|
|
for entry in table_entries:
|
|
info(tableEntryToString(entry))
|
|
insertTableEntry(sw, entry, p4info_helper)
|
|
|
|
if 'multicast_group_entries' in sw_conf:
|
|
group_entries = sw_conf['multicast_group_entries']
|
|
info("Inserting %d group entries..." % len(group_entries))
|
|
for entry in group_entries:
|
|
info(groupEntryToString(entry))
|
|
insertMulticastGroupEntry(sw, entry, p4info_helper)
|
|
|
|
if 'clone_session_entries' in sw_conf:
|
|
clone_entries = sw_conf['clone_session_entries']
|
|
info("Inserting %d clone entries..." % len(clone_entries))
|
|
for entry in clone_entries:
|
|
info(cloneEntryToString(entry))
|
|
insertCloneGroupEntry(sw, entry, p4info_helper)
|
|
|
|
finally:
|
|
sw.shutdown()
|
|
|
|
|
|
def insertTableEntry(sw, flow, p4info_helper):
|
|
table_name = flow['table']
|
|
match_fields = flow.get('match') # None if not found
|
|
action_name = flow['action_name']
|
|
default_action = flow.get('default_action') # None if not found
|
|
action_params = flow['action_params']
|
|
priority = flow.get('priority') # None if not found
|
|
|
|
table_entry = p4info_helper.buildTableEntry(
|
|
table_name=table_name,
|
|
match_fields=match_fields,
|
|
default_action=default_action,
|
|
action_name=action_name,
|
|
action_params=action_params,
|
|
priority=priority)
|
|
|
|
sw.WriteTableEntry(table_entry)
|
|
|
|
|
|
def json_load_byteified(file_handle):
|
|
return json.load(file_handle)
|
|
|
|
|
|
def _byteify(data, ignore_dicts=False):
|
|
# if this is a unicode string, return its string representation
|
|
if isinstance(data, str):
|
|
return data.encode('utf-8')
|
|
# if this is a list of values, return list of byteified values
|
|
if isinstance(data, list):
|
|
return [_byteify(item, ignore_dicts=True) for item in data]
|
|
# if this is a dictionary, return dictionary of byteified keys and values
|
|
# but only if we haven't already byteified it
|
|
if isinstance(data, dict) and not ignore_dicts:
|
|
return {
|
|
_byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
|
|
for key, value in data.items()
|
|
}
|
|
# if it's anything else, return it in its original form
|
|
return data
|
|
|
|
|
|
def tableEntryToString(flow):
|
|
if 'match' in flow:
|
|
match_str = ['%s=%s' % (match_name, str(flow['match'][match_name])) for match_name in
|
|
flow['match']]
|
|
match_str = ', '.join(match_str)
|
|
elif 'default_action' in flow and flow['default_action']:
|
|
match_str = '(default action)'
|
|
else:
|
|
match_str = '(any)'
|
|
params = ['%s=%s' % (param_name, str(flow['action_params'][param_name])) for param_name in
|
|
flow['action_params']]
|
|
params = ', '.join(params)
|
|
return "%s: %s => %s(%s)" % (
|
|
flow['table'], match_str, flow['action_name'], params)
|
|
|
|
|
|
def groupEntryToString(rule):
|
|
group_id = rule["multicast_group_id"]
|
|
replicas = ['%d' % replica["egress_port"] for replica in rule['replicas']]
|
|
ports_str = ', '.join(replicas)
|
|
return 'Group {0} => ({1})'.format(group_id, ports_str)
|
|
|
|
def cloneEntryToString(rule):
|
|
clone_id = rule["clone_session_id"]
|
|
if "packet_length_bytes" in rule:
|
|
packet_length_bytes = str(rule["packet_length_bytes"])+"B"
|
|
else:
|
|
packet_length_bytes = "NO_TRUNCATION"
|
|
replicas = ['%d' % replica["egress_port"] for replica in rule['replicas']]
|
|
ports_str = ', '.join(replicas)
|
|
return 'Clone Session {0} => ({1}) ({2})'.format(clone_id, ports_str, packet_length_bytes)
|
|
|
|
def insertMulticastGroupEntry(sw, rule, p4info_helper):
|
|
mc_entry = p4info_helper.buildMulticastGroupEntry(rule["multicast_group_id"], rule['replicas'])
|
|
sw.WritePREEntry(mc_entry)
|
|
|
|
def insertCloneGroupEntry(sw, rule, p4info_helper):
|
|
clone_entry = p4info_helper.buildCloneSessionEntry(rule['clone_session_id'], rule['replicas'],
|
|
rule.get('packet_length_bytes', 0))
|
|
sw.WritePREEntry(clone_entry)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|