From 8cb8e68a24e2b20711b63b8518633a2bf8a1d76c Mon Sep 17 00:00:00 2001 From: isaaguilar Date: Tue, 18 Jul 2023 11:06:03 -0400 Subject: [PATCH] add vcluster health checks --- pkg/api/api.go | 2 ++ pkg/api/resource.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/pkg/api/api.go b/pkg/api/api.go index e3c262d..16b9f2c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -34,6 +34,8 @@ func (h APIHandler) RegisterRoutes() { cluster := routes.Group("/cluster") cluster.POST("/", h.AddCluster) // Resource from Add/Update/Delete event + cluster.GET("/:cluster_name/health", h.VClusterHealth) + cluster.GET("/:cluster_name/tfohealth", h.VClusterTFOHealth) cluster.PUT("/:cluster_name/sync-dependencies", h.SyncEvent) cluster.POST("/:cluster_name/event", h.ResourceEvent) // routes.GET("/cluster-name/:cluster_name", h.GetCluster) // to be removed cluster.PUT("/:cluster_name/event", h.ResourceEvent) diff --git a/pkg/api/resource.go b/pkg/api/resource.go index 2cfe05c..563156b 100644 --- a/pkg/api/resource.go +++ b/pkg/api/resource.go @@ -229,6 +229,68 @@ func (h APIHandler) getClusterID(clusterName string) uint { return clusters[0].ID } +func (h APIHandler) VClusterHealth(c *gin.Context) { + clusterName := c.Param("cluster_name") + clusterID := h.getClusterID(clusterName) + if clusterID == 0 { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, fmt.Sprintf("cluster_name '%s' not found", clusterName), nil)) + return + } + config, err := getVclusterConfig(h.clientset, "internal", clusterName) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil)) + return + } + clientset := kubernetes.NewForConfigOrDie(config) + n, err := clientset.CoreV1().Namespaces().List(c, metav1.ListOptions{}) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil)) + return + } + if len(n.Items) == 0 { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, "No namespaces available", nil)) + return + } + c.JSON(http.StatusNoContent, nil) +} + +func (h APIHandler) VClusterTFOHealth(c *gin.Context) { + clusterName := c.Param("cluster_name") + clusterID := h.getClusterID(clusterName) + if clusterID == 0 { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, fmt.Sprintf("cluster_name '%s' not found", clusterName), nil)) + return + } + config, err := getVclusterConfig(h.clientset, "internal", clusterName) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil)) + return + } + tfoclientset := tfo.NewForConfigOrDie(config) + if _, err = tfoclientset.TfV1beta1().Terraforms("").List(c, metav1.ListOptions{}); err != nil { + // tfo client cannot query crds and therefore tfo health is not ready + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil)) + return + } + + // Following checks if tfo is running. TFO must be installed similar to the bundled packages in the + // terraform-operator github repo. + clientset := kubernetes.NewForConfigOrDie(config) + n, err := clientset.CoreV1().Pods("tf-system").List(c, metav1.ListOptions{ + LabelSelector: "app=terraform-operator,component=controller", + FieldSelector: "status.phase=Running", + }) + if err != nil { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil)) + return + } + if len(n.Items) == 0 { + c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, "Terraform operator controller is not running", nil)) + return + } + c.JSON(http.StatusNoContent, nil) +} + // ResourcePoll is a short poll that checks and returns resources created from the tf resource's workflow // that have the correct label and annotation value. func (h APIHandler) ResourcePoll(c *gin.Context) {