diff --git a/plugins/ipam/k8s.go b/plugins/ipam/k8s.go index 35545c8..edad637 100644 --- a/plugins/ipam/k8s.go +++ b/plugins/ipam/k8s.go @@ -33,6 +33,7 @@ type K8sClient struct { Client client.Client Namespace string SubnetNames []string + Ctx context.Context EventRecorder record.EventRecorder } @@ -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) @@ -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 } @@ -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) + 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()) + // 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)