-
Notifications
You must be signed in to change notification settings - Fork 332
/
allocator.go
200 lines (174 loc) · 5.81 KB
/
allocator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package persistentips
import (
"errors"
"fmt"
ipam "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip"
"k8s.io/klog/v2"
"net"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
ipamclaimsapi "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1"
ipamclaimslister "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube"
ovnktypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
)
var (
ErrIgnoredIPAMClaim = errors.New("ignored IPAMClaim: it belongs to other network")
ErrPersistentIPsNotAvailableOnNetwork = errors.New("ipam claims not supported on this network")
)
type IPReleaser interface {
ReleaseIPs(ips []*net.IPNet) error
}
type IPAllocator interface {
AllocateIPs(ips []*net.IPNet) error
}
type PersistentAllocations interface {
FindIPAMClaim(claimName string, namespace string) (*ipamclaimsapi.IPAMClaim, error)
Reconcile(oldIPAMClaim *ipamclaimsapi.IPAMClaim, newIPAMClaim *ipamclaimsapi.IPAMClaim, ipReleaser IPReleaser) error
}
// IPAMClaimReconciler acts on IPAMClaim events handed off by the cluster network
// controller and allocates or releases IPs for IPAMClaims.
type IPAMClaimReconciler struct {
kube kube.InterfaceOVN
// netInfo is used to filter relevant IPAMClaim events when syncing
// i.e. each NetworkController has a PersistentIPs.IPAMClaimReconciler, which syncs
// and deletes IPAM claims for a *single* network
// we need this to know if the network supports IPAM
netInfo util.NetInfo
lister ipamclaimslister.IPAMClaimLister
}
// NewIPAMClaimReconciler builds a new PersistentIPsAllocator
func NewIPAMClaimReconciler(kube kube.InterfaceOVN, netConfig util.NetInfo, lister ipamclaimslister.IPAMClaimLister) *IPAMClaimReconciler {
pipsAllocator := &IPAMClaimReconciler{
kube: kube,
netInfo: netConfig,
lister: lister,
}
return pipsAllocator
}
// Reconcile updates an IPAMClaim with the IP addresses allocated to the pod's
// interface
func (icr *IPAMClaimReconciler) Reconcile(
oldIPAMClaim *ipamclaimsapi.IPAMClaim,
newIPAMClaim *ipamclaimsapi.IPAMClaim,
ipReleaser IPReleaser,
) error {
var ipamClaim *ipamclaimsapi.IPAMClaim
if oldIPAMClaim != nil {
ipamClaim = oldIPAMClaim
}
if newIPAMClaim != nil {
ipamClaim = newIPAMClaim
}
if ipamClaim == nil {
return nil
}
if newIPAMClaim == nil {
if err := icr.releaseIPs(oldIPAMClaim, ipReleaser); err != nil {
return fmt.Errorf("error releasing IPs %q from IPAM claim: %w", oldIPAMClaim.Status.IPs, err)
}
return nil
}
mustUpdateIPAMClaim := (oldIPAMClaim == nil ||
len(oldIPAMClaim.Status.IPs) == 0) &&
newIPAMClaim != nil
if mustUpdateIPAMClaim {
if err := icr.kube.UpdateIPAMClaimIPs(newIPAMClaim); err != nil {
return fmt.Errorf(
"failed to update the allocation %q with allocations %q: %w",
newIPAMClaim.Name,
newIPAMClaim.Status.IPs,
err,
)
}
return nil
}
var originalIPs []string
if len(oldIPAMClaim.Status.IPs) > 0 {
originalIPs = oldIPAMClaim.Status.IPs
}
var newIPs []string
if newIPAMClaim != nil && len(newIPAMClaim.Status.IPs) > 0 {
newIPs = newIPAMClaim.Status.IPs
}
areClaimsEqual := cmp.Equal(
originalIPs,
newIPs,
cmpopts.SortSlices(func(a, b string) bool { return a < b }),
)
if !areClaimsEqual {
ipamClaimKey := fmt.Sprintf("%s/%s", ipamClaim.Namespace, ipamClaim.Name)
return fmt.Errorf(
"failed to update IPAMClaim %q - overwriting existing IPs %q with newer IPs %q",
ipamClaimKey,
originalIPs,
newIPs,
)
}
return nil
}
func (icr *IPAMClaimReconciler) FindIPAMClaim(claimName string, namespace string) (*ipamclaimsapi.IPAMClaim, error) {
if icr.lister == nil ||
!util.DoesNetworkRequireIPAM(icr.netInfo) ||
icr.netInfo.TopologyType() == ovnktypes.Layer3Topology ||
claimName == "" {
return nil, ErrPersistentIPsNotAvailableOnNetwork
}
claim, err := icr.lister.IPAMClaims(namespace).Get(claimName)
if err != nil {
return nil, fmt.Errorf("failed to get IPAMClaim %q: %w", claimName, err)
}
return claim, nil
}
// Sync initializes the IPs allocator with the IPAMClaims already existing on
// the cluster. For live pods, therse are already allocated, so no error will
// be thrown (e.g. we ignore the `ipam.IsErrAllocated` error
func (icr *IPAMClaimReconciler) Sync(objs []interface{}, ipAllocator IPAllocator) error {
var ips []*net.IPNet
for _, obj := range objs {
ipamClaim, ok := obj.(*ipamclaimsapi.IPAMClaim)
if !ok {
klog.Errorf("Could not cast %T object to *ipamclaimsapi.IPAMClaim", obj)
continue
}
if ipamClaim.Spec.Network != icr.netInfo.GetNetworkName() {
klog.V(5).Infof(
"Ignoring IPAMClaim for network %q in controller: %s",
ipamClaim.Spec.Network,
icr.netInfo.GetNetworkName(),
)
continue
}
ipnets, err := util.ParseIPNets(ipamClaim.Status.IPs)
if err != nil {
return fmt.Errorf("failed at parsing IP when allocating persistent IPs: %w", err)
}
ips = append(ips, ipnets...)
}
if len(ips) > 0 {
if err := ipAllocator.AllocateIPs(ips); err != nil && !ipam.IsErrAllocated(err) {
return fmt.Errorf("failed allocating persistent ips: %w", err)
}
}
return nil
}
func (icr *IPAMClaimReconciler) releaseIPs(ipamClaim *ipamclaimsapi.IPAMClaim, ipReleaser IPReleaser) error {
if ipamClaim.Spec.Network != icr.netInfo.GetNetworkName() {
return ErrIgnoredIPAMClaim
}
ips, err := util.ParseIPNets(ipamClaim.Status.IPs)
if err != nil {
klog.Errorf(
"Failed parsing ipnets while trying to release persistent IPs %q: %v",
ipamClaim.Status.IPs,
err,
)
return nil
}
if err := ipReleaser.ReleaseIPs(ips); err != nil {
return fmt.Errorf("failed releasing persistent IPs: %v", err)
}
klog.V(5).Infof("Released IPs: %+v", ips)
return nil
}