test: add test case for run command

This commit is contained in:
yunlzheng
2020-03-12 19:54:32 +08:00
parent f1df3fe357
commit dc32216daa
10 changed files with 225 additions and 105 deletions
+4 -4
View File
@@ -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
+3 -2
View File
@@ -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
+54 -64
View File
@@ -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)
+1 -1
View File
@@ -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
}
+20 -19
View File
@@ -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
}
+120 -5
View File
@@ -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
}
+1 -1
View File
@@ -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
+2 -3
View File
@@ -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)
}
+12 -3
View File
@@ -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
}
+8 -3
View File
@@ -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 ...