diff --git a/apis/networking/v1alpha1/configuration_types.go b/apis/networking/v1alpha1/configuration_types.go index 9f2c0c8a50..d40f678377 100644 --- a/apis/networking/v1alpha1/configuration_types.go +++ b/apis/networking/v1alpha1/configuration_types.go @@ -51,7 +51,7 @@ type ClusterConfig struct { // ConfigurationSpec defines the desired state of Configuration. type ConfigurationSpec struct { // Local network configuration (the cluster where the resource is created). - Local ClusterConfig `json:"local,omitempty"` + Local *ClusterConfig `json:"local,omitempty"` // Remote network configuration (the other cluster). Remote ClusterConfig `json:"remote,omitempty"` } diff --git a/apis/networking/v1alpha1/zz_generated.deepcopy.go b/apis/networking/v1alpha1/zz_generated.deepcopy.go index 09f8849405..87c4bb0772 100644 --- a/apis/networking/v1alpha1/zz_generated.deepcopy.go +++ b/apis/networking/v1alpha1/zz_generated.deepcopy.go @@ -84,7 +84,7 @@ func (in *Configuration) DeepCopyInto(out *Configuration) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -141,7 +141,11 @@ func (in *ConfigurationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) { *out = *in - out.Local = in.Local + if in.Local != nil { + in, out := &in.Local, &out.Local + *out = new(ClusterConfig) + **out = **in + } out.Remote = in.Remote } diff --git a/cmd/liqoctl/cmd/generate.go b/cmd/liqoctl/cmd/generate.go index 76b7b4a271..5e63ecb538 100644 --- a/cmd/liqoctl/cmd/generate.go +++ b/cmd/liqoctl/cmd/generate.go @@ -23,6 +23,7 @@ import ( "github.com/liqotech/liqo/pkg/liqoctl/factory" "github.com/liqotech/liqo/pkg/liqoctl/generate" "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" ) const liqoctlGeneratePeerLongHelp = `Generate the command to execute on another cluster to peer with the local cluster. @@ -49,6 +50,23 @@ func newGenerateCommand(ctx context.Context, f *factory.Factory) *cobra.Command } cmd.AddCommand(newGeneratePeerCommand(ctx, f)) + + options := &rest.GenerateOptions{ + Factory: f, + } + + for _, r := range liqoResources { + api := r() + + apiOptions := api.APIOptions() + if apiOptions.EnableGenerate { + cmd.AddCommand(api.Generate(ctx, options)) + } + } + + f.AddNamespaceFlag(cmd.PersistentFlags()) + f.AddLiqoNamespaceFlag(cmd.PersistentFlags()) + return cmd } diff --git a/cmd/liqoctl/cmd/network.go b/cmd/liqoctl/cmd/network.go new file mode 100644 index 0000000000..73d1b401e7 --- /dev/null +++ b/cmd/liqoctl/cmd/network.go @@ -0,0 +1,82 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "time" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/network" + "github.com/liqotech/liqo/pkg/liqoctl/output" +) + +const liqoctlNetworkLongHelp = `Manage liqo networking.` + +const liqoctlNetworkInitLongHelp = `Initialize the liqo networking between two clusters.` + +func newNetworkCommand(ctx context.Context, f *factory.Factory) *cobra.Command { + options := &network.Options{LocalFactory: f} + cmd := &cobra.Command{ + Use: "network", + Short: "Manage liqo networking", + Long: WithTemplate(liqoctlNetworkLongHelp), + Args: cobra.NoArgs, + } + + cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 120*time.Second, "Timeout for completion") + cmd.PersistentFlags().BoolVar(&options.Wait, "wait", false, "Wait for completion") + + cmd.AddCommand(newNetworkInitCommand(ctx, options)) + return cmd +} + +func newNetworkInitCommand(ctx context.Context, options *network.Options) *cobra.Command { + options.RemoteFactory = factory.NewForRemote() + + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize the liqo networking between two clusters", + Long: WithTemplate(liqoctlNetworkInitLongHelp), + Args: cobra.NoArgs, + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + twoClustersPersistentPreRun(cmd, options.LocalFactory, options.RemoteFactory, factory.WithScopedPrinter) + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(options.RunInit(ctx)) + }, + } + + options.LocalFactory.AddFlags(cmd.Flags(), cmd.RegisterFlagCompletionFunc) + options.RemoteFactory.AddFlags(cmd.Flags(), cmd.RegisterFlagCompletionFunc) + + options.LocalFactory.AddNamespaceFlag(cmd.Flags()) + options.RemoteFactory.AddNamespaceFlag(cmd.Flags()) + + options.LocalFactory.AddLiqoNamespaceFlag(cmd.Flags()) + options.RemoteFactory.AddLiqoNamespaceFlag(cmd.Flags()) + + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("namespace", + completion.Namespaces(ctx, options.LocalFactory, completion.NoLimit))) + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("remote-namespace", + completion.Namespaces(ctx, options.RemoteFactory, completion.NoLimit))) + + return cmd +} diff --git a/cmd/liqoctl/cmd/root.go b/cmd/liqoctl/cmd/root.go index 4fe4ca0908..17dd1e5486 100644 --- a/cmd/liqoctl/cmd/root.go +++ b/cmd/liqoctl/cmd/root.go @@ -33,7 +33,9 @@ import ( "github.com/liqotech/liqo/pkg/liqoctl/create" "github.com/liqotech/liqo/pkg/liqoctl/delete" "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/get" "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/liqoctl/rest/configuration" "github.com/liqotech/liqo/pkg/liqoctl/rest/virtualnode" ) @@ -41,6 +43,7 @@ var liqoctl string var liqoResources = []rest.APIProvider{ virtualnode.VirtualNode, + configuration.Configuration, } func init() { @@ -125,6 +128,8 @@ func NewRootCommand(ctx context.Context) *cobra.Command { cmd.AddCommand(newMoveCommand(ctx, f)) cmd.AddCommand(newVersionCommand(ctx, f)) cmd.AddCommand(newDocsCommand(ctx)) + cmd.AddCommand(newNetworkCommand(ctx, f)) + cmd.AddCommand(get.NewGetCommand(ctx, liqoResources, f)) cmd.AddCommand(create.NewCreateCommand(ctx, liqoResources, f)) cmd.AddCommand(delete.NewDeleteCommand(ctx, liqoResources, f)) return cmd diff --git a/cmd/liqoctl/main.go b/cmd/liqoctl/main.go index 5b69b74a56..e3f62d9bff 100644 --- a/cmd/liqoctl/main.go +++ b/cmd/liqoctl/main.go @@ -26,6 +26,7 @@ import ( discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" sharingv1alpha1 "github.com/liqotech/liqo/apis/sharing/v1alpha1" virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" @@ -38,6 +39,7 @@ func init() { utilruntime.Must(offloadingv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(sharingv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(virtualkubeletv1alpha1.AddToScheme(scheme.Scheme)) + utilruntime.Must(networkingv1alpha1.AddToScheme(scheme.Scheme)) } func main() { diff --git a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml index 1c757a313e..46f0071b3a 100644 --- a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml @@ -347,6 +347,14 @@ rules: - scrape/metrics verbs: - get +- apiGroups: + - net.liqo.io + resources: + - ipamstorages + verbs: + - get + - list + - watch - apiGroups: - net.liqo.io resources: diff --git a/go.mod b/go.mod index 64a356a377..420d491ec5 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( k8s.io/klog/v2 v2.100.1 k8s.io/kubectl v0.28.2 k8s.io/metrics v0.28.2 - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/aws-iam-authenticator v0.6.12 sigs.k8s.io/controller-runtime v0.15.1 sigs.k8s.io/sig-storage-lib-external-provisioner/v7 v7.0.1 @@ -238,9 +238,8 @@ require ( go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect + go.uber.org/zap v1.25.0 // indirect golang.org/x/crypto v0.13.0 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect @@ -261,7 +260,7 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 98a64493ff..0ca751e95a 100644 --- a/go.sum +++ b/go.sum @@ -863,14 +863,12 @@ go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLk go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab h1:+yW1yrZ09EYNu1spCUOHBBNRbrLnfmutwyhbhCv3b6Q= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1300,8 +1298,8 @@ k8s.io/metrics v0.28.2 h1:Z/oMk5SmiT/Ji1SaWOPfW2l9W831BLO9/XxDq9iS3ak= k8s.io/metrics v0.28.2/go.mod h1:QTIIdjMrq+KodO+rmp6R9Pr1LZO8kTArNtkWoQXw0sw= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -1322,8 +1320,8 @@ sigs.k8s.io/sig-storage-lib-external-provisioner/v7 v7.0.1/go.mod h1:kWYdKvf1O/T sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/pkg/liqo-controller-manager/external-network/configuration-controller/configuration-controller.go b/pkg/liqo-controller-manager/external-network/configuration-controller/configuration-controller.go index 04664d88f9..5155d3c07b 100644 --- a/pkg/liqo-controller-manager/external-network/configuration-controller/configuration-controller.go +++ b/pkg/liqo-controller-manager/external-network/configuration-controller/configuration-controller.go @@ -19,6 +19,7 @@ import ( "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" @@ -28,6 +29,7 @@ import ( ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/utils/events" + liqogetters "github.com/liqotech/liqo/pkg/utils/getters" ) // ConfigurationReconciler manage Configuration lifecycle. @@ -35,6 +37,8 @@ type ConfigurationReconciler struct { client.Client Scheme *runtime.Scheme EventsRecorder record.EventRecorder + + localCIDR *networkingv1alpha1.ClusterConfigCIDR } // NewConfigurationReconciler returns a new ConfigurationReconciler. @@ -43,6 +47,8 @@ func NewConfigurationReconciler(cl client.Client, s *runtime.Scheme, er record.E Client: cl, Scheme: s, EventsRecorder: er, + + localCIDR: nil, } } @@ -51,6 +57,7 @@ func NewConfigurationReconciler(cl client.Client, s *runtime.Scheme, er record.E // +kubebuilder:rbac:groups=networking.liqo.io,resources=configurations/status,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks,verbs=get;list;watch;create // +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks/status,verbs=get;list;watch +// +kubebuilder:rbac:groups=net.liqo.io,resources=ipamstorages,verbs=get;list;watch // Reconcile manage Configurations, remapping cidrs with Networks resources. func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -62,6 +69,14 @@ func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Reques } return ctrl.Result{}, fmt.Errorf("unable to get the configuration %q: %w", req.NamespacedName, err) } + + if configuration.Spec.Local == nil { + err := r.defaultLocalNetwork(ctx, configuration) + if err != nil { + return ctrl.Result{}, err + } + } + events.Event(r.EventsRecorder, configuration, "Processing") err := r.RemapConfiguration(ctx, configuration, r.EventsRecorder) @@ -82,6 +97,25 @@ func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } +func (r *ConfigurationReconciler) defaultLocalNetwork(ctx context.Context, cfg *networkingv1alpha1.Configuration) error { + if r.localCIDR == nil { + ipamStorage, err := liqogetters.GetIPAMStorageByLabel(ctx, r.Client, labels.NewSelector()) + if err != nil { + return fmt.Errorf("unable to get IPAM storage: %w", err) + } + + r.localCIDR = &networkingv1alpha1.ClusterConfigCIDR{ + Pod: networkingv1alpha1.CIDR(ipamStorage.Spec.PodCIDR), + External: networkingv1alpha1.CIDR(ipamStorage.Spec.ExternalCIDR), + } + } + + cfg.Spec.Local = &networkingv1alpha1.ClusterConfig{ + CIDR: *r.localCIDR, + } + return r.Client.Update(ctx, cfg) +} + // RemapConfiguration remap the configuration using ipamv1alpha1.Network. func (r *ConfigurationReconciler) RemapConfiguration(ctx context.Context, cfg *networkingv1alpha1.Configuration, er record.EventRecorder) error { diff --git a/pkg/liqo-controller-manager/network-controller/network_controller.go b/pkg/liqo-controller-manager/network-controller/network_controller.go index 139fe1baea..636b17e994 100644 --- a/pkg/liqo-controller-manager/network-controller/network_controller.go +++ b/pkg/liqo-controller-manager/network-controller/network_controller.go @@ -31,7 +31,6 @@ import ( networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/consts" "github.com/liqotech/liqo/pkg/liqonet/ipam" - foreignclusterutils "github.com/liqotech/liqo/pkg/utils/foreignCluster" ) const ( @@ -66,18 +65,13 @@ func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // Retrieve the remote cluster ID from the labels. - remoteclusterID, found := nw.Labels[consts.RemoteClusterID] // it should always be present thanks to validating webhook + _, found := nw.Labels[consts.RemoteClusterID] // it should always be present thanks to validating webhook if !found { err := fmt.Errorf("missing label %q on Network %q (webhook disabled or misconfigured)", consts.RemoteClusterID, req.NamespacedName) klog.Error(err) return ctrl.Result{}, err } - _, err := foreignclusterutils.CheckForeignClusterExistence(ctx, r.Client, remoteclusterID) - if err != nil { - return ctrl.Result{}, err - } - desiredCIDR = nw.Spec.CIDR if nw.GetDeletionTimestamp().IsZero() { @@ -100,6 +94,7 @@ func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // The IPAM MapNetworkCIDR() function is not idempotent, so we avoid to call it // multiple times by checking if the status is already set. if nw.Status.CIDR == "" { + var err error remappedCIDR, err = getRemappedCIDR(ctx, r.IpamClient, desiredCIDR) if err != nil { return ctrl.Result{}, err diff --git a/pkg/liqoctl/create/types.go b/pkg/liqoctl/create/types.go index 8df91db483..c30e58879e 100644 --- a/pkg/liqoctl/create/types.go +++ b/pkg/liqoctl/create/types.go @@ -37,6 +37,7 @@ func NewCreateCommand(ctx context.Context, liqoResources []rest.APIProvider, f * } f.AddNamespaceFlag(cmd.PersistentFlags()) + f.AddLiqoNamespaceFlag(cmd.PersistentFlags()) for _, r := range liqoResources { api := r() diff --git a/pkg/liqoctl/factory/factory.go b/pkg/liqoctl/factory/factory.go index f8fdf3da26..b55d9975dc 100644 --- a/pkg/liqoctl/factory/factory.go +++ b/pkg/liqoctl/factory/factory.go @@ -38,6 +38,9 @@ var verbose bool // FlagNamespace -> the name of the namespace flag. const FlagNamespace = "namespace" +// FlagLiqoNamespace -> the name of the Liqo namespace flag. +const FlagLiqoNamespace = "liqo-namespace" + type completionFuncRegisterer func(flagName string, f func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective)) error // Factory provides a set of clients and configurations to authenticate and @@ -60,11 +63,11 @@ type Factory struct { // Whether to add a scope to the printer (i.e., local/remote). ScopedPrinter bool - // Namespace is the namespace that the user has requested with the "--namespace" / "-n" flag, if registered (alternative to LiqoNamespace). + // Namespace is the namespace that the user has requested with the "--namespace" / "-n" flag. Namespace string - // LiqoNamespace is the namespace (where Liqo is installed) that the user has requested with the "--namespace" / "-n" flag, - // if registered (alternative to Namespace). + // LiqoNamespace is the namespace (where Liqo is installed) that the user has requested with the "--liqo-namespace" / "-l" flag, + // if registered. LiqoNamespace string // RESTConfig is a Kubernetes REST config that contains the user's authentication and access configuration. @@ -132,7 +135,7 @@ func (f *Factory) AddFlags(flags *pflag.FlagSet, register completionFuncRegister f.configFlags.AddFlags(tmp) tmp.VisitAll(func(flag *pflag.Flag) { - if flag.Name == "namespace" { + if flag.Name == FlagNamespace { // Exclude the flag concerning the namespace, as manually added only to the relevant subcommands. flag.Usage = "The namespace scope for this request" f.namespaceFlag = flag @@ -159,14 +162,30 @@ func (f *Factory) AddFlags(flags *pflag.FlagSet, register completionFuncRegister // AddNamespaceFlag registers the flag to select the target namespace (alternative to AddLiqoNamespaceFlag). func (f *Factory) AddNamespaceFlag(flags *pflag.FlagSet) { + otherFlag := flags.Lookup(f.remotify(FlagNamespace)) + if otherFlag != nil { + // The flag is already registered. + panic("the namespace flag is already registered, make sure to call AddNamespaceFlag before AddLiqoNamespaceFlag") + } flags.AddFlag(f.remotifyFlag(f.namespaceFlag)) } // AddLiqoNamespaceFlag registers the flag to select the Liqo namespace (alternative to AddNamespaceFlag). func (f *Factory) AddLiqoNamespaceFlag(flags *pflag.FlagSet) { tmp := pflag.NewFlagSet("factory", pflag.PanicOnError) - tmp.StringVarP(&f.LiqoNamespace, FlagNamespace, "n", consts.DefaultLiqoNamespace, "The namespace where Liqo is installed in") - flags.AddFlag(f.remotifyFlag(tmp.Lookup(FlagNamespace))) + var flagName string + var short string + otherFlag := flags.Lookup(f.remotify(FlagNamespace)) + if otherFlag == nil { + flagName = FlagNamespace + short = "n" + } else { + flagName = FlagLiqoNamespace + short = "" + } + tmp.StringVarP(&f.LiqoNamespace, flagName, short, consts.DefaultLiqoNamespace, "The namespace where Liqo is installed in") + fl := tmp.Lookup(flagName) + flags.AddFlag(f.remotifyFlag(fl)) } type options struct{ scoped bool } diff --git a/pkg/liqoctl/get/doc.go b/pkg/liqoctl/get/doc.go new file mode 100644 index 0000000000..a467fc50db --- /dev/null +++ b/pkg/liqoctl/get/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package get contains the implementation of the 'get' command +package get diff --git a/pkg/liqoctl/get/types.go b/pkg/liqoctl/get/types.go new file mode 100644 index 0000000000..4b1a1e6833 --- /dev/null +++ b/pkg/liqoctl/get/types.go @@ -0,0 +1,51 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package get + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// NewGetCommand returns the cobra command for the get subcommand. +func NewGetCommand(ctx context.Context, liqoResources []rest.APIProvider, f *factory.Factory) *cobra.Command { + options := &rest.GetOptions{ + Factory: f, + } + + cmd := &cobra.Command{ + Use: "get", + Short: "Get Liqo resources", + Long: "Get Liqo resources.", + Args: cobra.NoArgs, + } + + f.AddNamespaceFlag(cmd.PersistentFlags()) + + for _, r := range liqoResources { + api := r() + + apiOptions := api.APIOptions() + if apiOptions.EnableGet { + cmd.AddCommand(api.Get(ctx, options)) + } + } + + return cmd +} diff --git a/pkg/liqoctl/network/cluster.go b/pkg/liqoctl/network/cluster.go new file mode 100644 index 0000000000..cd33479751 --- /dev/null +++ b/pkg/liqoctl/network/cluster.go @@ -0,0 +1,115 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package network + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/discovery" + "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest/configuration" + "github.com/liqotech/liqo/pkg/liqoctl/wait" + liqogetters "github.com/liqotech/liqo/pkg/utils/getters" + liqolabels "github.com/liqotech/liqo/pkg/utils/labels" +) + +// Cluster contains the information about a cluster. +type Cluster struct { + local *factory.Factory + remote *factory.Factory + Waiter *wait.Waiter + + clusterIdentity *discoveryv1alpha1.ClusterIdentity + + NetworkConfiguration *networkingv1alpha1.Configuration +} + +// NewCluster returns a new Cluster struct. +func NewCluster(local, remote *factory.Factory) *Cluster { + return &Cluster{ + local: local, + remote: remote, + Waiter: wait.NewWaiterFromFactory(local), + } +} + +// Init initializes the cluster struct. +func (c *Cluster) Init(ctx context.Context) error { + // Get cluster identity. + s := c.local.Printer.StartSpinner("Retrieving cluster identity") + selector, err := metav1.LabelSelectorAsSelector(&liqolabels.ClusterIDConfigMapLabelSelector) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while retrieving cluster identity: %v", output.PrettyErr(err))) + return err + } + cm, err := liqogetters.GetConfigMapByLabel(ctx, c.local.CRClient, c.local.LiqoNamespace, selector) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while retrieving cluster identity: %v", output.PrettyErr(err))) + return err + } + clusterIdentity, err := liqogetters.RetrieveClusterIDFromConfigMap(cm) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while retrieving cluster identity: %v", output.PrettyErr(err))) + return err + } + c.clusterIdentity = clusterIdentity + s.Success("Cluster identity correctly retrieved") + + // Get network configuration. + s = c.local.Printer.StartSpinner("Retrieving network configuration") + conf, err := configuration.ForgeLocalConfiguration(ctx, c.local.CRClient, c.local.Namespace, c.local.LiqoNamespace) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while retrieving network configuration: %v", output.PrettyErr(err))) + return err + } + c.NetworkConfiguration = conf + s.Success("Network configuration correctly retrieved") + + return nil +} + +// SetupConfiguration sets up the network configuration. +func (c *Cluster) SetupConfiguration(ctx context.Context, + conf *networkingv1alpha1.Configuration) error { + s := c.local.Printer.StartSpinner("Setting up network configuration") + conf.Namespace = c.local.Namespace + confCopy := conf.DeepCopy() + _, err := controllerutil.CreateOrUpdate(ctx, c.local.CRClient, conf, func() error { + if conf.Labels == nil { + conf.Labels = make(map[string]string) + } + if confCopy.Labels != nil { + if cID, ok := confCopy.Labels[discovery.ClusterIDLabel]; ok { + conf.Labels[discovery.ClusterIDLabel] = cID + } + } + conf.Spec.Remote = confCopy.Spec.Remote + return nil + }) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while setting up network configuration: %v", output.PrettyErr(err))) + return err + } + + s.Success("Network configuration correctly set up") + return nil +} diff --git a/pkg/liqoctl/network/doc.go b/pkg/liqoctl/network/doc.go new file mode 100644 index 0000000000..3445f8ff7e --- /dev/null +++ b/pkg/liqoctl/network/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package network contains the commands to manage the network of Liqo. +package network diff --git a/pkg/liqoctl/network/handler.go b/pkg/liqoctl/network/handler.go new file mode 100644 index 0000000000..c9e997a5b4 --- /dev/null +++ b/pkg/liqoctl/network/handler.go @@ -0,0 +1,73 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package network + +import ( + "context" + "time" + + "github.com/liqotech/liqo/pkg/liqoctl/factory" +) + +// Options encapsulates the arguments of the network command. +type Options struct { + LocalFactory *factory.Factory + RemoteFactory *factory.Factory + + Timeout time.Duration + Wait bool +} + +// RunInit initializes the liqo networking between two clusters. +func (o *Options) RunInit(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, o.Timeout) + defer cancel() + + // Create and initialize cluster 1. + cluster1 := NewCluster(o.LocalFactory, o.RemoteFactory) + if err := cluster1.Init(ctx); err != nil { + return err + } + + // Create and initialize cluster 2. + cluster2 := NewCluster(o.RemoteFactory, o.LocalFactory) + if err := cluster2.Init(ctx); err != nil { + return err + } + + // Setup Configurations in cluster 1. + if err := cluster1.SetupConfiguration(ctx, cluster2.NetworkConfiguration); err != nil { + return err + } + + // Setup Configurations in cluster 2. + if err := cluster2.SetupConfiguration(ctx, cluster1.NetworkConfiguration); err != nil { + return err + } + + if o.Wait { + // Wait for cluster 1 to be ready. + if err := cluster1.Waiter.ForConfiguration(ctx, cluster2.NetworkConfiguration); err != nil { + return err + } + + // Wait for cluster 2 to be ready. + if err := cluster2.Waiter.ForConfiguration(ctx, cluster1.NetworkConfiguration); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/liqoctl/rest/configuration/create.go b/pkg/liqoctl/rest/configuration/create.go new file mode 100644 index 0000000000..ad16ebc5be --- /dev/null +++ b/pkg/liqoctl/rest/configuration/create.go @@ -0,0 +1,187 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + liqoconsts "github.com/liqotech/liqo/pkg/consts" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlCreateConfigurationLongHelp = `Create a Configuration. + +The Configuration resource is used to represent a remote cluster network configuration. + +Examples: + $ {{ .Executable }} create configuration my-cluster --cluster-id my-cluster-id \ + --pod-cidr 10.0.0.0/16 --external-cidr 10.10.0.0/16` + +// Create creates a Configuration. +func (o *Options) Create(ctx context.Context, options *rest.CreateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "") + + o.createOptions = options + + cmd := &cobra.Command{ + Use: "configuration", + Aliases: []string{"config", "configurations"}, + Short: "Create a Configuration", + Long: liqoctlCreateConfigurationLongHelp, + Args: cobra.ExactArgs(1), + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + options.Name = args[0] + o.createOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleCreate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output format of the resulting Configuration resource. Supported formats: json, yaml") + + cmd.Flags().StringVar(&o.ClusterID, "cluster-id", "", "The cluster ID of the remote cluster") + cmd.Flags().Var(&o.PodCIDR, "pod-cidr", "The pod CIDR of the remote cluster") + cmd.Flags().Var(&o.ExternalCIDR, "external-cidr", "The external CIDR of the remote cluster") + cmd.Flags().BoolVar(&o.Wait, "wait", false, "Wait for the Configuration to be ready") + + runtime.Must(cmd.MarkFlagRequired("cluster-id")) + runtime.Must(cmd.MarkFlagRequired("pod-cidr")) + runtime.Must(cmd.MarkFlagRequired("external-cidr")) + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("cluster-id", completion.ClusterIDs(ctx, + o.createOptions.Factory, completion.NoLimit))) + + return cmd +} + +func (o *Options) handleCreate(ctx context.Context) error { + opts := o.createOptions + + conf := forgeConfiguration(o.createOptions.Name, o.createOptions.Namespace, + o.ClusterID, o.PodCIDR.String(), o.ExternalCIDR.String()) + + if opts.OutputFormat != "" { + opts.Printer.CheckErr(o.output(conf)) + return nil + } + + s := opts.Printer.StartSpinner("Creating configuration") + _, err := controllerutil.CreateOrUpdate(ctx, opts.CRClient, conf, func() error { + mutateConfiguration(conf, o.ClusterID, o.PodCIDR.String(), o.ExternalCIDR.String()) + return nil + }) + if err != nil { + s.Fail("Unable to create configuration: %v", output.PrettyErr(err)) + return err + } + s.Success("Configuration created") + + if o.Wait { + s = opts.Printer.StartSpinner("Waiting for configuration to be ready") + interval := 1 * time.Second + if err := wait.PollUntilContextCancel(ctx, interval, false, func(context.Context) (done bool, err error) { + var appliedConf networkingv1alpha1.Configuration + err = opts.CRClient.Get(ctx, types.NamespacedName{ + Namespace: conf.Namespace, + Name: conf.Name, + }, &appliedConf) + if err != nil { + return false, err + } + + return appliedConf.Status.Remote != nil, nil + }); err != nil { + s.Fail("Unable to wait for configuration to be ready: %v", output.PrettyErr(err)) + return err + } + s.Success("Configuration is ready") + } + + return nil +} + +func forgeConfiguration(name, namespace, clusterID, podCIDR, externalCIDR string) *networkingv1alpha1.Configuration { + conf := &networkingv1alpha1.Configuration{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.ConfigurationKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + liqoconsts.RemoteClusterID: clusterID, + }, + }, + } + mutateConfiguration(conf, clusterID, podCIDR, externalCIDR) + return conf +} + +func mutateConfiguration(conf *networkingv1alpha1.Configuration, clusterID, podCIDR, externalCIDR string) { + conf.Kind = networkingv1alpha1.ConfigurationKind + conf.APIVersion = networkingv1alpha1.GroupVersion.String() + if conf.Labels == nil { + conf.Labels = make(map[string]string) + } + conf.Labels[liqoconsts.RemoteClusterID] = clusterID + conf.Spec.Remote.CIDR.Pod = networkingv1alpha1.CIDR(podCIDR) + conf.Spec.Remote.CIDR.External = networkingv1alpha1.CIDR(externalCIDR) +} + +// output implements the logic to output the generated Configuration resource. +func (o *Options) output(conf *networkingv1alpha1.Configuration) error { + var outputFormat string + switch { + case o.createOptions != nil: + outputFormat = o.createOptions.OutputFormat + case o.generateOptions != nil: + outputFormat = o.generateOptions.OutputFormat + default: + return fmt.Errorf("unable to determine output format") + } + var printer printers.ResourcePrinter + switch outputFormat { + case "yaml": + printer = &printers.YAMLPrinter{} + case "json": + printer = &printers.JSONPrinter{} + default: + return fmt.Errorf("unsupported output format %q", outputFormat) + } + + return printer.PrintObj(conf, os.Stdout) +} diff --git a/pkg/liqoctl/rest/configuration/delete.go b/pkg/liqoctl/rest/configuration/delete.go new file mode 100644 index 0000000000..6a8e6d9e8a --- /dev/null +++ b/pkg/liqoctl/rest/configuration/delete.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Delete deletes a Configuration. +func (o *Options) Delete(_ context.Context, _ *rest.DeleteOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/configuration/doc.go b/pkg/liqoctl/rest/configuration/doc.go new file mode 100644 index 0000000000..990bee62dc --- /dev/null +++ b/pkg/liqoctl/rest/configuration/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package configuration contains the logic to manage the network configuration. +package configuration diff --git a/pkg/liqoctl/rest/configuration/generate.go b/pkg/liqoctl/rest/configuration/generate.go new file mode 100644 index 0000000000..15f14f8e0f --- /dev/null +++ b/pkg/liqoctl/rest/configuration/generate.go @@ -0,0 +1,121 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + liqoconsts "github.com/liqotech/liqo/pkg/consts" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + liqoutils "github.com/liqotech/liqo/pkg/utils" + "github.com/liqotech/liqo/pkg/utils/args" + liqogetters "github.com/liqotech/liqo/pkg/utils/getters" +) + +const liqoctlGenerateConfigHelp = `Generate the local network configuration to be applied to other clusters.` + +// Generate generates a Configuration. +func (o *Options) Generate(ctx context.Context, options *rest.GenerateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "yaml") + + o.generateOptions = options + + cmd := &cobra.Command{ + Use: "configuration", + Aliases: []string{"config", "configurations"}, + Short: "Generate a Configuration", + Long: liqoctlGenerateConfigHelp, + Args: cobra.NoArgs, + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + o.generateOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleGenerate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output format of the resulting Configuration resource. Supported formats: json, yaml") + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + + return cmd +} + +func (o *Options) handleGenerate(ctx context.Context) error { + opts := o.generateOptions + + conf, err := ForgeLocalConfiguration(ctx, opts.CRClient, opts.Namespace, opts.LiqoNamespace) + if err != nil { + opts.Printer.CheckErr(fmt.Errorf("unable to forge local configuration: %w", err)) + return err + } + + opts.Printer.CheckErr(o.output(conf)) + return nil +} + +// ForgeLocalConfiguration creates a local configuration starting from the cluster identity and the IPAM storage. +func ForgeLocalConfiguration(ctx context.Context, cl client.Client, namespace, liqoNamespace string) (*networkingv1alpha1.Configuration, error) { + clusterIdentity, err := liqoutils.GetClusterIdentityWithControllerClient(ctx, cl, liqoNamespace) + if err != nil { + return nil, fmt.Errorf("unable to get cluster identity: %w", err) + } + + ipamStorage, err := liqogetters.GetIPAMStorageByLabel(ctx, cl, labels.NewSelector()) + if err != nil { + return nil, fmt.Errorf("unable to get IPAM storage: %w", err) + } + + cnf := &networkingv1alpha1.Configuration{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.ConfigurationKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: clusterIdentity.ClusterName, + Labels: map[string]string{ + liqoconsts.RemoteClusterID: clusterIdentity.ClusterID, + }, + }, + Spec: networkingv1alpha1.ConfigurationSpec{ + Remote: networkingv1alpha1.ClusterConfig{ + CIDR: networkingv1alpha1.ClusterConfigCIDR{ + Pod: networkingv1alpha1.CIDR(ipamStorage.Spec.PodCIDR), + External: networkingv1alpha1.CIDR(ipamStorage.Spec.ExternalCIDR), + }, + }, + }, + } + + if namespace != "" && namespace != corev1.NamespaceDefault { + cnf.Namespace = namespace + } + return cnf, nil +} diff --git a/pkg/liqoctl/rest/configuration/get.go b/pkg/liqoctl/rest/configuration/get.go new file mode 100644 index 0000000000..0d8ceb0aa7 --- /dev/null +++ b/pkg/liqoctl/rest/configuration/get.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Get implements the get command. +func (o *Options) Get(_ context.Context, _ *rest.GetOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/configuration/types.go b/pkg/liqoctl/rest/configuration/types.go new file mode 100644 index 0000000000..7b4a955fe0 --- /dev/null +++ b/pkg/liqoctl/rest/configuration/types.go @@ -0,0 +1,46 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +// Options encapsulates the arguments of the configuration command. +type Options struct { + createOptions *rest.CreateOptions + generateOptions *rest.GenerateOptions + + ClusterID string + PodCIDR args.CIDR + ExternalCIDR args.CIDR + Wait bool +} + +var _ rest.API = &Options{} + +// Configuration returns the rest API for the configuration command. +func Configuration() rest.API { + return &Options{} +} + +// APIOptions returns the APIOptions for the configuration API. +func (o *Options) APIOptions() *rest.APIOptions { + return &rest.APIOptions{ + EnableCreate: true, + EnableGenerate: true, + } +} diff --git a/pkg/liqoctl/rest/configuration/update.go b/pkg/liqoctl/rest/configuration/update.go new file mode 100644 index 0000000000..7c3bca4a1b --- /dev/null +++ b/pkg/liqoctl/rest/configuration/update.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configuration + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Update implements the update command. +func (o *Options) Update(_ context.Context, _ *rest.UpdateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/types.go b/pkg/liqoctl/rest/types.go index 4e593b7710..4a8c4ab2e2 100644 --- a/pkg/liqoctl/rest/types.go +++ b/pkg/liqoctl/rest/types.go @@ -24,10 +24,11 @@ import ( // APIOptions contains the options for the API. type APIOptions struct { - EnableCreate bool - EnableDelete bool - EnableGet bool - EnableUpdate bool + EnableCreate bool + EnableDelete bool + EnableGet bool + EnableUpdate bool + EnableGenerate bool } // CreateOptions contains the options for the create API. @@ -48,6 +49,9 @@ type DeleteOptions struct { // GetOptions contains the options for the get API. type GetOptions struct { *factory.Factory + + OutputFormat string + Name string } // UpdateOptions contains the options for the update API. @@ -55,6 +59,13 @@ type UpdateOptions struct { *factory.Factory } +// GenerateOptions contains the options for the generate API. +type GenerateOptions struct { + *factory.Factory + + OutputFormat string +} + // API is the interface that must be implemented by the API. type API interface { APIOptions() *APIOptions @@ -62,6 +73,7 @@ type API interface { Delete(ctx context.Context, options *DeleteOptions) *cobra.Command Get(ctx context.Context, options *GetOptions) *cobra.Command Update(ctx context.Context, options *UpdateOptions) *cobra.Command + Generate(ctx context.Context, options *GenerateOptions) *cobra.Command } // APIProvider is the function that returns the API. diff --git a/pkg/liqoctl/rest/virtualnode/generate.go b/pkg/liqoctl/rest/virtualnode/generate.go new file mode 100644 index 0000000000..6fc114e796 --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/generate.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package virtualnode + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Generate generates a VirtualNode. +func (o *Options) Generate(_ context.Context, _ *rest.GenerateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/wait/wait.go b/pkg/liqoctl/wait/wait.go index f101762c02..59dbae3a98 100644 --- a/pkg/liqoctl/wait/wait.go +++ b/pkg/liqoctl/wait/wait.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" "github.com/liqotech/liqo/pkg/liqoctl/factory" "github.com/liqotech/liqo/pkg/liqoctl/output" @@ -197,3 +198,27 @@ func (w *Waiter) ForUnoffloading(ctx context.Context, namespace string) error { s.Success("Unoffloading completed successfully") return nil } + +// ForConfiguration waits until the status on the Configuration resource states that the configuration has been +// successfully applied. +func (w *Waiter) ForConfiguration(ctx context.Context, conf *networkingv1alpha1.Configuration) error { + s := w.Printer.StartSpinner("Waiting for configuration to be applied") + err := wait.PollUntilContextCancel(ctx, 1*time.Second, true, func(ctx context.Context) (done bool, err error) { + currentConf := &networkingv1alpha1.Configuration{} + err = w.CRClient.Get(ctx, client.ObjectKey{Name: conf.Name, Namespace: conf.Namespace}, currentConf) + if err != nil { + return false, client.IgnoreNotFound(err) + } + + return currentConf.Status.Remote != nil && + currentConf.Status.Remote.CIDR.Pod.String() != "" && + currentConf.Status.Remote.CIDR.External.String() != "", + nil + }) + if err != nil { + s.Fail(fmt.Sprintf("Failed waiting for configuration to be applied: %s", output.PrettyErr(err))) + return err + } + s.Success("Configuration applied successfully") + return nil +}