From 51ea3b03114ec07004866e3a249ab23f0e56d570 Mon Sep 17 00:00:00 2001 From: Francesco Cheinasso Date: Fri, 25 Oct 2024 14:36:46 +0200 Subject: [PATCH] feat: liqoctl gateway template check --- cmd/liqoctl/cmd/network.go | 14 +- cmd/liqoctl/cmd/peer.go | 10 +- ...iqo-wireguard-gateway-server-template.yaml | 2 + pkg/liqoctl/factory/factory.go | 9 ++ pkg/liqoctl/network/cluster.go | 120 ++++++++++++++++++ pkg/liqoctl/network/handler.go | 30 +++-- pkg/liqoctl/peer/handler.go | 24 ++-- pkg/utils/maps/maps.go | 21 +++ 8 files changed, 199 insertions(+), 31 deletions(-) diff --git a/cmd/liqoctl/cmd/network.go b/cmd/liqoctl/cmd/network.go index 7468472b20..4d64caa20b 100644 --- a/cmd/liqoctl/cmd/network.go +++ b/cmd/liqoctl/cmd/network.go @@ -156,15 +156,15 @@ func newNetworkConnectCommand(ctx context.Context, options *network.Options) *co cmd.Flags().StringVar(&options.ServerTemplateNamespace, "server-template-namespace", "", "Namespace of the Gateway Server template") cmd.Flags().Var(options.ServerServiceType, "server-service-type", - fmt.Sprintf("Service type of the Gateway Server. Default: %s."+ + fmt.Sprintf("Service type of the Gateway Server service. Default: %s."+ " Note: use ClusterIP only if you know what you are doing and you have a proper network configuration", forge.DefaultGwServerServiceType)) - cmd.Flags().Int32Var(&options.ServerPort, "server-port", forge.DefaultGwServerPort, - fmt.Sprintf("Port of the Gateway Server. Default: %d", forge.DefaultGwServerPort)) - cmd.Flags().Int32Var(&options.ServerNodePort, "node-port", 0, - "Force the NodePort of the Gateway Server. Leave empty to let Kubernetes allocate a random NodePort") - cmd.Flags().StringVar(&options.ServerLoadBalancerIP, "load-balancer-ip", "", - "Force LoadBalancer IP of the Gateway Server. Leave empty to use the one provided by the LoadBalancer provider") + cmd.Flags().Int32Var(&options.ServerServicePort, "server-service-port", forge.DefaultGwServerPort, + fmt.Sprintf("Port of the Gateway Server service. Default: %d", forge.DefaultGwServerPort)) + cmd.Flags().Int32Var(&options.ServerServiceNodePort, "server-service-nodeport", 0, + "Force the NodePort of the Gateway Server service. Leave empty to let Kubernetes allocate a random NodePort") + cmd.Flags().StringVar(&options.ServerServiceLoadBalancerIP, "server-service-loadbalancerip", "", + "Force LoadBalancer IP of the Gateway Server service. Leave empty to use the one provided by the LoadBalancer provider") // Client flags cmd.Flags().StringVar(&options.ClientGatewayType, "client-type", forge.DefaultGwClientType, diff --git a/cmd/liqoctl/cmd/peer.go b/cmd/liqoctl/cmd/peer.go index 92b3c73db5..f56c1d1bf2 100644 --- a/cmd/liqoctl/cmd/peer.go +++ b/cmd/liqoctl/cmd/peer.go @@ -90,11 +90,15 @@ func newPeerCommand(ctx context.Context, f *factory.Factory) *cobra.Command { // Networking flags cmd.Flags().BoolVar(&options.NetworkingDisabled, "networking-disabled", false, "Disable networking between the two clusters") cmd.Flags().Var(options.ServerServiceType, "server-service-type", - fmt.Sprintf("Service type of the Gateway Server. Default: %s."+ + fmt.Sprintf("Service type of the Gateway Server service. Default: %s."+ " Note: use ClusterIP only if you know what you are doing and you have a proper network configuration", nwforge.DefaultGwServerServiceType)) - cmd.Flags().Int32Var(&options.ServerPort, "server-port", nwforge.DefaultGwServerPort, - fmt.Sprintf("Port of the Gateway Server. Default: %d", nwforge.DefaultGwServerPort)) + cmd.Flags().Int32Var(&options.ServerServicePort, "server-service-port", nwforge.DefaultGwServerPort, + fmt.Sprintf("Port of the Gateway Server service. Default: %d", nwforge.DefaultGwServerPort)) + cmd.Flags().Int32Var(&options.ServerServiceNodePort, "server-service-nodeport", 0, + "Force the NodePort of the Gateway Server service. Leave empty to let Kubernetes allocate a random NodePort") + cmd.Flags().StringVar(&options.ServerServiceLoadBalancerIP, "server-service-loadbalancerip", "", + "IP of the LoadBalancer for the Gateway Server service") cmd.Flags().IntVar(&options.MTU, "mtu", nwforge.DefaultMTU, fmt.Sprintf("MTU of the Gateway server and client. Default: %d", nwforge.DefaultMTU)) diff --git a/deployments/liqo/templates/liqo-wireguard-gateway-server-template.yaml b/deployments/liqo/templates/liqo-wireguard-gateway-server-template.yaml index 1d7db1cdc9..00ef599a76 100644 --- a/deployments/liqo/templates/liqo-wireguard-gateway-server-template.yaml +++ b/deployments/liqo/templates/liqo-wireguard-gateway-server-template.yaml @@ -36,6 +36,8 @@ spec: - port: "{{"{{ .Spec.Endpoint.Port }}"}}" protocol: UDP targetPort: "{{"{{ .Spec.Endpoint.Port }}"}}" + nodePort: "{{"{{ .Spec.Endpoint.NodePort }}"}}" + {{- if .Values.networking.gatewayTemplates.server.service.allocateLoadBalancerNodePorts }} allocateLoadBalancerNodePorts: {{ .Values.networking.gatewayTemplates.server.service.allocateLoadBalancerNodePorts }} {{- end }} diff --git a/pkg/liqoctl/factory/factory.go b/pkg/liqoctl/factory/factory.go index dc956c9ef0..3b85b9bf04 100644 --- a/pkg/liqoctl/factory/factory.go +++ b/pkg/liqoctl/factory/factory.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/pflag" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" cmdutil "k8s.io/kubectl/pkg/cmd/util" @@ -79,6 +80,9 @@ type Factory struct { // kubeClient is a Kubernetes clientset for interacting with the base Kubernetes APIs. KubeClient kubernetes.Interface + // DynCLient + DynClient *dynamic.DynamicClient + helmClient helm.Client } @@ -232,6 +236,11 @@ func (f *Factory) Initialize(opts ...Options) (err error) { return err } + f.DynClient, err = dynamic.NewForConfig(f.RESTConfig) + if err != nil { + return err + } + // Leverage the REST mapper retrieved from the factory, to defer the loading of the mappings until the first API // request is made. This prevents errors in case of invalid kubeconfigs, if no interaction is required. f.CRClient, err = client.New(f.RESTConfig, client.Options{Mapper: restMapper}) diff --git a/pkg/liqoctl/network/cluster.go b/pkg/liqoctl/network/cluster.go index 53eeddd06c..bf7bc78db6 100644 --- a/pkg/liqoctl/network/cluster.go +++ b/pkg/liqoctl/network/cluster.go @@ -21,6 +21,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -29,6 +30,7 @@ import ( networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1" "github.com/liqotech/liqo/pkg/consts" gwforge "github.com/liqotech/liqo/pkg/gateway/forge" + enutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/external-network/utils" "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/forge" networkingutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/utils" "github.com/liqotech/liqo/pkg/liqoctl/factory" @@ -37,6 +39,7 @@ import ( tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace" liqoutils "github.com/liqotech/liqo/pkg/utils" "github.com/liqotech/liqo/pkg/utils/getters" + "github.com/liqotech/liqo/pkg/utils/maps" ) // Cluster contains the information about a cluster. @@ -194,6 +197,123 @@ func (c *Cluster) SetupConfiguration(ctx context.Context, conf *networkingv1beta return nil } +// CheckTemplateGwClient checks if the GatewayClient template is correctly set up. +func (c *Cluster) CheckTemplateGwClient(ctx context.Context, opts *Options) error { + templateName := opts.ServerTemplateName + templateNamespace := opts.ServerTemplateNamespace + templateGVR := opts.ServerGatewayType + + s := c.local.Printer.StartSpinner(fmt.Sprintf("Checking gateway client template %s/%s", templateName, templateNamespace)) + + _, err := c.checkTemplate(ctx, templateName, templateNamespace, templateGVR) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while checking gateway template %s/%s: %v", templateName, templateNamespace, output.PrettyErr(err))) + return err + } + + s.Success(fmt.Sprintf("Gateway client template %s/%s correctly checked", templateName, templateNamespace)) + return nil +} + +// CheckTemplateGwServer checks if the GatewayServer template is correctly set up. +func (c *Cluster) CheckTemplateGwServer(ctx context.Context, opts *Options) error { + templateName := opts.ServerTemplateName + templateNamespace := opts.ServerTemplateNamespace + templateGVR := opts.ServerGatewayType + + s := c.local.Printer.StartSpinner(fmt.Sprintf("Checking gateway client template %s/%s", templateName, templateNamespace)) + + template, err := c.checkTemplate(ctx, templateName, templateNamespace, templateGVR) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while checking gateway template %s/%s: %v", templateName, templateNamespace, output.PrettyErr(err))) + return err + } + + if err := c.checkTemplateServerService(template, opts); err != nil { + s.Fail(fmt.Sprintf("An error occurred while checking gateway template %s/%s: %v", templateName, templateNamespace, output.PrettyErr(err))) + return err + } + + s.Success(fmt.Sprintf("Gateway client template %s/%s correctly checked", templateName, templateNamespace)) + return nil +} + +func (c *Cluster) checkTemplate(ctx context.Context, templateName, templateNamespace, templateGvr string) (*unstructured.Unstructured, error) { + // Server Template Reference + gvr, err := enutils.ParseGroupVersionResource(templateGvr) + if err != nil { + return nil, err + } + + template, err := c.local.DynClient.Resource(gvr).Namespace(templateNamespace).Get(ctx, templateName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return template, nil +} + +// checkTemplateServerService checks if the GatewayServer service template is correctly set up. +func (c *Cluster) checkTemplateServerService(template *unstructured.Unstructured, opts *Options) error { + switch corev1.ServiceType(opts.ServerServiceType.Value) { + case corev1.ServiceTypeClusterIP: + return c.checkTemplateServerServiceClusterIP(template, opts) + case corev1.ServiceTypeNodePort: + return c.checkTemplateServerServiceNodePort(template, opts) + case corev1.ServiceTypeLoadBalancer: + return c.checkTemplateServerServiceLoadBalancer(template, opts) + case corev1.ServiceTypeExternalName: + return fmt.Errorf("externalName service type not supported") + } + return nil +} + +func (c *Cluster) checkTemplateServerServiceClusterIP(_ *unstructured.Unstructured, _ *Options) error { + return nil +} +func (c *Cluster) checkTemplateServerServiceNodePort(template *unstructured.Unstructured, opts *Options) error { + if opts.ServerServiceNodePort == 0 { + return nil + } + + path := "spec.template.spec.service.spec.ports" + templateServicePorts, err := maps.GetNestedField(template.Object, path) + if err != nil { + return fmt.Errorf("unable to get %s of the server template", path) + } + + templateServicePortsSlice, ok := templateServicePorts.([]interface{}) + if !ok { + return fmt.Errorf("unable to cast %s to []interface{}", path) + } + + port, ok := templateServicePortsSlice[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("unable to cast %s to map[string]interface{}", path) + } + + _, err = maps.GetNestedField(port, "nodeport") + if err != nil { + return fmt.Errorf("unable to get spec.template.spec.service.spec.ports[0].nodeport int the server template, " + + "since you specified the flag \"--server-service-nodeport\" you need to add the nodeport field in the template") + } + + return nil +} +func (c *Cluster) checkTemplateServerServiceLoadBalancer(template *unstructured.Unstructured, opts *Options) error { + if opts.ServerServiceLoadBalancerIP == "" { + return nil + } + + path := "spec.template.spec.service.spec.loadBalancerIP" + _, err := maps.GetNestedField(template.Object, path) + if err != nil { + return fmt.Errorf("unable to get %s of the server template, "+ + "since you specified the flag \"--server-service-loadbalancerip\" you need to add the loadBalancerIP field in the template", path) + } + return nil +} + // CheckNetworkInitialized checks if the network is initialized correctly. func (c *Cluster) CheckNetworkInitialized(ctx context.Context, remoteClusterID liqov1beta1.ClusterID) error { s := c.local.Printer.StartSpinner("Checking network is initialized correctly") diff --git a/pkg/liqoctl/network/handler.go b/pkg/liqoctl/network/handler.go index 1b1fd3d24e..d32d451f7f 100644 --- a/pkg/liqoctl/network/handler.go +++ b/pkg/liqoctl/network/handler.go @@ -38,13 +38,13 @@ type Options struct { Timeout time.Duration Wait bool - ServerGatewayType string - ServerTemplateName string - ServerTemplateNamespace string - ServerServiceType *argsutils.StringEnum - ServerPort int32 - ServerNodePort int32 - ServerLoadBalancerIP string + ServerGatewayType string + ServerTemplateName string + ServerTemplateNamespace string + ServerServiceType *argsutils.StringEnum + ServerServicePort int32 + ServerServiceNodePort int32 + ServerServiceLoadBalancerIP string ClientGatewayType string ClientTemplateName string @@ -165,6 +165,16 @@ func (o *Options) RunConnect(ctx context.Context) error { return err } + // Check if the Templates exists and is valid on cluster 1 + if err := cluster1.CheckTemplateGwClient(ctx, o); err != nil { + return err + } + + // Check if the Templates exists and is valid on cluster 2 + if err := cluster2.CheckTemplateGwServer(ctx, o); err != nil { + return err + } + // Check if the Networking is initialized on cluster 1 if err := cluster1.CheckNetworkInitialized(ctx, cluster2.localClusterID); err != nil { return err @@ -331,9 +341,9 @@ func (o *Options) newGatewayServerForgeOptions(kubeClient kubernetes.Interface, TemplateNamespace: o.ServerTemplateNamespace, ServiceType: corev1.ServiceType(o.ServerServiceType.Value), MTU: o.MTU, - Port: o.ServerPort, - NodePort: ptr.To(o.ServerNodePort), - LoadBalancerIP: ptr.To(o.ServerLoadBalancerIP), + Port: o.ServerServicePort, + NodePort: ptr.To(o.ServerServiceNodePort), + LoadBalancerIP: ptr.To(o.ServerServiceLoadBalancerIP), } } diff --git a/pkg/liqoctl/peer/handler.go b/pkg/liqoctl/peer/handler.go index 8551eacf12..09b0ac5d9c 100644 --- a/pkg/liqoctl/peer/handler.go +++ b/pkg/liqoctl/peer/handler.go @@ -39,10 +39,12 @@ type Options struct { Timeout time.Duration // Networking options - NetworkingDisabled bool - ServerServiceType *argsutils.StringEnum - ServerPort int32 - MTU int + NetworkingDisabled bool + ServerServiceType *argsutils.StringEnum + ServerServicePort int32 + ServerServiceNodePort int32 + ServerServiceLoadBalancerIP string + MTU int // Authentication options CreateResourceSlice bool @@ -105,13 +107,13 @@ func ensureNetworking(ctx context.Context, o *Options) error { Timeout: o.Timeout, Wait: true, - ServerGatewayType: nwforge.DefaultGwServerType, - ServerTemplateName: nwforge.DefaultGwServerTemplateName, - ServerTemplateNamespace: o.RemoteFactory.LiqoNamespace, - ServerServiceType: o.ServerServiceType, - ServerPort: o.ServerPort, - ServerNodePort: 0, - ServerLoadBalancerIP: "", + ServerGatewayType: nwforge.DefaultGwServerType, + ServerTemplateName: nwforge.DefaultGwServerTemplateName, + ServerTemplateNamespace: o.RemoteFactory.LiqoNamespace, + ServerServiceType: o.ServerServiceType, + ServerServicePort: o.ServerServicePort, + ServerServiceNodePort: o.ServerServiceNodePort, + ServerServiceLoadBalancerIP: o.ServerServiceLoadBalancerIP, ClientGatewayType: nwforge.DefaultGwClientType, ClientTemplateName: nwforge.DefaultGwClientTemplateName, diff --git a/pkg/utils/maps/maps.go b/pkg/utils/maps/maps.go index 14c74d7106..1279e1b9ca 100644 --- a/pkg/utils/maps/maps.go +++ b/pkg/utils/maps/maps.go @@ -197,3 +197,24 @@ func SerializeMap(m map[string]string) string { func DeSerializeCache(s string) []string { return strings.Split(s, ",") } + +// GetNestedField returns the nested field of a map. +// Example: GetNestedField(map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "a.b") returns "c". +func GetNestedField(m map[string]interface{}, path string) (interface{}, error) { + fields := strings.Split(path, ".") + current := m + for i, field := range fields { + next, ok := current[field] + if !ok { + return nil, fmt.Errorf("unable to get %s", strings.Join(fields[:i+1], ".")) + } + if i == len(fields)-1 { + return next, nil + } + current, ok = next.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unable to get %s", strings.Join(fields[:i+1], ".")) + } + } + return current, nil +}