Skip to content

Commit

Permalink
Merge pull request #258 from seanpang-vmware/vpc_crd_1
Browse files Browse the repository at this point in the history
Add VPC service and controller
  • Loading branch information
seanpang-vmware committed Aug 10, 2023
2 parents 37414a5 + 904b2e7 commit 4156402
Show file tree
Hide file tree
Showing 20 changed files with 1,272 additions and 31 deletions.
6 changes: 6 additions & 0 deletions build/yaml/samples/nsx_v1alpha1_vpcs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: nsx.vmware.com/v1alpha1
kind: VPC
metadata:
name: vpc-default
spec:

38 changes: 38 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@ import (
"github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1"
"github.com/vmware-tanzu/nsx-operator/pkg/config"
commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common"
namespacecontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/namespace"
nsxserviceaccountcontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/nsxserviceaccount"
securitypolicycontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/securitypolicy"
staticroutecontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/staticroute"
"github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnet"
"github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnetport"
"github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnetset"
vpccontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/vpc"
"github.com/vmware-tanzu/nsx-operator/pkg/logger"
"github.com/vmware-tanzu/nsx-operator/pkg/metrics"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/nsxserviceaccount"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc"
)

var (
Expand Down Expand Up @@ -102,6 +105,37 @@ func StartNSXServiceAccountController(mgr ctrl.Manager, commonService common.Ser
}
}

func StartVPCController(mgr ctrl.Manager, commonService common.Service) {
vpcReconciler := &vpccontroller.VPCReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if vpcService, err := vpc.InitializeVPC(commonService); err != nil {
log.Error(err, "failed to initialize vpc commonService", "controller", "VPC")
os.Exit(1)
} else {
vpcReconciler.Service = vpcService
commonctl.ServiceMediator.VPCService = vpcService
}
if err := vpcReconciler.Start(mgr); err != nil {
log.Error(err, "failed to create vpc controller", "controller", "VPC")
os.Exit(1)
}
}

func StartNamespaceController(mgr ctrl.Manager, commonService common.Service) {
nsReconciler := &namespacecontroller.NamespaceReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
NSXConfig: commonService.NSXConfig,
}

if err := nsReconciler.Start(mgr); err != nil {
log.Error(err, "failed to create namespace controller", "controller", "Namespace")
os.Exit(1)
}
}

func main() {
log.Info("starting NSX Operator")

Expand Down Expand Up @@ -145,9 +179,13 @@ func main() {

staticroutecontroller.StartStaticRouteController(mgr, commonService)
subnetport.StartSubnetPortController(mgr, commonService)

StartNamespaceController(mgr, commonService)
StartVPCController(mgr, commonService)
}
// Start the security policy controller, it supports VPC and non VPC mode
StartSecurityPolicyController(mgr, commonService)

// Start the NSXServiceAccount controller.
if cf.EnableAntreaNSXInterworking {
StartNSXServiceAccountController(mgr, commonService)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ require (
github.com/stretchr/objx v0.4.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.4.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
9 changes: 7 additions & 2 deletions pkg/controllers/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ const (
MetricResTypeStaticRoute = "staticroute"
MetricResTypeSubnet = "subnet"
MetricResTypeSubnetSet = "subnetset"
MetricResTypeVPC = "vpc"
MetricResTypeNamespace = "namespace"
)

var (
ResultNormal = ctrl.Result{}
ResultRequeue = ctrl.Result{Requeue: true}
ResultNormal = ctrl.Result{}
ResultRequeue = ctrl.Result{Requeue: true}
// for k8s events that need to retry in short loop, eg: namespace creation
ResultRequeueAfter10sec = ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}
// for unstable event, eg: failed to k8s resources when reconciling, may due to k8s unstable
ResultRequeueAfter5mins = ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Minute}

ServiceMediator = mediator.ServiceMediator{}
Expand Down
19 changes: 19 additions & 0 deletions pkg/controllers/namespace/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package namespace

import (
"github.com/google/uuid"
"github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1"
)

func BuildVPCCR(ns string, ncName string, vpcName *string) *v1alpha1.VPC {
log.V(2).Info("building vpc", "ns", ns, "nc", ncName, "VPC", vpcName)
vpc := &v1alpha1.VPC{}
if vpcName == nil {
vpc.Name = "vpc-" + uuid.New().String()
} else {
vpc.Name = *vpcName
}

vpc.Namespace = ns
return vpc
}
197 changes: 197 additions & 0 deletions pkg/controllers/namespace/namespace_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/* Copyright © 2023 VMware, Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0 */

package namespace

import (
"context"
"errors"
"fmt"
"runtime"
"strings"

v1 "k8s.io/api/core/v1"
apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"

"github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1"
"github.com/vmware-tanzu/nsx-operator/pkg/config"
"github.com/vmware-tanzu/nsx-operator/pkg/controllers/common"
"github.com/vmware-tanzu/nsx-operator/pkg/logger"
"github.com/vmware-tanzu/nsx-operator/pkg/metrics"
_ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter"
types "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common"
"github.com/vmware-tanzu/nsx-operator/pkg/util"
)

var (
AnnotationNamespaceVPCError = " nsx.vmware.com/vpc_error"

log = logger.Log
)

// NamespaceReconciler process namespace create/delete event
type NamespaceReconciler struct {
Client client.Client
Scheme *apimachineryruntime.Scheme
NSXConfig *config.NSXOperatorConfig
}

func (r *NamespaceReconciler) createVPCCR(ctx *context.Context, obj client.Object, ns string, ncName string, vpcName *string) (*v1alpha1.VPC, error) {
// check if vpc cr already exist under this namespace
vpcs := &v1alpha1.VPCList{}
r.Client.List(*ctx, vpcs, client.InNamespace(ns))
if len(vpcs.Items) > 0 {
// if there is already one vpc exist under this ns, return this vpc.
log.Info("vpc cr already exist, skip creating", "VPC", vpcs.Items[0].Name)
return &vpcs.Items[0], nil
}
nc, ncExist := common.ServiceMediator.GetVPCNetworkConfig(ncName)
if !ncExist {
message := fmt.Sprintf("missing network config %s for namespace %s", ncName, ns)
r.namespaceError(ctx, obj, message, nil)
return nil, errors.New(message)
}

//TODO: in next patch, remove this validation. If user did not provide private cidr
// use a hardcoded cidr to create a private ip block.
if !common.ServiceMediator.ValidateNetworkConfig(nc) {
// if netwrok config is not valid, no need to retry, skip processing
message := fmt.Sprintf("invalid network config %s for namespace %s, missing private cidr", ncName, ns)
r.namespaceError(ctx, obj, message, nil)
return nil, errors.New(message)
}

// create vpc cr with exisitng vpc network config
vpcCR := BuildVPCCR(ns, ncName, vpcName)
err := r.Client.Create(*ctx, vpcCR)
if err != nil {
message := "failed to create VPC CR"
r.namespaceError(ctx, obj, message, err)
// If create VPC CR failed, put ns create event back to queue.
return nil, err
}

changes := map[string]string{
AnnotationNamespaceVPCError: "",
}
util.UpdateK8sResourceAnnotation(r.Client, ctx, obj, changes)
log.Info("create vpc CR", "VPC", vpcCR.Name, "Namespace", vpcCR.Namespace)
return vpcCR, nil
}

func (r *NamespaceReconciler) namespaceError(ctx *context.Context, k8sObj client.Object, msg string, err error) {
logErr := util.If(err == nil, errors.New(msg), err).(error)
log.Error(logErr, msg)
changes := map[string]string{AnnotationNamespaceVPCError: msg}
util.UpdateK8sResourceAnnotation(r.Client, ctx, k8sObj, changes)
}

func (r *NamespaceReconciler) insertNamespaceNetworkconfigBinding(ns string, anno map[string]string) {
if anno == nil {
log.Info("empty annotation for namespace", "Namespace", ns)
return
}

ncName, ncExist := anno[types.AnnotationVPCNetworkConfig]
if !ncExist {
ncName = types.DefaultNetworkConfigName
}

log.V(2).Info("insert namespace and network config mapping", "Namespace", ns, "Networkconfig", ncName)
common.ServiceMediator.RegisterNamespaceNetworkconfigBinding(ns, ncName)
}

/*
VPC creation strategy:
We suppose namespace should have following annotations:
- "nsx.vmware.com/vpc_name": "<Namespace Name>/<Supervisor ID>"
If the ns contains this annotation, first check if the namespace in annotation is the same as
the one in ns event, if yes, create an infra VPC for it. if not, skip the whole ns event as the infra
VPC will be created its corresponding ns creation event.
- "nsx.vmware.com/vpc_network_config":"<Supervisor ID>"
If ns do not contains "nsx.vmware.com/vpc_name" annotation. Use this annotation to handle VPC creation.
VPC will locate the network config with the CR name, and create VPC using its config.
- If the ns do not have either of the annotation above, then we believe it is using default VPC, try to search
default VPC in network config CR store. The default VPC network config CR's name is "default".
*/
func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

obj := &v1.Namespace{}
log.Info("reconciling K8s namespace", "namespace", req.NamespacedName)
metrics.CounterInc(r.NSXConfig, metrics.ControllerSyncTotal, common.MetricResTypeNamespace)

if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil {
log.Error(err, "unable to fetch namespace", "req", req.NamespacedName)
return common.ResultRequeueAfter5mins, err
}

// processing create/update event
ns := obj.GetName()
if obj.ObjectMeta.DeletionTimestamp.IsZero() {
metrics.CounterInc(r.NSXConfig, metrics.ControllerUpdateTotal, common.MetricResTypeNamespace)
log.Info("start processing namespace create/update event", "namespace", ns)
ctx := context.Background()
annotations := obj.GetAnnotations()
r.insertNamespaceNetworkconfigBinding(ns, annotations)
// read anno "nsx.vmware.com/vpc_name", if ns contains this annotation, it means it will share
// infra VPC, if the ns in the annotation is the same as ns event, create infra VPC, if not,
// skip the event.
ncName, ncExist := annotations[types.AnnotationVPCNetworkConfig]
vpcName, nameExist := annotations[types.AnnotationVPCName]
var create_vpc_name *string
if nameExist {
log.Info("read ns annotation vpcName", "VPCNAME", vpcName)
res := strings.Split(vpcName, "/")
// The format should be namespace/vpc_name
if len(res) != 2 {
message := fmt.Sprintf("incorrect vpcName annotation %s for namespace %s", vpcName, ns)
r.namespaceError(&ctx, obj, message, nil)
// If illegal format, skip handling this event?
return common.ResultNormal, nil
}
log.Info("start to handle vpcName anno", "VPCNS", res[1], "NS", ns)

if ns != res[0] {
log.Info("name space is using shared vpc, with vpc name anno", "VPCNAME", vpcName, "Namespace", ns)
return common.ResultNormal, nil
}
create_vpc_name = &res[1]
log.Info("creating vpc using customer defined vpc name", "VPCName", res[1])
}

// If ns do not have network config name tag, then use default vpc network config name
if !ncExist {
log.Info("network config name not found on ns, using default network config", "Namespace", ns)
ncName = types.DefaultNetworkConfigName
}

if _, err := r.createVPCCR(&ctx, obj, ns, ncName, create_vpc_name); err != nil {
return common.ResultRequeueAfter10sec, nil
}
return common.ResultNormal, nil
} else {
log.Info("skip ns deletion event for ns", "Namespace", ns)
metrics.CounterInc(r.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeNamespace)
common.ServiceMediator.UnRegisterNamespaceNetworkconfigBinding(obj.GetNamespace())
return common.ResultNormal, nil
}
}

func (r *NamespaceReconciler) setupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1.Namespace{}).
WithOptions(
controller.Options{
MaxConcurrentReconciles: runtime.NumCPU(),
}).
Complete(r)
}

// Start setup manager and launch GC
func (r *NamespaceReconciler) Start(mgr ctrl.Manager) error {
return r.setupWithManager(mgr)
}
Loading

0 comments on commit 4156402

Please sign in to comment.