From 0b2f8e3966fd16f55cef5528df4fbc471aeb1782 Mon Sep 17 00:00:00 2001 From: Thomas Vachuska Date: Tue, 31 Jan 2023 11:47:31 -0800 Subject: [PATCH] Sketch of a sample app, API revisions, controller state, etc. (#27) * Sketch of a sample app, API revisions, controller state, etc. * Tweaked a test --- pkg/api/control.go | 24 +++++++++++++++++ pkg/api/translation.go | 12 ++++++++- pkg/controller/controller.go | 11 +++++++- pkg/controller/controllers.go | 13 +++++----- pkg/store/store_test.go | 30 +++++++++++++--------- pkg/store/stores_test.go | 2 +- test/app/sample.go | 47 ++++++++++++++++++++++++++++++++++ {pkg/store => test}/p4info.txt | 0 8 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 test/app/sample.go rename {pkg/store => test}/p4info.txt (100%) diff --git a/pkg/api/control.go b/pkg/api/control.go index f480bab..1a11d6c 100644 --- a/pkg/api/control.go +++ b/pkg/api/control.go @@ -12,9 +12,30 @@ import ( p4api "github.com/p4lang/p4runtime/go/p4/v1" ) +// State represents the various states of controller lifecycle +type State int + +// Disconnected => Connected => Synchronizing => Synchronized => Validating => Synchronized + +const ( + // Disconnected represents the default/initial state + Disconnected State = iota + // Connected represents state where connection to the P4Runtime endpoint has been established + Connected + // Synchronizing represents state where pipeline is being validated and the initial synchronization of entries is in progress + Synchronizing + // Synchronized represents state where all entries have been synchronized + Synchronized + // Validating represents state where the validation of entry synchronization is in progress + Validating +) + // DeviceControl is an abstraction of an entity allowing control over the // forwarding behavior of a single device type DeviceControl interface { + // State returns the current state of the controller + State() State + // Read receives a query and returns back all requested control entries on the given channel Read(ctx context.Context, entities *[]p4api.Entity, ch chan<- []*p4api.Entity) error @@ -32,6 +53,9 @@ type DeviceControl interface { // Version returns the P4Runtime version of the target Version() string + + // TODO: Add means for application to watch the state? + // TODO: Consider changing the read to use iterator pattern rather than a channel } // PacketHandler is an abstraction of an entity capable of handling an incoming packet-in diff --git a/pkg/api/translation.go b/pkg/api/translation.go index e0e2cd8..30e8b5a 100644 --- a/pkg/api/translation.go +++ b/pkg/api/translation.go @@ -5,6 +5,7 @@ package api import ( + "github.com/onosproject/onos-net-lib/pkg/p4utils" p4info "github.com/p4lang/p4runtime/go/p4/config/v1" p4api "github.com/p4lang/p4runtime/go/p4/v1" ) @@ -31,11 +32,20 @@ type identityTranslator struct { p4info *p4info.P4Info } -// NewIdentityTranslator returns a new identity pipeline translator +// NewIdentityTranslator returns a new identity pipeline translator for the specified pipeline info func NewIdentityTranslator(info *p4info.P4Info) PipelineTranslator { return &identityTranslator{p4info: info} } +// NewIdentityTranslatorFromFile returns a new identity pipeline translator for pipeline info loaded from the given file +func NewIdentityTranslatorFromFile(path string) (PipelineTranslator, error) { + info, err := p4utils.LoadP4Info(path) + if err != nil { + return nil, err + } + return NewIdentityTranslator(info), nil +} + // Translate returns the same entities as what was provided to it. func (t *identityTranslator) Translate(entities *[]p4api.Entity) *[]p4api.Entity { return entities diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 0ff0343..5bd8cc5 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -19,17 +19,26 @@ type deviceController struct { id topo.ID endpoint string translator api.PipelineTranslator + store store.EntityStore version string + state api.State } -func newDeviceController(id topo.ID, endpoint string, store store.EntityStore, translator api.PipelineTranslator) api.DeviceControl { +func newDeviceController(id topo.ID, endpoint string, entityStore store.EntityStore, translator api.PipelineTranslator) api.DeviceControl { return &deviceController{ id: id, endpoint: endpoint, translator: translator, + store: entityStore, } } +// State returns the current state of the controller +func (d *deviceController) State() api.State { + // TODO: Add synchronization + return d.state +} + // Read receives a query and returns back all requested control entries on the given channel func (d *deviceController) Read(ctx context.Context, entities *[]p4api.Entity, ch chan<- []*p4api.Entity) error { // TODO: Implement me diff --git a/pkg/controller/controllers.go b/pkg/controller/controllers.go index 57ae0f9..008abb2 100644 --- a/pkg/controller/controllers.go +++ b/pkg/controller/controllers.go @@ -7,16 +7,17 @@ package controller import ( "context" - "github.com/onosproject/onos-api/go/onos/stratum" + "github.com/atomix/go-sdk/pkg/primitive" "github.com/onosproject/onos-api/go/onos/topo" "github.com/onosproject/onos-control/pkg/api" "github.com/onosproject/onos-control/pkg/store" + p4api "github.com/p4lang/p4runtime/go/p4/v1" "sync" ) type devicesController struct { api.Devices - role stratum.P4RoleConfig + role *p4api.Role stores store.Stores mu sync.RWMutex @@ -25,10 +26,10 @@ type devicesController struct { // NewController creates a new controller for device control contexts using the supplied role descriptor // and pipeline translator -func NewController(role stratum.P4RoleConfig, stores store.Stores) api.Devices { +func NewController(role *p4api.Role, client primitive.Client) api.Devices { return &devicesController{ role: role, - stores: stores, + stores: store.NewStoreManager(client), } } @@ -41,11 +42,11 @@ func (c *devicesController) Add(ctx context.Context, id topo.ID, p4rtEndpoint st return d, nil } - store, err := c.stores.Get(ctx, id, translator.FromPipeline()) + s, err := c.stores.Get(ctx, id, translator.FromPipeline()) if err != nil { return nil, err } - return newDeviceController(id, p4rtEndpoint, store, translator), nil + return newDeviceController(id, p4rtEndpoint, s, translator), nil } // Remove requests removal of device control context diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index 658284f..294dde1 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -9,6 +9,7 @@ import ( "github.com/atomix/go-sdk/pkg/test" "github.com/onosproject/onos-net-lib/pkg/p4utils" testutils "github.com/onosproject/onos-net-lib/pkg/test" + p4info "github.com/p4lang/p4runtime/go/p4/config/v1" p4api "github.com/p4lang/p4runtime/go/p4/v1" "github.com/stretchr/testify/assert" "math/rand" @@ -20,7 +21,7 @@ func TestStoreBasics(t *testing.T) { ctx := context.TODO() - info, err := p4utils.LoadP4Info("p4info.txt") + info, err := p4utils.LoadP4Info("../../test/p4info.txt") assert.NoError(t, err) store, err := NewEntityStore(ctx, client, "foo", info) @@ -30,17 +31,7 @@ func TestStoreBasics(t *testing.T) { assert.Len(t, es.tables, 20) // Generate a slew of random updates and store them - updates := make([]*p4api.Update, 0, 512) - tl := int32(len(info.Tables)) - for i := 0; i < cap(updates); i++ { - tableInfo := info.Tables[rand.Int31n(tl)] - for tableInfo.Size < 128 || tableInfo.IsConstTable { - tableInfo = info.Tables[rand.Int31n(tl)] - } - entry := testutils.GenerateTableEntry(tableInfo, rand.Int31n(10), nil) - update := &p4api.Update{Type: p4api.Update_INSERT, Entity: &p4api.Entity{Entity: &p4api.Entity_TableEntry{TableEntry: entry}}} - updates = append(updates, update) - } + updates := generateRandomUpdates(info) err = store.Write(ctx, updates) assert.NoError(t, err) @@ -79,6 +70,21 @@ func TestStoreBasics(t *testing.T) { readEntries(ctx, t, store, query, 0) } +func generateRandomUpdates(info *p4info.P4Info) []*p4api.Update { + updates := make([]*p4api.Update, 0, 512) + tl := int32(len(info.Tables)) + for i := 0; i < cap(updates); i++ { + tableInfo := info.Tables[rand.Int31n(tl)] + for tableInfo.Size < 128 || tableInfo.IsConstTable { + tableInfo = info.Tables[rand.Int31n(tl)] + } + entry := testutils.GenerateTableEntry(tableInfo, rand.Int31n(10), nil) + update := &p4api.Update{Type: p4api.Update_INSERT, Entity: &p4api.Entity{Entity: &p4api.Entity_TableEntry{TableEntry: entry}}} + updates = append(updates, update) + } + return updates +} + func readEntries(ctx context.Context, t *testing.T, store EntityStore, query []*p4api.Entity, count int) []*p4api.Entity { ch := make(chan *p4api.Entity, 1024) errs := store.Read(ctx, query, ch) diff --git a/pkg/store/stores_test.go b/pkg/store/stores_test.go index 87e7eb8..1ccf3d7 100644 --- a/pkg/store/stores_test.go +++ b/pkg/store/stores_test.go @@ -18,7 +18,7 @@ func TestStoresBasics(t *testing.T) { ctx := context.TODO() - info, err := p4utils.LoadP4Info("p4info.txt") + info, err := p4utils.LoadP4Info("../../test/p4info.txt") assert.NoError(t, err) stores := NewStoreManager(client) diff --git a/test/app/sample.go b/test/app/sample.go new file mode 100644 index 0000000..dd9052a --- /dev/null +++ b/test/app/sample.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023-present Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +// Package app hosts code snippets simulating various application use-cases of the onos-control library +package app + +import ( + "context" + "github.com/atomix/go-sdk/pkg/client" + "github.com/onosproject/onos-control/pkg/api" + "github.com/onosproject/onos-control/pkg/controller" + "github.com/onosproject/onos-lib-go/pkg/logging" + "github.com/onosproject/onos-net-lib/pkg/p4utils" +) + +var log = logging.GetLogger("sample") + +// InitExample sketches out steps necessary to initialize the library, add a device controller and query its status. +func InitExample() error { + // Node: Consider creating a StratumRoleBuilder + role := p4utils.NewStratumRole("sample", 0, []byte{}, false, false) + devices := controller.NewController(role, client.NewClient()) + + // Note: Consider including a utility to easily add all devices from onos-topo using a realm label. + // This would fetch all devices matching the realm-label and the required onos.topo.StratumAgents, and + // onos.provisioner.DeviceConfig aspects, which it would use to get the P4Info from the device provisioner and for + // P4Runtime endpoint. This would require either specifying or internally creating onos-topo and device-provisioner + // client. E.g. sdk.AddRealmDevices(ctx, devices, topoClient, provisionerClient, realmLabel, realmValue) + + // Create a translator from the specified p4info file + translator, err := api.NewIdentityTranslatorFromFile("../../test/p4info.txt") + if err != nil { + log.Errorf("Unable to create pipeline translator: %+v", err) + return err + } + + ctx := context.Background() + fooDevice, err := devices.Add(ctx, "foo", "fabric-sim:20000", translator) + if err != nil { + log.Errorf("Unable to add controller for device %s: %+v", "foo", err) + return err + } + + log.Infof("Controller state: %s", fooDevice.State()) + return nil +} diff --git a/pkg/store/p4info.txt b/test/p4info.txt similarity index 100% rename from pkg/store/p4info.txt rename to test/p4info.txt