Skip to content

Commit

Permalink
Add resource reader for gce
Browse files Browse the repository at this point in the history
  • Loading branch information
sawsa307 committed May 24, 2024
1 parent 9e9f70f commit 7afc205
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 8 deletions.
5 changes: 2 additions & 3 deletions pkg/i2gw/providers/gce/gce.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)

// The ProviderName returned to the provider's registry.
const ProviderName = "gce"

func init() {
Expand Down Expand Up @@ -57,8 +56,8 @@ func (p *Provider) ReadResourcesFromCluster(ctx context.Context) error {
return nil
}

func (p *Provider) ReadResourcesFromFile(ctx context.Context, filename string) error {
storage, err := p.reader.readResourcesFromFile(ctx, filename)
func (p *Provider) ReadResourcesFromFile(_ context.Context, filename string) error {
storage, err := p.reader.readResourcesFromFile(filename)
if err != nil {
return fmt.Errorf("failed to read resources from file: %w", err)
}
Expand Down
85 changes: 80 additions & 5 deletions pkg/i2gw/providers/gce/resource_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ limitations under the License.
package gce

import (
"bytes"
"context"
"fmt"
"os"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// converter implements the i2gw.CustomResourceReader interface.
Expand All @@ -35,11 +43,78 @@ func newResourceReader(conf *i2gw.ProviderConf) reader {
}

func (r *reader) readResourcesFromCluster(ctx context.Context) (*storage, error) {
// read example-gateway related resources from the cluster.
return nil, nil
storage := newResourcesStorage()

ingresses, err := r.readGCEIngressesFromCluster(ctx, r.conf.Client)
if err != nil {
return nil, err
}
storage.Ingresses = ingresses

return storage, nil
}

func (r *reader) readResourcesFromFile(filename string) (*storage, error) {
storage := newResourcesStorage()

ingresses, err := r.readGCEIngressesFromFile(filename, r.conf.Namespace)
if err != nil {
return nil, err
}
storage.Ingresses = ingresses

return storage, nil
}

// readGCEIngressesFromCluster lists ingresses from cluster, validates ingress
// class annotation used by GCE, and return the list of supported ingresses.
func (r *reader) readGCEIngressesFromCluster(ctx context.Context, client client.Client) (map[types.NamespacedName]*networkingv1.Ingress, error) {
var ingressList networkingv1.IngressList
err := client.List(ctx, &ingressList)
if err != nil {
return nil, fmt.Errorf("failed to get ingresses from the cluster: %w", err)
}

ingresses := map[types.NamespacedName]*networkingv1.Ingress{}
for i, ingress := range ingressList.Items {
if !isSupportedGCEIngressClass(ingress) {
continue
}
ingresses[types.NamespacedName{Namespace: ingress.Namespace, Name: ingress.Name}] = &ingressList.Items[i]
}

return ingresses, nil
}

func (r *reader) readResourcesFromFile(ctx context.Context, filename string) (*storage, error) {
// read example-gateway related resources from the file.
return nil, nil
// readGCEIngressesFromCluster reads ingress configuration from the given file,
// validates ingress class annotation used by GCE, and return the list of
// supported ingresses.
func (r *reader) readGCEIngressesFromFile(filename string, namespace string) (map[types.NamespacedName]*networkingv1.Ingress, error) {
stream, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read file %v: %w", filename, err)
}

unstructuredObjects, err := common.ExtractObjectsFromReader(bytes.NewReader(stream), namespace)
if err != nil {
return nil, fmt.Errorf("failed to extract objects: %w", err)
}

ingresses := map[types.NamespacedName]*networkingv1.Ingress{}
for _, f := range unstructuredObjects {
if !f.GroupVersionKind().Empty() && f.GroupVersionKind().Kind == "Ingress" {
var ingress networkingv1.Ingress
err = runtime.DefaultUnstructuredConverter.
FromUnstructured(f.UnstructuredContent(), &ingress)
if err != nil {
return nil, err
}
if !isSupportedGCEIngressClass(ingress) {
continue
}
ingresses[types.NamespacedName{Namespace: ingress.Namespace, Name: ingress.Name}] = &ingress
}

}
return ingresses, nil
}
25 changes: 25 additions & 0 deletions pkg/i2gw/providers/gce/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2024 The Kubernetes 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 gce

const (
gceIngressClass = "gce"
gceL7ILBIngressClass = "gce-internal"

gceL7GlobalExternalManagedGatewayClass = "gke-l7-global-external-managed"
gceL7RegionalInternalGatewayClass = "gke-l7-rilb"
)
121 changes: 121 additions & 0 deletions pkg/i2gw/providers/gce/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright 2024 The Kubernetes 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 gce

import (
"fmt"
"strings"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

// isSupportedGCEIngressClass verifies if the given ingress is supported by
// GCE.
// `kubernetes.io/ingress.class` annotation is still used by GCE to specify the
// type of ingresses.
// If the value of this annotation is one of the supported values(`gce` or
// `gce-internal`), `ingressClassName` is ignored by GKE Ingress controller.
// If the annotation is not set, the GKE Ingress controller will only process
// the ingress if its ingressClassName is one of the supported values(`gce` or
// `gce-internal`) or empty.
func isSupportedGCEIngressClass(ingress networkingv1.Ingress) bool {
legacyIngressClass := common.GetIngressClass(ingress)
if legacyIngressClass != "" {
return legacyIngressClass == gceIngressClass || legacyIngressClass == gceL7ILBIngressClass
}
if ingress.Spec.IngressClassName == nil {
return true
}
ingressClass := *ingress.Spec.IngressClassName
return ingressClass == gceIngressClass || ingressClass == gceL7ILBIngressClass
}

// toGceGatewayClass updates the Gateway to use GCE GatewayClass.
func toGceGatewayClass(ingresses []networkingv1.Ingress, gatewayResources *i2gw.GatewayResources) field.ErrorList {
var errs field.ErrorList

// Since we already validated ingress resources when reading, there are
// only two cases here:
// 1. `kubernetes.io/ingress.class` exists in ingress anntation. In this
// case, we use it to map to the corresponding gateway class.
// 2. Annotation does not exist. In this case, the ingress is defaulted
// to use the GCE external ingress implementation, and should be
// mapped to `gke-l7-gxlb`.
for _, ingress := range ingresses {
gwKey := types.NamespacedName{Namespace: ingress.Namespace, Name: common.GetIngressClass(ingress)}
existingGateway := gatewayResources.Gateways[gwKey]

newGateway, err := setGCEGatewayClass(ingress, existingGateway)
if err != nil {
errs = append(errs, err)
}
gatewayResources.Gateways[gwKey] = newGateway
}
if len(errs) > 0 {
return errs
}
return nil
}

// setGCEGatewayClass sets the Gateway to the corresponding GCE GatewayClass.
func setGCEGatewayClass(ingress networkingv1.Ingress, gateway gatewayv1.Gateway) (gatewayv1.Gateway, *field.Error) {
ingressClass := common.GetIngressClass(ingress)

ingressName := fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name)
// Get GCE GatewayClass from from GCE Ingress class.
newGatewayClass, err := ingClassToGwyClassGCE(ingressClass)
if err != nil {
return gateway, field.NotSupported(field.NewPath(ingressName).Child("ObjectMeta", "Annotations", "kubernetes.io/ingress.class"), ingressClass, []string{gceIngressClass, gceL7ILBIngressClass})
}
gateway.Spec.GatewayClassName = gatewayv1.ObjectName(newGatewayClass)
return gateway, nil
}

// ingClassToGwyClassGCE returns the corresponding GCE Gateway Class based on the
// given GCE ingress class.
func ingClassToGwyClassGCE(ingressClass string) (string, error) {
switch ingressClass {
case gceIngressClass:
return gceL7GlobalExternalManagedGatewayClass, nil
case gceL7ILBIngressClass:
return gceL7RegionalInternalGatewayClass, nil
case "":
return gceL7GlobalExternalManagedGatewayClass, nil
default:
return "", fmt.Errorf("Given GCE Ingress Class not supported")
}
}

// checkImplementationSpecificPath checks if this ingress contains a
// ImplementationSpecific path with * and throws an error if it does.
func checkImplementationSpecificPath(ingress *networkingv1.Ingress) *field.Error {
for _, rule := range ingress.Spec.Rules {
for _, path := range rule.HTTP.Paths {
if path.PathType != nil && *path.PathType == networkingv1.PathTypeImplementationSpecific && strings.HasSuffix(path.Path, "/*") {
ingressName := fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name)
detail := "ImplementationSpecific Path with * is not supported for GCE ingress2gateway conversion"
return field.Invalid(field.NewPath(ingressName).Child("Spec", "Rules", "HTTP", "Paths", "Path"), path.Path, detail)
}
}
}
return nil
}
Loading

0 comments on commit 7afc205

Please sign in to comment.