Skip to content

Commit

Permalink
Recover broker-info.subm file
Browse files Browse the repository at this point in the history
This PR does the following:

1. Adds a `subctl recover-broker-info` command to recover
   broker-info.subm file from an existing cluster setup
2. Adds logic to collect necessary information from Broker and
   Submariner clusters to populate broker-info.subm file
3. Add system tests for various scenarios

Epic: submariner-io/enhancements#143

Signed-off-by: Janki Chhatbar <[email protected]>
  • Loading branch information
Jaanki committed Mar 15, 2023
1 parent 0df6bfe commit f9c18d3
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 0 deletions.
176 changes: 176 additions & 0 deletions cmd/subctl/recover_broker_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
SPDX-License-Identifier: Apache-2.0
Copyright Contributors to the Submariner project.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package subctl

import (
"errors"
"fmt"

"github.com/spf13/cobra"
"github.com/submariner-io/admiral/pkg/reporter"
"github.com/submariner-io/subctl/internal/cli"
"github.com/submariner-io/subctl/internal/constants"
"github.com/submariner-io/subctl/internal/exit"
"github.com/submariner-io/subctl/internal/restconfig"
"github.com/submariner-io/subctl/pkg/broker"
"github.com/submariner-io/subctl/pkg/cluster"
"github.com/submariner-io/submariner-operator/api/v1alpha1"
submarinerv1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)

var recoverRestConfigProducer = restconfig.NewProducer().WithPrefixedContext("broker").
WithDefaultNamespace(constants.DefaultBrokerNamespace).
WithPrefixedNamespace("broker", constants.DefaultBrokerNamespace)

// recoverBrokerInfo represents the reconstruct command.
var recoverBrokerInfo = &cobra.Command{
Use: "recover-broker-info",
Short: "Recovers the broker-info.subm file from the installed Broker",
Run: func(cmd *cobra.Command, args []string) {
status := cli.NewReporter()
// if --brokerconfig flag provided, get the broker and proceed to get Submariner
contextFound, err := recoverRestConfigProducer.RunOnSelectedPrefixedContext("broker",
recoverBrokerFromConfigContext, status)
exit.OnError(err)

// if --brokerconfig not provided, search for broker on current context and proceed to get Submariner
if !contextFound {
err = recoverRestConfigProducer.RunOnSelectedContext(recoverBrokerFromCurrentContext, status)
}
exit.OnError(err)
},
}

func init() {
recoverRestConfigProducer.SetupFlags(recoverBrokerInfo.Flags())
rootCmd.AddCommand(recoverBrokerInfo)
}

func recoverBrokerFromConfigContext(brokerCluster *cluster.Info, brokerNamespace string, status reporter.Interface) error {
brokerObj, err := getBroker(brokerCluster, brokerNamespace, "Please try again with another cluster", status)
if err != nil {
return err
}

clusters, err := brokerCluster.GetClusters(brokerNamespace)
if err != nil {
return status.Error(err, "error listing joined clusters")
}

ok, err := tryToRecoverFromBroker(brokerCluster, brokerObj, brokerNamespace, clusters, status)
if ok || err != nil {
return err
}

status.Warning("Submariner is not installed on the same cluster as Broker")
status.Start("Checking if Submariner is installed on a different cluster")
//nolint:wrapcheck // No need to wrap errors here.
return recoverRestConfigProducer.RunOnSelectedContext(
func(submCluster *cluster.Info, namespace string, status reporter.Interface) error {
if isSubmJoinedToBroker(clusters, submCluster) {
status.Success("Found a Submariner installation on cluster %q joined to the Broker", submCluster.Name)
//nolint:wrapcheck // No need to wrap errors here.
return broker.RecoverData(brokerCluster, submCluster, brokerObj, namespace, status)
}

return status.Error(
fmt.Errorf("submariner is not installed on cluster %s. "+
"Please specify the cluster where Submariner is installed via `--kubeconfig` or `--context` flag"+
"", submCluster.Name), "")
}, status)
}

func recoverBrokerFromCurrentContext(clusterInfo *cluster.Info, namespace string, status reporter.Interface) error {
// if --brokerconfig not provided, search for broker on current context and proceed to get Submariner
brokerObj, err := getBroker(clusterInfo, namespace, "Please specify the cluster where the Broker is installed "+
"via the `--brokerconfig` or `--brokercontext` flag and the namespace via the `--brokernamespace` flag", status)
if err != nil {
return err
}

clusters, err := clusterInfo.GetClusters(namespace)
if err != nil {
return status.Error(err, "error listing joined clusters")
}

ok, err := tryToRecoverFromBroker(clusterInfo, brokerObj, namespace, clusters, status)
if ok || err != nil {
return err
}

return status.Error(
fmt.Errorf("submariner is not installed on cluster %q. "+
"Please specify the cluster where Submariner is installed via `--kubeconfig` or `--context` flag"+
"", clusterInfo.Name), "")
}

func getBroker(clusterInfo *cluster.Info, namespace, notFoundMsg string, status reporter.Interface) (*v1alpha1.Broker, error) {
status.Start("Checking if the Broker is installed on cluster %q in namespace %q", clusterInfo.Name, namespace)

brokerObj, err := clusterInfo.GetBroker(namespace)
if apierrors.IsNotFound(err) {
return nil, status.Error(fmt.Errorf("the Broker is not installed on the specified cluster in namespace %s. %s", notFoundMsg,
namespace), "")
}

if err != nil {
return nil, status.Error(err, "")
}

status.End()

return brokerObj, nil
}

func tryToRecoverFromBroker(brokerCluster *cluster.Info, brokerObj *v1alpha1.Broker,
brokerNamespace string, clusters []submarinerv1.Cluster, status reporter.Interface,
) (bool, error) {
status.Start("Checking if there are any clusters joined to the Broker")

if len(clusters) == 0 {
return false, status.Error(
errors.New(
"no clusters are joined to the Broker. Please re-run the `deploy-broker` command to regenerate the broker-info."+
"subm file"), "")
}

status.Success("Found %d cluster(s) joined to the Broker", len(clusters))
status.End()

if isSubmJoinedToBroker(clusters, brokerCluster) {
status.Success("Found a local Submariner installation joined to the Broker")
//nolint:wrapcheck // No need to wrap errors here.
return true, broker.RecoverData(brokerCluster, brokerCluster, brokerObj, brokerNamespace, status)
}

return false, nil
}

func isSubmJoinedToBroker(clusters []submarinerv1.Cluster, clusterInfo *cluster.Info) bool {
if clusterInfo.Submariner != nil {
for i := range clusters {
if clusters[i].Spec.ClusterID == clusterInfo.Submariner.Spec.ClusterID {
return true
}
}
}

return false
}
76 changes: 76 additions & 0 deletions pkg/broker/recover_broker_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
SPDX-License-Identifier: Apache-2.0
Copyright Contributors to the Submariner project.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package broker

import (
"context"
"encoding/base64"

"github.com/submariner-io/admiral/pkg/reporter"
"github.com/submariner-io/subctl/internal/constants"
"github.com/submariner-io/subctl/internal/rbac"
"github.com/submariner-io/subctl/pkg/cluster"
"github.com/submariner-io/submariner-operator/api/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func RecoverData(
brokerCluster, submCluster *cluster.Info, broker *v1alpha1.Broker, namespace string, status reporter.Interface,
) error {
status.Start("Retrieving data to reconstruct broker-info.subm")
defer status.End()

data := &Info{}
var err error

data.BrokerURL = brokerCluster.RestConfig.Host + brokerCluster.RestConfig.APIPath

data.ClientToken, err = rbac.GetClientTokenSecret(
context.TODO(), brokerCluster.ClientProducer.ForKubernetes(), namespace,
constants.SubmarinerBrokerAdminSA,
)
if err != nil {
return status.Error(err, "error getting broker client secret")
}

data.Components = broker.Spec.Components
data.ServiceDiscovery = data.IsServiceDiscoveryEnabled()
data.CustomDomains = &broker.Spec.DefaultCustomDomains

status.Success("Retrieving IPSec PSK secret from Submariner found on cluster %s", submCluster.Name)

decodedPSKSecret, err := base64.StdEncoding.DecodeString(submCluster.Submariner.Spec.CeIPSecPSK)
if err != nil {
return status.Error(err, "error decoding the secret")
}

data.IPSecPSK = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: ipsecPSKSecretName,
},
Data: map[string][]byte{"psk": decodedPSKSecret},
}

status.Success("Successfully retrieved the data. Writing it to broker-info.subm")

err = data.writeToFile("broker-info.subm")

return status.Error(err, "error reconstructing broker-info.subm")
}
23 changes: 23 additions & 0 deletions pkg/cluster/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/pkg/errors"
"github.com/submariner-io/subctl/internal/constants"
"github.com/submariner-io/subctl/pkg/brokercr"
"github.com/submariner-io/subctl/pkg/client"
"github.com/submariner-io/subctl/pkg/image"
"github.com/submariner-io/submariner-operator/api/v1alpha1"
Expand Down Expand Up @@ -189,6 +190,28 @@ func (c *Info) OperatorNamespace() string {
return constants.OperatorNamespace
}

func (c *Info) GetBroker(namespace string) (*v1alpha1.Broker, error) {
broker := &v1alpha1.Broker{}
err := c.ClientProducer.ForGeneral().Get(
context.TODO(), controllerClient.ObjectKey{
Namespace: namespace,
Name: brokercr.Name,
}, broker)

return broker, errors.Wrap(err, "error retrieving Broker")
}

func (c *Info) GetClusters(namespace string) ([]submarinerv1.Cluster, error) {
clusters := &submarinerv1.ClusterList{}

err := c.ClientProducer.ForGeneral().List(context.TODO(), clusters, controllerClient.InNamespace(namespace))
if err != nil {
return nil, errors.Wrap(err, "error retrieving Clusters")
}

return clusters.Items, nil
}

var validOverrides = []string{
names.OperatorComponent,
names.GatewayComponent,
Expand Down
39 changes: 39 additions & 0 deletions scripts/test/system.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,46 @@ _subctl cloud prepare generic --context cluster1
# Deprecated variant
_subctl cloud prepare generic --kubecontext cluster1 && exit 1

# Rename old broker-info.subm to be able to compare it with the recovered broker-info.sum
echo "Renaming broker-info.subm to broker-info.subm.orig"
mv "${DAPPER_SOURCE}"/output/broker-info.subm "${DAPPER_SOURCE}"/output/broker-info.subm.orig

# Test subctl recover-broker-info invocations with both Broker and Submariner installed on the cluster
_subctl recover-broker-info --brokercontext cluster1 --context cluster1
cmp "${DAPPER_SOURCE}"/output/broker-info.subm.orig broker-info.subm
rm -f broker-info.subm

_subctl recover-broker-info --context cluster1
cmp "${DAPPER_SOURCE}"/output/broker-info.subm.orig broker-info.subm
rm -f broker-info.subm

# Failure tests
# Invalid Broker namespace
_subctl recover-broker-info --namespace non-existent --context cluster1 && exit 1
_subctl recover-broker-info --brokernamespace non-existent --brokercontext cluster1 && exit 1

# No Broker installed
_subctl recover-broker-info --brokercontext cluster2 && exit 1
_subctl recover-broker-info --context cluster2 && exit 1

# Test subctl uninstall invocations
_subctl uninstall -y --kubeconfig "${KUBECONFIGS_DIR}"/kind-config-cluster1

# Test subctl recover-broker-info invocations with Submariner not installed on Broker cluster
_subctl recover-broker-info --brokercontext cluster1 --context cluster2
cmp "${DAPPER_SOURCE}"/output/broker-info.subm.orig broker-info.subm
rm -f broker-info.subm

_subctl recover-broker-info --brokercontext cluster1 --context cluster1 && exit 1
_subctl recover-broker-info --context cluster1 && exit 1

# Test subctl uninstall invocations
_subctl uninstall -y --context cluster2

# Test subctl recover-broker-info invocations with Submariner not installed on any cluster
_subctl recover-broker-info --brokercontext cluster1 --context cluster1 && exit 1
_subctl recover-broker-info --brokercontext cluster1 --context cluster2 && exit 1
_subctl recover-broker-info --context cluster1 && exit 1

# Uninstall Broker
_subctl uninstall -y --kubeconfig "${KUBECONFIGS_DIR}"/kind-config-cluster1

0 comments on commit f9c18d3

Please sign in to comment.