diff --git a/pkg/clusterprovider/helper.go b/pkg/clusterprovider/helper.go index d91488e..7af48bf 100644 --- a/pkg/clusterprovider/helper.go +++ b/pkg/clusterprovider/helper.go @@ -30,6 +30,11 @@ import ( "tkestack.io/kstone/pkg/etcd" ) +type EtcdAlarm struct { + MemberID uint64 + AlarmType string +} + // GetStorageMemberEndpoints get member of cluster status func GetStorageMemberEndpoints(cluster *kstonev1alpha1.EtcdCluster) []string { members := cluster.Status.Members @@ -182,3 +187,71 @@ func GetEtcdClusterMemberStatus( return newMembers, clusterStatus } + +// GetEtcdAlarms get alarm list of etcd +func GetEtcdAlarms( + endpoints []string, + tls *transport.TLSInfo) ([]EtcdAlarm, error) { + etcdAlarms := make([]EtcdAlarm, 0) + + ca, cert, key := "", "", "" + if tls != nil { + ca, cert, key = tls.TrustedCAFile, tls.CertFile, tls.KeyFile + } + + // GetAlarmList + client, err := etcd.NewClientv3(ca, cert, key, endpoints) + if err != nil { + klog.Errorf("failed to get new etcd clientv3, err is %v ", err) + return etcdAlarms, err + } + defer client.Close() + + alarmRsp, err := etcd.AlarmList(client) + if err != nil { + klog.Errorf("failed to get alarm list, err is %v", err) + return etcdAlarms, err + } + + for _, a := range alarmRsp.Alarms { + etcdAlarms = append(etcdAlarms, EtcdAlarm{ + MemberID: a.MemberID, + AlarmType: a.Alarm.String(), + }) + } + return etcdAlarms, nil +} + +// DisarmEtcdAlarms disarm alarm of etcd +func DisarmEtcdAlarm( + endpoints []string, + tls *transport.TLSInfo) ([]EtcdAlarm, error) { + etcdAlarms := make([]EtcdAlarm, 0) + + ca, cert, key := "", "", "" + if tls != nil { + ca, cert, key = tls.TrustedCAFile, tls.CertFile, tls.KeyFile + } + + // DisarmEtcdAlarm + client, err := etcd.NewClientv3(ca, cert, key, endpoints) + if err != nil { + klog.Errorf("failed to get new etcd clientv3, err is %v ", err) + return etcdAlarms, err + } + defer client.Close() + + disarmRsp, err := etcd.AlarmDisarm(client) + if err != nil { + klog.Errorf("failed to get member list, err is %v", endpoints, err) + return etcdAlarms, err + } + + for _, a := range disarmRsp.Alarms { + etcdAlarms = append(etcdAlarms, EtcdAlarm{ + MemberID: a.MemberID, + AlarmType: a.Alarm.String(), + }) + } + return etcdAlarms, nil +} diff --git a/pkg/etcd/helper.go b/pkg/etcd/helper.go index 8c02704..8075957 100644 --- a/pkg/etcd/helper.go +++ b/pkg/etcd/helper.go @@ -305,3 +305,31 @@ func getTransport(dialTimeout, totalTimeout time.Duration, scfg *secureConfig, s DisableKeepAlives: true, }, nil } + +// AlarmList list etcd alarm +func AlarmList(cli *clientv3.Client) (*clientv3.AlarmResponse, error) { + ctx, cancel := context.WithTimeout(context.Background(), DefaultDialTimeout) + defer cancel() + + rsp, err := cli.AlarmList(ctx) + if err != nil { + klog.Errorf("failed list etcd alarm,err is %v", err) + return rsp, err + } + klog.V(6).Infof("list etcd alarm succ,resp info %v", rsp) + return rsp, err +} + +// AlarmDisarm disarm etcd alarm +func AlarmDisarm(cli *clientv3.Client) (*clientv3.AlarmResponse, error) { + ctx, cancel := context.WithTimeout(context.Background(), DefaultDialTimeout) + defer cancel() + + rsp, err := cli.AlarmDisarm(ctx, &clientv3.AlarmMember{}) + if err != nil { + klog.Errorf("failed disarm etcd alarm,err is %v", err) + return rsp, err + } + klog.V(6).Infof("disarm etcd alarm succ,resp info %v", rsp) + return rsp, err +} diff --git a/pkg/router/alarm.go b/pkg/router/alarm.go new file mode 100644 index 0000000..2745bb9 --- /dev/null +++ b/pkg/router/alarm.go @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2023 Tencent. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://opensource.org/licenses/Apache-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package router + +import ( + "github.com/gin-gonic/gin" + + "tkestack.io/kstone/pkg/clusterprovider" +) + +// AlarmList returns alarm list +func AlarmList(ctx *gin.Context) { + cluster, tlsConfig := GetEtcdClusterInfo(ctx) + clusterprovider.GetEtcdAlarms([]string{cluster.Status.ServiceName}, tlsConfig) +} + +// AlarmList returns alarm list +func AlarmDisarm(ctx *gin.Context) { + cluster, tlsConfig := GetEtcdClusterInfo(ctx) + clusterprovider.DisarmEtcdAlarm([]string{cluster.Status.ServiceName}, tlsConfig) +} diff --git a/pkg/router/backup.go b/pkg/router/backup.go new file mode 100644 index 0000000..2dd68cd --- /dev/null +++ b/pkg/router/backup.go @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2023 Tencent. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://opensource.org/licenses/Apache-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package router + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "k8s.io/klog/v2" + + "tkestack.io/kstone/pkg/backup" +) + +// BackupList returns backup list +func BackupList(ctx *gin.Context) { + cluster, _ := GetEtcdClusterInfo(ctx) + + // generate backup config + strCfg, found := cluster.Annotations[backup.AnnoBackupConfig] + if !found || strCfg == "" { + err := fmt.Errorf( + "backup config not found, annotation key %s not exists, namespace is %s, name is %s", + backup.AnnoBackupConfig, + cluster.Namespace, + cluster.Name, + ) + klog.Errorf(err.Error()) + ctx.JSON(http.StatusOK, []interface{}{}) + return + } + backupConfig := &backup.Config{} + err := json.Unmarshal([]byte(strCfg), backupConfig) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + + // generate backup provider + backupProvider, err := backup.GetBackupProvider(string(backupConfig.StorageType), &backup.ProviderConfig{ + Kubeconfig: "", + }) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + resp, err := backupProvider.List(cluster) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + ctx.JSON(http.StatusOK, resp) +} diff --git a/pkg/router/key.go b/pkg/router/key.go new file mode 100644 index 0000000..bf0b3a8 --- /dev/null +++ b/pkg/router/key.go @@ -0,0 +1,121 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2023 Tencent. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://opensource.org/licenses/Apache-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package router + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" + clientv3 "go.etcd.io/etcd/client/v3" + "k8s.io/klog/v2" + + "tkestack.io/kstone/pkg/etcd" +) + +// EtcdKeyList returns etcd key list +func EtcdKeyList(ctx *gin.Context) { + etcdKey := ctx.DefaultQuery("key", "") + + cluster, tlsConfig := GetEtcdClusterInfo(ctx) + ca, cert, key := "", "", "" + if tlsConfig != nil { + ca, cert, key = tlsConfig.TrustedCAFile, tlsConfig.CertFile, tlsConfig.KeyFile + } + klog.Infof("endpoint: %s, ca: %s, cert: %s, key: %s", cluster.Status.ServiceName, ca, cert, key) + client, err := etcd.NewClientv3(ca, cert, key, []string{cluster.Status.ServiceName}) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + defer client.Close() + + if etcdKey == "" { + resp, err := client.Get(context.TODO(), "", clientv3.WithPrefix(), clientv3.WithKeysOnly()) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + + data := make([]string, 0) + for _, value := range resp.Kvs { + data = append(data, string(value.Key)) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 0, + "data": data, + }) + return + } + klog.Infof("get value by key: %s", etcdKey) + resp, err := client.Get(context.TODO(), etcdKey, clientv3.WithPrefix()) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + if resp.Count == 0 { + ctx.JSON(http.StatusNotFound, map[string]interface{}{ + "code": 1, + "data": "", + }) + } else { + result := map[string]interface{}{ + "code": 0, + "err": "", + } + if cluster.Annotations["kubernetes"] == "true" && etcdKey != "compact_rev_key" { + jsonValue := etcd.ConvertToJSON(resp.Kvs[0]) + inMediaType, in, err := etcd.DetectAndExtract(resp.Kvs[0].Value) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return + } + respData, err := etcd.ConvertToData(inMediaType, in) + if err != nil { + klog.Errorf(err.Error()) + if respData == nil { + respData = make(map[string]string) + } + result["err"] = err.Error() + } + respData["json"] = jsonValue + respDataList := make([]map[string]string, 0) + for dataType, value := range respData { + respDataList = append(respDataList, map[string]string{ + "type": dataType, + "data": value, + }) + } + result["data"] = respDataList + } else { + result["data"] = []map[string]string{ + { + "type": "javascript", + "data": string(resp.Kvs[0].Value), + }, + } + } + ctx.JSON(http.StatusOK, result) + } +} diff --git a/pkg/router/router.go b/pkg/router/router.go index 3e950b9..ef54705 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -19,9 +19,7 @@ package router import ( - "context" "crypto/tls" - "encoding/json" "fmt" "net/http" "net/http/httputil" @@ -29,17 +27,8 @@ import ( "time" "github.com/gin-gonic/gin" - clientv3 "go.etcd.io/etcd/client/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" - klog "k8s.io/klog/v2" - - "tkestack.io/kstone/pkg/backup" // import backup provider _ "tkestack.io/kstone/pkg/backup/providers" - "tkestack.io/kstone/pkg/controllers/util" - "tkestack.io/kstone/pkg/etcd" - clientset "tkestack.io/kstone/pkg/generated/clientset/versioned" ) var ( @@ -67,6 +56,8 @@ func NewRouter() *gin.Engine { r.GET("/apis/etcd/:etcdName", EtcdKeyList) r.GET("/apis/backup/:etcdName", BackupList) + r.GET("/apis/alarm/:etcdName", AlarmList) + r.POST("/apis/alarm/:etcdName", AlarmDisarm) return r } @@ -132,203 +123,3 @@ func ReverseProxy() gin.HandlerFunc { proxy.ServeHTTP(c.Writer, c.Request) } } - -// EtcdKeyList returns etcd key list -func EtcdKeyList(ctx *gin.Context) { - etcdName := ctx.Param("etcdName") - etcdKey := ctx.DefaultQuery("key", "") - - // generate etcd client - cfg, err := clientcmd.BuildConfigFromFlags("", "") - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - clusterClient, err := clientset.NewForConfig(cfg) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - cluster, err := clusterClient.KstoneV1alpha1().EtcdClusters("kstone"). - Get(context.TODO(), etcdName, metav1.GetOptions{}) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - annotations := cluster.Annotations - secretName := "" - if annotations != nil { - if _, found := annotations["certName"]; found { - secretName = annotations["certName"] - } - } - tlsGetter := etcd.NewTLSSecretGetter(util.NewSimpleClientBuilder("")) - klog.Infof("secretName: %s", secretName) - tlsConfig, err := tlsGetter.Config(cluster.Name, secretName) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - ca, cert, key := "", "", "" - if tlsConfig != nil { - ca, cert, key = tlsConfig.TrustedCAFile, tlsConfig.CertFile, tlsConfig.KeyFile - } - klog.Infof("endpoint: %s, ca: %s, cert: %s, key: %s", cluster.Status.ServiceName, ca, cert, key) - client, err := etcd.NewClientv3(ca, cert, key, []string{cluster.Status.ServiceName}) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - defer client.Close() - - if etcdKey == "" { - resp, err := client.Get(context.TODO(), "", clientv3.WithPrefix(), clientv3.WithKeysOnly()) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - data := make([]string, 0) - for _, value := range resp.Kvs { - data = append(data, string(value.Key)) - } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "data": data, - }) - return - } - klog.Infof("get value by key: %s", etcdKey) - resp, err := client.Get(context.TODO(), etcdKey, clientv3.WithPrefix()) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - if resp.Count == 0 { - ctx.JSON(http.StatusNotFound, map[string]interface{}{ - "code": 1, - "data": "", - }) - } else { - result := map[string]interface{}{ - "code": 0, - "err": "", - } - if cluster.Annotations["kubernetes"] == "true" && etcdKey != "compact_rev_key" { - jsonValue := etcd.ConvertToJSON(resp.Kvs[0]) - inMediaType, in, err := etcd.DetectAndExtract(resp.Kvs[0].Value) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - respData, err := etcd.ConvertToData(inMediaType, in) - if err != nil { - klog.Errorf(err.Error()) - if respData == nil { - respData = make(map[string]string) - } - result["err"] = err.Error() - } - respData["json"] = jsonValue - respDataList := make([]map[string]string, 0) - for dataType, value := range respData { - respDataList = append(respDataList, map[string]string{ - "type": dataType, - "data": value, - }) - } - result["data"] = respDataList - } else { - result["data"] = []map[string]string{ - { - "type": "javascript", - "data": string(resp.Kvs[0].Value), - }, - } - } - ctx.JSON(http.StatusOK, result) - } -} - -// BackupList returns backup list -func BackupList(ctx *gin.Context) { - etcdName := ctx.Param("etcdName") - - kubeconfigPath := "" - - // generate etcd client - cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - // generate k8s client - clusterClient, err := clientset.NewForConfig(cfg) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - // get cluster - cluster, err := clusterClient.KstoneV1alpha1().EtcdClusters(Namespace). - Get(context.TODO(), etcdName, metav1.GetOptions{}) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - // generate backup config - strCfg, found := cluster.Annotations[backup.AnnoBackupConfig] - if !found || strCfg == "" { - err = fmt.Errorf( - "backup config not found, annotation key %s not exists, namespace is %s, name is %s", - backup.AnnoBackupConfig, - cluster.Namespace, - cluster.Name, - ) - klog.Errorf(err.Error()) - ctx.JSON(http.StatusOK, []interface{}{}) - return - } - backupConfig := &backup.Config{} - err = json.Unmarshal([]byte(strCfg), backupConfig) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - - // generate backup provider - backupProvider, err := backup.GetBackupProvider(string(backupConfig.StorageType), &backup.ProviderConfig{ - Kubeconfig: kubeconfigPath, - }) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - resp, err := backupProvider.List(cluster) - if err != nil { - klog.Errorf(err.Error()) - ctx.JSON(http.StatusInternalServerError, err) - return - } - ctx.JSON(http.StatusOK, resp) -} diff --git a/pkg/router/util.go b/pkg/router/util.go new file mode 100644 index 0000000..558cafc --- /dev/null +++ b/pkg/router/util.go @@ -0,0 +1,81 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2023 Tencent. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://opensource.org/licenses/Apache-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package router + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" + "go.etcd.io/etcd/client/pkg/v3/transport" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog/v2" + + kstoneapiv1 "tkestack.io/kstone/pkg/apis/kstone/v1alpha1" + "tkestack.io/kstone/pkg/controllers/util" + "tkestack.io/kstone/pkg/etcd" + clientset "tkestack.io/kstone/pkg/generated/clientset/versioned" +) + +func GetEtcdClusterInfo(ctx *gin.Context) (*kstoneapiv1.EtcdCluster, *transport.TLSInfo) { + etcdName := ctx.Param("etcdName") + // generate etcd client + cfg, err := clientcmd.BuildConfigFromFlags("", "") + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return nil, nil + } + + // generate k8s client + clusterClient, err := clientset.NewForConfig(cfg) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return nil, nil + } + + // get cluster + cluster, err := clusterClient.KstoneV1alpha1().EtcdClusters(Namespace). + Get(context.TODO(), etcdName, metav1.GetOptions{}) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return nil, nil + } + + annotations := cluster.Annotations + secretName := "" + if annotations != nil { + if _, found := annotations["certName"]; found { + secretName = annotations["certName"] + } + } + tlsGetter := etcd.NewTLSSecretGetter(util.NewSimpleClientBuilder("")) + klog.Infof("secretName: %s", secretName) + tlsConfig, err := tlsGetter.Config(cluster.Name, secretName) + if err != nil { + klog.Errorf(err.Error()) + ctx.JSON(http.StatusInternalServerError, err) + return nil, nil + } + + return cluster, tlsConfig +}