• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubeovn / kube-ovn / 13425935899

20 Feb 2025 01:32AM UTC coverage: 22.263% (+0.2%) from 22.068%
13425935899

Pull #4991

github

zhangzujian
wip

Signed-off-by: zhangzujian <zhangzujian.7@gmail.com>
Pull Request #4991: add support for internalTrafficPolicy=Local

175 of 617 new or added lines in 13 files covered. (28.36%)

8 existing lines in 5 files now uncovered.

10436 of 46876 relevant lines covered (22.26%)

0.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/controller/init.go
1
package controller
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "strings"
8
        "time"
9

10
        "github.com/scylladb/go-set/strset"
11
        v1 "k8s.io/api/core/v1"
12
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
13
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
        "k8s.io/apimachinery/pkg/labels"
15
        "k8s.io/apimachinery/pkg/types"
16
        "k8s.io/client-go/tools/cache"
17
        "k8s.io/klog/v2"
18
        "k8s.io/utils/ptr"
19
        "sigs.k8s.io/controller-runtime/pkg/client"
20
        "sigs.k8s.io/controller-runtime/pkg/client/config"
21
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
22

23
        kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
24
        "github.com/kubeovn/kube-ovn/pkg/ovs"
25
        "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb"
26
        "github.com/kubeovn/kube-ovn/pkg/util"
27
)
28

29
func (c *Controller) InitOVN() error {
×
30
        var err error
×
31

×
32
        if err := c.InitDefaultVpc(); err != nil {
×
33
                klog.Errorf("init default vpc failed: %v", err)
×
34
                return err
×
35
        }
×
36

37
        if err = c.initClusterRouter(); err != nil {
×
38
                klog.Errorf("init cluster router failed: %v", err)
×
39
                return err
×
40
        }
×
41

42
        if c.config.EnableLb {
×
NEW
43
                klog.Info("init load balancer")
×
44
                if err = c.initLoadBalancer(); err != nil {
×
45
                        klog.Errorf("init load balancer failed: %v", err)
×
46
                        return err
×
47
                }
×
48
        }
49

50
        if err = c.initDefaultVlan(); err != nil {
×
51
                klog.Errorf("init default vlan failed: %v", err)
×
52
                return err
×
53
        }
×
54

55
        if err = c.initNodeSwitch(); err != nil {
×
56
                klog.Errorf("init node switch failed: %v", err)
×
57
                return err
×
58
        }
×
59

60
        if err = c.initDefaultLogicalSwitch(); err != nil {
×
61
                klog.Errorf("init default switch failed: %v", err)
×
62
                return err
×
63
        }
×
64

65
        return nil
×
66
}
67

68
func (c *Controller) InitDefaultVpc() error {
×
69
        cachedVpc, err := c.vpcsLister.Get(c.config.ClusterRouter)
×
70
        if err != nil {
×
71
                if !k8serrors.IsNotFound(err) {
×
72
                        klog.Errorf("failed to get default vpc %q: %v", c.config.ClusterRouter, err)
×
73
                        return err
×
74
                }
×
75
                // create default vpc
76
                vpc := &kubeovnv1.Vpc{
×
77
                        ObjectMeta: metav1.ObjectMeta{Name: c.config.ClusterRouter},
×
78
                }
×
79
                cachedVpc, err = c.config.KubeOvnClient.KubeovnV1().Vpcs().Create(context.Background(), vpc, metav1.CreateOptions{})
×
80
                if err != nil {
×
81
                        klog.Errorf("failed to create default vpc %q: %v", c.config.ClusterRouter, err)
×
82
                        return err
×
83
                }
×
84
        }
85

86
        // update default vpc status
87
        vpc := cachedVpc.DeepCopy()
×
88
        if !vpc.Status.Default || !vpc.Status.Standby ||
×
89
                vpc.Status.Router != c.config.ClusterRouter ||
×
90
                vpc.Status.DefaultLogicalSwitch != c.config.DefaultLogicalSwitch {
×
91
                vpc.Status.Standby = true
×
92
                vpc.Status.Default = true
×
93
                vpc.Status.Router = c.config.ClusterRouter
×
94
                vpc.Status.DefaultLogicalSwitch = c.config.DefaultLogicalSwitch
×
95

×
96
                if _, err = c.config.KubeOvnClient.KubeovnV1().Vpcs().UpdateStatus(context.Background(), vpc, metav1.UpdateOptions{}); err != nil {
×
97
                        klog.Errorf("failed to update default vpc %q: %v", c.config.ClusterRouter, err)
×
98
                        return err
×
99
                }
×
100
        }
101

102
        return nil
×
103
}
104

105
// InitDefaultLogicalSwitch init the default logical switch for ovn network
106
func (c *Controller) initDefaultLogicalSwitch() error {
×
107
        subnet, err := c.subnetsLister.Get(c.config.DefaultLogicalSwitch)
×
108
        if err == nil {
×
109
                if subnet != nil && util.CheckProtocol(c.config.DefaultCIDR) != util.CheckProtocol(subnet.Spec.CIDRBlock) {
×
110
                        // single-stack upgrade to dual-stack
×
111
                        if util.CheckProtocol(c.config.DefaultCIDR) == kubeovnv1.ProtocolDual {
×
112
                                subnet := subnet.DeepCopy()
×
113
                                subnet.Spec.CIDRBlock = c.config.DefaultCIDR
×
114
                                if _, err = c.formatSubnet(subnet); err != nil {
×
115
                                        klog.Errorf("init format subnet %s failed: %v", c.config.DefaultLogicalSwitch, err)
×
116
                                        return err
×
117
                                }
×
118
                        }
119
                }
120
                return nil
×
121
        }
122

123
        if !k8serrors.IsNotFound(err) {
×
124
                klog.Errorf("get default subnet %s failed: %v", c.config.DefaultLogicalSwitch, err)
×
125
                return err
×
126
        }
×
127

128
        defaultSubnet := kubeovnv1.Subnet{
×
129
                ObjectMeta: metav1.ObjectMeta{Name: c.config.DefaultLogicalSwitch},
×
130
                Spec: kubeovnv1.SubnetSpec{
×
131
                        Vpc:                 c.config.ClusterRouter,
×
132
                        Default:             true,
×
133
                        Provider:            util.OvnProvider,
×
134
                        CIDRBlock:           c.config.DefaultCIDR,
×
135
                        Gateway:             c.config.DefaultGateway,
×
136
                        GatewayNode:         "",
×
137
                        DisableGatewayCheck: !c.config.DefaultGatewayCheck,
×
138
                        ExcludeIps:          strings.Split(c.config.DefaultExcludeIps, ","),
×
139
                        NatOutgoing:         true,
×
140
                        GatewayType:         kubeovnv1.GWDistributedType,
×
141
                        Protocol:            util.CheckProtocol(c.config.DefaultCIDR),
×
142
                        EnableLb:            &c.config.EnableLb,
×
143
                },
×
144
        }
×
145
        if c.config.NetworkType == util.NetworkTypeVlan {
×
146
                defaultSubnet.Spec.Vlan = c.config.DefaultVlanName
×
147
                if c.config.DefaultLogicalGateway && c.config.DefaultU2OInterconnection {
×
148
                        err = errors.New("logicalGateway and u2oInterconnection can't be opened at the same time")
×
149
                        klog.Error(err)
×
150
                        return err
×
151
                }
×
152
                defaultSubnet.Spec.LogicalGateway = c.config.DefaultLogicalGateway
×
153
                defaultSubnet.Spec.U2OInterconnection = c.config.DefaultU2OInterconnection
×
154
        }
155

156
        if _, err = c.config.KubeOvnClient.KubeovnV1().Subnets().Create(context.Background(), &defaultSubnet, metav1.CreateOptions{}); err != nil {
×
157
                klog.Errorf("failed to create default subnet %q: %v", c.config.DefaultLogicalSwitch, err)
×
158
                return err
×
159
        }
×
160
        return nil
×
161
}
162

163
// InitNodeSwitch init node switch to connect host and pod
164
func (c *Controller) initNodeSwitch() error {
×
165
        subnet, err := c.subnetsLister.Get(c.config.NodeSwitch)
×
166
        if err == nil {
×
167
                if util.CheckProtocol(c.config.NodeSwitchCIDR) == kubeovnv1.ProtocolDual && util.CheckProtocol(subnet.Spec.CIDRBlock) != kubeovnv1.ProtocolDual {
×
168
                        // single-stack upgrade to dual-stack
×
169
                        subnet := subnet.DeepCopy()
×
170
                        subnet.Spec.CIDRBlock = c.config.NodeSwitchCIDR
×
171
                        if _, err = c.formatSubnet(subnet); err != nil {
×
172
                                klog.Errorf("init format subnet %s failed: %v", c.config.NodeSwitch, err)
×
173
                                return err
×
174
                        }
×
175
                } else {
×
176
                        c.config.NodeSwitchCIDR = subnet.Spec.CIDRBlock
×
177
                }
×
178
                return nil
×
179
        }
180

181
        if !k8serrors.IsNotFound(err) {
×
182
                klog.Errorf("get node subnet %s failed: %v", c.config.NodeSwitch, err)
×
183
                return err
×
184
        }
×
185

186
        nodeSubnet := kubeovnv1.Subnet{
×
187
                ObjectMeta: metav1.ObjectMeta{Name: c.config.NodeSwitch},
×
188
                Spec: kubeovnv1.SubnetSpec{
×
189
                        Vpc:                    c.config.ClusterRouter,
×
190
                        Default:                false,
×
191
                        Provider:               util.OvnProvider,
×
192
                        CIDRBlock:              c.config.NodeSwitchCIDR,
×
193
                        Gateway:                c.config.NodeSwitchGateway,
×
194
                        GatewayNode:            "",
×
195
                        ExcludeIps:             strings.Split(c.config.NodeSwitchGateway, ","),
×
196
                        Protocol:               util.CheckProtocol(c.config.NodeSwitchCIDR),
×
197
                        DisableInterConnection: true,
×
198
                },
×
199
        }
×
200

×
201
        if _, err = c.config.KubeOvnClient.KubeovnV1().Subnets().Create(context.Background(), &nodeSubnet, metav1.CreateOptions{}); err != nil {
×
202
                klog.Errorf("failed to create node subnet %q: %v", c.config.NodeSwitch, err)
×
203
                return err
×
204
        }
×
205
        return nil
×
206
}
207

208
// InitClusterRouter init cluster router to connect different logical switches
209
func (c *Controller) initClusterRouter() error {
×
210
        if err := c.OVNNbClient.CreateLogicalRouter(c.config.ClusterRouter); err != nil {
×
211
                klog.Errorf("create logical router %s failed: %v", c.config.ClusterRouter, err)
×
212
                return err
×
213
        }
×
214

215
        lr, err := c.OVNNbClient.GetLogicalRouter(c.config.ClusterRouter, false)
×
216
        if err != nil {
×
217
                klog.Errorf("get logical router %s failed: %v", c.config.ClusterRouter, err)
×
218
                return err
×
219
        }
×
220

221
        lr.Options = map[string]string{"always_learn_from_arp_request": "false", "dynamic_neigh_routers": "true", "mac_binding_age_threshold": "300"}
×
222
        err = c.OVNNbClient.UpdateLogicalRouter(lr, &lr.Options)
×
223
        if err != nil {
×
224
                klog.Errorf("update logical router %s failed: %v", c.config.ClusterRouter, err)
×
225
                return err
×
226
        }
×
227

228
        return nil
×
229
}
230

NEW
231
func (c *Controller) initLB(name, protocol string, template, sessionAffinity bool) error {
×
NEW
232
        var selectFields string
×
233
        if sessionAffinity {
×
234
                selectFields = ovnnb.LoadBalancerSelectionFieldsIPSrc
×
235
        }
×
236

NEW
237
        if err := c.OVNNbClient.CreateLoadBalancer(name, strings.ToLower(protocol), selectFields, template); err != nil {
×
238
                klog.Errorf("create load balancer %s: %v", name, err)
×
239
                return err
×
240
        }
×
241

242
        if sessionAffinity {
×
NEW
243
                if err := c.OVNNbClient.SetLoadBalancerAffinityTimeout(name, util.DefaultServiceSessionStickinessTimeout); err != nil {
×
244
                        klog.Errorf("failed to set affinity timeout of %s load balancer %s: %v", protocol, name, err)
×
245
                        return err
×
246
                }
×
247
        }
248

249
        return nil
×
250
}
251

252
// InitLoadBalancer init the default tcp and udp cluster loadbalancer
253
func (c *Controller) initLoadBalancer() error {
×
254
        vpcs, err := c.vpcsLister.List(labels.Everything())
×
255
        if err != nil {
×
256
                klog.Errorf("failed to list vpc: %v", err)
×
257
                return err
×
258
        }
×
259

260
        for _, cachedVpc := range vpcs {
×
261
                vpc := cachedVpc.DeepCopy()
×
262
                vpcLb := c.GenVpcLoadBalancer(vpc.Name)
×
NEW
263
                for _, lb := range vpcLb.LBs() {
×
NEW
264
                        klog.Infof("init load balancer %s", lb.Name)
×
NEW
265
                        if err = c.initLB(lb.Name, lb.Protocol, lb.Template, lb.SessionAffinity); err != nil {
×
NEW
266
                                klog.Error(err)
×
NEW
267
                                return err
×
NEW
268
                        }
×
269
                }
270

NEW
271
                vpc.Status.TCPLoadBalancer = vpcLb.TCPLoadBalancer.Name
×
NEW
272
                vpc.Status.TCPSessionLoadBalancer = vpcLb.TCPSessLoadBalancer.Name
×
NEW
273
                vpc.Status.UDPLoadBalancer = vpcLb.UDPLoadBalancer.Name
×
NEW
274
                vpc.Status.UDPSessionLoadBalancer = vpcLb.UDPSessLoadBalancer.Name
×
NEW
275
                vpc.Status.SCTPLoadBalancer = vpcLb.SCTPLoadBalancer.Name
×
NEW
276
                vpc.Status.SCTPSessionLoadBalancer = vpcLb.SCTPSessLoadBalancer.Name
×
NEW
277
                vpc.Status.LocalTCPLoadBalancer = vpcLb.LocalTCPLoadBalancer.Name
×
NEW
278
                vpc.Status.LocalTCPSessionLoadBalancer = vpcLb.LocalTCPSessLoadBalancer.Name
×
NEW
279
                vpc.Status.LocalUDPLoadBalancer = vpcLb.LocalUDPLoadBalancer.Name
×
NEW
280
                vpc.Status.LocalUDPSessionLoadBalancer = vpcLb.LocalUDPSessLoadBalancer.Name
×
NEW
281
                vpc.Status.LocalSCTPLoadBalancer = vpcLb.LocalSCTPLoadBalancer.Name
×
NEW
282
                vpc.Status.LocalSCTPSessionLoadBalancer = vpcLb.LocalSCTPSessLoadBalancer.Name
×
283
                bytes, err := vpc.Status.Bytes()
×
284
                if err != nil {
×
285
                        klog.Error(err)
×
286
                        return err
×
287
                }
×
288
                if _, err = c.config.KubeOvnClient.KubeovnV1().Vpcs().Patch(context.Background(), vpc.Name, types.MergePatchType, bytes, metav1.PatchOptions{}, "status"); err != nil {
×
289
                        klog.Error(err)
×
290
                        return err
×
291
                }
×
292
        }
293
        return nil
×
294
}
295

296
func (c *Controller) InitIPAM() error {
×
297
        start := time.Now()
×
298
        subnets, err := c.subnetsLister.List(labels.Everything())
×
299
        if err != nil {
×
300
                klog.Errorf("failed to list subnet: %v", err)
×
301
                return err
×
302
        }
×
303
        subnetProviderMaps := make(map[string]string, len(subnets))
×
304
        for _, subnet := range subnets {
×
305
                klog.Infof("Init subnet %s", subnet.Name)
×
306

×
307
                subnetProviderMaps[subnet.Name] = subnet.Spec.Provider
×
308

×
309
                if err := c.ipam.AddOrUpdateSubnet(subnet.Name, subnet.Spec.CIDRBlock, subnet.Spec.Gateway, subnet.Spec.ExcludeIps); err != nil {
×
310
                        klog.Errorf("failed to init subnet %s: %v", subnet.Name, err)
×
311
                }
×
312

313
                u2oInterconnName := fmt.Sprintf(util.U2OInterconnName, subnet.Spec.Vpc, subnet.Name)
×
314
                u2oInterconnLrpName := fmt.Sprintf("%s-%s", subnet.Spec.Vpc, subnet.Name)
×
315
                if subnet.Status.U2OInterconnectionIP != "" {
×
316
                        var mac *string
×
317
                        if subnet.Status.U2OInterconnectionMAC != "" {
×
318
                                mac = ptr.To(subnet.Status.U2OInterconnectionMAC)
×
319
                        } else {
×
320
                                lrp, err := c.OVNNbClient.GetLogicalRouterPort(u2oInterconnLrpName, true)
×
321
                                if err != nil {
×
322
                                        klog.Errorf("failed to get logical router port %s: %v", u2oInterconnLrpName, err)
×
323
                                        return err
×
324
                                }
×
325
                                if lrp != nil {
×
326
                                        mac = ptr.To(lrp.MAC)
×
327
                                }
×
328
                        }
329
                        if _, _, _, err = c.ipam.GetStaticAddress(u2oInterconnName, u2oInterconnLrpName, subnet.Status.U2OInterconnectionIP, mac, subnet.Name, true); err != nil {
×
330
                                klog.Errorf("failed to init subnet %q u2o interconnection ip to ipam %v", subnet.Name, err)
×
331
                        }
×
332
                }
333
        }
334

335
        ippools, err := c.ippoolLister.List(labels.Everything())
×
336
        if err != nil {
×
337
                klog.Errorf("failed to list ippool: %v", err)
×
338
                return err
×
339
        }
×
340
        for _, ippool := range ippools {
×
341
                if err = c.ipam.AddOrUpdateIPPool(ippool.Spec.Subnet, ippool.Name, ippool.Spec.IPs); err != nil {
×
342
                        klog.Errorf("failed to init ippool %s: %v", ippool.Name, err)
×
343
                }
×
344
        }
345

346
        pods, err := c.podsLister.List(labels.Everything())
×
347
        if err != nil {
×
348
                klog.Errorf("failed to list pods: %v", err)
×
349
                return err
×
350
        }
×
351

352
        ips, err := c.ipsLister.List(labels.Everything())
×
353
        if err != nil {
×
354
                klog.Errorf("failed to list IPs: %v", err)
×
355
                return err
×
356
        }
×
357

358
        for _, ip := range ips {
×
359
                // recover sts and kubevirt vm ip, other ip recover in later pod loop
×
360
                if ip.Spec.PodType != util.StatefulSet && ip.Spec.PodType != util.VM {
×
361
                        continue
×
362
                }
363

364
                var ipamKey string
×
365
                if ip.Spec.Namespace != "" {
×
366
                        ipamKey = fmt.Sprintf("%s/%s", ip.Spec.Namespace, ip.Spec.PodName)
×
367
                } else {
×
368
                        ipamKey = util.NodeLspName(ip.Spec.PodName)
×
369
                }
×
370
                if _, _, _, err = c.ipam.GetStaticAddress(ipamKey, ip.Name, ip.Spec.IPAddress, &ip.Spec.MacAddress, ip.Spec.Subnet, true); err != nil {
×
371
                        klog.Errorf("failed to init IPAM from IP CR %s: %v", ip.Name, err)
×
372
                }
×
373
        }
374

375
        for _, pod := range pods {
×
376
                if pod.Spec.HostNetwork {
×
377
                        continue
×
378
                }
379

380
                isAlive := isPodAlive(pod)
×
381
                isStsPod, _, _ := isStatefulSetPod(pod)
×
382
                if !isAlive && !isStsPod {
×
383
                        continue
×
384
                }
385

386
                podNets, err := c.getPodKubeovnNets(pod)
×
387
                if err != nil {
×
388
                        klog.Errorf("failed to get pod kubeovn nets %s.%s address %s: %v", pod.Name, pod.Namespace, pod.Annotations[util.IPAddressAnnotation], err)
×
389
                        continue
×
390
                }
391

392
                podType := getPodType(pod)
×
393
                podName := c.getNameByPod(pod)
×
394
                key := fmt.Sprintf("%s/%s", pod.Namespace, podName)
×
395
                for _, podNet := range podNets {
×
396
                        if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] == "true" {
×
397
                                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
398
                                ip := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
×
399
                                mac := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
×
400
                                _, _, _, err := c.ipam.GetStaticAddress(key, portName, ip, &mac, podNet.Subnet.Name, true)
×
401
                                if err != nil {
×
402
                                        klog.Errorf("failed to init pod %s.%s address %s: %v", podName, pod.Namespace, pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)], err)
×
403
                                } else {
×
404
                                        err = c.createOrUpdateIPCR(portName, podName, ip, mac, podNet.Subnet.Name, pod.Namespace, pod.Spec.NodeName, podType)
×
405
                                        if err != nil {
×
406
                                                klog.Errorf("failed to create/update ips CR %s.%s with ip address %s: %v", podName, pod.Namespace, ip, err)
×
407
                                        }
×
408
                                }
409

410
                                // Append ExternalIds is added in v1.7, used for upgrading from v1.6.3. It should be deleted now since v1.7 is not used anymore.
411
                        }
412
                }
413
        }
414

415
        vips, err := c.virtualIpsLister.List(labels.Everything())
×
416
        if err != nil {
×
417
                klog.Errorf("failed to list vips: %v", err)
×
418
                return err
×
419
        }
×
420
        for _, vip := range vips {
×
421
                provider, ok := subnetProviderMaps[vip.Spec.Subnet]
×
422
                if !ok {
×
423
                        klog.Errorf("failed to find subnet %s for vip %s", vip.Spec.Subnet, vip.Name)
×
424
                        continue
×
425
                }
426
                portName := ovs.PodNameToPortName(vip.Name, vip.Spec.Namespace, provider)
×
427
                if _, _, _, err = c.ipam.GetStaticAddress(vip.Name, portName, vip.Status.V4ip, &vip.Status.Mac, vip.Spec.Subnet, true); err != nil {
×
428
                        klog.Errorf("failed to init ipam from vip cr %s: %v", vip.Name, err)
×
429
                }
×
430
        }
431

432
        eips, err := c.iptablesEipsLister.List(labels.Everything())
×
433
        if err != nil {
×
434
                klog.Errorf("failed to list EIPs: %v", err)
×
435
                return err
×
436
        }
×
437
        for _, eip := range eips {
×
438
                externalNetwork := util.GetExternalNetwork(eip.Spec.ExternalSubnet)
×
439
                if _, _, _, err = c.ipam.GetStaticAddress(eip.Name, eip.Name, eip.Status.IP, &eip.Spec.MacAddress, externalNetwork, true); err != nil {
×
440
                        klog.Errorf("failed to init ipam from iptables eip cr %s: %v", eip.Name, err)
×
441
                }
×
442
        }
443

444
        oeips, err := c.ovnEipsLister.List(labels.Everything())
×
445
        if err != nil {
×
446
                klog.Errorf("failed to list ovn eips: %v", err)
×
447
                return err
×
448
        }
×
449
        for _, oeip := range oeips {
×
450
                if _, _, _, err = c.ipam.GetStaticAddress(oeip.Name, oeip.Name, oeip.Status.V4Ip, &oeip.Status.MacAddress, oeip.Spec.ExternalSubnet, true); err != nil {
×
451
                        klog.Errorf("failed to init ipam from ovn eip cr %s: %v", oeip.Name, err)
×
452
                }
×
453
        }
454

455
        nodes, err := c.nodesLister.List(labels.Everything())
×
456
        if err != nil {
×
457
                klog.Errorf("failed to list nodes: %v", err)
×
458
                return err
×
459
        }
×
460
        for _, node := range nodes {
×
461
                if node.Annotations[util.AllocatedAnnotation] == "true" {
×
462
                        portName := util.NodeLspName(node.Name)
×
463
                        mac := node.Annotations[util.MacAddressAnnotation]
×
464
                        v4IP, v6IP, _, err := c.ipam.GetStaticAddress(portName, portName,
×
465
                                node.Annotations[util.IPAddressAnnotation], &mac,
×
466
                                node.Annotations[util.LogicalSwitchAnnotation], true)
×
467
                        if err != nil {
×
468
                                klog.Errorf("failed to init node %s.%s address %s: %v", node.Name, node.Namespace, node.Annotations[util.IPAddressAnnotation], err)
×
469
                        }
×
470
                        if v4IP != "" && v6IP != "" {
×
471
                                node.Annotations[util.IPAddressAnnotation] = util.GetStringIP(v4IP, v6IP)
×
472
                        }
×
473
                }
474
        }
475

476
        klog.Infof("take %.2f seconds to initialize IPAM", time.Since(start).Seconds())
×
477
        return nil
×
478
}
479

480
func (c *Controller) initDefaultProviderNetwork() error {
×
481
        _, err := c.providerNetworksLister.Get(c.config.DefaultProviderName)
×
482
        if err == nil {
×
483
                return nil
×
484
        }
×
485
        if !k8serrors.IsNotFound(err) {
×
486
                klog.Errorf("failed to get default provider network %s: %v", c.config.DefaultProviderName, err)
×
487
                return err
×
488
        }
×
489

490
        nodes, err := c.nodesLister.List(labels.Everything())
×
491
        if err != nil {
×
492
                klog.Errorf("failed to get nodes: %v", err)
×
493
                return err
×
494
        }
×
495

496
        pn := kubeovnv1.ProviderNetwork{
×
497
                ObjectMeta: metav1.ObjectMeta{
×
498
                        Name: c.config.DefaultProviderName,
×
499
                },
×
500
                Spec: kubeovnv1.ProviderNetworkSpec{
×
501
                        DefaultInterface: c.config.DefaultHostInterface,
×
502
                        ExchangeLinkName: c.config.DefaultExchangeLinkName,
×
503
                },
×
504
        }
×
505

×
506
        excludeAnno := fmt.Sprintf(util.ProviderNetworkExcludeTemplate, c.config.DefaultProviderName)
×
507
        interfaceAnno := fmt.Sprintf(util.ProviderNetworkInterfaceTemplate, c.config.DefaultProviderName)
×
508
        patchNodes := make([]string, 0, len(nodes))
×
509
        for _, node := range nodes {
×
510
                if len(node.Annotations) == 0 {
×
511
                        continue
×
512
                }
513

514
                if node.Annotations[excludeAnno] == "true" {
×
515
                        pn.Spec.ExcludeNodes = append(pn.Spec.ExcludeNodes, node.Name)
×
516
                        patchNodes = append(patchNodes, node.Name)
×
517
                } else if s := node.Annotations[interfaceAnno]; s != "" {
×
518
                        var index *int
×
519
                        for i := range pn.Spec.CustomInterfaces {
×
520
                                if pn.Spec.CustomInterfaces[i].Interface == s {
×
521
                                        index = &i
×
522
                                        break
×
523
                                }
524
                        }
525
                        if index != nil {
×
526
                                pn.Spec.CustomInterfaces[*index].Nodes = append(pn.Spec.CustomInterfaces[*index].Nodes, node.Name)
×
527
                        } else {
×
528
                                ci := kubeovnv1.CustomInterface{Interface: s, Nodes: []string{node.Name}}
×
529
                                pn.Spec.CustomInterfaces = append(pn.Spec.CustomInterfaces, ci)
×
530
                        }
×
531
                        patchNodes = append(patchNodes, node.Name)
×
532
                }
533
        }
534

535
        defer func() {
×
536
                if err != nil {
×
537
                        return
×
538
                }
×
539

540
                // update nodes only when provider network has been created successfully
541
                patch := util.KVPatch{excludeAnno: nil, interfaceAnno: nil}
×
542
                for _, node := range patchNodes {
×
543
                        if err := util.PatchAnnotations(c.config.KubeClient.CoreV1().Nodes(), node, patch); err != nil {
×
544
                                klog.Errorf("failed to patch node %s: %v", node, err)
×
545
                        }
×
546
                }
547
        }()
548

549
        _, err = c.config.KubeOvnClient.KubeovnV1().ProviderNetworks().Create(context.Background(), &pn, metav1.CreateOptions{})
×
550
        if err != nil {
×
551
                klog.Errorf("failed to create provider network %s: %v", c.config.DefaultProviderName, err)
×
552
                return err
×
553
        }
×
554
        return nil
×
555
}
556

557
func (c *Controller) initDefaultVlan() error {
×
558
        if c.config.NetworkType != util.NetworkTypeVlan {
×
559
                return nil
×
560
        }
×
561

562
        if err := c.initDefaultProviderNetwork(); err != nil {
×
563
                klog.Error(err)
×
564
                return err
×
565
        }
×
566

567
        _, err := c.vlansLister.Get(c.config.DefaultVlanName)
×
568
        if err == nil {
×
569
                return nil
×
570
        }
×
571

572
        if !k8serrors.IsNotFound(err) {
×
573
                klog.Errorf("get default vlan %s failed: %v", c.config.DefaultVlanName, err)
×
574
                return err
×
575
        }
×
576

577
        if c.config.DefaultVlanID < 0 || c.config.DefaultVlanID > 4095 {
×
578
                return errors.New("the default vlan id is not between 1-4095")
×
579
        }
×
580

581
        defaultVlan := kubeovnv1.Vlan{
×
582
                ObjectMeta: metav1.ObjectMeta{Name: c.config.DefaultVlanName},
×
583
                Spec: kubeovnv1.VlanSpec{
×
584
                        ID:       c.config.DefaultVlanID,
×
585
                        Provider: c.config.DefaultProviderName,
×
586
                },
×
587
        }
×
588

×
589
        _, err = c.config.KubeOvnClient.KubeovnV1().Vlans().Create(context.Background(), &defaultVlan, metav1.CreateOptions{})
×
590
        if err != nil {
×
591
                klog.Errorf("failed to create vlan %s: %v", defaultVlan.Name, err)
×
592
                return err
×
593
        }
×
594
        return nil
×
595
}
596

597
func (c *Controller) syncIPCR() error {
×
598
        klog.Info("start to sync ips")
×
599
        ips, err := c.ipsLister.List(labels.Everything())
×
600
        if err != nil {
×
601
                if k8serrors.IsNotFound(err) {
×
602
                        return nil
×
603
                }
×
604
                klog.Error(err)
×
605
                return err
×
606
        }
607

608
        ipMap := strset.New(c.getVMLsps()...)
×
609
        for _, ip := range ips {
×
610
                if !ip.DeletionTimestamp.IsZero() && len(ip.GetFinalizers()) != 0 {
×
611
                        klog.Infof("enqueue update for deleting ip %s", ip.Name)
×
612
                        c.updateIPQueue.Add(ip.Name)
×
613
                        continue
×
614
                }
615
                changed := false
×
616
                ip = ip.DeepCopy()
×
617
                if ipMap.Has(ip.Name) && ip.Spec.PodType == "" {
×
618
                        ip.Spec.PodType = util.VM
×
619
                        changed = true
×
620
                }
×
621

622
                v4IP, v6IP := util.SplitStringIP(ip.Spec.IPAddress)
×
623
                if ip.Spec.V4IPAddress == v4IP && ip.Spec.V6IPAddress == v6IP && !changed {
×
624
                        continue
×
625
                }
626

627
                ip.Spec.V4IPAddress = v4IP
×
628
                ip.Spec.V6IPAddress = v6IP
×
629
                _, err := c.config.KubeOvnClient.KubeovnV1().IPs().Update(context.Background(), ip, metav1.UpdateOptions{})
×
630
                if err != nil {
×
631
                        klog.Errorf("failed to sync crd ip %s: %v", ip.Spec.IPAddress, err)
×
632
                        return err
×
633
                }
×
634
        }
635
        return nil
×
636
}
637

638
func (c *Controller) syncSubnetCR() error {
×
639
        klog.Info("start to sync subnets")
×
640
        subnets, err := c.subnetsLister.List(labels.Everything())
×
641
        if err != nil {
×
642
                if k8serrors.IsNotFound(err) {
×
643
                        return nil
×
644
                }
×
645
                klog.Error(err)
×
646
                return err
×
647
        }
648
        for _, cachedSubnet := range subnets {
×
649
                subnet := cachedSubnet.DeepCopy()
×
650
                if !subnet.Status.IsReady() {
×
651
                        klog.Warningf("subnet %s is not ready", subnet.Name)
×
652
                        continue
×
653
                }
654
                if util.CheckProtocol(subnet.Spec.CIDRBlock) == kubeovnv1.ProtocolDual {
×
655
                        subnet, err = c.calcDualSubnetStatusIP(subnet)
×
656
                } else {
×
657
                        subnet, err = c.calcSubnetStatusIP(subnet)
×
658
                }
×
659
                if err != nil {
×
660
                        klog.Errorf("failed to calculate subnet %s used ip: %v", cachedSubnet.Name, err)
×
661
                        return err
×
662
                }
×
663

664
                // only sync subnet spec enableEcmp when subnet.Spec.EnableEcmp is false and c.config.EnableEcmp is true
665
                if subnet.Spec.GatewayType == kubeovnv1.GWCentralizedType && !subnet.Spec.EnableEcmp && subnet.Spec.EnableEcmp != c.config.EnableEcmp {
×
666
                        subnet, err = c.subnetsLister.Get(subnet.Name)
×
667
                        if err != nil {
×
668
                                klog.Errorf("failed to get subnet %s: %v", subnet.Name, err)
×
669
                                return err
×
670
                        }
×
671

672
                        subnet.Spec.EnableEcmp = c.config.EnableEcmp
×
673
                        if _, err := c.config.KubeOvnClient.KubeovnV1().Subnets().Update(context.Background(), subnet, metav1.UpdateOptions{}); err != nil {
×
674
                                klog.Errorf("failed to sync subnet spec enableEcmp with kube-ovn-controller config enableEcmp %s: %v", subnet.Name, err)
×
675
                                return err
×
676
                        }
×
677
                }
678
        }
679
        return nil
×
680
}
681

682
func (c *Controller) syncVpcNatGatewayCR() error {
×
683
        klog.Info("start to sync crd vpc nat gw")
×
684
        gws, err := c.vpcNatGatewayLister.List(labels.Everything())
×
685
        if err != nil {
×
686
                klog.Errorf("failed to list vpc nat gateway, %v", err)
×
687
                return err
×
688
        }
×
689
        if len(gws) == 0 {
×
690
                return nil
×
691
        }
×
692
        // get vpc nat gateway enable state
693
        cm, err := c.configMapsLister.ConfigMaps(c.config.PodNamespace).Get(util.VpcNatGatewayConfig)
×
694
        if err != nil && !k8serrors.IsNotFound(err) {
×
695
                klog.Errorf("failed to get config map %s, %v", util.VpcNatGatewayConfig, err)
×
696
                return err
×
697
        }
×
698
        if k8serrors.IsNotFound(err) || cm.Data["enable-vpc-nat-gw"] == "false" {
×
699
                return nil
×
700
        }
×
701
        // get vpc nat gateway image
702
        cm, err = c.configMapsLister.ConfigMaps(c.config.PodNamespace).Get(util.VpcNatConfig)
×
703
        if err != nil {
×
704
                if k8serrors.IsNotFound(err) {
×
705
                        klog.Errorf("should set config map for vpc-nat-gateway %s, %v", util.VpcNatConfig, err)
×
706
                        return err
×
707
                }
×
708
                klog.Errorf("failed to get config map %s, %v", util.VpcNatConfig, err)
×
709
                return err
×
710
        }
711

712
        if cm.Data["image"] == "" {
×
713
                err = errors.New("should set image for vpc-nat-gateway pod")
×
714
                klog.Error(err)
×
715
                return err
×
716
        }
×
717

718
        for _, gw := range gws {
×
719
                if err := c.updateCrdNatGwLabels(gw.Name, ""); err != nil {
×
720
                        klog.Errorf("failed to update nat gw %s: %v", gw.Name, err)
×
721
                        return err
×
722
                }
×
723
        }
724
        return nil
×
725
}
726

727
func (c *Controller) syncVlanCR() error {
×
728
        klog.Info("start to sync vlans")
×
729
        vlans, err := c.vlansLister.List(labels.Everything())
×
730
        if err != nil {
×
731
                if k8serrors.IsNotFound(err) {
×
732
                        return nil
×
733
                }
×
734
                klog.Error(err)
×
735
                return err
×
736
        }
737

738
        for _, vlan := range vlans {
×
739
                var needUpdate bool
×
740
                newVlan := vlan.DeepCopy()
×
741
                if newVlan.Spec.VlanID != 0 && newVlan.Spec.ID == 0 {
×
742
                        newVlan.Spec.ID = newVlan.Spec.VlanID
×
743
                        newVlan.Spec.VlanID = 0
×
744
                        needUpdate = true
×
745
                }
×
746
                if newVlan.Spec.ProviderInterfaceName != "" && newVlan.Spec.Provider == "" {
×
747
                        newVlan.Spec.Provider = newVlan.Spec.ProviderInterfaceName
×
748
                        newVlan.Spec.ProviderInterfaceName = ""
×
749
                        needUpdate = true
×
750
                }
×
751
                if needUpdate {
×
752
                        if _, err = c.config.KubeOvnClient.KubeovnV1().Vlans().Update(context.Background(), newVlan, metav1.UpdateOptions{}); err != nil {
×
753
                                klog.Errorf("failed to update spec of vlan %s: %v", newVlan.Name, err)
×
754
                                return err
×
755
                        }
×
756
                }
757
        }
758

759
        return nil
×
760
}
761

762
func (c *Controller) batchMigrateNodeRoute(nodes []*v1.Node) error {
×
763
        start := time.Now()
×
764
        addPolicies := make([]*kubeovnv1.PolicyRoute, 0)
×
765
        delPolicies := make([]*kubeovnv1.PolicyRoute, 0)
×
766
        staticRoutes := make([]*kubeovnv1.StaticRoute, 0)
×
767
        externalIDsMap := make(map[string]map[string]string)
×
768
        delAsNames := make([]string, 0)
×
769
        for _, node := range nodes {
×
770
                if node.Annotations[util.AllocatedAnnotation] != "true" {
×
771
                        continue
×
772
                }
773
                nodeName := node.Name
×
774
                nodeIPv4, nodeIPv6 := util.GetNodeInternalIP(*node)
×
775
                joinAddrV4, joinAddrV6 := util.SplitStringIP(node.Annotations[util.IPAddressAnnotation])
×
776
                if nodeIPv4 != "" && joinAddrV4 != "" {
×
777
                        buildNodeRoute(4, nodeName, joinAddrV4, nodeIPv4, &addPolicies, &delPolicies, &staticRoutes, externalIDsMap, &delAsNames)
×
778
                }
×
779
                if nodeIPv6 != "" && joinAddrV6 != "" {
×
780
                        buildNodeRoute(6, nodeName, joinAddrV6, nodeIPv6, &addPolicies, &delPolicies, &staticRoutes, externalIDsMap, &delAsNames)
×
781
                }
×
782
        }
783

784
        if err := c.batchAddPolicyRouteToVpc(c.config.ClusterRouter, addPolicies, externalIDsMap); err != nil {
×
785
                klog.Errorf("failed to batch add logical router policy for lr %s nodes %d: %v", c.config.ClusterRouter, len(nodes), err)
×
786
                return err
×
787
        }
×
788
        if err := c.batchDeleteStaticRouteFromVpc(c.config.ClusterRouter, staticRoutes); err != nil {
×
789
                klog.Errorf("failed to batch delete  obsolete logical router static route for lr %s nodes %d: %v", c.config.ClusterRouter, len(nodes), err)
×
790
                return err
×
791
        }
×
792
        if err := c.batchDeletePolicyRouteFromVpc(c.config.ClusterRouter, delPolicies); err != nil {
×
793
                klog.Errorf("failed to batch delete obsolete logical router policy for lr %s nodes %d: %v", c.config.ClusterRouter, len(nodes), err)
×
794
                return err
×
795
        }
×
796
        if err := c.OVNNbClient.BatchDeleteAddressSetByNames(delAsNames); err != nil {
×
797
                klog.Errorf("failed to batch delete obsolete address set for asNames %v nodes %d: %v", delAsNames, len(nodes), err)
×
798
                return err
×
799
        }
×
800
        klog.V(3).Infof("take to %v batch migrate node route for router: %s priority: %d add policy len: %d extrenalID len: %d del policy len: %d del address set len: %d",
×
801
                time.Since(start), c.config.ClusterRouter, util.NodeRouterPolicyPriority, len(addPolicies), len(externalIDsMap), len(delPolicies), len(delAsNames))
×
802

×
803
        return nil
×
804
}
805

806
func buildNodeRoute(af int, nodeName, nexthop, ip string, addPolicies, delPolicies *[]*kubeovnv1.PolicyRoute, staticRoutes *[]*kubeovnv1.StaticRoute, externalIDsMap map[string]map[string]string, delAsNames *[]string) {
×
807
        var (
×
808
                match       = fmt.Sprintf("ip%d.dst == %s", af, ip)
×
809
                action      = kubeovnv1.PolicyRouteActionReroute
×
810
                externalIDs = map[string]string{
×
811
                        "vendor": util.CniTypeName,
×
812
                        "node":   nodeName,
×
813
                }
×
814
        )
×
815
        *addPolicies = append(*addPolicies, &kubeovnv1.PolicyRoute{
×
816
                Priority:  util.NodeRouterPolicyPriority,
×
817
                Match:     match,
×
818
                Action:    action,
×
819
                NextHopIP: nexthop,
×
820
        })
×
821
        externalIDsMap[buildExternalIDsMapKey(match, string(action), util.NodeRouterPolicyPriority)] = externalIDs
×
822
        *staticRoutes = append(*staticRoutes, &kubeovnv1.StaticRoute{
×
823
                Policy:     kubeovnv1.PolicyDst,
×
824
                RouteTable: util.MainRouteTable,
×
825
                NextHopIP:  "",
×
826
                CIDR:       ip,
×
827
        })
×
828
        asName := nodeUnderlayAddressSetName(nodeName, af)
×
829
        obsoleteMatch := fmt.Sprintf("ip%d.dst == %s && ip%d.src != $%s", af, ip, af, asName)
×
830
        *delPolicies = append(*delPolicies, &kubeovnv1.PolicyRoute{
×
831
                Match:    obsoleteMatch,
×
832
                Priority: util.NodeRouterPolicyPriority,
×
833
        })
×
834
        *delAsNames = append(*delAsNames, asName)
×
835
}
×
836

837
func (c *Controller) syncNodeRoutes() error {
×
838
        nodes, err := c.nodesLister.List(labels.Everything())
×
839
        if err != nil {
×
840
                klog.Errorf("failed to list nodes: %v", err)
×
841
                return err
×
842
        }
×
843

844
        if err := c.batchMigrateNodeRoute(nodes); err != nil {
×
845
                klog.Errorf("failed to batch migrate node routes: %v", err)
×
846
                return err
×
847
        }
×
848

849
        if err := c.addNodeGatewayStaticRoute(); err != nil {
×
850
                klog.Errorf("failed to add static route for node gateway")
×
851
                return err
×
852
        }
×
853
        return nil
×
854
}
855

856
func (c *Controller) initNodeChassis() error {
×
857
        nodes, err := c.nodesLister.List(labels.Everything())
×
858
        if err != nil {
×
859
                klog.Errorf("failed to list nodes: %v", err)
×
860
                return err
×
861
        }
×
862
        chassises, err := c.OVNSbClient.GetKubeOvnChassisses()
×
863
        if err != nil {
×
864
                klog.Errorf("failed to get chassis nodes: %v", err)
×
865
                return err
×
866
        }
×
867
        chassisNodes := make(map[string]string, len(*chassises))
×
868
        for _, chassis := range *chassises {
×
869
                chassisNodes[chassis.Name] = chassis.Hostname
×
870
        }
×
871
        for _, node := range nodes {
×
872
                if err := c.UpdateChassisTag(node); err != nil {
×
873
                        klog.Error(err)
×
874
                        if _, ok := err.(*ErrChassisNotFound); !ok {
×
875
                                return err
×
876
                        }
×
877
                }
878
        }
879
        return nil
×
880
}
881

882
func migrateFinalizers(c client.Client, list client.ObjectList, getObjectItem func(int) (client.Object, client.Object)) error {
×
883
        if err := c.List(context.Background(), list); err != nil {
×
884
                klog.Errorf("failed to list objects: %v", err)
×
885
                return err
×
886
        }
×
887

888
        var i int
×
889
        var cachedObj, patchedObj client.Object
×
890
        for {
×
891
                if cachedObj, patchedObj = getObjectItem(i); cachedObj == nil {
×
892
                        break
×
893
                }
894
                if !controllerutil.ContainsFinalizer(cachedObj, util.DepreciatedFinalizerName) {
×
895
                        i++
×
896
                        continue
×
897
                }
898
                controllerutil.RemoveFinalizer(patchedObj, util.DepreciatedFinalizerName)
×
899
                if cachedObj.GetDeletionTimestamp() == nil {
×
900
                        // if the object is not being deleted, add the new finalizer
×
901
                        controllerutil.AddFinalizer(patchedObj, util.KubeOVNControllerFinalizer)
×
902
                }
×
903
                if err := c.Patch(context.Background(), patchedObj, client.MergeFrom(cachedObj)); client.IgnoreNotFound(err) != nil {
×
904
                        klog.Errorf("failed to sync finalizers for %s %s: %v",
×
905
                                patchedObj.GetObjectKind().GroupVersionKind().Kind,
×
906
                                cache.MetaObjectToName(patchedObj), err)
×
907
                        return err
×
908
                }
×
909
                i++
×
910
        }
911

912
        return nil
×
913
}
914

915
func (c *Controller) syncFinalizers() error {
×
916
        cl, err := client.New(config.GetConfigOrDie(), client.Options{})
×
917
        if err != nil {
×
918
                klog.Errorf("failed to create client: %v", err)
×
919
                return err
×
920
        }
×
921

922
        // migrate depreciated finalizer to new finalizer
923
        klog.Info("start to sync finalizers")
×
924
        if err := c.syncIPFinalizer(cl); err != nil {
×
925
                klog.Errorf("failed to sync ip finalizer: %v", err)
×
926
                return err
×
927
        }
×
928
        if err := c.syncOvnDnatFinalizer(cl); err != nil {
×
929
                klog.Errorf("failed to sync ovn dnat finalizer: %v", err)
×
930
                return err
×
931
        }
×
932
        if err := c.syncOvnEipFinalizer(cl); err != nil {
×
933
                klog.Errorf("failed to sync ovn eip finalizer: %v", err)
×
934
                return err
×
935
        }
×
936
        if err := c.syncOvnFipFinalizer(cl); err != nil {
×
937
                klog.Errorf("failed to sync ovn fip finalizer: %v", err)
×
938
                return err
×
939
        }
×
940
        if err := c.syncOvnSnatFinalizer(cl); err != nil {
×
941
                klog.Errorf("failed to sync ovn snat finalizer: %v", err)
×
942
                return err
×
943
        }
×
944
        if err := c.syncQoSPolicyFinalizer(cl); err != nil {
×
945
                klog.Errorf("failed to sync qos policy finalizer: %v", err)
×
946
                return err
×
947
        }
×
948
        if err := c.syncSubnetFinalizer(cl); err != nil {
×
949
                klog.Errorf("failed to sync subnet finalizer: %v", err)
×
950
                return err
×
951
        }
×
952
        if err := c.syncVipFinalizer(cl); err != nil {
×
953
                klog.Errorf("failed to sync vip finalizer: %v", err)
×
954
                return err
×
955
        }
×
956
        if err := c.syncIptablesEipFinalizer(cl); err != nil {
×
957
                klog.Errorf("failed to sync iptables eip finalizer: %v", err)
×
958
                return err
×
959
        }
×
960
        if err := c.syncIptablesFipFinalizer(cl); err != nil {
×
961
                klog.Errorf("failed to sync iptables fip finalizer: %v", err)
×
962
                return err
×
963
        }
×
964
        if err := c.syncIptablesDnatFinalizer(cl); err != nil {
×
965
                klog.Errorf("failed to sync iptables dnat finalizer: %v", err)
×
966
                return err
×
967
        }
×
968
        if err := c.syncIptablesSnatFinalizer(cl); err != nil {
×
969
                klog.Errorf("failed to sync iptables snat finalizer: %v", err)
×
970
                return err
×
971
        }
×
972
        klog.Info("sync finalizers done")
×
973
        return nil
×
974
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc