Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Commit

Permalink
cli: add verify command (#4639)
Browse files Browse the repository at this point in the history
Adds `osm verify` command to verify the correctness
of the system for specific scenarios. Adds the stubs
to verify pod to pod connectivity configs.

Part of #4634

Signed-off-by: Shashank Ram <[email protected]>
  • Loading branch information
shashankram authored and nojnhuh committed Apr 14, 2022
1 parent 1b94fbf commit 1198792
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/cli/osm.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func newRootCmd(config *action.Configuration, stdin io.Reader, stdout io.Writer,
newPolicyCmd(stdout, stderr),
newSupportCmd(config, stdout, stderr),
newUninstallCmd(config, stdin, stdout),
newVerifyCmd(stdout, stderr),
)

// Add subcommands related to unmanaged environments
Expand Down
24 changes: 24 additions & 0 deletions cmd/cli/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"io"

"github.com/spf13/cobra"
)

const verifyDescription = `
This command consists of multiple subcommands related to verifying
mesh configurations.
`

func newVerifyCmd(stdout io.Writer, stderr io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
Short: "verify mesh configurations",
Long: verifyDescription,
Args: cobra.NoArgs,
}
cmd.AddCommand(newVerifyConnectivityCmd(stdout, stderr))

return cmd
}
100 changes: 100 additions & 0 deletions cmd/cli/verify_connectivity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"fmt"
"io"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"

"github.com/openservicemesh/osm/pkg/cli/verifier"
"github.com/openservicemesh/osm/pkg/constants"
"github.com/openservicemesh/osm/pkg/k8s"
)

const verifyConnectivityDescription = `
This command consists of multiple subcommands related to verifying
connectivity related configurations.
`

var (
fromPod string
toPod string
)

type verifyConnectCmd struct {
stdout io.Writer
stderr io.Writer
kubeClient kubernetes.Interface
srcPod types.NamespacedName
dstPod types.NamespacedName
appProtocol string
meshName string
}

func newVerifyConnectivityCmd(stdout io.Writer, stderr io.Writer) *cobra.Command {
verifyCmd := &verifyConnectCmd{
stdout: stdout,
stderr: stderr,
}

cmd := &cobra.Command{
Use: "connectivity",
Short: "verify connectivity between a pod and a destination",
Long: verifyConnectivityDescription,
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config, err := settings.RESTClientGetter().ToRESTConfig()
if err != nil {
return errors.Errorf("Error fetching kubeconfig: %s", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return errors.Errorf("Could not access Kubernetes cluster, check kubeconfig: %s", err)
}
verifyCmd.kubeClient = clientset

namespacedName, err := k8s.NamespacedNameFrom(fromPod)
if err != nil {
return errors.Errorf("Source must be a namespaced name of the form <namespace>/<name>, got %s", fromPod)
}
verifyCmd.srcPod = namespacedName
namespacedName, err = k8s.NamespacedNameFrom(toPod)
if err != nil {
return errors.Errorf("Destination must be a namespaced name of the form <namespace>/<name>, got %s", toPod)
}
verifyCmd.dstPod = namespacedName

return verifyCmd.run()
},
}

f := cmd.Flags()
f.StringVar(&fromPod, "from-pod", "", "Namespaced name of client pod: <namespace>/<name>")
//nolint: errcheck
//#nosec G104: Errors unhandled
cmd.MarkFlagRequired("from-pod")
f.StringVar(&toPod, "to-pod", "", "Namespaced name of destination pod: <namespace>/<name>")
//nolint: errcheck
//#nosec G104: Errors unhandled
cmd.MarkFlagRequired("to-pod")
f.StringVar(&verifyCmd.appProtocol, "app-protocol", constants.ProtocolHTTP, "Application protocol")
f.StringVar(&verifyCmd.meshName, "mesh-name", defaultMeshName, "Mesh name")

return cmd
}

func (cmd *verifyConnectCmd) run() error {
podConnectivityVerifier := verifier.NewPodConnectivityVerifier(cmd.stdout, cmd.stderr, cmd.kubeClient,
cmd.srcPod, cmd.dstPod, cmd.appProtocol, cmd.meshName)
result := podConnectivityVerifier.Run()

fmt.Fprintln(cmd.stdout, "---------------------------------------------")
verifier.Print(result, cmd.stdout)
fmt.Fprintln(cmd.stdout, "---------------------------------------------")

return nil
}
50 changes: 50 additions & 0 deletions pkg/cli/verifier/connectivity_pod_to_pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package verifier

import (
"fmt"
"io"

"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
)

// PodConnectivityVerifier implements the Verifier interface for pod connectivity
type PodConnectivityVerifier struct {
stdout io.Writer
stderr io.Writer
kubeClient kubernetes.Interface
srcPod types.NamespacedName
dstPod types.NamespacedName
appProtocol string
meshName string
}

// NewPodConnectivityVerifier implements verification for pod connectivity
func NewPodConnectivityVerifier(stdout io.Writer, stderr io.Writer, kubeClient kubernetes.Interface,
srcPod types.NamespacedName, dstPod types.NamespacedName, appProtocol string, meshName string) Verifier {
return &PodConnectivityVerifier{
stdout: stdout,
stderr: stderr,
kubeClient: kubeClient,
srcPod: srcPod,
dstPod: dstPod,
appProtocol: appProtocol,
meshName: meshName,
}
}

// Run executes the pod connectivity verifier
func (v *PodConnectivityVerifier) Run() Result {
ctx := fmt.Sprintf("Verify if pod %q can access pod %q for app protocol %q", v.srcPod, v.dstPod, v.appProtocol)

verifiers := Set{
// ---
// Verify prerequisites
//
// Namespace monitor verification
NewNamespaceMonitorVerifier(v.stdout, v.stderr, v.kubeClient, v.srcPod.Namespace, v.meshName),
NewNamespaceMonitorVerifier(v.stdout, v.stderr, v.kubeClient, v.dstPod.Namespace, v.meshName),
}

return verifiers.Run(ctx)
}
120 changes: 120 additions & 0 deletions pkg/cli/verifier/connectivity_pod_to_pod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package verifier

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"

"github.com/openservicemesh/osm/pkg/constants"
)

func TestRun(t *testing.T) {
testMeshName := "test"

testCases := []struct {
name string
resources []runtime.Object
srcPod types.NamespacedName
dstPod types.NamespacedName
expected Result
}{
{
name: "pods have config to communicate",
resources: []runtime.Object{
&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns1",
},
},
&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns2",
},
},
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns1",
Labels: map[string]string{
constants.OSMKubeResourceMonitorAnnotation: testMeshName,
},
},
},
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns2",
Labels: map[string]string{
constants.OSMKubeResourceMonitorAnnotation: testMeshName,
},
},
},
},
srcPod: types.NamespacedName{Namespace: "ns1", Name: "pod1"},
dstPod: types.NamespacedName{Namespace: "ns2", Name: "pod2"},
expected: Result{
Status: Success,
},
},
{
name: "pod doesn't belong to monitored namespace",
resources: []runtime.Object{
&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns1",
},
},
&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns2",
},
},
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns1",
Labels: map[string]string{
constants.OSMKubeResourceMonitorAnnotation: testMeshName,
},
},
},
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns2", // not monitored
},
},
},
srcPod: types.NamespacedName{Namespace: "ns1", Name: "pod1"},
dstPod: types.NamespacedName{Namespace: "ns2", Name: "pod2"},
expected: Result{
Status: Failure,
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := assert.New(t)

fakeClient := fake.NewSimpleClientset(tc.resources...)
v := &PodConnectivityVerifier{
srcPod: tc.srcPod,
dstPod: tc.dstPod,
kubeClient: fakeClient,
meshName: testMeshName,
}

actual := v.Run()
out := new(bytes.Buffer)
Print(actual, out)
a.Equal(tc.expected.Status, actual.Status, out)
})
}
}
63 changes: 63 additions & 0 deletions pkg/cli/verifier/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package verifier

import (
"context"
"fmt"
"io"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/openservicemesh/osm/pkg/constants"
)

// NamespaceMonitorVerifier implements the Verifier interface for pod connectivity
type NamespaceMonitorVerifier struct {
stdout io.Writer
stderr io.Writer
kubeClient kubernetes.Interface
namespace string
meshName string
}

// NewNamespaceMonitorVerifier implements verification for namespace monitoring
func NewNamespaceMonitorVerifier(stdout io.Writer, stderr io.Writer, kubeClient kubernetes.Interface, namespace string, meshName string) Verifier {
return &NamespaceMonitorVerifier{
stdout: stdout,
stderr: stderr,
kubeClient: kubeClient,
namespace: namespace,
meshName: meshName,
}
}

// Run executes the namespace monitor verification
func (v *NamespaceMonitorVerifier) Run() Result {
result := Result{
Context: fmt.Sprintf("Verify if namespace %q is monitored", v.namespace),
}

ns, err := v.kubeClient.CoreV1().Namespaces().Get(context.Background(), v.namespace, metav1.GetOptions{})
if err != nil {
result.Status = Failure
result.Reason = fmt.Sprintf("Error fetching namespace %q", v.namespace)
return result
}

annotatedMeshName, ok := ns.Labels[constants.OSMKubeResourceMonitorAnnotation]
if !ok {
result.Status = Failure
result.Reason = fmt.Sprintf("Missing label %q on namespace %q", constants.OSMKubeResourceMonitorAnnotation, v.namespace)
result.Suggestion = fmt.Sprintf("Add label %q on namespace %q to include it in the mesh and restart the app", constants.OSMKubeResourceMonitorAnnotation, v.namespace)
return result
}
if annotatedMeshName != v.meshName {
result.Status = Failure
result.Reason = fmt.Sprintf("Expected label %q to have value %q, got %q",
constants.OSMKubeResourceMonitorAnnotation, v.meshName, annotatedMeshName)
return result
}

result.Status = Success
return result
}
Loading

0 comments on commit 1198792

Please sign in to comment.