diff --git a/cli/cmd/subManagement.go b/cli/cmd/subManagement.go index ffade6c3a0904d1b8d408a7840788bc9cd4705a2..f34cb1949007c3a0470ee10265ffb9ae73433194 100644 --- a/cli/cmd/subManagement.go +++ b/cli/cmd/subManagement.go @@ -43,6 +43,8 @@ var subCmd = &cobra.Command{ Long: `The subscription command contains all sub-commands for subscription management. It has no functionality in itself.`, } +var subUUID string + func init() { rootCmd.AddCommand(subCmd) } diff --git a/cli/cmd/subManagementAdd.go b/cli/cmd/subManagementAdd.go new file mode 100644 index 0000000000000000000000000000000000000000..e61ffbfbe5b2f80724bebb48e9298ba00f6407f1 --- /dev/null +++ b/cli/cmd/subManagementAdd.go @@ -0,0 +1,69 @@ +/* +Copyright © 2021 da/net Research Group <danet@h-da.de> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +package cmd + +import ( + "code.fbi.h-da.de/danet/gosdn/controller/api" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// subAddCmd represents the add one subscription command. +var subAddCmd = &cobra.Command{ + Use: "add", + Short: "Add creates a new subscription for the network element matching the provided ID using the provided subscribe options.", + Long: "Add creates a new subscription for the network element matching the provided ID using the provided subscribe options.", + Run: func(cmd *cobra.Command, args []string) { + spinner, _ := pterm.DefaultSpinner.Start("Adding subscription") + + // Note: Currently, doesn't support providing options/paths. + _, err := api.Add(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), mneUUID, nil) + if err != nil { + spinner.Fail(err) + return + } + spinner.Success() + }, + + PostRun: func(cmd *cobra.Command, args []string) { + // Necessary for prompt mode. The flag variables have to be resetted, + // since in prompt mode the program keeps running. + mneUUID = "" + }, +} + +func init() { + subCmd.AddCommand(subAddCmd) + + subAddCmd.Flags().StringVar(&mneUUID, "uuid", "", "network element UUID") +} diff --git a/cli/cmd/subManagementDelete.go b/cli/cmd/subManagementDelete.go new file mode 100644 index 0000000000000000000000000000000000000000..ed161dc1f1e4c3c5a4e89443b189b668bebda419 --- /dev/null +++ b/cli/cmd/subManagementDelete.go @@ -0,0 +1,68 @@ +/* +Copyright © 2021 da/net Research Group <danet@h-da.de> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +package cmd + +import ( + "code.fbi.h-da.de/danet/gosdn/controller/api" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// subDeleteCmd represents the delete one subscription command. +var subDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Stops and removes one running gNMI subscription.", + Long: "Stops and removes one running gNMI subscription.", + Run: func(cmd *cobra.Command, args []string) { + spinner, _ := pterm.DefaultSpinner.Start("Removing subscription") + + _, err := api.Delete(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), subUUID) + if err != nil { + pterm.Error.Println(err) + return + } + + spinner.Success() + }, + PostRun: func(cmd *cobra.Command, args []string) { + // Necessary for prompt mode. The flag variables have to be resetted, + // since in prompt mode the program keeps running. + subUUID = "" + }, +} + +func init() { + subCmd.AddCommand(subDeleteCmd) + + subDeleteCmd.Flags().StringVar(&subUUID, "uuid", "", "UUID of the subcription") +} diff --git a/cli/cmd/subManagementGet.go b/cli/cmd/subManagementGet.go new file mode 100644 index 0000000000000000000000000000000000000000..32910766c61e0784362251d6ba6b3924d99a0ef4 --- /dev/null +++ b/cli/cmd/subManagementGet.go @@ -0,0 +1,94 @@ +/* +Copyright © 2021 da/net Research Group <danet@h-da.de> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +package cmd + +import ( + "fmt" + + "code.fbi.h-da.de/danet/gosdn/controller/api" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// subGetCmd represents the get one subscription command. +var subGetCmd = &cobra.Command{ + Use: "get", + Short: "Fetches details about one specific currently running gNMI subscriptions.", + Long: "Fetches details about one specific currently running gNMI subscriptions.", + Run: func(cmd *cobra.Command, args []string) { + spinner, _ := pterm.DefaultSpinner.Start("Fetching subscription") + + resp, err := api.Get(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), subUUID) + if err != nil { + pterm.Error.Println(err) + return + } + spinner.Success() + + data1 := pterm.TableData{[]string{"SubUUID", "MneID", "MneName", "PndID"}} + data1 = append(data1, []string{resp.Subscriptions.Subid, resp.Subscriptions.Mneid, resp.Subscriptions.MneName, resp.Subscriptions.Pid}) + err = pterm.DefaultTable.WithHasHeader().WithData(data1).Render() + if err != nil { + return + } + + pterm.Print(pterm.FgLightCyan.Sprintf("Subscribe Options\n")) + data2 := pterm.TableData{[]string{"GnmiMode", "GnmiStreamMode", "SampleInterval (ns)"}} + data2 = append(data2, []string{resp.Subscriptions.SubscribeOptions.GnmiMode, resp.Subscriptions.SubscribeOptions.GnmiStreamMode, fmt.Sprint(resp.Subscriptions.SubscribeOptions.SampleInterval)}) + err = pterm.DefaultTable.WithHasHeader().WithData(data2).Render() + if err != nil { + return + } + + data3 := pterm.TableData{[]string{"Paths"}} + for _, path := range resp.Subscriptions.Paths { + data3 = append(data3, path.Elem) + } + + err = pterm.DefaultTable.WithHasHeader().WithData(data3).Render() + if err != nil { + return + } + }, + PostRun: func(cmd *cobra.Command, args []string) { + // Necessary for prompt mode. The flag variables have to be resetted, + // since in prompt mode the program keeps running. + subUUID = "" + }, +} + +func init() { + subCmd.AddCommand(subGetCmd) + + subGetCmd.Flags().StringVar(&subUUID, "uuid", "", "UUID of the subcription") +} diff --git a/controller/api/subManagement.go b/controller/api/subManagement.go index f439d2413420593e662b10cb191bf647965e7721..afba1a831c60e336e06db5a49da6fab098f2e00e 100644 --- a/controller/api/subManagement.go +++ b/controller/api/subManagement.go @@ -35,3 +35,49 @@ func GetAll(ctx context.Context, addr string) (*subpb.GetAllResponse, error) { return subClient.GetAll(ctx, req) } + +// Get fetches detailed info for one specific available subscription. +func Get(ctx context.Context, addr string, subID string) (*subpb.GetResponse, error) { + subClient, err := nbi.SubManagementClient(addr, dialOptions...) + if err != nil { + return nil, err + } + + req := &subpb.GetRequest{ + Timestamp: time.Now().UnixNano(), + Subid: subID, + } + + return subClient.Get(ctx, req) +} + +// Delete stops and removes one specific running subscription is a no-op if object connected to ID does not exist. +func Delete(ctx context.Context, addr string, subID string) (*subpb.DeleteResponse, error) { + subClient, err := nbi.SubManagementClient(addr, dialOptions...) + if err != nil { + return nil, err + } + + req := &subpb.DeleteRequest{ + Timestamp: time.Now().UnixNano(), + Subid: subID, + } + + return subClient.Delete(ctx, req) +} + +// Add creates a new subscription for the network element matching the provided ID using the provided subscribe options. +func Add(ctx context.Context, addr string, mneID string, subscribeOptions *subpb.Subscription) (*subpb.AddResponse, error) { + subClient, err := nbi.SubManagementClient(addr, dialOptions...) + if err != nil { + return nil, err + } + + req := &subpb.AddRequest{ + Timestamp: time.Now().UnixNano(), + Mneid: mneID, + Subscription: subscribeOptions, + } + + return subClient.Add(ctx, req) +} diff --git a/controller/controller.go b/controller/controller.go index b46d77770e7e3a94b4684e826aebebd2e300d60f..a1a24aaf60deb2c47f7b2bf4dc9aa0e37bc66fa4 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -361,6 +361,9 @@ func ensureAdminRoleExists() error { "/gosdn.topology.TopologyService/DeleteLink", "/gosdn.subscriptionmanagement.SubscriptionManagementService/ResetAllSubscriptions", "/gosdn.subscriptionmanagement.SubscriptionManagementService/GetAll", + "/gosdn.subscriptionmanagement.SubscriptionManagementService/Get", + "/gosdn.subscriptionmanagement.SubscriptionManagementService/Delete", + "/gosdn.subscriptionmanagement.SubscriptionManagementService/Add", })) if err != nil { return err diff --git a/controller/northbound/server/submanagement.go b/controller/northbound/server/submanagement.go index eb75420e7a482e753895e46d11ae687d7f10adee..762fffe8cc5b3858070c6df06f8d6bfb4597e336 100644 --- a/controller/northbound/server/submanagement.go +++ b/controller/northbound/server/submanagement.go @@ -6,6 +6,8 @@ import ( subpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/subscriptionmanagement" "code.fbi.h-da.de/danet/gosdn/controller/nucleus" + "code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi" + "github.com/google/uuid" ) // SubManagementServer represents a SubsriptionManagementServer. @@ -67,3 +69,103 @@ func (s *SubManagementServer) GetAll(ctx context.Context, request *subpb.GetAllR Subscriptions: subInfosToReturn, }, nil } + +// Get fetches detailed info for one specific available subscription. +func (s *SubManagementServer) Get(ctx context.Context, request *subpb.GetRequest) (*subpb.GetResponse, error) { + subUUID, err := uuid.Parse(request.GetSubid()) + if err != nil { + return &subpb.GetResponse{ + Timestamp: time.Now().UnixNano(), + }, err + } + + info, err := s.networkElementWatchter.GetSubscriptionInformations(subUUID) + if err != nil { + return &subpb.GetResponse{ + Timestamp: time.Now().UnixNano(), + }, err + } + + var pathsToReturn []*subpb.Path + for _, path := range info.Opts.Paths { + var elems []string + for _, elem := range path { + elems = append(elems, elem) + } + + pathsToReturn = append(pathsToReturn, &subpb.Path{Elem: elems}) + } + + return &subpb.GetResponse{ + Timestamp: time.Now().UnixNano(), + Subscriptions: &subpb.Subscription{ + Subid: info.SubID, + Pid: info.PndID, + Mneid: info.MneID, + MneName: info.MneName, + Paths: pathsToReturn, + SubscribeOptions: &subpb.SubscribeOptions{ + GnmiMode: info.Opts.Mode, + GnmiStreamMode: info.Opts.StreamMode, + SampleInterval: info.Opts.SampleInterval, + }, + }, + }, nil +} + +// Delete stops and removes one specific running subscription is a no-op if object connected to ID does not exist. +func (s *SubManagementServer) Delete(ctx context.Context, request *subpb.DeleteRequest) (*subpb.DeleteResponse, error) { + subUUID, err := uuid.Parse(request.GetSubid()) + if err != nil { + return &subpb.DeleteResponse{ + Timestamp: time.Now().UnixNano(), + }, err + } + + s.networkElementWatchter.StopAndRemoveNetworkElementSubscription(subUUID) + + return &subpb.DeleteResponse{ + Timestamp: time.Now().UnixNano(), + }, nil +} + +// Add creates a new subscription for the network element matching the provided ID using the provided subscribe options. +func (s *SubManagementServer) Add(ctx context.Context, request *subpb.AddRequest) (*subpb.AddResponse, error) { + mneID, err := uuid.Parse(request.GetMneid()) + if err != nil { + return &subpb.AddResponse{ + Timestamp: time.Now().UnixNano(), + }, err + } + + var gNMISubScribeOptions *gnmi.SubscribeOptions + if request.Subscription != nil { + var paths [][]string + for _, path := range request.Subscription.Paths { + var elems []string + for _, elem := range path.Elem { + elems = append(elems, elem) + } + paths = append(paths, elems) + } + gNMISubScribeOptions = &gnmi.SubscribeOptions{ + Mode: request.Subscription.SubscribeOptions.GnmiMode, + StreamMode: request.Subscription.SubscribeOptions.GnmiStreamMode, + SampleInterval: request.Subscription.SubscribeOptions.SampleInterval, + Paths: paths, + } + } + + err = s.networkElementWatchter.SubscribeToNetworkElementWithID(mneID, + gNMISubScribeOptions, + ) + if err != nil { + return &subpb.AddResponse{ + Timestamp: time.Now().UnixNano(), + }, err + } + + return &subpb.AddResponse{ + Timestamp: time.Now().UnixNano(), + }, nil +} diff --git a/controller/nucleus/networkElementWatcher.go b/controller/nucleus/networkElementWatcher.go index dd8713aa834ce8156ce423f98a7a9e40e328fb8e..8b542d086ca5c61e7fb5d776137c6c11260fd858 100644 --- a/controller/nucleus/networkElementWatcher.go +++ b/controller/nucleus/networkElementWatcher.go @@ -14,6 +14,7 @@ import ( "code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement" "code.fbi.h-da.de/danet/gosdn/controller/interfaces/transport" "code.fbi.h-da.de/danet/gosdn/controller/nucleus/types" + "code.fbi.h-da.de/danet/gosdn/controller/store" "code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi" "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" @@ -104,6 +105,27 @@ func (n *NetworkElementWatcher) SubscribeToNetworkElement(mne networkelement.Net n.subscribeToNetworkElement(mne, opts) } +// SubscribeToNetworkElementWithID subscribes to the network element matching the provided ID according to provided SubscribeOptions. +// SubscribeOptions can be nil. Use nil for a fixed, pre-defined set of gNMI subscription options (streaming in sample mode each second). +func (n *NetworkElementWatcher) SubscribeToNetworkElementWithID(mneID uuid.UUID, opts *gnmi.SubscribeOptions) error { + if opts == nil { + opts = &gnmi.SubscribeOptions{ + Mode: gNMISubscribeMode, + StreamMode: gNMIStreamMode, + SampleInterval: subscribeSampleInterval, + } + } + + mne, err := n.mneService.Get(store.Query{ID: mneID}) + if err != nil { + log.Error(err) + return err + } + + n.subscribeToNetworkElement(mne, opts) + return nil +} + func (n *NetworkElementWatcher) subscribeToNetworkElement(mne networkelement.NetworkElement, opts *gnmi.SubscribeOptions) { subID := uuid.New()