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 25, 2024
1 parent b2ac20b commit 51ea3b0
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 31 deletions.
14 changes: 7 additions & 7 deletions cmd/liqoctl/cmd/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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,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 }}
Expand Down
9 changes: 9 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 @@ -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
}

Expand Down Expand Up @@ -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})
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, " +
"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
}

// 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
24 changes: 13 additions & 11 deletions pkg/liqoctl/peer/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions pkg/utils/maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 51ea3b0

Please sign in to comment.