Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCP NFS Validations #58

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions components/kcp/api/cloud-control/v1beta1/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const (

ReasonScopeNotFound = "ScopeNoFound"

ReasonUnknown = "Unknown"
ReasonReady = "Ready"
ReasonGcpError = "GCPError"
ReasonNotSupported = "NotSupported"
ReasonUnknown = "Unknown"
ReasonReady = "Ready"
ReasonGcpError = "GCPError"
ReasonNotSupported = "NotSupported"
ReasonValidationFailed = "ValidationFailed"
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type NfsOptionsGcp struct {
type GcpFileTier string

const (
STANDARD = GcpFileTier("STANDARD")
PREMIUM = GcpFileTier("PREMIUM")
BASIC_HDD = GcpFileTier("BASIC_HDD")
BASIC_SSD = GcpFileTier("BASIC_SSD")
HIGH_SCALE_SSD = GcpFileTier("HIGH_SCALE_SSD")
Expand Down
3 changes: 3 additions & 0 deletions components/kcp/pkg/provider/gcp/client/gcpConstants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client

import (
"fmt"
"regexp"
"time"

"github.com/kyma-project/cloud-manager/components/kcp/api/cloud-control/v1beta1"
Expand All @@ -18,6 +19,8 @@ const filestoreParentPattern = "projects/%s/locations/%s"
const GcpRetryWaitTime = time.Second * 3
const GcpOperationWaitTime = time.Second * 5

var FilestoreInstanceRegEx *regexp.Regexp = regexp.MustCompile(`^projects\/([^/]+)\/locations\/([^/]+)\/instances\/([^/]+)$`)

func GetVPCPath(projectId, vpcId string) string {
return fmt.Sprintf(vPCPathPattern, projectId, vpcId)
}
Expand Down
2 changes: 2 additions & 0 deletions components/kcp/pkg/provider/gcp/nfsinstance/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ func New(stateFactory StateFactory) composed.Action {
}
return composed.ComposeActions(
"gcsNfsInstance",
validateAlways,
actions.AddFinalizer,
checkGcpOperation,
loadNfsInstance,
validatePostCreate,
checkNUpdateState,
syncNfsInstance,
composed.BuildBranchingAction("RunFinalizer", StatePredicate(client.Deleted, ctx, state),
Expand Down
14 changes: 10 additions & 4 deletions components/kcp/pkg/provider/gcp/nfsinstance/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type State struct {
curState v1beta1.StatusState
operation gcpclient.OperationType
updateMask []string
validations []string
fsInstance *file.Instance
filestoreClient client.FilestoreClient
}
Expand Down Expand Up @@ -83,6 +84,14 @@ func (s State) toInstance() *file.Instance {
project := gcpScope.Project
vpc := gcpScope.VpcNetwork

nwConfig := &file.NetworkConfig{
Network: gcpclient.GetVPCPath(project, vpc),
ConnectMode: string(gcpOptions.ConnectMode),
}
if s.IpRange() != nil {
nwConfig.ReservedIpRange = s.IpRange().Spec.Cidr
}

return &file.Instance{
Description: nfsInstance.Name,
Tier: string(gcpOptions.Tier),
Expand All @@ -94,10 +103,7 @@ func (s State) toInstance() *file.Instance {
},
},
Networks: []*file.NetworkConfig{
{
Network: gcpclient.GetVPCPath(project, vpc),
ConnectMode: string(gcpOptions.ConnectMode),
},
nwConfig,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func syncNfsInstance(ctx context.Context, st composed.State) (error, context.Con
}

if operation != nil {
if state.operation == client.ADD {
nfsInstance.Status.Id = client.GetFilestoreInstancePath(project, location, name)
}
nfsInstance.Status.OpIdentifier = operation.Name
state.UpdateObjStatus(ctx)
}
Expand Down
48 changes: 48 additions & 0 deletions components/kcp/pkg/provider/gcp/nfsinstance/validateAlways.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nfsinstance

import (
"context"
"errors"
"strings"

"github.com/kyma-project/cloud-manager/components/kcp/api/cloud-control/v1beta1"
"github.com/kyma-project/cloud-manager/components/lib/composed"
)

func validateAlways(ctx context.Context, st composed.State) (error, context.Context) {
state := st.(*State)
logger := composed.LoggerFromCtx(ctx)

//If the instance already exists, continue.
if state.fsInstance != nil {
return nil, nil
}

nfsInstance := state.ObjAsNfsInstance()
logger.WithValues("NfsInstance :", nfsInstance.Name).Info("Validating Instance Details")

//Get GCP details.
gcpOptions := nfsInstance.Spec.Instance.Gcp

//Validate whether the requested capacity is a valid value.
if _, err := IsValidCapacity(gcpOptions.Tier, gcpOptions.CapacityGb); err != nil {
state.validations = append(state.validations, err.Error())
}

//Validate whether the nwMask is a valid value.
cidr := ""
if state.IpRange() != nil {
cidr = state.IpRange().Spec.Cidr
}
if _, err := IsValidNwMask(gcpOptions.Tier, cidr); err != nil {
state.validations = append(state.validations, err.Error())
}

if len(state.validations) > 0 {
err := errors.New(strings.Join(state.validations, "\n"))
state.AddErrorCondition(ctx, v1beta1.ReasonValidationFailed, err)
return composed.LogErrorAndReturn(err, "Validation Failed", composed.StopAndForget, nil)
}

return nil, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nfsinstance

import (
"context"
"errors"
"strings"

"github.com/kyma-project/cloud-manager/components/kcp/api/cloud-control/v1beta1"
"github.com/kyma-project/cloud-manager/components/kcp/pkg/provider/gcp/client"
"github.com/kyma-project/cloud-manager/components/lib/composed"
)

func validatePostCreate(ctx context.Context, st composed.State) (error, context.Context) {
state := st.(*State)
logger := composed.LoggerFromCtx(ctx)

nfsInstance := state.ObjAsNfsInstance()
logger.WithValues("NfsInstance :", nfsInstance.Name).Info("Validating Instance Details")

//Get GCP details.
gcpOptions := nfsInstance.Spec.Instance.Gcp
name := nfsInstance.Spec.RemoteRef.Name

id := nfsInstance.Status.Id
if id != "" {
matches := client.FilestoreInstanceRegEx.FindStringSubmatch(id)
l := len(matches)

//Validate the location of the instance is not modified
if l > 2 && matches[2] != gcpOptions.Location {
state.validations = append(state.validations, "Location cannot be modified")
}

//Validate if the name of the instance is not modified.
if l > 3 && matches[3] != name {
state.validations = append(state.validations, "Name cannot be modified")
}
}

//Validate the Tier is not modified.
if state.fsInstance != nil && v1beta1.GcpFileTier(state.fsInstance.Tier) != gcpOptions.Tier {
state.validations = append(state.validations, "Tier cannot be modified")
}

//Validate the instance is not being scale down.
if !CanScaleDown(gcpOptions.Tier) && state.fsInstance != nil &&
state.fsInstance.FileShares[0].CapacityGb > int64(gcpOptions.CapacityGb) {
state.validations = append(state.validations, "Capacity cannot be reduced.")
}

//Add error condition
if len(state.validations) > 0 {
err := errors.New(strings.Join(state.validations, "\n"))
state.AddErrorCondition(ctx, v1beta1.ReasonValidationFailed, err)
return composed.LogErrorAndReturn(err, "Validation Failed", composed.StopAndForget, nil)
}

return nil, nil
}
81 changes: 81 additions & 0 deletions components/kcp/pkg/provider/gcp/nfsinstance/validations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package nfsinstance

import (
"errors"
"fmt"
"strings"

"github.com/kyma-project/cloud-manager/components/kcp/api/cloud-control/v1beta1"
)

type GcpFileTierValidation interface {
IsValidCapacity(capacityGb int) (bool, error)
CanScaleDown() bool
IsValidNwMask(cidr string) (bool, error)
}

func IsValidCapacity(tier v1beta1.GcpFileTier, capacityGb int) (bool, error) {
switch tier {
case v1beta1.BASIC_HDD, v1beta1.STANDARD:
if capacityGb < 1024 {
return false, errors.New("Capacity should be > 1 TB")
} else if capacityGb > 65433 {
return false, errors.New("Capacity should be < 63.9 TB")
}
case v1beta1.BASIC_SSD, v1beta1.PREMIUM:
if capacityGb < 2560 {
return false, errors.New("Capacity should be > 2.5 TB")
} else if capacityGb > 65433 {
return false, errors.New("Capacity should be < 63.9 TB")
}
case v1beta1.ZONAL, v1beta1.ENTERPRISE, v1beta1.REGIONAL:
if capacityGb < 1024 {
return false, errors.New("Capacity should be > 1 TB")
} else if capacityGb > 10240 {
return false, errors.New("Capacity should be < 10 TB")
} else if capacityGb%256 != 0 {
return false, errors.New("Capacity should be in increments of 256 GBs")
}
case v1beta1.HIGH_SCALE_SSD:
if capacityGb < 10240 {
return false, errors.New("Capacity should be > 10 TB")
} else if capacityGb > 102400 {
return false, errors.New("Capacity should be < 100 TB")
} else if capacityGb%2560 != 0 {
return false, errors.New("Capacity should be in increments of 2560 GBs")
}
default:
return false, errors.New("Unknown Tier")
}
return true, nil
}

func CanScaleDown(tier v1beta1.GcpFileTier) bool {
return tier == v1beta1.ZONAL || tier == v1beta1.HIGH_SCALE_SSD ||
tier == v1beta1.ENTERPRISE || tier == v1beta1.REGIONAL
}

func IsValidNwMask(tier v1beta1.GcpFileTier, cidr string) (bool, error) {
if cidr == "" {
return true, nil
}
switch tier {
case v1beta1.BASIC_HDD, v1beta1.STANDARD, v1beta1.BASIC_SSD, v1beta1.PREMIUM, v1beta1.ZONAL:
return checkCidrSuffix(tier, cidr, "/29")
case v1beta1.ENTERPRISE, v1beta1.REGIONAL:
return checkCidrSuffix(tier, cidr, "/26")
case v1beta1.HIGH_SCALE_SSD:
return checkCidrSuffix(tier, cidr, "/24")
default:
return false, errors.New("Unknown Tier")
}
}

func checkCidrSuffix(tier v1beta1.GcpFileTier, cidr, suffix string) (bool, error) {
valid := strings.HasSuffix(cidr, suffix)
if valid {
return valid, nil
} else {
return valid, errors.New(fmt.Sprintf("CIDR block should be %s for Tier: %s", suffix, string(tier)))
}
}
Loading