Skip to content
Snippets Groups Projects
Commit ce1b4e85 authored by Neil-Jocelyn Schark's avatar Neil-Jocelyn Schark
Browse files

Create basic inventory manager

See merge request !1061
parent e3fabe28
Branches
No related tags found
1 merge request!1061Create basic inventory manager
Pipeline #227783 passed
Showing
with 424 additions and 10 deletions
......@@ -23,14 +23,15 @@
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker",
"golang.go",
"EditorConfig.EditorConfig",
"eamodio.gitlens",
"ms-vscode.makefile-tools",
"redhat.vscode-yaml",
"valentjn.vscode-ltex"
]
"ms-azuretools.vscode-docker",
"golang.go",
"EditorConfig.EditorConfig",
"eamodio.gitlens",
"ms-vscode.makefile-tools",
"redhat.vscode-yaml",
"valentjn.vscode-ltex",
"zxh404.vscode-proto3"
]
}
},
"mounts": [
......
......@@ -14,5 +14,7 @@ build-all-binaries:
- artifacts/gosdnc
- artifacts/orchestrator
- artifacts/venv-manager
- artifacts/inventory-manager
- artifacts/plugin-registry
expire_in: 1 week
<<: *build-binaries
......@@ -66,6 +66,15 @@ build-plugin-registry-image:
- docker push "$PLUGIN_REGISTRY_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
<<: *build
build-inventory-manager-image:
script:
- INVENTORY_MANAGER_IMAGE_NAME="${CI_REGISTRY_IMAGE}/inventory-manager"
- docker buildx build -t "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA" -f "${CI_PROJECT_DIR}/applications/inventory-manager/inventory-manager.Dockerfile" --build-arg "GOLANG_VERSION=$GOLANG_VERSION" --build-arg "GITLAB_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/" .
- docker push "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA"
- docker tag "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_SHA" "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
- docker push "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_REF_SLUG"
<<: *build
build-integration-test-images:
needs: ["build-controller-image"]
script:
......
......@@ -58,9 +58,9 @@ lint: install-tools
lint-fix: install-tools
./$(TOOLS_DIR)/golangci-lint run --config .golangci.yml --fix
build: pre build-gosdn build-gosdnc build-plugin-registry build-venv-manager build-arista-routing-engine-app build-hostname-checker-app build-basic-interface-monitoring-app
build: pre build-gosdn build-gosdnc build-plugin-registry build-venv-manager build-arista-routing-engine-app build-hostname-checker-app build-basic-interface-monitoring-app build-inventory-manager
containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry
containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry containerize-venv-manager containerize-arista-routing-engine-app containerize-inventory-manager
generate-all-certs: pre generate-root-ca generate-gosdn-certs generate-gnmi-target-certs
......
......@@ -97,6 +97,40 @@ operations for managed network elements.
In addition, we provide a simple Northbound-API (gRPC) for the controller [right here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/controller/api).
To use the API, you can build a login method as is done in the `inventory-manager` in `utils.go`.
You log in, create a session context with the returned token, then you can simply make API calls with this context.
Example:
In your `utils.go`
```golang
// Login logs in to the controller.
func Login(conn *grpc.ClientConn, address, user, pw string) (string, error) {
loginResp, err := api.Login(context.Background(), address, user, pw)
if err != nil {
return "", err
}
return loginResp.GetToken(), nil
}
// createContextWithAuthorization creates a context with the token received after login.
func createContextWithAuthorization(sessionToken string) context.Context {
md := metadata.Pairs("authorize", sessionToken)
return metadata.NewOutgoingContext(context.Background(), md)
}
```
The code in your app:
```golang
import "code.fbi.h-da.de/danet/gosdn/controller/api"
sessionToken, err := Login(conn, config.ControllerAddress, config.UserName, config.UserPW)
ctx := createContextWithAuthorization(sessionToken)
_, err := api.AddNetworkElement(i.sessionContext, i.controllerAddress, networkElement.Name, networkElement.UUID, &transportOptions, pluginUUID, pndUUID, []string{})
```
The gRPC services can also be reached using HTTP requests via the gRPC-Gateway. The fitting OpenAPI definitions can be found [here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/api/openapiv2?ref_type=heads). Note, that this is experimental and tested less well. If you want to use the controller in secure mode which implies it's mandatory to login and provide the received token in other requests via the HTTP header with the key-value pair:
`"authorize: token"`.
......@@ -335,6 +369,7 @@ The framework provides some basic code to easily set up the subscription to the
Examples where the application framework is used can be found [here](https://code.fbi.h-da.de/danet/gosdn/-/tree/master/applications).
## Integration tests
The integration tests are currently in its own folder named `integration-tests`, as they use a complete black box design.
......
# Inventory Management App
## Overview
The Inventory Management App is designed to manage connected devices of an SDN (Software-Defined Networking) controller. It provides functionalities to add, update, delete, and view devices.
## Features
- Add new devices
- Update existing devices
- Delete devices
## Config file structure:
```yaml
ControllerAddress: "127.0.0.1:55055"
PndID: "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
UserName: "testuser"
UserPW: "securepassword"
AppName: "Inventory-Manager"
NetworkElements:
- Name: "Device01"
UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa2"
Address: "10.0.0.2:1337"
Username: "admin"
Password: "adminpassword"
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
Tls: false
- Name: "Device02"
UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa1"
Address: "hostname:7030"
Username: "user"
Password: "userpassword"
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
Tls: true
```
package config
type Config struct {
ControllerAddress string `yaml:"ControllerAddress"`
PndID string `yaml:"PndID"`
UserName string `yaml:"UserName"`
UserPW string `yaml:"UserPW"`
AppName string `yaml:"AppName"`
NetworkElements []NetworkElement `yaml:"NetworkElements"`
}
type NetworkElement struct {
Name string `yaml:"Name"` // e.g. "Device01"
UUID string `yaml:"UUID"` // e.g. "0d24fbcf-67db-4aac-bf0f-8902b014a7ed"
Address string `yaml:"Address"` // e.g. "10.0.0.2:1337" or "hostname:7030"
Username string `yaml:"Username"` // e.g. "admin"
Password string `yaml:"Password"` // e.g. "adminpassword"
Tls bool `yaml:"Tls"` // e.g. false
PluginID string `yaml:"PluginID"` // UUID e.g. "823aad29-69be-42f0-b279-90f2c1b6a94d"
}
ControllerAddress: "127.0.0.1:55055"
PndID: "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
UserName: "admin"
UserPW: "TestPassword"
AppName: "Inventory-Manager"
NetworkElements:
- Name: "kms01"
UUID: "0ff33c82-7fe1-482b-a0ca-67565806ee4b"
Address: "kms01:7030"
Username: "admin"
Password: "admin"
Tls : false
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
- Name: "kms02"
UUID: "5e41c291-6121-4335-84f6-41e04b8bdaa2"
Address: "kms02:7030"
Username: "admin"
Password: "admin"
Tls : false
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
- Name: "kms03"
UUID: "f80db2c0-2480-46b9-b7d1-b63f954e8227"
Address: "kms03:7030"
Username: "admin"
Password: "admin"
Tls : false
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
- Name: "kms04"
UUID: "968fd594-b0e7-41f0-ba4b-de259047a933"
Address: "kms04:7030"
Username: "admin"
Password: "admin"
Tls : false
PluginID: "823aad29-69be-42f0-b279-90f2c1b6a94d"
ARG GOLANG_VERSION=1.23
ARG BUILDARGS
ARG GITLAB_PROXY
FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm AS builder
WORKDIR /gosdn
COPY . .
RUN make build-inventory-manager
FROM ${GITLAB_PROXY}debian:12
COPY --from=builder /gosdn/artifacts/inventory-manager /inventory-manager
ENTRYPOINT ["/inventory-manager"]
.git
.gitlab
build
documentation
mocks
test
clab-gosdn_csbi_arista_base
clab-gosdn_sts_demo_basic
.cobra.yaml
.dockerignore
.gitlab-ci.yaml
ARCHITECTURE.md
CONTRIBUTING.md
README.md
artifacts
build-tools
models/YangModels
models/arista
models/YangModels
*.Dockerfile
package inventorymanager
import (
"context"
"time"
tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
"code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/config"
"code.fbi.h-da.de/danet/gosdn/controller/api"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type InventoryManager struct {
pndID string
controllerAddress string
grpcClientConn *grpc.ClientConn
sessionContext context.Context
sessionToken string
//eventService event.ServiceInterface
configNetworkElements []config.NetworkElement // NetworkElements from the config file
}
// InitializeInventoryManager sets up the inventory manager and logs in to the controller.
func InitializeInventoryManager(config *config.Config) (*InventoryManager, error) {
// Create controller client object
newConn, err := grpc.NewClient(config.ControllerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
logrus.Errorf("Failed to create controller client with error: %s", err.Error())
return nil, err
}
var conn *grpc.ClientConn
var sessionToken string
logrus.Info("Connecting and logging in to controller ", config.ControllerAddress)
for {
sessionToken, err = Login(conn, config.ControllerAddress, config.UserName, config.UserPW)
if err == nil {
logrus.Info("Connected and logged in to Controller")
conn = newConn
break
}
logrus.Errorf("Failed to connect and log in to Controller with error: %s, retrying in 1 second", err.Error())
time.Sleep(1 * time.Second)
}
app, err := NewInventoryManager(conn, sessionToken, config.ControllerAddress, config.PndID, config.AppName, config.NetworkElements)
if err != nil {
return nil, err
}
return app, nil
}
func NewInventoryManager(grpcClientConn *grpc.ClientConn, sessionToken, controllerAddress, pndID, appName string, networkElements []config.NetworkElement) (*InventoryManager, error) {
ctx := createContextWithAuthorization(sessionToken)
// Not needed now, but later when lifecycle is implemented
//queueCredentials, err := getQueueCredentials(ctx, controllerAddress, appName, registrationToken)
//if err != nil {
// return nil, err
//}
//
//eventService, err := event.NewEventService(
// queueCredentials,
// []event.Topic{event.ManagedNetworkElement},
//)
//if err != nil {
// return nil, err
//}
return &InventoryManager{
grpcClientConn: grpcClientConn,
sessionContext: ctx,
sessionToken: sessionToken,
//eventService: eventService,
pndID: pndID,
controllerAddress: controllerAddress,
configNetworkElements: networkElements,
}, nil
}
// Run starts the InventoryManager. This will elad to it trying to create every network element from the config file.
// It will try to create it indefinitely.
// TODO: After creating a network element, it will ensure that the element exists in the controller.
func (i *InventoryManager) Run() error {
i.createNetworkelements()
logrus.Info("Created all network elements.")
for {
// Management logic after creation should replace this sleep
time.Sleep(60 * time.Second)
}
}
func (i *InventoryManager) createNetworkelements() {
IterateDevicesLoop:
for _, networkElement := range i.configNetworkElements {
transportOptions := tpb.TransportOption{
Address: networkElement.Address,
Username: networkElement.Username,
Password: networkElement.Password,
Tls: networkElement.Tls,
TransportOption: &tpb.TransportOption_GnmiTransportOption{
GnmiTransportOption: &tpb.GnmiTransportOption{},
},
}
AddDeviceLoop:
for {
pluginUUID, err := uuid.Parse(networkElement.PluginID)
if err != nil {
logrus.Errorf("No or wrong plugin UUID for device %s provided. Skipping device creation.", networkElement.Name)
continue IterateDevicesLoop
}
pndUUID, err := uuid.Parse(i.pndID)
if err != nil {
logrus.Errorf("No or wrong PND UUID for device %s provided. Skipping device creation.", networkElement.Name)
continue IterateDevicesLoop
}
_, err = api.AddNetworkElement(i.sessionContext, i.controllerAddress, networkElement.Name, networkElement.UUID, &transportOptions, pluginUUID, pndUUID, []string{})
if err != nil {
logrus.Errorf("Failed to create network element %s with error: %s. Sleeping one second and retrying...", networkElement.Name, err.Error())
time.Sleep(1 * time.Second)
continue AddDeviceLoop
}
break AddDeviceLoop
}
logrus.Infof("Successfully created network element %s", networkElement.Name)
}
}
package inventorymanager
import (
"context"
"code.fbi.h-da.de/danet/gosdn/controller/api"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
//type CustomJwtClaims struct {
// jwt.StandardClaims
// Username string `json:"username,omitempty"`
//}
// Login logs in to the controller.
func Login(conn *grpc.ClientConn, address, user, pw string) (string, error) {
loginResp, err := api.Login(context.Background(), address, user, pw)
if err != nil {
return "", err
}
return loginResp.GetToken(), nil
}
// createContextWithAuthorization creates a context with the token received after login.
func createContextWithAuthorization(sessionToken string) context.Context {
md := metadata.Pairs("authorize", sessionToken)
return metadata.NewOutgoingContext(context.Background(), md)
}
// getQueueCredentials registers an app for the event system of the controller and returns the provided credentials.
//func getQueueCredentials(ctx context.Context, controllerAddress, appName, registrationToken string) (string, error) {
// return registration.Register(ctx, controllerAddress, appName, registrationToken)
//}
package main
import (
"flag"
"log"
"os"
"code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/config"
inventorymanager "code.fbi.h-da.de/danet/gosdn/applications/inventory-manager/inventoryManager"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/resolver"
"gopkg.in/yaml.v3"
)
func main() {
configFile := flag.String("config", "", "app config file")
logLevel := flag.String("log", "info", "logrus lof level (debug, info, warn, error, fatal, panic)")
noGRPCPassthrough := flag.Bool("noGRPCPassthrough", false, "set the default resolve scheme for grpc to not use passthrough, default is false")
flag.Parse()
if *noGRPCPassthrough {
logrus.Info("gRPC default resolver scheme is not set to passthrough. This might cause issues with the gRPC connection when no real DNS server is available as each gRPC requests requires a DNS request.")
} else {
logrus.Info("Setting gRPC default resolver scheme to passthrough. No DNS queries are being made when doing a gRPC request.")
resolver.SetDefaultScheme("passthrough")
}
// parse string, this is built-in feature of logrus
ll, err := logrus.ParseLevel(*logLevel)
if err != nil {
ll = logrus.InfoLevel
logrus.Warn("invalid log level, using default: ", ll)
}
// set global log level
logrus.Info("setting log level to ", ll)
logrus.SetLevel(ll)
logrus.Debugf("current config path: %s", *configFile)
file, err := os.ReadFile(*configFile)
if err != nil {
currentFolder, _ := os.Getwd()
log.Fatalf("error reading config file: %s, current folder: %s", *configFile, currentFolder)
}
appConfig := &config.Config{}
if err = yaml.Unmarshal(file, appConfig); err != nil {
logrus.Fatal(err)
}
// Just for printing the config
configBytes, err := yaml.Marshal(appConfig)
if err != nil {
logrus.Fatal("error marshaling config to YAML: ", err)
}
logrus.Debugf("config:\n%s", string(configBytes))
inventoryManager, err := inventorymanager.InitializeInventoryManager(appConfig)
if err != nil {
logrus.Fatal(err)
}
err = inventoryManager.Run()
if err != nil {
logrus.Fatal(err)
}
}
......@@ -35,3 +35,6 @@ build-basic-interface-monitoring-app: pre
build-ws-events-app: pre
$(GOBUILD) -trimpath -o $(BUILD_ARTIFACTS_PATH)/ws-events ./applications/ws-events
build-inventory-manager: pre
$(GOBUILD) -trimpath -o $(BUILD_ARTIFACTS_PATH)/inventory-manager ./applications/inventory-manager
......@@ -22,3 +22,6 @@ containerize-hostname-checker-app:
containerize-ws-events-app:
docker buildx build --rm -t ws-events-app -f applications/ws-events/ws-events.Dockerfile .
containerize-inventory-manager:
docker buildx build --rm -t venv-manager --load -f applications/inventory-manager/inventory-manager.Dockerfile .
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment