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 }