diff --git a/README.md b/README.md index 6ff64af..1d78dbb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ NERPA (Network Programming with Relational and Procedural Abstractions) seeks to 0. Clone this repository. We will call its top-level directory `$NERPA_DIR`. I would recommend using a fresh Ubuntu 18.04 VM for painless P4 installation. 1. Install DDlog using the provided [installation instructions](https://github.com/vmware/differential-datalog/blob/master/README.md#installation). This codebase used version [v0.36.0](https://github.com/vmware/differential-datalog/releases/tag/v0.36.0). 2. Install P4 using these [installation instructions](https://github.com/jafingerhut/p4-guide/blob/master/bin/README-install-troubleshooting.md#quick-instructions-for-successful-install-script-run). We used the install script `install-p4dev-v2.sh`. It is much more usable than the P4 README installation, and clones all necessary repositories and installs dependencies. -For better organization, run it in a dedicated directory for dependencies, called `$NERPA_DEPS`. This directory should be outside your clone of this repository. + +For better organization, create a dedicated directory for these dependencies, outside your clone of this repository. Run the installation script within this directory. Set `$NERPA_DEPS` equal to this directory's path. 3. Generate the DDlog crate using the [setup script](nerpa_controlplane/generate.sh). We do not commit this crate so that small differences in developer toolchains do not create significant hassle. ``` @@ -37,10 +38,7 @@ cargo build ``` ### Test -1. Start `simple_switch_grpc` from its build directory (`$NERPA_DEPS/targets/simple_switch_grpc`). -``` -./simple_switch_grpc --log-console --no-p4 -- --grpc-server-addr 0.0.0.0:50051 --cpu-port 1010 -``` +1. Set the environmental variable `NERPA_DEPS` to the directory containing Nerpa dependencies, including `behavioral-model`. In other words, the `simple_switch_grpc` binary should have the following path: `$NERPA_DEPS/behavioral-model/targets/simple_switch_grpc/simple_switch_grpc`. 2. Run the P4 Runtime library tests. ``` @@ -49,7 +47,7 @@ cargo test ``` ### Run -1. Start `simple_switch_grpc` from its build directory (`$NERPA_DEPS/targets/simple_switch_grpc`). +1. Start `simple_switch_grpc` from its build directory (`$NERPA_DEPS/behavioral-model/targets/simple_switch_grpc`). ``` ./simple_switch_grpc --log-console --no-p4 -- --grpc-server-addr 0.0.0.0:50051 --cpu-port 1010 ``` diff --git a/nerpa_controller/Cargo.lock b/nerpa_controller/Cargo.lock index 977efdd..3ed685d 100644 --- a/nerpa_controller/Cargo.lock +++ b/nerpa_controller/Cargo.lock @@ -1157,6 +1157,7 @@ dependencies = [ "proto", "protobuf", "protobuf-codegen", + "rusty-fork", "tokio", ] @@ -1286,6 +1287,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.9" @@ -1451,6 +1458,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "rustyline" version = "1.0.0" @@ -1903,6 +1922,15 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.1" diff --git a/p4ext/Cargo.lock b/p4ext/Cargo.lock index d550d29..179c92d 100644 --- a/p4ext/Cargo.lock +++ b/p4ext/Cargo.lock @@ -150,6 +150,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.13" @@ -479,6 +485,7 @@ dependencies = [ "proto", "protobuf", "protobuf-codegen", + "rusty-fork", "tokio", ] @@ -608,6 +615,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.9" @@ -702,6 +715,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -850,6 +875,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" diff --git a/p4ext/Cargo.toml b/p4ext/Cargo.toml index 5f03566..2345b2a 100644 --- a/p4ext/Cargo.toml +++ b/p4ext/Cargo.toml @@ -14,5 +14,8 @@ itertools = "0.10.0" proto = {path = "../proto"} protobuf = "2.22.0" protobuf-codegen = "2.22.0" +rusty-fork = "0.3.0" tokio = { version = "1.2.0", features = ["full"] } +[dev-dependencies] +rusty-fork = "0.3.0" diff --git a/p4ext/src/lib.rs b/p4ext/src/lib.rs index 819c2fd..c36e5e8 100644 --- a/p4ext/src/lib.rs +++ b/p4ext/src/lib.rs @@ -20,7 +20,7 @@ SOFTWARE. use byteorder::{BigEndian, WriteBytesExt}; -use grpcio::WriteFlags; +use grpcio::{ChannelBuilder, EnvBuilder, WriteFlags}; use itertools::Itertools; @@ -43,11 +43,14 @@ use proto::p4types; use protobuf::{Message, RepeatedField}; use std::collections::HashMap; +use std::env; use std::ffi::OsStr; use std::fmt::{self, Display}; use std::fs; +use std::process::Command; use std::str::FromStr; use std::string::String; +use std::sync::Arc; #[derive(Clone)] pub struct SourceLocation { @@ -658,6 +661,75 @@ impl fmt::Display for P4Error { } } +pub struct TestSetup { + pub p4info: String, + pub opaque: String, + pub cookie: String, + pub action: String, + pub device_id: u64, + pub role_id: u64, + pub target: String, + pub client: P4RuntimeClient, + pub table_name: String, + pub action_name: String, + pub params_values: HashMap, + pub match_fields_map: HashMap, +} + +impl TestSetup { + pub fn new() -> Self { + let deps_var = "NERPA_DEPS"; + let switch_path = "behavioral-model/targets/simple_switch_grpc/simple_switch_grpc"; + + let nerpa_deps = match env::var(deps_var) { + Ok(val) => val, + Err(err) => panic!("Set env var ${} before running tests (error: {})!", deps_var, err), + }; + + let filepath = format!("{}/{}", nerpa_deps, switch_path); + let mut command = Command::new(filepath); + command.args(&[ + "--no-p4", + "--", + "--grpc-server-addr", + "0.0.0.0:50051", + "--cpu-port", + "1010" + ]); + + match command.spawn() { + Ok(child) => println!("server process id: {}", child.id()), + Err(e) => panic!("server didn't start: {}", e), + } + + let target = "localhost:50051"; + let env = Arc::new(EnvBuilder::new().build()); + let ch = ChannelBuilder::new(env).connect(target); + let client = P4RuntimeClient::new(ch); + + let mut params_values : HashMap = HashMap::new(); + params_values.insert("port".to_string(), 11); + let mut match_fields_map : HashMap = HashMap::new(); + match_fields_map.insert("standard_metadata.ingress_port".to_string(), 11); + match_fields_map.insert("hdr.vlan.vid".to_string(), 1); + + + Self { + p4info: "examples/vlan/vlan.p4info.bin".to_string(), + opaque: "examples/vlan/vlan.json".to_string(), + cookie: "".to_string(), + action: "verify-and-commit".to_string(), + device_id: 0, + role_id: 0, + target: target.to_string(), + client: client, + table_name: "MyIngress.vlan_incoming_exact".to_string(), + action_name: "MyIngress.vlan_incoming_forward".to_string(), + params_values: params_values, + match_fields_map: match_fields_map, + } + } +} pub fn set_pipeline( p4info_str: &str, @@ -899,12 +971,19 @@ pub async fn stream_channel( use futures::SinkExt; let send_result = sink.send((request, WriteFlags::default())).await; match send_result { - Err(e) => return Err(P4Error{ - message: format!("could not send stream message to sink: ({})", e) + Err(err) => return Err(P4Error{ + message: format!("could not send stream message to sink: ({})", err) }), - Ok(r) => {}, - } - sink.close(); + Ok(_) => {}, + }; + + let close_result = sink.close().await; + match close_result { + Err(err) => return Err(P4Error{ + message: format!("could not close sink: ({})", err) + }), + Ok(_) => {}, + }; use futures::StreamExt; let (_, receive_result) = receiver.enumerate().next().await.unwrap(); diff --git a/p4ext/tests/p4ext_test.rs b/p4ext/tests/p4ext_test.rs index cb6b8e8..e32f2b4 100644 --- a/p4ext/tests/p4ext_test.rs +++ b/p4ext/tests/p4ext_test.rs @@ -20,142 +20,96 @@ SOFTWARE. extern crate p4ext; -use grpcio::{ChannelBuilder, EnvBuilder}; -use proto::p4runtime::StreamMessageRequest; -use proto::p4runtime_grpc::P4RuntimeClient; +use rusty_fork::rusty_fork_test; use std::collections::HashMap; -use std::string::String; -use std::sync::Arc; -struct Setup { - p4info: String, - opaque: String, - cookie: String, - action: String, - device_id: u64, - role_id: u64, - target: String, - client: P4RuntimeClient, - table_name: String, - action_name: String, - params_values: HashMap, - match_fields_map: HashMap, -} - -impl Setup { - fn new() -> Self { - let target = "localhost:50051"; - let env = Arc::new(EnvBuilder::new().build()); - let ch = ChannelBuilder::new(env).connect(target); - let client = P4RuntimeClient::new(ch); - - let mut params_values : HashMap = HashMap::new(); - params_values.insert("port".to_string(), 11); - let mut match_fields_map : HashMap = HashMap::new(); - match_fields_map.insert("standard_metadata.ingress_port".to_string(), 11); - match_fields_map.insert("hdr.vlan.vid".to_string(), 1); - - - Self { - p4info: "examples/vlan/vlan.p4info.bin".to_string(), - opaque: "examples/vlan/vlan.json".to_string(), - cookie: "".to_string(), - action: "verify-and-commit".to_string(), - device_id: 0, - role_id: 0, - target: target.to_string(), - client: client, - table_name: "MyIngress.vlan_incoming_exact".to_string(), - action_name: "MyIngress.vlan_incoming_forward".to_string(), - params_values: params_values, - match_fields_map: match_fields_map, - } +rusty_fork_test! { + #[test] + fn set_get_pipeline() { + let setup = p4ext::TestSetup::new(); + + p4ext::set_pipeline( + &setup.p4info, + &setup.opaque, + &setup.cookie, + &setup.action, + setup.device_id, + setup.role_id, + &setup.target, + &setup.client, + ); + + let cfg = p4ext::get_pipeline_config(setup.device_id, &setup.target, &setup.client); + let switch : p4ext::Switch = cfg.get_p4info().into(); + assert_eq!(switch.tables.len(), 4); } } -#[test] -fn set_get_pipeline() { - let setup = Setup::new(); - - p4ext::set_pipeline( - &setup.p4info, - &setup.opaque, - &setup.cookie, - &setup.action, - setup.device_id, - setup.role_id, - &setup.target, - &setup.client, - ); - - let cfg = p4ext::get_pipeline_config(setup.device_id, &setup.target, &setup.client); - let switch : p4ext::Switch = cfg.get_p4info().into(); - assert_eq!(switch.tables.len(), 4); -} - -#[test] -fn build_table_entry() { - let setup = Setup::new(); - - p4ext::set_pipeline( - &setup.p4info, - &setup.opaque, - &setup.cookie, - &setup.action, - setup.device_id, - setup.role_id, - &setup.target, - &setup.client, - ); - - // all valid arguments - assert!(p4ext::build_table_entry( - &setup.table_name, - &setup.action_name, - &setup.params_values, - &setup.match_fields_map, - setup.device_id, - &setup.target, - &setup.client, - ).is_ok()); - - // invalid table name - assert!(p4ext::build_table_entry( - "", - &setup.action_name, - &setup.params_values, - &setup.match_fields_map, - setup.device_id, - &setup.target, - &setup.client, - ).is_err()); - - // invalid action name - assert!(p4ext::build_table_entry( - &setup.table_name, - "", - &setup.params_values, - &setup.match_fields_map, - setup.device_id, - &setup.target, - &setup.client, - ).is_err()); - - // no field matches - assert!(p4ext::build_table_entry( - &setup.table_name, - &setup.action_name, - &setup.params_values, - &HashMap::new(), - setup.device_id, - &setup.target, - &setup.client, - ).is_err()); +rusty_fork_test! { + #[test] + fn build_table_entry() { + let setup = p4ext::TestSetup::new(); + + p4ext::set_pipeline( + &setup.p4info, + &setup.opaque, + &setup.cookie, + &setup.action, + setup.device_id, + setup.role_id, + &setup.target, + &setup.client, + ); + + // all valid arguments + assert!(p4ext::build_table_entry( + &setup.table_name, + &setup.action_name, + &setup.params_values, + &setup.match_fields_map, + setup.device_id, + &setup.target, + &setup.client, + ).is_ok()); + + // invalid table name + assert!(p4ext::build_table_entry( + "", + &setup.action_name, + &setup.params_values, + &setup.match_fields_map, + setup.device_id, + &setup.target, + &setup.client, + ).is_err()); + + // invalid action name + assert!(p4ext::build_table_entry( + &setup.table_name, + "", + &setup.params_values, + &setup.match_fields_map, + setup.device_id, + &setup.target, + &setup.client, + ).is_err()); + + // no field matches + assert!(p4ext::build_table_entry( + &setup.table_name, + &setup.action_name, + &setup.params_values, + &HashMap::new(), + setup.device_id, + &setup.target, + &setup.client, + ).is_err()); + } } #[tokio::test] async fn write_read() { - let setup = Setup::new(); + let setup = p4ext::TestSetup::new(); p4ext::set_pipeline( &setup.p4info, &setup.opaque, @@ -206,7 +160,7 @@ async fn write_read() { #[tokio::test] async fn stream_channel() { - let setup = Setup::new(); + let setup = p4ext::TestSetup::new(); p4ext::set_pipeline( &setup.p4info, &setup.opaque, diff --git a/proto/src/code.rs b/proto/src/code.rs deleted file mode 100644 index 97107f5..0000000 --- a/proto/src/code.rs +++ /dev/null @@ -1,145 +0,0 @@ -// This file is generated by rust-protobuf 2.22.1. Do not edit -// @generated - -// https://github.com/rust-lang/rust-clippy/issues/702 -#![allow(unknown_lints)] -#![allow(clippy::all)] - -#![allow(unused_attributes)] -#![cfg_attr(rustfmt, rustfmt::skip)] - -#![allow(box_pointers)] -#![allow(dead_code)] -#![allow(missing_docs)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(trivial_casts)] -#![allow(unused_imports)] -#![allow(unused_results)] -//! Generated file from `google/rpc/code.proto` - -/// Generated files are compatible only with the same version -/// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1; - -#[derive(Clone,PartialEq,Eq,Debug,Hash)] -pub enum Code { - OK = 0, - CANCELLED = 1, - UNKNOWN = 2, - INVALID_ARGUMENT = 3, - DEADLINE_EXCEEDED = 4, - NOT_FOUND = 5, - ALREADY_EXISTS = 6, - PERMISSION_DENIED = 7, - UNAUTHENTICATED = 16, - RESOURCE_EXHAUSTED = 8, - FAILED_PRECONDITION = 9, - ABORTED = 10, - OUT_OF_RANGE = 11, - UNIMPLEMENTED = 12, - INTERNAL = 13, - UNAVAILABLE = 14, - DATA_LOSS = 15, -} - -impl ::protobuf::ProtobufEnum for Code { - fn value(&self) -> i32 { - *self as i32 - } - - fn from_i32(value: i32) -> ::std::option::Option { - match value { - 0 => ::std::option::Option::Some(Code::OK), - 1 => ::std::option::Option::Some(Code::CANCELLED), - 2 => ::std::option::Option::Some(Code::UNKNOWN), - 3 => ::std::option::Option::Some(Code::INVALID_ARGUMENT), - 4 => ::std::option::Option::Some(Code::DEADLINE_EXCEEDED), - 5 => ::std::option::Option::Some(Code::NOT_FOUND), - 6 => ::std::option::Option::Some(Code::ALREADY_EXISTS), - 7 => ::std::option::Option::Some(Code::PERMISSION_DENIED), - 16 => ::std::option::Option::Some(Code::UNAUTHENTICATED), - 8 => ::std::option::Option::Some(Code::RESOURCE_EXHAUSTED), - 9 => ::std::option::Option::Some(Code::FAILED_PRECONDITION), - 10 => ::std::option::Option::Some(Code::ABORTED), - 11 => ::std::option::Option::Some(Code::OUT_OF_RANGE), - 12 => ::std::option::Option::Some(Code::UNIMPLEMENTED), - 13 => ::std::option::Option::Some(Code::INTERNAL), - 14 => ::std::option::Option::Some(Code::UNAVAILABLE), - 15 => ::std::option::Option::Some(Code::DATA_LOSS), - _ => ::std::option::Option::None - } - } - - fn values() -> &'static [Self] { - static values: &'static [Code] = &[ - Code::OK, - Code::CANCELLED, - Code::UNKNOWN, - Code::INVALID_ARGUMENT, - Code::DEADLINE_EXCEEDED, - Code::NOT_FOUND, - Code::ALREADY_EXISTS, - Code::PERMISSION_DENIED, - Code::UNAUTHENTICATED, - Code::RESOURCE_EXHAUSTED, - Code::FAILED_PRECONDITION, - Code::ABORTED, - Code::OUT_OF_RANGE, - Code::UNIMPLEMENTED, - Code::INTERNAL, - Code::UNAVAILABLE, - Code::DATA_LOSS, - ]; - values - } - - fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor { - static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT; - descriptor.get(|| { - ::protobuf::reflect::EnumDescriptor::new_pb_name::("Code", file_descriptor_proto()) - }) - } -} - -impl ::std::marker::Copy for Code { -} - -impl ::std::default::Default for Code { - fn default() -> Self { - Code::OK - } -} - -impl ::protobuf::reflect::ProtobufValue for Code { - fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { - ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self)) - } -} - -static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x15google/rpc/code.proto\x12\ngoogle.rpc*\xb7\x02\n\x04Code\x12\x06\n\ - \x02OK\x10\0\x12\r\n\tCANCELLED\x10\x01\x12\x0b\n\x07UNKNOWN\x10\x02\x12\ - \x14\n\x10INVALID_ARGUMENT\x10\x03\x12\x15\n\x11DEADLINE_EXCEEDED\x10\ - \x04\x12\r\n\tNOT_FOUND\x10\x05\x12\x12\n\x0eALREADY_EXISTS\x10\x06\x12\ - \x15\n\x11PERMISSION_DENIED\x10\x07\x12\x13\n\x0fUNAUTHENTICATED\x10\x10\ - \x12\x16\n\x12RESOURCE_EXHAUSTED\x10\x08\x12\x17\n\x13FAILED_PRECONDITIO\ - N\x10\t\x12\x0b\n\x07ABORTED\x10\n\x12\x10\n\x0cOUT_OF_RANGE\x10\x0b\x12\ - \x11\n\rUNIMPLEMENTED\x10\x0c\x12\x0c\n\x08INTERNAL\x10\r\x12\x0f\n\x0bU\ - NAVAILABLE\x10\x0e\x12\r\n\tDATA_LOSS\x10\x0fBX\n\x0ecom.google.rpcB\tCo\ - deProtoP\x01Z3google.golang.org/genproto/googleapis/rpc/code;code\xa2\ - \x02\x03RPCb\x06proto3\ -"; - -static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; - -fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { - ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() -} - -pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { - file_descriptor_proto_lazy.get(|| { - parse_descriptor_proto() - }) -}