github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cluster/helper.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  	"context"
    24  	"fmt"
    25  	"strings"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/client-go/dynamic"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	"github.com/1aal/kubeblocks/pkg/cli/testing"
    36  	"github.com/1aal/kubeblocks/pkg/cli/types"
    37  	"github.com/1aal/kubeblocks/pkg/cli/util"
    38  	"github.com/1aal/kubeblocks/pkg/constant"
    39  )
    40  
    41  const (
    42  	ComponentNameEmpty = ""
    43  )
    44  
    45  // GetSimpleInstanceInfos returns simple instance info that only contains instance name and role, the default
    46  // instance should be the first element in the returned array.
    47  func GetSimpleInstanceInfos(dynamic dynamic.Interface, name, namespace string) []*InstanceInfo {
    48  	return GetSimpleInstanceInfosForComponent(dynamic, name, ComponentNameEmpty, namespace)
    49  }
    50  
    51  // GetSimpleInstanceInfosForComponent returns simple instance info that only contains instance name and role for a component
    52  func GetSimpleInstanceInfosForComponent(dynamic dynamic.Interface, name, componentName, namespace string) []*InstanceInfo {
    53  	// first get instance info from status, using the status as a cache
    54  	if infos := getInstanceInfoFromStatus(dynamic, name, componentName, namespace); len(infos) > 0 {
    55  		return infos
    56  	}
    57  
    58  	// missed in the status, try to list all pods and build instance info
    59  	return getInstanceInfoByList(dynamic, name, componentName, namespace)
    60  }
    61  
    62  // getInstancesInfoFromCluster gets instances info from cluster status
    63  func getInstanceInfoFromStatus(dynamic dynamic.Interface, name, componentName, namespace string) []*InstanceInfo {
    64  	var infos []*InstanceInfo
    65  	cluster, err := GetClusterByName(dynamic, name, namespace)
    66  	if err != nil {
    67  		return nil
    68  	}
    69  	// traverse all components, check the workload type
    70  	for compName, c := range cluster.Status.Components {
    71  		// filter by component name
    72  		if len(componentName) > 0 && compName != componentName {
    73  			continue
    74  		}
    75  
    76  		var info *InstanceInfo
    77  		// workload type is Consensus
    78  		if c.ConsensusSetStatus != nil {
    79  			buildInfoByStatus := func(status *appsv1alpha1.ConsensusMemberStatus) {
    80  				if status == nil {
    81  					return
    82  				}
    83  				info = &InstanceInfo{Role: status.Name, Name: status.Pod}
    84  				infos = append(infos, info)
    85  			}
    86  
    87  			// leader must be first
    88  			buildInfoByStatus(&c.ConsensusSetStatus.Leader)
    89  
    90  			// followers
    91  			for _, f := range c.ConsensusSetStatus.Followers {
    92  				buildInfoByStatus(&f)
    93  			}
    94  
    95  			// learner
    96  			buildInfoByStatus(c.ConsensusSetStatus.Learner)
    97  		}
    98  
    99  		// workload type is Replication
   100  		if c.ReplicationSetStatus != nil {
   101  			buildInfoByStatus := func(status *appsv1alpha1.ReplicationMemberStatus) {
   102  				if status == nil {
   103  					return
   104  				}
   105  				info = &InstanceInfo{Name: status.Pod}
   106  				infos = append(infos, info)
   107  			}
   108  			// primary
   109  			buildInfoByStatus(&c.ReplicationSetStatus.Primary)
   110  
   111  			// secondaries
   112  			for _, f := range c.ReplicationSetStatus.Secondaries {
   113  				buildInfoByStatus(&f)
   114  			}
   115  		}
   116  	}
   117  	return infos
   118  }
   119  
   120  // getInstanceInfoByList gets instances info by listing all pods
   121  func getInstanceInfoByList(dynamic dynamic.Interface, name, componentName, namespace string) []*InstanceInfo {
   122  	var infos []*InstanceInfo
   123  	// filter by cluster name
   124  	labels := util.BuildLabelSelectorByNames("", []string{name})
   125  	// filter by component name
   126  	if len(componentName) > 0 {
   127  		labels = util.BuildComponentNameLabels(labels, []string{componentName})
   128  	}
   129  
   130  	objs, err := dynamic.Resource(schema.GroupVersionResource{Group: corev1.GroupName, Version: types.K8sCoreAPIVersion, Resource: "pods"}).
   131  		Namespace(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labels})
   132  
   133  	if err != nil {
   134  		return nil
   135  	}
   136  
   137  	for _, o := range objs.Items {
   138  		infos = append(infos, &InstanceInfo{Name: o.GetName()})
   139  	}
   140  	return infos
   141  }
   142  
   143  // FindClusterComp finds component in cluster object based on the component definition name
   144  func FindClusterComp(cluster *appsv1alpha1.Cluster, compDefName string) *appsv1alpha1.ClusterComponentSpec {
   145  	for i, c := range cluster.Spec.ComponentSpecs {
   146  		if c.ComponentDefRef == compDefName {
   147  			return &cluster.Spec.ComponentSpecs[i]
   148  		}
   149  	}
   150  	return nil
   151  }
   152  
   153  // GetComponentEndpoints gets component internal and external endpoints
   154  func GetComponentEndpoints(svcList *corev1.ServiceList, c *appsv1alpha1.ClusterComponentSpec) ([]string, []string) {
   155  	var (
   156  		internalEndpoints []string
   157  		externalEndpoints []string
   158  	)
   159  
   160  	getEndpoints := func(ip string, ports []corev1.ServicePort) []string {
   161  		var result []string
   162  		for _, port := range ports {
   163  			result = append(result, fmt.Sprintf("%s:%d", ip, port.Port))
   164  		}
   165  		return result
   166  	}
   167  
   168  	internalSvcs, externalSvcs := GetComponentServices(svcList, c)
   169  	for _, svc := range internalSvcs {
   170  		dns := fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, svc.Namespace)
   171  		internalEndpoints = append(internalEndpoints, getEndpoints(dns, svc.Spec.Ports)...)
   172  	}
   173  
   174  	for _, svc := range externalSvcs {
   175  		externalEndpoints = append(externalEndpoints, getEndpoints(GetExternalAddr(svc), svc.Spec.Ports)...)
   176  	}
   177  	return internalEndpoints, externalEndpoints
   178  }
   179  
   180  // GetComponentServices gets component services
   181  func GetComponentServices(svcList *corev1.ServiceList, c *appsv1alpha1.ClusterComponentSpec) ([]*corev1.Service, []*corev1.Service) {
   182  	if svcList == nil {
   183  		return nil, nil
   184  	}
   185  
   186  	var internalSvcs, externalSvcs []*corev1.Service
   187  	for i, svc := range svcList.Items {
   188  		if svc.GetLabels()[constant.KBAppComponentLabelKey] != c.Name {
   189  			continue
   190  		}
   191  
   192  		var (
   193  			internalIP   = svc.Spec.ClusterIP
   194  			externalAddr = GetExternalAddr(&svc)
   195  		)
   196  		if svc.Spec.Type == corev1.ServiceTypeClusterIP && internalIP != "" && internalIP != "None" {
   197  			internalSvcs = append(internalSvcs, &svcList.Items[i])
   198  		}
   199  		if externalAddr != "" {
   200  			externalSvcs = append(externalSvcs, &svcList.Items[i])
   201  		}
   202  	}
   203  	return internalSvcs, externalSvcs
   204  }
   205  
   206  // GetExternalAddr gets external IP from service annotation
   207  func GetExternalAddr(svc *corev1.Service) string {
   208  	for _, ingress := range svc.Status.LoadBalancer.Ingress {
   209  		if ingress.Hostname != "" {
   210  			return ingress.Hostname
   211  		}
   212  
   213  		if ingress.IP != "" {
   214  			return ingress.IP
   215  		}
   216  	}
   217  	if svc.GetAnnotations()[types.ServiceHAVIPTypeAnnotationKey] != types.ServiceHAVIPTypeAnnotationValue {
   218  		return ""
   219  	}
   220  	return svc.GetAnnotations()[types.ServiceFloatingIPAnnotationKey]
   221  }
   222  
   223  // GetK8SClientObject gets the client object of k8s,
   224  // obj must be a struct pointer so that obj can be updated with the response.
   225  func GetK8SClientObject(dynamic dynamic.Interface,
   226  	obj client.Object,
   227  	gvr schema.GroupVersionResource,
   228  	namespace,
   229  	name string) error {
   230  	unstructuredObj, err := dynamic.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   231  	if err != nil {
   232  		return err
   233  	}
   234  	return runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.UnstructuredContent(), obj)
   235  }
   236  
   237  func GetClusterDefByName(dynamic dynamic.Interface, name string) (*appsv1alpha1.ClusterDefinition, error) {
   238  	clusterDef := &appsv1alpha1.ClusterDefinition{}
   239  	if err := GetK8SClientObject(dynamic, clusterDef, types.ClusterDefGVR(), "", name); err != nil {
   240  		return nil, err
   241  	}
   242  	return clusterDef, nil
   243  }
   244  
   245  func GetDefaultCompName(cd *appsv1alpha1.ClusterDefinition) (string, error) {
   246  	if len(cd.Spec.ComponentDefs) >= 1 {
   247  		return cd.Spec.ComponentDefs[0].Name, nil
   248  	}
   249  	return "", fmt.Errorf("failed to get the default component definition name")
   250  }
   251  
   252  func GetClusterByName(dynamic dynamic.Interface, name string, namespace string) (*appsv1alpha1.Cluster, error) {
   253  	cluster := &appsv1alpha1.Cluster{}
   254  	if err := GetK8SClientObject(dynamic, cluster, types.ClusterGVR(), namespace, name); err != nil {
   255  		return nil, err
   256  	}
   257  	return cluster, nil
   258  }
   259  
   260  func GetVersionByClusterDef(dynamic dynamic.Interface, clusterDef string) (*appsv1alpha1.ClusterVersionList, error) {
   261  	versionList := &appsv1alpha1.ClusterVersionList{}
   262  	obj, err := dynamic.Resource(types.ClusterVersionGVR()).List(context.TODO(), metav1.ListOptions{
   263  		LabelSelector: fmt.Sprintf("%s=%s", constant.ClusterDefLabelKey, clusterDef),
   264  	})
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  	if obj == nil {
   269  		return nil, fmt.Errorf("failed to find component version referencing cluster definition %s", clusterDef)
   270  	}
   271  	if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), versionList); err != nil {
   272  		return nil, err
   273  	}
   274  	return versionList, nil
   275  }
   276  
   277  func FakeClusterObjs() *ClusterObjects {
   278  	clusterObjs := NewClusterObjects()
   279  	clusterObjs.Cluster = testing.FakeCluster(testing.ClusterName, testing.Namespace)
   280  	clusterObjs.ClusterDef = testing.FakeClusterDef()
   281  	clusterObjs.Pods = testing.FakePods(3, testing.Namespace, testing.ClusterName)
   282  	clusterObjs.Secrets = testing.FakeSecrets(testing.Namespace, testing.ClusterName)
   283  	clusterObjs.Nodes = []*corev1.Node{testing.FakeNode()}
   284  	clusterObjs.Services = testing.FakeServices()
   285  	return clusterObjs
   286  }
   287  
   288  func BuildStorageSize(storages []StorageInfo) string {
   289  	var sizes []string
   290  	for _, s := range storages {
   291  		sizes = append(sizes, fmt.Sprintf("%s:%s", s.Name, s.Size))
   292  	}
   293  	return util.CheckEmpty(strings.Join(sizes, "\n"))
   294  }
   295  
   296  func BuildStorageClass(storages []StorageInfo) string {
   297  	var scs []string
   298  	for _, s := range storages {
   299  		scs = append(scs, s.StorageClass)
   300  	}
   301  	return util.CheckEmpty(strings.Join(scs, "\n"))
   302  }
   303  
   304  // GetDefaultVersion gets the default cluster version that referencing the cluster definition.
   305  // If only one version is found, it will be returned directly, otherwise the version with
   306  // constant.DefaultClusterVersionAnnotationKey label will be returned.
   307  func GetDefaultVersion(dynamic dynamic.Interface, clusterDef string) (string, error) {
   308  	versionList, err := GetVersionByClusterDef(dynamic, clusterDef)
   309  	if err != nil {
   310  		return "", err
   311  	}
   312  
   313  	if len(versionList.Items) == 1 {
   314  		return versionList.Items[0].Name, nil
   315  	}
   316  
   317  	defaultVersion := ""
   318  	for _, item := range versionList.Items {
   319  		if k, ok := item.Annotations[constant.DefaultClusterVersionAnnotationKey]; !ok || k != "true" {
   320  			continue
   321  		}
   322  		if defaultVersion != "" {
   323  			return "", fmt.Errorf("found more than one default cluster version referencing cluster definition %s", clusterDef)
   324  		}
   325  		defaultVersion = item.Name
   326  	}
   327  
   328  	if defaultVersion == "" {
   329  		return "", fmt.Errorf("failed to find default cluster version referencing cluster definition %s", clusterDef)
   330  	}
   331  	return defaultVersion, nil
   332  }
   333  
   334  type CompInfo struct {
   335  	Component       *appsv1alpha1.ClusterComponentSpec
   336  	ComponentStatus *appsv1alpha1.ClusterComponentStatus
   337  	ComponentDef    *appsv1alpha1.ClusterComponentDefinition
   338  }
   339  
   340  func (info *CompInfo) InferPodName() (string, error) {
   341  	if info.ComponentStatus == nil {
   342  		return "", fmt.Errorf("component status is missing")
   343  	}
   344  	if info.ComponentStatus.Phase != appsv1alpha1.RunningClusterCompPhase || !*info.ComponentStatus.PodsReady {
   345  		return "", fmt.Errorf("component is not ready, please try later")
   346  	}
   347  	if info.ComponentStatus.ConsensusSetStatus != nil {
   348  		return info.ComponentStatus.ConsensusSetStatus.Leader.Pod, nil
   349  	}
   350  	if info.ComponentStatus.ReplicationSetStatus != nil {
   351  		return info.ComponentStatus.ReplicationSetStatus.Primary.Pod, nil
   352  	}
   353  	return "", fmt.Errorf("cannot pick a pod to connect, please specify the pod name explicitly by `--instance` flag")
   354  }
   355  
   356  func FillCompInfoByName(ctx context.Context, dynamic dynamic.Interface, namespace, clusterName, componentName string) (*CompInfo, error) {
   357  	cluster, err := GetClusterByName(dynamic, clusterName, namespace)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	if cluster.Status.Phase != appsv1alpha1.RunningClusterPhase {
   362  		return nil, fmt.Errorf("cluster %s is not running, please try again later", clusterName)
   363  	}
   364  
   365  	compInfo := &CompInfo{}
   366  	// fill component
   367  	if len(componentName) == 0 {
   368  		compInfo.Component = &cluster.Spec.ComponentSpecs[0]
   369  	} else {
   370  		compInfo.Component = cluster.Spec.GetComponentByName(componentName)
   371  	}
   372  
   373  	if compInfo.Component == nil {
   374  		return nil, fmt.Errorf("component %s not found in cluster %s", componentName, clusterName)
   375  	}
   376  	// fill component status
   377  	for name, compStatus := range cluster.Status.Components {
   378  		if name == compInfo.Component.Name {
   379  			compInfo.ComponentStatus = &compStatus
   380  			break
   381  		}
   382  	}
   383  	if compInfo.ComponentStatus == nil {
   384  		return nil, fmt.Errorf("componentStatus %s not found in cluster %s", componentName, clusterName)
   385  	}
   386  
   387  	// find cluster def
   388  	clusterDef, err := GetClusterDefByName(dynamic, cluster.Spec.ClusterDefRef)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  	// find component def by reference
   393  	for _, compDef := range clusterDef.Spec.ComponentDefs {
   394  		if compDef.Name == compInfo.Component.ComponentDefRef {
   395  			compInfo.ComponentDef = &compDef
   396  			break
   397  		}
   398  	}
   399  	if compInfo.ComponentDef == nil {
   400  		return nil, fmt.Errorf("componentDef %s not found in clusterDef %s", compInfo.Component.ComponentDefRef, clusterDef.Name)
   401  	}
   402  	return compInfo, nil
   403  }
   404  
   405  func GetPodClusterName(pod *corev1.Pod) string {
   406  	if pod.Labels == nil {
   407  		return ""
   408  	}
   409  	return pod.Labels[constant.AppInstanceLabelKey]
   410  }
   411  
   412  func GetPodComponentName(pod *corev1.Pod) string {
   413  	if pod.Labels == nil {
   414  		return ""
   415  	}
   416  	return pod.Labels[constant.KBAppComponentLabelKey]
   417  }
   418  
   419  func GetConfigMapByName(dynamic dynamic.Interface, namespace, name string) (*corev1.ConfigMap, error) {
   420  	cmObj := &corev1.ConfigMap{}
   421  	if err := GetK8SClientObject(dynamic, cmObj, types.ConfigmapGVR(), namespace, name); err != nil {
   422  		return nil, err
   423  	}
   424  	return cmObj, nil
   425  }
   426  
   427  func GetConfigConstraintByName(dynamic dynamic.Interface, name string) (*appsv1alpha1.ConfigConstraint, error) {
   428  	ccObj := &appsv1alpha1.ConfigConstraint{}
   429  	if err := GetK8SClientObject(dynamic, ccObj, types.ConfigConstraintGVR(), "", name); err != nil {
   430  		return nil, err
   431  	}
   432  	return ccObj, nil
   433  }
   434  
   435  func GetServiceRefs(cd *appsv1alpha1.ClusterDefinition) []string {
   436  	var serviceRefs []string
   437  	for _, compDef := range cd.Spec.ComponentDefs {
   438  		if compDef.ServiceRefDeclarations == nil {
   439  			continue
   440  		}
   441  
   442  		for _, ref := range compDef.ServiceRefDeclarations {
   443  			serviceRefs = append(serviceRefs, ref.Name)
   444  		}
   445  	}
   446  	return serviceRefs
   447  }
   448  
   449  // GetDefaultServiceRef will return the ServiceRefDeclarations in cluster-definition when the cluster-definition contains only one ServiceRefDeclaration
   450  func GetDefaultServiceRef(cd *appsv1alpha1.ClusterDefinition) (string, error) {
   451  	serviceRefs := GetServiceRefs(cd)
   452  	if len(serviceRefs) != 1 {
   453  		return "", fmt.Errorf("failed to get the cluster default service reference name")
   454  	}
   455  	return serviceRefs[0], nil
   456  }