-
Notifications
You must be signed in to change notification settings - Fork 250
/
utils.go
805 lines (726 loc) · 25.8 KB
/
utils.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
// Copyright (c) 2015-2020 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/sirupsen/logrus"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/projectcalico/cni-plugin/internal/pkg/azure"
"github.com/projectcalico/cni-plugin/pkg/types"
"github.com/projectcalico/libcalico-go/lib/apiconfig"
api "github.com/projectcalico/libcalico-go/lib/apis/v3"
client "github.com/projectcalico/libcalico-go/lib/clientv3"
"github.com/projectcalico/libcalico-go/lib/names"
cnet "github.com/projectcalico/libcalico-go/lib/net"
"github.com/projectcalico/libcalico-go/lib/options"
)
func Min(a, b int) int {
if a < b {
return a
}
return b
}
// DetermineNodename gets the node name, in order of priority:
// 1. Nodename field in NetConf
// 2. Nodename from the file /var/lib/calico/nodename
// 3. Hostname field in NetConf (DEPRECATED).
// 4. OS Hostname.
func DetermineNodename(conf types.NetConf) (nodename string) {
if conf.Nodename != "" {
logrus.Debugf("Read node name from CNI conf: %s", conf.Nodename)
nodename = conf.Nodename
} else if nff := nodenameFromFile(conf.NodenameFile); nff != "" {
logrus.Debugf("Read node name from file: %s", nff)
nodename = nff
} else if conf.Hostname != "" {
nodename = conf.Hostname
logrus.Warn("Configuration option 'hostname' is deprecated, use 'nodename' instead")
} else {
nodename, _ = names.Hostname()
logrus.Debugf("Read node name from OS Hostname")
}
logrus.Debugf("Using node name %s", nodename)
return
}
// nodenameFromFile reads the /var/lib/calico/nodename file if it exists and
// returns the nodename within.
func nodenameFromFile(filename string) string {
if filename == "" {
filename = "/var/lib/calico/nodename"
}
data, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
// File doesn't exist, return empty string.
logrus.Infof("File %s does not exist", filename)
return ""
}
logrus.WithError(err).Errorf("Failed to read %s", filename)
return ""
}
return string(data)
}
// MTUFromFile reads the /var/lib/calico/mtu file if it exists and
// returns the MTU within.
func MTUFromFile(filename string) (int, error) {
if filename == "" {
filename = "/var/lib/calico/mtu"
}
data, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
// File doesn't exist, return zero.
logrus.Infof("File %s does not exist", filename)
return 0, nil
}
logrus.WithError(err).Errorf("Failed to read %s", filename)
return 0, err
}
return strconv.Atoi(strings.TrimSpace(string(data)))
}
// CreateOrUpdate creates the WorkloadEndpoint if ResourceVersion is not specified,
// or Update if it's specified.
func CreateOrUpdate(ctx context.Context, client client.Interface, wep *api.WorkloadEndpoint) (*api.WorkloadEndpoint, error) {
if wep.ResourceVersion != "" {
return client.WorkloadEndpoints().Update(ctx, wep, options.SetOptions{})
}
return client.WorkloadEndpoints().Create(ctx, wep, options.SetOptions{})
}
// AddIPAM calls through to the configured IPAM plugin.
// It also contains IPAM plugin specific logic based on the configured plugin.
func AddIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) (*current.Result, error) {
// Check if we're configured to use the Azure IPAM plugin.
var an *azure.AzureNetwork
if conf.IPAM.Type == "azure-vnet-ipam" {
// Load the azure network configuration, if any exists. Then, use
// that configuration to mutate the config we'll pass to the IPAM plugin.
logger.Info("Configured to use Azure IPAM, check for subnet")
an = &azure.AzureNetwork{Name: conf.Name}
if err := an.Load(); err != nil {
return nil, err
}
if err := azure.MutateConfigAdd(args, *an); err != nil {
return nil, err
}
}
// Actually call the IPAM plugin.
logger.Debugf("Calling IPAM plugin %s", conf.IPAM.Type)
ipamResult, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
if err != nil {
return nil, err
}
logger.Debugf("IPAM plugin returned: %+v", ipamResult)
// Convert the IPAM result into the current version.
result, err := current.NewResultFromResult(ipamResult)
if err != nil {
return nil, err
}
if len(result.IPs) == 0 {
return nil, errors.New("IPAM plugin returned missing IP config")
}
// If we're using the Azure plugin, then write azure network and endpoint information here.
// We'll need this information on delete so we can clean up any allocated IPs.
if an != nil {
// Store the Azure network data so that we can access it on subsequent calls.
subnetPrefix := result.IPs[0].Address
subnetPrefix.IP = subnetPrefix.IP.Mask(subnetPrefix.Mask)
an.Subnets = []string{subnetPrefix.String()}
if err := an.Write(); err != nil {
return nil, err
}
logger.Infof("Stored azure subnet on disk: %s", subnetPrefix)
// Store the Azure endpoint data for use on delete.
var ips []string
for _, ip := range result.IPs {
ips = append(ips, ip.Address.IP.String())
}
ae := azure.AzureEndpoint{
Network: conf.Name,
ContainerID: args.ContainerID,
Interface: args.IfName,
Addresses: ips,
}
if err := ae.Write(); err != nil {
return nil, err
}
}
return result, nil
}
// DeleteIPAM calls IPAM plugin to release the IP address.
// It also contains IPAM plugin specific logic based on the configured plugin,
// and is the logical counterpart to AddIPAM.
func DeleteIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) error {
logger.Info("Calico CNI releasing IP address")
logger.WithFields(logrus.Fields{"paths": os.Getenv("CNI_PATH"),
"type": conf.IPAM.Type}).Debug("Looking for IPAM plugin in paths")
var ae *azure.AzureEndpoint
if conf.IPAM.Type == "host-local" {
// We need to replace "usePodCidr" with a valid, but dummy podCidr string with "host-local" IPAM.
// host-local IPAM releases the IP by ContainerID, so podCidr isn't really used to release the IP.
// It just needs a valid CIDR, but it doesn't have to be the CIDR associated with the host.
const dummyPodCidr = "0.0.0.0/0"
var stdinData map[string]interface{}
err := json.Unmarshal(args.StdinData, &stdinData)
if err != nil {
return err
}
logger.WithField("podCidr", dummyPodCidr).Info("Using a dummy podCidr to release the IP")
getDummyPodCIDR := func() (string, error) {
return dummyPodCidr, nil
}
err = ReplaceHostLocalIPAMPodCIDRs(logger, stdinData, getDummyPodCIDR)
if err != nil {
return err
}
args.StdinData, err = json.Marshal(stdinData)
if err != nil {
return err
}
logger.Debug("Updated stdin data for Delete Cmd")
} else if conf.IPAM.Type == "azure-vnet-ipam" {
// The azure-vnet-ipam plugin expects two values to be passed in the CNI config in order to
// successfully clean up: ipAddress and subnet. Populate these based on data stored to disk.
logger.Info("Configured to use Azure IPAM, load network and endpoint")
an := &azure.AzureNetwork{Name: conf.Name}
if err := an.Load(); err != nil {
return err
}
ae = &azure.AzureEndpoint{Network: conf.Name, ContainerID: args.ContainerID, Interface: args.IfName}
if err := ae.Load(); err != nil {
return err
}
if len(ae.Addresses) == 0 {
// If we couldn't find this endpoint, then simply return successfully.
logger.WithField("AzureEndpoint", ae).Infof("No endpoint addresses, skip IPAM release")
return nil
}
if err := azure.MutateConfigDel(args, *an, *ae); err != nil {
return err
}
}
// Call the CNI plugin.
err := ipam.ExecDel(conf.IPAM.Type, args.StdinData)
if err != nil {
logger.Error(err)
} else if ae != nil {
// Clean up any Azure endpoint data now that we've claened up the IPAM allocation.
// However, don't do this if the IPAM release failed - otherwise we'll lose information we need
// in order to release the address.
if err := ae.Delete(); err != nil {
logger.WithError(err).Errorf("Error deleting Azure endpoint")
}
}
return err
}
// ReplaceHostLocalIPAMPodCIDRs extracts the host-local IPAM config section and replaces our special-case "usePodCidr"
// subnet value with pod CIDR retrieved by the passed-in getPodCIDR function. Typically, the passed-in function
// would access the datastore to retrieve the podCIDR. However, for tear-down we use a dummy value that returns
// 0.0.0.0/0.
//
// To make sure that unknown fields are round-tripped, we manipulate the JSON as maps and slices rather than by
// unmarshaling it into a struct. The structure of the JSON is as follows; we support replacing usePodCidr in
// either the "ipam" dict or its nested ranges section:
//
// {
// "cniVersion": "%s",
// ...
// "ipam": {
// "type": "host-local",
// "subnet": "usePodCidr",
// "ranges": [
// [
// {
// "subnet": "usePodCidr"
// }
// ]
// ]
// }
// ...
// }
func ReplaceHostLocalIPAMPodCIDRs(logger *logrus.Entry, stdinData map[string]interface{}, getPodCIDR func() (string, error)) error {
ipamData, ok := stdinData["ipam"].(map[string]interface{})
if !ok {
return fmt.Errorf("failed to parse host-local IPAM data; was expecting a dict, not: %v", stdinData["ipam"])
}
// Older versions of host-local IPAM store a single subnet in the top-level IPAM dict.
err := replaceHostLocalIPAMPodCIDR(logger, ipamData, getPodCIDR)
if err != nil {
return err
}
// Newer versions store one or more subnets in the "ranges" list:
untypedRanges := ipamData["ranges"]
if untypedRanges != nil {
rangeSets, ok := untypedRanges.([]interface{})
if !ok {
return fmt.Errorf("failed to parse host-local IPAM ranges section; was expecting a list, not: %v",
ipamData["ranges"])
}
for _, urs := range rangeSets {
rs, ok := urs.([]interface{})
if !ok {
return fmt.Errorf("failed to parse host-local IPAM range set; was expecting a list, not: %v", rs)
}
for _, r := range rs {
err := replaceHostLocalIPAMPodCIDR(logger, r, getPodCIDR)
if err != nil {
return err
}
}
}
}
return nil
}
func replaceHostLocalIPAMPodCIDR(logger *logrus.Entry, rawIpamData interface{}, getPodCidr func() (string, error)) error {
logrus.WithField("ipamData", rawIpamData).Debug("Examining IPAM data for usePodCidr")
ipamData, ok := rawIpamData.(map[string]interface{})
if !ok {
return fmt.Errorf("failed to parse host-local IPAM data; was expecting a dict, not: %v", rawIpamData)
}
subnet, _ := ipamData["subnet"].(string)
if strings.EqualFold(subnet, "usePodCidr") {
logger.Info("Calico CNI fetching podCidr from Kubernetes")
podCidr, err := getPodCidr()
if err != nil {
logger.Info("Failed to getPodCidr")
return err
}
logger.WithField("podCidr", podCidr).Info("Fetched podCidr")
ipamData["subnet"] = podCidr
subnet = podCidr
logger.Infof("Calico CNI passing podCidr to host-local IPAM: %s", podCidr)
}
err := updateHostLocalIPAMDataForOS(subnet, ipamData)
if err != nil {
return err
}
return nil
}
// This function will update host-local IPAM data based on input from cni.conf
func UpdateHostLocalIPAMDataForWindows(subnet string, ipamData map[string]interface{}) error {
if len(subnet) == 0 {
return nil
}
//Checks whether the ip is valid or not
logrus.Info("Updating host-local IPAM configuration to reserve IPs for Windows bridge.")
ip, ipnet, err := net.ParseCIDR(subnet)
if err != nil {
return err
}
//process only if we have ipv4 subnet
if ip.To4() != nil {
//get Expected start and end range for given CIDR
expStartRange, expEndRange := getIPRanges(ip, ipnet)
//validate ranges given in cni.conf
rangeStart, _ := ipamData["rangeStart"].(string)
startRange, err := validateRangeOrSetDefault(rangeStart, expStartRange, ipnet, true)
if err != nil {
return err
}
ipamData["rangeStart"] = startRange
rangeEnd, _ := ipamData["rangeEnd"].(string)
endRange, err := validateRangeOrSetDefault(rangeEnd, expEndRange, ipnet, false)
if err != nil {
return err
}
ipamData["rangeEnd"] = endRange
}
return nil
}
func getIPRanges(ip net.IP, ipnet *net.IPNet) (string, string) {
ip = ip.To4()
// Mask the address
ip.Mask(ipnet.Mask)
// OR in the start address.
ip[len(ip)-1] |= 3
startRange := ip.String()
// Now find the broadbcast address and decrement by 1 to get endRange
for i := 0; i < len(ip); i++ {
ip[i] |= (^ipnet.Mask[i])
}
ip[len(ip)-1] -= 1
endRange := ip.String()
return startRange, endRange
}
func validateStartRange(startRange net.IP, expStartRange net.IP) (net.IP, error) {
//check if we have ipv4 ip address
startRange = startRange.To4()
expStartRange = expStartRange.To4()
if startRange == nil || expStartRange == nil {
return nil, fmt.Errorf("Invalid ip address")
}
if bytes.Compare([]byte(startRange), []byte(expStartRange)) < 0 {
//if ip is not in given range,return default
return expStartRange, nil
}
return startRange, nil
}
func validateEndRange(endRange net.IP, expEndRange net.IP) (net.IP, error) {
//check if we have ipv4 ip address
endRange = endRange.To4()
expEndRange = expEndRange.To4()
if endRange == nil || expEndRange == nil {
return nil, fmt.Errorf("Invalid ip address")
}
if bytes.Compare([]byte(endRange), []byte(expEndRange)) > 0 {
//if ip is not in given range,return default
return expEndRange, nil
}
return endRange, nil
}
// This function will validate and return an ip within expected start/end range
func validateRangeOrSetDefault(rangeData string, expRange string, ipnet *net.IPNet, isRangeStart bool) (string, error) {
var parsedIP *cnet.IP
var expRangeIP *cnet.IP
var ip net.IP
//Parse IP and convert into 4 bytes address
if expRangeIP = cnet.ParseIP(expRange); expRangeIP == nil {
return "", fmt.Errorf("expRange contains invalid ip")
}
if len(rangeData) > 0 {
//Checks whether the ip is valid or not
if parsedIP = cnet.ParseIP(rangeData); parsedIP == nil {
return "", fmt.Errorf("range contains invalid ip")
} else if ipnet.Contains(parsedIP.IP) { //Checks whether the ip belongs to subnet
if isRangeStart {
//check if Startrange should be in expected limit
ip, _ = validateStartRange(parsedIP.IP, expRangeIP.IP)
} else {
//check if Endrange exceeds expected limit
ip, _ = validateEndRange(parsedIP.IP, expRangeIP.IP)
}
return ip.String(), nil
}
}
//return default range
return expRangeIP.IP.String(), nil
}
// ValidateNetworkName checks that the network name meets felix's expectations
func ValidateNetworkName(name string) error {
matched, err := regexp.MatchString(`^[a-zA-Z0-9_\.\-]+$`, name)
if err != nil {
return err
}
if !matched {
return errors.New("invalid characters detected in the given network name. " +
"Only letters a-z, numbers 0-9, and symbols _.- are supported")
}
return nil
}
// SanitizeMesosLabel converts a string from a valid mesos label to a valid Calico label.
// Mesos labels have no restriction outside of being unicode.
func SanitizeMesosLabel(s string) string {
// Inspired by:
// https://github.com/projectcalico/libcalico-go/blob/2ff29bed865c4b364d4fcf1ad214b2bd8d9b4afa/lib/upgrade/converters/names.go#L39-L58
invalidChar := regexp.MustCompile("[^-_.a-zA-Z0-9]+")
dotDashSeq := regexp.MustCompile("[.-]*[.][.-]*")
trailingLeadingDotsDashes := regexp.MustCompile("^[.-]*(.*?)[.-]*$")
// - Convert [/] to .
s = strings.Replace(s, "/", ".", -1)
// - Convert any other invalid chars
s = invalidChar.ReplaceAllString(s, "-")
// Convert any multi-byte sequence of [-.] with at least one [.] to a single .
s = dotDashSeq.ReplaceAllString(s, ".")
// Extract the trailing and leading dots and dashes. This should always match even if
// the matched substring is empty. The second item in the returned submatch
// slice is the captured match group.
submatches := trailingLeadingDotsDashes.FindStringSubmatch(s)
s = submatches[1]
return s
}
// AddIgnoreUnknownArgs appends the 'IgnoreUnknown=1' option to CNI_ARGS before calling the IPAM plugin. Otherwise, it will
// complain about the Kubernetes arguments. See https://github.com/kubernetes/kubernetes/pull/24983
func AddIgnoreUnknownArgs() error {
cniArgs := "IgnoreUnknown=1"
if os.Getenv("CNI_ARGS") != "" {
cniArgs = fmt.Sprintf("%s;%s", cniArgs, os.Getenv("CNI_ARGS"))
}
return os.Setenv("CNI_ARGS", cniArgs)
}
// CreateResultFromEndpoint takes a WorkloadEndpoint, extracts IP information
// and populates that into a CNI Result.
func CreateResultFromEndpoint(wep *api.WorkloadEndpoint) (*current.Result, error) {
result := ¤t.Result{}
for _, v := range wep.Spec.IPNetworks {
parsedIPConfig := current.IPConfig{}
ipAddr, ipNet, err := net.ParseCIDR(v)
if err != nil {
return nil, err
}
parsedIPConfig.Address = *ipNet
if ipAddr.To4() != nil {
parsedIPConfig.Version = "4"
} else {
parsedIPConfig.Version = "6"
}
result.IPs = append(result.IPs, &parsedIPConfig)
}
return result, nil
}
// PopulateEndpointNets takes a WorkloadEndpoint and a CNI Result, extracts IP address and mask
// and populates that information into the WorkloadEndpoint.
func PopulateEndpointNets(wep *api.WorkloadEndpoint, result *current.Result) error {
var copyIpNet net.IPNet
if len(result.IPs) == 0 {
return errors.New("IPAM plugin did not return any IP addresses")
}
for _, ipNet := range result.IPs {
copyIpNet = net.IPNet{IP: ipNet.Address.IP, Mask: ipNet.Address.Mask}
if ipNet.Version == "4" {
copyIpNet.Mask = net.CIDRMask(32, 32)
} else {
copyIpNet.Mask = net.CIDRMask(128, 128)
}
wep.Spec.IPNetworks = append(wep.Spec.IPNetworks, copyIpNet.String())
}
return nil
}
type WEPIdentifiers struct {
Namespace string
WEPName string
names.WorkloadEndpointIdentifiers
}
// GetIdentifiers takes CNI command arguments, and extracts identifiers i.e. pod name, pod namespace,
// container ID, endpoint(container interface name) and orchestratorID based on the orchestrator.
func GetIdentifiers(args *skel.CmdArgs, nodename string) (*WEPIdentifiers, error) {
// Determine if running under k8s by checking the CNI args
k8sArgs := types.K8sArgs{}
if err := cnitypes.LoadArgs(args.Args, &k8sArgs); err != nil {
return nil, err
}
logrus.Debugf("Getting WEP identifiers with arguments: %s, for node %s", args.Args, nodename)
logrus.Debugf("Loaded k8s arguments: %v", k8sArgs)
epIDs := WEPIdentifiers{}
epIDs.ContainerID = args.ContainerID
epIDs.Node = nodename
epIDs.Endpoint = args.IfName
// Check if the workload is running under Kubernetes.
if string(k8sArgs.K8S_POD_NAMESPACE) != "" && string(k8sArgs.K8S_POD_NAME) != "" {
epIDs.Orchestrator = "k8s"
epIDs.Pod = string(k8sArgs.K8S_POD_NAME)
epIDs.Namespace = string(k8sArgs.K8S_POD_NAMESPACE)
} else {
epIDs.Orchestrator = "cni"
epIDs.Pod = ""
// For any non-k8s orchestrator we set the namespace to default.
epIDs.Namespace = "default"
// Warning: CNITestArgs is used for test purpose only and subject to change without prior notice.
CNITestArgs := types.CNITestArgs{}
if err := cnitypes.LoadArgs(args.Args, &CNITestArgs); err == nil {
// Set namespace with the value passed by CNI test args.
if string(CNITestArgs.CNI_TEST_NAMESPACE) != "" {
epIDs.Namespace = string(CNITestArgs.CNI_TEST_NAMESPACE)
}
}
}
return &epIDs, nil
}
func GetHandleID(netName, containerID, workload string) string {
handleID := fmt.Sprintf("%s.%s", netName, containerID)
logrus.WithFields(logrus.Fields{
"HandleID": handleID,
"Network": netName,
"Workload": workload,
"ContainerID": containerID,
}).Debug("Generated IPAM handle")
return handleID
}
func CreateClient(conf types.NetConf) (client.Interface, error) {
if err := ValidateNetworkName(conf.Name); err != nil {
return nil, err
}
// Use the config file to override environment variables.
// These variables will be loaded into the client config.
if conf.EtcdAuthority != "" {
if err := os.Setenv("ETCD_AUTHORITY", conf.EtcdAuthority); err != nil {
return nil, err
}
}
if conf.EtcdEndpoints != "" {
if err := os.Setenv("ETCD_ENDPOINTS", conf.EtcdEndpoints); err != nil {
return nil, err
}
}
if conf.EtcdDiscoverySrv != "" {
if err := os.Setenv("ETCD_DISCOVERY_SRV", conf.EtcdDiscoverySrv); err != nil {
return nil, err
}
}
if conf.EtcdScheme != "" {
if err := os.Setenv("ETCD_SCHEME", conf.EtcdScheme); err != nil {
return nil, err
}
}
if conf.EtcdKeyFile != "" {
if err := os.Setenv("ETCD_KEY_FILE", conf.EtcdKeyFile); err != nil {
return nil, err
}
}
if conf.EtcdCertFile != "" {
if err := os.Setenv("ETCD_CERT_FILE", conf.EtcdCertFile); err != nil {
return nil, err
}
}
if conf.EtcdCaCertFile != "" {
if err := os.Setenv("ETCD_CA_CERT_FILE", conf.EtcdCaCertFile); err != nil {
return nil, err
}
}
if conf.DatastoreType != "" {
if err := os.Setenv("DATASTORE_TYPE", conf.DatastoreType); err != nil {
return nil, err
}
}
// Set Kubernetes specific variables for use with the Kubernetes libcalico backend.
if conf.Kubernetes.Kubeconfig != "" {
if err := os.Setenv("KUBECONFIG", conf.Kubernetes.Kubeconfig); err != nil {
return nil, err
}
}
if conf.Kubernetes.K8sAPIRoot != "" {
if err := os.Setenv("K8S_API_ENDPOINT", conf.Kubernetes.K8sAPIRoot); err != nil {
return nil, err
}
}
if conf.Policy.K8sAuthToken != "" {
if err := os.Setenv("K8S_API_TOKEN", conf.Policy.K8sAuthToken); err != nil {
return nil, err
}
}
// Load the client config from the current environment.
clientConfig, err := apiconfig.LoadClientConfig("")
if err != nil {
return nil, err
}
// Create a new client.
calicoClient, err := client.New(*clientConfig)
if err != nil {
return nil, err
}
return calicoClient, nil
}
// ReleaseIPAllocation is called to cleanup IPAM allocations if something goes wrong during
// CNI ADD execution. It forces the CNI_COMMAND to be DEL.
func ReleaseIPAllocation(logger *logrus.Entry, conf types.NetConf, args *skel.CmdArgs) {
logger.Info("Cleaning up IP allocations for failed ADD")
if err := os.Setenv("CNI_COMMAND", "DEL"); err != nil {
// Failed to set CNI_COMMAND to DEL.
logger.Warning("Failed to set CNI_COMMAND=DEL")
} else {
if err := DeleteIPAM(conf, args, logger); err != nil {
// Failed to cleanup the IP allocation.
logger.Warning("Failed to clean up IP allocations for failed ADD")
}
}
}
// Set up logging for both Calico and libcalico using the provided log level,
func ConfigureLogging(conf types.NetConf) {
if strings.EqualFold(conf.LogLevel, "debug") {
logrus.SetLevel(logrus.DebugLevel)
} else if strings.EqualFold(conf.LogLevel, "info") {
logrus.SetLevel(logrus.InfoLevel)
} else if strings.EqualFold(conf.LogLevel, "error") {
logrus.SetLevel(logrus.ErrorLevel)
} else {
// Default level
logrus.SetLevel(logrus.InfoLevel)
}
writers := []io.Writer{os.Stderr}
// Set the log output to write to a log file if specified.
if conf.LogFilePath != "" {
// Create the path for the log file if it does not exist
err := os.MkdirAll(filepath.Dir(conf.LogFilePath), 0755)
if err != nil {
logrus.WithError(err).Errorf("Failed to create path for CNI log file: %v", filepath.Dir(conf.LogFilePath))
}
// Create file logger with log file rotation.
fileLogger := &lumberjack.Logger{
Filename: conf.LogFilePath,
MaxSize: 100,
MaxAge: 30,
MaxBackups: 10,
}
// Set the max size if exists. Defaults to 100 MB.
if conf.LogFileMaxSize != 0 {
fileLogger.MaxSize = conf.LogFileMaxSize
}
// Set the max time in days to retain a log file before it is cleaned up. Defaults to 30 days.
if conf.LogFileMaxAge != 0 {
fileLogger.MaxAge = conf.LogFileMaxAge
}
// Set the max number of log files to retain before they are cleaned up. Defaults to 10.
if conf.LogFileMaxCount != 0 {
fileLogger.MaxBackups = conf.LogFileMaxCount
}
writers = append(writers, fileLogger)
}
mw := io.MultiWriter(writers...)
logrus.SetOutput(mw)
}
// ResolvePools takes an array of CIDRs or IP Pool names and resolves it to a slice of pool CIDRs.
func ResolvePools(ctx context.Context, c client.Interface, pools []string, isv4 bool) ([]cnet.IPNet, error) {
// First, query all IP pools. We need these so we can resolve names to CIDRs.
pl, err := c.IPPools().List(ctx, options.ListOptions{})
if err != nil {
return nil, err
}
// Iterate through the provided pools. If it parses as a CIDR, just use that.
// If it does not parse as a CIDR, then attempt to lookup an IP pool with a matching name.
result := []cnet.IPNet{}
for _, p := range pools {
_, cidr, err := net.ParseCIDR(p)
if err != nil {
// Didn't parse as a CIDR - check if it's the name
// of a configured IP pool.
for _, ipp := range pl.Items {
if ipp.Name == p {
// Found a match. Use the CIDR from the matching pool.
_, cidr, err = net.ParseCIDR(ipp.Spec.CIDR)
if err != nil {
return nil, fmt.Errorf("failed to parse IP pool cidr: %s", err)
}
logrus.Infof("Resolved pool name %s to cidr %s", ipp.Name, cidr)
}
}
if cidr == nil {
// Unable to resolve this pool to a CIDR - return an error.
return nil, fmt.Errorf("error parsing pool %q: %s", p, err)
}
}
ip := cidr.IP
if isv4 && ip.To4() == nil {
return nil, fmt.Errorf("%q isn't a IPv4 address", ip)
}
if !isv4 && ip.To4() != nil {
return nil, fmt.Errorf("%q isn't a IPv6 address", ip)
}
result = append(result, cnet.IPNet{IPNet: *cidr})
}
return result, nil
}