Skip to content
Snippets Groups Projects
Commit f06ca22d authored by Fabian Seidl's avatar Fabian Seidl
Browse files

Resolve "Implement integration tests for applications"


See merge request !691

Co-authored-by: default avatarNeil Schark <neil.schark@h-da.de>
parent 2fe3252e
No related branches found
No related tags found
1 merge request!691Resolve "Implement integration tests for applications"
Pipeline #178959 passed
Showing with 453 additions and 6 deletions
......@@ -30,6 +30,9 @@ RUN apt-get update \
&& rm -Rf /usr/share/doc && rm -Rf /usr/share/man \
&& apt-get clean
# Install gnmic
RUN bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
# Install oh-my-zsh for more terminal features and set it as primary shell
ENV SHELL /bin/zsh
RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
......
......@@ -11,6 +11,7 @@ integration-test-gosdn:
MONGO_INITDB_ROOT_PASSWORD: example
INTEGRATION_TEST_TARGET_A: gnmi-target_A:7030
INTEGRATION_TEST_TARGET_B: gnmi-target_B:7030
RABBITMQ_HOSTNAME: rabbitmq
services:
- name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/mongo:5
alias: mongo
......@@ -39,6 +40,7 @@ integration-test-gosdn:
# Remove start of goSDN later when fixed. See: https://code.fbi.h-da.de/danet/gosdn/-/issues/335
- cp -r artifacts/ssl/gosdn/certs artifacts/ssl/
- cp -r artifacts/ssl/gosdn/private artifacts/ssl/
- mkdir artifacts/configs && cp controller/configs/gNMISubscriptions.txt.example artifacts/configs/gNMISubscriptions.txt
- make build-gosdn
- cd artifacts && GOSDN_ADMIN_PASSWORD=TestPassword ./gosdn --config ../controller/configs/integration-test-gosdn.toml --security secure &
- INTEGRATION_TEST_CONTROLLER_URL="localhost:55055" go test -p 1 ./integration-tests/*
......
......@@ -10,13 +10,12 @@ import (
)
// Register registers a new app at the control plane.
func Register(gosdnAddress, name, token string) (string, error) {
func Register(ctx context.Context, gosdnAddress, name, token string) (string, error) {
conn, err := grpc.Dial(gosdnAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return "", err
}
ctx := context.Background()
appService := app.NewAppServiceClient(conn)
request := &app.AppRegisterRequest{
......
package main
import (
"context"
"os"
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
......@@ -9,7 +10,7 @@ import (
)
func main() {
queueCredentials, err := registration.Register("localhost:55055", "arista-routing-engine", "SecurePresharedToken")
queueCredentials, err := registration.Register(context.Background(), "localhost:55055", "arista-routing-engine", "SecurePresharedToken")
if err != nil {
logrus.Errorf("failed to register application on control plane. %v", err)
os.Exit(1)
......
package main
import (
"context"
"flag"
"os"
......@@ -18,7 +19,7 @@ func main() {
flag.Parse()
queueCredentials, err := registration.Register(controllerAddress, "basic-interface-monitoring", "SecurePresharedToken")
queueCredentials, err := registration.Register(context.Background(), controllerAddress, "basic-interface-monitoring", "SecurePresharedToken")
if err != nil {
logrus.Errorf("failed to register application on control plane. %v", err)
os.Exit(1)
......
package main
import (
"context"
"os"
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
......@@ -9,7 +10,7 @@ import (
)
func main() {
queueCredentials, err := registration.Register("localhost:55055", "hostname-checker", "SecurePresharedToken")
queueCredentials, err := registration.Register(context.Background(), "localhost:55055", "hostname-checker", "SecurePresharedToken")
if err != nil {
logrus.Errorf("failed to register application on control plane. %v", err)
os.Exit(1)
......
package main
import (
"context"
"flag"
"os"
......@@ -14,7 +15,7 @@ func main() {
flag.Parse()
// Register the application at the controller
queueCredentials, err := registration.Register(*controllerAddress, "ws-events", "SecurePresharedToken")
queueCredentials, err := registration.Register(context.Background(), *controllerAddress, "ws-events", "SecurePresharedToken")
if err != nil {
logrus.Errorf("failed to register application on control plane. %v", err)
os.Exit(1)
......
......@@ -19,6 +19,9 @@ services:
rabbitmq:
image: rabbitmq:3-management
ports:
- 127.0.0.1:5672:5672
- 127.0.0.1:15672:15672
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 30s
......
package integration_test_application
import (
"context"
"os"
"os/signal"
"strings"
"syscall"
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
"code.fbi.h-da.de/danet/gosdn/application-framework/registration"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
const localhost = "127.0.0.1"
// Application is an example for a sdn application.
type Application struct {
eventService event.ServiceInterface
stopChannel chan os.Signal
grpcClientConn *grpc.ClientConn
addEventChannel chan event.Event
updateEventChannel chan event.Event
deleteEventChannel chan event.Event
subscribeEventChannel chan event.Event
}
func NewApplication(ctx context.Context, grpcClientConn *grpc.ClientConn, controllerAddress string, topics []event.Topic, rabbitMQAddress string) *Application {
queueCredentials, err := registration.Register(ctx, controllerAddress, "integration-test-application", "SecurePresharedToken")
if err != nil {
logrus.Errorf("failed to register application on control plane. %v", err)
os.Exit(1)
}
if rabbitMQAddress != "" {
queueCredentials = strings.ReplaceAll(queueCredentials, localhost, rabbitMQAddress)
}
eventService, err := event.NewEventService(
queueCredentials,
topics,
)
if err != nil {
logrus.Errorf("failed to create event service. %v", err)
os.Exit(1)
}
return &Application{
eventService: eventService,
stopChannel: make(chan os.Signal, 1),
grpcClientConn: grpcClientConn,
addEventChannel: make(chan event.Event, 1),
updateEventChannel: make(chan event.Event, 1),
deleteEventChannel: make(chan event.Event, 1),
subscribeEventChannel: make(chan event.Event, 1),
}
}
// Run runs the application.
func (a *Application) Run(eventTypeCallbackTuples []event.TypeToCallbackTuple) {
signal.Notify(a.stopChannel, os.Interrupt, syscall.SIGTERM)
a.eventService.SubscribeToEventType(eventTypeCallbackTuples)
a.eventService.SetupEventReciever(a.stopChannel)
var forever chan struct{}
go func() {
for {
select {
case <-a.stopChannel:
close(forever)
_ = a.grpcClientConn.Close()
return
}
}
}()
<-forever
}
func (a *Application) addCallback(event *event.Event) {
logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
a.addEventChannel <- *event
}
func (a *Application) updateCallback(event *event.Event) {
logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
a.updateEventChannel <- *event
}
func (a *Application) deleteCallback(event *event.Event) {
logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
a.deleteEventChannel <- *event
}
func (a *Application) subscribeCallback(event *event.Event) {
logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
a.subscribeEventChannel <- *event
}
package integration_test_application
import (
"context"
"fmt"
"os"
"testing"
"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict"
mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
integration_test_utils "code.fbi.h-da.de/danet/gosdn/integration-tests/integrationTestUtils"
"google.golang.org/grpc"
)
// Notes:
// When adding tests take into account all the messages that get added to the message
// queues during test setups and actual test cases. So make sure to read from the according eventChannels
// of the application.
//
// Do not reset test setup in this because it causes weird interactions between test cases and RabbitMQ message queues.
// Reason for that is that cleanup creates messages to be put into the queues of RabbitMQ.
const targetAAdress = "gnmi-target_A:7030"
const targetUserAndPW = "admin"
const pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
const pluginID = "d1c269a2-6482-4010-b0d8-679dff73153b"
const mneID = "9ee7bf15-15ff-44b8-aafa-035487d1e97f"
const userID = "23224223-aeb6-433b-a797-2d671d7424f0"
const roleID = "679f7982-d740-452e-b78d-c1d133233694"
// The connection to the controller to use in each test.
var conn *grpc.ClientConn
// The context containing the credentials when authenticated.
var ctx context.Context
// A defaultSDN config with default/empty values.
var defaultSDNConfig string
var application *Application
func TestMain(m *testing.M) {
localConn, localCtx, err := integration_test_utils.CreateSecureConnection()
if err != nil {
fmt.Println(err.Error())
}
conn = localConn
ctx = localCtx
sndConfig, err := integration_test_utils.ExportCurrentSDNConfig(conn, ctx)
defaultSDNConfig = sndConfig
if err != nil {
fmt.Println(err.Error())
}
topics := []event.Topic{event.ManagedNetworkElement, event.User, event.Role}
rabbitMQAddress := ""
envVarRabbitmq := os.Getenv("RABBITMQ_HOSTNAME")
if envVarRabbitmq != "" {
rabbitMQAddress = envVarRabbitmq
}
application = NewApplication(ctx, conn, ":55055", topics, rabbitMQAddress)
eventTypeCallbackTuples := []event.TypeToCallbackTuple{
{
Type: event.Add,
Callback: application.addCallback,
},
{
Type: event.Update,
Callback: application.updateCallback,
},
{
Type: event.Delete,
Callback: application.deleteCallback,
},
{
Type: event.Subscribe,
Callback: application.subscribeCallback,
},
}
go application.Run(eventTypeCallbackTuples)
// This is needed to clear the go channel of the messages sent by RabbitMQ when creating
// and logging in with the admin user.
// Important note: only works once after starting the setup, because first time use creates
// a user and role and update the user because of the login. After then only logins are done, no user and role creations.
// This means that this will block after trying once, because of the three attempts to read from eventChannels.
_ = <-application.addEventChannel
_ = <-application.addEventChannel
_ = <-application.updateEventChannel
m.Run()
}
func TestNetworkElementAddAndSubscribeEvent(t *testing.T) {
// setup required parameters
opt := &tpb.TransportOption{
Address: targetAAdress,
Username: targetUserAndPW,
Password: targetUserAndPW,
TransportOption: &tpb.TransportOption_GnmiTransportOption{
GnmiTransportOption: &tpb.GnmiTransportOption{},
},
Tls: true,
}
addListRequest := &mnepb.AddListRequest{
Timestamp: integration_test_utils.GetTimestamp(),
Mne: []*mnepb.SetMne{
{
MneId: mneID,
Address: "gnmi-target_A:7030",
Pid: pndID,
PluginId: pluginID,
MneName: "Horst",
TransportOption: opt,
},
},
Pid: pndID,
}
deleteMneRequest := &mnepb.DeleteRequest{
Timestamp: integration_test_utils.GetTimestamp(),
Pid: pndID,
Mneid: mneID,
}
// setup gRPC services
mneService := mnepb.NewNetworkElementServiceClient(conn)
// add one device to the controller
_, err := mneService.AddList(ctx, addListRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
// check if events are available and correct type and content
addEvent := <-application.addEventChannel
assertAddEvent(t, addEvent, mneID)
subscribeEvent := <-application.subscribeEventChannel
assertSubscribeEvent(t, subscribeEvent, mneID)
subscribeEvent = <-application.subscribeEventChannel
assertSubscribeEvent(t, subscribeEvent, mneID)
// TODO: add tests fot rhis event type when update function is implemented properly
// update mne and check events
// delete mne and check for events
_, err = mneService.Delete(ctx, deleteMneRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
deleteEvent := <-application.deleteEventChannel
assertDeleteEvent(t, deleteEvent, mneID)
}
func TestUserEvents(t *testing.T) {
// setup required parameters
const newUserName = "new name"
addUserRequest := &apb.CreateUsersRequest{
Timestamp: integration_test_utils.GetTimestamp(),
User: []*apb.User{
{
Id: userID,
Name: "user",
Roles: map[string]string{pndID: "admin"},
Password: targetUserAndPW,
Metadata: &conflict.Metadata{
ResourceVersion: 0,
},
},
},
}
updateUserRequest := &apb.UpdateUsersRequest{
Timestamp: integration_test_utils.GetTimestamp(),
User: []*apb.UpdateUser{
{
Id: userID,
Name: newUserName,
Metadata: &conflict.Metadata{},
},
},
}
deleteUserRequest := &apb.DeleteUsersRequest{
Timestamp: integration_test_utils.GetTimestamp(),
Username: []string{newUserName},
}
// setup gRPC services
userService := apb.NewUserServiceClient(conn)
// add one device to the controller
_, err := userService.CreateUsers(ctx, addUserRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
// check if event is available and correct type
addEvent := <-application.addEventChannel
assertAddEvent(t, addEvent, userID)
// update user and check for event
_, err = userService.UpdateUsers(ctx, updateUserRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
updateEvent := <-application.updateEventChannel
assertUpdateEvent(t, updateEvent, userID)
// delete user and check for event
_, err = userService.DeleteUsers(ctx, deleteUserRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
deleteEvent := <-application.deleteEventChannel
assertDeleteEvent(t, deleteEvent, userID)
}
func TestRoleEvents(t *testing.T) {
// setup required parameters
const roleName = "new role"
addRoleRequest := &apb.CreateRolesRequest{
Timestamp: integration_test_utils.GetTimestamp(),
Roles: []*apb.Role{
{
Id: roleID,
Name: roleName,
Description: "well, not much to see here",
Permissions: []string{"some permission"},
},
},
}
updateRolesRequest := &apb.UpdateRolesRequest{
Timestamp: integration_test_utils.GetTimestamp(),
Roles: []*apb.Role{
{
Id: roleID,
Name: roleName,
Description: "even less now",
},
},
}
deleteRoleRequest := &apb.DeleteRolesRequest{
Timestamp: integration_test_utils.GetTimestamp(),
RoleName: []string{roleName},
}
// setup gRPC services
roleService := apb.NewRoleServiceClient(conn)
// add role and check add event
_, err := roleService.CreateRoles(ctx, addRoleRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
addEvent := <-application.addEventChannel
assertAddEvent(t, addEvent, roleID)
// update role and check for event
_, err = roleService.UpdateRoles(ctx, updateRolesRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
updateEvent := <-application.updateEventChannel
assertUpdateEvent(t, updateEvent, roleID)
// delete new role and check for event
_, err = roleService.DeleteRoles(ctx, deleteRoleRequest)
if err != nil {
t.Error(err)
t.FailNow()
}
deleteEvent := <-application.deleteEventChannel
assertDeleteEvent(t, deleteEvent, roleID)
}
package integration_test_application
import (
"code.fbi.h-da.de/danet/gosdn/application-framework/event"
"github.com/stretchr/testify/assert"
)
func assertAddEvent(t assert.TestingT, addEvent event.Event, ID string) {
assert.Equal(t, event.Add.String(), addEvent.Type)
assert.Equal(t, ID, addEvent.EntityID.String())
}
func assertUpdateEvent(t assert.TestingT, updateEvent event.Event, ID string) {
assert.Equal(t, event.Update.String(), updateEvent.Type)
assert.Equal(t, ID, updateEvent.EntityID.String())
}
func assertDeleteEvent(t assert.TestingT, deleteEvent event.Event, ID string) {
assert.Equal(t, event.Delete.String(), deleteEvent.Type)
assert.Equal(t, ID, deleteEvent.EntityID.String())
}
func assertSubscribeEvent(t assert.TestingT, subscribeEvent event.Event, ID string) {
assert.Equal(t, event.Subscribe.String(), subscribeEvent.Type)
assert.Equal(t, ID, subscribeEvent.EntityID.String())
}
......@@ -31,6 +31,9 @@ func TestMain(m *testing.M) {
if err != nil {
fmt.Println(err.Error())
}
integration_test_utils.ApplySDNConfig(conn, ctx, defaultSDNConfig)
m.Run()
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment