Skip to content

Commit

Permalink
direct controller for ComputeForwardingRule
Browse files Browse the repository at this point in the history
  • Loading branch information
gemmahou committed Aug 1, 2024
1 parent 4bd0962 commit 1c33b1d
Show file tree
Hide file tree
Showing 6 changed files with 638 additions and 1 deletion.
2 changes: 1 addition & 1 deletion apis/compute/v1beta1/computeforwardingrule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var (
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = SchemeBuilder.AddToScheme

ComputeForwardingRuleGVK = schema.GroupVersionKind{
GroupVersionKind = schema.GroupVersionKind{
Group: SchemeGroupVersion.Group,
Version: SchemeGroupVersion.Version,
Kind: "ComputeForwardingRule",
Expand Down
Binary file added dev/tools/controllerbuilder/main
Binary file not shown.
302 changes: 302 additions & 0 deletions pkg/controller/direct/compute/forwardingrule_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// Copyright 2024 Google LLC
//
// 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 compute

import (
"context"
"fmt"
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
"reflect"

gcp "cloud.google.com/go/compute/apiv1"
computepb "cloud.google.com/go/compute/apiv1/computepb"
krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/compute/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
"google.golang.org/api/option"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func init() {
registry.RegisterModel(krm.GroupVersionKind, NewForwardingRuleModel)
}

func NewForwardingRuleModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
return &forwardingRuleModel{config: config}, nil
}

type forwardingRuleModel struct {
config *config.ControllerConfig
}

// model implements the Model interface.
var _ directbase.Model = &forwardingRuleModel{}

type forwardingRuleAdapter struct {
resourceID string
projectID string
Location string
gcpClient *gcp.ForwardingRulesClient
desired *krm.ComputeForwardingRule
actual *computepb.ForwardingRule
}

var _ directbase.Adapter = &forwardingRuleAdapter{}

func (m *forwardingRuleModel) forwardingRulesClient(ctx context.Context) (*gcp.ForwardingRulesClient, error) {
var opts []option.ClientOption
if m.config.UserAgent != "" {
opts = append(opts, option.WithUserAgent(m.config.UserAgent))
}
if m.config.HTTPClient != nil {
opts = append(opts, option.WithHTTPClient(m.config.HTTPClient))
}
if m.config.UserProjectOverride && m.config.BillingProject != "" {
opts = append(opts, option.WithQuotaProject(m.config.BillingProject))
}

gcpClient, err := gcp.NewForwardingRulesRESTClient(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("building ComputeForwardingRule client: %w", err)
}
return gcpClient, err
}

func (m *forwardingRuleModel) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
obj := &krm.ComputeForwardingRule{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
return nil, fmt.Errorf("error converting to %T: %w", obj, err)
}

// Get ResourceID
resourceID, err := refs.GetResourceID(u)
if err != nil {
return nil, err
}

// Get projectID
projectID, err := refs.ResolveProjectID(ctx, reader, u)
if err != nil {
return nil, err
}

// Get network
if obj.Spec.NetworkRef != nil {
networkRef, err := refs.ResolveComputeNetwork(ctx, reader, obj, obj.Spec.NetworkRef)
if err != nil {
return nil, err

}
obj.Spec.NetworkRef.External = networkRef.String()
}

// Get target targetHTTPProxy
if obj.Spec.Target.TargetHTTPProxyRef != nil {
targetHTTPProxyRef, err := refs.ResolveTargetHTTPProxy(ctx, reader, obj, obj.Spec.Target.TargetHTTPProxyRef)
if err != nil {
return nil, err

}
obj.Spec.Target.TargetHTTPProxyRef.External = targetHTTPProxyRef.String()
}

// Get location
location := obj.Spec.Location

// Get compute GCP client
gcpClient, err := m.forwardingRulesClient(ctx)
if err != nil {
return nil, err
}
return &forwardingRuleAdapter{
resourceID: resourceID,
projectID: projectID,
Location: location,
gcpClient: gcpClient,
desired: obj,
}, nil
}

func (m *forwardingRuleModel) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
// TODO: Support URLs
return nil, nil
}

func (a *forwardingRuleAdapter) Find(ctx context.Context) (bool, error) {
log := klog.FromContext(ctx).WithName(ctrlName)
log.V(2).Info("getting ComputeForwardingRule", "name", a.fullyQualifiedName())

if a.resourceID == "" {
return false, nil
}

req := &computepb.GetForwardingRuleRequest{
ForwardingRule: a.fullyQualifiedName(),
Project: a.projectID,
}
forwardingRule, err := a.gcpClient.Get(ctx, req)
if err != nil {
if direct.IsNotFound(err) {
return false, nil
}
return false, fmt.Errorf("getting ComputeForwardingRule %q failed: %w", a.fullyQualifiedName(), err)
}

a.actual = forwardingRule
return true, nil
}

func (a *forwardingRuleAdapter) Create(ctx context.Context, u *unstructured.Unstructured) error {
log := klog.FromContext(ctx).WithName(ctrlName)
log.V(2).Info("creating ComputeForwardingRule", "name", a.fullyQualifiedName())
mapCtx := &direct.MapContext{}

projectID := a.projectID
if projectID == "" {
return fmt.Errorf("project is empty")
}
if a.resourceID == "" {
return fmt.Errorf("resourceID is empty")
}

desired := a.desired.DeepCopy()

forwardingRule := ComputeForwardingRuleSpec_ToProto(mapCtx, &desired.Spec)
if mapCtx.Err() != nil {
return mapCtx.Err()
}

req := &computepb.InsertForwardingRuleRequest{
ForwardingRuleResource: forwardingRule,
Region: a.Location,
Project: a.projectID,
}
op, err := a.gcpClient.Insert(ctx, req)
if err != nil {
return fmt.Errorf("creating ComputeForwardingRule %s failed: %w", a.fullyQualifiedName(), err)
}
log.V(2).Info("successfully created ComputeForwardingRule", "name", a.fullyQualifiedName())

status := &krm.ComputeForwardingRuleStatus{}
// TODO(yuhou): Do we need other status beside conditions and observedGeneration?
status.CreationTimestamp = op.Proto().CreationTimestamp
status.SelfLink = direct.LazyPtr(a.fullyQualifiedName())
return setStatus(u, status)
}

func (a *forwardingRuleAdapter) Update(ctx context.Context, u *unstructured.Unstructured) error {
log := klog.FromContext(ctx).WithName(ctrlName)
log.V(2).Info("updating ComputeForwardingRule", "name", a.fullyQualifiedName())
mapCtx := &direct.MapContext{}

desired := a.desired.DeepCopy()
forwardingRule := ComputeForwardingRuleSpec_ToProto(mapCtx, &desired.Spec)
if mapCtx.Err() != nil {
return mapCtx.Err()
}

status := &krm.ComputeForwardingRuleStatus{}
// Patch only support update on networkTier field, which KCC does not support yet.
// Use setTarget and setLabels to update target and labels field.
if !reflect.DeepEqual(forwardingRule.Target, a.actual.Target) {
setTargetReq := &computepb.SetTargetForwardingRuleRequest{
ForwardingRule: a.resourceID,
TargetReferenceResource: &computepb.TargetReference{Target: forwardingRule.Target},
Project: a.projectID,
Region: a.Location,
}
op, err := a.gcpClient.SetTarget(ctx, setTargetReq)
if err != nil {
return fmt.Errorf("updating ComputeForwardingRule target %s failed: %w", a.fullyQualifiedName(), err)
}
log.V(2).Info("successfully updated ComputeForwardingRule target", "name", a.fullyQualifiedName())

// TODO(yuhou): Do we need other status beside conditions and observedGeneration?
status.CreationTimestamp = op.Proto().CreationTimestamp
status.SelfLink = direct.LazyPtr(a.fullyQualifiedName())
}

if !reflect.DeepEqual(forwardingRule.Labels, a.actual.Labels) {
setLabelsReq := &computepb.SetLabelsForwardingRuleRequest{
Resource: a.resourceID,
RegionSetLabelsRequestResource: &computepb.RegionSetLabelsRequest{Labels: forwardingRule.Labels},
Project: a.projectID,
Region: a.Location,
}
op, err := a.gcpClient.SetLabels(ctx, setLabelsReq)
if err != nil {
return fmt.Errorf("updating ComputeForwardingRule labels %s failed: %w", a.fullyQualifiedName(), err)
}
log.V(2).Info("successfully updated ComputeForwardingRule labels", "name", a.fullyQualifiedName())

// TODO(yuhou): Do we need other status beside conditions and observedGeneration?
status.CreationTimestamp = op.Proto().CreationTimestamp
status.SelfLink = direct.LazyPtr(a.fullyQualifiedName())
}
return setStatus(u, status)
}

func (a *forwardingRuleAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
// TODO(kcc)
return nil, nil
}

// Delete implements the Adapter interface.
func (a *forwardingRuleAdapter) Delete(ctx context.Context) (bool, error) {
log := klog.FromContext(ctx).WithName(ctrlName)
log.V(2).Info("deleting ComputeForwardingRule", "name", a.fullyQualifiedName())

exist, err := a.Find(ctx)
if err != nil {
return false, err
}
if !exist {
// return (false, nil) if the object was not found but should be presumed deleted.
return false, nil
}

if a.resourceID == "" {
return false, nil
}
req := &computepb.DeleteForwardingRuleRequest{
ForwardingRule: a.fullyQualifiedName(),
Region: a.Location,
Project: a.projectID,
}
op, err := a.gcpClient.Delete(ctx, req)
if err != nil {
return false, fmt.Errorf("deleting ComputeForwardingRule %s failed: %w", a.fullyQualifiedName(), err)
}
err = op.Wait(ctx)
if err != nil {
return false, fmt.Errorf("waiting delete ComputeForwardingRule %s failed: %w", a.fullyQualifiedName(), err)
}
log.V(2).Info("successfully deleted ComputeForwardingRule", "name", a.fullyQualifiedName())
return true, nil
}

func (a *forwardingRuleAdapter) fullyQualifiedName() string {
return fmt.Sprintf("projects/%s/location/%s/forwardingRules/%s", a.projectID, a.Location, a.resourceID)
}

func (a *forwardingRuleAdapter) getParent() string {
// TODO(user): Write the GCP URI parent for your resource
return fmt.Sprintf("projects/%s", a.projectID)
}
Loading

0 comments on commit 1c33b1d

Please sign in to comment.