This repository has been archived by the owner on Jul 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
1b94fbf
commit 1198792
Showing
7 changed files
with
451 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.