github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/logs_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package cluster
    21  
    22  import (
    23  	"net/http"
    24  	"os"
    25  	"time"
    26  
    27  	. "github.com/onsi/ginkgo/v2"
    28  	. "github.com/onsi/gomega"
    29  
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/client-go/kubernetes/scheme"
    35  	restclient "k8s.io/client-go/rest"
    36  	"k8s.io/client-go/rest/fake"
    37  	cmdexec "k8s.io/kubectl/pkg/cmd/exec"
    38  	cmdlogs "k8s.io/kubectl/pkg/cmd/logs"
    39  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    40  
    41  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    42  	"github.com/1aal/kubeblocks/pkg/cli/cluster"
    43  	"github.com/1aal/kubeblocks/pkg/cli/exec"
    44  	"github.com/1aal/kubeblocks/pkg/constant"
    45  )
    46  
    47  var _ = Describe("logs", func() {
    48  	It("isStdoutForContainer Test", func() {
    49  		o := &LogsOptions{}
    50  		Expect(o.isStdoutForContainer()).Should(BeTrue())
    51  		o.fileType = "stdout"
    52  		Expect(o.isStdoutForContainer()).Should(BeTrue())
    53  		o.fileType = "slow"
    54  		Expect(o.isStdoutForContainer()).Should(BeFalse())
    55  		o.filePath = "/var/log/yum.log"
    56  		Expect(o.isStdoutForContainer()).Should(BeFalse())
    57  	})
    58  
    59  	It("prefixingWriter Test", func() {
    60  		pw := &prefixingWriter{
    61  			prefix: []byte("prefix"),
    62  			writer: os.Stdout,
    63  		}
    64  		n, _ := pw.Write([]byte(""))
    65  		Expect(n).Should(Equal(0))
    66  		num, _ := pw.Write([]byte("test"))
    67  		Expect(num).Should(Equal(4))
    68  	})
    69  
    70  	It("assembleTailCommand Test", func() {
    71  		command := assembleTail(true, 1, 100)
    72  		Expect(command).ShouldNot(BeNil())
    73  		Expect(command).Should(Equal("tail -f --lines=1 --bytes=100"))
    74  	})
    75  
    76  	It("addPrefixIfNeeded Test", func() {
    77  		l := &LogsOptions{
    78  			ExecOptions: &exec.ExecOptions{
    79  				StreamOptions: cmdexec.StreamOptions{
    80  					ContainerName: "container",
    81  				},
    82  			},
    83  		}
    84  		// no set prefix
    85  		w := l.addPrefixIfNeeded(corev1.ObjectReference{}, os.Stdout)
    86  		Expect(w).Should(Equal(os.Stdout))
    87  		// set prefix
    88  		o := corev1.ObjectReference{
    89  			Name:      "name",
    90  			FieldPath: "FieldPath",
    91  		}
    92  		l.logOptions.Prefix = true
    93  		w = l.addPrefixIfNeeded(o, os.Stdout)
    94  		_, ok := w.(*prefixingWriter)
    95  		Expect(ok).Should(BeTrue())
    96  	})
    97  
    98  	It("new logs command Test", func() {
    99  		tf := cmdtesting.NewTestFactory().WithNamespace("test")
   100  		defer tf.Cleanup()
   101  		codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   102  		ns := scheme.Codecs.WithoutConversion()
   103  		tf.Client = &fake.RESTClient{
   104  			GroupVersion:         schema.GroupVersion{Group: "", Version: "v1"},
   105  			NegotiatedSerializer: ns,
   106  			Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   107  				body := cmdtesting.ObjBody(codec, mockPod())
   108  				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
   109  			}),
   110  		}
   111  		tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: "v1"}}}
   112  
   113  		stream := genericiooptions.NewTestIOStreamsDiscard()
   114  		l := &LogsOptions{
   115  			ExecOptions: exec.NewExecOptions(tf, stream),
   116  			logOptions: cmdlogs.LogsOptions{
   117  				IOStreams: stream,
   118  			},
   119  		}
   120  
   121  		cmd := NewLogsCmd(tf, stream)
   122  		Expect(cmd).ShouldNot(BeNil())
   123  		Expect(cmd.Use).ShouldNot(BeNil())
   124  		Expect(cmd.Example).ShouldNot(BeNil())
   125  
   126  		// Complete without args
   127  		Expect(l.complete([]string{})).Should(MatchError("cluster name or instance name should be specified"))
   128  		// Complete with args
   129  		l.PodName = "foo"
   130  		l.Client, _ = l.Factory.KubernetesClientSet()
   131  		l.filePath = "/var/log"
   132  		Expect(l.complete([]string{"cluster-name"})).Should(HaveOccurred())
   133  		Expect(l.clusterName).Should(Equal("cluster-name"))
   134  		// Validate stdout
   135  		l.filePath = ""
   136  		l.fileType = ""
   137  		l.Namespace = "test"
   138  		l.logOptions.SinceSeconds = time.Minute
   139  		Expect(l.complete([]string{"cluster-name"})).Should(Succeed())
   140  		Expect(l.validate()).Should(Succeed())
   141  		Expect(l.logOptions.Options).ShouldNot(BeNil())
   142  
   143  	})
   144  
   145  	It("createFileTypeCommand Test", func() {
   146  		pod := &corev1.Pod{
   147  			ObjectMeta: metav1.ObjectMeta{
   148  				Name:            "foo",
   149  				Namespace:       "test",
   150  				ResourceVersion: "10",
   151  				Labels: map[string]string{
   152  					"app.kubernetes.io/name":        "mysql-apecloud-mysql",
   153  					constant.KBAppComponentLabelKey: "component-name",
   154  				},
   155  			},
   156  		}
   157  		obj := cluster.NewClusterObjects()
   158  		l := &LogsOptions{}
   159  		// corner case
   160  		cmd, err := l.createFileTypeCommand(pod, obj)
   161  		Expect(cmd).Should(Equal(""))
   162  		Expect(err).Should(HaveOccurred())
   163  		// normal case
   164  		obj.Cluster = &appsv1alpha1.Cluster{
   165  			Spec: appsv1alpha1.ClusterSpec{
   166  				ComponentSpecs: []appsv1alpha1.ClusterComponentSpec{
   167  					{
   168  						Name:            "component-name",
   169  						ComponentDefRef: "component-type",
   170  					},
   171  				},
   172  			},
   173  		}
   174  		obj.ClusterDef = &appsv1alpha1.ClusterDefinition{
   175  			Spec: appsv1alpha1.ClusterDefinitionSpec{
   176  				ComponentDefs: []appsv1alpha1.ClusterComponentDefinition{
   177  					{
   178  						Name: "component-type",
   179  						LogConfigs: []appsv1alpha1.LogConfig{
   180  							{
   181  								Name:            "slow",
   182  								FilePathPattern: "/log/mysql/*slow.log",
   183  							},
   184  							{
   185  								Name:            "error",
   186  								FilePathPattern: "/log/mysql/*.err",
   187  							},
   188  						},
   189  					},
   190  				},
   191  			},
   192  		}
   193  		l.fileType = "slow"
   194  		cmd, err = l.createFileTypeCommand(pod, obj)
   195  		Expect(err).Should(BeNil())
   196  		Expect(cmd).Should(Equal("ls /log/mysql/*slow.log | xargs tail --lines=0"))
   197  		// error case
   198  		l.fileType = "slow-error"
   199  		cmd, err = l.createFileTypeCommand(pod, obj)
   200  		Expect(err).Should(HaveOccurred())
   201  		Expect(cmd).Should(Equal(""))
   202  	})
   203  })