Skip to content

Commit

Permalink
Merge pull request #5 from ykulazhenkov/controller
Browse files Browse the repository at this point in the history
IPAM controller implementation
  • Loading branch information
ykulazhenkov committed May 4, 2023
2 parents 0add66f + adb8c16 commit 4c5bda4
Show file tree
Hide file tree
Showing 15 changed files with 1,317 additions and 10 deletions.
46 changes: 41 additions & 5 deletions cmd/ipam-controller/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import (

"github.com/go-logr/logr"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/term"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/healthz"

// register json format for logger
Expand All @@ -37,7 +41,10 @@ import (

"github.com/Mellanox/nvidia-k8s-ipam/cmd/ipam-controller/app/options"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/common"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/ipam-controller/allocator"
configmapctrl "github.com/Mellanox/nvidia-k8s-ipam/pkg/ipam-controller/controllers/configmap"
nodectrl "github.com/Mellanox/nvidia-k8s-ipam/pkg/ipam-controller/controllers/node"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/ipam-controller/selector"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/version"
)

Expand Down Expand Up @@ -85,7 +92,9 @@ func RunController(ctx context.Context, opts *options.Options) error {
logger := logr.FromContextOrDiscard(ctx)
ctrl.SetLogger(logger)

logger.Info("start IPAM controller", "version", version.GetVersionString())
logger.Info("start IPAM controller",
"version", version.GetVersionString(), "config", opts.ConfigMapName,
"configNamespace", opts.ConfigMapNamespace)

scheme := runtime.NewScheme()

Expand All @@ -101,7 +110,14 @@ func RunController(ctx context.Context, opts *options.Options) error {
}

mgr, err := ctrl.NewManager(conf, ctrl.Options{
Scheme: scheme,
Scheme: scheme,
NewCache: cache.BuilderWithOptions(cache.Options{
SelectorsByObject: cache.SelectorsByObject{&corev1.ConfigMap{}: cache.ObjectSelector{
Field: fields.AndSelectors(
fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", opts.ConfigMapName)),
fields.ParseSelectorOrDie(fmt.Sprintf("metadata.namespace=%s", opts.ConfigMapNamespace))),
}},
}),
MetricsBindAddress: opts.MetricsAddr,
Port: 9443,
HealthProbeBindAddress: opts.ProbeAddr,
Expand All @@ -115,11 +131,31 @@ func RunController(ctx context.Context, opts *options.Options) error {
return err
}

netAllocator := allocator.New()
nodeSelector := selector.New()
configEventCH := make(chan event.GenericEvent, 1)

if err = (&nodectrl.NodeReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Allocator: netAllocator,
Selector: nodeSelector,
ConfigEventCh: configEventCH,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
logger.Error(err, "unable to create controller", "controller", "Node")
return err
}

if err = (&configmapctrl.ConfigMapReconciler{
Allocator: netAllocator,
Selector: nodeSelector,
ConfigEventCh: configEventCH,
ConfigMapName: opts.ConfigMapName,
ConfigMapNamespace: opts.ConfigMapNamespace,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
logger.Error(err, "unable to create controller", "controller", "IPClaim")
logger.Error(err, "unable to create controller", "controller", "ConfigMap")
return err
}

Expand Down
8 changes: 8 additions & 0 deletions cmd/ipam-controller/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func New() *Options {
MetricsAddr: ":8080",
ProbeAddr: ":8081",
EnableLeaderElection: false,
ConfigMapName: "nvidia-k8s-ipam-config",
ConfigMapNamespace: "kube-system",
}
}

Expand All @@ -38,6 +40,8 @@ type Options struct {
MetricsAddr string
ProbeAddr string
EnableLeaderElection bool
ConfigMapName string
ConfigMapNamespace string
}

// AddNamedFlagSets register flags for common options in NamedFlagSets
Expand All @@ -57,6 +61,10 @@ func (o *Options) AddNamedFlagSets(sharedFS *cliflag.NamedFlagSets) {
controllerFS.BoolVar(&o.EnableLeaderElection, "leader-elect", o.EnableLeaderElection,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
controllerFS.StringVar(&o.ConfigMapName, "config-name",
o.ConfigMapName, "The name of the ConfigMap which holds controller configuration")
controllerFS.StringVar(&o.ConfigMapNamespace, "config-namespace",
o.ConfigMapNamespace, "The name of the namespace where ConfigMap with controller configuration exist")
}

// Validate registered options
Expand Down
2 changes: 1 addition & 1 deletion pkg/cni/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/Mellanox/nvidia-k8s-ipam/pkg/cni/pool"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/cni/types"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/pool"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/version"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cni/types/host-local.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ package types
import (
"path/filepath"

"github.com/Mellanox/nvidia-k8s-ipam/pkg/cni/pool"
"github.com/Mellanox/nvidia-k8s-ipam/pkg/pool"
)

// TODO: do we want to support Routes ? DNS entires from ResolvConf as host-local CNI ?
Expand Down
154 changes: 154 additions & 0 deletions pkg/ip/cidr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright 2015 CNI 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 ip

import (
"encoding/binary"
"math/big"
"net"
)

// NextIP returns IP incremented by 1, if IP is invalid, return nil
func NextIP(ip net.IP) net.IP {
normalizedIP := normalizeIP(ip)
if normalizedIP == nil {
return nil
}

i := ipToInt(normalizedIP)
return intToIP(i.Add(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
}

// NextIPWithOffset returns IP incremented by offset, if IP is invalid, return nil
func NextIPWithOffset(ip net.IP, offset int64) net.IP {
normalizedIP := normalizeIP(ip)
if normalizedIP == nil {
return nil
}

i := ipToInt(normalizedIP)
return intToIP(i.Add(i, big.NewInt(offset)), len(normalizedIP) == net.IPv6len)
}

// PrevIP returns IP decremented by 1, if IP is invalid, return nil
func PrevIP(ip net.IP) net.IP {
normalizedIP := normalizeIP(ip)
if normalizedIP == nil {
return nil
}

i := ipToInt(normalizedIP)
return intToIP(i.Sub(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
}

// Cmp compares two IPs, returning the usual ordering:
// a < b : -1
// a == b : 0
// a > b : 1
// incomparable : -2
func Cmp(a, b net.IP) int {
normalizedA := normalizeIP(a)
normalizedB := normalizeIP(b)

if len(normalizedA) == len(normalizedB) && len(normalizedA) != 0 {
return ipToInt(normalizedA).Cmp(ipToInt(normalizedB))
}

return -2
}

// Distance returns amount of IPs between a and b
// returns -1 if result is negative
// returns -2 if result is too large
func Distance(a, b net.IP) int64 {
normalizedA := normalizeIP(a)
normalizedB := normalizeIP(b)
count := big.NewInt(0).Sub(ipToInt(normalizedB), ipToInt(normalizedA))
if !count.IsInt64() {
return -2
}
if count.Cmp(big.NewInt(0)) < 0 {
return -1
}
return count.Int64()
}

func ipToInt(ip net.IP) *big.Int {
return big.NewInt(0).SetBytes(ip)
}

func intToIP(i *big.Int, isIPv6 bool) net.IP {
intBytes := i.Bytes()

if len(intBytes) == net.IPv4len || len(intBytes) == net.IPv6len {
return intBytes
}

if isIPv6 {
return append(make([]byte, net.IPv6len-len(intBytes)), intBytes...)
}

return append(make([]byte, net.IPv4len-len(intBytes)), intBytes...)
}

// normalizeIP will normalize IP by family,
// IPv4 : 4-byte form
// IPv6 : 16-byte form
// others : nil
func normalizeIP(ip net.IP) net.IP {
if ipTo4 := ip.To4(); ipTo4 != nil {
return ipTo4
}
return ip.To16()
}

// Network masks off the host portion of the IP, if IPNet is invalid,
// return nil
func Network(ipn *net.IPNet) *net.IPNet {
if ipn == nil {
return nil
}

maskedIP := ipn.IP.Mask(ipn.Mask)
if maskedIP == nil {
return nil
}

return &net.IPNet{
IP: maskedIP,
Mask: ipn.Mask,
}
}

// IsBroadcast returns true if provided IP is IPv4 Broadcast ip of the network
func IsBroadcast(ip net.IP, network *net.IPNet) bool {
if network == nil {
return false
}
if ip.To4() == nil {
// no broadcast IPs in ipv6
return false
}
if network.IP.To4() == nil {
return false
}
if !network.Contains(ip) {
return false
}
masked := make(net.IP, len(ip.To4()))
binary.BigEndian.PutUint32(masked,
binary.BigEndian.Uint32(network.IP.To4())|^binary.BigEndian.Uint32(net.IP(network.Mask).To4()))
return ip.Equal(masked)
}
Loading

0 comments on commit 4c5bda4

Please sign in to comment.