hgctl/pkg/code_debug.go (240 lines of code) (raw):

// Copyright (c) 2022 Alibaba Group Holding Ltd. // // 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 // // http://www.apache.org/licenses/LICENSE-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 OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hgctl import ( "context" "fmt" "io" "net" "os" "regexp" "time" "github.com/alibaba/higress/hgctl/pkg/helm" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) const ( DefaultIp = "127.0.0.1" DefaultPort = ":15051" ) func newCodeDebugCmd() *cobra.Command { codeDebugCmd := &cobra.Command{ Use: "code-debug", Short: "Start or stop code debug", } codeDebugCmd.AddCommand(getStartCodeDebugCmd()) codeDebugCmd.AddCommand(getStopCodeDebugCmd()) return codeDebugCmd } func getStartCodeDebugCmd() *cobra.Command { homeDir, err := os.UserHomeDir() if err != nil { fmt.Printf("fail to get user home dir: %v", err) os.Exit(1) } kubeConfigDir := homeDir + "/.kube/config" startCodeDebugCmd := &cobra.Command{ Use: "start", Aliases: []string{"start"}, Short: "Start code debug", Example: "hgctl code-debug start", RunE: func(c *cobra.Command, args []string) error { writer := c.OutOrStdout() // wait for user to confirm if !promptCodeDebug(writer, "local grpc address") { return nil } // check profile type is local or not fmt.Fprintf(writer, "Checking profile type...\n") profiles, err := getAllProfiles() if err != nil { return fmt.Errorf("fail to get all profiles: %v", err) } if len(profiles) == 0 { fmt.Fprintf(writer, "Higress hasn't been installed yet!\n") return nil } for _, profile := range profiles { if profile.Install != helm.InstallLocalK8s { fmt.Fprintf(writer, "\nHigress needs to be installed locally!\n") return nil } } // get kubernetes clientSet fmt.Fprintf(writer, "Getting kubernetes clientset...\n") config, err := clientcmd.BuildConfigFromFlags("", kubeConfigDir) if err != nil { fmt.Fprintf(writer, "fail to build config from kubeconfig: %v", err) return nil } clientSet, err := kubernetes.NewForConfig(config) if err != nil { fmt.Fprintf(writer, "fail to create kubernetes clientset: %v", err) return nil } // get non-loopback IPv4 address fmt.Fprintf(writer, "Getting non-loopback IPv4 address...\n") ip, err := getNonLoopbackIPv4() if err != nil { fmt.Fprintf(writer, "fail to get non-loopback IPv4 address: %v", err) return nil } // update the xds address in higress-config ConfigMap // and trigger rollout for higress-controller and higress-gateway deployments fmt.Fprintf(writer, "Updating xds address in higress-config ConfigMap "+ "and triggering rollout for higress-controller and higress-gateway deployments...\n") err = updateXdsIpAndRollout(clientSet, ip, DefaultPort) if err != nil { fmt.Fprintf(writer, "fail to update xds address in higress-config ConfigMap: %v", err) return nil } fmt.Fprintf(writer, "Code debug started!\n") return nil }, } startCodeDebugCmd.PersistentFlags().StringVar(&kubeConfigDir, "kubeconfig", kubeConfigDir, "Use a Kubernetes configuration file instead of in-cluster configuration") return startCodeDebugCmd } func getStopCodeDebugCmd() *cobra.Command { homeDir, err := os.UserHomeDir() if err != nil { fmt.Printf("fail to get user home dir: %v", err) os.Exit(1) } kubeConfigDir := homeDir + "/.kube/config" stopCodeDebugCmd := &cobra.Command{ Use: "stop", Aliases: []string{"stop"}, Short: "Stop code debug", Example: "hgctl code-debug stop", RunE: func(c *cobra.Command, args []string) error { // wait for user to confirm writer := c.OutOrStdout() if !promptCodeDebug(writer, "default grpc address") { return nil } // check profile type is local or not fmt.Fprintf(writer, "Checking profile type...\n") profiles, err := getAllProfiles() if err != nil { return fmt.Errorf("fail to get all profiles: %v", err) } if len(profiles) == 0 { fmt.Fprintf(writer, "Higress hasn't been installed yet!\n") return nil } for _, profile := range profiles { if profile.Install != helm.InstallLocalK8s { fmt.Fprintf(writer, "\nHigress needs to be installed locally!\n") return nil } } // get kubernetes clientSet fmt.Fprintf(writer, "Getting kubernetes clientset...\n") config, err := clientcmd.BuildConfigFromFlags("", kubeConfigDir) if err != nil { fmt.Fprintf(writer, "fail to build config from kubeconfig: %v", err) return nil } clientSet, err := kubernetes.NewForConfig(config) if err != nil { fmt.Fprintf(writer, "fail to create kubernetes clientset: %v", err) return nil } // recover the xds address in higress-config ConfigMap // and trigger rollout for higress-controller and higress-gateway deployments fmt.Fprintf(writer, "Recovering xds address in higress-config ConfigMap "+ "and triggering rollout for higress-controller and higress-gateway deployments...\n") err = updateXdsIpAndRollout(clientSet, DefaultIp, DefaultPort) if err != nil { fmt.Fprintf(writer, "fail to recover xds address in higress-config ConfigMap: %v", err) return nil } fmt.Fprintf(writer, "Code debug stopped!\n") return nil }, } stopCodeDebugCmd.PersistentFlags().StringVar(&kubeConfigDir, "kubeconfig", kubeConfigDir, "Use a Kubernetes configuration file instead of in-cluster configuration") return stopCodeDebugCmd } // getNonLoopbackIPv4 returns the first non-loopback IPv4 address of the host. func getNonLoopbackIPv4() (string, error) { // get all network interfaces interfaces, err := net.Interfaces() if err != nil { return "", err } // traverse all network interfaces for _, i := range interfaces { // exclude loopback interface and virtual interface if i.Flags&net.FlagLoopback == 0 && i.Flags&net.FlagUp != 0 { // get all addresses of the interface addrs, err := i.Addrs() if err != nil { return "", err } // traverse all addresses of the interface for _, addr := range addrs { // check the type of the address is IP address if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { // check the IP address is IPv4 address if ipnet.IP.To4() != nil { return ipnet.IP.String(), nil } } } } } return "", fmt.Errorf("Non-loopback IPv4 address not found") } // updateXdsIpAndRollout updates the xds address in higress-config ConfigMap // and triggers rollout for higress-controller and higress-gateway deployments // also can recover the xds address in higress-config ConfigMap func updateXdsIpAndRollout(c *kubernetes.Clientset, ip string, port string) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Get higress-config ConfigMap cm, err := c.CoreV1().ConfigMaps("higress-system").Get(ctx, "higress-config", metav1.GetOptions{}) if err != nil { return err } // Update mesh field in higress-config ConfigMap if _, ok := cm.Data["mesh"]; !ok { return fmt.Errorf("mesh not found in configmap higress-config") } mesh := cm.Data["mesh"] newMesh, err := replaceXDSAddress(mesh, ip, port) if err != nil { return err } cm.Data["mesh"] = newMesh // Update higress-config ConfigMap _, err = c.CoreV1().ConfigMaps("higress-system").Update(ctx, cm, metav1.UpdateOptions{}) if err != nil { return err } // Trigger rollout for higress-controller deployment err = triggerRollout(c, "higress-controller") if err != nil { return err } // Trigger rollout for higress-gateway deployment err = triggerRollout(c, "higress-gateway") if err != nil { return err } return nil } // triggerRollout triggers rollout for the specified deployment func triggerRollout(clientset *kubernetes.Clientset, deploymentName string) error { deploymentsClient := clientset.AppsV1().Deployments("higress-system") // Get the deployment deployment, err := deploymentsClient.Get(context.TODO(), deploymentName, metav1.GetOptions{}) if err != nil { return err } // Increment the deployment's revision to trigger a rollout deployment.Spec.Template.ObjectMeta.Labels["version"] = time.Now().Format("20060102150405") // Update the deployment _, err = deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{}) if err != nil { return err } return nil } // replaceXDSAddress replaces the xds address in the config string with new IP and Port func replaceXDSAddress(configString, newIP, newPort string) (string, error) { // define the regular expression to match xds address xdsRegex := regexp.MustCompile(`xds://[0-9.:]+`) // find the first match match := xdsRegex.FindString(configString) if match == "" { // if no match, return error return "", fmt.Errorf("no xds address found in config string") } // replace xds address with new IP and Port newXDSAddress := fmt.Sprintf("xds://%s%s", newIP, newPort) result := xdsRegex.ReplaceAllString(configString, newXDSAddress) return result, nil } // promptCodeDebug prompts user to confirm code debug func promptCodeDebug(writer io.Writer, t string) bool { answer := "" for { fmt.Fprintf(writer, "This will start set xds address to %s in higress-config ConfigMap "+ "and trigger rollout for higress-controller and higress-gateway deployments. \nProceed? (y/N)", t) fmt.Scanln(&answer) if answer == "y" { return true } if answer == "N" { fmt.Fprintf(writer, "Cancelled.\n") return false } } }