istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/gateway/gatewayclass.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gateway
    16  
    17  import (
    18  	"github.com/hashicorp/go-multierror"
    19  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/types"
    22  	k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
    23  	gateway "sigs.k8s.io/gateway-api/apis/v1beta1"
    24  
    25  	"istio.io/istio/pilot/pkg/model/kstatus"
    26  	"istio.io/istio/pkg/kube"
    27  	"istio.io/istio/pkg/kube/controllers"
    28  	"istio.io/istio/pkg/kube/kclient"
    29  	"istio.io/istio/pkg/util/istiomultierror"
    30  )
    31  
    32  // ClassController is a controller that creates the default Istio GatewayClass(s). This will not
    33  // continually reconcile the full state of the GatewayClass object, and instead only create the class
    34  // if it doesn't exist. This allows users to manage it through other means or modify it as they wish.
    35  // If it is deleted, however, it will be added back.
    36  // This controller intentionally does not do leader election for simplicity. Because we only create
    37  // and not update there is no need; the first controller to create the GatewayClass wins.
    38  type ClassController struct {
    39  	queue   controllers.Queue
    40  	classes kclient.Client[*gateway.GatewayClass]
    41  }
    42  
    43  func NewClassController(kc kube.Client) *ClassController {
    44  	gc := &ClassController{}
    45  	gc.queue = controllers.NewQueue("gateway class",
    46  		controllers.WithReconciler(gc.Reconcile),
    47  		controllers.WithMaxAttempts(25))
    48  
    49  	gc.classes = kclient.New[*gateway.GatewayClass](kc)
    50  	gc.classes.AddEventHandler(controllers.FilteredObjectHandler(gc.queue.AddObject, func(o controllers.Object) bool {
    51  		_, f := builtinClasses[gateway.ObjectName(o.GetName())]
    52  		return f
    53  	}))
    54  	return gc
    55  }
    56  
    57  func (c *ClassController) Run(stop <-chan struct{}) {
    58  	// Ensure we initially reconcile the current state
    59  	c.queue.Add(types.NamespacedName{})
    60  	c.queue.Run(stop)
    61  }
    62  
    63  func (c *ClassController) Reconcile(types.NamespacedName) error {
    64  	err := istiomultierror.New()
    65  	for class := range builtinClasses {
    66  		err = multierror.Append(err, c.reconcileClass(class))
    67  	}
    68  	return err.ErrorOrNil()
    69  }
    70  
    71  func (c *ClassController) reconcileClass(class gateway.ObjectName) error {
    72  	if c.classes.Get(string(class), "") != nil {
    73  		log.Debugf("GatewayClass/%v already exists, no action", class)
    74  		return nil
    75  	}
    76  	controller := builtinClasses[class]
    77  	classInfo, f := classInfos[controller]
    78  	if !f {
    79  		// Should only happen when ambient is disabled; otherwise builtinClasses and classInfos should be consistent
    80  		return nil
    81  	}
    82  	gc := &gateway.GatewayClass{
    83  		ObjectMeta: metav1.ObjectMeta{
    84  			Name: string(class),
    85  		},
    86  		Spec: gateway.GatewayClassSpec{
    87  			ControllerName: gateway.GatewayController(classInfo.controller),
    88  			Description:    &classInfo.description,
    89  		},
    90  	}
    91  	_, err := c.classes.Create(gc)
    92  	if err != nil && !kerrors.IsConflict(err) {
    93  		return err
    94  	} else if err != nil && kerrors.IsConflict(err) {
    95  		// This is not really an error, just a race condition
    96  		log.Infof("Attempted to create GatewayClass/%v, but it was already created", class)
    97  	}
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func GetClassStatus(existing *k8sv1.GatewayClassStatus, gen int64) k8sv1.GatewayClassStatus {
   106  	if existing == nil {
   107  		existing = &k8sv1.GatewayClassStatus{}
   108  	}
   109  	existing.Conditions = kstatus.UpdateConditionIfChanged(existing.Conditions, metav1.Condition{
   110  		Type:               string(k8sv1.GatewayClassConditionStatusAccepted),
   111  		Status:             kstatus.StatusTrue,
   112  		ObservedGeneration: gen,
   113  		LastTransitionTime: metav1.Now(),
   114  		Reason:             string(k8sv1.GatewayClassConditionStatusAccepted),
   115  		Message:            "Handled by Istio controller",
   116  	})
   117  	return *existing
   118  }