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

Refactor plugin IPAM #110

Merged
merged 2 commits into from
Mar 22, 2024
Merged
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
207 changes: 118 additions & 89 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,120 +67,148 @@ 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)
return err
}

// 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)
subnet, err := k.getMatchingSubnet(subnetName, ipaddr)
if err != nil {
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)
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 ipamIP != nil {
err = k.doCreateIpamIP(ipamIP, subnetName)
if err != nil {
return err
}
}
// break at first subnet match, there can be only one
break
}

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)
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")
if !subnetMatch {
log.Warningf("No matching subnet found for IP %s/%s", k.Namespace, ipaddr)
}

return nil
}

func (k K8sClient) getMatchingSubnet(subnetName string, ipaddr net.IP) (*ipamv1alpha1.Subnet, error) {
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, err
}
if apierrors.IsNotFound(err) {
log.Debugf("Cannot select subnet %s/%s, does not exist", k.Namespace, subnetName)
return nil, nil
}
if !checkIPv6InCIDR(ipaddr, existingSubnet.Status.Reserved.String()) {
log.Debugf("Cannot select subnet %s/%s, CIDR mismatch", k.Namespace, subnetName)
return nil, nil
}

return subnet, nil
}

func (k K8sClient) prepareCreateIpamIP(subnetName string, ipaddr net.IP, mac net.HardwareAddr) (*ipamv1alpha1.IP, error) {
ip, err := ipamv1alpha1.IPAddrFromString(ipaddr.String())
if err != nil {
err = errors.Wrapf(err, "Failed to parse IP %s", ipaddr)
return nil, err
}

// 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", subnet.Namespace, subnet.Name)
log.Infof("IP already exists in subnet %s/%s, nothing to do", k.Namespace, subnetName)
return nil, nil
}
break
}

if !subnetMatch {
log.Warningf("No matching subnet found for IP %s/%s", k.Namespace, ip)
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
Expand Down
Loading