From 3bb6f855fe4428909e3955dce40f0723b145f7f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Sterba?= <andre.sterba@stud.h-da.de>
Date: Tue, 31 May 2022 21:39:50 +0200
Subject: [PATCH] Add topology related structures and services

---
 controller/topology/links/link.go        |  17 ++
 controller/topology/nodes/node.go        |   9 ++
 controller/topology/nodes/nodeService.go |  86 ++++++++++
 controller/topology/nodes/nodeStore.go   | 192 ++++++++++++++++++++++
 controller/topology/ports/port.go        |   9 ++
 controller/topology/ports/portService.go |  86 ++++++++++
 controller/topology/ports/portStore.go   | 193 +++++++++++++++++++++++
 controller/topology/topology.go          |   1 +
 controller/topology/topologyService.go   |  62 ++++++++
 controller/topology/topologyStore.go     | 193 +++++++++++++++++++++++
 10 files changed, 848 insertions(+)
 create mode 100644 controller/topology/links/link.go
 create mode 100644 controller/topology/nodes/node.go
 create mode 100644 controller/topology/nodes/nodeService.go
 create mode 100644 controller/topology/nodes/nodeStore.go
 create mode 100644 controller/topology/ports/port.go
 create mode 100644 controller/topology/ports/portService.go
 create mode 100644 controller/topology/ports/portStore.go
 create mode 100644 controller/topology/topology.go
 create mode 100644 controller/topology/topologyService.go
 create mode 100644 controller/topology/topologyStore.go

diff --git a/controller/topology/links/link.go b/controller/topology/links/link.go
new file mode 100644
index 000000000..0d0632493
--- /dev/null
+++ b/controller/topology/links/link.go
@@ -0,0 +1,17 @@
+package links
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
+	"github.com/google/uuid"
+)
+
+// Link is a representation of a physical or virtual link between two nodes and their ports
+type Link struct {
+	ID         uuid.UUID  `bson:"_id"`
+	Name       string     `bson:"name,omitempty"`
+	SourceNode nodes.Node `bson:"source_node,omitempty"`
+	TargetNode nodes.Node `bson:"target_node,omitempty"`
+	SourcePort ports.Port `bson:"source_port,omitempty"`
+	TargetPort ports.Port `bson:"target_port,omitempty"`
+}
diff --git a/controller/topology/nodes/node.go b/controller/topology/nodes/node.go
new file mode 100644
index 000000000..88d250aeb
--- /dev/null
+++ b/controller/topology/nodes/node.go
@@ -0,0 +1,9 @@
+package nodes
+
+import "github.com/google/uuid"
+
+// Node is a representation of a network element
+type Node struct {
+	ID   uuid.UUID `bson:"_id"`
+	Name string    `bson:"name"`
+}
diff --git a/controller/topology/nodes/nodeService.go b/controller/topology/nodes/nodeService.go
new file mode 100644
index 000000000..c540f85c6
--- /dev/null
+++ b/controller/topology/nodes/nodeService.go
@@ -0,0 +1,86 @@
+package nodes
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+// Service defines a interface for a NodeService
+type Service interface {
+	EnsureExists(Node) (Node, error)
+	Update(Node) error
+	Delete(Node) error
+	Get(store.Query) (Node, error)
+	GetAll() ([]Node, error)
+}
+
+// NodeService is a NodeService
+type NodeService struct {
+	store Store
+}
+
+// NewNodeService creates a NodeService
+func NewNodeService(store Store) Service {
+	return &NodeService{
+		store: store,
+	}
+}
+
+// EnsureExists either creates a new node or returns an already existing node
+func (p *NodeService) EnsureExists(node Node) (Node, error) {
+	if node.ID == uuid.Nil {
+		node.ID = uuid.New()
+		err := p.store.Add(node)
+		if err != nil {
+			return node, err
+		}
+
+		return node, nil
+	}
+
+	node, err := p.Get(store.Query{ID: node.ID})
+	if err != nil {
+		return node, err
+	}
+
+	return node, nil
+}
+
+// Update updates an existing node
+func (p *NodeService) Update(node Node) error {
+	err := p.store.Update(node)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Delete deletes a node
+func (p *NodeService) Delete(node Node) error {
+	err := p.store.Delete(node)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Get gets a node
+func (p *NodeService) Get(query store.Query) (Node, error) {
+	node, err := p.store.Get(query)
+	if err != nil {
+		return node, err
+	}
+
+	return node, nil
+}
+
+// GetAll gets all existing nodes
+func (p *NodeService) GetAll() ([]Node, error) {
+	nodes, err := p.store.GetAll()
+	if err != nil {
+		return nodes, err
+	}
+	return nodes, nil
+}
diff --git a/controller/topology/nodes/nodeStore.go b/controller/topology/nodes/nodeStore.go
new file mode 100644
index 000000000..517c4f74d
--- /dev/null
+++ b/controller/topology/nodes/nodeStore.go
@@ -0,0 +1,192 @@
+package nodes
+
+import (
+	"fmt"
+	"log"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+// Store defines a NodeStore interface
+type Store interface {
+	Add(Node) error
+	Update(Node) error
+	Delete(Node) error
+	Get(store.Query) (Node, error)
+	GetAll() ([]Node, error)
+}
+
+// DatabaseNodeStore is a database store for nodes
+type DatabaseNodeStore struct {
+	storeName string
+}
+
+// NewDatabaseNodeStore returns a NodeStore
+func NewDatabaseNodeStore() Store {
+	return &DatabaseNodeStore{
+		storeName: fmt.Sprintf("node-store.json"),
+	}
+}
+
+// Get takes a nodes's UUID or name and returns the nodes.
+func (s *DatabaseNodeStore) Get(query store.Query) (Node, error) {
+	var loadedNode Node
+
+	if query.ID.String() != "" {
+		loadedNode, err := s.getByID(query.ID)
+		if err != nil {
+			return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+		}
+
+		return loadedNode, nil
+	}
+
+	loadedNode, err := s.getByName(query.Name)
+	if err != nil {
+		return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedNode, nil
+}
+
+func (s *DatabaseNodeStore) getByID(idOfLink uuid.UUID) (Node, error) {
+	var loadedNode Node
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: idOfLink.String()}})
+	if result == nil {
+		return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedNode)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedNode, nil
+}
+
+func (s *DatabaseNodeStore) getByName(nameOfDevice string) (Node, error) {
+	var loadedNode Node
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "name", Value: nameOfDevice}})
+	if result == nil {
+		return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedNode)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedNode, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedNode, nil
+}
+
+// GetAll returns all stored nodes.
+func (s *DatabaseNodeStore) GetAll() ([]Node, error) {
+	var loadedNode []Node
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return []Node{}, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedNode)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return loadedNode, errors.ErrCouldNotMarshall{StoreName: s.storeName}
+	}
+
+	return loadedNode, nil
+}
+
+// Add adds a node to the node store.
+func (s *DatabaseNodeStore) Add(node Node) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		InsertOne(ctx, node)
+	if err != nil {
+		log.Printf("Could not add Node: %v", err)
+		return errors.ErrCouldNotCreate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// Update updates a existing node.
+func (s *DatabaseNodeStore) Update(node Node) error {
+	var updatedLoadedNodes Node
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	update := bson.D{primitive.E{Key: "$set", Value: node}}
+
+	upsert := false
+	after := options.After
+	opt := options.FindOneAndUpdateOptions{
+		Upsert:         &upsert,
+		ReturnDocument: &after,
+	}
+
+	err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		FindOneAndUpdate(
+			ctx, bson.M{"_id": node.ID.String()}, update, &opt).
+		Decode(&updatedLoadedNodes)
+	if err != nil {
+		log.Printf("Could not update Node: %v", err)
+
+		return errors.ErrCouldNotUpdate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// Delete deletes a node from the node store.
+func (s *DatabaseNodeStore) Delete(node Node) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	_, err := collection.DeleteOne(ctx, bson.D{primitive.E{Key: node.ID.String()}})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/controller/topology/ports/port.go b/controller/topology/ports/port.go
new file mode 100644
index 000000000..841ae9b90
--- /dev/null
+++ b/controller/topology/ports/port.go
@@ -0,0 +1,9 @@
+package ports
+
+import "github.com/google/uuid"
+
+// Port is a representation of physical port on a network element
+type Port struct {
+	ID   uuid.UUID `bson:"_id"`
+	Name string    `bson:"name,omitempty"`
+}
diff --git a/controller/topology/ports/portService.go b/controller/topology/ports/portService.go
new file mode 100644
index 000000000..fadf31934
--- /dev/null
+++ b/controller/topology/ports/portService.go
@@ -0,0 +1,86 @@
+package ports
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+)
+
+// Service defines an interface for a PortService
+type Service interface {
+	EnsureExists(Port) (Port, error)
+	Update(Port) error
+	Delete(Port) error
+	Get(store.Query) (Port, error)
+	GetAll() ([]Port, error)
+}
+
+// PortService is a service for ports
+type PortService struct {
+	store Store
+}
+
+// NewPortService creates a new PortService
+func NewPortService(store Store) Service {
+	return &PortService{
+		store: store,
+	}
+}
+
+// EnsureExists either creates a new port or returns an already existing port
+func (p *PortService) EnsureExists(port Port) (Port, error) {
+	if port.ID == uuid.Nil {
+		port.ID = uuid.New()
+		err := p.store.Add(port)
+		if err != nil {
+			return port, err
+		}
+
+		return port, nil
+	}
+
+	port, err := p.Get(store.Query{ID: port.ID})
+	if err != nil {
+		return port, err
+	}
+
+	return port, nil
+}
+
+// Update updates an existing port
+func (p *PortService) Update(port Port) error {
+	err := p.store.Update(port)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Delete deletes a port
+func (p *PortService) Delete(port Port) error {
+	err := p.store.Delete(port)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Get gets a port
+func (p *PortService) Get(query store.Query) (Port, error) {
+	port, err := p.store.Get(query)
+	if err != nil {
+		return port, err
+	}
+
+	return port, nil
+}
+
+// GetAll gets all existing ports
+func (p *PortService) GetAll() ([]Port, error) {
+	nodes, err := p.store.GetAll()
+	if err != nil {
+		return nodes, err
+	}
+	return nodes, nil
+}
diff --git a/controller/topology/ports/portStore.go b/controller/topology/ports/portStore.go
new file mode 100644
index 000000000..7b2b6bc43
--- /dev/null
+++ b/controller/topology/ports/portStore.go
@@ -0,0 +1,193 @@
+package ports
+
+import (
+	"fmt"
+	"log"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"github.com/google/uuid"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+// Store defines a PortStore interface
+type Store interface {
+	Add(Port) error
+	Update(Port) error
+	Delete(Port) error
+	Get(store.Query) (Port, error)
+	GetAll() ([]Port, error)
+}
+
+// DatabasePortStore is a database store for ports
+type DatabasePortStore struct {
+	storeName string
+}
+
+// NewDatabasePortStore returns a PortStore
+func NewDatabasePortStore() Store {
+	return &DatabasePortStore{
+		storeName: fmt.Sprintf("port-store.json"),
+	}
+}
+
+// Get takes a Ports's UUID or name and returns the port.
+func (s *DatabasePortStore) Get(query store.Query) (Port, error) {
+	var loadedPort Port
+
+	if query.ID.String() != "" {
+		loadedPort, err := s.getByID(query.ID)
+		if err != nil {
+			return loadedPort, errors.ErrCouldNotFind{StoreName: s.storeName}
+		}
+
+		return loadedPort, nil
+	}
+
+	loadedPort, err := s.getByName(query.Name)
+	if err != nil {
+		return loadedPort, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedPort, nil
+}
+
+func (s *DatabasePortStore) getByID(idOfLink uuid.UUID) (Port, error) {
+	var loadedPort Port
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: idOfLink.String()}})
+	if result == nil {
+		return loadedPort, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedPort)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedPort, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedPort, nil
+}
+
+func (s *DatabasePortStore) getByName(nameOfDevice string) (Port, error) {
+	var loadedDevice Port
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "name", Value: nameOfDevice}})
+	if result == nil {
+		return loadedDevice, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedDevice)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedDevice, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedDevice, nil
+}
+
+// GetAll returns all stored ports.
+func (s *DatabasePortStore) GetAll() ([]Port, error) {
+	var loadedPort []Port
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return []Port{}, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedPort)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return loadedPort, errors.ErrCouldNotMarshall{StoreName: s.storeName}
+	}
+
+	return loadedPort, nil
+}
+
+// Add adds a port to the port store.
+func (s *DatabasePortStore) Add(port Port) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		InsertOne(ctx, port)
+	if err != nil {
+		log.Printf("Could not add Port: %v", err)
+		return errors.ErrCouldNotCreate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// Update updates a existing port.
+func (s *DatabasePortStore) Update(port Port) error {
+	var updatedLoadedDevice device.LoadedDevice
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	update := bson.D{primitive.E{Key: "$set", Value: port}}
+
+	upsert := false
+	after := options.After
+	opt := options.FindOneAndUpdateOptions{
+		Upsert:         &upsert,
+		ReturnDocument: &after,
+	}
+
+	err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		FindOneAndUpdate(
+			ctx, bson.M{"_id": port.ID.String()}, update, &opt).
+		Decode(&updatedLoadedDevice)
+	if err != nil {
+		log.Printf("Could not update Port: %v", err)
+
+		return errors.ErrCouldNotUpdate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// Delete deletes a port from the port store.
+func (s *DatabasePortStore) Delete(port Port) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	_, err := collection.DeleteOne(ctx, bson.D{primitive.E{Key: port.ID.String()}})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/controller/topology/topology.go b/controller/topology/topology.go
new file mode 100644
index 000000000..18f8a87cb
--- /dev/null
+++ b/controller/topology/topology.go
@@ -0,0 +1 @@
+package topology
diff --git a/controller/topology/topologyService.go b/controller/topology/topologyService.go
new file mode 100644
index 000000000..a017db246
--- /dev/null
+++ b/controller/topology/topologyService.go
@@ -0,0 +1,62 @@
+package topology
+
+import "code.fbi.h-da.de/danet/gosdn/controller/topology/links"
+
+// TService defines an interface for a TopologyService
+type TService interface {
+	AddLink(links.Link) error
+	UpdateLink(links.Link) error
+	DeleteLink(links.Link) error
+	GetAll() ([]links.Link, error)
+}
+
+// Service is a service for ports
+type Service struct {
+	store Store
+}
+
+// NewTopologyService creates a new TopologyService
+func NewTopologyService(store Store) TService {
+	return &Service{
+		store: store,
+	}
+}
+
+// AddLink adds a new link to the topology
+func (t *Service) AddLink(link links.Link) error {
+	err := t.store.AddLink(link)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateLink updates an existing link
+func (t *Service) UpdateLink(link links.Link) error {
+	err := t.store.UpdateLink(link)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteLink deletes a link
+func (t *Service) DeleteLink(link links.Link) error {
+	err := t.store.DeleteLink(link)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetAll returns the current topology
+func (t *Service) GetAll() ([]links.Link, error) {
+	topo, err := t.store.GetAll()
+	if err != nil {
+		return topo, err
+	}
+	return topo, nil
+}
diff --git a/controller/topology/topologyStore.go b/controller/topology/topologyStore.go
new file mode 100644
index 000000000..363985b44
--- /dev/null
+++ b/controller/topology/topologyStore.go
@@ -0,0 +1,193 @@
+package topology
+
+import (
+	"fmt"
+	"log"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
+	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
+	"code.fbi.h-da.de/danet/gosdn/controller/topology/links"
+	"github.com/google/uuid"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+// Store defines a Topology store interface
+type Store interface {
+	AddLink(links.Link) error
+	UpdateLink(links.Link) error
+	DeleteLink(links.Link) error
+	Get(store.Query) (links.Link, error)
+	GetAll() ([]links.Link, error)
+}
+
+// DatabaseTopologyStore is a database store for the topology
+type DatabaseTopologyStore struct {
+	storeName string
+}
+
+// NewDatabaseTopologyStore returns a TopologyStore
+func NewDatabaseTopologyStore() Store {
+	return &DatabaseTopologyStore{
+		storeName: fmt.Sprintf("topology-store.json"),
+	}
+}
+
+// Get takes a link's UUID or name and returns the link.
+func (s *DatabaseTopologyStore) Get(query store.Query) (links.Link, error) {
+	var loadedTopology links.Link
+
+	if query.ID.String() != "" {
+		loadedDevice, err := s.getByID(query.ID)
+		if err != nil {
+			return loadedDevice, errors.ErrCouldNotFind{StoreName: s.storeName}
+		}
+
+		return loadedDevice, nil
+	}
+
+	loadedTopology, err := s.getByName(query.Name)
+	if err != nil {
+		return loadedTopology, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedTopology, nil
+}
+
+func (s *DatabaseTopologyStore) getByID(idOfLink uuid.UUID) (links.Link, error) {
+	var loadedTopology links.Link
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "_id", Value: idOfLink.String()}})
+	if result == nil {
+		return loadedTopology, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedTopology)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedTopology, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedTopology, nil
+}
+
+func (s *DatabaseTopologyStore) getByName(nameOfDevice string) (links.Link, error) {
+	var loadedTopology links.Link
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	result := collection.FindOne(ctx, bson.D{primitive.E{Key: "name", Value: nameOfDevice}})
+	if result == nil {
+		return loadedTopology, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	err := result.Decode(&loadedTopology)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+		return loadedTopology, errors.ErrCouldNotFind{StoreName: s.storeName}
+	}
+
+	return loadedTopology, nil
+}
+
+// GetAll returns all stored links.
+func (s *DatabaseTopologyStore) GetAll() ([]links.Link, error) {
+	var loadedTopology []links.Link
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+
+	cursor, err := collection.Find(ctx, bson.D{})
+	if err != nil {
+		return loadedTopology, err
+	}
+	defer cursor.Close(ctx)
+
+	err = cursor.All(ctx, &loadedTopology)
+	if err != nil {
+		log.Printf("Failed marshalling %v", err)
+
+		return loadedTopology, errors.ErrCouldNotMarshall{StoreName: s.storeName}
+	}
+
+	return loadedTopology, nil
+}
+
+// AddLink adds a link to the link store.
+func (s *DatabaseTopologyStore) AddLink(link links.Link) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	_, err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		InsertOne(ctx, link)
+	if err != nil {
+		log.Printf("Could not add Link: %v", err)
+		return errors.ErrCouldNotCreate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// UpdateLink updates a existing link.
+func (s *DatabaseTopologyStore) UpdateLink(linkToUpdate links.Link) error {
+	var updatedLink links.Link
+
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	update := bson.D{primitive.E{Key: "$set", Value: linkToUpdate}}
+
+	upsert := false
+	after := options.After
+	opt := options.FindOneAndUpdateOptions{
+		Upsert:         &upsert,
+		ReturnDocument: &after,
+	}
+
+	err := client.Database(database.DatabaseName).
+		Collection(s.storeName).
+		FindOneAndUpdate(
+			ctx, bson.M{"_id": linkToUpdate.ID.String()}, update, &opt).
+		Decode(&updatedLink)
+	if err != nil {
+		log.Printf("Could not update link: %v", err)
+
+		return errors.ErrCouldNotUpdate{StoreName: s.storeName}
+	}
+
+	return nil
+}
+
+// DeleteLink deletes a link from the link store.
+func (s *DatabaseTopologyStore) DeleteLink(linkToDelete links.Link) error {
+	client, ctx, cancel := database.GetMongoConnection()
+	defer cancel()
+	defer client.Disconnect(ctx)
+
+	db := client.Database(database.DatabaseName)
+	collection := db.Collection(s.storeName)
+	_, err := collection.DeleteOne(ctx, bson.D{primitive.E{Key: linkToDelete.ID.String()}})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
-- 
GitLab