From dc32216daa65bb07b02c60e1e63a781bc6030246 Mon Sep 17 00:00:00 2001 From: yunlzheng Date: Thu, 12 Mar 2020 19:54:32 +0800 Subject: [PATCH] test: add test case for run command --- pkg/fake/kt/action/action_mock.go | 8 +- pkg/fake/kt/kt_mock.go | 5 +- pkg/kt/cluster/kubernetes.go | 118 +++++++++++++--------------- pkg/kt/cluster/types.go | 2 +- pkg/kt/command/run.go | 39 +++++----- pkg/kt/command/run_test.go | 125 ++++++++++++++++++++++++++++-- pkg/kt/command/types.go | 2 +- pkg/kt/connect/inbound.go | 5 +- pkg/kt/options/options.go | 15 +++- pkg/kt/types.go | 11 ++- 10 files changed, 225 insertions(+), 105 deletions(-) diff --git a/pkg/fake/kt/action/action_mock.go b/pkg/fake/kt/action/action_mock.go index 85c4b9c..2787f13 100644 --- a/pkg/fake/kt/action/action_mock.go +++ b/pkg/fake/kt/action/action_mock.go @@ -78,17 +78,17 @@ func (mr *MockActionInterfaceMockRecorder) Check(cli interface{}) *gomock.Call { } // Run mocks base method -func (m *MockActionInterface) Run(service string, options *options.DaemonOptions) error { +func (m *MockActionInterface) Run(service string, cli kt.CliInterface, options *options.DaemonOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Run", service, options) + ret := m.ctrl.Call(m, "Run", service, cli, options) ret0, _ := ret[0].(error) return ret0 } // Run indicates an expected call of Run -func (mr *MockActionInterfaceMockRecorder) Run(service, options interface{}) *gomock.Call { +func (mr *MockActionInterfaceMockRecorder) Run(service, cli, options interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockActionInterface)(nil).Run), service, options) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockActionInterface)(nil).Run), service, cli, options) } // Exchange mocks base method diff --git a/pkg/fake/kt/kt_mock.go b/pkg/fake/kt/kt_mock.go index 74a6c95..f6de581 100644 --- a/pkg/fake/kt/kt_mock.go +++ b/pkg/fake/kt/kt_mock.go @@ -37,11 +37,12 @@ func (m *MockCliInterface) EXPECT() *MockCliInterfaceMockRecorder { } // Kubernetes mocks base method -func (m *MockCliInterface) Kubernetes() cluster.KubernetesInterface { +func (m *MockCliInterface) Kubernetes() (cluster.KubernetesInterface, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Kubernetes") ret0, _ := ret[0].(cluster.KubernetesInterface) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Kubernetes indicates an expected call of Kubernetes diff --git a/pkg/kt/cluster/kubernetes.go b/pkg/kt/cluster/kubernetes.go index 11faadd..ed2b0e4 100644 --- a/pkg/kt/cluster/kubernetes.go +++ b/pkg/kt/cluster/kubernetes.go @@ -33,14 +33,63 @@ func (k *Kubernetes) Scale(deployment *appV1.Deployment, replicas *int32) (err e } // Deployment get deployment -func (k *Kubernetes) Deployment(name, namespace string) (deployment *appV1.Deployment, err error) { - deployment, err = k.Clientset.AppsV1().Deployments(namespace).Get(name, metaV1.GetOptions{}) - return +func (k *Kubernetes) Deployment(name, namespace string) (*appV1.Deployment, error) { + return k.Clientset.AppsV1().Deployments(namespace).Get(name, metaV1.GetOptions{}) } // CreateShadow create shadow func (k *Kubernetes) CreateShadow(name, namespace, image string, labels map[string]string, debug bool) (podIP, podName, sshcm string, credential *util.SSHCredential, err error) { - return CreateShadow(k.Clientset, name, labels, namespace, image, debug) + component, version := labels["kt-component"], labels["version"] + sshcm = fmt.Sprintf("kt-%s-public-key-%s", component, version) + + generator, err := util.Generate(util.PrivateKeyPath(component, version)) + if err != nil { + return + } + + clientSet := k.Clientset + + labels["kt"] = sshcm + cli := clientSet.CoreV1().ConfigMaps(namespace) + configMap, err := cli.Create(&v1.ConfigMap{ + ObjectMeta: metaV1.ObjectMeta{ + Name: sshcm, + Namespace: namespace, + Labels: labels, + }, + Data: map[string]string{ + vars.SSHAuthKey: string(generator.PublicKey), + }, + }) + if err != nil { + return + } + + log.Info().Msgf("successful create ssh config map %v", configMap.ObjectMeta.Name) + + localIPAddress := util.GetOutboundIP() + log.Info().Msgf("Client address %s", localIPAddress) + labels["remoteAddress"] = localIPAddress + + labels["kt"] = name + client := clientSet.AppsV1().Deployments(namespace) + deployment := generatorDeployment(namespace, name, labels, image, sshcm, debug) + log.Info().Msg("shadow template is prepare ready.") + result, err := client.Create(deployment) + if err != nil { + return + } + log.Info().Msgf("deploy shadow deployment %s in namespace %s\n", result.GetObjectMeta().GetName(), namespace) + + pod, err := waitPodReadyUsingInformer(namespace, name, clientSet) + if err != nil { + return + } + podIP = pod.Status.PodIP + podName = pod.GetObjectMeta().GetName() + credential = util.NewDefaultSSHCredential() + credential.PrivateKeyPath = generator.PrivateKeyPath + return } // CreateService create kubernetes service @@ -136,66 +185,7 @@ func RemoveService( return client.Delete(name, &metaV1.DeleteOptions{}) } -// CreateShadow create shadow -func CreateShadow( - clientset *kubernetes.Clientset, - name string, - labels map[string]string, - namespace, - image string, - debug bool, -) (podIP, podName, sshcm string, credential *util.SSHCredential, err error) { - - component, version := labels["kt-component"], labels["version"] - sshcm = fmt.Sprintf("kt-%s-public-key-%s", component, version) - - generator, err := util.Generate(util.PrivateKeyPath(component, version)) - if err != nil { - return - } - - labels["kt"] = sshcm - cli := clientset.CoreV1().ConfigMaps(namespace) - _, err = cli.Create(&v1.ConfigMap{ - ObjectMeta: metaV1.ObjectMeta{ - Name: sshcm, - Namespace: namespace, - Labels: labels, - }, - Data: map[string]string{ - vars.SSHAuthKey: string(generator.PublicKey), - }, - }) - if err != nil { - return - } - - localIPAddress := util.GetOutboundIP() - log.Info().Msgf("Client address %s", localIPAddress) - labels["remoteAddress"] = localIPAddress - - labels["kt"] = name - client := clientset.AppsV1().Deployments(namespace) - deployment := generatorDeployment(namespace, name, labels, image, sshcm, debug) - result, err := client.Create(deployment) - if err != nil { - return - } - log.Info().Msgf("deploy shadow deployment %s in namespace %s\n", result.GetObjectMeta().GetName(), namespace) - - // pod, err := waitPodReady(namespace, name, clientset) - pod, err := waitPodReadyUsingInformer(namespace, name, clientset) - if err != nil { - return - } - podIP = pod.Status.PodIP - podName = pod.GetObjectMeta().GetName() - credential = util.NewDefaultSSHCredential() - credential.PrivateKeyPath = generator.PrivateKeyPath - return -} - -func waitPodReadyUsingInformer(namespace, name string, clientset *kubernetes.Clientset) (pod v1.Pod, err error) { +func waitPodReadyUsingInformer(namespace, name string, clientset kubernetes.Interface) (pod v1.Pod, err error) { stopSignal := make(chan struct{}) defer close(stopSignal) podListener, err := clusterWatcher.PodListener(clientset, stopSignal) diff --git a/pkg/kt/cluster/types.go b/pkg/kt/cluster/types.go index 887b3d0..aa091b0 100644 --- a/pkg/kt/cluster/types.go +++ b/pkg/kt/cluster/types.go @@ -34,7 +34,7 @@ type KubernetesInterface interface { type Kubernetes struct { KubeConfig string // TODO: should remove - Clientset *kubernetes.Clientset + Clientset kubernetes.Interface // TODO: should remove ServiceListener v1.ServiceLister } diff --git a/pkg/kt/command/run.go b/pkg/kt/command/run.go index e478da1..32129e0 100644 --- a/pkg/kt/command/run.go +++ b/pkg/kt/command/run.go @@ -7,33 +7,31 @@ import ( "github.com/alibaba/kt-connect/pkg/kt" - "github.com/alibaba/kt-connect/pkg/kt/cluster" - "github.com/alibaba/kt-connect/pkg/kt/connect" "github.com/alibaba/kt-connect/pkg/kt/options" "github.com/alibaba/kt-connect/pkg/kt/util" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/urfave/cli" + urfave "github.com/urfave/cli" ) // newRunCommand return new run command -func newRunCommand(kt kt.CliInterface, options *options.DaemonOptions, action ActionInterface) cli.Command { - return cli.Command{ +func newRunCommand(cli kt.CliInterface, options *options.DaemonOptions, action ActionInterface) urfave.Command { + return urfave.Command{ Name: "run", Usage: "create a shadow deployment to redirect request to user local", - Flags: []cli.Flag{ - cli.IntFlag{ + Flags: []urfave.Flag{ + urfave.IntFlag{ Name: "port", Usage: "The port that exposes", Destination: &options.RunOptions.Port, }, - cli.BoolFlag{ + urfave.BoolFlag{ Name: "expose", Usage: " If true, a public, external service is created", Destination: &options.RunOptions.Expose, }, }, - Action: func(c *cli.Context) error { + Action: func(c *urfave.Context) error { if options.Debug { zerolog.SetGlobalLevel(zerolog.DebugLevel) } @@ -41,15 +39,22 @@ func newRunCommand(kt kt.CliInterface, options *options.DaemonOptions, action Ac if port == 0 { return errors.New("--port is required") } - return action.Run(c.Args().First(), options) + return action.Run(c.Args().First(), cli, options) }, } } // Run create a new service in cluster -func (action *Action) Run(service string, options *options.DaemonOptions) error { +func (action *Action) Run(service string, cli kt.CliInterface, options *options.DaemonOptions) error { ch := SetUpCloseHandler(options) - kubernetes, err := cluster.Create(options.KubeConfig) + run(service, cli, options) + <-ch + return nil +} + +// Run create a new service in cluster +func run(service string, cli kt.CliInterface, options *options.DaemonOptions) error { + kubernetes, err := cli.Kubernetes() if err != nil { return err } @@ -74,7 +79,8 @@ func (action *Action) Run(service string, options *options.DaemonOptions) error if options.RunOptions.Expose { log.Info().Msgf("expose deployment %s to %s:%v", service, service, options.RunOptions.Port) - if _, err = kubernetes.CreateService(service, options.Namespace, options.RunOptions.Port, labels); err != nil { + _, err = kubernetes.CreateService(service, options.Namespace, options.RunOptions.Port, labels) + if err != nil { return err } options.RuntimeOptions.Service = service @@ -83,16 +89,11 @@ func (action *Action) Run(service string, options *options.DaemonOptions) error options.RuntimeOptions.Shadow = service options.RuntimeOptions.SSHCM = sshcm - shadow := connect.Create(options) - err = shadow.Inbound(strconv.Itoa(options.RunOptions.Port), podName, podIP, credential) + err = cli.Shadow().Inbound(strconv.Itoa(options.RunOptions.Port), podName, podIP, credential) if err != nil { return err } log.Info().Msgf("forward remote %s:%v -> 127.0.0.1:%v", podIP, options.RunOptions.Port, options.RunOptions.Port) - - s := <-ch - log.Info().Msgf("Terminal Signal is %s", s) - return nil } diff --git a/pkg/kt/command/run_test.go b/pkg/kt/command/run_test.go index f750da2..59fb490 100644 --- a/pkg/kt/command/run_test.go +++ b/pkg/kt/command/run_test.go @@ -6,21 +6,24 @@ import ( "io/ioutil" "testing" - "github.com/alibaba/kt-connect/pkg/fake/kt" - + fakeKt "github.com/alibaba/kt-connect/pkg/fake/kt" "github.com/alibaba/kt-connect/pkg/fake/kt/action" + "github.com/alibaba/kt-connect/pkg/fake/kt/cluster" + "github.com/alibaba/kt-connect/pkg/fake/kt/connect" "github.com/alibaba/kt-connect/pkg/kt/options" + "github.com/alibaba/kt-connect/pkg/kt/util" "github.com/golang/mock/gomock" "github.com/urfave/cli" + coreV1 "k8s.io/api/core/v1" ) func Test_runCommand(t *testing.T) { ctl := gomock.NewController(t) - mockAction := action.NewMockActionInterface(ctl) - fakeKtCli := kt.NewMockCliInterface(ctl) + fakeKtCli := fakeKt.NewMockCliInterface(ctl) - mockAction.EXPECT().Run(gomock.Eq("service"), gomock.Any()).Return(nil).AnyTimes() + mockAction := action.NewMockActionInterface(ctl) + mockAction.EXPECT().Run(gomock.Eq("service"), fakeKtCli, gomock.Any()).Return(nil).AnyTimes() cases := []struct { testArgs []string @@ -56,3 +59,115 @@ func Test_runCommand(t *testing.T) { } } + +func Test_run(t *testing.T) { + + ctl := gomock.NewController(t) + fakeKtCli := fakeKt.NewMockCliInterface(ctl) + kubernetes := cluster.NewMockKubernetesInterface(ctl) + shadow := connect.NewMockShadowInterface(ctl) + + fakeKtCli.EXPECT().Kubernetes().AnyTimes().Return(kubernetes, nil) + fakeKtCli.EXPECT().Shadow().AnyTimes().Return(shadow) + + type args struct { + service string + options *options.DaemonOptions + shadowResponse createShadowResponse + serviceResponse createServiceResponse + inboundResponse inboundResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "shouldExposeLocalServiceToCluster", + args: args{ + service: "test", + options: options.NewRunDaemonOptions( + "aa=bb", + &options.RunOptions{ + Expose: true, + Port: 8081, + }), + shadowResponse: createShadowResponse{ + podIP: "172.168.0.1", + podName: "shadow", + sshcm: "shadow-ssh-cm", + credential: &util.SSHCredential{ + RemoteHost: "127.0.0.1", + Port: "2222", + PrivateKeyPath: "/tmp/pk", + }, + err: nil, + }, + serviceResponse: createServiceResponse{ + service: &coreV1.Service{}, + err: nil, + }, + inboundResponse: inboundResponse{ + err: nil, + }, + }, + wantErr: false, + }, + { + name: "shouldExposeLocalServiceFailWhenShadowCreateFail", + args: args{ + service: "test2", + options: options.NewRunDaemonOptions( + "aaa=bbb", + &options.RunOptions{ + Expose: true, + Port: 8081, + }), + shadowResponse: createShadowResponse{ + podIP: "172.168.0.1", + podName: "shadow", + sshcm: "shadow-ssh-cm", + credential: &util.SSHCredential{ + RemoteHost: "127.0.0.1", + Port: "2222", + PrivateKeyPath: "/tmp/pk", + }, + err: errors.New("fail create shadow"), + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kubernetes.EXPECT(). + CreateShadow(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1). + Return(tt.args.shadowResponse.podIP, tt.args.shadowResponse.podName, tt.args.shadowResponse.sshcm, tt.args.shadowResponse.credential, tt.args.shadowResponse.err) + kubernetes.EXPECT().CreateService(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(tt.args.serviceResponse.service, tt.args.serviceResponse.err) + shadow.EXPECT(). + Inbound(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1). + Return(tt.args.inboundResponse.err) + + if err := run(tt.args.service, fakeKtCli, tt.args.options); (err != nil) != tt.wantErr { + t.Errorf("run() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +type inboundResponse struct { + err error +} + +type createServiceResponse struct { + service *coreV1.Service + err error +} + +type createShadowResponse struct { + podIP string + podName string + sshcm string + credential *util.SSHCredential + err error +} diff --git a/pkg/kt/command/types.go b/pkg/kt/command/types.go index e894559..5199a3c 100644 --- a/pkg/kt/command/types.go +++ b/pkg/kt/command/types.go @@ -10,7 +10,7 @@ type ActionInterface interface { OpenDashboard(options *options.DaemonOptions) error Connect(options *options.DaemonOptions) error Check(cli kt.CliInterface) error - Run(service string, options *options.DaemonOptions) error + Run(service string, cli kt.CliInterface, options *options.DaemonOptions) error Exchange(service string, options *options.DaemonOptions) error Mesh(service string, options *options.DaemonOptions) error ApplyDashboard(options *options.DaemonOptions) error diff --git a/pkg/kt/connect/inbound.go b/pkg/kt/connect/inbound.go index 895f6d8..a2c9574 100644 --- a/pkg/kt/connect/inbound.go +++ b/pkg/kt/connect/inbound.go @@ -17,10 +17,9 @@ import ( // Inbound mapping local port from cluster func (s *Shadow) Inbound(exposePort, podName, remoteIP string, credential *util.SSHCredential) (err error) { - kubernetesCli := &kubectl.Cli{ - KubeConfig: s.Options.KubeConfig, - } + kubernetesCli := &kubectl.Cli{KubeConfig: s.Options.KubeConfig} sshCli := &ssh.Cli{} + log.Info().Msg("creating shadow inbound(remote->local)") return inbound(exposePort, podName, remoteIP, credential, s.Options, kubernetesCli, sshCli) } diff --git a/pkg/kt/options/options.go b/pkg/kt/options/options.go index 0a9eb01..8f1b36a 100644 --- a/pkg/kt/options/options.go +++ b/pkg/kt/options/options.go @@ -6,7 +6,8 @@ import ( "github.com/alibaba/kt-connect/pkg/kt/util" ) -type runOptions struct { +// RunOptions ... +type RunOptions struct { Expose bool Port int } @@ -57,7 +58,7 @@ type DaemonOptions struct { Image string Labels string RuntimeOptions *runtimeOptions - RunOptions *runOptions + RunOptions *RunOptions ConnectOptions *connectOptions ExchangeOptions *exchangeOptions MeshOptions *meshOptions @@ -80,6 +81,14 @@ func NewDaemonOptions() *DaemonOptions { ExchangeOptions: &exchangeOptions{}, MeshOptions: &meshOptions{}, DashboardOptions: &dashboardOptions{}, - RunOptions: &runOptions{}, + RunOptions: &RunOptions{}, } } + +// NewRunDaemonOptions ... +func NewRunDaemonOptions(labels string, options *RunOptions) *DaemonOptions { + daemonOptions := NewDaemonOptions() + daemonOptions.Labels = labels + daemonOptions.RunOptions = options + return daemonOptions +} diff --git a/pkg/kt/types.go b/pkg/kt/types.go index e82e4d8..c7385c7 100644 --- a/pkg/kt/types.go +++ b/pkg/kt/types.go @@ -9,7 +9,7 @@ import ( // CliInterface ... type CliInterface interface { - Kubernetes() cluster.KubernetesInterface + Kubernetes() (cluster.KubernetesInterface, error) Shadow() connect.ShadowInterface Exec() exec.CliInterface } @@ -20,10 +20,15 @@ type Cli struct { } // Kubernetes ... -func (c *Cli) Kubernetes() cluster.KubernetesInterface { +func (c *Cli) Kubernetes() (cluster.KubernetesInterface, error) { + clientset, err := cluster.GetKubernetesClient(c.Options.KubeConfig) + if err != nil { + return nil, err + } return &cluster.Kubernetes{ KubeConfig: c.Options.KubeConfig, - } + Clientset: clientset, + }, nil } // Shadow ...