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

Merged
merged 1 commit into from
Nov 4, 2024
Merged
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
22 changes: 9 additions & 13 deletions cmd/liqoctl/cmd/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func newNetworkCommand(ctx context.Context, f *factory.Factory) *cobra.Command {

cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 120*time.Second, "Timeout for completion")
cmd.PersistentFlags().BoolVar(&options.Wait, "wait", false, "Wait for completion")
cmd.PersistentFlags().BoolVar(&options.SkipValidation, "skip-validation", false, "Skip the validation")

options.LocalFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
options.RemoteFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
Expand Down Expand Up @@ -139,12 +140,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 @@ -156,15 +152,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,
Expand Down
11 changes: 8 additions & 3 deletions cmd/liqoctl/cmd/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func newPeerCommand(ctx context.Context, f *factory.Factory) *cobra.Command {
}

cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 10*time.Minute, "Timeout for peering completion")
cmd.PersistentFlags().BoolVar(&options.SkipValidation, "skip-validation", false, "Skip the validation")

options.LocalFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
options.RemoteFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
Expand All @@ -90,11 +91,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
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
127 changes: 127 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,130 @@ 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.ClientTemplateName
templateNamespace := opts.ClientTemplateNamespace
templateGVR := opts.ClientGatewayType

cheina97 marked this conversation as resolved.
Show resolved Hide resolved
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 client 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 server 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 server 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 server template \"%s/%s\": %v",
templateName, templateNamespace, output.PrettyErr(err)))
return err
}

s.Success(fmt.Sprintf("Gateway server 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, " +
cheina97 marked this conversation as resolved.
Show resolved Hide resolved
"since you specified the flag \"--server-service-nodeport\" you need to add the \"nodePort\" field in the template")
fra98 marked this conversation as resolved.
Show resolved Hide resolved
}

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
55 changes: 34 additions & 21 deletions pkg/liqoctl/network/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ type Options struct {
LocalFactory *factory.Factory
RemoteFactory *factory.Factory

Timeout time.Duration
Wait bool

ServerGatewayType string
ServerTemplateName string
ServerTemplateNamespace string
ServerServiceType *argsutils.StringEnum
ServerPort int32
ServerNodePort int32
ServerLoadBalancerIP string
Timeout time.Duration
Wait bool
SkipValidation bool

ServerGatewayType string
ServerTemplateName string
ServerTemplateNamespace string
ServerServiceType *argsutils.StringEnum
ServerServicePort int32
ServerServiceNodePort int32
ServerServiceLoadBalancerIP string

ClientGatewayType string
ClientTemplateName string
Expand Down Expand Up @@ -153,6 +154,14 @@ func (o *Options) RunConnect(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, o.Timeout)
defer cancel()

if o.ServerTemplateNamespace == "" {
o.ServerTemplateNamespace = o.RemoteFactory.LiqoNamespace
}

if o.ClientTemplateNamespace == "" {
o.ClientTemplateNamespace = o.LocalFactory.LiqoNamespace
}

// Create and initialize cluster 1.
cluster1, err := NewCluster(ctx, o.LocalFactory, o.RemoteFactory, true)
if err != nil {
Expand All @@ -165,6 +174,18 @@ func (o *Options) RunConnect(ctx context.Context) error {
return err
}

if !o.SkipValidation {
// Check if the Templates exists and is valid on cluster 2
if err := cluster2.CheckTemplateGwServer(ctx, o); err != nil {
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 Networking is initialized on cluster 1
if err := cluster1.CheckNetworkInitialized(ctx, cluster2.localClusterID); err != nil {
return err
Expand Down Expand Up @@ -319,10 +340,6 @@ func (o *Options) RunDisconnect(ctx context.Context, cluster1, cluster2 *Cluster
}

func (o *Options) newGatewayServerForgeOptions(kubeClient kubernetes.Interface, remoteClusterID liqov1beta1.ClusterID) *forge.GwServerOptions {
if o.ServerTemplateNamespace == "" {
o.ServerTemplateNamespace = o.RemoteFactory.LiqoNamespace
}

return &forge.GwServerOptions{
KubeClient: kubeClient,
RemoteClusterID: remoteClusterID,
Expand All @@ -331,18 +348,14 @@ 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),
}
}

func (o *Options) newGatewayClientForgeOptions(kubeClient kubernetes.Interface, remoteClusterID liqov1beta1.ClusterID,
serverEndpoint *networkingv1beta1.EndpointStatus) *forge.GwClientOptions {
if o.ClientTemplateNamespace == "" {
o.ClientTemplateNamespace = o.LocalFactory.LiqoNamespace
}

return &forge.GwClientOptions{
KubeClient: kubeClient,
RemoteClusterID: remoteClusterID,
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