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

⚠️ Make ip-address-manager an IPAM provider for CAPI #692

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ARG BUILD_IMAGE=docker.io/golang:1.22.8@sha256:b274ff14d8eb9309b61b1a45333bf0559
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f

# Build the manager binary on golang image
FROM $BUILD_IMAGE as builder
FROM $BUILD_IMAGE AS builder
WORKDIR /workspace

# Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ $(RELEASE_NOTES_DIR):
.PHONY: release-manifests
release-manifests: $(KUSTOMIZE) $(RELEASE_DIR) ## Builds the manifests to publish with a release
$(KUSTOMIZE) build config/default > $(RELEASE_DIR)/ipam-components.yaml
cp metadata.yaml $(RELEASE_DIR)/metadata.yaml

.PHONY: release-notes
release-notes: $(RELEASE_NOTES_DIR) $(RELEASE_NOTES)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Deploys IPAM CRDs and deploys IPAM controllers
Runs IPAM controller locally

```sh
kubectl scale -n capm3-system \
kubectl scale -n metal3-ipam-system \
deployment.v1.apps/metal3-ipam-controller-manager --replicas 0
make run
```
Expand Down
7 changes: 3 additions & 4 deletions config/default/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# Adds namespace to all resources. Keep it in capm3-system, as it is a
# dependency for CAPM3
namespace: capm3-system
# Adds namespace to all resources.
namespace: metal3-ipam-system

namePrefix: ipam-

labels:
- includeSelectors: true
pairs:
cluster.x-k8s.io/provider: infrastructure-metal3
cluster.x-k8s.io/provider: ipam-metal3

resources:
- ../rbac
Expand Down
7 changes: 7 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: controller-manager
name: system
---
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down
40 changes: 40 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,46 @@ rules:
- clusters/status
verbs:
- get
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
- ipaddressclaims
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
- ipaddressclaims/status
verbs:
- get
- patch
- update
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
- ipaddresses
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
- ipaddresses/status
verbs:
- get
- patch
- update
- apiGroups:
- ipam.metal3.io
resources:
Expand Down
39 changes: 37 additions & 2 deletions controllers/ippool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
capipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
"sigs.k8s.io/cluster-api/util/annotations"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/cluster-api/util/predicates"
Expand Down Expand Up @@ -55,6 +56,10 @@ type IPPoolReconciler struct {
// +kubebuilder:rbac:groups=ipam.metal3.io,resources=ipclaims/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ipam.metal3.io,resources=ipaddresses,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ipam.metal3.io,resources=ipaddresses/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=ipaddressclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=ipaddressclaims/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=ipaddresses,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=ipaddresses/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters/status,verbs=get
// +kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;update;patch
Expand Down Expand Up @@ -97,7 +102,7 @@ func (r *IPPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
ipamv1IPPool.ObjectMeta.Labels = make(map[string]string)
}
ipamv1IPPool.ObjectMeta.Labels[clusterv1.ClusterNameLabel] = *ipamv1IPPool.Spec.ClusterName
ipamv1IPPool.ObjectMeta.Labels[clusterv1.ProviderNameLabel] = "infrastructure-metal3"
ipamv1IPPool.ObjectMeta.Labels[clusterv1.ProviderNameLabel] = "ipam-metal3"

// Fetch the Cluster. Ignore an error if the deletion timestamp is set
err = r.Client.Get(ctx, key, cluster)
Expand Down Expand Up @@ -171,7 +176,7 @@ func (r *IPPoolReconciler) reconcileDelete(ctx context.Context,
}

// SetupWithManager will add watches for this controller.
func (r *IPPoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
func (r *IPPoolReconciler) SetupWithManagerForIPClaim(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
return ctrl.NewControllerManagedBy(mgr).
For(&ipamv1.IPPool{}).
WithOptions(options).
Expand All @@ -183,6 +188,19 @@ func (r *IPPoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manage
Complete(r)
}

// SetupWithManager will add watches for this controller.
func (r *IPPoolReconciler) SetupWithManagerForIPAddressClaim(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
return ctrl.NewControllerManagedBy(mgr).
For(&ipamv1.IPPool{}).
WithOptions(options).
Watches(
&capipamv1.IPAddressClaim{},
handler.EnqueueRequestsFromMapFunc(r.IPAddressClaimToIPPool),
).
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
Complete(r)
}

// IPClaimToIPPool will return a reconcile request for a
// Metal3DataTemplate if the event is for a
// IPClaim and that IPClaim references a Metal3DataTemplate.
Expand All @@ -206,6 +224,23 @@ func (r *IPPoolReconciler) IPClaimToIPPool(_ context.Context, obj client.Object)
return []ctrl.Request{}
}

func (r *IPPoolReconciler) IPAddressClaimToIPPool(_ context.Context, obj client.Object) []ctrl.Request {
if ipac, ok := obj.(*capipamv1.IPAddressClaim); ok {
if ipac.Spec.PoolRef.Name != "" {
namespace := ipac.Namespace
return []ctrl.Request{
{
NamespacedName: types.NamespacedName{
Name: ipac.Spec.PoolRef.Name,
Namespace: namespace,
},
},
}
}
}
return []ctrl.Request{}
}

func checkRequeueError(err error, errMessage string) (ctrl.Result, error) {
if err == nil {
return ctrl.Result{}, nil
Expand Down
60 changes: 60 additions & 0 deletions controllers/ippool_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
capipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand Down Expand Up @@ -407,4 +408,63 @@ var _ = Describe("IPPool controller", func() {
},
),
)

type TestCaseK8SIPACToM3IPP struct {
IPAddressClaim *capipamv1.IPAddressClaim
ExpectRequest bool
}

DescribeTable("IPAddressClaim To IPPool tests",
func(tc TestCaseK8SIPACToM3IPP) {
r := IPPoolReconciler{}
obj := client.Object(tc.IPAddressClaim)
reqs := r.IPAddressClaimToIPPool(context.Background(), obj)

if tc.ExpectRequest {
Expect(len(reqs)).To(Equal(1), "Expected 1 request, found %d", len(reqs))

req := reqs[0]
Expect(req.NamespacedName.Name).To(Equal(tc.IPAddressClaim.Spec.PoolRef.Name),
"Expected name %s, found %s", tc.IPAddressClaim.Spec.PoolRef.Name, req.NamespacedName.Name)
} else {
Expect(len(reqs)).To(Equal(0), "Expected 0 request, found %d", len(reqs))

}
},
Entry("No IPPool in Spec",
TestCaseK8SIPACToM3IPP{
IPAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: testObjectMeta,
Spec: capipamv1.IPAddressClaimSpec{},
},
ExpectRequest: false,
},
),
Entry("IPPool in Spec, with namespace",
TestCaseK8SIPACToM3IPP{
IPAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: testObjectMeta,
Spec: capipamv1.IPAddressClaimSpec{
PoolRef: corev1.TypedLocalObjectReference{
Name: "abc",
},
},
},
ExpectRequest: true,
},
),
Entry("IPPool in Spec, no namespace",
TestCaseK8SIPACToM3IPP{
IPAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: testObjectMeta,
Spec: capipamv1.IPAddressClaimSpec{
PoolRef: corev1.TypedLocalObjectReference{
Name: "abc",
},
},
},
ExpectRequest: true,
},
),
)
})
9 changes: 9 additions & 0 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
ipamv1 "github.com/metal3-io/ip-address-manager/api/v1alpha1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
capipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
Expand All @@ -57,6 +58,7 @@ func init() {
_ = apiextensionsv1.AddToScheme(scheme.Scheme)
_ = clusterv1.AddToScheme(scheme.Scheme)
_ = ipamv1.AddToScheme(scheme.Scheme)
_ = capipamv1.AddToScheme(scheme.Scheme)
}

func setupScheme() *runtime.Scheme {
Expand All @@ -70,6 +72,10 @@ func setupScheme() *runtime.Scheme {
panic(err)
}

if err := capipamv1.AddToScheme(s); err != nil {
panic(err)
}

return s
}
func TestAPIs(t *testing.T) {
Expand All @@ -94,6 +100,9 @@ var _ = BeforeSuite(func() {
err = ipamv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

err = capipamv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

err = apiextensionsv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

Expand Down
57 changes: 54 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ The *spec* field contains the following :
* **pools**: this is a list of IP address pools
* **prefix**: This is a default prefix for this IPPool
* **gateway**: This is a default gateway for this IPPool
* **preAllocations**: This is a default preallocated IP address for this IPPool
* **preAllocations**: This is a default preallocated IP address for this IPPool.
Preallocations asossiate a claim's name to an IP address. It doesn't matter if
the claim type is (metal3)IPClaim or (capi)IPAddressClaim.

The *prefix* and *gateway* can be overridden per pool. The pool definition is
as follows :
Expand All @@ -51,12 +53,13 @@ as follows :
It is used to verify that the allocated address belongs to this subnet.
* **prefix**: override of the default prefix for this pool
* **gateway**: override of the default gateway for this pool
* **DNSServers**: override of the default dns servers for this pool

## IPClaim

An IPClaim is an object representing a request for an IP address allocation.

Example pool:
Example IPClaim:

```yaml
apiVersion: ipam.metal3.io/v1alpha1
Expand All @@ -78,7 +81,7 @@ The *spec* field contains the following :

An IPAddress is an object representing an IP address allocation.

Example pool:
Example IPAddress:

```yaml
apiVersion: ipam.metal3.io/v1alpha1
Expand All @@ -105,8 +108,56 @@ The *spec* field contains the following :
* **address**: the allocated IP address
* **prefix**: the prefix for this address
* **gateway**: the gateway for this address
* **DNSServers**: a list of dns servers

## Metal3 dev env examples

You can find CR examples in the
[Metal3-io dev env project](https://github.com/metal3-io/metal3-dev-env)

## Handling CAPI CRs

This IPAM can be deployed and used as an
[IPAM provider](https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/book/src/reference/glossary.md#ipam-provider)for
[CAPI](https://github.com/kubernetes-sigs/cluster-api).

IPPool reconsiles (metal3)ipclaims into (metal3)ipaddresses
and (capi)ipaddressclaims into (capi)ipaddresses.

### IPAddressClaim

Check out more on [IPAddressClaim docs](https://docs.openshift.com/container-platform/4.16/rest_api/network_apis/ipaddressclaim-ipam-cluster-x-k8s-io-v1beta1.html).

### IpAddress

Check out more on [IPAddress docs](https://docs.openshift.com/container-platform/4.16/rest_api/network_apis/ipaddress-ipam-cluster-x-k8s-io-v1beta1.html).

### Set up via clusterctl

Since it's not added to the built-in list of providers yet,
you'll need to add the following to your
```$XDG_CONFIG_HOME/cluster-api/clusterctl.yaml```
if you want to install it using ```clusterctl init --ipam metal3```:

```yaml
providers:
- name: metal3
url: https://github.com/metal3-io/ip-address-manager/releases/latest/ipam-components.yaml
type: IPAMProvider
```

If you are also specifying infrastructure provider
metal3 liko so:
```clusterctl init --infrastructure metal3 --ipam metal3```.
It might cause a problem to have the same name
for both providers if you are creating local
[overrides layers](https://cluster-api.sigs.k8s.io/clusterctl/configuration#overrides-layer).
Solution is to change the ipam providers name:
```clusterctl init --infrastructure metal3 --ipam m3ipam``

```yaml
providers:
- name: m3ipam
url: https://github.com/metal3-io/ip-address-manager/releases/latest/ipam-components.yaml
type: IPAMProvider
```
2 changes: 1 addition & 1 deletion examples/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ OUTPUT_DIR=${OUTPUT_DIR:-${SOURCE_DIR}/_out}

# Cluster.
export CLUSTER_NAME="${CLUSTER_NAME:-test1}"
export NAMESPACE="${NAMESPACE:-capm3-system}"
export NAMESPACE="${NAMESPACE:-metal3-ipam-system}"

# Outputs.
COMPONENTS_CERT_MANAGER_GENERATED_FILE=${OUTPUT_DIR}/cert-manager.yaml
Expand Down
Loading