Skip to content

Commit

Permalink
Refactor plugin IPAM
Browse files Browse the repository at this point in the history
- split into different procedures
- improve logging
  • Loading branch information
damyan committed Mar 22, 2024
1 parent 31f3690 commit 476492c
Showing 1 changed file with 112 additions and 84 deletions.
196 changes: 112 additions & 84 deletions plugins/ipam/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type K8sClient struct {
Client client.Client
Namespace string
SubnetNames []string
Ctx context.Context
EventRecorder record.EventRecorder
}

Expand Down Expand Up @@ -66,14 +67,12 @@ func NewK8sClient(namespace string, subnetNames []string) K8sClient {
Client: cl,
Namespace: namespace,
SubnetNames: subnetNames,
Ctx: context.Background(),
EventRecorder: recorder,
}
}

func (k K8sClient) createIpamIP(ipaddr net.IP, mac net.HardwareAddr) error {
ctx := context.Background()
macKey := strings.ReplaceAll(mac.String(), ":", "")

ip, err := ipamv1alpha1.IPAddrFromString(ipaddr.String())
if err != nil {
err = errors.Wrapf(err, "Failed to parse IP %s", ip)
Expand All @@ -83,97 +82,24 @@ func (k K8sClient) createIpamIP(ipaddr net.IP, mac net.HardwareAddr) error {
// select the subnet matching the CIDR of the request
subnetMatch := false
for _, subnetName := range k.SubnetNames {
subnet := &ipamv1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{
Name: subnetName,
Namespace: k.Namespace,
},
}
existingSubnet := subnet.DeepCopy()
err = k.Client.Get(ctx, client.ObjectKeyFromObject(subnet), existingSubnet)
if err != nil && !apierrors.IsNotFound(err) {
err = errors.Wrapf(err, "Failed to get subnet %s/%s", subnet.Namespace, subnetName)
return err
}
if apierrors.IsNotFound(err) {
log.Debugf("Cannot select subnet %s/%s, does not exist", subnet.Namespace, subnetName)
continue
}
if !checkIPv6InCIDR(ipaddr, existingSubnet.Status.Reserved.String()) {
log.Debugf("Cannot select subnet %s/%s, CIDR mismatch", subnet.Namespace, subnet.Name)
subnet := k.getMatchingSubnet(subnetName, ipaddr)
if subnet == nil {
continue
}
log.Debugf("Selecting subnet %s", subnetName)
subnetMatch = true

// a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and
// must start and end with an alphanumeric character.
// 2001:abcd:abcd::1 will become 2001-abcd-abcd-0000-0000-0000-0000-00001
longIpv6 := getLongIPv6(ipaddr)
name := longIpv6 + "-" + origin
ipamIP := &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: k.Namespace,
Labels: map[string]string{
"ip": longIpv6,
"mac": macKey,
"origin": origin,
},
},
Spec: ipamv1alpha1.IPSpec{
IP: ip,
Subnet: corev1.LocalObjectReference{
Name: subnetName,
},
},
}

existingIpamIP := ipamIP.DeepCopy()
err = k.Client.Get(ctx, client.ObjectKeyFromObject(ipamIP), existingIpamIP)
if err != nil && !apierrors.IsNotFound(err) {
err = errors.Wrapf(err, "Failed to get IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)
var ipamIP *ipamv1alpha1.IP
ipamIP, err = k.prepareCreateIpamIP(subnetName, ipaddr, mac)
if err != nil {
return err
}

createIpamIP := false
// create IPAM IP if not exists or delete existing if ip differs
if apierrors.IsNotFound(err) {
createIpamIP = true
} else {
if !reflect.DeepEqual(ipamIP.Spec, existingIpamIP.Spec) {
log.Debugf("\nOld IP: %v,\nnew IP: %v", prettyFormat(existingIpamIP.Spec), prettyFormat(ipamIP.Spec))
log.Infof("Delete old IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)

// delete old IP object
err = k.Client.Delete(ctx, existingIpamIP)
if err != nil {
err = errors.Wrapf(err, "Failed to delete IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)
return err
}

k.EventRecorder.Eventf(existingIpamIP, corev1.EventTypeNormal, "Deleted", "Deleted old IPAM IP")
log.Infof("Old IP deleted from subnet %s/%s, sleeping for 5 seconds, so the finalizer can run", subnet.Namespace, subnet.Name)
time.Sleep(5 * time.Second)
createIpamIP = true
}
}

if createIpamIP {
err = k.Client.Create(ctx, ipamIP)
if err != nil && !apierrors.IsAlreadyExists(err) {
err = errors.Wrapf(err, "Failed to create IP %s/%s", ipamIP.Namespace, ipamIP.Name)
if ipamIP != nil {
err = k.doCreateIpamIP(ipamIP, subnetName)
if err != nil {
return err
}
if apierrors.IsAlreadyExists(err) {
// do not create IP, because the deletion is not yet ready
noop()
} else {
log.Infof("New IP created in subnet %s/%s", subnet.Namespace, subnet.Name)
k.EventRecorder.Eventf(ipamIP, corev1.EventTypeNormal, "Created", "Created IPAM IP")
}
} else {
log.Infof("IP already exists in subnet %s/%s, nothing to do", subnet.Namespace, subnet.Name)
}
break
}
Expand All @@ -185,6 +111,108 @@ func (k K8sClient) createIpamIP(ipaddr net.IP, mac net.HardwareAddr) error {
return nil
}

func (k K8sClient) getMatchingSubnet(subnetName string, ipaddr net.IP) *ipamv1alpha1.Subnet {
subnet := &ipamv1alpha1.Subnet{
ObjectMeta: metav1.ObjectMeta{
Name: subnetName,
Namespace: k.Namespace,
},
}
existingSubnet := subnet.DeepCopy()
err := k.Client.Get(k.Ctx, client.ObjectKeyFromObject(subnet), existingSubnet)
if err != nil && !apierrors.IsNotFound(err) {
err = errors.Wrapf(err, "Failed to get subnet %s/%s", k.Namespace, subnetName)

Check failure on line 124 in plugins/ipam/k8s.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
return nil
}
if apierrors.IsNotFound(err) {
log.Debugf("Cannot select subnet %s/%s, does not exist", k.Namespace, subnetName)
return nil
}
if !checkIPv6InCIDR(ipaddr, existingSubnet.Status.Reserved.String()) {
log.Debugf("Cannot select subnet %s/%s, CIDR mismatch", k.Namespace, subnetName)
return nil
}

return subnet
}

func (k K8sClient) prepareCreateIpamIP(subnetName string, ipaddr net.IP, mac net.HardwareAddr) (*ipamv1alpha1.IP, error) {
ip, err := ipamv1alpha1.IPAddrFromString(ipaddr.String())

Check failure on line 140 in plugins/ipam/k8s.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
// a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and
// must start and end with an alphanumeric character.
// 2001:abcd:abcd::1 will become 2001-abcd-abcd-0000-0000-0000-0000-00001
longIpv6 := getLongIPv6(ipaddr)
name := longIpv6 + "-" + origin
macKey := strings.ReplaceAll(mac.String(), ":", "")
ipamIP := &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: k.Namespace,
Labels: map[string]string{
"ip": longIpv6,
"mac": macKey,
"origin": origin,
},
},
Spec: ipamv1alpha1.IPSpec{
IP: ip,
Subnet: corev1.LocalObjectReference{
Name: subnetName,
},
},
}

existingIpamIP := ipamIP.DeepCopy()
err = k.Client.Get(k.Ctx, client.ObjectKeyFromObject(ipamIP), existingIpamIP)
if err != nil && !apierrors.IsNotFound(err) {
err = errors.Wrapf(err, "Failed to get IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)
return nil, err
}

// create IPAM IP if not exists or delete existing if ip differs
if apierrors.IsNotFound(err) {
noop()
} else {
if !reflect.DeepEqual(ipamIP.Spec, existingIpamIP.Spec) {
log.Debugf("IP mismatch:\nold IP: %v,\nnew IP: %v", prettyFormat(existingIpamIP.Spec), prettyFormat(ipamIP.Spec))
log.Infof("Delete old IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)

// delete old IP object
err = k.Client.Delete(k.Ctx, existingIpamIP)
if err != nil {
err = errors.Wrapf(err, "Failed to delete IP %s/%s", existingIpamIP.Namespace, existingIpamIP.Name)
return nil, err
}

k.EventRecorder.Eventf(existingIpamIP, corev1.EventTypeNormal, "Deleted", "Deleted old IPAM IP")
log.Infof("Old IP deleted from subnet %s/%s, sleeping for 5 seconds, so the finalizer can run", k.Namespace, subnetName)
time.Sleep(5 * time.Second)
} else {
log.Infof("IP already exists in subnet %s/%s, nothing to do", k.Namespace, subnetName)
return nil, nil
}
}

return ipamIP, nil
}

func (k K8sClient) doCreateIpamIP(ipamIP *ipamv1alpha1.IP, subnetName string) error {
err := k.Client.Create(k.Ctx, ipamIP)
if err != nil && !apierrors.IsAlreadyExists(err) {
err = errors.Wrapf(err, "Failed to create IP %s/%s", ipamIP.Namespace, ipamIP.Name)
return err
}
if apierrors.IsAlreadyExists(err) {
// do not create IP, because the deletion is not yet ready
noop()
} else {
log.Infof("New IP created in subnet %s/%s", k.Namespace, subnetName)
k.EventRecorder.Eventf(ipamIP, corev1.EventTypeNormal, "Created", "Created IPAM IP")
}

return nil
}

func getLongIPv6(ip net.IP) string {
dst := make([]byte, hex.EncodedLen(len(ip)))
_ = hex.Encode(dst, ip)
Expand Down

0 comments on commit 476492c

Please sign in to comment.