Skip to content

Commit

Permalink
feat: liqoctl gateway template check
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 committed Oct 30, 2024
1 parent b2ac20b commit eed1239
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 58 deletions.
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

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, " +
"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
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

0 comments on commit eed1239

Please sign in to comment.