diff --git a/go.mod b/go.mod index 7632886d6e..82c4bd51ee 100644 --- a/go.mod +++ b/go.mod @@ -75,6 +75,7 @@ require github.com/google/go-cmp v0.6.0 // indirect require k8s.io/component-base v0.29.4 // indirect require ( + github.com/hashicorp/go-multierror v1.1.1 go.uber.org/automaxprocs v1.5.3 k8s.io/kubectl v0.26.3 ) @@ -113,6 +114,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.13 // indirect diff --git a/go.sum b/go.sum index 21a31b13c5..baf749f77a 100644 --- a/go.sum +++ b/go.sum @@ -341,6 +341,11 @@ github.com/grafana/pyroscope-go v1.0.2/go.mod h1:bShDKsVZdzxq+Ol6no0JKigU9y5FTWU github.com/grafana/pyroscope-go/godeltaprof v0.1.3 h1:eunWpv1B3Z7ZK9o4499EmQGlY+CsDmSZ4FbxjRx37uk= github.com/grafana/pyroscope-go/godeltaprof v0.1.3/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= diff --git a/test/e2e/common/constant.go b/test/e2e/common/constant.go index 264b04c029..5bdd5726fc 100644 --- a/test/e2e/common/constant.go +++ b/test/e2e/common/constant.go @@ -32,6 +32,8 @@ const ( BatchCreateTimeout = time.Minute * 5 KdoctorCheckTime = time.Minute * 10 SpiderSyncMultusTime = time.Minute * 2 + InformerSyncStatusTime = time.Second * 30 + KDoctorRunTimeout = time.Minute * 10 ) var ForcedWaitingTime = time.Second diff --git a/test/e2e/common/node.go b/test/e2e/common/node.go index 3cd58cd993..bd7e85b7f9 100644 --- a/test/e2e/common/node.go +++ b/test/e2e/common/node.go @@ -8,6 +8,7 @@ import ( "fmt" "os/exec" + "github.com/hashicorp/go-multierror" . "github.com/onsi/ginkgo/v2" e2e "github.com/spidernet-io/e2eframework/framework" corev1 "k8s.io/api/core/v1" @@ -55,3 +56,36 @@ func RestartNodeUntilClusterReady(ctx context.Context, frame *e2e.Framework, nod GinkgoWriter.Println("Check that the status of all Pods in the cluster is running") return nil } + +func GetNodeNetworkInfo(ctx context.Context, frame *e2e.Framework, nodeList []string) error { + var jobResult *multierror.Error + for _, node := range nodeList { + GinkgoWriter.Printf("=============== Check the network information of the node %v ============== \n", node) + commands := []string{ + "ip a", + "ip link show", + "ip n", + "ip -6 n", + "ip rule", + "ip -6 rule", + "ip route", + "ip route show table 100", + "ip route show table 101", + "ip route show table 500", + "ip -6 route", + "ip -6 route show table 100", + "ip -6 route show table 101", + "ip -6 route show table 500", + } + + for _, command := range commands { + GinkgoWriter.Printf("--------------- execute %v in node: %v ------------ \n", command, node) + out, err := frame.DockerExecCommand(ctx, node, command) + if err != nil { + jobResult = multierror.Append(jobResult, fmt.Errorf("node %v: command '%v' failed with error: %w, output: %s", node, command, err, out)) + } + } + } + + return jobResult.ErrorOrNil() +} diff --git a/test/e2e/common/pod.go b/test/e2e/common/pod.go index de3a6853fa..11e138820a 100644 --- a/test/e2e/common/pod.go +++ b/test/e2e/common/pod.go @@ -9,7 +9,9 @@ import ( "time" "github.com/spidernet-io/spiderpool/pkg/constant" + "github.com/spidernet-io/spiderpool/pkg/utils/retry" + "github.com/hashicorp/go-multierror" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" e2e "github.com/spidernet-io/e2eframework/framework" @@ -48,7 +50,14 @@ func GenerateExamplePodYaml(podName, namespace string) *corev1.Pod { func CreatePodUntilReady(frame *e2e.Framework, podYaml *corev1.Pod, podName, namespace string, waitPodStartTimeout time.Duration) (pod *corev1.Pod, podIPv4, podIPv6 string) { // create pod GinkgoWriter.Printf("create pod %v/%v \n", namespace, podName) - err := frame.CreatePod(podYaml) + err := retry.RetryOnConflictWithContext(context.Background(), retry.DefaultBackoff, func(ctx context.Context) error { + err := frame.CreatePod(podYaml) + if err != nil { + GinkgoLogr.Error(fmt.Errorf("failed to create pod %v/%v, error: %v", namespace, podName, err), "Failed") + return err + } + return nil + }) Expect(err).NotTo(HaveOccurred(), "failed to create pod") // wait for pod ip @@ -138,3 +147,34 @@ func ValidatePodIPConflict(podList *corev1.PodList) error { } return nil } + +func GetPodNetworkInfo(ctx context.Context, frame *e2e.Framework, podList *corev1.PodList) error { + var jobResult *multierror.Error + for _, pod := range podList.Items { + GinkgoWriter.Printf("=============== Check the network information of the pod %v/%v ============== \n", pod.Namespace, pod.Name) + commands := []string{ + "ip a", + "ip link show", + "ip n", + "ip -6 n", + "ip rule", + "ip -6 rule", + "ip route", + "ip route show table 100", + "ip route show table 101", + "ip -6 route", + "ip -6 route show table 100", + "ip -6 route show table 101", + } + + for _, command := range commands { + GinkgoWriter.Printf("--------------- execute %v in pod: %v/%v on node: %v ------------ \n", command, pod.Namespace, pod.Name, pod.Spec.NodeName) + out, err := frame.ExecCommandInPod(pod.Name, pod.Namespace, command, ctx) + if err != nil { + jobResult = multierror.Append(jobResult, fmt.Errorf("pod %v/%v: command '%v' failed with error: %w, output: %s", pod.Namespace, pod.Name, command, err, out)) + } + } + } + + return jobResult.ErrorOrNil() +} diff --git a/test/e2e/common/spiderpool.go b/test/e2e/common/spiderpool.go index c6f227ac56..896a8a76af 100644 --- a/test/e2e/common/spiderpool.go +++ b/test/e2e/common/spiderpool.go @@ -470,7 +470,7 @@ func DeleteIPPoolUntilFinish(f *frame.Framework, poolName string, ctx context.Co default: _, err := GetIppoolByName(f, poolName) if err != nil { - GinkgoWriter.Printf("IPPool '%s' has been removed,error: %v", poolName, err) + GinkgoWriter.Printf("IPPool '%s' has been removed, error: %v", poolName, err) return nil } time.Sleep(ForcedWaitingTime) @@ -562,7 +562,7 @@ func WaitWorkloadDeleteUntilFinish(ctx context.Context, f *frame.Framework, name _, err := GetWorkloadByName(f, namespace, name) if err != nil { if api_errors.IsNotFound(err) { - GinkgoWriter.Printf("workload '%s/%s' has been removed,error: %v", namespace, name, err) + GinkgoWriter.Printf("workload '%s/%s' has been removed, error: %v", namespace, name, err) return nil } return err @@ -877,14 +877,15 @@ func CheckIppoolSanity(f *frame.Framework, poolName string) error { podYaml, err := f.GetPod(podName, podNS) if err != nil { if api_errors.IsNotFound(err) { - GinkgoLogr.Error(fmt.Errorf("pod %s/%s does not exist", podNS, podName), "Failed") + GinkgoLogr.Error(fmt.Errorf("the pod %s/%s in ippool %s, but pod does not exist in kubernetes", podNS, podName, poolName), "Failed") + isSanity = false + continue } else { return fmt.Errorf("failed to get pod %s/%s, error: %v", podNS, podName, err) } } - podNetworkIPs, err := ParsePodNetworkAnnotation(f, podYaml) - if nil != err { + if err != nil { return fmt.Errorf("failed to parse pod %s/%s network annotation \n pod yaml %v, \n error: %v ", podNS, podName, podYaml, err) } @@ -917,9 +918,11 @@ func CheckIppoolSanity(f *frame.Framework, poolName string) error { wep, err := GetWorkloadByName(f, podYaml.Namespace, podYaml.Name) if err != nil { if api_errors.IsNotFound(err) { - GinkgoLogr.Error(fmt.Errorf("endpoint %s/%s dose not exist", podYaml.Namespace, podYaml.Name), "Failed") + GinkgoLogr.Error(fmt.Errorf("pod %s/%s exists in ippool %s, but endpoint does not exist", podYaml.Namespace, podYaml.Name, poolName), "Failed") + isSanity = false + continue } - return fmt.Errorf("failed to get endpoint %s/%s, error %v", podYaml.Namespace, podYaml.Name, err) + return fmt.Errorf("pod %s/%s exists in ippool %s, but failed to get endpoint, error %v", podYaml.Namespace, podYaml.Name, poolName, err) } podUsedIPs := convert.GroupIPAllocationDetails(wep.Status.Current.UID, wep.Status.Current.IPs) @@ -941,24 +944,50 @@ func CheckIppoolSanity(f *frame.Framework, poolName string) error { } } - if *ippool.Status.AllocatedIPCount > *ippool.Status.TotalIPCount { - GinkgoWriter.Printf( - "allocated IP count (%v) exceeds total IP count (%v) \n", - *ippool.Status.AllocatedIPCount, *ippool.Status.TotalIPCount, - ) - isSanity = false - } + // The status of IPPool is automatically synchronized by the IPPool informer based on the events it receives. + // In the CI environment, the creation of IPPools happens very quickly, and their health checks are performed promptly. + // When checking the TotalIPCount status, if the spiderpool-controller undergoes a leader election or the informer has not yet completed synchronization, + // the IPPool status TotalIPCount may be nil. This can lead to a panic. + // In such cases, try waiting for the informer to complete status synchronization before checking the robustness of the IPPool. + ctx, cancel := context.WithTimeout(context.Background(), InformerSyncStatusTime) + defer cancel() + for { + select { + case <-ctx.Done(): + return fmt.Errorf("waiting for informer to synchronize IPPool %s status timed out", poolName) + default: + if ippool.Status.AllocatedIPCount == nil || ippool.Status.TotalIPCount == nil { + GinkgoLogr.Error(fmt.Errorf("IPPool %s has nil status fields, retrying", poolName), "Failed") + ippool, err = GetIppoolByName(f, poolName) + if err != nil { + if api_errors.IsNotFound(err) { + return fmt.Errorf("ippool %s does not exist", poolName) + } + return fmt.Errorf("failed to get ippool %s, error %v", poolName, err) + } + time.Sleep(ForcedWaitingTime) + continue + } - // Ensure that the IP pool's reported usage matches the actual usage - if actualIPUsageCount != int(*ippool.Status.AllocatedIPCount) { - GinkgoWriter.Printf("IPPool %s usage count mismatch: expected %d, got %d \n", poolName, actualIPUsageCount, *ippool.Status.AllocatedIPCount) - isSanity = false - } + if *ippool.Status.AllocatedIPCount > *ippool.Status.TotalIPCount { + GinkgoWriter.Printf( + "allocated IP count (%v) exceeds total IP count (%v) \n", + *ippool.Status.AllocatedIPCount, *ippool.Status.TotalIPCount, + ) + isSanity = false + } + // Ensure that the IP pool's reported usage matches the actual usage + if actualIPUsageCount != int(*ippool.Status.AllocatedIPCount) { + GinkgoWriter.Printf("IPPool %s usage count mismatch: expected %d, got %d \n", poolName, actualIPUsageCount, *ippool.Status.AllocatedIPCount) + isSanity = false + } - if !isSanity { - return fmt.Errorf("IPPool %s sanity check failed", poolName) - } + if !isSanity { + return fmt.Errorf("IPPool %s sanity check failed", poolName) + } - GinkgoWriter.Printf("Successfully checked IPPool %s sanity, IPPool record information is correct \n", poolName) - return nil + GinkgoWriter.Printf("Successfully checked IPPool %s sanity, IPPool record information is correct \n", poolName) + return nil + } + } } diff --git a/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_suite_test.go b/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_suite_test.go index 49913ab443..f2f278d689 100644 --- a/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_suite_test.go +++ b/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_suite_test.go @@ -33,7 +33,6 @@ var ( request *kdoctorV1beta1.NetHttpRequest condition *kdoctorV1beta1.NetSuccessCondition schedule *kdoctorV1beta1.SchedulePlan - run = true ) var _ = BeforeSuite(func() { diff --git a/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_test.go b/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_test.go index 5a763dfff6..130a40b097 100644 --- a/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_test.go +++ b/test/e2e/coordinator/macvlan-underlay-one/macvlan_underlay_one_test.go @@ -12,8 +12,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spidernet-io/e2eframework/tools" - "github.com/spidernet-io/spiderpool/test/e2e/common" + corev1 "k8s.io/api/core/v1" + api_errors "k8s.io/apimachinery/pkg/api/errors" apitypes "k8s.io/apimachinery/pkg/types" + + "github.com/spidernet-io/spiderpool/test/e2e/common" ) var _ = Describe("MacvlanUnderlayOne", Serial, Label("underlay", "one-interface", "coordinator"), func() { @@ -43,7 +46,7 @@ var _ = Describe("MacvlanUnderlayOne", Serial, Label("underlay", "one-interface" task.Spec.AgentSpec = netreach }) - It("kdoctor connectivity should be succeed", Label("C00001"), Label("ebpf"), func() { + It("kdoctor connectivity should be succeed", Label("C00001", "C00013"), Label("ebpf"), func() { enable := true disable := false @@ -52,9 +55,12 @@ var _ = Describe("MacvlanUnderlayOne", Serial, Label("underlay", "one-interface" GinkgoWriter.Printf("Start the netreach task: %v", task.Name) // Schedule - crontab := "0 1" + crontab := "1 1" schedule.Schedule = &crontab - schedule.RoundNumber = 1 + // The sporadic test failures in kdoctor were attempted to be reproduced, but couldn't be. + // By leveraging kdoctor's loop testing, if a failure occurs in the first test, + // check whether it also fails on the second attempt. + schedule.RoundNumber = 3 schedule.RoundTimeoutMinute = 1 task.Spec.Schedule = schedule @@ -72,69 +78,98 @@ var _ = Describe("MacvlanUnderlayOne", Serial, Label("underlay", "one-interface" // request request.DurationInSecond = 10 - request.QPS = 3 - request.PerRequestTimeoutInMS = 15000 + request.QPS = 1 + request.PerRequestTimeoutInMS = 7000 task.Spec.Request = request // success condition condition.SuccessRate = &successRate condition.MeanAccessDelayInMs = &delayMs task.Spec.SuccessCondition = condition - taskCopy := task - GinkgoWriter.Printf("kdoctor task: %+v \n", task) err := frame.CreateResource(task) - Expect(err).NotTo(HaveOccurred(), " kdoctor nethttp crd create failed") + Expect(err).NotTo(HaveOccurred(), "failed to create kdoctor task") + GinkgoWriter.Printf("succeeded to create kdoctor task: %+v \n", task) + + // update the kdoctor service to use corev1.ServiceExternalTrafficPolicyLocal + if frame.Info.IpV4Enabled { + kdoctorIPv4ServiceName := fmt.Sprintf("%s-%s-ipv4", "kdoctor-netreach", task.Name) + var kdoctorIPv4Service *corev1.Service + Eventually(func() bool { + kdoctorIPv4Service, err = frame.GetService(kdoctorIPv4ServiceName, "kube-system") + if api_errors.IsNotFound(err) { + return false + } + if err != nil { + return false + } + return true + }).WithTimeout(time.Minute).WithPolling(time.Second * 3).Should(BeTrue()) + kdoctorIPv4Service.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyLocal + kdoctorIPv4Service.Spec.Type = corev1.ServiceTypeNodePort + Expect(frame.UpdateResource(kdoctorIPv4Service)).NotTo(HaveOccurred()) + } + if frame.Info.IpV6Enabled { + kdoctorIPv6ServiceName := fmt.Sprintf("%s-%s-ipv6", "kdoctor-netreach", task.Name) + var kdoctorIPv6Service *corev1.Service + Eventually(func() bool { + kdoctorIPv6Service, err = frame.GetService(kdoctorIPv6ServiceName, "kube-system") + if api_errors.IsNotFound(err) { + return false + } + if err != nil { + return false + } + return true + }).WithTimeout(time.Minute).WithPolling(time.Second * 3).Should(BeTrue()) + kdoctorIPv6Service.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyLocal + kdoctorIPv6Service.Spec.Type = corev1.ServiceTypeNodePort + Expect(frame.UpdateResource(kdoctorIPv6Service)).NotTo(HaveOccurred()) + } - err = frame.GetResource(apitypes.NamespacedName{Name: name}, taskCopy) - Expect(err).NotTo(HaveOccurred(), " kdoctor nethttp crd get failed") - ctx, cancel := context.WithTimeout(context.Background(), time.Second*60*5) + // waiting for kdoctor task to finish + ctx, cancel := context.WithTimeout(context.Background(), common.KDoctorRunTimeout) defer cancel() - - var err1 = errors.New("error has occurred") - - for run { + for { select { case <-ctx.Done(): - run = false - Expect(errors.New("wait nethttp test timeout")).NotTo(HaveOccurred(), " running kdoctor task timeout") + Expect(errors.New("timeout waiting for kdoctor task to finish")).NotTo(HaveOccurred()) default: + taskCopy := task err = frame.GetResource(apitypes.NamespacedName{Name: name}, taskCopy) - Expect(err).NotTo(HaveOccurred(), " kdoctor nethttp crd get failed") - - if taskCopy.Status.Finish == true { - command := fmt.Sprintf("get netreaches.kdoctor.io %s -oyaml", taskCopy.Name) - netreachesLog, _ := frame.ExecKubectl(command, ctx) - GinkgoWriter.Printf("kdoctor's netreaches execution result %+v \n", string(netreachesLog)) - - for _, v := range taskCopy.Status.History { - if v.Status == "succeed" { - err1 = nil + Expect(err).NotTo(HaveOccurred(), "Failed to get kdoctor task") + if taskCopy.Status.Finish { + roundFailed := false + for _, t := range taskCopy.Status.History { + // No configuration has been changed, The first round of the test is not considered a failure + if t.RoundNumber != 1 && t.Status == "failed" { + roundFailed = true + break } } - run = false - - ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second*30) - defer cancel1() - for { - select { - case <-ctx1.Done(): - Expect(errors.New("wait kdoctorreport timeout")).NotTo(HaveOccurred(), "failed to run kdoctor task and wait kdoctorreport timeout") - default: - command = fmt.Sprintf("get kdoctorreport %s -oyaml", taskCopy.Name) - kdoctorreportLog, err := frame.ExecKubectl(command, ctx) - if err != nil { - time.Sleep(common.ForcedWaitingTime) - continue - } - GinkgoWriter.Printf("kdoctor's kdoctorreport execution result %+v \n", string(kdoctorreportLog)) - } - break + if roundFailed { + Fail("kdoctor task is not successful") + } + return + } + for _, t := range taskCopy.Status.History { + // If the check is successful, exit directly. + if t.RoundNumber == 1 && t.Status == "succeed" { + GinkgoWriter.Println("succeed to run kdoctor task") + return + } + // If the check fails, we should collect the failed Pod network information as soon as possible + // If the first attempt failed but the second attempt succeeded, + // we collected network logs and compared the two attempts to see if there were any differences. + if t.Status == "failed" || (t.RoundNumber != 1 && t.Status == "succeed") { + GinkgoLogr.Error(fmt.Errorf("Failed to run kdoctor task, round %d, at time %s", t.RoundNumber, time.Now()), "Failed") + podList, err := frame.GetPodListByLabel(map[string]string{"app.kubernetes.io/name": taskCopy.Name}) + Expect(err).NotTo(HaveOccurred(), "Failed to get pod list by label") + Expect(common.GetPodNetworkInfo(ctx, frame, podList)).NotTo(HaveOccurred(), "Failed to get pod network info") + Expect(common.GetNodeNetworkInfo(ctx, frame, frame.Info.KindNodeList)).NotTo(HaveOccurred(), "Failed to get node network info") } } - time.Sleep(time.Second * 5) } } - Expect(err1).NotTo(HaveOccurred()) }) }) diff --git a/test/e2e/reliability/reliability_test.go b/test/e2e/reliability/reliability_test.go index 64ccdf2306..51969a999b 100644 --- a/test/e2e/reliability/reliability_test.go +++ b/test/e2e/reliability/reliability_test.go @@ -4,6 +4,7 @@ package reliability_test import ( "context" + "fmt" "sync" "time" @@ -14,6 +15,7 @@ import ( spiderpool "github.com/spidernet-io/spiderpool/pkg/k8s/apis/spiderpool.spidernet.io/v2beta1" "github.com/spidernet-io/spiderpool/test/e2e/common" corev1 "k8s.io/api/core/v1" + "k8s.io/kubectl/pkg/util/podutils" ) var _ = Describe("test reliability", Label("reliability"), Serial, func() { @@ -94,81 +96,96 @@ var _ = Describe("test reliability", Label("reliability"), Serial, func() { DescribeTable("reliability test table", func(componentName string, label map[string]string, startupTimeRequired time.Duration) { - // get component pod list - GinkgoWriter.Printf("get %v pod list \n", componentName) - podList, e := frame.GetPodListByLabel(label) - Expect(e).NotTo(HaveOccurred()) - Expect(podList.Items).NotTo(HaveLen(0)) - expectPodNum := len(podList.Items) - GinkgoWriter.Printf("the %v pod number is: %v \n", componentName, expectPodNum) - - // delete component pod - GinkgoWriter.Printf("now time: %s, restart %v %v pod \n", time.Now().Format(time.RFC3339Nano), expectPodNum, componentName) - podList, e = frame.DeletePodListUntilReady(podList, startupTimeRequired) - GinkgoWriter.Printf("pod %v recovery time: %s \n", componentName, time.Now().Format(time.RFC3339Nano)) - Expect(e).NotTo(HaveOccurred()) - Expect(podList).NotTo(BeNil()) - - // create pod when component is unstable - GinkgoWriter.Printf("create pod %v/%v when %v is unstable \n", namespace, podName, componentName) - podYaml := common.GenerateExamplePodYaml(podName, namespace) + componentPodList, err := frame.GetPodListByLabel(label) + Expect(err).NotTo(HaveOccurred(), "failed to get %v pod list", componentName) + expectPodNum := len(componentPodList.Items) + GinkgoWriter.Printf("succeeded to get %v pod list \n", componentName) + + // Define a set of daemonSets with Pods on each node to verify that the components on each node can provide services for the Pods. + dsName := "ds" + tools.RandomName() + dsYaml := common.GenerateExampleDaemonSetYaml(dsName, namespace) podIppoolAnnoStr := common.GeneratePodIPPoolAnnotations(frame, common.NIC1, globalDefaultV4IppoolList, globalDefaultV6IppoolList) - podYaml.Annotations = map[string]string{constant.AnnoPodIPPool: podIppoolAnnoStr} - e = frame.CreatePod(podYaml) - Expect(e).NotTo(HaveOccurred()) + dsYaml.Spec.Template.Annotations = map[string]string{constant.AnnoPodIPPool: podIppoolAnnoStr} - wg.Add(1) + // Concurrently delete components and create a new pod + wg.Add(2) go func() { defer GinkgoRecover() - // delete component pod - startT1 := time.Now() + defer wg.Done() GinkgoWriter.Printf("now time: %s, restart %v %v pod \n", time.Now().Format(time.RFC3339Nano), expectPodNum, componentName) - podList, e1 := frame.DeletePodListUntilReady(podList, startupTimeRequired) - GinkgoWriter.Printf("pod %v recovery time: %s \n", componentName, time.Now().Format(time.RFC3339Nano)) - Expect(e1).NotTo(HaveOccurred()) - Expect(podList).NotTo(BeNil()) - endT1 := time.Since(startT1) - GinkgoWriter.Printf("component restart until running time cost is:%v\n", endT1) - wg.Done() + err := frame.DeletePodList(componentPodList) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() error { + componentPodList, err := frame.GetPodListByLabel(label) + if err != nil { + return fmt.Errorf("failed to get component %v pod list", componentName) + } + if len(componentPodList.Items) != expectPodNum { + return fmt.Errorf("the number of component %s pod is not equal to expectPodNum %d", componentName, expectPodNum) + } + for _, pod := range componentPodList.Items { + if !podutils.IsPodReady(&pod) { + return fmt.Errorf("the pod %v is not ready", pod.Name) + } + } + + // Check webhook service ready after restarting the spiderpool-controller, Avoid affecting the creation of IPPool + if componentName == constant.SpiderpoolController { + ctx, cancel := context.WithTimeout(context.Background(), common.PodReStartTimeout) + defer cancel() + Expect(common.WaitWebhookReady(ctx, frame, common.WebhookPort)).NotTo(HaveOccurred()) + } + return nil + }).WithTimeout(common.PodReStartTimeout).WithPolling(time.Second * 3).Should(BeNil()) }() - if componentName == constant.SpiderpoolController { - // Check wbehook service ready after restarting the controller - ctx, cancel := context.WithTimeout(context.Background(), common.PodReStartTimeout) - defer cancel() - Expect(common.WaitWebhookReady(ctx, frame, common.WebhookPort)).NotTo(HaveOccurred()) - } + go func() { + defer GinkgoRecover() + defer wg.Done() + GinkgoWriter.Printf("create daemonSet %v/%v when %v is unstable \n", namespace, dsName, componentName) + err := frame.CreateDaemonSet(dsYaml) + Expect(err).NotTo(HaveOccurred()) - // Wait test Pod ready - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) - defer cancel() - pod, e := frame.WaitPodStarted(podName, namespace, ctx) - Expect(e).NotTo(HaveOccurred()) - Expect(pod.Status.PodIPs).NotTo(BeEmpty(), "pod failed to assign ip") - GinkgoWriter.Printf("pod: %v/%v, ips: %+v \n", namespace, podName, pod.Status.PodIPs) - - // Check the Pod's IP recorded IPPool - ok, _, _, err := common.CheckPodIpRecordInIppool(frame, globalDefaultV4IppoolList, globalDefaultV6IppoolList, &corev1.PodList{Items: []corev1.Pod{*pod}}) - Expect(err).NotTo(HaveOccurred()) - Expect(ok).To(BeTrue()) + Eventually(func() error { + podList, err := frame.GetPodListByLabel(dsYaml.Spec.Template.Labels) + if err != nil { + return err + } + if len(podList.Items) != len(frame.Info.KindNodeList) { + return fmt.Errorf("the number of pod is not equal to expectPodNum %v", len(frame.Info.KindNodeList)) + } + for _, pod := range podList.Items { + if !podutils.IsPodReady(&pod) { + return fmt.Errorf("the pod %v is not ready", pod.Name) + } + } + + // Check the Pod's IP recorded IPPool + ok, _, _, err := common.CheckPodIpRecordInIppool(frame, globalDefaultV4IppoolList, globalDefaultV6IppoolList, podList) + if err != nil && !ok { + return err + } + + if err := frame.DeleteDaemonSet(dsName, namespace); err != nil { + return err + } + + if err := common.WaitIPReclaimedFinish(frame, globalDefaultV4IppoolList, globalDefaultV6IppoolList, podList, common.IPReclaimTimeout); err != nil { + return err + } + return nil + }).WithTimeout(common.PodStartTimeout).WithPolling(time.Second * 5).Should(BeNil()) + }() wg.Wait() - - // try to delete pod - GinkgoWriter.Printf("delete pod %v/%v \n", namespace, podName) - Expect(frame.DeletePod(podName, namespace)).NotTo(HaveOccurred()) - // G00008: The Spiderpool component recovery from repeated reboot, and could correctly reclaim IP - if componentName == constant.SpiderpoolAgent || componentName == constant.SpiderpoolController { - Expect(common.WaitIPReclaimedFinish(frame, globalDefaultV4IppoolList, globalDefaultV6IppoolList, &corev1.PodList{Items: []corev1.Pod{*pod}}, 2*common.IPReclaimTimeout)).To(Succeed()) - } }, Entry("Successfully run a pod during the ETCD is restarting", Label("R00002"), "etcd", map[string]string{"component": "etcd"}, common.PodStartTimeout), Entry("Successfully run a pod during the API-server is restarting", Label("R00003"), "apiserver", map[string]string{"component": "kube-apiserver"}, common.PodStartTimeout), - // https://github.com/spidernet-io/spiderpool/issues/1916 - //Entry("Successfully run a pod during the coreDns is restarting", - // Label("R00005"), "coredns", map[string]string{"k8s-app": "kube-dns"}, time.Minute*3), + Entry("Successfully run a pod during the coreDns is restarting", + Label("R00005"), "coredns", map[string]string{"k8s-app": "kube-dns"}, common.PodStartTimeout), Entry("Successfully run a pod during the Spiderpool agent is restarting", Label("R00004", "G00008"), constant.SpiderpoolAgent, map[string]string{"app.kubernetes.io/component": constant.SpiderpoolAgent}, common.PodStartTimeout), Entry("Successfully run a pod during the Spiderpool controller is restarting", diff --git a/test/scripts/debugEnv.sh b/test/scripts/debugEnv.sh index 80ace83a81..c0a3a953c0 100755 --- a/test/scripts/debugEnv.sh +++ b/test/scripts/debugEnv.sh @@ -254,6 +254,19 @@ elif [ "$TYPE"x == "detail"x ] ; then kubectl logs ${POD} -n ${NAMESPACE} --kubeconfig ${E2E_KUBECONFIG} --previous done + echo "" + echo "=============== kdoctor netreach details ============== " + kubectl get netreach --kubeconfig ${E2E_KUBECONFIG} + kubectl get netreach --kubeconfig ${E2E_KUBECONFIG} -o yaml + + if [ -n "$KDOCTOR_POD_LIST" ]; then + echo "Fetching kdoctor reports..." + echo "--------- kubectl get kdoctorreport -A -ojson --------- " + kubectl get kdoctorreport -A -ojson --kubeconfig ${E2E_KUBECONFIG} + echo "--------- kubectl get kdoctorreport -A -oyaml --------- " + kubectl get kdoctorreport -A -oyaml --kubeconfig ${E2E_KUBECONFIG} + fi + echo "" echo "=============== open kruise logs ============== " for POD in $KRUISE_POD_LIST ; do @@ -263,11 +276,22 @@ elif [ "$TYPE"x == "detail"x ] ; then echo "--------- kubectl logs ${POD} -n kruise-system --previous" kubectl logs ${POD} -n kruise-system --kubeconfig ${E2E_KUBECONFIG} --previous done - + echo "" - echo "=============== kdoctor netreach details ============== " - kubectl get netreach --kubeconfig ${E2E_KUBECONFIG} - kubectl get netreach --kubeconfig ${E2E_KUBECONFIG} -o yaml + echo "=============== kubelet and docker log ============== " + KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-"spider"} + KIND_NODES=$( kind get nodes --name ${KIND_CLUSTER_NAME} ) + [ -z "$KIND_NODES" ] && echo "warning, failed to find nodes of kind cluster $KIND_CLUSTER_NAME " || true + for NODE in $KIND_NODES ; do + echo "--------- kubelet status from node ${NODE}" + docker exec $NODE systemctl status kubelet -l + echo "--------- kubelete logs from node ${NODE}" + docker exec $NODE journalctl -u kubelet -n 500 + echo "--------- docker status from node ${NODE}" + docker exec $NODE systemctl status docker -l + echo "--------- docker logs from node ${NODE}" + docker exec $NODE journalctl -u docker -n 500 + done elif [ "$TYPE"x == "error"x ] ; then CHECK_ERROR(){ diff --git a/test/scripts/install-kdoctor.sh b/test/scripts/install-kdoctor.sh index 0dacb9247b..8676b91216 100644 --- a/test/scripts/install-kdoctor.sh +++ b/test/scripts/install-kdoctor.sh @@ -24,7 +24,7 @@ echo "$CURRENT_FILENAME : KDOCTOR_REPORT_PATH $KDOCTOR_REPORT_PATH " [ ! -f "$E2E_KUBECONFIG" ] && echo "error, could not find file $E2E_KUBECONFIG " && exit 1 echo "$CURRENT_FILENAME : E2E_KUBECONFIG $E2E_KUBECONFIG " -KDOCTOR_VERSION=${KDOCTOR_VERSION:-0.2.0} +KDOCTOR_VERSION=${KDOCTOR_VERSION:-0.2.2} E2E_KDOCTOR_IMAGE_REPO=${E2E_KDOCTOR_IMAGE_REPO:-"ghcr.io"} INSTALL_TIME_OUT=300s diff --git a/vendor/github.com/hashicorp/errwrap/LICENSE b/vendor/github.com/hashicorp/errwrap/LICENSE new file mode 100644 index 0000000000..c33dcc7c92 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/errwrap/README.md b/vendor/github.com/hashicorp/errwrap/README.md new file mode 100644 index 0000000000..444df08f8e --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/README.md @@ -0,0 +1,89 @@ +# errwrap + +`errwrap` is a package for Go that formalizes the pattern of wrapping errors +and checking if an error contains another error. + +There is a common pattern in Go of taking a returned `error` value and +then wrapping it (such as with `fmt.Errorf`) before returning it. The problem +with this pattern is that you completely lose the original `error` structure. + +Arguably the _correct_ approach is that you should make a custom structure +implementing the `error` interface, and have the original error as a field +on that structure, such [as this example](http://golang.org/pkg/os/#PathError). +This is a good approach, but you have to know the entire chain of possible +rewrapping that happens, when you might just care about one. + +`errwrap` formalizes this pattern (it doesn't matter what approach you use +above) by giving a single interface for wrapping errors, checking if a specific +error is wrapped, and extracting that error. + +## Installation and Docs + +Install using `go get github.com/hashicorp/errwrap`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/errwrap + +## Usage + +#### Basic Usage + +Below is a very basic example of its usage: + +```go +// A function that always returns an error, but wraps it, like a real +// function might. +func tryOpen() error { + _, err := os.Open("/i/dont/exist") + if err != nil { + return errwrap.Wrapf("Doesn't exist: {{err}}", err) + } + + return nil +} + +func main() { + err := tryOpen() + + // We can use the Contains helpers to check if an error contains + // another error. It is safe to do this with a nil error, or with + // an error that doesn't even use the errwrap package. + if errwrap.Contains(err, "does not exist") { + // Do something + } + if errwrap.ContainsType(err, new(os.PathError)) { + // Do something + } + + // Or we can use the associated `Get` functions to just extract + // a specific error. This would return nil if that specific error doesn't + // exist. + perr := errwrap.GetType(err, new(os.PathError)) +} +``` + +#### Custom Types + +If you're already making custom types that properly wrap errors, then +you can get all the functionality of `errwraps.Contains` and such by +implementing the `Wrapper` interface with just one function. Example: + +```go +type AppError { + Code ErrorCode + Err error +} + +func (e *AppError) WrappedErrors() []error { + return []error{e.Err} +} +``` + +Now this works: + +```go +err := &AppError{Err: fmt.Errorf("an error")} +if errwrap.ContainsType(err, fmt.Errorf("")) { + // This will work! +} +``` diff --git a/vendor/github.com/hashicorp/errwrap/errwrap.go b/vendor/github.com/hashicorp/errwrap/errwrap.go new file mode 100644 index 0000000000..44e368e569 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/errwrap.go @@ -0,0 +1,178 @@ +// Package errwrap implements methods to formalize error wrapping in Go. +// +// All of the top-level functions that take an `error` are built to be able +// to take any error, not just wrapped errors. This allows you to use errwrap +// without having to type-check and type-cast everywhere. +package errwrap + +import ( + "errors" + "reflect" + "strings" +) + +// WalkFunc is the callback called for Walk. +type WalkFunc func(error) + +// Wrapper is an interface that can be implemented by custom types to +// have all the Contains, Get, etc. functions in errwrap work. +// +// When Walk reaches a Wrapper, it will call the callback for every +// wrapped error in addition to the wrapper itself. Since all the top-level +// functions in errwrap use Walk, this means that all those functions work +// with your custom type. +type Wrapper interface { + WrappedErrors() []error +} + +// Wrap defines that outer wraps inner, returning an error type that +// can be cleanly used with the other methods in this package, such as +// Contains, GetAll, etc. +// +// This function won't modify the error message at all (the outer message +// will be used). +func Wrap(outer, inner error) error { + return &wrappedError{ + Outer: outer, + Inner: inner, + } +} + +// Wrapf wraps an error with a formatting message. This is similar to using +// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap +// errors, you should replace it with this. +// +// format is the format of the error message. The string '{{err}}' will +// be replaced with the original error message. +// +// Deprecated: Use fmt.Errorf() +func Wrapf(format string, err error) error { + outerMsg := "" + if err != nil { + outerMsg = err.Error() + } + + outer := errors.New(strings.Replace( + format, "{{err}}", outerMsg, -1)) + + return Wrap(outer, err) +} + +// Contains checks if the given error contains an error with the +// message msg. If err is not a wrapped error, this will always return +// false unless the error itself happens to match this msg. +func Contains(err error, msg string) bool { + return len(GetAll(err, msg)) > 0 +} + +// ContainsType checks if the given error contains an error with +// the same concrete type as v. If err is not a wrapped error, this will +// check the err itself. +func ContainsType(err error, v interface{}) bool { + return len(GetAllType(err, v)) > 0 +} + +// Get is the same as GetAll but returns the deepest matching error. +func Get(err error, msg string) error { + es := GetAll(err, msg) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetType is the same as GetAllType but returns the deepest matching error. +func GetType(err error, v interface{}) error { + es := GetAllType(err, v) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetAll gets all the errors that might be wrapped in err with the +// given message. The order of the errors is such that the outermost +// matching error (the most recent wrap) is index zero, and so on. +func GetAll(err error, msg string) []error { + var result []error + + Walk(err, func(err error) { + if err.Error() == msg { + result = append(result, err) + } + }) + + return result +} + +// GetAllType gets all the errors that are the same type as v. +// +// The order of the return value is the same as described in GetAll. +func GetAllType(err error, v interface{}) []error { + var result []error + + var search string + if v != nil { + search = reflect.TypeOf(v).String() + } + Walk(err, func(err error) { + var needle string + if err != nil { + needle = reflect.TypeOf(err).String() + } + + if needle == search { + result = append(result, err) + } + }) + + return result +} + +// Walk walks all the wrapped errors in err and calls the callback. If +// err isn't a wrapped error, this will be called once for err. If err +// is a wrapped error, the callback will be called for both the wrapper +// that implements error as well as the wrapped error itself. +func Walk(err error, cb WalkFunc) { + if err == nil { + return + } + + switch e := err.(type) { + case *wrappedError: + cb(e.Outer) + Walk(e.Inner, cb) + case Wrapper: + cb(err) + + for _, err := range e.WrappedErrors() { + Walk(err, cb) + } + case interface{ Unwrap() error }: + cb(err) + Walk(e.Unwrap(), cb) + default: + cb(err) + } +} + +// wrappedError is an implementation of error that has both the +// outer and inner errors. +type wrappedError struct { + Outer error + Inner error +} + +func (w *wrappedError) Error() string { + return w.Outer.Error() +} + +func (w *wrappedError) WrappedErrors() []error { + return []error{w.Outer, w.Inner} +} + +func (w *wrappedError) Unwrap() error { + return w.Inner +} diff --git a/vendor/github.com/hashicorp/go-multierror/LICENSE b/vendor/github.com/hashicorp/go-multierror/LICENSE new file mode 100644 index 0000000000..82b4de97c7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-multierror/Makefile b/vendor/github.com/hashicorp/go-multierror/Makefile new file mode 100644 index 0000000000..b97cd6ed02 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/Makefile @@ -0,0 +1,31 @@ +TEST?=./... + +default: test + +# test runs the test suite and vets the code. +test: generate + @echo "==> Running tests..." + @go list $(TEST) \ + | grep -v "/vendor/" \ + | xargs -n1 go test -timeout=60s -parallel=10 ${TESTARGS} + +# testrace runs the race checker +testrace: generate + @echo "==> Running tests (race)..." + @go list $(TEST) \ + | grep -v "/vendor/" \ + | xargs -n1 go test -timeout=60s -race ${TESTARGS} + +# updatedeps installs all the dependencies needed to run and build. +updatedeps: + @sh -c "'${CURDIR}/scripts/deps.sh' '${NAME}'" + +# generate runs `go generate` to build the dynamically generated source files. +generate: + @echo "==> Generating..." + @find . -type f -name '.DS_Store' -delete + @go list ./... \ + | grep -v "/vendor/" \ + | xargs -n1 go generate + +.PHONY: default test testrace updatedeps generate diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md new file mode 100644 index 0000000000..71dd308ed8 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/README.md @@ -0,0 +1,150 @@ +# go-multierror + +[![CircleCI](https://img.shields.io/circleci/build/github/hashicorp/go-multierror/master)](https://circleci.com/gh/hashicorp/go-multierror) +[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-multierror.svg)](https://pkg.go.dev/github.com/hashicorp/go-multierror) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/hashicorp/go-multierror) + +[circleci]: https://app.circleci.com/pipelines/github/hashicorp/go-multierror +[godocs]: https://pkg.go.dev/github.com/hashicorp/go-multierror + +`go-multierror` is a package for Go that provides a mechanism for +representing a list of `error` values as a single `error`. + +This allows a function in Go to return an `error` that might actually +be a list of errors. If the caller knows this, they can unwrap the +list and access the errors. If the caller doesn't know, the error +formats to a nice human-readable format. + +`go-multierror` is fully compatible with the Go standard library +[errors](https://golang.org/pkg/errors/) package, including the +functions `As`, `Is`, and `Unwrap`. This provides a standardized approach +for introspecting on error values. + +## Installation and Docs + +Install using `go get github.com/hashicorp/go-multierror`. + +Full documentation is available at +https://pkg.go.dev/github.com/hashicorp/go-multierror + +### Requires go version 1.13 or newer + +`go-multierror` requires go version 1.13 or newer. Go 1.13 introduced +[error wrapping](https://golang.org/doc/go1.13#error_wrapping), which +this library takes advantage of. + +If you need to use an earlier version of go, you can use the +[v1.0.0](https://github.com/hashicorp/go-multierror/tree/v1.0.0) +tag, which doesn't rely on features in go 1.13. + +If you see compile errors that look like the below, it's likely that +you're on an older version of go: + +``` +/go/src/github.com/hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As +/go/src/github.com/hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is +``` + +## Usage + +go-multierror is easy to use and purposely built to be unobtrusive in +existing Go applications/libraries that may not be aware of it. + +**Building a list of errors** + +The `Append` function is used to create a list of errors. This function +behaves a lot like the Go built-in `append` function: it doesn't matter +if the first argument is nil, a `multierror.Error`, or any other `error`, +the function behaves as you would expect. + +```go +var result error + +if err := step1(); err != nil { + result = multierror.Append(result, err) +} +if err := step2(); err != nil { + result = multierror.Append(result, err) +} + +return result +``` + +**Customizing the formatting of the errors** + +By specifying a custom `ErrorFormat`, you can customize the format +of the `Error() string` function: + +```go +var result *multierror.Error + +// ... accumulate errors here, maybe using Append + +if result != nil { + result.ErrorFormat = func([]error) string { + return "errors!" + } +} +``` + +**Accessing the list of errors** + +`multierror.Error` implements `error` so if the caller doesn't know about +multierror, it will work just fine. But if you're aware a multierror might +be returned, you can use type switches to access the list of errors: + +```go +if err := something(); err != nil { + if merr, ok := err.(*multierror.Error); ok { + // Use merr.Errors + } +} +``` + +You can also use the standard [`errors.Unwrap`](https://golang.org/pkg/errors/#Unwrap) +function. This will continue to unwrap into subsequent errors until none exist. + +**Extracting an error** + +The standard library [`errors.As`](https://golang.org/pkg/errors/#As) +function can be used directly with a multierror to extract a specific error: + +```go +// Assume err is a multierror value +err := somefunc() + +// We want to know if "err" has a "RichErrorType" in it and extract it. +var errRich RichErrorType +if errors.As(err, &errRich) { + // It has it, and now errRich is populated. +} +``` + +**Checking for an exact error value** + +Some errors are returned as exact errors such as the [`ErrNotExist`](https://golang.org/pkg/os/#pkg-variables) +error in the `os` package. You can check if this error is present by using +the standard [`errors.Is`](https://golang.org/pkg/errors/#Is) function. + +```go +// Assume err is a multierror value +err := somefunc() +if errors.Is(err, os.ErrNotExist) { + // err contains os.ErrNotExist +} +``` + +**Returning a multierror only if there are errors** + +If you build a `multierror.Error`, you can use the `ErrorOrNil` function +to return an `error` implementation only if there are errors to return: + +```go +var result *multierror.Error + +// ... accumulate errors here + +// Return the `error` only if errors were added to the multierror, otherwise +// return nil since there are no errors. +return result.ErrorOrNil() +``` diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go new file mode 100644 index 0000000000..3e2589bfde --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/append.go @@ -0,0 +1,43 @@ +package multierror + +// Append is a helper function that will append more errors +// onto an Error in order to create a larger multi-error. +// +// If err is not a multierror.Error, then it will be turned into +// one. If any of the errs are multierr.Error, they will be flattened +// one level into err. +// Any nil errors within errs will be ignored. If err is nil, a new +// *Error will be returned. +func Append(err error, errs ...error) *Error { + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Go through each error and flatten + for _, e := range errs { + switch e := e.(type) { + case *Error: + if e != nil { + err.Errors = append(err.Errors, e.Errors...) + } + default: + if e != nil { + err.Errors = append(err.Errors, e) + } + } + } + + return err + default: + newErrs := make([]error, 0, len(errs)+1) + if err != nil { + newErrs = append(newErrs, err) + } + newErrs = append(newErrs, errs...) + + return Append(&Error{}, newErrs...) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/flatten.go b/vendor/github.com/hashicorp/go-multierror/flatten.go new file mode 100644 index 0000000000..aab8e9abec --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/flatten.go @@ -0,0 +1,26 @@ +package multierror + +// Flatten flattens the given error, merging any *Errors together into +// a single *Error. +func Flatten(err error) error { + // If it isn't an *Error, just return the error as-is + if _, ok := err.(*Error); !ok { + return err + } + + // Otherwise, make the result and flatten away! + flatErr := new(Error) + flatten(err, flatErr) + return flatErr +} + +func flatten(err error, flatErr *Error) { + switch err := err.(type) { + case *Error: + for _, e := range err.Errors { + flatten(e, flatErr) + } + default: + flatErr.Errors = append(flatErr.Errors, err) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/format.go b/vendor/github.com/hashicorp/go-multierror/format.go new file mode 100644 index 0000000000..47f13c49a6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/format.go @@ -0,0 +1,27 @@ +package multierror + +import ( + "fmt" + "strings" +) + +// ErrorFormatFunc is a function callback that is called by Error to +// turn the list of errors into a string. +type ErrorFormatFunc func([]error) string + +// ListFormatFunc is a basic formatter that outputs the number of errors +// that occurred along with a bullet point list of the errors. +func ListFormatFunc(es []error) string { + if len(es) == 1 { + return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0]) + } + + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %s", err) + } + + return fmt.Sprintf( + "%d errors occurred:\n\t%s\n\n", + len(es), strings.Join(points, "\n\t")) +} diff --git a/vendor/github.com/hashicorp/go-multierror/group.go b/vendor/github.com/hashicorp/go-multierror/group.go new file mode 100644 index 0000000000..9c29efb7f8 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/group.go @@ -0,0 +1,38 @@ +package multierror + +import "sync" + +// Group is a collection of goroutines which return errors that need to be +// coalesced. +type Group struct { + mutex sync.Mutex + err *Error + wg sync.WaitGroup +} + +// Go calls the given function in a new goroutine. +// +// If the function returns an error it is added to the group multierror which +// is returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.mutex.Lock() + g.err = Append(g.err, err) + g.mutex.Unlock() + } + }() +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the multierror. +func (g *Group) Wait() *Error { + g.wg.Wait() + g.mutex.Lock() + defer g.mutex.Unlock() + return g.err +} diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go new file mode 100644 index 0000000000..f545743264 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/multierror.go @@ -0,0 +1,121 @@ +package multierror + +import ( + "errors" + "fmt" +) + +// Error is an error type to track multiple errors. This is used to +// accumulate errors in cases and return them as a single "error". +type Error struct { + Errors []error + ErrorFormat ErrorFormatFunc +} + +func (e *Error) Error() string { + fn := e.ErrorFormat + if fn == nil { + fn = ListFormatFunc + } + + return fn(e.Errors) +} + +// ErrorOrNil returns an error interface if this Error represents +// a list of errors, or returns nil if the list of errors is empty. This +// function is useful at the end of accumulation to make sure that the value +// returned represents the existence of errors. +func (e *Error) ErrorOrNil() error { + if e == nil { + return nil + } + if len(e.Errors) == 0 { + return nil + } + + return e +} + +func (e *Error) GoString() string { + return fmt.Sprintf("*%#v", *e) +} + +// WrappedErrors returns the list of errors that this Error is wrapping. It is +// an implementation of the errwrap.Wrapper interface so that multierror.Error +// can be used with that library. +// +// This method is not safe to be called concurrently. Unlike accessing the +// Errors field directly, this function also checks if the multierror is nil to +// prevent a null-pointer panic. It satisfies the errwrap.Wrapper interface. +func (e *Error) WrappedErrors() []error { + if e == nil { + return nil + } + return e.Errors +} + +// Unwrap returns an error from Error (or nil if there are no errors). +// This error returned will further support Unwrap to get the next error, +// etc. The order will match the order of Errors in the multierror.Error +// at the time of calling. +// +// The resulting error supports errors.As/Is/Unwrap so you can continue +// to use the stdlib errors package to introspect further. +// +// This will perform a shallow copy of the errors slice. Any errors appended +// to this error after calling Unwrap will not be available until a new +// Unwrap is called on the multierror.Error. +func (e *Error) Unwrap() error { + // If we have no errors then we do nothing + if e == nil || len(e.Errors) == 0 { + return nil + } + + // If we have exactly one error, we can just return that directly. + if len(e.Errors) == 1 { + return e.Errors[0] + } + + // Shallow copy the slice + errs := make([]error, len(e.Errors)) + copy(errs, e.Errors) + return chain(errs) +} + +// chain implements the interfaces necessary for errors.Is/As/Unwrap to +// work in a deterministic way with multierror. A chain tracks a list of +// errors while accounting for the current represented error. This lets +// Is/As be meaningful. +// +// Unwrap returns the next error. In the cleanest form, Unwrap would return +// the wrapped error here but we can't do that if we want to properly +// get access to all the errors. Instead, users are recommended to use +// Is/As to get the correct error type out. +// +// Precondition: []error is non-empty (len > 0) +type chain []error + +// Error implements the error interface +func (e chain) Error() string { + return e[0].Error() +} + +// Unwrap implements errors.Unwrap by returning the next error in the +// chain or nil if there are no more errors. +func (e chain) Unwrap() error { + if len(e) == 1 { + return nil + } + + return e[1:] +} + +// As implements errors.As by attempting to map to the current value. +func (e chain) As(target interface{}) bool { + return errors.As(e[0], target) +} + +// Is implements errors.Is by comparing the current value directly. +func (e chain) Is(target error) bool { + return errors.Is(e[0], target) +} diff --git a/vendor/github.com/hashicorp/go-multierror/prefix.go b/vendor/github.com/hashicorp/go-multierror/prefix.go new file mode 100644 index 0000000000..5c477abe44 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/prefix.go @@ -0,0 +1,37 @@ +package multierror + +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) + +// Prefix is a helper function that will prefix some text +// to the given error. If the error is a multierror.Error, then +// it will be prefixed to each wrapped error. +// +// This is useful to use when appending multiple multierrors +// together in order to give better scoping. +func Prefix(err error, prefix string) error { + if err == nil { + return nil + } + + format := fmt.Sprintf("%s {{err}}", prefix) + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Wrap each of the errors + for i, e := range err.Errors { + err.Errors[i] = errwrap.Wrapf(format, e) + } + + return err + default: + return errwrap.Wrapf(format, err) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/sort.go b/vendor/github.com/hashicorp/go-multierror/sort.go new file mode 100644 index 0000000000..fecb14e81c --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/sort.go @@ -0,0 +1,16 @@ +package multierror + +// Len implements sort.Interface function for length +func (err Error) Len() int { + return len(err.Errors) +} + +// Swap implements sort.Interface function for swapping elements +func (err Error) Swap(i, j int) { + err.Errors[i], err.Errors[j] = err.Errors[j], err.Errors[i] +} + +// Less implements sort.Interface function for determining order +func (err Error) Less(i, j int) bool { + return err.Errors[i].Error() < err.Errors[j].Error() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 32eed090be..efc71a9962 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -297,6 +297,12 @@ github.com/grafana/pyroscope-go/upstream/remote ## explicit; go 1.16 github.com/grafana/pyroscope-go/godeltaprof github.com/grafana/pyroscope-go/godeltaprof/internal/pprof +# github.com/hashicorp/errwrap v1.1.0 +## explicit +github.com/hashicorp/errwrap +# github.com/hashicorp/go-multierror v1.1.1 +## explicit; go 1.13 +github.com/hashicorp/go-multierror # github.com/hashicorp/hcl v1.0.0 ## explicit github.com/hashicorp/hcl