Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] liqoctl gateway template check #2791

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions cmd/liqoctl/cmd/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,7 @@ func newNetworkConnectCommand(ctx context.Context, options *network.Options) *co
Args: cobra.NoArgs,

Run: func(_ *cobra.Command, _ []string) {
err := options.RunConnect(ctx)
if err != nil {
options.LocalFactory.Printer.CheckErr(
fmt.Errorf("`network connect` failed (error: %w). Issue `network disconnect` to cleanup the environment", err))
}
output.ExitOnErr(err)
output.ExitOnErr(options.RunConnect(ctx))
},
}

Expand All @@ -153,18 +148,18 @@ func newNetworkConnectCommand(ctx context.Context, options *network.Options) *co
"Type of Gateway Server. Leave empty to use default Liqo implementation of WireGuard")
cmd.Flags().StringVar(&options.ServerTemplateName, "server-template-name", forge.DefaultGwServerTemplateName,
"Name of the Gateway Server template")
cmd.Flags().StringVar(&options.ServerTemplateNamespace, "server-template-namespace", "",
cmd.Flags().StringVar(&options.ServerTemplateNamespace, "server-template-namespace", forge.DefaultGwServerTemplateNamespace,
"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,
Expand Down
10 changes: 7 additions & 3 deletions cmd/liqoctl/cmd/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ spec:
- port: "{{"{{ .Spec.Endpoint.Port }}"}}"
protocol: UDP
targetPort: "{{"{{ .Spec.Endpoint.Port }}"}}"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should align this file to the eks one also on what concerns empty lines

{{- if .Values.networking.gatewayTemplates.server.service.allocateLoadBalancerNodePorts }}
allocateLoadBalancerNodePorts: {{ .Values.networking.gatewayTemplates.server.service.allocateLoadBalancerNodePorts }}
{{- end }}
Expand Down
11 changes: 6 additions & 5 deletions pkg/liqo-controller-manager/networking/forge/gatewayserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ import (

// Default values for the GatewayServer.
const (
DefaultGwServerType = "networking.liqo.io/v1beta1/wggatewayservertemplates"
DefaultGwServerTemplateName = "wireguard-server"
DefaultGwServerServiceType = corev1.ServiceTypeLoadBalancer
DefaultGwServerPort = 51840
DefaultKeysDir = "/etc/wireguard/keys"
DefaultGwServerType = "networking.liqo.io/v1beta1/wggatewayservertemplates"
DefaultGwServerTemplateName = "wireguard-server"
DefaultGwServerTemplateNamespace = "liqo"
DefaultGwServerServiceType = corev1.ServiceTypeLoadBalancer
DefaultGwServerPort = 51840
DefaultKeysDir = "/etc/wireguard/keys"
)

// defaultGatewayServerName returns the default name for a GatewayServer.
Expand Down
13 changes: 13 additions & 0 deletions pkg/liqoctl/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -58,6 +59,8 @@ type Factory struct {

// Printer is the object used to output messages in the appropriate format.
Printer *output.Printer
// PrinterGlobal is the object used to output messages in the appropriate format. It is not scoped to local or remote cluster.
PrinterGlobal *output.Printer
// SkipConfirm determines whether to skip confirmations.
SkipConfirm bool
// Whether to add a scope to the printer (i.e., local/remote).
Expand All @@ -79,6 +82,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
}

Expand Down Expand Up @@ -209,6 +215,8 @@ func (f *Factory) Initialize(opts ...Options) (err error) {
f.Printer = output.NewLocalPrinter(o.scoped, verbose)
}

f.PrinterGlobal = output.NewGlobalPrinter(o.scoped, verbose)

if f.Namespace == "" {
f.Namespace, _, err = f.factory.ToRawKubeConfigLoader().Namespace()
if err != nil {
Expand All @@ -232,6 +240,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})
Expand Down
120 changes: 120 additions & 0 deletions pkg/liqoctl/network/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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, " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we add a flag to skip this check (as suggested by aleoli), we can add a suggestion of how to skip this check. Morevoer, we might also add a documentation page explaining the concept of template and how to add a field in the template, so that we can add a link to it in the error message

"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")
Expand Down
30 changes: 20 additions & 10 deletions pkg/liqoctl/network/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Comment on lines +169 to +176
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a flag to skip these checks


// Check if the Networking is initialized on cluster 1
if err := cluster1.CheckNetworkInitialized(ctx, cluster2.localClusterID); err != nil {
return err
Expand Down Expand Up @@ -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),
}
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/liqoctl/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ func NewRemotePrinter(scoped, verbose bool) *Printer {
return newPrinter(remoteClusterName, remoteClusterColor, scoped, verbose)
}

// NewGlobalPrinter returns a new printer referring to the global scope.
func NewGlobalPrinter(scoped, verbose bool) *Printer {
return newPrinter("global", pterm.FgDefault, scoped, verbose)
}

func newPrinter(scope string, color pterm.Color, scoped, verbose bool) *Printer {
generic := &pterm.PrefixPrinter{MessageStyle: pterm.NewStyle(pterm.FgDefault)}

Expand Down
Loading