p4tutorials-turkce/utils/p4runtime_lib/simple_controller.py
Andy Fingerhut c7f3139533
Add March 2021 VM based on Ubuntu 20.04 (#403)
* 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>
2021-09-07 19:34:30 -07:00

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()