From f559df117041c2df701113c37deb03cf37509753 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 17 Apr 2025 12:50:54 +0200 Subject: [PATCH 001/278] [docs] update ovn-observability with some more details Signed-off-by: Nadia Pinaeva --- docs/observability/ovn-observability.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/observability/ovn-observability.md b/docs/observability/ovn-observability.md index 0507b9e725..d9e735a1b4 100644 --- a/docs/observability/ovn-observability.md +++ b/docs/observability/ovn-observability.md @@ -68,9 +68,11 @@ No API changes were done. ### OVN sampling details OVN has 3 main db tables that are used for sampling: -- `Sample_collector`: This table is used to define the sampling collector. It defines the sampling rate and collectorID, -which is used to set up collectors in the OVS. +- `Sample_collector`: This table is used to define the sampling collector. It defines the sampling rate via `Probability` field +and collectorID via `SetID` field, which is used to set up collectors in the OVS. - `Sampling_app`: This table is used to set `ID`s for existing OVN sampling applications, that are sent together with the samples. +There is a supported set of `Sampling_app` types, for example `acl-new` app is used to sample new connections matched by an ACL. +`Sampling_app.ID` is a way to identify the application that generated the sample. - `Sample`: This table is used to define required samples and point to the collectors. Every sample has `Metadata` that is sent together with the sample. @@ -84,15 +86,21 @@ that is decoded by `go-controller/observability-lib`. When one of the supported objects (for example, network policy) is created, ovn-kuberentes generates an nbdb `Sample` for it. To decode the samples into human-readable information, `go-controller/observability-lib` is used. It finds `Sample` -by the attached `Sample.Metadata` and then gets corresponding db object based on `Sampling_add.ID` and `Sample.UUID`. -The message is then constructed using db object `external_ids`. - -### Full stack architecture +by the attached `Sample.Metadata` and then gets corresponding db object (e.g. ACL) based on `Sampling_app.ID` and `Sample.UUID`. +The message is then constructed using db object (e.g. ACL) `external_ids`. ![ovnkube-observ](../images/ovnkube-observ.png) The diagram shows how all involved components (kernel, OVS, OVN, ovn-kubernetes) are connected. +#### Enabling collectors + +Currently, we have only 1 default collector with hard-coded ID, which is set via the `Sample_collector.SetID` field. +To make OVS start sending samples for an existing `Sample_collector`, a new OVSDB `Flow_Sample_Collector_Set` entry +needs to be created with `Flow_Sample_Collector_Set.ID` value of `Sample_collector.SetID`. +This is done by the `go-controller/observability-lib` and it is important to note that only one `Flow_Sample_Collector_Set` +should be created for a given `Sample_collector.SetID` value at a time. But if such entry already exists, it can be reused. + ## Best Practices TDB @@ -126,6 +134,9 @@ This applies to in both cases ANP will have only first-packet sample. +Use caution when running the `ovnkube-observe` tool. Currently it has poor resource management and consumes a lot of +CPU when many packets are sent. Tracked here https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5203 + ## References NONE From 3d104e66ddc897df115ce57a68aa240bfd03c53e Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 20 May 2025 15:55:08 -0400 Subject: [PATCH 002/278] Retry all pods smarter Related to investigating the root cause for: #5260. This commit removes adding pods that are not scheduled to the retry framework. When the pod is scheduled the controller will receive an event. Additionally these functions that add pods were using the kubeclient instead of informer cache. That means everytime a UDN was added we would issue kubeclient command to get all pods, which is really bad for performance. Signed-off-by: Tim Rozet --- .../network_cluster_controller.go | 2 +- .../pkg/ovn/base_network_controller.go | 13 ++++--- go-controller/pkg/retry/obj_retry.go | 36 ++++++++----------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/go-controller/pkg/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index fde745ac00..9d9abb77d3 100644 --- a/go-controller/pkg/clustermanager/network_cluster_controller.go +++ b/go-controller/pkg/clustermanager/network_cluster_controller.go @@ -475,7 +475,7 @@ func (ncc *networkClusterController) Reconcile(netInfo util.NetInfo) error { klog.Errorf("Failed to reconcile network %s: %v", ncc.GetNetworkName(), err) } if reconcilePendingPods && ncc.retryPods != nil { - if err := objretry.RequeuePendingPods(ncc.kube, ncc.GetNetInfo(), ncc.retryPods); err != nil { + if err := objretry.RequeuePendingPods(ncc.watchFactory, ncc.GetNetInfo(), ncc.retryPods); err != nil { klog.Errorf("Failed to requeue pending pods for network %s: %v", ncc.GetNetworkName(), err) } } diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index 1a3f8685e4..ae38418d70 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -14,8 +14,6 @@ import ( corev1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -241,7 +239,7 @@ func (oc *BaseNetworkController) reconcile(netInfo util.NetInfo, setNodeFailed f } if reconcilePendingPods { - if err := ovnretry.RequeuePendingPods(oc.kube, oc.GetNetInfo(), oc.retryPods); err != nil { + if err := ovnretry.RequeuePendingPods(oc.watchFactory, oc.GetNetInfo(), oc.retryPods); err != nil { klog.Errorf("Failed to requeue pending pods for network %s: %v", oc.GetNetworkName(), err) } } @@ -579,12 +577,10 @@ func (bnc *BaseNetworkController) deleteNodeLogicalNetwork(nodeName string) erro func (bnc *BaseNetworkController) addAllPodsOnNode(nodeName string) []error { errs := []error{} - pods, err := bnc.kube.GetPods(metav1.NamespaceAll, metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("spec.nodeName", nodeName).String(), - }) + pods, err := bnc.watchFactory.GetAllPods() if err != nil { errs = append(errs, err) - klog.Errorf("Unable to list existing pods on node: %s, existing pods on this node may not function", + klog.Errorf("Unable to list existing pods for synchronizing node: %s, existing pods on this node may not function", nodeName) } else { klog.V(5).Infof("When adding node %s for network %s, found %d pods to add to retryPods", nodeName, bnc.GetNetworkName(), len(pods)) @@ -593,6 +589,9 @@ func (bnc *BaseNetworkController) addAllPodsOnNode(nodeName string) []error { if util.PodCompleted(&pod) { continue } + if pod.Spec.NodeName != nodeName { + continue + } klog.V(5).Infof("Adding pod %s/%s to retryPods for network %s", pod.Namespace, pod.Name, bnc.GetNetworkName()) err = bnc.retryPods.AddRetryObjWithAddNoBackoff(&pod) if err != nil { diff --git a/go-controller/pkg/retry/obj_retry.go b/go-controller/pkg/retry/obj_retry.go index 5f9dfffb16..b6dd8ffa5e 100644 --- a/go-controller/pkg/retry/obj_retry.go +++ b/go-controller/pkg/retry/obj_retry.go @@ -9,14 +9,11 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/syncmap" ovntypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -771,38 +768,35 @@ func (r *RetryFramework) WatchResourceFiltered(namespaceForFilteredHandler strin return handler, nil } -// getPendingPods returns all pods that are in the Pending state -func getPendingPods(kubeClient kube.InterfaceOVN) ([]*corev1.Pod, error) { - var allPods []*corev1.Pod - - pods, err := kubeClient.GetPods(corev1.NamespaceAll, metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("status.phase", string(corev1.PodPending)).String(), - }) - if err != nil { - return nil, err - } - allPods = append(allPods, pods...) - return allPods, nil -} - // RequeuePendingPods enqueues all Pending pods into the retryPods associated with netInfo. -func RequeuePendingPods(kubeClient kube.InterfaceOVN, netInfo util.NetInfo, retryPods *RetryFramework) error { +func RequeuePendingPods(wf *factory.WatchFactory, netInfo util.NetInfo, retryPods *RetryFramework) error { var errs []error // NOTE: A pod may reference a NAD from a different namespace, so check all pending pods. - allPods, err := getPendingPods(kubeClient) + allPods, err := wf.GetAllPods() if err != nil { - return err + return fmt.Errorf("failed to get all pods: %w", err) } + podsAdded := false for _, pod := range allPods { pod := *pod + if !util.PodScheduled(&pod) { + continue + } + if pod.Status.Phase != corev1.PodPending { + continue + } klog.V(5).Infof("Adding pending pod %s/%s to retryPods for network %s", pod.Namespace, pod.Name, netInfo.GetNetworkName()) err := retryPods.AddRetryObjWithAddNoBackoff(&pod) if err != nil { errs = append(errs, err) + continue } + podsAdded = true + } + if podsAdded { + retryPods.RequestRetryObjs() } - retryPods.RequestRetryObjs() return utilerrors.Join(errs...) } From d31d1717da84a98b8feb08c261ec36c10cb416c5 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 20 May 2025 13:32:07 -0400 Subject: [PATCH 003/278] Configures ephemeral port range for OVN SNAT'ing There was a previous bug where when an egress packet would be SNAT'ed to the node IP, using a nodeport source port, it would cause reply traffic to get DNAT'ed to the nodeport load balancer. This happened because the egress connections were not conntracked correctly. This was fixed via: https://issues.redhat.com/browse/OCPBUGS-25889 https://issues.redhat.com/browse/FDP-291 However, that fix was not hardware offloadable. The ideal fix here would be to always commit to conntrack and have it be HW offloadable. Until we have a better solution, we can configure the port range for OVN to use on its SNAT. This applies to all SNATs for traffic that enters the local host or leaves the host. The new config option --ephemeral-port-range "-" can be used to specify the port range to use with OVN. If not provided, this value will be automatically derived from the ephemeral port range in /proc/sys/net/ipv4/ip_local_port_range, which is typically set already to avoid nodeport range conflicts. Signed-off-by: Tim Rozet --- go-controller/pkg/config/config.go | 34 +++++++++++++++++ go-controller/pkg/config/utils.go | 48 ++++++++++++++++++++++++ go-controller/pkg/libovsdb/ops/router.go | 6 ++- go-controller/pkg/ovn/gateway_test.go | 18 +++++++-- go-controller/pkg/ovn/namespace_test.go | 1 + 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/go-controller/pkg/config/config.go b/go-controller/pkg/config/config.go index c7df666cbc..7cd97479c4 100644 --- a/go-controller/pkg/config/config.go +++ b/go-controller/pkg/config/config.go @@ -38,6 +38,9 @@ const DefaultVXLANPort = 4789 const DefaultDBTxnTimeout = time.Second * 100 +// DefaultEphemeralPortRange is used for unit testing only +const DefaultEphemeralPortRange = "32768-60999" + // The following are global config parameters that other modules may access directly var ( // Build information. Populated at build-time. @@ -494,6 +497,10 @@ type GatewayConfig struct { DisableForwarding bool `gcfg:"disable-forwarding"` // AllowNoUplink (disabled by default) controls if the external gateway bridge without an uplink port is allowed in local gateway mode. AllowNoUplink bool `gcfg:"allow-no-uplink"` + // EphemeralPortRange is the range of ports used by egress SNAT operations in OVN. Specifically for NAT where + // the source IP of the NAT will be a shared Node IP address. If unset, the value will be determined by sysctl lookup + // for the kernel's ephemeral range: net.ipv4.ip_local_port_range. Format is "-". + EphemeralPortRange string `gfcg:"ephemeral-port-range"` } // OvnAuthConfig holds client authentication and location details for @@ -664,6 +671,9 @@ func PrepareTestConfig() error { Kubernetes.DisableRequestedChassis = false EnableMulticast = false Default.OVSDBTxnTimeout = 5 * time.Second + if Gateway.Mode != GatewayModeDisabled { + Gateway.EphemeralPortRange = DefaultEphemeralPortRange + } if err := completeConfig(); err != nil { return err @@ -1509,6 +1519,14 @@ var OVNGatewayFlags = []cli.Flag{ Usage: "Allow the external gateway bridge without an uplink port in local gateway mode", Destination: &cliConfig.Gateway.AllowNoUplink, }, + &cli.StringFlag{ + Name: "ephemeral-port-range", + Usage: "The port range in '-' format for OVN to use when SNAT'ing to a node IP. " + + "This range should not collide with the node port range being used in Kubernetes. If not provided, " + + "the default value will be derived from checking the sysctl value of net.ipv4.ip_local_port_range on the node.", + Destination: &cliConfig.Gateway.EphemeralPortRange, + Value: Gateway.EphemeralPortRange, + }, // Deprecated CLI options &cli.BoolFlag{ Name: "init-gateways", @@ -1917,6 +1935,19 @@ func buildGatewayConfig(ctx *cli.Context, cli, file *config) error { if !found { return fmt.Errorf("invalid gateway mode %q: expect one of %s", string(Gateway.Mode), strings.Join(validModes, ",")) } + + if len(Gateway.EphemeralPortRange) > 0 { + if !isValidEphemeralPortRange(Gateway.EphemeralPortRange) { + return fmt.Errorf("invalid ephemeral-port-range, should be in the format -") + } + } else { + // auto-detect ephermal range + portRange, err := getKernelEphemeralPortRange() + if err != nil { + return fmt.Errorf("unable to auto-detect ephemeral port range to use with OVN") + } + Gateway.EphemeralPortRange = portRange + } } // Options are only valid if Mode is not disabled @@ -1927,6 +1958,9 @@ func buildGatewayConfig(ctx *cli.Context, cli, file *config) error { if Gateway.NextHop != "" { return fmt.Errorf("gateway next-hop option %q not allowed when gateway is disabled", Gateway.NextHop) } + if len(Gateway.EphemeralPortRange) > 0 { + return fmt.Errorf("gateway ephemeral port range option not allowed when gateway is disabled") + } } if Gateway.Mode != GatewayModeShared && Gateway.VLANID != 0 { diff --git a/go-controller/pkg/config/utils.go b/go-controller/pkg/config/utils.go index 7ff8eff484..f0f0ff1a6b 100644 --- a/go-controller/pkg/config/utils.go +++ b/go-controller/pkg/config/utils.go @@ -3,7 +3,9 @@ package config import ( "fmt" "net" + "os" "reflect" + "regexp" "strconv" "strings" @@ -328,3 +330,49 @@ func AllocateV6MasqueradeIPs(masqueradeSubnetNetworkAddress net.IP, masqueradeIP } return nil } + +func isValidEphemeralPortRange(s string) bool { + // Regex to match "-" with no extra characters + re := regexp.MustCompile(`^(\d{1,5})-(\d{1,5})$`) + matches := re.FindStringSubmatch(s) + if matches == nil { + return false + } + + minPort, err1 := strconv.Atoi(matches[1]) + maxPort, err2 := strconv.Atoi(matches[2]) + if err1 != nil || err2 != nil { + return false + } + + // Port numbers must be in the 1-65535 range + if minPort < 1 || minPort > 65535 || maxPort < 0 || maxPort > 65535 { + return false + } + + return maxPort > minPort +} + +func getKernelEphemeralPortRange() (string, error) { + data, err := os.ReadFile("/proc/sys/net/ipv4/ip_local_port_range") + if err != nil { + return "", fmt.Errorf("failed to read port range: %w", err) + } + + parts := strings.Fields(string(data)) + if len(parts) != 2 { + return "", fmt.Errorf("unexpected format: %q", string(data)) + } + + minPort, err := strconv.Atoi(parts[0]) + if err != nil { + return "", fmt.Errorf("invalid min port: %w", err) + } + + maxPort, err := strconv.Atoi(parts[1]) + if err != nil { + return "", fmt.Errorf("invalid max port: %w", err) + } + + return fmt.Sprintf("%d-%d", minPort, maxPort), nil +} diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go index da518f7cb3..3d5a6fc255 100644 --- a/go-controller/pkg/libovsdb/ops/router.go +++ b/go-controller/pkg/libovsdb/ops/router.go @@ -961,6 +961,10 @@ func buildNAT( Match: match, } + if config.Gateway.Mode != config.GatewayModeDisabled { + nat.ExternalPortRange = config.Gateway.EphemeralPortRange + } + if logicalPort != "" { nat.LogicalPort = &logicalPort } @@ -1061,7 +1065,7 @@ func isEquivalentNAT(existing *nbdb.NAT, searched *nbdb.NAT) bool { return false } - // Compre externalIP if its not empty. + // Compare externalIP if it's not empty. if searched.ExternalIP != "" && searched.ExternalIP != existing.ExternalIP { return false } diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index da48869991..57f5fb4be2 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -220,13 +220,17 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN natUUID := fmt.Sprintf("nat-%d-UUID", i) natUUIDs = append(natUUIDs, natUUID) physicalIP, _ := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(subnet), l3GatewayConfig.IPAddresses) - testData = append(testData, &nbdb.NAT{ + nat := nbdb.NAT{ UUID: natUUID, ExternalIP: physicalIP.IP.String(), LogicalIP: subnet.String(), Options: map[string]string{"stateless": "false"}, Type: nbdb.NATTypeSNAT, - }) + } + if config.Gateway.Mode != config.GatewayModeDisabled { + nat.ExternalPortRange = config.DefaultEphemeralPortRange + } + testData = append(testData, &nat) } } @@ -234,13 +238,17 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN natUUID := fmt.Sprintf("nat-join-%d-UUID", i) natUUIDs = append(natUUIDs, natUUID) joinLRPIP, _ := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(physicalIP), joinLRPIPs) - testData = append(testData, &nbdb.NAT{ + nat := nbdb.NAT{ UUID: natUUID, ExternalIP: physicalIP.IP.String(), LogicalIP: joinLRPIP.IP.String(), Options: map[string]string{"stateless": "false"}, Type: nbdb.NATTypeSNAT, - }) + } + if config.Gateway.Mode != config.GatewayModeDisabled { + nat.ExternalPortRange = config.DefaultEphemeralPortRange + } + testData = append(testData, &nat) } testData = append(testData, &nbdb.MeterBand{ @@ -394,6 +402,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { ginkgo.Context("Gateway Creation Operations Shared Gateway Mode", func() { ginkgo.BeforeEach(func() { config.Gateway.Mode = config.GatewayModeShared + config.Gateway.EphemeralPortRange = config.DefaultEphemeralPortRange }) ginkgo.It("creates an IPv4 gateway in OVN", func() { @@ -1441,6 +1450,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { ginkgo.BeforeEach(func() { config.Gateway.Mode = config.GatewayModeLocal config.IPv6Mode = false + config.Gateway.EphemeralPortRange = config.DefaultEphemeralPortRange }) ginkgo.It("creates a dual-stack gateway in OVN", func() { diff --git a/go-controller/pkg/ovn/namespace_test.go b/go-controller/pkg/ovn/namespace_test.go index c067098709..3e8c556b8a 100644 --- a/go-controller/pkg/ovn/namespace_test.go +++ b/go-controller/pkg/ovn/namespace_test.go @@ -238,6 +238,7 @@ var _ = ginkgo.Describe("OVN Namespace Operations", func() { ginkgo.It("creates an address set for existing nodes when the host network traffic namespace is created", func() { config.Gateway.Mode = config.GatewayModeShared config.Gateway.NodeportEnable = true + config.Gateway.EphemeralPortRange = config.DefaultEphemeralPortRange var err error config.Default.ClusterSubnets, err = config.ParseClusterSubnetEntries(clusterCIDR) gomega.Expect(err).NotTo(gomega.HaveOccurred()) From 7a30735fc45cde1ae1fbea6012ca5c8f17cdb598 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 28 May 2025 12:30:20 -0400 Subject: [PATCH 004/278] Use watchFactory instead of kclient for gateway snat cleanup Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/gateway.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 9ecbb512fd..7c38289737 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -11,8 +11,6 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -161,9 +159,7 @@ func (gw *GatewayManager) cleanupStalePodSNATs(nodeName string, nodeIPs []*net.I // the SNATs stale podIPsWithSNAT := sets.New[string]() if !gw.isRoutingAdvertised(nodeName) && config.Gateway.DisableSNATMultipleGWs { - pods, err := gw.kube.GetPods(metav1.NamespaceAll, metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("spec.nodeName", nodeName).String(), - }) + pods, err := gw.watchFactory.GetAllPods() if err != nil { return fmt.Errorf("unable to list existing pods on node: %s, %w", nodeName, err) @@ -173,6 +169,9 @@ func (gw *GatewayManager) cleanupStalePodSNATs(nodeName string, nodeIPs []*net.I if !util.PodScheduled(&pod) { //if the pod is not scheduled we should not remove the nat continue } + if pod.Spec.NodeName != nodeName { + continue + } if util.PodCompleted(&pod) { collidingPod, err := findPodWithIPAddresses(gw.watchFactory, gw.netInfo, []net.IP{utilnet.ParseIPSloppy(pod.Status.PodIP)}, "") //even if a pod is completed we should still delete the nat if the ip is not in use anymore if err != nil { From 8cf444c9e4bdd77795954e49c8cfc3dc45ffb3b8 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 28 May 2025 16:07:24 -0400 Subject: [PATCH 005/278] Removes as much kubeclient Get methods as possible Kubeclient get for nodes and pods were being used in other places in the code. Removed all of their uses except for specific cases like the ovn db manager and windows, where we do not have full informer setups. While transitioning to use the factory, it created a cylical dependency between metrics and factory libraries, due to the configuration duration recorder. Split the configuration duration recorder into its own sub-package under metrics/recorders. Signed-off-by: Tim Rozet --- .../pkg/controller/ho_node_windows.go | 6 +- .../pkg/controller/ovn_node_linux.go | 2 +- .../controllermanager/controller_manager.go | 3 +- .../node_controller_manager_test.go | 2 +- go-controller/pkg/kube/annotator_test.go | 2 +- go-controller/pkg/kube/kube.go | 57 +- go-controller/pkg/kube/kube_test.go | 2 +- go-controller/pkg/kube/mocks/Interface.go | 104 +-- go-controller/pkg/kube/mocks/InterfaceOVN.go | 102 +-- go-controller/pkg/metrics/cluster_manager.go | 41 +- go-controller/pkg/metrics/metrics.go | 15 +- go-controller/pkg/metrics/node.go | 23 +- go-controller/pkg/metrics/ovn.go | 59 +- go-controller/pkg/metrics/ovn_db.go | 77 +- go-controller/pkg/metrics/ovn_northd.go | 21 +- .../pkg/metrics/ovnkube_controller.go | 697 ++---------------- go-controller/pkg/metrics/ovs.go | 137 ++-- .../pkg/metrics/recorders/duration.go | 565 ++++++++++++++ .../duration_test.go} | 64 +- .../node/default_node_network_controller.go | 8 +- .../pkg/node/gateway_egressip_test.go | 24 +- go-controller/pkg/node/gateway_udn_test.go | 8 +- go-controller/pkg/ovn/base_event_handler.go | 18 +- .../pkg/ovn/base_network_controller.go | 3 +- .../admin_network_policy/metrics.go | 6 +- .../pkg/ovn/controller/network_qos/metrics.go | 26 +- .../ovn/controller/services/loadbalancer.go | 4 +- .../services/services_controller.go | 11 +- .../pkg/ovn/default_network_controller.go | 19 +- go-controller/pkg/ovn/hybrid.go | 4 +- go-controller/pkg/ovn/master.go | 2 +- go-controller/pkg/ovn/master_test.go | 2 +- go-controller/pkg/ovn/ovn_test.go | 2 +- .../secondary_localnet_network_controller.go | 4 +- .../pkg/ovndbmanager/ovndbmanager.go | 2 +- go-controller/pkg/types/const.go | 13 + 36 files changed, 989 insertions(+), 1146 deletions(-) create mode 100644 go-controller/pkg/metrics/recorders/duration.go rename go-controller/pkg/metrics/{ovnkube_controller_test.go => recorders/duration_test.go} (86%) diff --git a/go-controller/hybrid-overlay/pkg/controller/ho_node_windows.go b/go-controller/hybrid-overlay/pkg/controller/ho_node_windows.go index 5de9b75391..339bc289f7 100644 --- a/go-controller/hybrid-overlay/pkg/controller/ho_node_windows.go +++ b/go-controller/hybrid-overlay/pkg/controller/ho_node_windows.go @@ -57,7 +57,7 @@ func newNodeController(kube kube.Interface, "UDP port. Please make sure you install all the KB updates on your system.") } - node, err := kube.GetNode(nodeName) + node, err := kube.GetNodeForWindows(nodeName) if err != nil { return nil, err } @@ -345,7 +345,7 @@ func (n *NodeController) initSelf(node *corev1.Node, nodeSubnet *net.IPNet) erro } // Add existing nodes - nodes, err := n.kube.GetNodes() + nodes, err := n.kube.GetNodesForWindows() if err != nil { return fmt.Errorf("error in initializing/fetching nodes: %v", err) } @@ -370,7 +370,7 @@ func (n *NodeController) uninitSelf(node *corev1.Node) error { networkName, n.networkID, node.Name) // Remove existing nodes - nodes, err := n.kube.GetNodes() + nodes, err := n.kube.GetNodesForWindows() if err != nil { return fmt.Errorf("failed to get nodes: %v", err) } diff --git a/go-controller/hybrid-overlay/pkg/controller/ovn_node_linux.go b/go-controller/hybrid-overlay/pkg/controller/ovn_node_linux.go index 3c74239db0..df8a9559c8 100644 --- a/go-controller/hybrid-overlay/pkg/controller/ovn_node_linux.go +++ b/go-controller/hybrid-overlay/pkg/controller/ovn_node_linux.go @@ -261,7 +261,7 @@ func (n *NodeController) AddNode(node *corev1.Node) error { } else { // Make sure the local node has been initialized before adding a hybridOverlay remote node if atomic.LoadUint32(n.initState) < hotypes.DistributedRouterInitialized { - localNode, err := n.kube.GetNode(n.nodeName) + localNode, err := n.nodeLister.Get(n.nodeName) if err != nil { return fmt.Errorf("cannot get local node: %s: %w", n.nodeName, err) } diff --git a/go-controller/pkg/controllermanager/controller_manager.go b/go-controller/pkg/controllermanager/controller_manager.go index 06d88c4ce4..6b5f1e9f89 100644 --- a/go-controller/pkg/controllermanager/controller_manager.go +++ b/go-controller/pkg/controllermanager/controller_manager.go @@ -23,6 +23,7 @@ import ( libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/observability" @@ -438,7 +439,7 @@ func (cm *ControllerManager) Start(ctx context.Context) error { // with k=10, // for a cluster with 10 nodes, measurement of 1 in every 100 requests // for a cluster with 100 nodes, measurement of 1 in every 1000 requests - metrics.GetConfigDurationRecorder().Run(cm.nbClient, cm.kube, 10, time.Second*5, cm.stopChan) + recorders.GetConfigDurationRecorder().Run(cm.nbClient, cm.watchFactory, 10, time.Second*5, cm.stopChan) } cm.podRecorder.Run(cm.sbClient, cm.stopChan) diff --git a/go-controller/pkg/controllermanager/node_controller_manager_test.go b/go-controller/pkg/controllermanager/node_controller_manager_test.go index cf96448fe9..92d51a25d8 100644 --- a/go-controller/pkg/controllermanager/node_controller_manager_test.go +++ b/go-controller/pkg/controllermanager/node_controller_manager_test.go @@ -228,7 +228,7 @@ var _ = Describe("Healthcheck tests", func() { }, } nodeList := []*corev1.Node{node} - factoryMock.On("GetNode", nodeName).Return(nodeList[0], nil) + factoryMock.On("GetNodeForWindows", nodeName).Return(nodeList[0], nil) factoryMock.On("GetNodes").Return(nodeList, nil) factoryMock.On("UserDefinedNetworkInformer").Return(nil) factoryMock.On("ClusterUserDefinedNetworkInformer").Return(nil) diff --git a/go-controller/pkg/kube/annotator_test.go b/go-controller/pkg/kube/annotator_test.go index 0caa0956a0..4c66adb81c 100644 --- a/go-controller/pkg/kube/annotator_test.go +++ b/go-controller/pkg/kube/annotator_test.go @@ -79,7 +79,7 @@ var _ = Describe("Annotator", func() { err := nodeAnnot.Run() Expect(err).ToNot(HaveOccurred()) - node, err := kube.GetNode(nodeName) + node, err := kube.GetNodeForWindows(nodeName) Expect(err).ToNot(HaveOccurred()) // should contain initial annotations diff --git a/go-controller/pkg/kube/kube.go b/go-controller/pkg/kube/kube.go index 4171e398e2..7eccec3d7f 100644 --- a/go-controller/pkg/kube/kube.go +++ b/go-controller/pkg/kube/kube.go @@ -12,7 +12,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" @@ -62,12 +61,11 @@ type Interface interface { PatchNode(old, new *corev1.Node) error UpdateNodeStatus(node *corev1.Node) error UpdatePodStatus(pod *corev1.Pod) error - GetAnnotationsOnPod(namespace, name string) (map[string]string, error) - GetNodes() ([]*corev1.Node, error) - GetNamespaces(labelSelector metav1.LabelSelector) ([]*corev1.Namespace, error) - GetPods(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) - GetPod(namespace, name string) (*corev1.Pod, error) - GetNode(name string) (*corev1.Node, error) + // GetPodsForDBChecker should only be used by legacy DB checker. Use watchFactory instead to get pods. + GetPodsForDBChecker(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) + // GetNodeForWindows should only be used for windows hybrid overlay binary and never in linux code + GetNodeForWindows(name string) (*corev1.Node, error) + GetNodesForWindows() ([]*corev1.Node, error) Events() kv1core.EventInterface } @@ -201,7 +199,7 @@ func (k *Kube) SetAnnotationsOnService(namespace, name string, annotations map[s // SetTaintOnNode tries to add a new taint to the node. If the taint already exists, it doesn't do anything. func (k *Kube) SetTaintOnNode(nodeName string, taint *corev1.Taint) error { - node, err := k.GetNode(nodeName) + node, err := k.GetNodeForWindows(nodeName) if err != nil { klog.Errorf("Unable to retrieve node %s for tainting %s: %v", nodeName, taint.ToString(), err) return err @@ -234,7 +232,7 @@ func (k *Kube) SetTaintOnNode(nodeName string, taint *corev1.Taint) error { // RemoveTaintFromNode removes all the taints that have the same key and effect from the node. // If the taint doesn't exist, it doesn't do anything. func (k *Kube) RemoveTaintFromNode(nodeName string, taint *corev1.Taint) error { - node, err := k.GetNode(nodeName) + node, err := k.GetNodeForWindows(nodeName) if err != nil { klog.Errorf("Unable to retrieve node %s for tainting %s: %v", nodeName, taint.ToString(), err) return err @@ -324,32 +322,8 @@ func (k *Kube) UpdatePodStatus(pod *corev1.Pod) error { return err } -// GetAnnotationsOnPod obtains the pod annotations from kubernetes apiserver, given the name and namespace -func (k *Kube) GetAnnotationsOnPod(namespace, name string) (map[string]string, error) { - pod, err := k.KClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return pod.ObjectMeta.Annotations, nil -} - -// GetNamespaces returns the list of all Namespace objects matching the labelSelector -func (k *Kube) GetNamespaces(labelSelector metav1.LabelSelector) ([]*corev1.Namespace, error) { - list := []*corev1.Namespace{} - err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { - return k.KClient.CoreV1().Namespaces().List(ctx, opts) - }).EachListItem(context.TODO(), metav1.ListOptions{ - LabelSelector: labels.Set(labelSelector.MatchLabels).String(), - ResourceVersion: "0", - }, func(obj runtime.Object) error { - list = append(list, obj.(*corev1.Namespace)) - return nil - }) - return list, err -} - -// GetPods returns the list of all Pod objects in a namespace matching the options -func (k *Kube) GetPods(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) { +// GetPodsForDBChecker returns the list of all Pod objects in a namespace matching the options. Only used by the legacy db checker. +func (k *Kube) GetPodsForDBChecker(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) { list := []*corev1.Pod{} opts.ResourceVersion = "0" err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { @@ -361,13 +335,8 @@ func (k *Kube) GetPods(namespace string, opts metav1.ListOptions) ([]*corev1.Pod return list, err } -// GetPod obtains the pod from kubernetes apiserver, given the name and namespace -func (k *Kube) GetPod(namespace, name string) (*corev1.Pod, error) { - return k.KClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) -} - -// GetNodes returns the list of all Node objects from kubernetes -func (k *Kube) GetNodes() ([]*corev1.Node, error) { +// GetNodesForWindows returns the list of all Node objects from kubernetes. Only used by windows binary. +func (k *Kube) GetNodesForWindows() ([]*corev1.Node, error) { list := []*corev1.Node{} err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { return k.KClient.CoreV1().Nodes().List(ctx, opts) @@ -380,8 +349,8 @@ func (k *Kube) GetNodes() ([]*corev1.Node, error) { return list, err } -// GetNode returns the Node resource from kubernetes apiserver, given its name -func (k *Kube) GetNode(name string) (*corev1.Node, error) { +// GetNodeForWindows returns the Node resource from kubernetes apiserver, given its name. Only used by windows binary. +func (k *Kube) GetNodeForWindows(name string) (*corev1.Node, error) { return k.KClient.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}) } diff --git a/go-controller/pkg/kube/kube_test.go b/go-controller/pkg/kube/kube_test.go index 4741cef0ed..d93119ff3a 100644 --- a/go-controller/pkg/kube/kube_test.go +++ b/go-controller/pkg/kube/kube_test.go @@ -96,7 +96,7 @@ var _ = Describe("Kube", func() { err := kube.SetTaintOnNode(node.Name, &taint) Expect(err).ToNot(HaveOccurred()) - updatedNode, err := kube.GetNode(node.Name) + updatedNode, err := kube.GetNodeForWindows(node.Name) Expect(err).ToNot(HaveOccurred()) Expect(updatedNode.Spec.Taints).To(Equal([]corev1.Taint{taint})) }) diff --git a/go-controller/pkg/kube/mocks/Interface.go b/go-controller/pkg/kube/mocks/Interface.go index 6631ca44c0..594d33d699 100644 --- a/go-controller/pkg/kube/mocks/Interface.go +++ b/go-controller/pkg/kube/mocks/Interface.go @@ -37,72 +37,12 @@ func (_m *Interface) Events() v1.EventInterface { return r0 } -// GetAnnotationsOnPod provides a mock function with given fields: namespace, name -func (_m *Interface) GetAnnotationsOnPod(namespace string, name string) (map[string]string, error) { - ret := _m.Called(namespace, name) - - if len(ret) == 0 { - panic("no return value specified for GetAnnotationsOnPod") - } - - var r0 map[string]string - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (map[string]string, error)); ok { - return rf(namespace, name) - } - if rf, ok := ret.Get(0).(func(string, string) map[string]string); ok { - r0 = rf(namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]string) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetNamespaces provides a mock function with given fields: labelSelector -func (_m *Interface) GetNamespaces(labelSelector metav1.LabelSelector) ([]*corev1.Namespace, error) { - ret := _m.Called(labelSelector) - - if len(ret) == 0 { - panic("no return value specified for GetNamespaces") - } - - var r0 []*corev1.Namespace - var r1 error - if rf, ok := ret.Get(0).(func(metav1.LabelSelector) ([]*corev1.Namespace, error)); ok { - return rf(labelSelector) - } - if rf, ok := ret.Get(0).(func(metav1.LabelSelector) []*corev1.Namespace); ok { - r0 = rf(labelSelector) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*corev1.Namespace) - } - } - - if rf, ok := ret.Get(1).(func(metav1.LabelSelector) error); ok { - r1 = rf(labelSelector) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetNode provides a mock function with given fields: name -func (_m *Interface) GetNode(name string) (*corev1.Node, error) { +func (_m *Interface) GetNodeForWindows(name string) (*corev1.Node, error) { ret := _m.Called(name) if len(ret) == 0 { - panic("no return value specified for GetNode") + panic("no return value specified for GetNodeForWindows") } var r0 *corev1.Node @@ -127,12 +67,12 @@ func (_m *Interface) GetNode(name string) (*corev1.Node, error) { return r0, r1 } -// GetNodes provides a mock function with given fields: -func (_m *Interface) GetNodes() ([]*corev1.Node, error) { +// GetNodesForWindows provides a mock function with given fields: +func (_m *Interface) GetNodesForWindows() ([]*corev1.Node, error) { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for GetNodes") + panic("no return value specified for GetNodesForWindows") } var r0 []*corev1.Node @@ -157,42 +97,12 @@ func (_m *Interface) GetNodes() ([]*corev1.Node, error) { return r0, r1 } -// GetPod provides a mock function with given fields: namespace, name -func (_m *Interface) GetPod(namespace string, name string) (*corev1.Pod, error) { - ret := _m.Called(namespace, name) - - if len(ret) == 0 { - panic("no return value specified for GetPod") - } - - var r0 *corev1.Pod - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (*corev1.Pod, error)); ok { - return rf(namespace, name) - } - if rf, ok := ret.Get(0).(func(string, string) *corev1.Pod); ok { - r0 = rf(namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.Pod) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetPods provides a mock function with given fields: namespace, opts -func (_m *Interface) GetPods(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) { +func (_m *Interface) GetPodsForDBChecker(namespace string, opts metav1.ListOptions) ([]*corev1.Pod, error) { ret := _m.Called(namespace, opts) if len(ret) == 0 { - panic("no return value specified for GetPods") + panic("no return value specified for GetPodsForDBChecker") } var r0 []*corev1.Pod diff --git a/go-controller/pkg/kube/mocks/InterfaceOVN.go b/go-controller/pkg/kube/mocks/InterfaceOVN.go index 14a8a33af6..18e93ed800 100644 --- a/go-controller/pkg/kube/mocks/InterfaceOVN.go +++ b/go-controller/pkg/kube/mocks/InterfaceOVN.go @@ -91,36 +91,6 @@ func (_m *InterfaceOVN) Events() corev1.EventInterface { return r0 } -// GetAnnotationsOnPod provides a mock function with given fields: namespace, name -func (_m *InterfaceOVN) GetAnnotationsOnPod(namespace string, name string) (map[string]string, error) { - ret := _m.Called(namespace, name) - - if len(ret) == 0 { - panic("no return value specified for GetAnnotationsOnPod") - } - - var r0 map[string]string - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (map[string]string, error)); ok { - return rf(namespace, name) - } - if rf, ok := ret.Get(0).(func(string, string) map[string]string); ok { - r0 = rf(namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]string) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetEgressFirewalls provides a mock function with given fields: func (_m *InterfaceOVN) GetEgressFirewalls() ([]*egressfirewallv1.EgressFirewall, error) { ret := _m.Called() @@ -211,42 +181,12 @@ func (_m *InterfaceOVN) GetEgressIPs() ([]*egressipv1.EgressIP, error) { return r0, r1 } -// GetNamespaces provides a mock function with given fields: labelSelector -func (_m *InterfaceOVN) GetNamespaces(labelSelector metav1.LabelSelector) ([]*apicorev1.Namespace, error) { - ret := _m.Called(labelSelector) - - if len(ret) == 0 { - panic("no return value specified for GetNamespaces") - } - - var r0 []*apicorev1.Namespace - var r1 error - if rf, ok := ret.Get(0).(func(metav1.LabelSelector) ([]*apicorev1.Namespace, error)); ok { - return rf(labelSelector) - } - if rf, ok := ret.Get(0).(func(metav1.LabelSelector) []*apicorev1.Namespace); ok { - r0 = rf(labelSelector) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*apicorev1.Namespace) - } - } - - if rf, ok := ret.Get(1).(func(metav1.LabelSelector) error); ok { - r1 = rf(labelSelector) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetNode provides a mock function with given fields: name -func (_m *InterfaceOVN) GetNode(name string) (*apicorev1.Node, error) { +func (_m *InterfaceOVN) GetNodeForWindows(name string) (*apicorev1.Node, error) { ret := _m.Called(name) if len(ret) == 0 { - panic("no return value specified for GetNode") + panic("no return value specified for GetNodeForWindows") } var r0 *apicorev1.Node @@ -271,8 +211,8 @@ func (_m *InterfaceOVN) GetNode(name string) (*apicorev1.Node, error) { return r0, r1 } -// GetNodes provides a mock function with given fields: -func (_m *InterfaceOVN) GetNodes() ([]*apicorev1.Node, error) { +// GetNodesForWindows provides a mock function with given fields: +func (_m *InterfaceOVN) GetNodesForWindows() ([]*apicorev1.Node, error) { ret := _m.Called() if len(ret) == 0 { @@ -301,42 +241,12 @@ func (_m *InterfaceOVN) GetNodes() ([]*apicorev1.Node, error) { return r0, r1 } -// GetPod provides a mock function with given fields: namespace, name -func (_m *InterfaceOVN) GetPod(namespace string, name string) (*apicorev1.Pod, error) { - ret := _m.Called(namespace, name) - - if len(ret) == 0 { - panic("no return value specified for GetPod") - } - - var r0 *apicorev1.Pod - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (*apicorev1.Pod, error)); ok { - return rf(namespace, name) - } - if rf, ok := ret.Get(0).(func(string, string) *apicorev1.Pod); ok { - r0 = rf(namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*apicorev1.Pod) - } - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetPods provides a mock function with given fields: namespace, opts -func (_m *InterfaceOVN) GetPods(namespace string, opts metav1.ListOptions) ([]*apicorev1.Pod, error) { +func (_m *InterfaceOVN) GetPodsForDBChecker(namespace string, opts metav1.ListOptions) ([]*apicorev1.Pod, error) { ret := _m.Called(namespace, opts) if len(ret) == 0 { - panic("no return value specified for GetPods") + panic("no return value specified for GetPodsForDBChecker") } var r0 []*apicorev1.Pod diff --git a/go-controller/pkg/metrics/cluster_manager.go b/go-controller/pkg/metrics/cluster_manager.go index 3acba72759..f97a338b89 100644 --- a/go-controller/pkg/metrics/cluster_manager.go +++ b/go-controller/pkg/metrics/cluster_manager.go @@ -7,28 +7,29 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" ) var registerClusterManagerBaseMetrics sync.Once // MetricClusterManagerLeader identifies whether this instance of ovnkube-cluster-manager is a leader or not var MetricClusterManagerLeader = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "leader", Help: "Identifies whether the instance of ovnkube-cluster-manager is a leader(1) or not(0).", }) var MetricClusterManagerReadyDuration = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "ready_duration_seconds", Help: "The duration for the cluster manager to get to ready state", }) var metricV4HostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "num_v4_host_subnets", Help: "The total number of v4 host subnets possible per network"}, []string{ @@ -37,8 +38,8 @@ var metricV4HostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricV6HostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "num_v6_host_subnets", Help: "The total number of v6 host subnets possible per network"}, []string{ @@ -47,8 +48,8 @@ var metricV6HostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricV4AllocatedHostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "allocated_v4_host_subnets", Help: "The total number of v4 host subnets currently allocated per network"}, []string{ @@ -57,8 +58,8 @@ var metricV4AllocatedHostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOp ) var metricV6AllocatedHostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "allocated_v6_host_subnets", Help: "The total number of v6 host subnets currently allocated per network"}, []string{ @@ -68,22 +69,22 @@ var metricV6AllocatedHostSubnetCount = prometheus.NewGaugeVec(prometheus.GaugeOp /** EgressIP metrics recorded from cluster-manager begins**/ var metricEgressIPCount = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "num_egress_ips", Help: "The number of defined egress IP addresses", }) var metricEgressIPNodeUnreacheableCount = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "egress_ips_node_unreachable_total", Help: "The total number of times assigned egress IP(s) were unreachable"}, ) var metricEgressIPRebalanceCount = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "egress_ips_rebalance_total", Help: "The total number of times assigned egress IP(s) needed to be moved to a different node"}, ) @@ -98,8 +99,8 @@ func RegisterClusterManagerBase() { prometheus.MustRegister(MetricClusterManagerReadyDuration) prometheus.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemClusterManager, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, Name: "build_info", Help: "A metric with a constant '1' value labeled by version, revision, branch, " + "and go version from which ovnkube was built and when and who built it", diff --git a/go-controller/pkg/metrics/metrics.go b/go-controller/pkg/metrics/metrics.go index 847619b8dd..ea86a65c0d 100644 --- a/go-controller/pkg/metrics/metrics.go +++ b/go-controller/pkg/metrics/metrics.go @@ -27,22 +27,11 @@ import ( libovsdbclient "github.com/ovn-org/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) const ( - MetricOvnkubeNamespace = "ovnkube" - MetricOvnkubeSubsystemController = "controller" - MetricOvnkubeSubsystemClusterManager = "clustermanager" - MetricOvnkubeSubsystemNode = "node" - MetricOvnNamespace = "ovn" - MetricOvnSubsystemDB = "db" - MetricOvnSubsystemNorthd = "northd" - MetricOvnSubsystemController = "controller" - MetricOvsNamespace = "ovs" - MetricOvsSubsystemVswitchd = "vswitchd" - MetricOvsSubsystemDB = "db" - ovnNorthd = "ovn-northd" ovnController = "ovn-controller" ovsVswitchd = "ovs-vswitchd" @@ -82,7 +71,7 @@ type stopwatchStatistics struct { // resource reached the maximum retry limit and will not be retried. This metric doesn't // need Subsystem string since it is applicable for both master and node. var MetricResourceRetryFailuresCount = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, + Namespace: types.MetricOvnkubeNamespace, Name: "resource_retry_failures_total", Help: "The total number of times processing a Kubernetes resource reached the maximum retry limit and was no longer processed", }) diff --git a/go-controller/pkg/metrics/node.go b/go-controller/pkg/metrics/node.go index 3b19c334d7..07e621fc97 100644 --- a/go-controller/pkg/metrics/node.go +++ b/go-controller/pkg/metrics/node.go @@ -7,13 +7,14 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" ) // MetricCNIRequestDuration is a prometheus metric that tracks the duration // of CNI requests var MetricCNIRequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemNode, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemNode, Name: "cni_request_duration_seconds", Help: "The duration of CNI server requests.", Buckets: prometheus.ExponentialBuckets(.1, 2, 15)}, @@ -22,23 +23,23 @@ var MetricCNIRequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOp ) var MetricNodeReadyDuration = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemNode, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemNode, Name: "ready_duration_seconds", Help: "The duration for the node to get to ready state.", }) var metricOvnNodePortEnabled = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemNode, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemNode, Name: "nodeport_enabled", Help: "Specifies if the node port is enabled on this node(1) or not(0).", }) // metric to get the size of ovnkube.log file var metricOvnKubeNodeLogFileSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemNode, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemNode, Name: "logfile_size_bytes", Help: "The size of ovnkube logfile on the node."}, []string{ @@ -56,8 +57,8 @@ func RegisterNodeMetrics(stopChan <-chan struct{}) { prometheus.MustRegister(metricOvnNodePortEnabled) prometheus.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemNode, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemNode, Name: "build_info", Help: "A metric with a constant '1' value labeled by version, revision, branch, " + "and go version from which ovnkube was built and when and who built it.", @@ -72,7 +73,7 @@ func RegisterNodeMetrics(stopChan <-chan struct{}) { }, func() float64 { return 1 }, )) - registerWorkqueueMetrics(MetricOvnkubeNamespace, MetricOvnkubeSubsystemNode) + registerWorkqueueMetrics(types.MetricOvnkubeNamespace, types.MetricOvnkubeSubsystemNode) if err := prometheus.Register(MetricResourceRetryFailuresCount); err != nil { if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { panic(err) diff --git a/go-controller/pkg/metrics/ovn.go b/go-controller/pkg/metrics/ovn.go index 4cce457ea5..63f057e38f 100644 --- a/go-controller/pkg/metrics/ovn.go +++ b/go-controller/pkg/metrics/ovn.go @@ -12,29 +12,30 @@ import ( libovsdbclient "github.com/ovn-org/libovsdb/client" ovsops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops/ovs" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/vswitchd" ) // ovnController Configuration metrics var metricRemoteProbeInterval = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "remote_probe_interval_seconds", Help: "The inactivity probe interval of the connection to the OVN SB DB.", }) var metricOpenFlowProbeInterval = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "openflow_probe_interval_seconds", Help: "The inactivity probe interval of the OpenFlow connection to the " + "OpenvSwitch integration bridge.", }) var metricMonitorAll = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "monitor_all", Help: "Specifies if ovn-controller should monitor all records of tables in OVN SB DB. " + "If set to false, it will conditionally monitor the records that " + @@ -42,8 +43,8 @@ var metricMonitorAll = prometheus.NewGauge(prometheus.GaugeOpts{ }) var metricEncapIP = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "encap_ip", Help: "A metric with a constant '1' value labeled by ipadress that " + "specifies the encapsulation ip address configured on that node.", @@ -54,8 +55,8 @@ var metricEncapIP = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricSbConnectionMethod = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "sb_connection_method", Help: "A metric with a constant '1' value labeled by connection_method that " + "specifies the ovn-remote value configured on that node.", @@ -66,8 +67,8 @@ var metricSbConnectionMethod = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricEncapType = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "encap_type", Help: "A metric with a constant '1' value labeled by type that " + "specifies the encapsulation type a chassis should use to " + @@ -79,8 +80,8 @@ var metricEncapType = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricBridgeMappings = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "bridge_mappings", Help: "A metric with a constant '1' value labeled by mapping that " + "specifies list of key-value pairs that map a physical network name " + @@ -92,8 +93,8 @@ var metricBridgeMappings = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOVNControllerSBDBConnection = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "southbound_database_connected", Help: "Specifies if OVN controller is connected to OVN southbound database (1) or not (0)", }) @@ -261,11 +262,11 @@ func setOvnControllerConfigurationMetrics(ovsDBClient libovsdbclient.Client) (er } openflowProbeField := openvSwitch.ExternalIDs["ovn-bridge-remote-probe-interval"] - openflowProbeVal := parseMetricToFloat(MetricOvnSubsystemController, "ovn-bridge-remote-probe-interval", openflowProbeField) + openflowProbeVal := parseMetricToFloat(types.MetricOvnSubsystemController, "ovn-bridge-remote-probe-interval", openflowProbeField) metricOpenFlowProbeInterval.Set(openflowProbeVal) remoteProbeField := openvSwitch.ExternalIDs["ovn-remote-probe-interval"] - remoteProbeValue := parseMetricToFloat(MetricOvnSubsystemController, "ovn-remote-probe-interval", remoteProbeField) + remoteProbeValue := parseMetricToFloat(types.MetricOvnSubsystemController, "ovn-remote-probe-interval", remoteProbeField) metricRemoteProbeInterval.Set(remoteProbeValue / 1000) var ovnMonitorValue float64 @@ -406,8 +407,8 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, getOvnControllerVersionInfo() ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "build_info", Help: "A metric with a constant '1' value labeled by version and library " + "from which ovn binaries were built", @@ -423,8 +424,8 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, ovnRegistry.MustRegister(metricOVNControllerSBDBConnection) ovnRegistry.MustRegister(prometheus.NewCounterFunc( prometheus.CounterOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "integration_bridge_openflow_total", Help: "The total number of OpenFlow flows in the integration bridge.", }, func() float64 { @@ -437,7 +438,7 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, for _, kvPair := range strings.Fields(stdout) { if strings.HasPrefix(kvPair, "flow_count=") { value := strings.Split(kvPair, "=")[1] - return parseMetricToFloat(MetricOvnSubsystemController, "integration_bridge_openflow_total", + return parseMetricToFloat(types.MetricOvnSubsystemController, "integration_bridge_openflow_total", value) } } @@ -445,8 +446,8 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, })) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "integration_bridge_patch_ports", Help: "Captures the number of patch ports that connect br-int OVS " + "bridge to physical OVS bridge and br-local OVS bridge.", @@ -456,8 +457,8 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, })) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemController, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemController, Name: "integration_bridge_geneve_ports", Help: "Captures the number of geneve ports that are on br-int OVS bridge.", }, @@ -475,11 +476,11 @@ func RegisterOvnControllerMetrics(ovsDBClient libovsdbclient.Client, ovnRegistry.MustRegister(metricBridgeMappings) // Register the ovn-controller coverage/show metrics componentCoverageShowMetricsMap[ovnController] = ovnControllerCoverageShowMetricsMap - registerCoverageShowMetrics(ovnController, MetricOvnNamespace, MetricOvnSubsystemController) + registerCoverageShowMetrics(ovnController, types.MetricOvnNamespace, types.MetricOvnSubsystemController) // Register the ovn-controller coverage/show metrics componentStopwatchShowMetricsMap[ovnController] = ovnControllerStopwatchShowMetricsMap - registerStopwatchShowMetrics(ovnController, MetricOvnNamespace, MetricOvnSubsystemController) + registerStopwatchShowMetrics(ovnController, types.MetricOvnNamespace, types.MetricOvnSubsystemController) // ovn-controller configuration metrics updater go ovnControllerConfigurationMetricsUpdater(ovsDBClient, diff --git a/go-controller/pkg/metrics/ovn_db.go b/go-controller/pkg/metrics/ovn_db.go index 8116a68732..ea206adc3c 100644 --- a/go-controller/pkg/metrics/ovn_db.go +++ b/go-controller/pkg/metrics/ovn_db.go @@ -14,12 +14,13 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) var metricOVNDBSessions = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "jsonrpc_server_sessions", Help: "Active number of JSON RPC Server sessions to the DB"}, []string{ @@ -28,8 +29,8 @@ var metricOVNDBSessions = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOVNDBMonitor = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "ovsdb_monitors", Help: "Number of OVSDB Monitors on the server"}, []string{ @@ -38,8 +39,8 @@ var metricOVNDBMonitor = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "db_size_bytes", Help: "The size of the database file associated with the OVN DB component."}, []string{ @@ -49,8 +50,8 @@ var metricDBSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // ClusterStatus metrics var metricDBClusterCID = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_id", Help: "A metric with a constant '1' value labeled by database name and cluster uuid"}, []string{ @@ -60,8 +61,8 @@ var metricDBClusterCID = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterSID = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_server_id", Help: "A metric with a constant '1' value labeled by database name, cluster uuid " + "and server uuid"}, @@ -73,8 +74,8 @@ var metricDBClusterSID = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterServerStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_server_status", Help: "A metric with a constant '1' value labeled by database name, cluster uuid, server uuid " + "server status"}, @@ -87,8 +88,8 @@ var metricDBClusterServerStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterServerRole = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_server_role", Help: "A metric with a constant '1' value labeled by database name, cluster uuid, server uuid " + "and server role"}, @@ -101,8 +102,8 @@ var metricDBClusterServerRole = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterTerm = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_term", Help: "A metric that returns the current election term value labeled by database name, cluster uuid, and " + "server uuid"}, @@ -114,8 +115,8 @@ var metricDBClusterTerm = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterServerVote = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_server_vote", Help: "A metric with a constant '1' value labeled by database name, cluster uuid, server uuid " + "and server vote"}, @@ -128,8 +129,8 @@ var metricDBClusterServerVote = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterElectionTimer = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_election_timer", Help: "A metric that returns the current election timer value labeled by database name, cluster uuid, " + "and server uuid"}, @@ -141,8 +142,8 @@ var metricDBClusterElectionTimer = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterLogIndexStart = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_log_index_start", Help: "A metric that returns the log entry index start value labeled by database name, cluster uuid, " + "and server uuid"}, @@ -154,8 +155,8 @@ var metricDBClusterLogIndexStart = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterLogIndexNext = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_log_index_next", Help: "A metric that returns the log entry index next value labeled by database name, cluster uuid, " + "and server uuid"}, @@ -167,8 +168,8 @@ var metricDBClusterLogIndexNext = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterLogNotCommitted = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_log_not_committed", Help: "A metric that returns the number of log entries not committed labeled by database name, cluster uuid, " + "and server uuid"}, @@ -180,8 +181,8 @@ var metricDBClusterLogNotCommitted = prometheus.NewGaugeVec(prometheus.GaugeOpts ) var metricDBClusterLogNotApplied = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_log_not_applied", Help: "A metric that returns the number of log entries not applied labeled by database name, cluster uuid, " + "and server uuid"}, @@ -193,8 +194,8 @@ var metricDBClusterLogNotApplied = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterConnIn = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_inbound_connections_total", Help: "A metric that returns the total number of inbound connections to the server labeled by " + "database name, cluster uuid, and server uuid"}, @@ -206,8 +207,8 @@ var metricDBClusterConnIn = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterConnOut = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_outbound_connections_total", Help: "A metric that returns the total number of outbound connections from the server labeled by " + "database name, cluster uuid, and server uuid"}, @@ -219,8 +220,8 @@ var metricDBClusterConnOut = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterConnInErr = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_inbound_connections_error_total", Help: "A metric that returns the total number of failed inbound connections to the server labeled by " + " database name, cluster uuid, and server uuid"}, @@ -232,8 +233,8 @@ var metricDBClusterConnInErr = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricDBClusterConnOutErr = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "cluster_outbound_connections_error_total", Help: "A metric that returns the total number of failed outbound connections from the server labeled by " + "database name, cluster uuid, and server uuid"}, @@ -382,8 +383,8 @@ func RegisterOvnDBMetrics(clientset kubernetes.Interface, k8sNodeName string, st ovnRegistry.MustRegister(metricOVNDBSessions) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "build_info", Help: "A metric with a constant '1' value labeled by ovsdb-server version and " + "NB and SB schema version", diff --git a/go-controller/pkg/metrics/ovn_northd.go b/go-controller/pkg/metrics/ovn_northd.go index ae2afe45c8..e72c89fdd3 100644 --- a/go-controller/pkg/metrics/ovn_northd.go +++ b/go-controller/pkg/metrics/ovn_northd.go @@ -11,6 +11,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -128,8 +129,8 @@ func RegisterOvnNorthdMetrics(clientset kubernetes.Interface, k8sNodeName string getOvnNorthdVersionInfo() ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemNorthd, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemNorthd, Name: "build_info", Help: "A metric with a constant '1' value labeled by version and library " + "from which ovn binaries were built", @@ -142,8 +143,8 @@ func RegisterOvnNorthdMetrics(clientset kubernetes.Interface, k8sNodeName string )) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemNorthd, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemNorthd, Name: "status", Help: "Specifies whether this instance of ovn-northd is standby(0) or active(1) or paused(2).", }, func() float64 { @@ -169,8 +170,8 @@ func RegisterOvnNorthdMetrics(clientset kubernetes.Interface, k8sNodeName string )) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemNorthd, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemNorthd, Name: "nb_connection_status", Help: "Specifies nb-connection-status of ovn-northd, not connected(0) or connected(1).", }, func() float64 { @@ -179,8 +180,8 @@ func RegisterOvnNorthdMetrics(clientset kubernetes.Interface, k8sNodeName string )) ovnRegistry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemNorthd, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemNorthd, Name: "sb_connection_status", Help: "Specifies sb-connection-status of ovn-northd, not connected(0) or connected(1).", }, func() float64 { @@ -190,11 +191,11 @@ func RegisterOvnNorthdMetrics(clientset kubernetes.Interface, k8sNodeName string // Register the ovn-northd coverage/show metrics with prometheus componentCoverageShowMetricsMap[ovnNorthd] = ovnNorthdCoverageShowMetricsMap - registerCoverageShowMetrics(ovnNorthd, MetricOvnNamespace, MetricOvnSubsystemNorthd) + registerCoverageShowMetrics(ovnNorthd, types.MetricOvnNamespace, types.MetricOvnSubsystemNorthd) go coverageShowMetricsUpdater(ovnNorthd, stopChan) // Register the ovn-northd stopwatch/show metrics with prometheus componentStopwatchShowMetricsMap[ovnNorthd] = ovnNorthdStopwatchShowMetricsMap - registerStopwatchShowMetrics(ovnNorthd, MetricOvnNamespace, MetricOvnSubsystemNorthd) + registerStopwatchShowMetrics(ovnNorthd, types.MetricOvnNamespace, types.MetricOvnSubsystemNorthd) go stopwatchShowMetricsUpdater(ovnNorthd, stopChan) } diff --git a/go-controller/pkg/metrics/ovnkube_controller.go b/go-controller/pkg/metrics/ovnkube_controller.go index b73a0ad8de..a4cb9fd693 100644 --- a/go-controller/pkg/metrics/ovnkube_controller.go +++ b/go-controller/pkg/metrics/ovnkube_controller.go @@ -3,8 +3,6 @@ package metrics import ( "errors" "fmt" - "hash/fnv" - "math" "runtime" "strconv" "sync" @@ -21,14 +19,13 @@ import ( "github.com/ovn-org/libovsdb/cache" libovsdbclient "github.com/ovn-org/libovsdb/client" "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -37,16 +34,16 @@ import ( // read from SB DB. This is registered within func RunTimestamp in order to allow gathering this // metric on the fly when metrics are scraped. var metricNbE2eTimestamp = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nb_e2e_timestamp", Help: "The current e2e-timestamp value as written to the northbound database"}, ) // metricDbTimestamp is the UNIX timestamp seen in NB and SB DBs. var metricDbTimestamp = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemDB, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemDB, Name: "e2e_timestamp", Help: "The current e2e-timestamp value as observed in this instance of the database"}, []string{ @@ -57,8 +54,8 @@ var metricDbTimestamp = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // metricPodCreationLatency is the time between a pod being scheduled and // completing its logical switch port configuration. var metricPodCreationLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_creation_latency_seconds", Help: "The duration between a pod being scheduled and completing its logical switch port configuration", Buckets: prometheus.ExponentialBuckets(.1, 2, 15), @@ -66,8 +63,8 @@ var metricPodCreationLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ // MetricResourceUpdateCount is the number of times a particular resource's UpdateFunc has been called. var MetricResourceUpdateCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "resource_update_total", Help: "The number of times a given resource event (add, update, or delete) has been handled"}, []string{ @@ -79,8 +76,8 @@ var MetricResourceUpdateCount = prometheus.NewCounterVec(prometheus.CounterOpts{ // MetricResourceAddLatency is the time taken to complete resource update by an handler. // This measures the latency for all of the handlers for a given resource. var MetricResourceAddLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "resource_add_latency_seconds", Help: "The duration to process all handlers for a given resource event - add.", Buckets: prometheus.ExponentialBuckets(.1, 2, 15)}, @@ -89,8 +86,8 @@ var MetricResourceAddLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ // MetricResourceUpdateLatency is the time taken to complete resource update by an handler. // This measures the latency for all of the handlers for a given resource. var MetricResourceUpdateLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "resource_update_latency_seconds", Help: "The duration to process all handlers for a given resource event - update.", Buckets: prometheus.ExponentialBuckets(.1, 2, 15)}, @@ -99,8 +96,8 @@ var MetricResourceUpdateLatency = prometheus.NewHistogram(prometheus.HistogramOp // MetricResourceDeleteLatency is the time taken to complete resource update by an handler. // This measures the latency for all of the handlers for a given resource. var MetricResourceDeleteLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "resource_delete_latency_seconds", Help: "The duration to process all handlers for a given resource event - delete.", Buckets: prometheus.ExponentialBuckets(.1, 2, 15)}, @@ -108,32 +105,32 @@ var MetricResourceDeleteLatency = prometheus.NewHistogram(prometheus.HistogramOp // MetricRequeueServiceCount is the number of times a particular service has been requeued. var MetricRequeueServiceCount = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "requeue_service_total", Help: "A metric that captures the number of times a service is requeued after failing to sync with OVN"}, ) // MetricSyncServiceCount is the number of times a particular service has been synced. var MetricSyncServiceCount = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "sync_service_total", Help: "A metric that captures the number of times a service is synced with OVN load balancers"}, ) // MetricSyncServiceLatency is the time taken to sync a service with the OVN load balancers. var MetricSyncServiceLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "sync_service_latency_seconds", Help: "The latency of syncing a service with the OVN load balancers", Buckets: prometheus.ExponentialBuckets(.1, 2, 15)}, ) var MetricOVNKubeControllerReadyDuration = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "ready_duration_seconds", Help: "The duration for the ovnkube-controller to get to ready state", }) @@ -141,8 +138,8 @@ var MetricOVNKubeControllerReadyDuration = prometheus.NewGauge(prometheus.GaugeO // MetricOVNKubeControllerSyncDuration is the time taken to complete initial Watch for different resource. // Resource name is in the label. var MetricOVNKubeControllerSyncDuration = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "sync_duration_seconds", Help: "The duration to sync and setup all handlers for a given resource"}, []string{ @@ -151,15 +148,15 @@ var MetricOVNKubeControllerSyncDuration = prometheus.NewGaugeVec(prometheus.Gaug // MetricOVNKubeControllerLeader identifies whether this instance of ovnkube-controller is a leader or not var MetricOVNKubeControllerLeader = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "leader", Help: "Identifies whether the instance of ovnkube-controller is a leader(1) or not(0).", }) var metricOvnKubeControllerLogFileSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "logfile_size_bytes", Help: "The size of ovnkube-controller log file."}, []string{ @@ -168,24 +165,24 @@ var metricOvnKubeControllerLogFileSize = prometheus.NewGaugeVec(prometheus.Gauge ) var metricEgressIPAssignLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "egress_ips_assign_latency_seconds", Help: "The latency of egress IP assignment to ovn nb database", Buckets: prometheus.ExponentialBuckets(.001, 2, 15), }) var metricEgressIPUnassignLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "egress_ips_unassign_latency_seconds", Help: "The latency of egress IP unassignment from ovn nb database", Buckets: prometheus.ExponentialBuckets(.001, 2, 15), }) var metricNetpolEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "network_policy_event_latency_seconds", Help: "The latency of full network policy event handling (create, delete)", Buckets: prometheus.ExponentialBuckets(.004, 2, 15)}, @@ -194,8 +191,8 @@ var metricNetpolEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOp }) var metricNetpolLocalPodEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "network_policy_local_pod_event_latency_seconds", Help: "The latency of local pod events handling (add, delete)", Buckets: prometheus.ExponentialBuckets(.002, 2, 15)}, @@ -204,8 +201,8 @@ var metricNetpolLocalPodEventLatency = prometheus.NewHistogramVec(prometheus.His }) var metricNetpolPeerNamespaceEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "network_policy_peer_namespace_event_latency_seconds", Help: "The latency of peer namespace events handling (add, delete)", Buckets: prometheus.ExponentialBuckets(.002, 2, 15)}, @@ -214,8 +211,8 @@ var metricNetpolPeerNamespaceEventLatency = prometheus.NewHistogramVec(prometheu }) var metricPodSelectorAddrSetPodEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_selector_address_set_pod_event_latency_seconds", Help: "The latency of peer pod events handling (add, delete)", Buckets: prometheus.ExponentialBuckets(.002, 2, 15)}, @@ -224,8 +221,8 @@ var metricPodSelectorAddrSetPodEventLatency = prometheus.NewHistogramVec(prometh }) var metricPodSelectorAddrSetNamespaceEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_selector_address_set_namespace_event_latency_seconds", Help: "The latency of peer namespace events handling (add, delete)", Buckets: prometheus.ExponentialBuckets(.002, 2, 15)}, @@ -234,8 +231,8 @@ var metricPodSelectorAddrSetNamespaceEventLatency = prometheus.NewHistogramVec(p }) var metricPodEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_event_latency_seconds", Help: "The latency of pod events handling (add, update, delete)", Buckets: prometheus.ExponentialBuckets(.002, 2, 15)}, @@ -244,51 +241,51 @@ var metricPodEventLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{ }) var metricEgressFirewallRuleCount = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "num_egress_firewall_rules", Help: "The number of egress firewall rules defined"}, ) var metricIPsecEnabled = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "ipsec_enabled", Help: "Specifies whether IPSec is enabled for this cluster(1) or not enabled for this cluster(0)", }) var metricEgressRoutingViaHost = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "egress_routing_via_host", Help: "Specifies whether egress gateway mode is via host networking stack(1) or not(0)", }) var metricEgressFirewallCount = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "num_egress_firewalls", Help: "The number of egress firewall policies", }) /** AdminNetworkPolicyMetrics Begin**/ var metricANPCount = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "admin_network_policies", Help: "The total number of admin network policies in the cluster", }) var metricBANPCount = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "baseline_admin_network_policies", Help: "The total number of baseline admin network policies in the cluster", }) var metricANPDBObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "admin_network_policies_db_objects", Help: "The total number of OVN NBDB objects (table_name) owned by AdminNetworkPolicy controller in the cluster"}, []string{ @@ -297,8 +294,8 @@ var metricANPDBObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricBANPDBObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "baseline_admin_network_policies_db_objects", Help: "The total number of OVN NBDB objects (table_name) owned by BaselineAdminNetworkPolicy controller in the cluster"}, []string{ @@ -310,64 +307,37 @@ var metricBANPDBObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // metricFirstSeenLSPLatency is the time between a pod first seen in OVN-Kubernetes and its Logical Switch Port is created var metricFirstSeenLSPLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_first_seen_lsp_created_duration_seconds", Help: "The duration between a pod first observed in OVN-Kubernetes and Logical Switch Port created", Buckets: prometheus.ExponentialBuckets(.01, 2, 15), }) var metricLSPPortBindingLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_lsp_created_port_binding_duration_seconds", Help: "The duration between a pods Logical Switch Port created and port binding observed in cache", Buckets: prometheus.ExponentialBuckets(.01, 2, 15), }) var metricPortBindingChassisLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_port_binding_port_binding_chassis_duration_seconds", Help: "The duration between a pods port binding observed and port binding chassis update observed in cache", Buckets: prometheus.ExponentialBuckets(.01, 2, 15), }) var metricPortBindingUpLatency = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "pod_port_binding_chassis_port_binding_up_duration_seconds", Help: "The duration between a pods port binding chassis update and port binding up observed in cache", Buckets: prometheus.ExponentialBuckets(.01, 2, 15), }) -var metricNetworkProgramming prometheus.ObserverVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, - Name: "network_programming_duration_seconds", - Help: "The duration to apply network configuration for a kind (e.g. pod, service, networkpolicy). " + - "Configuration includes add, update and delete events for each kind.", - Buckets: merge( - prometheus.LinearBuckets(0.25, 0.25, 2), // 0.25s, 0.50s - prometheus.LinearBuckets(1, 1, 59), // 1s, 2s, 3s, ... 59s - prometheus.LinearBuckets(60, 5, 12), // 60s, 65s, 70s, ... 115s - prometheus.LinearBuckets(120, 30, 11))}, // 2min, 2.5min, 3min, ..., 7min - []string{ - "kind", - }) - -var metricNetworkProgrammingOVN = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, - Name: "network_programming_ovn_duration_seconds", - Help: "The duration for OVN to apply network configuration", - Buckets: merge( - prometheus.LinearBuckets(0.25, 0.25, 2), // 0.25s, 0.50s - prometheus.LinearBuckets(1, 1, 59), // 1s, 2s, 3s, ... 59s - prometheus.LinearBuckets(60, 5, 12), // 60s, 65s, 70s, ... 115s - prometheus.LinearBuckets(120, 30, 11))}, // 2min, 2.5min, 3min, ..., 7min -) - const ( globalOptionsTimestampField = "e2e_timestamp" globalOptionsProbeIntervalField = "northd_probe_interval" @@ -381,8 +351,8 @@ func RegisterOVNKubeControllerBase() { prometheus.MustRegister(MetricOVNKubeControllerSyncDuration) prometheus.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "build_info", Help: "A metric with a constant '1' value labeled by version, revision, branch, " + "and go version from which ovnkube was built and when and who built it", @@ -410,11 +380,11 @@ func RegisterOVNKubeControllerPerformance(nbClient libovsdbclient.Client) { prometheus.MustRegister(MetricRequeueServiceCount) prometheus.MustRegister(MetricSyncServiceCount) prometheus.MustRegister(MetricSyncServiceLatency) - registerWorkqueueMetrics(MetricOvnkubeNamespace, MetricOvnkubeSubsystemController) + registerWorkqueueMetrics(types.MetricOvnkubeNamespace, types.MetricOvnkubeSubsystemController) prometheus.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnNamespace, - Subsystem: MetricOvnSubsystemNorthd, + Namespace: types.MetricOvnNamespace, + Subsystem: types.MetricOvnSubsystemNorthd, Name: "northd_probe_interval", Help: "The maximum number of milliseconds of idle time on connection to the OVN SB " + "and NB DB before sending an inactivity probe message", @@ -505,8 +475,8 @@ func RunTimestamp(stopChan <-chan struct{}, sbClient, nbClient libovsdbclient.Cl // cache when metrics HTTP endpoint is scraped. prometheus.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvnkubeNamespace, - Subsystem: MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "sb_e2e_timestamp", Help: "The current e2e-timestamp value as observed in the southbound database", }, func() float64 { @@ -916,521 +886,6 @@ func getPodUIDFromPortBinding(row *sbdb.PortBinding) kapimtypes.UID { return kapimtypes.UID(podUID) } -const ( - updateOVNMeasurementChSize = 500 - deleteOVNMeasurementChSize = 50 - processChSize = 1000 - nbGlobalTable = "NB_Global" - //fixme: remove when bug is fixed in OVN (Red Hat bugzilla bug number 2074019). Also, handle overflow event. - maxNbCfg = math.MaxUint32 - 1000 - maxMeasurementLifetime = 20 * time.Minute -) - -type ovnMeasurement struct { - // time just before ovsdb tx is called - startTimestamp time.Time - // time when the nbCfg value and its associated configuration is applied to all nodes - endTimestamp time.Time - // OVN measurement complete - start and end timestamps are valid - complete bool - // nb_cfg value that started the measurement - nbCfg int -} - -// measurement stores a measurement attempt through OVN-Kubernetes controller and optionally OVN -type measurement struct { - // kubernetes kind e.g. pod or service - kind string - // time when Add is executed - startTimestamp time.Time - // time when End is executed - endTimestamp time.Time - // if true endTimestamp is valid - end bool - // time when this measurement expires. Set during Add - expiresAt time.Time - // OVN measurement(s) via AddOVN - ovnMeasurements []ovnMeasurement -} - -// hvCfgUpdate holds the information received from OVN Northbound event handler -type hvCfgUpdate struct { - // timestamp is in milliseconds - timestamp int - hvCfg int -} - -type ConfigDurationRecorder struct { - // rate at which measurements are allowed. Probabilistically, 1 in every measurementRate - measurementRate uint64 - measurements map[string]measurement - // controls RW access to measurements map - measurementsMu sync.RWMutex - // channel to trigger processing a measurement following call to End func. Channel string is kind/namespace/name - triggerProcessCh chan string - enabled bool -} - -// global variable is needed because this functionality is accessed in many functions -var cdr *ConfigDurationRecorder - -// lock for accessing the cdr global variable -var cdrMutex sync.Mutex - -func GetConfigDurationRecorder() *ConfigDurationRecorder { - cdrMutex.Lock() - defer cdrMutex.Unlock() - if cdr == nil { - cdr = &ConfigDurationRecorder{} - } - return cdr -} - -var configDurationRegOnce sync.Once - -// Run monitors the config duration for OVN-Kube master to configure k8 kinds. A measurement maybe allowed and this is -// related to the number of k8 nodes, N [1] and by argument k [2] where there is a probability that 1 out of N*k -// measurement attempts are allowed. If k=0, all measurements are allowed. mUpdatePeriod determines the period to -// process and publish metrics -// [1] 1 0. The measurement rate is proportional to - // the number of nodes, N and argument k. 1 out of every N*k attempted measurements will succeed. - - // For the optional OVN measurement by calling AddOVN, when the CMS is about to make a transaction to configure - // whatever kind, a call to AddOVN function allows the caller to measure OVN duration. - // An ovsdb operation is returned to the caller of AddOVN, which they can bundle with their existing transactions - // sent to OVN which will tell OVN to measure how long it takes to configure all nodes with the config in the transaction. - // Config duration then waits for OVN to configure all nodes and calculates the time delta. - - // ** configuration duration recorder - caveats ** - // For the optional OVN recording, it does not give you an exact time duration for how long it takes to configure your - // k8 kind. When you are recording how long it takes OVN to complete your configuration to all nodes, other - // transactions may have occurred which may increases the overall time. You may also get longer processing times if one - // or more nodes are unavailable because we are measuring how long the functionality takes to apply to ALL nodes. - - // ** configuration duration recorder - How the duration of the config is measured within OVN ** - // We increment the nb_cfg integer value in the NB_Global table. - // ovn-northd notices the nb_cfg change and copies the nb_cfg value to SB_Global table field nb_cfg along with any - // other configuration that is changed in OVN Northbound database. - // All ovn-controllers detect nb_cfg value change and generate a 'barrier' on the openflow connection to the - // nodes ovs-vswitchd. Once ovn-controllers receive the 'barrier processed' reply from ovs-vswitchd which - // indicates that all relevant openflow operations associated with NB_Globals nb_cfg value have been - // propagated to the nodes OVS, it copies the SB_Global nb_cfg value to its Chassis_Private table nb_cfg record. - // ovn-northd detects changes to the Chassis_Private startRecords and computes the minimum nb_cfg for all Chassis_Private - // nb_cfg and stores this in NB_Global hv_cfg field along with a timestamp to field hv_cfg_timestamp which - // reflects the time when the slowest chassis catches up with the northbound configuration. - configDurationRegOnce.Do(func() { - prometheus.MustRegister(metricNetworkProgramming) - prometheus.MustRegister(metricNetworkProgrammingOVN) - }) - - cr.measurements = make(map[string]measurement) - // watch node count and adjust measurement rate if node count changes - cr.runMeasurementRateAdjuster(kube, k, time.Hour, stop) - // we currently do not clean the following channels up upon exit - cr.triggerProcessCh = make(chan string, processChSize) - updateOVNMeasurementCh := make(chan hvCfgUpdate, updateOVNMeasurementChSize) - deleteOVNMeasurementCh := make(chan int, deleteOVNMeasurementChSize) - go cr.processMeasurements(workerLoopPeriod, updateOVNMeasurementCh, deleteOVNMeasurementCh, stop) - - nbClient.Cache().AddEventHandler(&cache.EventHandlerFuncs{ - UpdateFunc: func(table string, old model.Model, new model.Model) { - if table != nbGlobalTable { - return - } - oldRow := old.(*nbdb.NBGlobal) - newRow := new.(*nbdb.NBGlobal) - - if oldRow.HvCfg != newRow.HvCfg && oldRow.HvCfgTimestamp != newRow.HvCfgTimestamp && newRow.HvCfgTimestamp > 0 { - select { - case updateOVNMeasurementCh <- hvCfgUpdate{hvCfg: newRow.HvCfg, timestamp: newRow.HvCfgTimestamp}: - default: - klog.Warning("Config duration recorder: unable to update OVN measurement") - select { - case deleteOVNMeasurementCh <- newRow.HvCfg: - default: - } - } - } - }, - }) - cr.enabled = true -} - -// Start allows the caller to attempt measurement of a control plane configuration duration, as a metric, -// the duration between functions Start and End. Optionally, if you wish to record OVN config duration, -// call AddOVN which will add the duration for OVN to apply the configuration to all nodes. -// The caller must pass kind,namespace,name which will be used to determine if the object -// is allowed to record. To allow no locking, each go routine that calls this function, can determine itself -// if it is allowed to measure. -// There is a mandatory two-step process to complete a measurement. -// Step 1) Call Start when you wish to begin a measurement - ideally when processing for the object starts -// Step 2) Call End which will complete a measurement -// Optionally, call AddOVN when you are making a transaction to OVN in order to add on the OVN duration to an existing -// measurement. This must be called between Start and End. Not every call to Start will result in a measurement -// and the rate of measurements depends on the number of nodes and function Run arg k. -// Only one measurement for a kind/namespace/name is allowed until the current measurement is Ended (via End) and -// processed. This is guaranteed by workqueues (even with multiple workers) and informer event handlers. -func (cr *ConfigDurationRecorder) Start(kind, namespace, name string) (time.Time, bool) { - if !cr.enabled { - return time.Time{}, false - } - kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) - if !cr.allowedToMeasure(kindNamespaceName) { - return time.Time{}, false - } - measurementTimestamp := time.Now() - cr.measurementsMu.Lock() - _, found := cr.measurements[kindNamespaceName] - // we only record for measurements that aren't in-progress - if !found { - cr.measurements[kindNamespaceName] = measurement{kind: kind, startTimestamp: measurementTimestamp, - expiresAt: measurementTimestamp.Add(maxMeasurementLifetime)} - } - cr.measurementsMu.Unlock() - return measurementTimestamp, !found -} - -// allowedToMeasure determines if we are allowed to measure or not. To avoid the cost of synchronisation by using locks, -// we use probability. For a value of kindNamespaceName that returns true, it will always return true. -func (cr *ConfigDurationRecorder) allowedToMeasure(kindNamespaceName string) bool { - if cr.measurementRate == 0 { - return true - } - // 1 in measurementRate chance of true - if hashToNumber(kindNamespaceName)%cr.measurementRate == 0 { - return true - } - return false -} - -func (cr *ConfigDurationRecorder) End(kind, namespace, name string) time.Time { - if !cr.enabled { - return time.Time{} - } - kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) - if !cr.allowedToMeasure(kindNamespaceName) { - return time.Time{} - } - measurementTimestamp := time.Now() - cr.measurementsMu.Lock() - if m, ok := cr.measurements[kindNamespaceName]; ok { - if !m.end { - m.end = true - m.endTimestamp = measurementTimestamp - cr.measurements[kindNamespaceName] = m - // if there are no OVN measurements, trigger immediate processing - if len(m.ovnMeasurements) == 0 { - select { - case cr.triggerProcessCh <- kindNamespaceName: - default: - // doesn't matter if channel is full because the measurement will be processed later anyway - } - } - } - } else { - // This can happen if Start was rejected for a resource because a measurement was in-progress for this - // kind/namespace/name, but during execution of this resource, the measurement was completed and now no record - // is found. - measurementTimestamp = time.Time{} - } - cr.measurementsMu.Unlock() - return measurementTimestamp -} - -// AddOVN adds OVN config duration to an existing recording - previously started by calling function Start -// It will return ovsdb operations which a user can add to existing operations they wish to track. -// Upon successful transaction of the operations to the ovsdb server, the user of this function must call a call-back -// function to lock-in the request to measure and report. Failure to call the call-back function, will result in no OVN -// measurement and no metrics are reported. AddOVN will result in a no-op if Start isn't called previously for the same -// kind/namespace/name. -// If multiple AddOVN is called between Start and End for the same kind/namespace/name, then the -// OVN durations will be summed and added to the total. There is an assumption that processing of kind/namespace/name is -// sequential -func (cr *ConfigDurationRecorder) AddOVN(nbClient libovsdbclient.Client, kind, namespace, name string) ( - []ovsdb.Operation, func(), time.Time, error) { - if !cr.enabled { - return []ovsdb.Operation{}, func() {}, time.Time{}, nil - } - kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) - if !cr.allowedToMeasure(kindNamespaceName) { - return []ovsdb.Operation{}, func() {}, time.Time{}, nil - } - cr.measurementsMu.RLock() - m, ok := cr.measurements[kindNamespaceName] - cr.measurementsMu.RUnlock() - if !ok { - // no measurement found, therefore no-op - return []ovsdb.Operation{}, func() {}, time.Time{}, nil - } - if m.end { - // existing measurement in-progress and not processed yet, therefore no-op - return []ovsdb.Operation{}, func() {}, time.Time{}, nil - } - nbGlobal := &nbdb.NBGlobal{} - nbGlobal, err := libovsdbops.GetNBGlobal(nbClient, nbGlobal) - if err != nil { - return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("failed to find OVN Northbound NB_Global table"+ - " entry: %v", err) - } - if nbGlobal.NbCfg < 0 { - return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("nb_cfg is negative, failed to add OVN measurement") - } - //stop recording if we are close to overflow - if nbGlobal.NbCfg > maxNbCfg { - return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("unable to measure OVN due to nb_cfg being close to overflow") - } - ops, err := nbClient.Where(nbGlobal).Mutate(nbGlobal, model.Mutation{ - Field: &nbGlobal.NbCfg, - Mutator: ovsdb.MutateOperationAdd, - Value: 1, - }) - if err != nil { - return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("failed to create update operation: %v", err) - } - ovnStartTimestamp := time.Now() - - return ops, func() { - // there can be a race condition here where we queue the wrong nbCfg value, but it is ok as long as it is - // less than or equal the hv_cfg value we see and this is the case because of atomic increments for nb_cfg - cr.measurementsMu.Lock() - m, ok = cr.measurements[kindNamespaceName] - if !ok { - klog.Errorf("Config duration recorder: expected a measurement entry. Call Start before AddOVN"+ - " for %s", kindNamespaceName) - cr.measurementsMu.Unlock() - return - } - m.ovnMeasurements = append(m.ovnMeasurements, ovnMeasurement{startTimestamp: ovnStartTimestamp, - nbCfg: nbGlobal.NbCfg + 1}) - cr.measurements[kindNamespaceName] = m - cr.measurementsMu.Unlock() - }, ovnStartTimestamp, nil -} - -// runMeasurementRateAdjuster will adjust the rate of measurements based on the number of nodes in the cluster and arg k -func (cr *ConfigDurationRecorder) runMeasurementRateAdjuster(kube kube.Interface, k float64, nodeCheckPeriod time.Duration, - stop <-chan struct{}) { - var currentMeasurementRate, newMeasurementRate uint64 - - updateMeasurementRate := func() { - if nodeCount, err := getNodeCount(kube); err != nil { - klog.Errorf("Config duration recorder: failed to update ticker duration considering node count: %v", err) - } else { - newMeasurementRate = uint64(math.Round(k * float64(nodeCount))) - if newMeasurementRate != currentMeasurementRate { - if newMeasurementRate > 0 { - currentMeasurementRate = newMeasurementRate - cr.measurementRate = newMeasurementRate - } - klog.V(5).Infof("Config duration recorder: updated measurement rate to approx 1 in"+ - " every %d requests", newMeasurementRate) - } - } - } - - // initial measurement rate adjustment - updateMeasurementRate() - - go func() { - nodeCheckTicker := time.NewTicker(nodeCheckPeriod) - for { - select { - case <-nodeCheckTicker.C: - updateMeasurementRate() - case <-stop: - nodeCheckTicker.Stop() - return - } - } - }() -} - -// processMeasurements manages the measurements map. It calculates metrics and cleans up finished or stale measurements -func (cr *ConfigDurationRecorder) processMeasurements(period time.Duration, updateOVNMeasurementCh chan hvCfgUpdate, - deleteOVNMeasurementCh chan int, stop <-chan struct{}) { - ticker := time.NewTicker(period) - var ovnKDelta, ovnDelta float64 - - for { - select { - case <-stop: - ticker.Stop() - return - // remove measurements if channel updateOVNMeasurementCh overflows, therefore we cannot trust existing measurements - case hvCfg := <-deleteOVNMeasurementCh: - cr.measurementsMu.Lock() - removeOVNMeasurements(cr.measurements, hvCfg) - cr.measurementsMu.Unlock() - case h := <-updateOVNMeasurementCh: - cr.measurementsMu.Lock() - cr.addHvCfg(h.hvCfg, h.timestamp) - cr.measurementsMu.Unlock() - // used for processing measurements that didn't require OVN measurement. Helps to keep measurement map small - case kindNamespaceName := <-cr.triggerProcessCh: - cr.measurementsMu.Lock() - m, ok := cr.measurements[kindNamespaceName] - if !ok { - klog.Errorf("Config duration recorder: expected measurement, but not found") - cr.measurementsMu.Unlock() - continue - } - if !m.end { - cr.measurementsMu.Unlock() - continue - } - if len(m.ovnMeasurements) != 0 { - cr.measurementsMu.Unlock() - continue - } - ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() - metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta) - klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller took %v"+ - " seconds. No OVN measurement.", kindNamespaceName, ovnKDelta) - delete(cr.measurements, kindNamespaceName) - cr.measurementsMu.Unlock() - // used for processing measurements that require OVN measurement or do not or are expired. - case <-ticker.C: - start := time.Now() - cr.measurementsMu.Lock() - // process and clean up measurements - for kindNamespaceName, m := range cr.measurements { - if start.After(m.expiresAt) { - // measurement may expire if OVN is degraded or End wasn't called - klog.Warningf("Config duration recorder: measurement expired for %s", kindNamespaceName) - delete(cr.measurements, kindNamespaceName) - continue - } - if !m.end { - // measurement didn't end yet, process later - continue - } - // for when no ovn measurements requested - if len(m.ovnMeasurements) == 0 { - ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() - metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta) - klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller"+ - " took %v seconds. No OVN measurement.", kindNamespaceName, ovnKDelta) - delete(cr.measurements, kindNamespaceName) - continue - } - // for each kind/namespace/name, there can be multiple calls to AddOVN between start and end - // we sum all the OVN durations and add it to the start and end duration - // first lets make sure all OVN measurements are finished - if complete := allOVNMeasurementsComplete(m.ovnMeasurements); !complete { - continue - } - - ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() - ovnDelta = calculateOVNDuration(m.ovnMeasurements) - metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta + ovnDelta) - metricNetworkProgrammingOVN.Observe(ovnDelta) - klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller took"+ - " %v seconds. OVN took %v seconds. Total took %v seconds", kindNamespaceName, ovnKDelta, - ovnDelta, ovnDelta+ovnKDelta) - delete(cr.measurements, kindNamespaceName) - } - cr.measurementsMu.Unlock() - } - } -} - -func (cr *ConfigDurationRecorder) addHvCfg(hvCfg, hvCfgTimestamp int) { - var altered bool - for i, m := range cr.measurements { - altered = false - for iOvnM, ovnM := range m.ovnMeasurements { - if ovnM.complete { - continue - } - if ovnM.nbCfg <= hvCfg { - ovnM.endTimestamp = time.UnixMilli(int64(hvCfgTimestamp)) - ovnM.complete = true - m.ovnMeasurements[iOvnM] = ovnM - altered = true - } - } - if altered { - cr.measurements[i] = m - } - } -} - -// removeOVNMeasurements remove any OVN measurements less than or equal argument hvCfg -func removeOVNMeasurements(measurements map[string]measurement, hvCfg int) { - for kindNamespaceName, m := range measurements { - var indexToDelete []int - for i, ovnM := range m.ovnMeasurements { - if ovnM.nbCfg <= hvCfg { - indexToDelete = append(indexToDelete, i) - } - } - if len(indexToDelete) == 0 { - continue - } - if len(indexToDelete) == len(m.ovnMeasurements) { - delete(measurements, kindNamespaceName) - } - for _, iDel := range indexToDelete { - m.ovnMeasurements = removeOVNMeasurement(m.ovnMeasurements, iDel) - } - measurements[kindNamespaceName] = m - } -} - -func removeOVNMeasurement(oM []ovnMeasurement, i int) []ovnMeasurement { - oM[i] = oM[len(oM)-1] - return oM[:len(oM)-1] -} -func hashToNumber(s string) uint64 { - h := fnv.New64() - h.Write([]byte(s)) - return h.Sum64() -} - -func calculateOVNDuration(ovnMeasurements []ovnMeasurement) float64 { - var totalDuration float64 - for _, oM := range ovnMeasurements { - if !oM.complete { - continue - } - totalDuration += oM.endTimestamp.Sub(oM.startTimestamp).Seconds() - } - return totalDuration -} - -func allOVNMeasurementsComplete(ovnMeasurements []ovnMeasurement) bool { - for _, oM := range ovnMeasurements { - if !oM.complete { - return false - } - } - return true -} - -// merge direct copy from k8 pkg/proxy/metrics/metrics.go -func merge(slices ...[]float64) []float64 { - result := make([]float64, 1) - for _, s := range slices { - result = append(result, s...) - } - return result -} - -func getNodeCount(kube kube.Interface) (int, error) { - nodes, err := kube.GetNodes() - if err != nil { - return 0, fmt.Errorf("unable to retrieve node list: %v", err) - } - return len(nodes), nil -} - // setNbE2eTimestamp return true if setting timestamp to NB global options is successful func setNbE2eTimestamp(ovnNBClient libovsdbclient.Client, timestamp int64) bool { // assumption that only first row is relevant in NB_Global table diff --git a/go-controller/pkg/metrics/ovs.go b/go-controller/pkg/metrics/ovs.go index b2cc1403a0..455142ae6b 100644 --- a/go-controller/pkg/metrics/ovs.go +++ b/go-controller/pkg/metrics/ovs.go @@ -18,6 +18,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" ovsops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops/ovs" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -27,15 +28,15 @@ var ( // ovs datapath Metrics var metricOvsDpTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_total", Help: "Represents total number of datapaths on the system.", }) var metricOvsDp = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp", Help: "A metric with a constant '1' value labeled by datapath " + "name present on the instance."}, @@ -46,8 +47,8 @@ var metricOvsDp = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpIfTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_if_total", Help: "Represents the number of ports connected to the datapath."}, []string{ @@ -56,8 +57,8 @@ var metricOvsDpIfTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpFlowsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_flows_total", Help: "Represents the number of flows in datapath."}, []string{ @@ -66,8 +67,8 @@ var metricOvsDpFlowsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpFlowsLookupHit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_flows_lookup_hit", Help: "Represents number of packets matching the existing flows " + "while processing incoming packets in the datapath."}, @@ -77,8 +78,8 @@ var metricOvsDpFlowsLookupHit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpFlowsLookupMissed = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_flows_lookup_missed", Help: "Represents the number of packets not matching any existing " + "flow and require user space processing."}, @@ -88,8 +89,8 @@ var metricOvsDpFlowsLookupMissed = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpFlowsLookupLost = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_flows_lookup_lost", Help: "number of packets destined for user space process but " + "subsequently dropped before reaching userspace."}, @@ -99,8 +100,8 @@ var metricOvsDpFlowsLookupLost = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpPacketsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_packets_total", Help: "Represents the total number of packets datapath processed " + "which is the sum of hit and missed."}, @@ -110,8 +111,8 @@ var metricOvsDpPacketsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsdpMasksHit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_masks_hit", Help: "Represents the total number of masks visited for matching incoming packets.", }, @@ -121,8 +122,8 @@ var metricOvsdpMasksHit = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpMasksTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_masks_total", Help: "Represents the number of masks in a datapath."}, []string{ @@ -131,8 +132,8 @@ var metricOvsDpMasksTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsDpMasksHitRatio = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "dp_masks_hit_ratio", Help: "Represents the average number of masks visited per packet " + "the ratio between hit and total number of packets processed by the datapath."}, @@ -143,16 +144,16 @@ var metricOvsDpMasksHitRatio = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // ovs bridge statistics & attributes metrics var metricOvsBridgeTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "bridge_total", Help: "Represents total number of OVS bridges on the system.", }, ) var metricOvsBridge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "bridge", Help: "A metric with a constant '1' value labeled by bridge name " + "present on the instance."}, @@ -162,8 +163,8 @@ var metricOvsBridge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsBridgePortsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "bridge_ports_total", Help: "Represents the number of OVS ports on the bridge."}, []string{ @@ -172,8 +173,8 @@ var metricOvsBridgePortsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ ) var metricOvsBridgeFlowsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "bridge_flows_total", Help: "Represents the number of OpenFlow flows on the OVS bridge."}, []string{ @@ -183,57 +184,57 @@ var metricOvsBridgeFlowsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ // ovs interface metrics var metricOvsInterfaceResetsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_resets_total", Help: "The number of link state changes observed by Open vSwitch interface(s).", }) var metricOvsInterfaceRxDroppedTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_rx_dropped_total", Help: "The total number of received packets dropped by Open vSwitch interface(s).", }) var metricOvsInterfaceTxDroppedTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_tx_dropped_total", Help: "The total number of transmitted packets dropped by Open vSwitch interface(s).", }) var metricOvsInterfaceRxErrorsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_rx_errors_total", Help: "The total number of received packets with errors by Open vSwitch interface(s).", }) var metricOvsInterfaceTxErrorsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_tx_errors_total", Help: "The total number of transmitted packets with errors by Open vSwitch interface(s).", }) var metricOvsInterfaceCollisionsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_collisions_total", Help: "The total number of packet collisions transmitted by Open vSwitch interface(s).", }) var metricOvsInterfaceTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interfaces_total", Help: "The total number of Open vSwitch interface(s) created for pods", }) var MetricOvsInterfaceUpWait = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "interface_up_wait_seconds_total", Help: "The total number of seconds that is required to wait for pod " + "Open vSwitch interface until its available", @@ -241,16 +242,16 @@ var MetricOvsInterfaceUpWait = prometheus.NewCounter(prometheus.CounterOpts{ // ovs memory metrics var metricOvsHandlersTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "handlers_total", Help: "Represents the number of handlers thread. This thread reads upcalls from dpif, " + "forwards each upcall's packet and possibly sets up a kernel flow as a cache.", }) var metricOvsRevalidatorsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "revalidators_total", Help: "Represents the number of revalidators thread. This thread processes datapath flows, " + "updates OpenFlow statistics, and updates or removes them if necessary.", @@ -258,16 +259,16 @@ var metricOvsRevalidatorsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ // ovs Hw offload metrics var metricOvsHwOffload = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "hw_offload", Help: "Represents whether netdev flow offload to hardware is enabled " + "or not -- false(0) and true(1).", }) var metricOvsTcPolicy = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, - Subsystem: MetricOvsSubsystemVswitchd, + Namespace: types.MetricOvsNamespace, + Subsystem: types.MetricOvsSubsystemVswitchd, Name: "tc_policy", Help: "Represents the policy used with HW offloading " + "-- none(0), skip_sw(1), and skip_hw(2).", @@ -310,15 +311,15 @@ func ovsDatapathLookupsMetrics(output, datapath string) { } switch elem[0] { case "hit": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_flows_lookup_hit", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_flows_lookup_hit", elem[1]) datapathPacketsTotal += value metricOvsDpFlowsLookupHit.WithLabelValues(datapath).Set(value) case "missed": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_flows_lookup_missed", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_flows_lookup_missed", elem[1]) datapathPacketsTotal += value metricOvsDpFlowsLookupMissed.WithLabelValues(datapath).Set(value) case "lost": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_flows_lookup_lost", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_flows_lookup_lost", elem[1]) metricOvsDpFlowsLookupLost.WithLabelValues(datapath).Set(value) } } @@ -335,13 +336,13 @@ func ovsDatapathMasksMetrics(output, datapath string) { } switch elem[0] { case "hit": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_masks_hit", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_masks_hit", elem[1]) metricOvsdpMasksHit.WithLabelValues(datapath).Set(value) case "total": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_masks_total", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_masks_total", elem[1]) metricOvsDpMasksTotal.WithLabelValues(datapath).Set(value) case "hit/pkt": - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_masks_hit_ratio", elem[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_masks_hit_ratio", elem[1]) metricOvsDpMasksHitRatio.WithLabelValues(datapath).Set(value) } } @@ -419,7 +420,7 @@ func setOvsDatapathMetrics(ovsAppctl ovsClient, datapaths []string) (err error) datapathPortCount++ } else if strings.HasPrefix(output, "flows:") { flowFields := strings.Fields(output) - value := parseMetricToFloat(MetricOvsSubsystemVswitchd, "dp_flows_total", flowFields[1]) + value := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "dp_flows_total", flowFields[1]) metricOvsDpFlowsTotal.WithLabelValues(datapathName).Set(value) } } @@ -504,7 +505,7 @@ func getOvsBridgeOpenFlowsCount(ovsOfctl ovsClient, bridgeName string) (float64, if strings.HasPrefix(kvPair, "flow_count=") { value := strings.Split(kvPair, "=")[1] metricName := bridgeName + "flows_total" - return parseMetricToFloat(MetricOvsSubsystemVswitchd, metricName, value), nil + return parseMetricToFloat(types.MetricOvsSubsystemVswitchd, metricName, value), nil } } return 0, fmt.Errorf("ovs-ofctl dump-aggregate %s output didn't contain "+ @@ -595,11 +596,11 @@ func setOvsMemoryMetrics(ovsVswitchdAppctl ovsClient) (err error) { for _, kvPair := range strings.Fields(stdout) { if strings.HasPrefix(kvPair, "handlers:") { value := strings.Split(kvPair, ":")[1] - count := parseMetricToFloat(MetricOvsSubsystemVswitchd, "handlers_total", value) + count := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "handlers_total", value) metricOvsHandlersTotal.Set(count) } else if strings.HasPrefix(kvPair, "revalidators:") { value := strings.Split(kvPair, ":")[1] - count := parseMetricToFloat(MetricOvsSubsystemVswitchd, "revalidators_total", value) + count := parseMetricToFloat(types.MetricOvsSubsystemVswitchd, "revalidators_total", value) metricOvsRevalidatorsTotal.Set(count) } } @@ -846,7 +847,7 @@ func registerOvsMetrics(ovsDBClient libovsdbclient.Client, metricsScrapeInterval getOvsVersionInfo(ovsDBClient) registry.MustRegister(prometheus.NewGaugeFunc( prometheus.GaugeOpts{ - Namespace: MetricOvsNamespace, + Namespace: types.MetricOvsNamespace, Name: "build_info", Help: "A metric with a constant '1' value labeled by ovs version.", ConstLabels: prometheus.Labels{ @@ -890,18 +891,18 @@ func registerOvsMetrics(ovsDBClient libovsdbclient.Client, metricsScrapeInterval registry.MustRegister(MetricOvsInterfaceUpWait) // Register the OVS coverage/show metrics componentCoverageShowMetricsMap[ovsVswitchd] = ovsVswitchdCoverageShowMetricsMap - registerCoverageShowMetrics(ovsVswitchd, MetricOvsNamespace, MetricOvsSubsystemVswitchd) + registerCoverageShowMetrics(ovsVswitchd, types.MetricOvsNamespace, types.MetricOvsSubsystemVswitchd) // When ovnkube-node is running in privileged mode, the hostPID will be set to true, // and therefore it can monitor OVS running on the host using PID. if !config.UnprivilegedMode { registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{ PidFn: prometheus.NewPidFileFn("/var/run/openvswitch/ovs-vswitchd.pid"), - Namespace: fmt.Sprintf("%s_%s", MetricOvsNamespace, MetricOvsSubsystemVswitchd), + Namespace: fmt.Sprintf("%s_%s", types.MetricOvsNamespace, types.MetricOvsSubsystemVswitchd), })) registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{ PidFn: prometheus.NewPidFileFn("/var/run/openvswitch/ovsdb-server.pid"), - Namespace: fmt.Sprintf("%s_%s", MetricOvsNamespace, MetricOvsSubsystemDB), + Namespace: fmt.Sprintf("%s_%s", types.MetricOvsNamespace, types.MetricOvsSubsystemDB), })) } diff --git a/go-controller/pkg/metrics/recorders/duration.go b/go-controller/pkg/metrics/recorders/duration.go new file mode 100644 index 0000000000..c0ae704e8f --- /dev/null +++ b/go-controller/pkg/metrics/recorders/duration.go @@ -0,0 +1,565 @@ +package recorders + +import ( + "fmt" + "hash/fnv" + "math" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "k8s.io/klog/v2" + + "github.com/ovn-org/libovsdb/cache" + libovsdbclient "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" +) + +const ( + updateOVNMeasurementChSize = 500 + deleteOVNMeasurementChSize = 50 + processChSize = 1000 + nbGlobalTable = "NB_Global" + //fixme: remove when bug is fixed in OVN (Red Hat bugzilla bug number 2074019). Also, handle overflow event. + maxNbCfg = math.MaxUint32 - 1000 + maxMeasurementLifetime = 20 * time.Minute +) + +var configDurationRegOnce sync.Once + +type ConfigDurationRecorder struct { + // rate at which measurements are allowed. Probabilistically, 1 in every measurementRate + measurementRate uint64 + measurements map[string]measurement + // controls RW access to measurements map + measurementsMu sync.RWMutex + // channel to trigger processing a measurement following call to End func. Channel string is kind/namespace/name + triggerProcessCh chan string + enabled bool +} + +type ovnMeasurement struct { + // time just before ovsdb tx is called + startTimestamp time.Time + // time when the nbCfg value and its associated configuration is applied to all nodes + endTimestamp time.Time + // OVN measurement complete - start and end timestamps are valid + complete bool + // nb_cfg value that started the measurement + nbCfg int +} + +// measurement stores a measurement attempt through OVN-Kubernetes controller and optionally OVN +type measurement struct { + // kubernetes kind e.g. pod or service + kind string + // time when Add is executed + startTimestamp time.Time + // time when End is executed + endTimestamp time.Time + // if true endTimestamp is valid + end bool + // time when this measurement expires. Set during Add + expiresAt time.Time + // OVN measurement(s) via AddOVN + ovnMeasurements []ovnMeasurement +} + +// hvCfgUpdate holds the information received from OVN Northbound event handler +type hvCfgUpdate struct { + // timestamp is in milliseconds + timestamp int + hvCfg int +} + +// global variable is needed because this functionality is accessed in many functions +var cdr *ConfigDurationRecorder + +// lock for accessing the cdr global variable +var cdrMutex sync.Mutex + +func GetConfigDurationRecorder() *ConfigDurationRecorder { + cdrMutex.Lock() + defer cdrMutex.Unlock() + if cdr == nil { + cdr = &ConfigDurationRecorder{} + } + return cdr +} + +// removeOVNMeasurements remove any OVN measurements less than or equal argument hvCfg +func removeOVNMeasurements(measurements map[string]measurement, hvCfg int) { + for kindNamespaceName, m := range measurements { + var indexToDelete []int + for i, ovnM := range m.ovnMeasurements { + if ovnM.nbCfg <= hvCfg { + indexToDelete = append(indexToDelete, i) + } + } + if len(indexToDelete) == 0 { + continue + } + if len(indexToDelete) == len(m.ovnMeasurements) { + delete(measurements, kindNamespaceName) + } + for _, iDel := range indexToDelete { + m.ovnMeasurements = removeOVNMeasurement(m.ovnMeasurements, iDel) + } + measurements[kindNamespaceName] = m + } +} + +var metricNetworkProgramming prometheus.ObserverVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, + Name: "network_programming_duration_seconds", + Help: "The duration to apply network configuration for a kind (e.g. pod, service, networkpolicy). " + + "Configuration includes add, update and delete events for each kind.", + Buckets: merge( + prometheus.LinearBuckets(0.25, 0.25, 2), // 0.25s, 0.50s + prometheus.LinearBuckets(1, 1, 59), // 1s, 2s, 3s, ... 59s + prometheus.LinearBuckets(60, 5, 12), // 60s, 65s, 70s, ... 115s + prometheus.LinearBuckets(120, 30, 11))}, // 2min, 2.5min, 3min, ..., 7min + []string{ + "kind", + }) + +var metricNetworkProgrammingOVN = prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, + Name: "network_programming_ovn_duration_seconds", + Help: "The duration for OVN to apply network configuration", + Buckets: merge( + prometheus.LinearBuckets(0.25, 0.25, 2), // 0.25s, 0.50s + prometheus.LinearBuckets(1, 1, 59), // 1s, 2s, 3s, ... 59s + prometheus.LinearBuckets(60, 5, 12), // 60s, 65s, 70s, ... 115s + prometheus.LinearBuckets(120, 30, 11))}, // 2min, 2.5min, 3min, ..., 7min +) + +// Run monitors the config duration for OVN-Kube master to configure k8 kinds. A measurement maybe allowed and this is +// related to the number of k8 nodes, N [1] and by argument k [2] where there is a probability that 1 out of N*k +// measurement attempts are allowed. If k=0, all measurements are allowed. mUpdatePeriod determines the period to +// process and publish metrics +// [1] 1 0. The measurement rate is proportional to + // the number of nodes, N and argument k. 1 out of every N*k attempted measurements will succeed. + + // For the optional OVN measurement by calling AddOVN, when the CMS is about to make a transaction to configure + // whatever kind, a call to AddOVN function allows the caller to measure OVN duration. + // An ovsdb operation is returned to the caller of AddOVN, which they can bundle with their existing transactions + // sent to OVN which will tell OVN to measure how long it takes to configure all nodes with the config in the transaction. + // Config duration then waits for OVN to configure all nodes and calculates the time delta. + + // ** configuration duration recorder - caveats ** + // For the optional OVN recording, it does not give you an exact time duration for how long it takes to configure your + // k8 kind. When you are recording how long it takes OVN to complete your configuration to all nodes, other + // transactions may have occurred which may increases the overall time. You may also get longer processing times if one + // or more nodes are unavailable because we are measuring how long the functionality takes to apply to ALL nodes. + + // ** configuration duration recorder - How the duration of the config is measured within OVN ** + // We increment the nb_cfg integer value in the NB_Global table. + // ovn-northd notices the nb_cfg change and copies the nb_cfg value to SB_Global table field nb_cfg along with any + // other configuration that is changed in OVN Northbound database. + // All ovn-controllers detect nb_cfg value change and generate a 'barrier' on the openflow connection to the + // nodes ovs-vswitchd. Once ovn-controllers receive the 'barrier processed' reply from ovs-vswitchd which + // indicates that all relevant openflow operations associated with NB_Globals nb_cfg value have been + // propagated to the nodes OVS, it copies the SB_Global nb_cfg value to its Chassis_Private table nb_cfg record. + // ovn-northd detects changes to the Chassis_Private startRecords and computes the minimum nb_cfg for all Chassis_Private + // nb_cfg and stores this in NB_Global hv_cfg field along with a timestamp to field hv_cfg_timestamp which + // reflects the time when the slowest chassis catches up with the northbound configuration. + configDurationRegOnce.Do(func() { + prometheus.MustRegister(metricNetworkProgramming) + prometheus.MustRegister(metricNetworkProgrammingOVN) + }) + + cr.measurements = make(map[string]measurement) + // watch node count and adjust measurement rate if node count changes + cr.runMeasurementRateAdjuster(wf, k, time.Hour, stop) + // we currently do not clean the following channels up upon exit + cr.triggerProcessCh = make(chan string, processChSize) + updateOVNMeasurementCh := make(chan hvCfgUpdate, updateOVNMeasurementChSize) + deleteOVNMeasurementCh := make(chan int, deleteOVNMeasurementChSize) + go cr.processMeasurements(workerLoopPeriod, updateOVNMeasurementCh, deleteOVNMeasurementCh, stop) + + nbClient.Cache().AddEventHandler(&cache.EventHandlerFuncs{ + UpdateFunc: func(table string, old model.Model, new model.Model) { + if table != nbGlobalTable { + return + } + oldRow := old.(*nbdb.NBGlobal) + newRow := new.(*nbdb.NBGlobal) + + if oldRow.HvCfg != newRow.HvCfg && oldRow.HvCfgTimestamp != newRow.HvCfgTimestamp && newRow.HvCfgTimestamp > 0 { + select { + case updateOVNMeasurementCh <- hvCfgUpdate{hvCfg: newRow.HvCfg, timestamp: newRow.HvCfgTimestamp}: + default: + klog.Warning("Config duration recorder: unable to update OVN measurement") + select { + case deleteOVNMeasurementCh <- newRow.HvCfg: + default: + } + } + } + }, + }) + cr.enabled = true +} + +// Start allows the caller to attempt measurement of a control plane configuration duration, as a metric, +// the duration between functions Start and End. Optionally, if you wish to record OVN config duration, +// call AddOVN which will add the duration for OVN to apply the configuration to all nodes. +// The caller must pass kind,namespace,name which will be used to determine if the object +// is allowed to record. To allow no locking, each go routine that calls this function, can determine itself +// if it is allowed to measure. +// There is a mandatory two-step process to complete a measurement. +// Step 1) Call Start when you wish to begin a measurement - ideally when processing for the object starts +// Step 2) Call End which will complete a measurement +// Optionally, call AddOVN when you are making a transaction to OVN in order to add on the OVN duration to an existing +// measurement. This must be called between Start and End. Not every call to Start will result in a measurement +// and the rate of measurements depends on the number of nodes and function Run arg k. +// Only one measurement for a kind/namespace/name is allowed until the current measurement is Ended (via End) and +// processed. This is guaranteed by workqueues (even with multiple workers) and informer event handlers. +func (cr *ConfigDurationRecorder) Start(kind, namespace, name string) (time.Time, bool) { + if !cr.enabled { + return time.Time{}, false + } + kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) + if !cr.allowedToMeasure(kindNamespaceName) { + return time.Time{}, false + } + measurementTimestamp := time.Now() + cr.measurementsMu.Lock() + _, found := cr.measurements[kindNamespaceName] + // we only record for measurements that aren't in-progress + if !found { + cr.measurements[kindNamespaceName] = measurement{kind: kind, startTimestamp: measurementTimestamp, + expiresAt: measurementTimestamp.Add(maxMeasurementLifetime)} + } + cr.measurementsMu.Unlock() + return measurementTimestamp, !found +} + +// allowedToMeasure determines if we are allowed to measure or not. To avoid the cost of synchronisation by using locks, +// we use probability. For a value of kindNamespaceName that returns true, it will always return true. +func (cr *ConfigDurationRecorder) allowedToMeasure(kindNamespaceName string) bool { + if cr.measurementRate == 0 { + return true + } + // 1 in measurementRate chance of true + if hashToNumber(kindNamespaceName)%cr.measurementRate == 0 { + return true + } + return false +} + +func (cr *ConfigDurationRecorder) End(kind, namespace, name string) time.Time { + if !cr.enabled { + return time.Time{} + } + kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) + if !cr.allowedToMeasure(kindNamespaceName) { + return time.Time{} + } + measurementTimestamp := time.Now() + cr.measurementsMu.Lock() + if m, ok := cr.measurements[kindNamespaceName]; ok { + if !m.end { + m.end = true + m.endTimestamp = measurementTimestamp + cr.measurements[kindNamespaceName] = m + // if there are no OVN measurements, trigger immediate processing + if len(m.ovnMeasurements) == 0 { + select { + case cr.triggerProcessCh <- kindNamespaceName: + default: + // doesn't matter if channel is full because the measurement will be processed later anyway + } + } + } + } else { + // This can happen if Start was rejected for a resource because a measurement was in-progress for this + // kind/namespace/name, but during execution of this resource, the measurement was completed and now no record + // is found. + measurementTimestamp = time.Time{} + } + cr.measurementsMu.Unlock() + return measurementTimestamp +} + +// AddOVN adds OVN config duration to an existing recording - previously started by calling function Start +// It will return ovsdb operations which a user can add to existing operations they wish to track. +// Upon successful transaction of the operations to the ovsdb server, the user of this function must call a call-back +// function to lock-in the request to measure and report. Failure to call the call-back function, will result in no OVN +// measurement and no metrics are reported. AddOVN will result in a no-op if Start isn't called previously for the same +// kind/namespace/name. +// If multiple AddOVN is called between Start and End for the same kind/namespace/name, then the +// OVN durations will be summed and added to the total. There is an assumption that processing of kind/namespace/name is +// sequential +func (cr *ConfigDurationRecorder) AddOVN(nbClient libovsdbclient.Client, kind, namespace, name string) ( + []ovsdb.Operation, func(), time.Time, error) { + if !cr.enabled { + return []ovsdb.Operation{}, func() {}, time.Time{}, nil + } + kindNamespaceName := fmt.Sprintf("%s/%s/%s", kind, namespace, name) + if !cr.allowedToMeasure(kindNamespaceName) { + return []ovsdb.Operation{}, func() {}, time.Time{}, nil + } + cr.measurementsMu.RLock() + m, ok := cr.measurements[kindNamespaceName] + cr.measurementsMu.RUnlock() + if !ok { + // no measurement found, therefore no-op + return []ovsdb.Operation{}, func() {}, time.Time{}, nil + } + if m.end { + // existing measurement in-progress and not processed yet, therefore no-op + return []ovsdb.Operation{}, func() {}, time.Time{}, nil + } + nbGlobal := &nbdb.NBGlobal{} + nbGlobal, err := libovsdbops.GetNBGlobal(nbClient, nbGlobal) + if err != nil { + return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("failed to find OVN Northbound NB_Global table"+ + " entry: %v", err) + } + if nbGlobal.NbCfg < 0 { + return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("nb_cfg is negative, failed to add OVN measurement") + } + //stop recording if we are close to overflow + if nbGlobal.NbCfg > maxNbCfg { + return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("unable to measure OVN due to nb_cfg being close to overflow") + } + ops, err := nbClient.Where(nbGlobal).Mutate(nbGlobal, model.Mutation{ + Field: &nbGlobal.NbCfg, + Mutator: ovsdb.MutateOperationAdd, + Value: 1, + }) + if err != nil { + return []ovsdb.Operation{}, func() {}, time.Time{}, fmt.Errorf("failed to create update operation: %v", err) + } + ovnStartTimestamp := time.Now() + + return ops, func() { + // there can be a race condition here where we queue the wrong nbCfg value, but it is ok as long as it is + // less than or equal the hv_cfg value we see and this is the case because of atomic increments for nb_cfg + cr.measurementsMu.Lock() + m, ok = cr.measurements[kindNamespaceName] + if !ok { + klog.Errorf("Config duration recorder: expected a measurement entry. Call Start before AddOVN"+ + " for %s", kindNamespaceName) + cr.measurementsMu.Unlock() + return + } + m.ovnMeasurements = append(m.ovnMeasurements, ovnMeasurement{startTimestamp: ovnStartTimestamp, + nbCfg: nbGlobal.NbCfg + 1}) + cr.measurements[kindNamespaceName] = m + cr.measurementsMu.Unlock() + }, ovnStartTimestamp, nil +} + +// runMeasurementRateAdjuster will adjust the rate of measurements based on the number of nodes in the cluster and arg k +func (cr *ConfigDurationRecorder) runMeasurementRateAdjuster(wf *factory.WatchFactory, k float64, nodeCheckPeriod time.Duration, + stop <-chan struct{}) { + var currentMeasurementRate, newMeasurementRate uint64 + + updateMeasurementRate := func() { + if nodeCount, err := getNodeCount(wf); err != nil { + klog.Errorf("Config duration recorder: failed to update ticker duration considering node count: %v", err) + } else { + newMeasurementRate = uint64(math.Round(k * float64(nodeCount))) + if newMeasurementRate != currentMeasurementRate { + if newMeasurementRate > 0 { + currentMeasurementRate = newMeasurementRate + cr.measurementRate = newMeasurementRate + } + klog.V(5).Infof("Config duration recorder: updated measurement rate to approx 1 in"+ + " every %d requests", newMeasurementRate) + } + } + } + + // initial measurement rate adjustment + updateMeasurementRate() + + go func() { + nodeCheckTicker := time.NewTicker(nodeCheckPeriod) + for { + select { + case <-nodeCheckTicker.C: + updateMeasurementRate() + case <-stop: + nodeCheckTicker.Stop() + return + } + } + }() +} + +// processMeasurements manages the measurements map. It calculates metrics and cleans up finished or stale measurements +func (cr *ConfigDurationRecorder) processMeasurements(period time.Duration, updateOVNMeasurementCh chan hvCfgUpdate, + deleteOVNMeasurementCh chan int, stop <-chan struct{}) { + ticker := time.NewTicker(period) + var ovnKDelta, ovnDelta float64 + + for { + select { + case <-stop: + ticker.Stop() + return + // remove measurements if channel updateOVNMeasurementCh overflows, therefore we cannot trust existing measurements + case hvCfg := <-deleteOVNMeasurementCh: + cr.measurementsMu.Lock() + removeOVNMeasurements(cr.measurements, hvCfg) + cr.measurementsMu.Unlock() + case h := <-updateOVNMeasurementCh: + cr.measurementsMu.Lock() + cr.addHvCfg(h.hvCfg, h.timestamp) + cr.measurementsMu.Unlock() + // used for processing measurements that didn't require OVN measurement. Helps to keep measurement map small + case kindNamespaceName := <-cr.triggerProcessCh: + cr.measurementsMu.Lock() + m, ok := cr.measurements[kindNamespaceName] + if !ok { + klog.Errorf("Config duration recorder: expected measurement, but not found") + cr.measurementsMu.Unlock() + continue + } + if !m.end { + cr.measurementsMu.Unlock() + continue + } + if len(m.ovnMeasurements) != 0 { + cr.measurementsMu.Unlock() + continue + } + ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() + metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta) + klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller took %v"+ + " seconds. No OVN measurement.", kindNamespaceName, ovnKDelta) + delete(cr.measurements, kindNamespaceName) + cr.measurementsMu.Unlock() + // used for processing measurements that require OVN measurement or do not or are expired. + case <-ticker.C: + start := time.Now() + cr.measurementsMu.Lock() + // process and clean up measurements + for kindNamespaceName, m := range cr.measurements { + if start.After(m.expiresAt) { + // measurement may expire if OVN is degraded or End wasn't called + klog.Warningf("Config duration recorder: measurement expired for %s", kindNamespaceName) + delete(cr.measurements, kindNamespaceName) + continue + } + if !m.end { + // measurement didn't end yet, process later + continue + } + // for when no ovn measurements requested + if len(m.ovnMeasurements) == 0 { + ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() + metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta) + klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller"+ + " took %v seconds. No OVN measurement.", kindNamespaceName, ovnKDelta) + delete(cr.measurements, kindNamespaceName) + continue + } + // for each kind/namespace/name, there can be multiple calls to AddOVN between start and end + // we sum all the OVN durations and add it to the start and end duration + // first lets make sure all OVN measurements are finished + if complete := allOVNMeasurementsComplete(m.ovnMeasurements); !complete { + continue + } + + ovnKDelta = m.endTimestamp.Sub(m.startTimestamp).Seconds() + ovnDelta = calculateOVNDuration(m.ovnMeasurements) + metricNetworkProgramming.With(prometheus.Labels{"kind": m.kind}).Observe(ovnKDelta + ovnDelta) + metricNetworkProgrammingOVN.Observe(ovnDelta) + klog.V(5).Infof("Config duration recorder: kind/namespace/name %s. OVN-Kubernetes controller took"+ + " %v seconds. OVN took %v seconds. Total took %v seconds", kindNamespaceName, ovnKDelta, + ovnDelta, ovnDelta+ovnKDelta) + delete(cr.measurements, kindNamespaceName) + } + cr.measurementsMu.Unlock() + } + } +} + +func (cr *ConfigDurationRecorder) addHvCfg(hvCfg, hvCfgTimestamp int) { + var altered bool + for i, m := range cr.measurements { + altered = false + for iOvnM, ovnM := range m.ovnMeasurements { + if ovnM.complete { + continue + } + if ovnM.nbCfg <= hvCfg { + ovnM.endTimestamp = time.UnixMilli(int64(hvCfgTimestamp)) + ovnM.complete = true + m.ovnMeasurements[iOvnM] = ovnM + altered = true + } + } + if altered { + cr.measurements[i] = m + } + } +} + +func getNodeCount(wf *factory.WatchFactory) (int, error) { + nodes, err := wf.GetNodes() + if err != nil { + return 0, fmt.Errorf("unable to retrieve node list: %v", err) + } + return len(nodes), nil +} + +func removeOVNMeasurement(oM []ovnMeasurement, i int) []ovnMeasurement { + oM[i] = oM[len(oM)-1] + return oM[:len(oM)-1] +} +func hashToNumber(s string) uint64 { + h := fnv.New64() + h.Write([]byte(s)) + return h.Sum64() +} + +func calculateOVNDuration(ovnMeasurements []ovnMeasurement) float64 { + var totalDuration float64 + for _, oM := range ovnMeasurements { + if !oM.complete { + continue + } + totalDuration += oM.endTimestamp.Sub(oM.startTimestamp).Seconds() + } + return totalDuration +} + +func allOVNMeasurementsComplete(ovnMeasurements []ovnMeasurement) bool { + for _, oM := range ovnMeasurements { + if !oM.complete { + return false + } + } + return true +} + +// merge direct copy from k8 pkg/proxy/metrics/metrics.go +func merge(slices ...[]float64) []float64 { + result := make([]float64, 1) + for _, s := range slices { + result = append(result, s...) + } + return result +} diff --git a/go-controller/pkg/metrics/ovnkube_controller_test.go b/go-controller/pkg/metrics/recorders/duration_test.go similarity index 86% rename from go-controller/pkg/metrics/ovnkube_controller_test.go rename to go-controller/pkg/metrics/recorders/duration_test.go index 1ff008db59..ee436d8d1b 100644 --- a/go-controller/pkg/metrics/ovnkube_controller_test.go +++ b/go-controller/pkg/metrics/recorders/duration_test.go @@ -1,4 +1,4 @@ -package metrics +package recorders import ( "fmt" @@ -14,17 +14,29 @@ import ( "github.com/ovn-org/libovsdb/client" + egressfirewallfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/clientset/versioned/fake" + egressipfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/clientset/versioned/fake" + egressqosfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressqos/v1/apis/clientset/versioned/fake" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/mocks" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) -func setupOvn(nbData libovsdbtest.TestSetup) (client.Client, client.Client, *libovsdbtest.Context) { - nbClient, sbClient, cleanup, err := libovsdbtest.NewNBSBTestHarness(nbData) +func setHvCfg(nbClient client.Client, hvCfg int, hvCfgTimestamp time.Time) { + nbGlobal := nbdb.NBGlobal{} + nbGlobalResp, err := libovsdbops.GetNBGlobal(nbClient, &nbGlobal) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + nbGlobalResp.HvCfg = hvCfg + nbGlobalResp.HvCfgTimestamp = int(hvCfgTimestamp.UnixMilli()) + ops, err := nbClient.Where(nbGlobalResp).Update(nbGlobalResp, &nbGlobalResp.HvCfg, &nbGlobalResp.HvCfgTimestamp) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(ops).To(gomega.HaveLen(1)) + _, err = libovsdbops.TransactAndCheck(nbClient, ops) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - return sbClient, nbClient, cleanup } func getKubeClient(nodeCount int) *kube.Kube { @@ -40,23 +52,16 @@ func getKubeClient(nodeCount int) *kube.Kube { return &kube.Kube{KClient: kubeFakeClient} } -func setHvCfg(nbClient client.Client, hvCfg int, hvCfgTimestamp time.Time) { - nbGlobal := nbdb.NBGlobal{} - nbGlobalResp, err := libovsdbops.GetNBGlobal(nbClient, &nbGlobal) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - nbGlobalResp.HvCfg = hvCfg - nbGlobalResp.HvCfgTimestamp = int(hvCfgTimestamp.UnixMilli()) - ops, err := nbClient.Where(nbGlobalResp).Update(nbGlobalResp, &nbGlobalResp.HvCfg, &nbGlobalResp.HvCfgTimestamp) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - gomega.Expect(ops).To(gomega.HaveLen(1)) - _, err = libovsdbops.TransactAndCheck(nbClient, ops) +func setupOvn(nbData libovsdbtest.TestSetup) (client.Client, client.Client, *libovsdbtest.Context) { + nbClient, sbClient, cleanup, err := libovsdbtest.NewNBSBTestHarness(nbData) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + return sbClient, nbClient, cleanup } var _ = ginkgo.Describe("Config Duration Operations", func() { var ( instance *ConfigDurationRecorder - k *kube.Kube + wf *factory.WatchFactory nbClient client.Client cleanup *libovsdbtest.Context stop chan struct{} @@ -69,7 +74,23 @@ var _ = ginkgo.Describe("Config Duration Operations", func() { ginkgo.BeforeEach(func() { cdr = nil instance = GetConfigDurationRecorder() - k = getKubeClient(1) + k := getKubeClient(1) + egressFirewallFakeClient := &egressfirewallfake.Clientset{} + egressIPFakeClient := &egressipfake.Clientset{} + egressQoSFakeClient := &egressqosfake.Clientset{} + fakeClient := &util.OVNClientset{ + KubeClient: k.KClient, + EgressIPClient: egressIPFakeClient, + EgressFirewallClient: egressFirewallFakeClient, + EgressQoSClient: egressQoSFakeClient, + } + + var err error + wf, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = wf.Start() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + stop = make(chan struct{}) _, nbClient, cleanup = setupOvn(libovsdbtest.TestSetup{ NBData: []libovsdbtest.TestData{&nbdb.NBGlobal{UUID: "cd-op-uuid"}}}) @@ -78,11 +99,12 @@ var _ = ginkgo.Describe("Config Duration Operations", func() { ginkgo.AfterEach(func() { cleanup.Cleanup() close(stop) + wf.Stop() }) ginkgo.Context("Runtime", func() { ginkgo.It("records correctly", func() { - instance.Run(nbClient, k, 0, time.Millisecond, stop) + instance.Run(nbClient, wf, 0, time.Millisecond, stop) histoMock := mocks.NewHistogramVecMock() metricNetworkProgramming = histoMock startTimestamp, ok := instance.Start("pod", testNamespaceA, testPodNameA) @@ -104,7 +126,7 @@ var _ = ginkgo.Describe("Config Duration Operations", func() { }) ginkgo.It("records correctly with OVN latency", func() { - instance.Run(nbClient, k, 0, time.Millisecond, stop) + instance.Run(nbClient, wf, 0, time.Millisecond, stop) histoMock := mocks.NewHistogramVecMock() metricNetworkProgramming = histoMock startTimestamp, ok := instance.Start("pod", testNamespaceA, testPodNameA) @@ -134,7 +156,7 @@ var _ = ginkgo.Describe("Config Duration Operations", func() { }) ginkgo.It("records multiple different objs including adding OVN latency", func() { - instance.Run(nbClient, k, 0, time.Millisecond, stop) + instance.Run(nbClient, wf, 0, time.Millisecond, stop) histoMock := mocks.NewHistogramVecMock() metricNetworkProgramming = histoMock // recording 1 @@ -186,13 +208,13 @@ var _ = ginkgo.Describe("Config Duration Operations", func() { }) ginkgo.It("denies recording when no start called", func() { - instance.Run(nbClient, k, 0, time.Millisecond, stop) + instance.Run(nbClient, wf, 0, time.Millisecond, stop) ops, _, _, _ := instance.AddOVN(nbClient, "pod", testNamespaceA, testPodNameA) gomega.Expect(ops).Should(gomega.BeEmpty()) }) ginkgo.It("allows multiple addOVN records for the same obj", func() { - instance.Run(nbClient, k, 0, time.Millisecond, stop) + instance.Run(nbClient, wf, 0, time.Millisecond, stop) histoMock := mocks.NewHistogramVecMock() metricNetworkProgramming = histoMock // recording 1 diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index 02a110b5d7..f9f3b36ec5 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -830,7 +830,7 @@ func (nc *DefaultNodeNetworkController) Init(ctx context.Context) error { } } - if node, err = nc.Kube.GetNode(nc.name); err != nil { + if node, err = nc.watchFactory.GetNode(nc.name); err != nil { return fmt.Errorf("error retrieving node %s: %v", nc.name, err) } @@ -895,7 +895,7 @@ func (nc *DefaultNodeNetworkController) Init(ctx context.Context) error { // First wait for the node logical switch to be created by the Master, timeout is 300s. err = wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 300*time.Second, true, func(_ context.Context) (bool, error) { - if node, err = nc.Kube.GetNode(nc.name); err != nil { + if node, err = nc.watchFactory.GetNode(nc.name); err != nil { klog.Infof("Waiting to retrieve node %s: %v", nc.name, err) return false, nil } @@ -999,7 +999,7 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { klog.Errorf("Setting klog \"loglevel\" to 5 failed, err: %v", err) } - if node, err = nc.Kube.GetNode(nc.name); err != nil { + if node, err = nc.watchFactory.GetNode(nc.name); err != nil { return fmt.Errorf("error retrieving node %s: %v", nc.name, err) } @@ -1079,7 +1079,7 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { err = wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 300*time.Second, true, func(_ context.Context) (bool, error) { // we loop through all the nodes in the cluster and ensure ovnkube-controller has finished creating the LRSR required for pod2pod overlay communication if !syncNodes { - nodes, err := nc.Kube.GetNodes() + nodes, err := nc.watchFactory.GetNodes() if err != nil { err1 = fmt.Errorf("upgrade hack: error retrieving node %s: %v", nc.name, err) return false, nil diff --git a/go-controller/pkg/node/gateway_egressip_test.go b/go-controller/pkg/node/gateway_egressip_test.go index bd09738200..db43f7450a 100644 --- a/go-controller/pkg/node/gateway_egressip_test.go +++ b/go-controller/pkg/node/gateway_egressip_test.go @@ -70,7 +70,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.addEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -85,7 +85,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.addEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -100,7 +100,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.addEgressIP(eip) gomega.Expect(err).Should(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -120,7 +120,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.addEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -143,7 +143,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.updateEgressIP(unassignedEIP, assignedEIP) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -168,7 +168,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err = addrMgr.updateEgressIP(assignedEIP, unassignedEIP) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -197,7 +197,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err = addrMgr.updateEgressIP(assignedEIP1, assignedEIP2) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -227,7 +227,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err = addrMgr.deleteEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -243,7 +243,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { isUpdated, err := addrMgr.deleteEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr2)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, @@ -267,7 +267,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { eipUnassigned3 := getEIPNotAssignedToNode(mark3, ipV4Addr3) err := addrMgr.syncEgressIP([]interface{}{eipAssigned1, eipAssigned2, eipUnassigned3}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -291,7 +291,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { eipAssigned2 := getEIPAssignedToNode(nodeName, mark2, ipV4Addr2) err := addrMgr.syncEgressIP([]interface{}{eipAssigned1, eipAssigned2}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, @@ -308,7 +308,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { eipAssigned := getEIPAssignedToNode(nodeName, "", ipV4Addr) err := addrMgr.syncEgressIP([]interface{}{eipAssigned}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") - node, err := addrMgr.kube.GetNode(nodeName) + node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.BeEmpty()) }) diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 7312521ab7..f8bd7e94c6 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -458,7 +458,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { mgtPortMAC = util.IPAddrToHWAddr(util.GetNodeManagementIfAddr(ipNet).IP).String() getCreationFakeCommands(fexec, mgtPort, mgtPortMAC, netName, nodeName, netInfo.MTU()) nodeLister.On("Get", mock.AnythingOfType("string")).Return(node, nil) - factoryMock.On("GetNode", "worker1").Return(node, nil) + factoryMock.On("GetNodeForWindows", "worker1").Return(node, nil) err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() @@ -501,7 +501,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(err).NotTo(HaveOccurred()) getDeletionFakeOVSCommands(fexec, mgtPort) nodeLister.On("Get", mock.AnythingOfType("string")).Return(node, nil) - factoryMock.On("GetNode", "worker1").Return(node, nil) + factoryMock.On("GetNodeForWindows", "worker1").Return(node, nil) cnode := node.DeepCopy() kubeMock.On("UpdateNodeStatus", cnode).Return(nil) // check if network key gets deleted from annotation err = testNS.Do(func(ns.NetNS) error { @@ -537,7 +537,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { mgtPortMAC = util.IPAddrToHWAddr(util.GetNodeManagementIfAddr(ipNet).IP).String() getCreationFakeCommands(fexec, mgtPort, mgtPortMAC, netName, nodeName, netInfo.MTU()) nodeLister.On("Get", mock.AnythingOfType("string")).Return(node, nil) - factoryMock.On("GetNode", "worker1").Return(node, nil) + factoryMock.On("GetNodeForWindows", "worker1").Return(node, nil) err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() ofm := getDummyOpenflowManager() @@ -579,7 +579,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(err).NotTo(HaveOccurred()) getDeletionFakeOVSCommands(fexec, mgtPort) nodeLister.On("Get", mock.AnythingOfType("string")).Return(node, nil) - factoryMock.On("GetNode", "worker1").Return(node, nil) + factoryMock.On("GetNodeForWindows", "worker1").Return(node, nil) cnode := node.DeepCopy() kubeMock.On("UpdateNodeStatus", cnode).Return(nil) // check if network key gets deleted from annotation err = testNS.Do(func(ns.NetNS) error { diff --git a/go-controller/pkg/ovn/base_event_handler.go b/go-controller/pkg/ovn/base_event_handler.go index b08bc30b99..f54afc9925 100644 --- a/go-controller/pkg/ovn/base_event_handler.go +++ b/go-controller/pkg/ovn/base_event_handler.go @@ -14,7 +14,7 @@ import ( egressfirewall "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -213,11 +213,11 @@ func (h *baseNetworkControllerEventHandler) recordAddEvent(objType reflect.Type, case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording add event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) case factory.MultiNetworkPolicyType: mnp := obj.(*mnpapi.MultiNetworkPolicy) klog.V(5).Infof("Recording add event on multinetwork policy %s/%s", mnp.Namespace, mnp.Name) - metrics.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) + recorders.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) } } @@ -227,11 +227,11 @@ func (h *baseNetworkControllerEventHandler) recordUpdateEvent(objType reflect.Ty case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording update event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) case factory.MultiNetworkPolicyType: mnp := obj.(*mnpapi.MultiNetworkPolicy) klog.V(5).Infof("Recording update event on multinetwork policy %s/%s", mnp.Namespace, mnp.Name) - metrics.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) + recorders.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) } } @@ -241,11 +241,11 @@ func (h *baseNetworkControllerEventHandler) recordDeleteEvent(objType reflect.Ty case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording delete event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) case factory.MultiNetworkPolicyType: mnp := obj.(*mnpapi.MultiNetworkPolicy) klog.V(5).Infof("Recording delete event on multinetwork policy %s/%s", mnp.Namespace, mnp.Name) - metrics.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) + recorders.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) } } @@ -255,10 +255,10 @@ func (h *baseNetworkControllerEventHandler) recordSuccessEvent(objType reflect.T case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording success event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().End("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().End("networkpolicy", np.Namespace, np.Name) case factory.MultiNetworkPolicyType: mnp := obj.(*mnpapi.MultiNetworkPolicy) klog.V(5).Infof("Recording success event on multinetwork policy %s/%s", mnp.Namespace, mnp.Name) - metrics.GetConfigDurationRecorder().End("multinetworkpolicy", mnp.Namespace, mnp.Name) + recorders.GetConfigDurationRecorder().End("multinetworkpolicy", mnp.Namespace, mnp.Name) } } diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index ae38418d70..db56f42cb9 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -33,6 +33,7 @@ import ( libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/observability" @@ -317,7 +318,7 @@ func (bnc *BaseNetworkController) GetLogicalPortName(pod *corev1.Pod, nadName st func (bnc *BaseNetworkController) AddConfigDurationRecord(kind, namespace, name string) ( []ovsdb.Operation, func(), time.Time, error) { if !bnc.IsSecondary() { - return metrics.GetConfigDurationRecorder().AddOVN(bnc.nbClient, kind, namespace, name) + return recorders.GetConfigDurationRecorder().AddOVN(bnc.nbClient, kind, namespace, name) } // TBD: no op for secondary network for now return []ovsdb.Operation{}, func() {}, time.Time{}, nil diff --git a/go-controller/pkg/ovn/controller/admin_network_policy/metrics.go b/go-controller/pkg/ovn/controller/admin_network_policy/metrics.go index cd2e636ea8..dcf1fb6aab 100644 --- a/go-controller/pkg/ovn/controller/admin_network_policy/metrics.go +++ b/go-controller/pkg/ovn/controller/admin_network_policy/metrics.go @@ -8,14 +8,14 @@ import ( anpapi "sigs.k8s.io/network-policy-api/apis/v1alpha1" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" ) // Descriptors used by the ANPControllerCollector below. var ( anpRuleCountDesc = prometheus.NewDesc( - prometheus.BuildFQName(metrics.MetricOvnkubeNamespace, metrics.MetricOvnkubeSubsystemController, "admin_network_policies_rules"), + prometheus.BuildFQName(types.MetricOvnkubeNamespace, types.MetricOvnkubeSubsystemController, "admin_network_policies_rules"), "The total number of rules across all admin network policies in the cluster", []string{ "direction", // direction is either "ingress" or "egress"; so cardinality is max 2 for this label @@ -23,7 +23,7 @@ var ( }, nil, ) banpRuleCountDesc = prometheus.NewDesc( - prometheus.BuildFQName(metrics.MetricOvnkubeNamespace, metrics.MetricOvnkubeSubsystemController, "baseline_admin_network_policies_rules"), + prometheus.BuildFQName(types.MetricOvnkubeNamespace, types.MetricOvnkubeSubsystemController, "baseline_admin_network_policies_rules"), "The total number of rules across all baseline admin network policies in the cluster", []string{ "direction", // direction is either "ingress" or "egress"; so cardinality is max 2 for this label diff --git a/go-controller/pkg/ovn/controller/network_qos/metrics.go b/go-controller/pkg/ovn/controller/network_qos/metrics.go index 96fa30834d..de5c6872ca 100644 --- a/go-controller/pkg/ovn/controller/network_qos/metrics.go +++ b/go-controller/pkg/ovn/controller/network_qos/metrics.go @@ -3,15 +3,15 @@ package networkqos import ( "github.com/prometheus/client_golang/prometheus" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" ) // Metrics to be exposed var ( nqosCount = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "num_network_qoses", Help: "The total number of network qoses in the cluster", }, @@ -20,8 +20,8 @@ var ( nqosOvnOperationDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nqos_ovn_operation_duration_ms", Help: "Time spent on reconciling a NetworkQoS event", Buckets: prometheus.ExponentialBuckets(.1, 2, 15), @@ -31,8 +31,8 @@ var ( nqosReconcileDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nqos_creation_duration_ms", Help: "Time spent on reconciling a NetworkQoS event", Buckets: prometheus.ExponentialBuckets(.1, 2, 15), @@ -42,8 +42,8 @@ var ( nqosPodReconcileDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nqos_deletion_duration_ms", Help: "Time spent on reconciling a Pod event", Buckets: prometheus.ExponentialBuckets(.1, 2, 15), @@ -53,8 +53,8 @@ var ( nqosNamespaceReconcileDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nqos_ns_reconcile_duration_ms", Help: "Time spent on reconciling Namespace change for all Pods related to NetworkQoSes", Buckets: prometheus.ExponentialBuckets(.1, 2, 15), @@ -64,8 +64,8 @@ var ( nqosStatusPatchDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Namespace: metrics.MetricOvnkubeNamespace, - Subsystem: metrics.MetricOvnkubeSubsystemController, + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemController, Name: "nqos_status_patch_duration_ms", Help: "Time spent on patching the status of a NetworkQoS", }, diff --git a/go-controller/pkg/ovn/controller/services/loadbalancer.go b/go-controller/pkg/ovn/controller/services/loadbalancer.go index ba4eebc43a..8c3d1c9114 100644 --- a/go-controller/pkg/ovn/controller/services/loadbalancer.go +++ b/go-controller/pkg/ovn/controller/services/loadbalancer.go @@ -14,7 +14,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -283,7 +283,7 @@ func EnsureLBs(nbClient libovsdbclient.Client, service *corev1.Service, existing return err } - recordOps, txOkCallBack, _, err := metrics.GetConfigDurationRecorder().AddOVN(nbClient, "service", + recordOps, txOkCallBack, _, err := recorders.GetConfigDurationRecorder().AddOVN(nbClient, "service", service.Namespace, service.Name) if err != nil { klog.Errorf("Failed to record config duration: %v", err) diff --git a/go-controller/pkg/ovn/controller/services/services_controller.go b/go-controller/pkg/ovn/controller/services/services_controller.go index 031a38a0d6..e03ad40b5c 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller.go +++ b/go-controller/pkg/ovn/controller/services/services_controller.go @@ -36,6 +36,7 @@ import ( libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -282,7 +283,7 @@ func (c *Controller) handleErr(err error, key string) { klog.ErrorS(err, "Failed to split meta namespace cache key", "key", key) } if err == nil { - metrics.GetConfigDurationRecorder().End("service", ns, name) + recorders.GetConfigDurationRecorder().End("service", ns, name) c.queue.Forget(key) return } @@ -296,7 +297,7 @@ func (c *Controller) handleErr(err error, key string) { } klog.Warningf("Dropping service %q out of the queue for network=%s: %v", key, c.netInfo.GetNetworkName(), err) - metrics.GetConfigDurationRecorder().End("service", ns, name) + recorders.GetConfigDurationRecorder().End("service", ns, name) c.queue.Forget(key) utilruntime.HandleError(err) } @@ -609,7 +610,7 @@ func (c *Controller) onServiceAdd(obj interface{}) { if c.skipService(service.Name, service.Namespace) { return } - metrics.GetConfigDurationRecorder().Start("service", service.Namespace, service.Name) + recorders.GetConfigDurationRecorder().Start("service", service.Namespace, service.Name) klog.V(5).Infof("Adding service %s for network=%s", key, c.netInfo.GetNetworkName()) c.queue.Add(key) } @@ -631,7 +632,7 @@ func (c *Controller) onServiceUpdate(oldObj, newObj interface{}) { return } - metrics.GetConfigDurationRecorder().Start("service", newService.Namespace, newService.Name) + recorders.GetConfigDurationRecorder().Start("service", newService.Namespace, newService.Name) c.queue.Add(key) } } @@ -651,7 +652,7 @@ func (c *Controller) onServiceDelete(obj interface{}) { klog.V(4).Infof("Deleting service %s for network=%s", key, c.netInfo.GetNetworkName()) - metrics.GetConfigDurationRecorder().Start("service", service.Namespace, service.Name) + recorders.GetConfigDurationRecorder().Start("service", service.Namespace, service.Name) c.queue.Add(key) } diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index cf6886e846..3ea00bcb17 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -22,6 +22,7 @@ import ( egressqoslisters "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressqos/v1/apis/listers/egressqos/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/observability" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" @@ -356,7 +357,7 @@ func (oc *DefaultNetworkController) Stop() { // // If true, then either quit or perform a complete reconfiguration of the cluster (recreate switches/routers with new subnet values) func (oc *DefaultNetworkController) init() error { - existingNodes, err := oc.kube.GetNodes() + existingNodes, err := oc.watchFactory.GetNodes() if err != nil { klog.Errorf("Error in fetching nodes: %v", err) return err @@ -657,11 +658,11 @@ func (h *defaultNetworkControllerEventHandler) RecordAddEvent(obj interface{}) { pod := obj.(*corev1.Pod) klog.V(5).Infof("Recording add event on pod %s/%s", pod.Namespace, pod.Name) h.oc.podRecorder.AddPod(pod.UID) - metrics.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) + recorders.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording add event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) } } @@ -671,11 +672,11 @@ func (h *defaultNetworkControllerEventHandler) RecordUpdateEvent(obj interface{} case factory.PodType: pod := obj.(*corev1.Pod) klog.V(5).Infof("Recording update event on pod %s/%s", pod.Namespace, pod.Name) - metrics.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) + recorders.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording update event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) } } @@ -686,11 +687,11 @@ func (h *defaultNetworkControllerEventHandler) RecordDeleteEvent(obj interface{} pod := obj.(*corev1.Pod) klog.V(5).Infof("Recording delete event on pod %s/%s", pod.Namespace, pod.Name) h.oc.podRecorder.CleanPod(pod.UID) - metrics.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) + recorders.GetConfigDurationRecorder().Start("pod", pod.Namespace, pod.Name) case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording delete event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().Start("networkpolicy", np.Namespace, np.Name) } } @@ -700,11 +701,11 @@ func (h *defaultNetworkControllerEventHandler) RecordSuccessEvent(obj interface{ case factory.PodType: pod := obj.(*corev1.Pod) klog.V(5).Infof("Recording success event on pod %s/%s", pod.Namespace, pod.Name) - metrics.GetConfigDurationRecorder().End("pod", pod.Namespace, pod.Name) + recorders.GetConfigDurationRecorder().End("pod", pod.Namespace, pod.Name) case factory.PolicyType: np := obj.(*knet.NetworkPolicy) klog.V(5).Infof("Recording success event on network policy %s/%s", np.Namespace, np.Name) - metrics.GetConfigDurationRecorder().End("networkpolicy", np.Namespace, np.Name) + recorders.GetConfigDurationRecorder().End("networkpolicy", np.Namespace, np.Name) } } diff --git a/go-controller/pkg/ovn/hybrid.go b/go-controller/pkg/ovn/hybrid.go index f7debe4ec8..6386d719e9 100644 --- a/go-controller/pkg/ovn/hybrid.go +++ b/go-controller/pkg/ovn/hybrid.go @@ -171,7 +171,7 @@ func (oc *DefaultNetworkController) setupHybridLRPolicySharedGw(nodeSubnets []*n // In cases of OpenShift SDN live migration, where config.HybridOverlay.ClusterSubnets is not provided, we // use the host subnets allocated by OpenShiftSDN as the hybrid-overlay-node-subnet and set up hybrid // overlay routes/policies to these subnets. - nodes, err := oc.kube.GetNodes() + nodes, err := oc.watchFactory.GetNodes() if err != nil { return err } @@ -407,7 +407,7 @@ func (oc *DefaultNetworkController) removeRoutesToHONodeSubnet(nodeName string, } // Delete routes to HO subnet from GRs - nodes, err := oc.kube.GetNodes() + nodes, err := oc.watchFactory.GetNodes() if err != nil { return err } diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 26eb1277fe..78b5ce2402 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -730,7 +730,7 @@ func (oc *DefaultNetworkController) addUpdateHoNodeEvent(node *corev1.Node) erro return err } - nodes, err := oc.kube.GetNodes() + nodes, err := oc.watchFactory.GetNodes() if err != nil { return err } diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index ad545b94d1..914bf7f326 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -1167,7 +1167,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { types.OVNClusterRouter, badRoute, p) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By("Syncing node with OVNK") - node, err := oc.kube.GetNode(testNode.Name) + node, err := oc.kube.GetNodeForWindows(testNode.Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = oc.syncNodeManagementPortDefault(node, node.Name, []*net.IPNet{subnet}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) diff --git a/go-controller/pkg/ovn/ovn_test.go b/go-controller/pkg/ovn/ovn_test.go index 0f2a9f1058..801777854a 100644 --- a/go-controller/pkg/ovn/ovn_test.go +++ b/go-controller/pkg/ovn/ovn_test.go @@ -276,7 +276,7 @@ func (o *FakeOVN) init(nadList []nettypes.NetworkAttachmentDefinition) { err = o.eIPController.SyncLocalNodeZonesCache() gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "syncing Nodes OVN zones status must succeed to support EgressIP") - existingNodes, err := o.controller.kube.GetNodes() + existingNodes, err := o.controller.watchFactory.GetNodes() if err == nil { for _, node := range existingNodes { o.controller.localZoneNodes.Store(node.Name, true) diff --git a/go-controller/pkg/ovn/secondary_localnet_network_controller.go b/go-controller/pkg/ovn/secondary_localnet_network_controller.go index 3c6fef1027..4046f819ce 100644 --- a/go-controller/pkg/ovn/secondary_localnet_network_controller.go +++ b/go-controller/pkg/ovn/secondary_localnet_network_controller.go @@ -15,7 +15,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/pod" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics/recorders" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" @@ -65,7 +65,7 @@ func (h *secondaryLocalnetNetworkControllerEventHandler) RecordAddEvent(obj inte case factory.MultiNetworkPolicyType: mnp := obj.(*mnpapi.MultiNetworkPolicy) klog.V(5).Infof("Recording add event on multinetwork policy %s/%s", mnp.Namespace, mnp.Name) - metrics.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) + recorders.GetConfigDurationRecorder().Start("multinetworkpolicy", mnp.Namespace, mnp.Name) } } diff --git a/go-controller/pkg/ovndbmanager/ovndbmanager.go b/go-controller/pkg/ovndbmanager/ovndbmanager.go index 2c5fac6ab9..7e26bb2b98 100644 --- a/go-controller/pkg/ovndbmanager/ovndbmanager.go +++ b/go-controller/pkg/ovndbmanager/ovndbmanager.go @@ -226,7 +226,7 @@ func ensureClusterRaftMembership(db *util.OvsDbProperties, kclient kube.Interfac r = regexp.MustCompile(`([a-z0-9]{4}) at ` + dbServerRegexp) members := r.FindAllStringSubmatch(out, -1) kickedMembersCount := 0 - dbPods, err := kclient.GetPods(config.Kubernetes.OVNConfigNamespace, metav1.ListOptions{ + dbPods, err := kclient.GetPodsForDBChecker(config.Kubernetes.OVNConfigNamespace, metav1.ListOptions{ LabelSelector: labels.Set(map[string]string{"ovn-db-pod": "true"}).String(), }) if err != nil { diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index 32c267b551..0577f54005 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -313,4 +313,17 @@ const ( // NFTNoPMTUDRemoteNodeIPsv6 is a set used to track remote node IPs that do not belong to // the local node's subnet. NFTNoPMTUDRemoteNodeIPsv6 = "no-pmtud-remote-node-ips-v6" + + // Metrics + MetricOvnkubeNamespace = "ovnkube" + MetricOvnkubeSubsystemController = "controller" + MetricOvnkubeSubsystemClusterManager = "clustermanager" + MetricOvnkubeSubsystemNode = "node" + MetricOvnNamespace = "ovn" + MetricOvnSubsystemDB = "db" + MetricOvnSubsystemNorthd = "northd" + MetricOvnSubsystemController = "controller" + MetricOvsNamespace = "ovs" + MetricOvsSubsystemVswitchd = "vswitchd" + MetricOvsSubsystemDB = "db" ) From 81ab59524a0ddc208b10643e640615f97e9eec6d Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 29 May 2025 18:32:42 -0400 Subject: [PATCH 006/278] Fix node update check for network cluster controller Introduced in 836ec3680aa33300d631b3e7f3bd3069bbfed2b9 This would just cause node updates to fire HandleAddUpdateNodeEvent everytime as the code prior to the aforementioned commit would have. Signed-off-by: Tim Rozet --- go-controller/pkg/clustermanager/network_cluster_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-controller/pkg/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index fde745ac00..0f0f7358e8 100644 --- a/go-controller/pkg/clustermanager/network_cluster_controller.go +++ b/go-controller/pkg/clustermanager/network_cluster_controller.go @@ -576,7 +576,7 @@ func (h *networkClusterControllerEventHandler) UpdateResource(oldObj, newObj int // 1. we missed an add event (bug in kapi informer code) // 2. a user removed the annotation on the node // Either way to play it safe for now do a partial json unmarshal check - if !nodeFailed && util.NoHostSubnet(oldNode) != util.NoHostSubnet(newNode) && !h.ncc.nodeAllocator.NeedsNodeAllocation(newNode) { + if !nodeFailed && util.NoHostSubnet(oldNode) == util.NoHostSubnet(newNode) && !h.ncc.nodeAllocator.NeedsNodeAllocation(newNode) { // no other node updates would require us to reconcile again return nil } From 2b812dd76b7d2a3765c3ee194d4548d7a8422f73 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 29 May 2025 20:49:58 -0400 Subject: [PATCH 007/278] Change NeedsNodeAllocation to a positive check We have unit tests that check to see if only certain annotations were removed, rather than an all or nothing approach. Additionally this function was added as a failsafe in case a user did modify the annotations, or some other unforseen event where the annotations are now missing. Change the function to check each annotation (if it applies to the allocator). Signed-off-by: Tim Rozet --- .../pkg/clustermanager/node/node_allocator.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/go-controller/pkg/clustermanager/node/node_allocator.go b/go-controller/pkg/clustermanager/node/node_allocator.go index 63593618b2..83c3d80fde 100644 --- a/go-controller/pkg/clustermanager/node/node_allocator.go +++ b/go-controller/pkg/clustermanager/node/node_allocator.go @@ -195,27 +195,24 @@ func (na *NodeAllocator) NeedsNodeAllocation(node *corev1.Node) bool { } // ovn node check - // allocation is all or nothing, so if one field was allocated from: - // nodeSubnets, joinSubnet, layer 2 tunnel id, then all of them were if na.hasNodeSubnetAllocation() { - if util.HasNodeHostSubnetAnnotation(node, na.netInfo.GetNetworkName()) { - return false + if !util.HasNodeHostSubnetAnnotation(node, na.netInfo.GetNetworkName()) { + return true } } - if na.hasJoinSubnetAllocation() { - if util.HasNodeGatewayRouterJoinNetwork(node, na.netInfo.GetNetworkName()) { - return false + if !util.HasNodeGatewayRouterJoinNetwork(node, na.netInfo.GetNetworkName()) { + return true } } if util.IsNetworkSegmentationSupportEnabled() && na.netInfo.IsPrimaryNetwork() && util.DoesNetworkRequireTunnelIDs(na.netInfo) { - if util.HasUDNLayer2NodeGRLRPTunnelID(node, na.netInfo.GetNetworkName()) { - return false + if !util.HasUDNLayer2NodeGRLRPTunnelID(node, na.netInfo.GetNetworkName()) { + return true } } - return true + return false } From c68299e597fa02c5d7e7faad588939183b335032 Mon Sep 17 00:00:00 2001 From: Jitse Klomp Date: Tue, 3 Jun 2025 12:01:10 +0200 Subject: [PATCH 008/278] Add mermaid mkdocs plugin Signed-off-by: Jitse Klomp --- mkdocs.yml | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index e21134af5a..ef1b23e0cb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ plugins: - macros: #include_dir: examples j2_line_comment_prefix: "#$" + - mermaid2 - blog: # NOTE: configuration options can be found at # https://squidfunk.github.io/mkdocs-material/setup/setting-up-a-blog/ diff --git a/requirements.txt b/requirements.txt index ecb270c79d..bb1c507df1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ mkdocs-awesome-pages-plugin mkdocs-macros-plugin mkdocs-material mkdocs-material-extensions +mkdocs-mermaid2-plugin mike pep562 Pygments From 07973c386692604deb3a1248df07710327adc61d Mon Sep 17 00:00:00 2001 From: Jitse Klomp Date: Tue, 3 Jun 2025 12:59:11 +0200 Subject: [PATCH 009/278] Add custom_fences config to mkdocs.yml Signed-off-by: Jitse Klomp --- mkdocs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index ef1b23e0cb..658b2ae20f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -59,7 +59,11 @@ markdown_extensions: - pymdownx.details - pymdownx.highlight - pymdownx.inlinehilite - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:mermaid2.fence_mermaid_custom - pymdownx.snippets: base_path: site-src check_paths: true From b56df72578329832990aedd3dc09dddab9788513 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 2 Jun 2025 16:01:51 -0400 Subject: [PATCH 010/278] Some quality of life improvements for layer 3 controllers node handling During update node events, local and remote addOrUpdate functions are called. There are a series of sync checks used to know what to configure. However, in some cases log messages were being printed no matter what, and hybrid overlay was being processed on every node event. This cleans things up so that hybrid overlay is only sync'ed when necessary, and logs are only printed when work is being done to add the local or remote node. Also, removes an old test case for hybrid overlay where the node-subnets annotation of a node was being removed. First introduced here: https://github.com/ovn-kubernetes/ovn-kubernetes/commit/aef135c61f2849d2a1f40bc48caa30b1d3ed30ef#diff-9ab180ea9a39f81dc8334a00ca8ea5e4cd04f9491c27dcfd910b07929c9ddbb5R193 It's not totally clear what the purpose of this test was, but we do not support clearing OVN configuration when OVNK assigned annotations are removed by the user. The node-subnets annotation should not be removed, and if is removed, it should be configured back onto the node by cluster-manager. Signed-off-by: Tim Rozet --- .../pkg/ovn/default_network_controller.go | 37 ++- go-controller/pkg/ovn/hybrid.go | 14 +- go-controller/pkg/ovn/hybrid_test.go | 223 ------------------ go-controller/pkg/ovn/master.go | 68 ++++-- .../secondary_layer3_network_controller.go | 6 +- .../ovn/zone_interconnect/zone_ic_handler.go | 2 +- 6 files changed, 89 insertions(+), 261 deletions(-) diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index 3ea00bcb17..f6b3727c55 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -15,6 +15,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" + hotypes "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/controller" egressfirewall "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1" @@ -762,6 +763,16 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from var aggregatedErrors []error if h.oc.isLocalZoneNode(node) { var nodeParams *nodeSyncs + hoNeedsCleanup := false + if !config.HybridOverlay.Enabled { + // check if the node has the stale annotations on it to signal that we need to clean up + if _, exists := node.Annotations[hotypes.HybridOverlayDRIP]; exists { + hoNeedsCleanup = true + } + if _, exist := node.Annotations[hotypes.HybridOverlayDRMAC]; exist { + hoNeedsCleanup = true + } + } if fromRetryLoop { _, nodeSync := h.oc.addNodeFailed.Load(node.Name) _, clusterRtrSync := h.oc.nodeClusterRouterPortFailed.Load(node.Name) @@ -774,7 +785,7 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from syncClusterRouterPort: clusterRtrSync, syncMgmtPort: mgmtSync, syncGw: gwSync, - syncHo: hoSync, + syncHo: hoSync || hoNeedsCleanup, syncZoneIC: zoneICSync} } else { nodeParams = &nodeSyncs{ @@ -782,10 +793,9 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from syncClusterRouterPort: true, syncMgmtPort: true, syncGw: true, - syncHo: config.HybridOverlay.Enabled, + syncHo: config.HybridOverlay.Enabled || hoNeedsCleanup, syncZoneIC: config.OVNKubernetesFeature.EnableInterconnect} } - if err = h.oc.addUpdateLocalNodeEvent(node, nodeParams); err != nil { klog.Infof("Node add failed for %s, will try again later: %v", node.Name, err) @@ -941,6 +951,16 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int _, failed = h.oc.gatewaysFailed.Load(newNode.Name) gwSync := failed || gatewayChanged(oldNode, newNode) || nodeSubnetChange || hostCIDRsChanged(oldNode, newNode) || nodeGatewayMTUSupportChanged(oldNode, newNode) + hoNeedsCleanup := false + if !config.HybridOverlay.Enabled { + // check if the node has the stale annotations on it to signal that we need to clean up + if _, exists := newNode.Annotations[hotypes.HybridOverlayDRIP]; exists { + hoNeedsCleanup = true + } + if _, exist := newNode.Annotations[hotypes.HybridOverlayDRMAC]; exist { + hoNeedsCleanup = true + } + } _, hoSync := h.oc.hybridOverlayFailed.Load(newNode.Name) _, syncZoneIC := h.oc.syncZoneICFailed.Load(newNode.Name) syncZoneIC = syncZoneIC || zoneClusterChanged || primaryAddrChanged(oldNode, newNode) @@ -949,12 +969,12 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int syncClusterRouterPort: clusterRtrSync, syncMgmtPort: mgmtSync, syncGw: gwSync, - syncHo: hoSync, + syncHo: hoSync || hoNeedsCleanup, syncZoneIC: syncZoneIC, } } else { - klog.Infof("Node %s moved from the remote zone %s to local zone %s.", - newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode)) + klog.Infof("Node %s moved from the remote zone %s to local zone %s, in network: %q", + newNode.Name, util.GetNodeZone(oldNode), util.GetNodeZone(newNode), h.oc.GetNetworkName()) // The node is now a local zone node. Trigger a full node sync. nodeSyncsParam = &nodeSyncs{ syncNode: true, @@ -964,7 +984,6 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int syncHo: true, syncZoneIC: config.OVNKubernetesFeature.EnableInterconnect} } - if err := h.oc.addUpdateLocalNodeEvent(newNode, nodeSyncsParam); err != nil { aggregatedErrors = append(aggregatedErrors, err) } @@ -977,8 +996,8 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int syncZoneIC = syncZoneIC || h.oc.isLocalZoneNode(oldNode) || nodeSubnetChange || zoneClusterChanged || switchToOvnNode || nodeEncapIPsChanged if syncZoneIC { - klog.Infof("Node %s in remote zone %s needs interconnect zone sync up. Zone cluster changed: %v", - newNode.Name, util.GetNodeZone(newNode), zoneClusterChanged) + klog.Infof("Node %q in remote zone %q, network %q, needs interconnect zone sync up. Zone cluster changed: %v", + newNode.Name, util.GetNodeZone(newNode), h.oc.GetNetworkName(), zoneClusterChanged) } if err := h.oc.addUpdateRemoteNodeEvent(newNode, syncZoneIC); err != nil { aggregatedErrors = append(aggregatedErrors, err) diff --git a/go-controller/pkg/ovn/hybrid.go b/go-controller/pkg/ovn/hybrid.go index 6386d719e9..7c84dea2aa 100644 --- a/go-controller/pkg/ovn/hybrid.go +++ b/go-controller/pkg/ovn/hybrid.go @@ -137,12 +137,18 @@ func (oc *DefaultNetworkController) handleHybridOverlayPort(node *corev1.Node, a } func (oc *DefaultNetworkController) deleteHybridOverlayPort(node *corev1.Node) error { - klog.Infof("Removing node %s hybrid overlay port", node.Name) portName := util.GetHybridOverlayPortName(node.Name) lsp := nbdb.LogicalSwitchPort{Name: portName} - sw := nbdb.LogicalSwitch{Name: oc.GetNetworkScopedSwitchName(node.Name)} - if err := libovsdbops.DeleteLogicalSwitchPorts(oc.nbClient, &sw, &lsp); err != nil { - return err + if _, err := libovsdbops.GetLogicalSwitchPort(oc.nbClient, &lsp); err != nil { + if !errors.Is(err, libovsdbclient.ErrNotFound) { + return fmt.Errorf("failed to get logical switch port for hybrid overlay port %s, err: %v", portName, err) + } + } else { + sw := nbdb.LogicalSwitch{Name: oc.GetNetworkScopedSwitchName(node.Name)} + klog.Infof("Removing node %s hybrid overlay port", node.Name) + if err := libovsdbops.DeleteLogicalSwitchPorts(oc.nbClient, &sw, &lsp); err != nil { + return err + } } if err := oc.removeHybridLRPolicySharedGW(node); err != nil { return err diff --git a/go-controller/pkg/ovn/hybrid_test.go b/go-controller/pkg/ovn/hybrid_test.go index a65294f345..1663e5a8f8 100644 --- a/go-controller/pkg/ovn/hybrid_test.go +++ b/go-controller/pkg/ovn/hybrid_test.go @@ -1475,229 +1475,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) }) - ginkgo.It("cleans up a Linux node when the OVN hostsubnet annotation is removed", func() { - app.Action = func(ctx *cli.Context) error { - const ( - nodeHOMAC string = "0a:58:0a:01:01:03" - hoSubnet string = "11.1.0.0/16" - nodeHOIP string = "10.1.1.3" - ) - node1 := tNode{ - Name: "node1", - NodeIP: "1.2.3.4", - NodeLRPMAC: "0a:58:0a:01:01:01", - LrpIP: "100.64.0.2", - DrLrpIP: "100.64.0.1", - PhysicalBridgeMAC: "11:22:33:44:55:66", - SystemID: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac6", - NodeSubnet: "10.1.1.0/24", - GWRouter: types.GWRouterPrefix + "node1", - GatewayRouterIPMask: "172.16.16.2/24", - GatewayRouterIP: "172.16.16.2", - GatewayRouterNextHop: "172.16.16.1", - PhysicalBridgeName: "br-eth0", - NodeGWIP: "10.1.1.1/24", - NodeMgmtPortIP: "10.1.1.2", - //NodeMgmtPortMAC: "0a:58:0a:01:01:02", - NodeMgmtPortMAC: "0a:58:64:40:00:03", - DnatSnatIP: "169.254.0.1", - } - testNode := node1.k8sNode("2") - - kubeFakeClient := fake.NewSimpleClientset(&corev1.NodeList{ - Items: []corev1.Node{testNode}, - }) - egressFirewallFakeClient := &egressfirewallfake.Clientset{} - egressIPFakeClient := &egressipfake.Clientset{} - egressQoSFakeClient := &egressqosfake.Clientset{} - egressServiceFakeClient := &egressservicefake.Clientset{} - fakeClient := &util.OVNMasterClientset{ - KubeClient: kubeFakeClient, - EgressIPClient: egressIPFakeClient, - EgressFirewallClient: egressFirewallFakeClient, - EgressQoSClient: egressQoSFakeClient, - EgressServiceClient: egressServiceFakeClient, - } - - vlanID := 1024 - _, err := config.InitConfig(ctx, nil, nil) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - config.Kubernetes.HostNetworkNamespace = "" - nodeAnnotator := kube.NewNodeAnnotator(&kube.Kube{KClient: kubeFakeClient}, testNode.Name) - l3Config := node1.gatewayConfig(config.GatewayModeShared, uint(vlanID)) - err = util.SetL3GatewayConfig(nodeAnnotator, l3Config) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = util.UpdateNodeManagementPortMACAddresses(&testNode, nodeAnnotator, - ovntest.MustParseMAC(node1.NodeMgmtPortMAC), types.DefaultNetworkName) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = util.SetNodeHostSubnetAnnotation(nodeAnnotator, ovntest.MustParseIPNets(node1.NodeSubnet)) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = util.SetNodeHostCIDRs(nodeAnnotator, sets.New(fmt.Sprintf("%s/24", node1.NodeIP))) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = nodeAnnotator.Run() - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - f, err = factory.NewMasterWatchFactory(fakeClient) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = f.Start() - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - expectedClusterLBGroup := newLoadBalancerGroup(types.ClusterLBGroupName) - expectedSwitchLBGroup := newLoadBalancerGroup(types.ClusterSwitchLBGroupName) - expectedRouterLBGroup := newLoadBalancerGroup(types.ClusterRouterLBGroupName) - expectedOVNClusterRouter := newOVNClusterRouter() - ovnClusterRouterLRP := &nbdb.LogicalRouterPort{ - Name: types.GWRouterToJoinSwitchPrefix + types.OVNClusterRouter, - Networks: []string{"100.64.0.1/16"}, - UUID: types.GWRouterToJoinSwitchPrefix + types.OVNClusterRouter + "-UUID", - } - expectedOVNClusterRouter.Ports = []string{ovnClusterRouterLRP.UUID} - expectedNodeSwitch := node1.logicalSwitch([]string{expectedClusterLBGroup.UUID, expectedSwitchLBGroup.UUID}) - expectedClusterRouterPortGroup := newRouterPortGroup() - expectedClusterPortGroup := newClusterPortGroup() - - dbSetup := libovsdbtest.TestSetup{ - NBData: []libovsdbtest.TestData{ - newClusterJoinSwitch(), - expectedNodeSwitch, - ovnClusterRouterLRP, - expectedOVNClusterRouter, - expectedClusterRouterPortGroup, - expectedClusterPortGroup, - expectedClusterLBGroup, - expectedSwitchLBGroup, - expectedRouterLBGroup, - }, - } - var libovsdbOvnNBClient, libovsdbOvnSBClient libovsdbclient.Client - libovsdbOvnNBClient, libovsdbOvnSBClient, libovsdbCleanup, err = libovsdbtest.NewNBSBTestHarness(dbSetup) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - expectedDatabaseState := []libovsdbtest.TestData{ovnClusterRouterLRP} - expectedDatabaseState = addNodeLogicalFlows(expectedDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1) - - clusterController, err := NewOvnController( - fakeClient, - f, - stopChan, - nil, - networkmanager.Default().Interface(), - libovsdbOvnNBClient, - libovsdbOvnSBClient, - record.NewFakeRecorder(10), - wg, - nil, - NewPortCache(stopChan), - ) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - setupCOPP := true - setupClusterController(clusterController, setupCOPP) - - //assuming all the pods have finished processing - atomic.StoreUint32(&clusterController.allInitialPodsProcessed, 1) - // Let the real code run and ensure OVN database sync - gomega.Expect(clusterController.WatchNodes()).To(gomega.Succeed()) - - gomega.Eventually(func() (map[string]string, error) { - updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return updatedNode.Annotations, nil - }, 2).Should(gomega.HaveKeyWithValue(hotypes.HybridOverlayDRMAC, nodeHOMAC)) - - subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - var clusterSubnets []*net.IPNet - for _, clusterSubnet := range config.Default.ClusterSubnets { - clusterSubnets = append(clusterSubnets, clusterSubnet.CIDR) - } - - skipSnat := false - expectedDatabaseState = generateGatewayInitExpectedNB(expectedDatabaseState, expectedOVNClusterRouter, - expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3Config, - []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, skipSnat, - node1.NodeMgmtPortIP, "1400") - - hybridSubnetStaticRoute1, hybridLogicalRouterStaticRoute, hybridSubnetLRP1, hybridSubnetLRP2, hybridLogicalSwitchPort := setupHybridOverlayOVNObjects(node1, "", hoSubnet, nodeHOIP, nodeHOMAC) - - var node1LogicalRouter *nbdb.LogicalRouter - var basicNode1StaticRoutes []string - - for _, obj := range expectedDatabaseState { - if logicalRouter, ok := obj.(*nbdb.LogicalRouter); ok { - if logicalRouter.Name == "GR_node1" { - // keep a referance so that we can edit this object - node1LogicalRouter = logicalRouter - basicNode1StaticRoutes = logicalRouter.StaticRoutes - logicalRouter.StaticRoutes = append(logicalRouter.StaticRoutes, hybridLogicalRouterStaticRoute.UUID) - } - } - } - - // keep copies of these before appending hybrid overlay elements - basicExpectedNodeSwitchPorts := expectedNodeSwitch.Ports - basicExpectedOVNClusterRouterPolicies := expectedOVNClusterRouter.Policies - basicExpectedOVNClusterStaticRoutes := expectedOVNClusterRouter.StaticRoutes - - expectedNodeSwitch.Ports = append(expectedNodeSwitch.Ports, hybridLogicalSwitchPort.UUID) - expectedOVNClusterRouter.Policies = append(expectedOVNClusterRouter.Policies, hybridSubnetLRP1.UUID, hybridSubnetLRP2.UUID) - expectedOVNClusterRouter.StaticRoutes = append(expectedOVNClusterRouter.StaticRoutes, hybridSubnetStaticRoute1.UUID) - - expectedDatabaseStateWithHybridNode := append([]libovsdbtest.TestData{hybridSubnetStaticRoute1, hybridSubnetLRP2, hybridSubnetLRP1, hybridLogicalSwitchPort, hybridLogicalRouterStaticRoute}, expectedDatabaseState...) - expectedStaticMACBinding := &nbdb.StaticMACBinding{ - UUID: "MAC-binding-HO-UUID", - IP: nodeHOIP, - LogicalPort: "rtos-node1", - MAC: nodeHOMAC, - OverrideDynamicMAC: true, - } - expectedDatabaseStateWithHybridNode = append(expectedDatabaseStateWithHybridNode, expectedStaticMACBinding) - gomega.Eventually(libovsdbOvnNBClient).Should(libovsdbtest.HaveData(expectedDatabaseStateWithHybridNode)) - - nodeAnnotator = kube.NewNodeAnnotator(&kube.Kube{KClient: kubeFakeClient}, testNode.Name) - util.DeleteNodeHostSubnetAnnotation(nodeAnnotator) - err = nodeAnnotator.Run() - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - gomega.Eventually(func() (map[string]string, error) { - updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return updatedNode.Annotations, nil - }, 5).ShouldNot(gomega.HaveKey(hotypes.HybridOverlayDRMAC)) - - // restore values from the non-hybrid versions - expectedNodeSwitch.Ports = basicExpectedNodeSwitchPorts - expectedOVNClusterRouter.Policies = basicExpectedOVNClusterRouterPolicies - expectedOVNClusterRouter.StaticRoutes = basicExpectedOVNClusterStaticRoutes - node1LogicalRouter.StaticRoutes = basicNode1StaticRoutes - - gomega.Eventually(libovsdbOvnNBClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) - - return nil - } - err := app.Run([]string{ - app.Name, - "-cluster-subnets=" + clusterCIDR, - "-gateway-mode=shared", - "-enable-hybrid-overlay", - "-hybrid-overlay-cluster-subnets=" + hybridOverlayClusterCIDR, - }) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - }) - ginkgo.It("cleans up a Linux node that has hybridOverlay annotations and database objects when hybrid overlay is disabled", func() { app.Action = func(ctx *cli.Context) error { const ( diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 78b5ce2402..c2ca98a59e 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -470,6 +470,16 @@ type nodeSyncs struct { syncReroute bool } +func nodeNeedsSync(syncs *nodeSyncs) bool { + return syncs.syncNode || + syncs.syncClusterRouterPort || + syncs.syncMgmtPort || + syncs.syncGw || + syncs.syncHo || + syncs.syncZoneIC || + syncs.syncReroute +} + func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *corev1.Node, nSyncs *nodeSyncs) error { var hostSubnets []*net.IPNet var errs []error @@ -492,7 +502,11 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *corev1.Node, n return nil } - klog.Infof("Adding or Updating Node %q", node.Name) + if !nodeNeedsSync(nSyncs) { + return nil + } + + klog.Infof("Adding or Updating local node %q for network %q", node.Name, oc.GetNetworkName()) if nSyncs.syncNode { if hostSubnets, err = oc.addNode(node); err != nil { oc.addNodeFailed.Store(node.Name, true) @@ -509,12 +523,10 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *corev1.Node, n } // since the nodeSync objects are created knowing if hybridOverlay is enabled this should work - if nSyncs.syncHo { + if nSyncs.syncHo && config.HybridOverlay.Enabled { if err = oc.allocateHybridOverlayDRIP(node); err != nil { errs = append(errs, err) oc.hybridOverlayFailed.Store(node.Name, true) - } else { - oc.hybridOverlayFailed.Delete(node.Name) } } @@ -551,27 +563,37 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *corev1.Node, n } } - annotator := kube.NewNodeAnnotator(oc.kube, node.Name) - if config.HybridOverlay.Enabled { - if err := oc.handleHybridOverlayPort(node, annotator); err != nil { - errs = append(errs, fmt.Errorf("failed to set up hybrid overlay logical switch port for %s: %v", node.Name, err)) - } - } else { - // the node needs to cleanup Hybrid overlay annotations LogicalRouterPolicies and Hybrid overlay port - // if it has them and hybrid overlay is not enabled - if err := oc.deleteHybridOverlayPort(node); err != nil { - errs = append(errs, err) - } - if _, exist := node.Annotations[hotypes.HybridOverlayDRMAC]; exist { - annotator.Delete(hotypes.HybridOverlayDRMAC) + if nSyncs.syncHo { + annotator := kube.NewNodeAnnotator(oc.kube, node.Name) + if config.HybridOverlay.Enabled { + if err := oc.handleHybridOverlayPort(node, annotator); err != nil { + errs = append(errs, fmt.Errorf("failed to set up hybrid overlay logical switch port for %s: %v", node.Name, err)) + oc.hybridOverlayFailed.Store(node.Name, true) + } else { + oc.hybridOverlayFailed.Delete(node.Name) + } + } else { + // pedantic - node should never be stored in hybridOverlayFailed if HO is not enabled + oc.hybridOverlayFailed.Delete(node.Name) + + // the node needs to cleanup Hybrid overlay annotations LogicalRouterPolicies and Hybrid overlay port + // if it has them and hybrid overlay is not enabled + if err := oc.deleteHybridOverlayPort(node); err != nil { + errs = append(errs, err) + } else { + // only clear annotations if tear down was successful + if _, exist := node.Annotations[hotypes.HybridOverlayDRMAC]; exist { + annotator.Delete(hotypes.HybridOverlayDRMAC) + } + if _, exist := node.Annotations[hotypes.HybridOverlayDRIP]; exist { + annotator.Delete(hotypes.HybridOverlayDRIP) + } + } } - if _, exist := node.Annotations[hotypes.HybridOverlayDRIP]; exist { - annotator.Delete(hotypes.HybridOverlayDRIP) + if err := annotator.Run(); err != nil { + errs = append(errs, fmt.Errorf("failed to set hybrid overlay annotations for node %s: %v", node.Name, err)) } } - if err := annotator.Run(); err != nil { - errs = append(errs, fmt.Errorf("failed to set hybrid overlay annotations for node %s: %v", node.Name, err)) - } if nSyncs.syncGw { err := oc.syncNodeGateway(node, nil) @@ -653,8 +675,8 @@ func (oc *DefaultNetworkController) addUpdateRemoteNodeEvent(node *corev1.Node, } else { oc.syncZoneICFailed.Delete(node.Name) } + klog.V(5).Infof("Creating Interconnect resources for remote node %q on network %q took: %s", node.Name, oc.GetNetworkName(), time.Since(start)) } - klog.V(5).Infof("Creating Interconnect resources for node %v took: %s", node.Name, time.Since(start)) return err } diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 65ca015ab7..cb9f82d08f 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -715,7 +715,11 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *corev1 return nil } - klog.Infof("Adding or Updating Node %q for network %s", node.Name, oc.GetNetworkName()) + if !nodeNeedsSync(nSyncs) { + return nil + } + + klog.Infof("Adding or Updating local node %q for network %q", node.Name, oc.GetNetworkName()) if nSyncs.syncNode { if hostSubnets, err = oc.addNode(node); err != nil { oc.addNodeFailed.Store(node.Name, true) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index 3e4cfa458b..d46c567c33 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -218,7 +218,7 @@ func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { if err := zic.createRemoteZoneNodeResources(node, nodeID); err != nil { return fmt.Errorf("creating interconnect resources for remote zone node %s for the network %s failed : err - %w", node.Name, zic.GetNetworkName(), err) } - klog.Infof("Creating Interconnect resources for node %v took: %s", node.Name, time.Since(start)) + klog.Infof("Creating Interconnect resources for node %q on network %q took: %s", node.Name, zic.GetNetworkName(), time.Since(start)) return nil } From edc159d0add6c45dfd81cefccec0333bb2d96af8 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 2 Jun 2025 17:36:01 -0400 Subject: [PATCH 011/278] Optimize ic handler a little for UDN When remote nodes are added (as new UDNs are created) the first remote add always fails. This is because the controller is waiting for the subnets annotation to be updated for the network. However, it only partially fails. It fails when the routes are attempting to be added, but this is after the logical switch port logic and some other parsing has already been done. Rather than execute this work twice, just bail early if the node does not have all of the annotations yet. This way we can execute the majority of the work only one time. With this change, only once all annotations are present will you see: "Creating interconnect resources for remote zone node" Signed-off-by: Tim Rozet --- .../ovn/zone_interconnect/zone_ic_handler.go | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index d46c567c33..87e178ae5b 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -207,7 +207,6 @@ func (zic *ZoneInterconnectHandler) AddLocalZoneNode(node *corev1.Node) error { // // See createRemoteZoneNodeResources() below for more details. func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { start := time.Now() - klog.Infof("Creating interconnect resources for remote zone node %s for the network %s", node.Name, zic.GetNetworkName()) nodeID := util.GetNodeID(node) if nodeID == -1 { @@ -215,7 +214,50 @@ func (zic *ZoneInterconnectHandler) AddRemoteZoneNode(node *corev1.Node) error { return fmt.Errorf("failed to get node id for node - %s", node.Name) } - if err := zic.createRemoteZoneNodeResources(node, nodeID); err != nil { + nodeSubnets, err := util.ParseNodeHostSubnetAnnotation(node, zic.GetNetworkName()) + if err != nil { + err = fmt.Errorf("failed to parse node %s subnets annotation %w", node.Name, err) + if util.IsAnnotationNotSetError(err) { + // remote node may not have the annotation yet, suppress it + return types.NewSuppressedError(err) + } + return err + } + + nodeTransitSwitchPortIPs, err := util.ParseNodeTransitSwitchPortAddrs(node) + if err != nil || len(nodeTransitSwitchPortIPs) == 0 { + err = fmt.Errorf("failed to get the node transit switch port IP addresses : %w", err) + if util.IsAnnotationNotSetError(err) { + return types.NewSuppressedError(err) + } + return err + } + + var nodeGRPIPs []*net.IPNet + // only primary networks have cluster router connected to join switch+GR + // used for adding routes to GR + if !zic.IsSecondary() || (util.IsNetworkSegmentationSupportEnabled() && zic.IsPrimaryNetwork()) { + nodeGRPIPs, err = util.ParseNodeGatewayRouterJoinAddrs(node, zic.GetNetworkName()) + if err != nil { + if util.IsAnnotationNotSetError(err) { + // FIXME(tssurya): This is present for backwards compatibility + // Remove me a few months from now + var err1 error + nodeGRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) + if err1 != nil { + err1 = fmt.Errorf("failed to parse node %s Gateway router LRP Addrs annotation %w", node.Name, err1) + if util.IsAnnotationNotSetError(err1) { + return types.NewSuppressedError(err1) + } + return err1 + } + } + } + } + + klog.Infof("Creating interconnect resources for remote zone node %s for the network %s", node.Name, zic.GetNetworkName()) + + if err := zic.createRemoteZoneNodeResources(node, nodeID, nodeTransitSwitchPortIPs, nodeSubnets, nodeGRPIPs); err != nil { return fmt.Errorf("creating interconnect resources for remote zone node %s for the network %s failed : err - %w", node.Name, zic.GetNetworkName(), err) } klog.Infof("Creating Interconnect resources for node %q on network %q took: %s", node.Name, zic.GetNetworkName(), time.Since(start)) @@ -403,16 +445,7 @@ func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(node *corev1.No // if the node name is ovn-worker and the network name is blue, the logical port name would be - blue.tstor.ovn-worker // - binds the remote port to the node remote chassis in SBDB // - adds static routes for the remote node via the remote port ip in the ovn_cluster_router -func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.Node, nodeID int) error { - nodeTransitSwitchPortIPs, err := util.ParseNodeTransitSwitchPortAddrs(node) - if err != nil || len(nodeTransitSwitchPortIPs) == 0 { - err = fmt.Errorf("failed to get the node transit switch port IP addresses : %w", err) - if util.IsAnnotationNotSetError(err) { - return types.NewSuppressedError(err) - } - return err - } - +func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.Node, nodeID int, nodeTransitSwitchPortIPs, nodeSubnets, nodeGRPIPs []*net.IPNet) error { transitRouterPortMac := util.IPAddrToHWAddr(nodeTransitSwitchPortIPs[0].IP) var transitRouterPortNetworks []string for _, ip := range nodeTransitSwitchPortIPs { @@ -438,7 +471,7 @@ func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.N return err } - if err := zic.addRemoteNodeStaticRoutes(node, nodeTransitSwitchPortIPs); err != nil { + if err := zic.addRemoteNodeStaticRoutes(node, nodeTransitSwitchPortIPs, nodeSubnets, nodeGRPIPs); err != nil { return err } @@ -534,7 +567,7 @@ func (zic *ZoneInterconnectHandler) cleanupNodeTransitSwitchPort(nodeName string // Then the below static routes are added // ip4.dst == 10.244.0.0/24 , nexthop = 100.88.0.2 // ip4.dst == 100.64.0.2/16 , nexthop = 100.88.0.2 (only for default primary network) -func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error { +func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs, nodeSubnets, nodeGRPIPs []*net.IPNet) error { addRoute := func(prefix, nexthop string) error { logicalRouterStaticRoute := nbdb.LogicalRouterStaticRoute{ ExternalIDs: map[string]string{ @@ -554,16 +587,6 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, return nil } - nodeSubnets, err := util.ParseNodeHostSubnetAnnotation(node, zic.GetNetworkName()) - if err != nil { - err = fmt.Errorf("failed to parse node %s subnets annotation %w", node.Name, err) - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) - } - return err - } - nodeSubnetStaticRoutes := zic.getStaticRoutes(nodeSubnets, nodeTransitSwitchPortIPs, false) for _, staticRoute := range nodeSubnetStaticRoutes { // Possible optimization: Add all the routes in one transaction @@ -580,23 +603,6 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, return nil } - nodeGRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, zic.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // FIXME(tssurya): This is present for backwards compatibility - // Remove me a few months from now - var err1 error - nodeGRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) - if err1 != nil { - err1 = fmt.Errorf("failed to parse node %s Gateway router LRP Addrs annotation %w", node.Name, err1) - if util.IsAnnotationNotSetError(err1) { - return types.NewSuppressedError(err1) - } - return err1 - } - } - } - nodeGRPIPStaticRoutes := zic.getStaticRoutes(nodeGRPIPs, nodeTransitSwitchPortIPs, true) for _, staticRoute := range nodeGRPIPStaticRoutes { // Possible optimization: Add all the routes in one transaction From 98518eaae261296c10ae18d20c783056ee95ee1d Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 2 Jun 2025 17:54:18 -0400 Subject: [PATCH 012/278] Minor improvement to route add for remote zone nodes Just execute the 2 route adds in the same txn Signed-off-by: Tim Rozet --- .../ovn/zone_interconnect/zone_ic_handler.go | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index 87e178ae5b..cc849b6c15 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -13,6 +13,7 @@ import ( utilnet "k8s.io/utils/net" libovsdbclient "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" @@ -568,6 +569,7 @@ func (zic *ZoneInterconnectHandler) cleanupNodeTransitSwitchPort(nodeName string // ip4.dst == 10.244.0.0/24 , nexthop = 100.88.0.2 // ip4.dst == 100.64.0.2/16 , nexthop = 100.88.0.2 (only for default primary network) func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs, nodeSubnets, nodeGRPIPs []*net.IPNet) error { + ops := make([]ovsdb.Operation, 0, 2) addRoute := func(prefix, nexthop string) error { logicalRouterStaticRoute := nbdb.LogicalRouterStaticRoute{ ExternalIDs: map[string]string{ @@ -581,37 +583,32 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, lrsr.Nexthop == nexthop && lrsr.ExternalIDs["ic-node"] == node.Name } - if err := libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(zic.nbClient, zic.networkClusterRouterName, &logicalRouterStaticRoute, p); err != nil { - return fmt.Errorf("failed to create static route: %w", err) + var err error + ops, err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicateOps(zic.nbClient, ops, zic.networkClusterRouterName, &logicalRouterStaticRoute, p) + if err != nil { + return fmt.Errorf("failed to create static route ops: %w", err) } return nil } nodeSubnetStaticRoutes := zic.getStaticRoutes(nodeSubnets, nodeTransitSwitchPortIPs, false) for _, staticRoute := range nodeSubnetStaticRoutes { - // Possible optimization: Add all the routes in one transaction if err := addRoute(staticRoute.prefix, staticRoute.nexthop); err != nil { return fmt.Errorf("error adding static route %s - %s to the router %s : %w", staticRoute.prefix, staticRoute.nexthop, zic.networkClusterRouterName, err) } } - if zic.IsSecondary() && !(util.IsNetworkSegmentationSupportEnabled() && zic.IsPrimaryNetwork()) { - // Secondary network cluster router doesn't connect to a join switch - // or to a Gateway router. - // - // Except for UDN primary L3 networks. - return nil - } - - nodeGRPIPStaticRoutes := zic.getStaticRoutes(nodeGRPIPs, nodeTransitSwitchPortIPs, true) - for _, staticRoute := range nodeGRPIPStaticRoutes { - // Possible optimization: Add all the routes in one transaction - if err := addRoute(staticRoute.prefix, staticRoute.nexthop); err != nil { - return fmt.Errorf("error adding static route %s - %s to the router %s : %w", staticRoute.prefix, staticRoute.nexthop, zic.networkClusterRouterName, err) + if len(nodeGRPIPs) > 0 { + nodeGRPIPStaticRoutes := zic.getStaticRoutes(nodeGRPIPs, nodeTransitSwitchPortIPs, true) + for _, staticRoute := range nodeGRPIPStaticRoutes { + if err := addRoute(staticRoute.prefix, staticRoute.nexthop); err != nil { + return fmt.Errorf("error adding static route %s - %s to the router %s : %w", staticRoute.prefix, staticRoute.nexthop, zic.networkClusterRouterName, err) + } } } - return nil + _, err := libovsdbops.TransactAndCheck(zic.nbClient, ops) + return err } // deleteLocalNodeStaticRoutes deletes the static routes added by the function addRemoteNodeStaticRoutes From 4fa8bf0087671d2683679bd24a6e217050e09f5c Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 3 Jun 2025 10:15:44 +0200 Subject: [PATCH 013/278] udn: Fix NAD template for join subnets field When a CUDN/UDN is create with joinSubnets field configured it should generate the net-attach-def with `joinSubnet` field, the code was using `joinSubnets` wich is not undertood by ovn-kubernetes. Signed-off-by: Enrique Llorente --- .../template/net-attach-def-template.go | 2 +- .../template/net-attach-def-template_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go index 8d11c0960b..a06e7085ed 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go @@ -192,7 +192,7 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s cniNetConf["mtu"] = mtu } if len(netConfSpec.JoinSubnet) > 0 { - cniNetConf["joinSubnets"] = netConfSpec.JoinSubnet + cniNetConf["joinSubnet"] = netConfSpec.JoinSubnet } if len(netConfSpec.Subnets) > 0 { cniNetConf["subnets"] = netConfSpec.Subnets diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go index 0c06f3a270..68f2e4022a 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go @@ -326,7 +326,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer3", - "joinSubnets": "100.65.0.0/16,fd99::/64", + "joinSubnet": "100.65.0.0/16,fd99::/64", "subnets": "192.168.100.0/16,2001:dbb::/60", "mtu": 1500 }`, @@ -350,7 +350,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer2", - "joinSubnets": "100.65.0.0/16,fd99::/64", + "joinSubnet": "100.65.0.0/16,fd99::/64", "subnets": "192.168.100.0/24,2001:dbb::/64", "mtu": 1500, "allowPersistentIPs": true @@ -376,7 +376,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer2", - "joinSubnets": "100.62.0.0/24,fd92::/64", + "joinSubnet": "100.62.0.0/24,fd92::/64", "subnets": "192.168.100.0/24,2001:dbb::/64", "mtu": 1500, "allowPersistentIPs": true @@ -461,7 +461,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer3", - "joinSubnets": "100.65.0.0/16,fd99::/64", + "joinSubnet": "100.65.0.0/16,fd99::/64", "subnets": "192.168.100.0/16,2001:dbb::/60", "mtu": 1500 }`, @@ -485,7 +485,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer2", - "joinSubnets": "100.65.0.0/16,fd99::/64", + "joinSubnet": "100.65.0.0/16,fd99::/64", "subnets": "192.168.100.0/24,2001:dbb::/64", "mtu": 1500, "allowPersistentIPs": true @@ -511,7 +511,7 @@ var _ = Describe("NetAttachDefTemplate", func() { "netAttachDefName": "mynamespace/test-net", "role": "primary", "topology": "layer2", - "joinSubnets": "100.62.0.0/24,fd92::/64", + "joinSubnet": "100.62.0.0/24,fd92::/64", "subnets": "192.168.100.0/24,2001:dbb::/64", "mtu": 1500, "allowPersistentIPs": true From 399915a64ba05538a1c41e67f91c1c1d95f3c397 Mon Sep 17 00:00:00 2001 From: Alin Gabriel Serdean Date: Sun, 8 Jun 2025 14:14:35 +0000 Subject: [PATCH 014/278] workflow: Add fix missing and apt update before trying to install VRF module Signed-off-by: Alin Gabriel Serdean --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dea0289e73..748144a7cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -200,6 +200,7 @@ jobs: if: steps.is_pr_image_build_needed.outputs.PR_IMAGE_RESTORED != 'true' && success() run: | set -x + sudo apt update sudo apt-get install linux-modules-extra-$(uname -r) -y sudo modprobe vrf @@ -500,6 +501,7 @@ jobs: - name: Install VRF kernel module run: | set -x + sudo apt update sudo apt-get install linux-modules-extra-$(uname -r) -y sudo modprobe vrf From 575f3c017c8b729b836a61c0fcb409b4b910803b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 27 May 2025 10:30:52 +0000 Subject: [PATCH 015/278] Align e2e test timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So that ginkgo times out first and we get useful output. Signed-off-by: Jaime Caamaño Ruiz --- .github/workflows/test.yml | 4 +++- test/scripts/e2e-cp.sh | 9 +++++++-- test/scripts/e2e-kind.sh | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 748144a7cf..7e574ecf4d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -616,7 +616,9 @@ jobs: - name: Run Tests # e2e tests take ~60 minutes normally, 120 should be more than enough # set 3 hours for control-plane tests as these might take a while - timeout-minutes: ${{ matrix.target == 'control-plane' && 180 || matrix.target == 'control-plane-helm' && 180 || matrix.target == 'external-gateway' && 180 || 120 }} + # give 10m extra to give ginkgo chance to timeout before github so that we + # get its output + timeout-minutes: ${{ matrix.target == 'bgp' && 190 || matrix.target == 'control-plane' && 190 || matrix.target == 'control-plane-helm' && 190 || matrix.target == 'external-gateway' && 190 || 130 }} run: | # used by e2e diagnostics package export OVN_IMAGE="ovn-daemonset-fedora:pr" diff --git a/test/scripts/e2e-cp.sh b/test/scripts/e2e-cp.sh index cf9589e589..40d8071f92 100755 --- a/test/scripts/e2e-cp.sh +++ b/test/scripts/e2e-cp.sh @@ -184,13 +184,18 @@ export NUM_NODES=2 FOCUS=$(echo ${@:1} | sed 's/ /\\s/g') +# Ginkgo test timeout needs to be lower than both github's timeout and go test +# timeout to be able to get proper Ginkgo output when it happens. +TEST_TIMEOUT=${TEST_TIMEOUT:-180} +GO_TEST_TIMEOUT=$((TEST_TIMEOUT + 5)) + pushd e2e go mod download -go test -test.timeout 180m -v . \ +go test -test.timeout ${GO_TEST_TIMEOUT}m -v . \ -ginkgo.v \ -ginkgo.focus ${FOCUS:-.} \ - -ginkgo.timeout 3h \ + -ginkgo.timeout ${TEST_TIMEOUT}m \ -ginkgo.flake-attempts ${FLAKE_ATTEMPTS:-2} \ -ginkgo.skip="${SKIPPED_TESTS}" \ -ginkgo.junit-report=${E2E_REPORT_DIR}/junit_${E2E_REPORT_PREFIX}report.xml \ diff --git a/test/scripts/e2e-kind.sh b/test/scripts/e2e-kind.sh index 2ec08b59ff..1cab2fc05d 100755 --- a/test/scripts/e2e-kind.sh +++ b/test/scripts/e2e-kind.sh @@ -200,7 +200,7 @@ fi # timeout needs to be lower than github's timeout. Otherwise github terminates # the job and doesn't give ginkgo a chance to print status so that we know why # the timeout happened. -TEST_TIMEOUT=${TEST_TIMEOUT:-100m} +TEST_TIMEOUT=${TEST_TIMEOUT:-120m} ginkgo --nodes=${NUM_NODES} \ --focus=${FOCUS} \ From b5bc88df55c4ac0654d2350f01996f85474f43ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 5 Jun 2025 13:01:17 +0000 Subject: [PATCH 016/278] Bump priority of egress ClusterIP traffic drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a flow [1] to prevent leaking traffic towards a ClusterIP. However we also have a flow to prevent EIP traffic to egress before being SNATed and an additional flow to actually allow the traffic to egress in ICNI/BGP scenarios for pods on the nodes subnet [2]. The higher priority of flow [2] prevents flow [1] to be in effect. Bump priority of flow [1] since there is no case where we should leak traffic towards ClusterIPs. [1] cookie=0xdeff105, duration=492.235s, table=0, n_packets=0, n_bytes=0, priority=105,ipv6,in_port="patch-breth0_ov",ipv6_dst=fd00:10:96::/112 actions=drop [2] cookie=0xdeff105, duration=2308.615s, table=0, n_packets=4, n_bytes=376, priority=109,ipv6,in_port="patch-breth0_ov",dl_src=96:b0:34:18:12:7c,ipv6_src=fd00:10:244:1::/64 actions=ct(commit,zone=64000,exec(load:0x1->NXM_NX_CT_MARK[])),output:eth0 cookie=0xdeff105, duration=1991.854s, table=0, n_packets=0, n_bytes=0, priority=104,ipv6,in_port="patch-breth0_ov",ipv6_src=fd00:10:244::/48 actions=drop Signed-off-by: Jaime Caamaño Ruiz --- go-controller/pkg/node/gateway_shared_intf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index bd7a17f8ca..2654291850 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1676,7 +1676,7 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st // at the GR load balancer or switch load balancer. It means the correct port wasn't provided. // nodeCIDR->serviceCIDR traffic flow is internal and it shouldn't be carried to outside the cluster dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=105, in_port=%s, %s, %s_dst=%s,"+ + fmt.Sprintf("cookie=%s, priority=115, in_port=%s, %s, %s_dst=%s,"+ "actions=drop", defaultOpenFlowCookie, netConfig.ofPortPatch, protoPrefix, protoPrefix, svcCIDR)) } } From 19f39c2cfa16a18bab3bf7b9287db7ef36dacd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 27 May 2025 10:33:08 +0000 Subject: [PATCH 017/278] Change BGP e2e lane config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change configuration in preparation for running all control plane tests: * Make both dualstack, not much value testing IPv4 single stack * Make one of the lanes noSnatGW to get signal from that as well * Enable multicast and empty LB events * Configure host to be able to route to networks from the external world * Ensure frr container is not able to route through the host/runner Signed-off-by: Jaime Caamaño Ruiz --- .github/workflows/test.yml | 8 ++++---- contrib/kind-common | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e574ecf4d..2cb46a04ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -464,15 +464,15 @@ jobs: - {"target": "network-segmentation", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "network-segmentation", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "bgp", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation"} - - {"target": "bgp", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation"} + - {"target": "bgp", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "dualstack", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation"} - {"target": "traffic-flow-test-only","ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "traffic-flow-tests": "1-24", "network-segmentation": "enable-network-segmentation"} - {"target": "tools", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "network-segmentation": "enable-network-segmentation"} needs: [ build-pr ] env: JOB_NAME: "${{ matrix.target }}-${{ matrix.ha }}-${{ matrix.gateway-mode }}-${{ matrix.ipfamily }}-${{ matrix.disable-snat-multiple-gws }}-${{ matrix.second-bridge }}-${{ matrix.ic }}" OVN_HYBRID_OVERLAY_ENABLE: ${{ (matrix.target == 'control-plane' || matrix.target == 'control-plane-helm') && (matrix.ipfamily == 'ipv4' || matrix.ipfamily == 'dualstack' ) }} - OVN_MULTICAST_ENABLE: "${{ matrix.target == 'control-plane' || matrix.target == 'control-plane-helm' || matrix.target == 'network-segmentation' }}" - OVN_EMPTY_LB_EVENTS: "${{ matrix.target == 'control-plane' || matrix.target == 'control-plane-helm' }}" + OVN_MULTICAST_ENABLE: "${{ matrix.target == 'control-plane' || matrix.target == 'control-plane-helm' || matrix.target == 'network-segmentation' || matrix.target == 'bgp' }}" + OVN_EMPTY_LB_EVENTS: "${{ matrix.target == 'control-plane' || matrix.target == 'control-plane-helm' || matrix.target == 'bgp' }}" OVN_HA: "${{ matrix.ha == 'HA' }}" OVN_DISABLE_SNAT_MULTIPLE_GWS: "${{ matrix.disable-snat-multiple-gws == 'noSnatGW' }}" KIND_INSTALL_METALLB: "${{ matrix.target == 'control-plane' || matrix.target == 'control-plane-helm' || matrix.target == 'network-segmentation' }}" @@ -559,7 +559,7 @@ jobs: echo OVN_TEST_EX_GW_NETWORK=xgw >> $GITHUB_ENV echo OVN_ENABLE_EX_GW_NETWORK_BRIDGE=true >> $GITHUB_ENV fi - if [[ "$JOB_NAME" == *"shard-conformance"* ]] && [ "$ADVERTISE_DEFAULT_NETWORK" == "true" ]; then + if [ "$ADVERTISE_DEFAULT_NETWORK" == "true" ]; then echo "ADVERTISE_DEFAULT_NETWORK=true" >> $GITHUB_ENV # Use proper variable declaration with default values diff --git a/contrib/kind-common b/contrib/kind-common index 66cc078d3e..e8bfb7be01 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -751,6 +751,13 @@ deploy_bgp_external_server() { echo "FRR kind network IPv6: ${bgp_network_frr_v6}" $OCI_BIN exec bgpserver ip -6 route replace default via "$bgp_network_frr_v6" fi + # disable the default route to make sure the container only routes accross + # directly connected or learnt networks (doing this at the very end since + # docker changes the routing table when a new network is connected) + docker exec frr ip route delete default + docker exec frr ip route + docker exec frr ip -6 route delete default + docker exec frr ip -6 route } destroy_bgp() { @@ -817,7 +824,7 @@ EOF rm -rf "${FRR_TMP_DIR}" # Add routes for pod networks dynamically into the github runner for return traffic to pass back - if [ -n "${JOB_NAME:-}" ] && [[ "$JOB_NAME" == *"shard-conformance"* ]] && [ "$ADVERTISE_DEFAULT_NETWORK" == "true" ]; then + if [ "$ADVERTISE_DEFAULT_NETWORK" = "true" ]; then echo "Adding routes for Kubernetes pod networks..." NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}') echo "Found nodes: $NODES" @@ -835,7 +842,7 @@ EOF # Add IPv4 route if [ -n "$ipv4_subnet" ] && [ -n "$node_ipv4" ]; then echo "Adding IPv4 route for $node ($node_ipv4): $ipv4_subnet" - sudo ip route add $ipv4_subnet via $node_ipv4 + sudo ip route replace $ipv4_subnet via $node_ipv4 fi fi @@ -847,7 +854,7 @@ EOF if [ -n "$ipv6_subnet" ] && [ -n "$node_ipv6" ]; then echo "Adding IPv6 route for $node ($node_ipv6): $ipv6_subnet" - sudo ip -6 route add $ipv6_subnet via $node_ipv6 + sudo ip -6 route replace $ipv6_subnet via $node_ipv6 fi fi done From 90b88fabf98655ae675d36d71a25e4b099145ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 27 May 2025 10:36:09 +0000 Subject: [PATCH 018/278] Run almost all control plane tests in BGP lanes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip those test that wouldn't be supported or otherwise require additional work. Signed-off-by: Jaime Caamaño Ruiz --- .github/workflows/test.yml | 2 +- test/scripts/e2e-cp.sh | 157 +++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 77 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2cb46a04ee..7965b19d76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -643,7 +643,7 @@ jobs: elif [ "${{ matrix.target }}" == "network-segmentation" ]; then make -C test control-plane WHAT="Network Segmentation" elif [ "${{ matrix.target }}" == "bgp" ]; then - make -C test control-plane WHAT="BGP" + make -C test control-plane elif [ "${{ matrix.target }}" == "tools" ]; then make -C go-controller build make -C test tools diff --git a/test/scripts/e2e-cp.sh b/test/scripts/e2e-cp.sh index 40d8071f92..59fc1cd01a 100755 --- a/test/scripts/e2e-cp.sh +++ b/test/scripts/e2e-cp.sh @@ -33,147 +33,152 @@ queries to the hostNetworked server pod on another node shall work for UDP|\ ipv4 pod" SKIPPED_TESTS="" +skip() { + if [ "$SKIPPED_TESTS" != "" ]; then + SKIPPED_TESTS+="|" + fi + SKIPPED_TESTS+=$* +} if [ "$PLATFORM_IPV4_SUPPORT" == true ]; then - if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then - # No support for these features in dual-stack yet - SKIPPED_TESTS="hybrid.overlay" - else - # Skip sflow in IPv4 since it's a long test (~5 minutes) - # We're validating netflow v5 with an ipv4 cluster, sflow with an ipv6 cluster - SKIPPED_TESTS="Should validate flow data of br-int is sent to an external gateway with sflow|ipv6 pod" - fi + if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then + # No support for these features in dual-stack yet + skip "hybrid.overlay" + else + # Skip sflow in IPv4 since it's a long test (~5 minutes) + # We're validating netflow v5 with an ipv4 cluster, sflow with an ipv6 cluster + skip "Should validate flow data of br-int is sent to an external gateway with sflow|ipv6 pod" + fi fi if [ "$PLATFORM_IPV4_SUPPORT" == false ]; then - SKIPPED_TESTS+="\[IPv4\]" + skip "\[IPv4\]" fi if [ "$OVN_HA" == false ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi # No support for these features in no-ha mode yet # TODO streamline the db delete tests - SKIPPED_TESTS+="recovering from deleting db files while maintaining connectivity|\ -Should validate connectivity before and after deleting all the db-pods at once in HA mode" + skip "recovering from deleting db files while maintaining connectivity" + skip "Should validate connectivity before and after deleting all the db-pods at once in HA mode" else - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - - SKIPPED_TESTS+="Should validate connectivity before and after deleting all the db-pods at once in Non-HA mode|\ - e2e br-int NetFlow export validation" + skip "Should validate connectivity before and after deleting all the db-pods at once in Non-HA mode" + skip "e2e br-int NetFlow export validation" fi if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi # No support for these tests in IPv6 mode yet - SKIPPED_TESTS+=$IPV6_SKIPPED_TESTS + skip $IPV6_SKIPPED_TESTS fi if [ "$OVN_DISABLE_SNAT_MULTIPLE_GWS" == false ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="e2e multiple external gateway stale conntrack entry deletion validation" + skip "e2e multiple external gateway stale conntrack entry deletion validation" fi if [ "$OVN_GATEWAY_MODE" == "shared" ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="Should ensure load balancer service|LGW" # See https://github.com/ovn-org/ovn-kubernetes/issues/4138 for details + skip "Should ensure load balancer service|LGW" fi if [ "$OVN_GATEWAY_MODE" == "local" ]; then - # See https://github.com/ovn-org/ovn-kubernetes/labels/ci-ipv6 for details: + # See https://github.com/ovn-org/ovn-kubernetes/labels/ci-ipv6 for details if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="Should be allowed by nodeport services|\ -Should successfully create then remove a static pod|\ -Should validate connectivity from a pod to a non-node host address on same node|\ -Should validate connectivity within a namespace of pods on separate nodes|\ -Services" + skip "Should be allowed by nodeport services" + skip "Should successfully create then remove a static pod" + skip "Should validate connectivity from a pod to a non-node host address on same node" + skip "Should validate connectivity within a namespace of pods on separate nodes" + skip "Services" fi fi # skipping the egress ip legacy health check test because it requires two # sequenced rollouts of both ovnkube-node and ovnkube-master that take a lot of # time. -SKIPPED_TESTS+="${SKIPPED_TESTS:+|}disabling egress nodes impeding Legacy health check" +skip "disabling egress nodes impeding Legacy health check" if [ "$ENABLE_MULTI_NET" != "true" ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="Multi Homing" + skip "Multi Homing" fi if [ "$OVN_NETWORK_QOS_ENABLE" != "true" ]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="e2e NetworkQoS validation" + skip "e2e NetworkQoS validation" fi # Only run Node IP/MAC address migration tests if they are explicitly requested IP_MIGRATION_TESTS="Node IP and MAC address migration" if [[ "${WHAT}" != "${IP_MIGRATION_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="Node IP and MAC address migration" + skip "Node IP and MAC address migration" fi # Only run Multi node zones interconnect tests if they are explicitly requested MULTI_NODE_ZONES_TESTS="Multi node zones interconnect" if [[ "${WHAT}" != "${MULTI_NODE_ZONES_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="Multi node zones interconnect" + skip "Multi node zones interconnect" fi # Only run external gateway tests if they are explicitly requested EXTERNAL_GATEWAY_TESTS="External Gateway" if [[ "${WHAT}" != "${EXTERNAL_GATEWAY_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+="External Gateway" + skip "External Gateway" fi # Only run kubevirt virtual machines tests if they are explicitly requested KV_LIVE_MIGRATION_TESTS="Kubevirt Virtual Machines" if [[ "${WHAT}" != "${KV_LIVE_MIGRATION_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+=$KV_LIVE_MIGRATION_TESTS + skip $KV_LIVE_MIGRATION_TESTS fi # Only run network segmentation tests if they are explicitly requested NETWORK_SEGMENTATION_TESTS="Network Segmentation" if [[ "${WHAT}" != "${NETWORK_SEGMENTATION_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" - fi - SKIPPED_TESTS+=$NETWORK_SEGMENTATION_TESTS + skip $NETWORK_SEGMENTATION_TESTS fi -# Only run bgp tests if they are explicitly requested BGP_TESTS="BGP" -if [[ "${WHAT}" != "${BGP_TESTS}"* ]]; then - if [ "$SKIPPED_TESTS" != "" ]; then - SKIPPED_TESTS+="|" +if [ "$ENABLE_ROUTE_ADVERTISEMENTS" != true ]; then + skip $BGP_TESTS +else + if [ "$ADVERTISE_DEFAULT_NETWORK" = true ]; then + # Some test don't work when the default network is advertised, either because + # the configuration that the test excercises does not make sense for an advertised network, or + # there is some bug or functional gap + # call out case by case + + # pod reached from default network through secondary interface, asymetric, configuration does not make sense + # TODO: perhaps the secondary network attached pods should not be attached to default network + skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to breth0 can be reached by a client pod in the default network on the same node" + skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to breth0 can be reached by a client pod in the default network on a different node" + + # these tests require metallb but the configuration we do for it is not compatible with the configuration we do to advertise the default network + # TODO: consolidate configuration + skip "Load Balancer Service Tests with MetalLB" + skip "EgressService" + + # tests that specifically expect the node SNAT to happen + # TODO: expect the pod IP where it makes sense + skip "e2e egress firewall policy validation with external containers" + skip "e2e egress IP validation Cluster Default Network \[OVN network\] Using different methods to disable a node's availability for egress Should validate the egress IP functionality against remote hosts" + skip "e2e egress IP validation Cluster Default Network \[OVN network\] Should validate the egress IP SNAT functionality against host-networked pods" + skip "e2e egress IP validation Cluster Default Network Should validate egress IP logic when one pod is managed by more than one egressIP object" + skip "e2e egress IP validation Cluster Default Network Should re-assign egress IPs when node readiness / reachability goes down/up" + skip "Pod to external server PMTUD when a client ovnk pod targeting an external server is created when tests are run towards the agnhost echo server queries to the hostNetworked server pod on another node shall work for UDP" + + # https://issues.redhat.com/browse/OCPBUGS-55028 + skip "e2e egress IP validation Cluster Default Network \[secondary-host-eip\]" + + # https://issues.redhat.com/browse/OCPBUGS-50636 + skip "Services of type NodePort should listen on each host addresses" + skip "Services of type NodePort should work on secondary node interfaces for ETP=local and ETP=cluster when backend pods are also served by EgressIP" + + # https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5240 + skip "e2e control plane test node readiness according to its defaults interface MTU size should get node not ready with a too small MTU" + + # buggy tests that don't work in dual stack mode + skip "Service Hairpin SNAT Should ensure service hairpin traffic is NOT SNATed to hairpin masquerade IP; GR LB" + skip "Services when a nodePort service targeting a pod with hostNetwork:false is created when tests are run towards the agnhost echo service queries to the nodePort service shall work for TCP" + skip "Services when a nodePort service targeting a pod with hostNetwork:true is created when tests are run towards the agnhost echo service queries to the nodePort service shall work for TCP" + skip "Services when a nodePort service targeting a pod with hostNetwork:false is created when tests are run towards the agnhost echo service queries to the nodePort service shall work for UDP" + skip "Services when a nodePort service targeting a pod with hostNetwork:true is created when tests are run towards the agnhost echo service queries to the nodePort service shall work for UDP" fi - SKIPPED_TESTS+=$BGP_TESTS fi # setting these is required to make RuntimeClass tests work ... :/ @@ -182,7 +187,7 @@ export KUBE_CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock export KUBE_CONTAINER_RUNTIME_NAME=containerd export NUM_NODES=2 -FOCUS=$(echo ${@:1} | sed 's/ /\\s/g') +FOCUS=$(echo "${@:1}" | sed 's/ /\\s/g') # Ginkgo test timeout needs to be lower than both github's timeout and go test # timeout to be able to get proper Ginkgo output when it happens. From f84d3f34252561e2ded05fcf39d026b615268107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 5 Jun 2025 18:10:54 +0000 Subject: [PATCH 019/278] Fix HO test flake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Caamaño Ruiz --- go-controller/pkg/ovn/master_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index 914bf7f326..0c3ba9e7a8 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -1639,7 +1639,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By("adding the node becomes possible") - gomega.Expect(oc.retryNodes.ResourceHandler.AddResource(&testNode, false)).To(gomega.Succeed()) + gomega.Eventually(oc.retryNodes.ResourceHandler.AddResource).WithArguments(&testNode, false).Should(gomega.Succeed()) return nil } From 9554ba6e988904cb3341ba4a91de520526e9436a Mon Sep 17 00:00:00 2001 From: Yossi Boaron Date: Thu, 13 Mar 2025 10:09:19 +0200 Subject: [PATCH 020/278] Add dontSNAT subnets rules to mgmtport-snat This PR adds rules to prevent SNAT if source IP belongs to the mgmtport-no-snat-subnets-v4 or mgmtport-no-snat-subnets-v6 sets, which store IPv4 and IPv6 subnets, respectively. Signed-off-by: Yossi Boaron --- .../pkg/node/managementport/port_linux.go | 28 +++++++++++++++++++ go-controller/pkg/types/const.go | 6 ++++ 2 files changed, 34 insertions(+) diff --git a/go-controller/pkg/node/managementport/port_linux.go b/go-controller/pkg/node/managementport/port_linux.go index 378e09b238..4fbd561017 100644 --- a/go-controller/pkg/node/managementport/port_linux.go +++ b/go-controller/pkg/node/managementport/port_linux.go @@ -321,6 +321,18 @@ func setupManagementPortNFTSets() error { Comment: knftables.PtrTo("eTP:Local short-circuit not subject to management port SNAT (IPv6)"), Type: "ipv6_addr . inet_proto . inet_service", }) + tx.Add(&knftables.Set{ + Name: types.NFTMgmtPortNoSNATSubnetsV4, + Comment: knftables.PtrTo("subnets not subject to management port SNAT (IPv4)"), + Type: "ipv4_addr", + Flags: []knftables.SetFlag{knftables.IntervalFlag}, + }) + tx.Add(&knftables.Set{ + Name: types.NFTMgmtPortNoSNATSubnetsV6, + Comment: knftables.PtrTo("subnets not subject to management port SNAT (IPv6)"), + Type: "ipv6_addr", + Flags: []knftables.SetFlag{knftables.IntervalFlag}, + }) err = nft.Run(context.TODO(), tx) if err != nil { @@ -402,6 +414,14 @@ func setupManagementPortNFTChain(interfaceName string, cfg *managementPortConfig "return", ), }) + tx.Add(&knftables.Rule{ + Chain: nftMgmtPortChain, + Rule: knftables.Concat( + "ip saddr", "@", types.NFTMgmtPortNoSNATSubnetsV4, + counterIfDebug, + "return", + ), + }) tx.Add(&knftables.Rule{ Chain: nftMgmtPortChain, Rule: knftables.Concat( @@ -441,6 +461,14 @@ func setupManagementPortNFTChain(interfaceName string, cfg *managementPortConfig "return", ), }) + tx.Add(&knftables.Rule{ + Chain: nftMgmtPortChain, + Rule: knftables.Concat( + "ip6 saddr", "@", types.NFTMgmtPortNoSNATSubnetsV6, + counterIfDebug, + "return", + ), + }) tx.Add(&knftables.Rule{ Chain: nftMgmtPortChain, Rule: knftables.Concat( diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index 92e5fc54c1..2acd2d5a23 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -330,4 +330,10 @@ const ( MetricOvsNamespace = "ovs" MetricOvsSubsystemVswitchd = "vswitchd" MetricOvsSubsystemDB = "db" + + // "mgmtport-no-snat-subnets-v4" and "mgmtport-no-snat-subnets-v6" are sets containing + // subnets, indicating traffic that should not be SNATted when passing through the + // management port. + NFTMgmtPortNoSNATSubnetsV4 = "mgmtport-no-snat-subnets-v4" + NFTMgmtPortNoSNATSubnetsV6 = "mgmtport-no-snat-subnets-v6" ) From 707776527e18d065ecf9f00a9d6ad4b298dc0345 Mon Sep 17 00:00:00 2001 From: Yossi Boaron Date: Mon, 14 Apr 2025 14:42:41 +0300 Subject: [PATCH 021/278] Configure mgmtport-no-snat-subnets sets elements Currently traffic gets SNATed at ovn-k8s-mp0 within the mgmtport-snat chain. Since OVNK has transitioned to nftables, this behavior can no longer be overridden. Previously, with iptables, SNAT could be avoided by adding a higher-priority rule in the POSTROUTING chain. However, with nftables, all rules are evaluated before making a final decision, making it impossible to skip SNAT. Some applications, like Submariner, need to preserve the source IP when traffic reaches the destination pod, as certain use cases depend on it. This PR Update mgmtport-no-snat-subnets-v4 and mgmtport-no-snat-subnets-v6 nftables set based on node's annotation values. Signed-off-by: Yossi Boaron --- .../pkg/node/managementport/port_linux.go | 51 +++++++++++++++++++ go-controller/pkg/node/obj_retry_node.go | 23 ++++++++- go-controller/pkg/ovnwebhook/nodeadmission.go | 1 + go-controller/pkg/util/node_annotations.go | 43 ++++++++++++++-- 4 files changed, 112 insertions(+), 6 deletions(-) diff --git a/go-controller/pkg/node/managementport/port_linux.go b/go-controller/pkg/node/managementport/port_linux.go index 4fbd561017..480c6f2789 100644 --- a/go-controller/pkg/node/managementport/port_linux.go +++ b/go-controller/pkg/node/managementport/port_linux.go @@ -485,6 +485,57 @@ func setupManagementPortNFTChain(interfaceName string, cfg *managementPortConfig return nil } +func UpdateNoSNATSubnetsSets(node *corev1.Node, getSubnetsFn func(*corev1.Node) ([]string, error)) error { + subnetsList, err := getSubnetsFn(node) + if err != nil { + return fmt.Errorf("error retrieving subnets list: %w", err) + } + + subNetV4 := make([]*knftables.Element, 0) + subNetV6 := make([]*knftables.Element, 0) + + for _, subnet := range subnetsList { + if utilnet.IPFamilyOfCIDRString(subnet) == utilnet.IPv4 { + subNetV4 = append(subNetV4, + &knftables.Element{ + Set: types.NFTMgmtPortNoSNATSubnetsV4, + Key: []string{subnet}, + }, + ) + } + if utilnet.IPFamilyOfCIDRString(subnet) == utilnet.IPv6 { + subNetV6 = append(subNetV6, + &knftables.Element{ + Set: types.NFTMgmtPortNoSNATSubnetsV6, + Key: []string{subnet}, + }, + ) + } + + } + nft, err := nodenft.GetNFTablesHelper() + if err != nil { + return fmt.Errorf("failed to get nftables: %v", err) + } + + tx := nft.NewTransaction() + tx.Flush(&knftables.Set{ + Name: types.NFTMgmtPortNoSNATSubnetsV4, + }) + tx.Flush(&knftables.Set{ + Name: types.NFTMgmtPortNoSNATSubnetsV6, + }) + + for _, elem := range subNetV4 { + tx.Add(elem) + } + for _, elem := range subNetV6 { + tx.Add(elem) + } + + return nft.Run(context.TODO(), tx) +} + // createPlatformManagementPort creates a management port attached to the node switch // that lets the node access its pods via their private IP address. This is used // for health checking and other management tasks. diff --git a/go-controller/pkg/node/obj_retry_node.go b/go-controller/pkg/node/obj_retry_node.go index 148bb3cc40..9c9657678e 100644 --- a/go-controller/pkg/node/obj_retry_node.go +++ b/go-controller/pkg/node/obj_retry_node.go @@ -11,6 +11,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -121,7 +122,7 @@ func (h *nodeEventHandler) AreResourcesEqual(obj1, obj2 interface{}) (bool, erro if !ok { return false, fmt.Errorf("could not cast obj2 of type %T to *kapi.Node", obj2) } - return reflect.DeepEqual(node1.Status.Addresses, node2.Status.Addresses), nil + return reflect.DeepEqual(node1.Status.Addresses, node2.Status.Addresses) && reflect.DeepEqual(node1.Annotations, node2.Annotations), nil default: return false, fmt.Errorf("no object comparison for type %s", h.objType) @@ -175,6 +176,13 @@ func (h *nodeEventHandler) AddResource(obj interface{}, _ bool) error { node := obj.(*corev1.Node) // if it's our node that is changing, then nothing to do as we dont add our own IP to the nftables rules if node.Name == h.nc.name { + if util.NodeDontSNATSubnetAnnotationExist(node) { + err := managementport.UpdateNoSNATSubnetsSets(node, util.ParseNodeDontSNATSubnetsList) + if err != nil { + return fmt.Errorf("error updating no snat subnets sets: %w", err) + } + } + return nil } return h.nc.addOrUpdateNode(node) @@ -218,6 +226,15 @@ func (h *nodeEventHandler) UpdateResource(oldObj, newObj interface{}, _ bool) er // if it's our node that is changing, then nothing to do as we dont add our own IP to the nftables rules if newNode.Name == h.nc.name { + + // if node's dont SNAT subnet annotation changed sync nftables + if !reflect.DeepEqual(oldNode.Annotations, newNode.Annotations) && + util.NodeDontSNATSubnetAnnotationChanged(oldNode, newNode) { + err := managementport.UpdateNoSNATSubnetsSets(newNode, util.ParseNodeDontSNATSubnetsList) + if err != nil { + return fmt.Errorf("error updating no snat subnets sets: %w", err) + } + } return nil } @@ -273,6 +290,10 @@ func (h *nodeEventHandler) DeleteResource(obj, _ interface{}) error { case factory.NodeType: h.nc.deleteNode(obj.(*corev1.Node)) + _ = managementport.UpdateNoSNATSubnetsSets(obj.(*corev1.Node), func(_ *corev1.Node) ([]string, error) { + return []string{}, nil + }) + return nil default: diff --git a/go-controller/pkg/ovnwebhook/nodeadmission.go b/go-controller/pkg/ovnwebhook/nodeadmission.go index b21a51bc87..08509903c9 100644 --- a/go-controller/pkg/ovnwebhook/nodeadmission.go +++ b/go-controller/pkg/ovnwebhook/nodeadmission.go @@ -34,6 +34,7 @@ var commonNodeAnnotationChecks = map[string]checkNodeAnnot{ util.OvnNodeMasqCIDR: nil, util.OvnNodeGatewayMtuSupport: nil, util.OvnNodeManagementPort: nil, + util.OvnNodeDontSNATSubnets: nil, util.OvnNodeChassisID: func(v annotationChange, _ string) error { if v.action == removed { return fmt.Errorf("%s cannot be removed", util.OvnNodeChassisID) diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index d3be36f2db..4e9a984748 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -154,6 +154,9 @@ const ( // ovnNodeEncapIPs is used to indicate encap IPs set on the node OVNNodeEncapIPs = "k8s.ovn.org/node-encap-ips" + + // OvnNodeDontSNATSubnets is a user assigned source subnets that should avoid SNAT at ovn-k8s-mp0 interface + OvnNodeDontSNATSubnets = "k8s.ovn.org/node-ingress-snat-exclude-subnets" ) type L3GatewayConfig struct { @@ -1115,15 +1118,45 @@ func ParseNodeHostCIDRsExcludeOVNNetworks(node *corev1.Node) ([]string, error) { } func ParseNodeHostCIDRsList(node *corev1.Node) ([]string, error) { - addrAnnotation, ok := node.Annotations[OVNNodeHostCIDRs] + return parseNodeAnnotationList(node, OVNNodeHostCIDRs) +} + +func ParseNodeDontSNATSubnetsList(node *corev1.Node) ([]string, error) { + return parseNodeAnnotationList(node, OvnNodeDontSNATSubnets) +} + +// NodeDontSNATSubnetAnnotationChanged returns true if the OvnNodeDontSNATSubnets in the corev1.Nodes doesn't match +func NodeDontSNATSubnetAnnotationChanged(oldNode, newNode *corev1.Node) bool { + oldVal, oldOk := oldNode.Annotations[OvnNodeDontSNATSubnets] + newVal, newOk := newNode.Annotations[OvnNodeDontSNATSubnets] + + if oldOk != newOk { + return true + } + + if oldOk && newOk && oldVal != newVal { + return true + } + + return false +} + +// NodeDontSNATSubnetAnnotationExist returns true OvnNodeDontSNATSubnets annotation key exists in node annotation +func NodeDontSNATSubnetAnnotationExist(node *corev1.Node) bool { + _, ok := node.Annotations[OvnNodeDontSNATSubnets] + return ok +} + +func parseNodeAnnotationList(node *corev1.Node, annotationKey string) ([]string, error) { + annotationValue, ok := node.Annotations[annotationKey] if !ok { - return nil, newAnnotationNotSetError("%s annotation not found for node %q", OVNNodeHostCIDRs, node.Name) + return []string{}, nil } var cfg []string - if err := json.Unmarshal([]byte(addrAnnotation), &cfg); err != nil { - return nil, fmt.Errorf("failed to unmarshal host cidrs annotation %s for node %q: %v", - addrAnnotation, node.Name, err) + if err := json.Unmarshal([]byte(annotationValue), &cfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal %s annotation %s for node %q: %v", + annotationKey, annotationValue, node.Name, err) } return cfg, nil } From 182ba9c2e12c58e530b13d54f73078687fb5fcb1 Mon Sep 17 00:00:00 2001 From: Yossi Boaron Date: Thu, 29 May 2025 20:51:17 +0300 Subject: [PATCH 022/278] Unit tests for node ingress snat exclude annotation Signed-off-by: Yossi Boaron --- .../default_node_network_controller_test.go | 323 ++++++++++++++++++ .../pkg/node/gateway_init_linux_test.go | 3 + .../pkg/node/managementport/port_linux.go | 4 +- .../node/managementport/port_linux_test.go | 2 +- .../pkg/util/node_annotations_unit_test.go | 171 ++++++++++ 5 files changed, 500 insertions(+), 3 deletions(-) diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index 368b333800..cb7087ff1b 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "strings" "sync" "time" @@ -21,6 +22,7 @@ import ( adminpolicybasedrouteclient "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/clientset/versioned/fake" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube/mocks" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" @@ -1238,4 +1240,325 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } }) + Describe("node ingress snat exclude subnets", func() { + + var ( + testNS ns.NetNS + nc *DefaultNodeNetworkController + app *cli.App + ) + + const ( + nodeName = "my-node" + ) + + BeforeEach(func() { + var err error + testNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + Expect(config.PrepareTestConfig()).To(Succeed()) + + app = cli.NewApp() + app.Name = "test" + app.Flags = config.Flags + }) + + AfterEach(func() { + util.ResetNetLinkOpMockInst() // other tests in this package rely directly on netlink (e.g. gateway_init_linux_test.go) + Expect(testNS.Close()).To(Succeed()) + }) + + Context("with a cluster in IPv4 mode", func() { + const ( + ethName string = "lo1337" + nodeIP string = "169.254.254.60" + ethCIDR string = nodeIP + "/24" + ) + var link netlink.Link + + BeforeEach(func() { + config.IPv4Mode = true + config.IPv6Mode = false + config.Gateway.Mode = config.GatewayModeShared + + // Note we must do this in default netNS because + // nc.WatchNodes() will spawn goroutines which we cannot lock to the testNS + ovntest.AddLink(ethName) + + var err error + link, err = netlink.LinkByName(ethName) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + + // Add an IP address + addr, err := netlink.ParseAddr(ethCIDR) + Expect(err).NotTo(HaveOccurred()) + addr.Scope = int(netlink.SCOPE_UNIVERSE) + err = netlink.AddrAdd(link, addr) + Expect(err).NotTo(HaveOccurred()) + + }) + + AfterEach(func() { + err := netlink.LinkDel(link) + Expect(err).NotTo(HaveOccurred()) + }) + + ovntest.OnSupportedPlatformsIt("empty annotation on startup", func() { + + app.Action = func(_ *cli.Context) error { + node := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Annotations: map[string]string{}, + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: nodeIP, + }, + }, + }, + } + + nft := nodenft.SetFakeNFTablesHelper() + + kubeFakeClient := fake.NewSimpleClientset(&corev1.NodeList{ + Items: []corev1.Node{node}, + }) + fakeClient := &util.OVNNodeClientset{ + KubeClient: kubeFakeClient, + AdminPolicyRouteClient: adminpolicybasedrouteclient.NewSimpleClientset(), + NetworkAttchDefClient: nadfake.NewSimpleClientset(), + } + + stop := make(chan struct{}) + wf, err := factory.NewNodeWatchFactory(fakeClient, nodeName) + Expect(err).NotTo(HaveOccurred()) + wg := &sync.WaitGroup{} + defer func() { + close(stop) + wg.Wait() + wf.Shutdown() + }() + + err = wf.Start() + Expect(err).NotTo(HaveOccurred()) + routeManager := routemanager.NewController() + cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) + nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) + nc.initRetryFrameworkForNode() + err = setupPMTUDNFTSets() + Expect(err).NotTo(HaveOccurred()) + err = setupPMTUDNFTChain() + Expect(err).NotTo(HaveOccurred()) + defaultNetConfig := &bridgeUDNConfiguration{ + ofPortPatch: "patch-breth0_ov", + } + nc.Gateway = &gateway{ + openflowManager: &openflowManager{ + flowCache: map[string][]string{}, + defaultBridge: &bridgeConfiguration{ + netConfig: map[string]*bridgeUDNConfiguration{ + types.DefaultNetworkName: defaultNetConfig, + }, + }, + }, + } + + err = managementport.SetupManagementPortNFTSets() + Expect(err).NotTo(HaveOccurred()) + + // must run route manager manually which is usually started with nc.Start() + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + nc.routeManager.Run(stop, 10*time.Second) + Expect(err).NotTo(HaveOccurred()) + }() + By("no nftables elements should present at startup") + + err = nc.WatchNodes() + Expect(err).NotTo(HaveOccurred()) + Expect(nft.Dump()).NotTo(ContainSubstring("add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.168.1.0/24 }")) + Expect(nft.Dump()).NotTo(ContainSubstring("add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }")) + + By("adding subnets to node annotation should update nftables elements") + node.Annotations[util.OvnNodeDontSNATSubnets] = `["192.167.1.0/24"]` + + _, err = kubeFakeClient.CoreV1().Nodes().Update(context.TODO(), &node, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.168.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + }).WithTimeout(2 * time.Second).Should(BeTrue()) + + By("adding extra subnets to node annotation should update nftables elements") + + node.Annotations[util.OvnNodeDontSNATSubnets] = `["192.167.1.0/24","fd00::/64","192.169.1.0/24","fd11::/64"]` + + _, err = kubeFakeClient.CoreV1().Nodes().Update(context.TODO(), &node, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.169.1.0/24 }") && + strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + }).WithTimeout(2 * time.Second).Should(BeTrue()) + + By("deleting node should remove nftables elements") + err = kubeFakeClient.CoreV1().Nodes().Delete(context.TODO(), nodeName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.169.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + + }).WithTimeout(2 * time.Second).Should(BeTrue()) + return nil + } + + err := app.Run([]string{app.Name}) + Expect(err).NotTo(HaveOccurred()) + }) + + ovntest.OnSupportedPlatformsIt("non-empty annotation on startup", func() { + + app.Action = func(_ *cli.Context) error { + node := corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Annotations: map[string]string{ + util.OvnNodeDontSNATSubnets: `["192.168.1.0/24","fd00::/64"]`, + }, + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: nodeIP, + }, + }, + }, + } + + nft := nodenft.SetFakeNFTablesHelper() + + kubeFakeClient := fake.NewSimpleClientset(&corev1.NodeList{ + Items: []corev1.Node{node}, + }) + fakeClient := &util.OVNNodeClientset{ + KubeClient: kubeFakeClient, + AdminPolicyRouteClient: adminpolicybasedrouteclient.NewSimpleClientset(), + NetworkAttchDefClient: nadfake.NewSimpleClientset(), + } + + stop := make(chan struct{}) + wf, err := factory.NewNodeWatchFactory(fakeClient, nodeName) + Expect(err).NotTo(HaveOccurred()) + wg := &sync.WaitGroup{} + defer func() { + close(stop) + wg.Wait() + wf.Shutdown() + }() + + err = wf.Start() + Expect(err).NotTo(HaveOccurred()) + routeManager := routemanager.NewController() + cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) + nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) + nc.initRetryFrameworkForNode() + err = setupPMTUDNFTSets() + Expect(err).NotTo(HaveOccurred()) + err = setupPMTUDNFTChain() + Expect(err).NotTo(HaveOccurred()) + defaultNetConfig := &bridgeUDNConfiguration{ + ofPortPatch: "patch-breth0_ov", + } + nc.Gateway = &gateway{ + openflowManager: &openflowManager{ + flowCache: map[string][]string{}, + defaultBridge: &bridgeConfiguration{ + netConfig: map[string]*bridgeUDNConfiguration{ + types.DefaultNetworkName: defaultNetConfig, + }, + }, + }, + } + + err = managementport.SetupManagementPortNFTSets() + Expect(err).NotTo(HaveOccurred()) + + // must run route manager manually which is usually started with nc.Start() + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + nc.routeManager.Run(stop, 10*time.Second) + Expect(err).NotTo(HaveOccurred()) + }() + By("expected nftables elements should present at startup") + + err = nc.WatchNodes() + Expect(err).NotTo(HaveOccurred()) + Expect(nft.Dump()).To(ContainSubstring("add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.168.1.0/24 }")) + Expect(nft.Dump()).To(ContainSubstring("add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }")) + + By("editing subnets on node annotation should update nftables elements") + node.Annotations[util.OvnNodeDontSNATSubnets] = `["192.167.1.0/24"]` + + _, err = kubeFakeClient.CoreV1().Nodes().Update(context.TODO(), &node, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.168.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + }).WithTimeout(2 * time.Second).Should(BeTrue()) + + By("adding extra subnets to node annotation should update nftables elements") + + node.Annotations[util.OvnNodeDontSNATSubnets] = `["192.167.1.0/24","fd00::/64","192.169.1.0/24","fd11::/64"]` + + _, err = kubeFakeClient.CoreV1().Nodes().Update(context.TODO(), &node, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.169.1.0/24 }") && + strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + }).WithTimeout(2 * time.Second).Should(BeTrue()) + + By("deleting node should remove nftables elements") + err = kubeFakeClient.CoreV1().Nodes().Delete(context.TODO(), nodeName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + cleanDump := strings.ReplaceAll(nft.Dump(), "\r", "") + return !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.167.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { 192.169.1.0/24 }") && + !strings.Contains(cleanDump, "add element inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { fd00::/64 }") + + }).WithTimeout(2 * time.Second).Should(BeTrue()) + return nil + } + + err := app.Run([]string{app.Name}) + Expect(err).NotTo(HaveOccurred()) + }) + + }) + }) }) diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 47162a98d0..e9f248c419 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -56,11 +56,14 @@ add table inet ovn-kubernetes add set inet ovn-kubernetes mgmtport-no-snat-nodeports { type inet_proto . inet_service ; comment "NodePorts not subject to management port SNAT" ; } add set inet ovn-kubernetes mgmtport-no-snat-services-v4 { type ipv4_addr . inet_proto . inet_service ; comment "eTP:Local short-circuit not subject to management port SNAT (IPv4)" ; } add set inet ovn-kubernetes mgmtport-no-snat-services-v6 { type ipv6_addr . inet_proto . inet_service ; comment "eTP:Local short-circuit not subject to management port SNAT (IPv6)" ; } +add set inet ovn-kubernetes mgmtport-no-snat-subnets-v4 { type ipv4_addr ; flags interval ; comment "subnets not subject to management port SNAT (IPv4)" ; } +add set inet ovn-kubernetes mgmtport-no-snat-subnets-v6 { type ipv6_addr ; flags interval ; comment "subnets not subject to management port SNAT (IPv6)" ; } add chain inet ovn-kubernetes mgmtport-snat { type nat hook postrouting priority 100 ; comment "OVN SNAT to Management Port" ; } add rule inet ovn-kubernetes mgmtport-snat oifname != %s return add rule inet ovn-kubernetes mgmtport-snat meta nfproto ipv4 ip saddr 10.1.1.2 counter return add rule inet ovn-kubernetes mgmtport-snat meta l4proto . th dport @mgmtport-no-snat-nodeports counter return add rule inet ovn-kubernetes mgmtport-snat ip daddr . meta l4proto . th dport @mgmtport-no-snat-services-v4 counter return +add rule inet ovn-kubernetes mgmtport-snat ip saddr @mgmtport-no-snat-subnets-v4 counter return add rule inet ovn-kubernetes mgmtport-snat counter snat ip to 10.1.1.2 ` diff --git a/go-controller/pkg/node/managementport/port_linux.go b/go-controller/pkg/node/managementport/port_linux.go index 480c6f2789..f6ea8676dd 100644 --- a/go-controller/pkg/node/managementport/port_linux.go +++ b/go-controller/pkg/node/managementport/port_linux.go @@ -101,7 +101,7 @@ func NewManagementPortController( } // setup NFT sets early as gateway initialization depends on it - err = setupManagementPortNFTSets() + err = SetupManagementPortNFTSets() if err != nil { return nil, err } @@ -299,7 +299,7 @@ func setupManagementPortConfig(link netlink.Link, cfg *managementPortConfig, rou // setupManagementPortNFTSets sets up the NFT sets that the management port SNAR // rules rely on. These sets are written to by other componets so they are setup // independantly and as early as possible. -func setupManagementPortNFTSets() error { +func SetupManagementPortNFTSets() error { nft, err := nodenft.GetNFTablesHelper() if err != nil { return err diff --git a/go-controller/pkg/node/managementport/port_linux_test.go b/go-controller/pkg/node/managementport/port_linux_test.go index d6d99d7577..13d7641c11 100644 --- a/go-controller/pkg/node/managementport/port_linux_test.go +++ b/go-controller/pkg/node/managementport/port_linux_test.go @@ -783,7 +783,7 @@ var _ = Describe("Management Port tests", func() { ipv4: &fakeMgmtPortIPFamilyConfig, netInfo: netInfo, } - err := setupManagementPortNFTSets() + err := SetupManagementPortNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupManagementPortNFTChain(types.K8sMgmtIntfName, &fakeMgmtPortConfig) Expect(err).NotTo(HaveOccurred()) diff --git a/go-controller/pkg/util/node_annotations_unit_test.go b/go-controller/pkg/util/node_annotations_unit_test.go index 463a83f31b..f987684e82 100644 --- a/go-controller/pkg/util/node_annotations_unit_test.go +++ b/go-controller/pkg/util/node_annotations_unit_test.go @@ -829,3 +829,174 @@ func TestParseUDNLayer2NodeGRLRPTunnelIDs(t *testing.T) { }) } } + +func TestNodeDontSNATSubnetAnnotationChanged(t *testing.T) { + tests := []struct { + desc string + oldNode *corev1.Node + newNode *corev1.Node + result bool + }{ + { + desc: "annotation added", + oldNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + }, + newNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24"]`, + }, + }, + }, + result: true, + }, + { + desc: "annotation removed", + oldNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24"]`, + }, + }, + }, + newNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + }, + result: true, + }, + { + desc: "annotation value changed", + oldNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24"]`, + }, + }, + }, + newNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["10.0.0.0/16"]`, + }, + }, + }, + result: true, + }, + { + desc: "false: annotation unchanged", + oldNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24"]`, + }, + }, + }, + newNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24"]`, + }, + }, + }, + result: false, + }, + { + desc: "annotation absent in both", + oldNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + }, + newNode: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + }, + result: false, + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) { + result := NodeDontSNATSubnetAnnotationChanged(tc.oldNode, tc.newNode) + assert.Equal(t, tc.result, result) + }) + } +} + +func TestParseNodeDontSNATSubnetsList(t *testing.T) { + tests := []struct { + desc string + node *corev1.Node + expected []string + expectError bool + }{ + { + desc: "no annotation present", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node", + Annotations: map[string]string{}, + }, + }, + expected: []string{}, + expectError: false, + }, + { + desc: "valid annotation list with IPv4 and IPv6 CIDRs", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `["192.168.1.0/24", "fd00::/64", "10.0.0.0/16"]`, + }, + }, + }, + expected: []string{"192.168.1.0/24", "fd00::/64", "10.0.0.0/16"}, + expectError: false, + }, + { + desc: "invalid annotation value (not JSON)", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node3", + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `not-a-json`, + }, + }, + }, + expected: nil, + expectError: true, + }, + { + desc: "empty JSON array annotation", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node4", + Annotations: map[string]string{ + OvnNodeDontSNATSubnets: `[]`, + }, + }, + }, + expected: []string{}, + expectError: false, + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) { + result, err := ParseNodeDontSNATSubnetsList(tc.node) + if tc.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + } + }) + } +} From cb32656996ea7b4718b0985cbc53c4d9ef74a10f Mon Sep 17 00:00:00 2001 From: thisisobate Date: Tue, 10 Jun 2025 14:28:08 +0100 Subject: [PATCH 023/278] chore: update footer with new LF trademark disclaimer Signed-off-by: thisisobate --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index e21134af5a..2f9d9cd495 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,7 +7,7 @@ extra_css: - stylesheets/extra.css site_dir: site docs_dir: docs -copyright: The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see Trademark Usage. +copyright: Copyright © OVN-Kubernetes a Series of LF Projects, LLC. For website terms of use, trademark policy and other project policies please see LF Projects Policies. theme: name: material icon: From 188309de2876c686fb9c5e19e8efd01af3226873 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 6 Jun 2025 17:27:34 -0400 Subject: [PATCH 024/278] Perf optimization: Stop every node event from triggering EIP Node update Everytime the node updates it is triggering addEgressNode, which does a route add operation libovsdb txn for default network and every UDN, initiated from the default controller egress node logic. Only runs when needed now. Signed-off-by: Tim Rozet --- .../pkg/ovn/default_network_controller.go | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index f6b3727c55..d6a0231ea6 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -131,6 +131,7 @@ type DefaultNetworkController struct { syncZoneICFailed sync.Map syncHostNetAddrSetFailed sync.Map syncEIPNodeRerouteFailed sync.Map + syncEIPNodeFailed sync.Map // variable to determine if all pods present on the node during startup have been processed // updated atomically @@ -843,8 +844,10 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from h.oc.eIPC.nodeZoneState.UnlockKey(node.Name) shouldSyncReroute := true + shouldSyncEIPNode := true if fromRetryLoop { _, shouldSyncReroute = h.oc.syncEIPNodeRerouteFailed.Load(node.Name) + _, shouldSyncEIPNode = h.oc.syncEIPNodeFailed.Load(node.Name) } if shouldSyncReroute { @@ -862,10 +865,19 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from h.oc.syncEIPNodeRerouteFailed.Store(node.Name, true) return err } + h.oc.syncEIPNodeRerouteFailed.Delete(node.Name) } - // Add routing specific to Egress IP NOTE: GARP configuration that - // Egress IP depends on is added from the gateway reconciliation logic - return h.oc.eIPC.addEgressNode(node) + if shouldSyncEIPNode { + // Add routing specific to Egress IP NOTE: GARP configuration that + // Egress IP depends on is added from the gateway reconciliation logic + err := h.oc.eIPC.addEgressNode(node) + if err != nil { + h.oc.syncEIPNodeFailed.Store(node.Name, true) + return err + } + h.oc.syncEIPNodeFailed.Delete(node.Name) + } + return nil case factory.NamespaceType: ns, ok := obj.(*corev1.Namespace) @@ -1038,24 +1050,36 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int h.oc.eIPC.nodeZoneState.Store(newNode.Name, h.oc.isLocalZoneNode(newNode)) h.oc.eIPC.nodeZoneState.UnlockKey(newNode.Name) - _, failed := h.oc.syncEIPNodeRerouteFailed.Load(newNode.Name) + _, syncEIPNodeRerouteFailed := h.oc.syncEIPNodeRerouteFailed.Load(newNode.Name) // node moved from remote -> local or previously failed reroute config - if (!h.oc.isLocalZoneNode(oldNode) || failed) && h.oc.isLocalZoneNode(newNode) { + if (!h.oc.isLocalZoneNode(oldNode) || syncEIPNodeRerouteFailed) && h.oc.isLocalZoneNode(newNode) { if err := h.oc.eIPC.ensureDefaultNoRerouteQoSRules(newNode.Name); err != nil { return err } } // update the nodeIP in the default-reRoute (102 priority) destination address-set - if failed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) { + if syncEIPNodeRerouteFailed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) { klog.Infof("Egress IP detected IP address change for node %s. Updating no re-route policies", newNode.Name) err := h.oc.eIPC.ensureDefaultNoRerouteNodePolicies() if err != nil { + h.oc.syncEIPNodeRerouteFailed.Store(newNode.Name, true) return err } + h.oc.syncEIPNodeRerouteFailed.Delete(newNode.Name) } - h.oc.syncEIPNodeRerouteFailed.Delete(newNode.Name) - return h.oc.eIPC.addEgressNode(newNode) + + _, syncEIPNodeFailed := h.oc.syncEIPNodeFailed.Load(newNode.Name) + // update only if the GR join IP changed for default network + if syncEIPNodeFailed || joinCIDRChanged(oldNode, newNode, h.oc.GetNetworkName()) { + err := h.oc.eIPC.addEgressNode(newNode) + if err != nil { + h.oc.syncEIPNodeFailed.Store(newNode.Name, true) + return err + } + h.oc.syncEIPNodeFailed.Delete(newNode.Name) + } + return nil case factory.NamespaceType: oldNs, newNs := oldObj.(*corev1.Namespace), newObj.(*corev1.Namespace) @@ -1118,6 +1142,7 @@ func (h *defaultNetworkControllerEventHandler) DeleteResource(obj, cachedObj int h.oc.eIPC.nodeZoneState.Delete(node.Name) h.oc.eIPC.nodeZoneState.UnlockKey(node.Name) h.oc.syncEIPNodeRerouteFailed.Delete(node.Name) + h.oc.syncEIPNodeFailed.Delete(node.Name) return nil case factory.NamespaceType: From bf5b8d41d2d137021c7703df3f9bce839d55f735 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 6 Jun 2025 17:34:07 -0400 Subject: [PATCH 025/278] Stop calling CreateDefaultRouteToExternal for UDNs by default This is unnecessary because there is another UDN path that will call this code: secondary_layer2/3_controller -> addUpdateLocalNodeEvent -> ensureRouterPoliciesForNetwork -> CreateDefaultRouteToExternal Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/egressip.go | 24 +-- go-controller/pkg/ovn/egressip_udn_l3_test.go | 190 +++++++++++++++++- .../secondary_layer3_network_controller.go | 2 - 3 files changed, 187 insertions(+), 29 deletions(-) diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index 5ca127140b..924008a45a 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -2050,28 +2050,10 @@ func (e *EgressIPController) addEgressNode(node *corev1.Node) error { // NOTE3: When the node gets deleted we do not remove this route intentionally because // on IC if the node is gone, then the ovn_cluster_router is also gone along with all // the routes on it. - processNetworkFn := func(ni util.NetInfo) error { - if ni.TopologyType() == types.Layer2Topology || len(ni.Subnets()) == 0 { - return nil - } - if err := libovsdbutil.CreateDefaultRouteToExternal(e.nbClient, ni.GetNetworkScopedClusterRouterName(), - ni.GetNetworkScopedGWRouterName(node.Name), ni.Subnets()); err != nil { - return fmt.Errorf("failed to create route to external for network %s: %v", ni.GetNetworkName(), err) - } - return nil - } ni := e.networkManager.GetNetwork(types.DefaultNetworkName) - if ni == nil { - return fmt.Errorf("failed to get default network from NAD controller") - } - if err := processNetworkFn(ni); err != nil { - return fmt.Errorf("failed to process default network: %v", err) - } - if !isEgressIPForUDNSupported() { - return nil - } - if err := e.networkManager.DoWithLock(processNetworkFn); err != nil { - return fmt.Errorf("failed to process all user defined networks route to external: %v", err) + if err := libovsdbutil.CreateDefaultRouteToExternal(e.nbClient, ni.GetNetworkScopedClusterRouterName(), + ni.GetNetworkScopedGWRouterName(node.Name), ni.Subnets()); err != nil { + return fmt.Errorf("failed to create route to external for network %s: %v", ni.GetNetworkName(), err) } } } diff --git a/go-controller/pkg/ovn/egressip_udn_l3_test.go b/go-controller/pkg/ovn/egressip_udn_l3_test.go index 5dae356208..1a5497d599 100644 --- a/go-controller/pkg/ovn/egressip_udn_l3_test.go +++ b/go-controller/pkg/ovn/egressip_udn_l3_test.go @@ -17,6 +17,7 @@ import ( k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" @@ -98,6 +99,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol config.OVNKubernetesFeature.EnableMultiNetwork = true config.Gateway.Mode = config.GatewayModeShared config.OVNKubernetesFeature.EgressIPNodeHealthCheckPort = 1234 + config.Gateway.V4MasqueradeSubnet = dummyMasqueradeSubnet().String() app = cli.NewApp() app.Name = "test" @@ -1195,9 +1197,11 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol // Add pod IPs to UDN cache iUDN, nUDN, _ := net.ParseCIDR(v4Pod1IPNode1Net1 + "/23") nUDN.IP = iUDN + secConInfo.bnc.zone = node1.Name secConInfo.bnc.logicalPortCache.add(&egressPodUDNLocal, "", util.GetNADName(nad.Namespace, nad.Name), "", nil, []*net.IPNet{nUDN}) _, err = fakeOvn.fakeClient.EgressIPClient.K8sV1().EgressIPs().Create(context.TODO(), &eIP, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(secConInfo.bnc.WatchNodes()).To(gomega.Succeed()) egressSVCServedPodsASv4, _ := buildEgressServiceAddressSets(nil) egressIPServedPodsASCDNv4, _ := buildEgressIPServedPodsAddressSets([]string{podV4IP}, ovntypes.DefaultNetworkName, DefaultNetworkControllerName) egressNodeIPsASv4, _ := buildEgressIPNodeAddressSets([]string{node1IPv4, node2IPv4}) @@ -1325,6 +1329,19 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + networkName1_ + node1.Name, Networks: []string{nodeLogicalRouterIfAddrV4}, }, + &nbdb.NAT{ + UUID: networkName1_ + node1Name + "-masqueradeNAT-UUID", + ExternalIDs: map[string]string{ + "k8s.ovn.org/topology": "layer3", + "k8s.ovn.org/network": networkName1, + }, + ExternalIP: "169.254.169.14", + LogicalIP: node1UDNSubnet.String(), + LogicalPort: ptr.To("rtos-" + networkName1_ + node1Name), + Match: "eth.dst == 0a:58:14:80:00:02", + Type: nbdb.NATTypeSNAT, + Options: map[string]string{"stateless": "false"}, + }, &nbdb.LogicalRouter{ Name: netInfo.GetNetworkScopedClusterRouterName(), UUID: netInfo.GetNetworkScopedClusterRouterName() + "-UUID", @@ -1333,6 +1350,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()), getReRoutePolicyUUID(eipNamespace2, podName2, IPFamilyValueV4, netInfo.GetNetworkName())}, StaticRoutes: []string{fmt.Sprintf("%s-reroute-static-route-UUID", netInfo.GetNetworkName())}, + Nat: []string{networkName1_ + node1Name + "-masqueradeNAT-UUID"}, }, &nbdb.LogicalRouter{ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", @@ -1345,14 +1363,57 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol &nbdb.LogicalSwitchPort{ UUID: "k8s-" + networkName1_ + node1Name + "-UUID", Name: "k8s-" + networkName1_ + node1Name, - Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + Addresses: []string{"0a:58:14:80:00:02 " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "stor-" + networkName1_ + node1Name + "-UUID", + Name: "stor-" + networkName1_ + node1Name, + Addresses: []string{"router"}, + Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Type: "router", + }, + &nbdb.ACL{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID", + Direction: nbdb.ACLDirectionToLport, + Action: nbdb.ACLActionAllowRelated, + ExternalIDs: map[string]string{ + "k8s.ovn.org/name": networkName1_ + node1Name, + "ip": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "k8s.ovn.org/id": fmt.Sprintf("%s-network-controller:NetpolNode:%s:%s", networkName1, networkName1_+node1Name, util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + "k8s.ovn.org/owner-controller": networkName1 + "-network-controller", + "k8s.ovn.org/owner-type": "NetpolNode", + }, + Match: fmt.Sprintf("ip4.src==%s", util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + Meter: ptr.To(ovntypes.OvnACLLoggingMeter), + Priority: ovntypes.PrimaryUDNAllowPriority, + Tier: ovntypes.DefaultACLTier, }, &nbdb.LogicalSwitch{ UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-UUID", Name: netInfo.GetNetworkScopedSwitchName(node1.Name), - Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID", "stor-" + networkName1_ + node1Name + "-UUID"}, ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer3Topology}, QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())}, + OtherConfig: map[string]string{ + "exclude_ips": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "subnet": node1UDNSubnet.String(), + }, + ACLs: []string{netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID"}, + }, + &nbdb.LogicalSwitch{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "TRANSIT-UUID", + Name: networkName1_ + ovntypes.TransitSwitch, + ExternalIDs: map[string]string{ + ovntypes.NetworkExternalID: netInfo.GetNetworkName(), + ovntypes.TopologyExternalID: ovntypes.Layer3Topology, + ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary}, + OtherConfig: map[string]string{ + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + "requested-tnl-key": "16711685", + }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName), @@ -1457,6 +1518,19 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + networkName1_ + node1.Name, Networks: []string{nodeLogicalRouterIfAddrV4}, }, + &nbdb.NAT{ + UUID: networkName1_ + node1Name + "-masqueradeNAT-UUID", + ExternalIDs: map[string]string{ + "k8s.ovn.org/topology": "layer3", + "k8s.ovn.org/network": networkName1, + }, + ExternalIP: "169.254.169.14", + LogicalIP: node1UDNSubnet.String(), + LogicalPort: ptr.To("rtos-" + networkName1_ + node1Name), + Match: "eth.dst == 0a:58:14:80:00:02", + Type: nbdb.NATTypeSNAT, + Options: map[string]string{"stateless": "false"}, + }, &nbdb.LogicalRouter{ Name: netInfo.GetNetworkScopedClusterRouterName(), UUID: netInfo.GetNetworkScopedClusterRouterName() + "-UUID", @@ -1465,6 +1539,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()), }, StaticRoutes: []string{fmt.Sprintf("%s-reroute-static-route-UUID", netInfo.GetNetworkName())}, + Nat: []string{networkName1_ + node1Name + "-masqueradeNAT-UUID"}, }, &nbdb.LogicalRouter{ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", @@ -1475,14 +1550,57 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol &nbdb.LogicalSwitchPort{ UUID: "k8s-" + networkName1_ + node1Name + "-UUID", Name: "k8s-" + networkName1_ + node1Name, - Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + Addresses: []string{"0a:58:14:80:00:02 " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "stor-" + networkName1_ + node1Name + "-UUID", + Name: "stor-" + networkName1_ + node1Name, + Addresses: []string{"router"}, + Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Type: "router", + }, + &nbdb.ACL{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID", + Direction: nbdb.ACLDirectionToLport, + Action: nbdb.ACLActionAllowRelated, + ExternalIDs: map[string]string{ + "k8s.ovn.org/name": networkName1_ + node1Name, + "ip": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "k8s.ovn.org/id": fmt.Sprintf("%s-network-controller:NetpolNode:%s:%s", networkName1, networkName1_+node1Name, util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + "k8s.ovn.org/owner-controller": networkName1 + "-network-controller", + "k8s.ovn.org/owner-type": "NetpolNode", + }, + Match: fmt.Sprintf("ip4.src==%s", util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + Meter: ptr.To(ovntypes.OvnACLLoggingMeter), + Priority: ovntypes.PrimaryUDNAllowPriority, + Tier: ovntypes.DefaultACLTier, }, &nbdb.LogicalSwitch{ UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-UUID", Name: netInfo.GetNetworkScopedSwitchName(node1.Name), - Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID", "stor-" + networkName1_ + node1Name + "-UUID"}, ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer3Topology}, QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())}, + OtherConfig: map[string]string{ + "exclude_ips": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "subnet": node1UDNSubnet.String(), + }, + ACLs: []string{netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID"}, + }, + &nbdb.LogicalSwitch{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "TRANSIT-UUID", + Name: networkName1_ + ovntypes.TransitSwitch, + ExternalIDs: map[string]string{ + ovntypes.NetworkExternalID: netInfo.GetNetworkName(), + ovntypes.TopologyExternalID: ovntypes.Layer3Topology, + ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary}, + OtherConfig: map[string]string{ + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + "requested-tnl-key": "16711685", + }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName), @@ -2415,6 +2533,8 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol gomega.Expect(err).NotTo(gomega.HaveOccurred()) secConInfo, ok := fakeOvn.secondaryControllers[networkName1] gomega.Expect(ok).To(gomega.BeTrue()) + secConInfo.bnc.zone = node1.Name + gomega.Expect(secConInfo.bnc.WatchNodes()).To(gomega.Succeed()) // Add pod IPs to UDN cache iUDN, nUDN, _ := net.ParseCIDR(v4Pod1IPNode1Net1 + "/23") nUDN.IP = iUDN @@ -2553,6 +2673,19 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + networkName1_ + node1.Name, Networks: []string{nodeLogicalRouterIfAddrV4}, }, + &nbdb.NAT{ + UUID: networkName1_ + node1Name + "-masqueradeNAT-UUID", + ExternalIDs: map[string]string{ + "k8s.ovn.org/topology": "layer3", + "k8s.ovn.org/network": networkName1, + }, + ExternalIP: "169.254.169.14", + LogicalIP: node1UDNSubnet.String(), + LogicalPort: ptr.To("rtos-" + networkName1_ + node1Name), + Match: "eth.dst == 0a:58:14:80:00:02", + Type: nbdb.NATTypeSNAT, + Options: map[string]string{"stateless": "false"}, + }, &nbdb.LogicalRouter{ Name: netInfo.GetNetworkScopedClusterRouterName(), UUID: netInfo.GetNetworkScopedClusterRouterName() + "-UUID", @@ -2561,6 +2694,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()), getReRoutePolicyUUID(eipNamespace2, podName2, IPFamilyValueV4, netInfo.GetNetworkName())}, StaticRoutes: []string{fmt.Sprintf("%s-reroute-static-route-UUID", netInfo.GetNetworkName())}, + Nat: []string{networkName1_ + node1Name + "-masqueradeNAT-UUID"}, }, &nbdb.LogicalRouter{ UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", @@ -2573,14 +2707,58 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol &nbdb.LogicalSwitchPort{ UUID: "k8s-" + networkName1_ + node1Name + "-UUID", Name: "k8s-" + networkName1_ + node1Name, - Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + Addresses: []string{"0a:58:14:80:00:02 " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "stor-" + networkName1_ + node1Name + "-UUID", + Name: "stor-" + networkName1_ + node1Name, + Addresses: []string{"router"}, + Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Type: "router", + }, + &nbdb.ACL{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID", + Direction: nbdb.ACLDirectionToLport, + Action: nbdb.ACLActionAllowRelated, + ExternalIDs: map[string]string{ + "k8s.ovn.org/name": networkName1_ + node1Name, + "ip": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "k8s.ovn.org/id": fmt.Sprintf("%s-network-controller:NetpolNode:%s:%s", networkName1, networkName1_+node1Name, util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + "k8s.ovn.org/owner-controller": networkName1 + "-network-controller", + "k8s.ovn.org/owner-type": "NetpolNode", + }, + Match: fmt.Sprintf("ip4.src==%s", util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()), + Meter: ptr.To(ovntypes.OvnACLLoggingMeter), + Priority: ovntypes.PrimaryUDNAllowPriority, + Tier: ovntypes.DefaultACLTier, }, &nbdb.LogicalSwitch{ UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-UUID", Name: netInfo.GetNetworkScopedSwitchName(node1.Name), - Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID", "stor-" + networkName1_ + node1Name + "-UUID"}, ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer3Topology}, QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())}, + OtherConfig: map[string]string{ + "exclude_ips": util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String(), + "subnet": node1UDNSubnet.String(), + }, + ACLs: []string{netInfo.GetNetworkScopedSwitchName(node1.Name) + "-NetpolNode-UUID"}, + }, + &nbdb.LogicalSwitch{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "TRANSIT-UUID", + Name: networkName1_ + ovntypes.TransitSwitch, + ExternalIDs: map[string]string{ + ovntypes.NetworkExternalID: netInfo.GetNetworkName(), + ovntypes.TopologyExternalID: ovntypes.Layer3Topology, + ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary, + }, + OtherConfig: map[string]string{ + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + "requested-tnl-key": "16711685", + }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName), diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index cb9f82d08f..9005020934 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -107,7 +107,6 @@ func (h *secondaryLayer3NetworkControllerEventHandler) AddResource(obj interface if !ok { return fmt.Errorf("could not cast %T object to *kapi.Node", obj) } - if h.oc.isLocalZoneNode(node) { var nodeParams *nodeSyncs if fromRetryLoop { @@ -704,7 +703,6 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *corev1 var hostSubnets []*net.IPNet var errs []error var err error - _, _ = oc.localZoneNodes.LoadOrStore(node.Name, true) if noHostSubnet := util.NoHostSubnet(node); noHostSubnet { From a0083457163cfa9d882a1b601a81c58daa1941aa Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 6 Jun 2025 17:47:06 -0400 Subject: [PATCH 026/278] L2 and L3 UDN should reconfigure reroute policies when join IP changes Signed-off-by: Tim Rozet --- go-controller/pkg/ovn/secondary_layer2_network_controller.go | 3 ++- go-controller/pkg/ovn/secondary_layer3_network_controller.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index c11ca2a2ae..69662caa96 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -180,7 +180,8 @@ func (h *secondaryLayer2NetworkControllerEventHandler) UpdateResource(oldObj, ne hostCIDRsChanged(oldNode, newNode) || nodeGatewayMTUSupportChanged(oldNode, newNode) _, syncRerouteFailed := h.oc.syncEIPNodeRerouteFailed.Load(newNode.Name) - shouldSyncReroute := syncRerouteFailed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) + shouldSyncReroute := syncRerouteFailed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) || + joinCIDRChanged(oldNode, newNode, h.oc.GetNetworkName()) nodeSyncsParam = &nodeSyncs{ syncMgmtPort: shouldSyncMgmtPort, syncGw: shouldSyncGW, diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 9005020934..f8601a6b37 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -186,7 +186,8 @@ func (h *secondaryLayer3NetworkControllerEventHandler) UpdateResource(oldObj, ne hostCIDRsChanged(oldNode, newNode) || nodeGatewayMTUSupportChanged(oldNode, newNode) _, failed = h.oc.syncEIPNodeRerouteFailed.Load(newNode.Name) - syncReroute := failed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) + syncReroute := failed || util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) || + joinCIDRChanged(oldNode, newNode, h.oc.GetNetworkName()) nodeSyncsParam = &nodeSyncs{ syncNode: nodeSync, syncClusterRouterPort: clusterRtrSync, From 5308cbf6e439f53b7ace90a1fbe005fda4729349 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 6 Jun 2025 18:49:08 -0400 Subject: [PATCH 027/278] CreateDefaultRouteToExternal should use node GR IP annotations This function is called from many different threads. Relying on nbdb for the GR IP is not safe here, as the GR IP could be changing due to a k8s event, and the route will be wrongly configured with an old IP still in OVN NBDB. Signed-off-by: Tim Rozet --- go-controller/pkg/kubevirt/router.go | 11 +++++++++- go-controller/pkg/libovsdb/util/router.go | 7 +------ .../pkg/libovsdb/util/router_test.go | 5 ++++- .../egressservice/egressservice_zone.go | 2 +- .../egressservice/egressservice_zone_node.go | 8 +++++++- go-controller/pkg/ovn/egressip.go | 14 ++++++++++--- go-controller/pkg/ovn/egressip_test.go | 2 ++ go-controller/pkg/ovn/egressip_udn_l2_test.go | 8 ++++---- go-controller/pkg/ovn/egressip_udn_l3_test.go | 20 +++++++++++++++---- go-controller/pkg/ovn/egressservices_test.go | 1 + go-controller/pkg/ovn/kubevirt_test.go | 3 +++ go-controller/pkg/ovn/ovn.go | 2 +- .../secondary_layer2_network_controller.go | 2 +- .../secondary_layer3_network_controller.go | 2 +- 14 files changed, 63 insertions(+), 24 deletions(-) diff --git a/go-controller/pkg/kubevirt/router.go b/go-controller/pkg/kubevirt/router.go index f6354d50f9..06a6499e1f 100644 --- a/go-controller/pkg/kubevirt/router.go +++ b/go-controller/pkg/kubevirt/router.go @@ -95,7 +95,16 @@ func EnsureLocalZonePodAddressesToNodeRoute(watchFactory *factory.WatchFactory, if config.OVNKubernetesFeature.EnableInterconnect { // NOTE: EIP & ESVC use same route and if this is already present thanks to those features, // this will be a no-op - if err := libovsdbutil.CreateDefaultRouteToExternal(nbClient, types.OVNClusterRouter, types.GWRouterPrefix+pod.Spec.NodeName, clusterSubnets); err != nil { + node, err := watchFactory.GetNode(pod.Spec.NodeName) + if err != nil { + return fmt.Errorf("failed getting to list node %q for pod %s/%s: %w", pod.Spec.NodeName, pod.Namespace, pod.Name, err) + } + gatewayIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, types.DefaultNetworkName) + if err != nil { + return fmt.Errorf("failed to get default network gateway router join IPs for node %q: %w", node.Name, err) + } + if err := libovsdbutil.CreateDefaultRouteToExternal(nbClient, types.OVNClusterRouter, + types.GWRouterPrefix+pod.Spec.NodeName, clusterSubnets, gatewayIPs); err != nil { return err } } diff --git a/go-controller/pkg/libovsdb/util/router.go b/go-controller/pkg/libovsdb/util/router.go index 12d7db3b27..12ee755d28 100644 --- a/go-controller/pkg/libovsdb/util/router.go +++ b/go-controller/pkg/libovsdb/util/router.go @@ -13,7 +13,6 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -34,11 +33,7 @@ import ( // (TODO: FIXME): With this route, we are officially breaking support for IC with zones that have multiple-nodes // NOTE: This route is exactly the same as what is added by pod-live-migration feature and we keep the route exactly // same across the 3 features so that if the route already exists on the node, this is just a no-op -func CreateDefaultRouteToExternal(nbClient libovsdbclient.Client, clusterRouter, gwRouterName string, clusterSubnets []config.CIDRNetworkEntry) error { - gatewayIPs, err := GetLRPAddrs(nbClient, types.GWRouterToJoinSwitchPrefix+gwRouterName) - if err != nil { - return fmt.Errorf("attempt at finding node gateway router %s network information failed, err: %w", gwRouterName, err) - } +func CreateDefaultRouteToExternal(nbClient libovsdbclient.Client, clusterRouter, gwRouterName string, clusterSubnets []config.CIDRNetworkEntry, gatewayIPs []*net.IPNet) error { for _, clusterSubnet := range clusterSubnets { isClusterSubnetIPV6 := utilnet.IsIPv6String(clusterSubnet.CIDR.IP.String()) gatewayIP, err := util.MatchFirstIPNetFamily(isClusterSubnetIPV6, gatewayIPs) diff --git a/go-controller/pkg/libovsdb/util/router_test.go b/go-controller/pkg/libovsdb/util/router_test.go index e2047f57a8..6b0b325189 100644 --- a/go-controller/pkg/libovsdb/util/router_test.go +++ b/go-controller/pkg/libovsdb/util/router_test.go @@ -31,6 +31,9 @@ func TestCreateDefaultRouteToExternal(t *testing.T) { gwRouterPortName := types.GWRouterToJoinSwitchPrefix + gwRouterName gwRouterIPAddressV4 := "100.64.0.3" gwRouterIPAddressV6 := "fd98::3" + gwRouterIPAddressV4CIDR := fmt.Sprintf("%s/32", gwRouterIPAddressV4) + gwRouterIPAddressV6CIDR := fmt.Sprintf("%s/128", gwRouterIPAddressV6) + gatewayIPs := []*net.IPNet{ovntest.MustParseIPNet(gwRouterIPAddressV4CIDR), ovntest.MustParseIPNet(gwRouterIPAddressV6CIDR)} gwRouterPort := &nbdb.LogicalRouterPort{ UUID: gwRouterPortName + "-uuid", Name: gwRouterPortName, @@ -228,7 +231,7 @@ func TestCreateDefaultRouteToExternal(t *testing.T) { tc.preTestAction() } - if err = CreateDefaultRouteToExternal(nbClient, ovnClusterRouterName, gwRouterName, config.Default.ClusterSubnets); err != nil { + if err = CreateDefaultRouteToExternal(nbClient, ovnClusterRouterName, gwRouterName, config.Default.ClusterSubnets, gatewayIPs); err != nil { t.Fatal(fmt.Errorf("failed to run CreateDefaultRouteToExternal: %v", err)) } diff --git a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go index 5f19a1001b..23f9f8f665 100644 --- a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go +++ b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go @@ -50,7 +50,7 @@ type InitClusterEgressPoliciesFunc func(client libovsdbclient.Client, addressSet type EnsureNoRerouteNodePoliciesFunc func(client libovsdbclient.Client, addressSetFactory addressset.AddressSetFactory, networkName, controllerName, clusterRouter string, nodeLister corelisters.NodeLister, v4, v6 bool) error type DeleteLegacyDefaultNoRerouteNodePoliciesFunc func(nbClient libovsdbclient.Client, clusterRouter, nodeName string) error -type CreateDefaultRouteToExternalFunc func(nbClient libovsdbclient.Client, clusterRouter, gwRouterName string, clusterSubnets []config.CIDRNetworkEntry) error +type CreateDefaultRouteToExternalFunc func(nbClient libovsdbclient.Client, clusterRouter, gwRouterName string, clusterSubnets []config.CIDRNetworkEntry, gatewayIPs []*net.IPNet) error type Controller struct { // network information diff --git a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_node.go b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_node.go index b3a5f6c103..94db811bb2 100644 --- a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_node.go +++ b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_node.go @@ -151,9 +151,15 @@ func (c *Controller) syncNode(key string) error { return nil } + gatewayIPs, err := util.ParseNodeGatewayRouterJoinAddrs(n, types.DefaultNetworkName) + if err != nil { + return fmt.Errorf("failed to get default network gateway router join IPs for node %q: %w", n.Name, err) + } + // At this point the node exists and is ready if config.OVNKubernetesFeature.EnableInterconnect && c.zone != types.OvnDefaultZone && c.isNodeInLocalZone(n) { - if err := c.createDefaultRouteToExternalForIC(c.nbClient, c.GetNetworkScopedClusterRouterName(), c.GetNetworkScopedGWRouterName(nodeName), c.Subnets()); err != nil { + if err := c.createDefaultRouteToExternalForIC(c.nbClient, c.GetNetworkScopedClusterRouterName(), + c.GetNetworkScopedGWRouterName(nodeName), c.Subnets(), gatewayIPs); err != nil { return err } } diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index 924008a45a..e79b9b29c5 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -2051,8 +2051,12 @@ func (e *EgressIPController) addEgressNode(node *corev1.Node) error { // on IC if the node is gone, then the ovn_cluster_router is also gone along with all // the routes on it. ni := e.networkManager.GetNetwork(types.DefaultNetworkName) + gatewayIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, types.DefaultNetworkName) + if err != nil { + return fmt.Errorf("failed to get default network gateway router join IPs for node %q: %w", node.Name, err) + } if err := libovsdbutil.CreateDefaultRouteToExternal(e.nbClient, ni.GetNetworkScopedClusterRouterName(), - ni.GetNetworkScopedGWRouterName(node.Name), ni.Subnets()); err != nil { + ni.GetNetworkScopedGWRouterName(node.Name), ni.Subnets(), gatewayIPs); err != nil { return fmt.Errorf("failed to create route to external for network %s: %v", ni.GetNetworkName(), err) } } @@ -3121,7 +3125,7 @@ func createDefaultNoRerouteServicePolicies(nbClient libovsdbclient.Client, netwo return nil } -func (e *EgressIPController) ensureRouterPoliciesForNetwork(ni util.NetInfo) error { +func (e *EgressIPController) ensureRouterPoliciesForNetwork(ni util.NetInfo, node *corev1.Node) error { e.nodeUpdateMutex.Lock() defer e.nodeUpdateMutex.Unlock() subnetEntries := ni.Subnets() @@ -3146,8 +3150,12 @@ func (e *EgressIPController) ensureRouterPoliciesForNetwork(ni util.NetInfo) err return fmt.Errorf("failed to ensure no reroute node policies for network %s: %v", ni.GetNetworkName(), err) } if config.OVNKubernetesFeature.EnableInterconnect && ni.TopologyType() == types.Layer3Topology { + gatewayIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, ni.GetNetworkName()) + if err != nil { + return fmt.Errorf("failed to get %q network gateway router join IPs for node %q, err: %w", ni.GetNetworkName(), node.Name, err) + } if err := libovsdbutil.CreateDefaultRouteToExternal(e.nbClient, routerName, - ni.GetNetworkScopedGWRouterName(localNode), subnetEntries); err != nil { + ni.GetNetworkScopedGWRouterName(localNode), subnetEntries, gatewayIPs); err != nil { return fmt.Errorf("failed to create route to external for network %s: %v", ni.GetNetworkName(), err) } } diff --git a/go-controller/pkg/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go index 36c2a2e5f7..b0e5ad142a 100644 --- a/go-controller/pkg/ovn/egressip_test.go +++ b/go-controller/pkg/ovn/egressip_test.go @@ -5418,6 +5418,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" "k8s.ovn.org/node-transit-switch-port-ifaddr": "{\"ipv4\":\"100.88.0.2/16\"}", // used only for ic=true test "k8s.ovn.org/zone-name": node1Zone, // used only for ic=true test util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0]), } if node1Zone != "global" { annotations["k8s.ovn.org/remote-zone-migrated"] = node1Zone // used only for ic=true test @@ -5432,6 +5433,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" "k8s.ovn.org/node-transit-switch-port-ifaddr": "{\"ipv4\":\"100.88.0.3/16\"}", // used only for ic=true test "k8s.ovn.org/zone-name": node2Zone, // used only for ic=true test util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0]), } if node2Zone != "global" { annotations["k8s.ovn.org/remote-zone-migrated"] = node2Zone // used only for ic=true test diff --git a/go-controller/pkg/ovn/egressip_udn_l2_test.go b/go-controller/pkg/ovn/egressip_udn_l2_test.go index 1432743e53..23a930b2ef 100644 --- a/go-controller/pkg/ovn/egressip_udn_l2_test.go +++ b/go-controller/pkg/ovn/egressip_udn_l2_test.go @@ -304,7 +304,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol fakeOvn.controller.eIPC.nodeZoneState.Store(node2Name, false) fakeOvn.controller.eIPC.zone = node1.Name fakeOvn.controller.zone = node1.Name - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -670,7 +670,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol gomega.Expect(err).NotTo(gomega.HaveOccurred()) defer fakeOvn.networkManager.Stop() // simulate Start() of secondary network controller - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(secConInfo.bnc.GetNetInfo()) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(secConInfo.bnc.GetNetInfo(), &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(secConInfo.bnc.GetNetInfo(), node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1662,7 +1662,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol defer fakeOvn.networkManager.Stop() err = fakeOvn.controller.WatchEgressNodes() gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -2026,7 +2026,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol err = fakeOvn.networkManager.Start() gomega.Expect(err).NotTo(gomega.HaveOccurred()) defer fakeOvn.networkManager.Stop() - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) diff --git a/go-controller/pkg/ovn/egressip_udn_l3_test.go b/go-controller/pkg/ovn/egressip_udn_l3_test.go index 1a5497d599..dc01cc84c4 100644 --- a/go-controller/pkg/ovn/egressip_udn_l3_test.go +++ b/go-controller/pkg/ovn/egressip_udn_l3_test.go @@ -161,6 +161,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node1Tsp), "k8s.ovn.org/zone-name": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -172,6 +173,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node2Tsp), "k8s.ovn.org/zone-name": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) eIP := egressipv1.EgressIP{ @@ -297,7 +299,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol fakeOvn.controller.eIPC.nodeZoneState.Store(node2Name, false) fakeOvn.controller.eIPC.zone = node1.Name fakeOvn.controller.zone = node1.Name - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -537,6 +539,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node1Name, "k8s.ovn.org/remote-zone-migrated": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -549,6 +552,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node2Name, "k8s.ovn.org/remote-zone-migrated": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) twoNodeStatus := []egressipv1.EgressIPStatusItem{ @@ -670,7 +674,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol gomega.Expect(err).NotTo(gomega.HaveOccurred()) defer fakeOvn.networkManager.Stop() // simulate Start() of secondary network controller - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(secConInfo.bnc.GetNetInfo()) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(secConInfo.bnc.GetNetInfo(), &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(secConInfo.bnc.GetNetInfo(), node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1056,6 +1060,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node1Name, "k8s.ovn.org/remote-zone-migrated": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -1068,6 +1073,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node2Name, "k8s.ovn.org/remote-zone-migrated": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) twoNodeStatus := []egressipv1.EgressIPStatusItem{ @@ -1665,6 +1671,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node1Name, "k8s.ovn.org/remote-zone-migrated": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -1677,6 +1684,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node2Name, "k8s.ovn.org/remote-zone-migrated": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) twoNodeStatus := []egressipv1.EgressIPStatusItem{ @@ -1798,7 +1806,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol err = fakeOvn.networkManager.Start() gomega.Expect(err).NotTo(gomega.HaveOccurred()) defer fakeOvn.networkManager.Stop() - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -2033,6 +2041,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node1Name, "k8s.ovn.org/remote-zone-migrated": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -2045,6 +2054,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node2Name, "k8s.ovn.org/remote-zone-migrated": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) twoNodeStatus := []egressipv1.EgressIPStatusItem{ @@ -2170,7 +2180,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol err = fakeOvn.networkManager.Start() gomega.Expect(err).NotTo(gomega.HaveOccurred()) defer fakeOvn.networkManager.Stop() - err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo) + err = fakeOvn.eIPController.ensureRouterPoliciesForNetwork(netInfo, &node1) gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = fakeOvn.eIPController.ensureSwitchPoliciesForNode(netInfo, node1Name) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -2392,6 +2402,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node1Name, "k8s.ovn.org/remote-zone-migrated": node1Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, nodeLogicalRouterIPv4[0], networkName1, nodeLogicalRouterIPv4[0]), } labels := map[string]string{ "k8s.ovn.org/egress-assignable": "", @@ -2404,6 +2415,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol "k8s.ovn.org/zone-name": node2Name, "k8s.ovn.org/remote-zone-migrated": node2Name, util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s/16"}, "%s":{"ipv4":"%s/16"}}`, node2LogicalRouterIPv4[0], networkName1, node2LogicalRouterIPv4[0]), } node2 := getNodeObj(node2Name, node2Annotations, labels) twoNodeStatus := []egressipv1.EgressIPStatusItem{ diff --git a/go-controller/pkg/ovn/egressservices_test.go b/go-controller/pkg/ovn/egressservices_test.go index cf2b6cb3af..5b47022a9d 100644 --- a/go-controller/pkg/ovn/egressservices_test.go +++ b/go-controller/pkg/ovn/egressservices_test.go @@ -956,6 +956,7 @@ var _ = ginkgo.Describe("OVN Egress Service Operations", func() { config.IPv6Mode = true config.OVNKubernetesFeature.EnableInterconnect = interconnectEnabled node1 := nodeFor(node1Name, node1IPv4, node1IPv6, node1IPv4Subnet, node1IPv6Subnet, node1transitIPv4, node1transitIPv6) + node1.Annotations[util.OVNNodeGRLRPAddrs] = `{"default":{"ipv4":"100.64.0.2/16", "ipv6":"fef0::56/16"}}` node2 := nodeFor(node2Name, node2IPv4, node2IPv6, node2IPv4Subnet, node2IPv6Subnet, node2transitIPv4, node2transitIPv6) clusterRouter := &nbdb.LogicalRouter{ diff --git a/go-controller/pkg/ovn/kubevirt_test.go b/go-controller/pkg/ovn/kubevirt_test.go index 1a7dd5fcae..b7c80c6399 100644 --- a/go-controller/pkg/ovn/kubevirt_test.go +++ b/go-controller/pkg/ovn/kubevirt_test.go @@ -665,6 +665,7 @@ var _ = Describe("OVN Kubevirt Operations", func() { Annotations: map[string]string{ "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf(`{"ipv4": %q, "ipv6": %q}`, nodeByName[node1].transitSwitchPortIPv4, nodeByName[node1].transitSwitchPortIPv6), "k8s.ovn.org/node-subnets": fmt.Sprintf(`{"default":[%q,%q]}`, nodeByName[node1].subnetIPv4, nodeByName[node1].subnetIPv6), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s", "ipv6":"%s"}}`, nodeByName[node1].lrpNetworkIPv4, nodeByName[node1].lrpNetworkIPv6), }, }, }, @@ -674,6 +675,7 @@ var _ = Describe("OVN Kubevirt Operations", func() { Annotations: map[string]string{ "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf(`{"ipv4": %q, "ipv6": %q}`, nodeByName[node2].transitSwitchPortIPv4, nodeByName[node2].transitSwitchPortIPv6), "k8s.ovn.org/node-subnets": fmt.Sprintf(`{"default":[%q,%q]}`, nodeByName[node2].subnetIPv4, nodeByName[node2].subnetIPv6), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s", "ipv6":"%s"}}`, nodeByName[node2].lrpNetworkIPv4, nodeByName[node2].lrpNetworkIPv6), }, }, }, @@ -683,6 +685,7 @@ var _ = Describe("OVN Kubevirt Operations", func() { Annotations: map[string]string{ "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf(`{"ipv4": %q, "ipv6": %q}`, nodeByName[node3].transitSwitchPortIPv4, nodeByName[node3].transitSwitchPortIPv6), "k8s.ovn.org/node-subnets": fmt.Sprintf(`{"default":[%q,%q]}`, nodeByName[node3].subnetIPv4, nodeByName[node3].subnetIPv6), + util.OVNNodeGRLRPAddrs: fmt.Sprintf(`{"default":{"ipv4":"%s", "ipv6":"%s"}}`, nodeByName[node3].lrpNetworkIPv4, nodeByName[node3].lrpNetworkIPv6), }, }, }, diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 7a1aad8ed7..692c768d95 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -497,7 +497,7 @@ func (oc *DefaultNetworkController) InitEgressServiceZoneController() (*egresssv return nil } // used only when IC=true - createDefaultNodeRouteToExternal := func(_ libovsdbclient.Client, _, _ string, _ []config.CIDRNetworkEntry) error { + createDefaultNodeRouteToExternal := func(_ libovsdbclient.Client, _, _ string, _ []config.CIDRNetworkEntry, _ []*net.IPNet) error { return nil } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 69662caa96..fd15ab6684 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -634,7 +634,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 if config.OVNKubernetesFeature.EnableEgressIP && nSyncs.syncReroute { rerouteFailed := false - if err := oc.eIPController.ensureRouterPoliciesForNetwork(oc.GetNetInfo()); err != nil { + if err := oc.eIPController.ensureRouterPoliciesForNetwork(oc.GetNetInfo(), node); err != nil { errs = append(errs, fmt.Errorf("failed to ensure EgressIP router policies for network %s: %v", oc.GetNetworkName(), err)) rerouteFailed = true } diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index f8601a6b37..a6c2d500bd 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -820,7 +820,7 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *corev1 if config.OVNKubernetesFeature.EnableEgressIP && util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() && nSyncs.syncReroute { rerouteFailed := false - if err = oc.eIPController.ensureRouterPoliciesForNetwork(oc.GetNetInfo()); err != nil { + if err = oc.eIPController.ensureRouterPoliciesForNetwork(oc.GetNetInfo(), node); err != nil { errs = append(errs, fmt.Errorf("failed to ensure EgressIP router polices for network %s: %v", oc.GetNetworkName(), err)) rerouteFailed = true } From 304975a0f0af8be29c6089428eb82c5702e9c4f2 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 30 May 2025 17:38:04 -0700 Subject: [PATCH 028/278] Add node deletion unit testing case for zone_ic_handler Signed-off-by: Yun Zhou --- .../zone_interconnect/zone_ic_handler_test.go | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go index e2cbeb3c8b..8af1215714 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go @@ -89,6 +89,15 @@ func invokeICHandlerAddNodeFunction(zone string, icHandler *ZoneInterconnectHand return nil } +func invokeICHandlerDeleteNodeFunction(icHandler *ZoneInterconnectHandler, nodes ...*corev1.Node) error { + for _, node := range nodes { + err := icHandler.DeleteNode(node) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + + return nil +} + func checkInterconnectResources(zone string, netName string, nbClient libovsdbclient.Client, testNodesRouteInfo map[string]map[string]string, nodes ...*corev1.Node) error { localZoneNodes := []*corev1.Node{} remoteZoneNodes := []*corev1.Node{} @@ -250,6 +259,7 @@ var _ = ginkgo.Describe("Zone Interconnect Operations", func() { initialNBDB []libovsdbtest.TestData initialSBDB []libovsdbtest.TestData testNodesRouteInfo map[string]map[string]string + nodeRouteInfoMap map[string]map[string]map[string]string ) const ( @@ -736,6 +746,137 @@ var _ = ginkgo.Describe("Zone Interconnect Operations", func() { }) }) + ginkgo.Context("Two secondary networks", func() { + ginkgo.BeforeEach(func() { + testNode1 = corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + Annotations: map[string]string{ + ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac6", + ovnNodeZoneNameAnnotation: "global", + ovnNodeIDAnnotaton: "2", + ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.2.0/24\"], \"blue\":[\"11.244.2.0/24\"]}", + ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.2/16\"}", + util.OVNNodeGRLRPAddrs: "{\"default\":{\"ipv4\":\"100.64.0.2/16\"}}", + ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}", + }, + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.10"}}, + }, + } + // node2 is a remote zone node + testNode2 = corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + Annotations: map[string]string{ + ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac7", + ovnNodeZoneNameAnnotation: "foo", + ovnNodeIDAnnotaton: "3", + ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.3.0/24\"], \"blue\":[\"11.244.3.0/24\"]}", + ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.3/16\"}", + util.OVNNodeGRLRPAddrs: "{\"defalut\":{\"ipv4\":\"100.64.0.3/16\"}}", + ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}", + }, + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.11"}}, + }, + } + // node3 is a remote zone node + testNode3 = corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node3", + Annotations: map[string]string{ + ovnNodeChassisIDAnnotatin: "cb9ec8fa-b409-4ef3-9f42-d9283c47aac8", + ovnNodeZoneNameAnnotation: "foo", + ovnNodeIDAnnotaton: "4", + ovnNodeSubnetsAnnotation: "{\"red\":[\"10.244.4.0/24\"], \"blue\":[\"11.244.4.0/24\"]}", + ovnTransitSwitchPortAddrAnnotation: "{\"ipv4\":\"100.88.0.4/16\"}", + util.OVNNodeGRLRPAddrs: "{\"default\":{\"ipv4\":\"100.64.0.4/16\"}}", + ovnNodeNetworkIDsAnnotation: "{\"red\":\"2\", \"blue\":\"1\"}", + }, + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.0.0.12"}}, + }, + } + + nodeRouteInfoMap = map[string]map[string]map[string]string{ + "red": { + "node1": {"node-subnets": "10.244.2.0/24", "ts-ip": "100.88.0.2", "host-route": "100.64.0.2/32"}, + "node2": {"node-subnets": "10.244.3.0/24", "ts-ip": "100.88.0.3", "host-route": "100.64.0.3/32"}, + "node3": {"node-subnets": "10.244.4.0/24", "ts-ip": "100.88.0.4", "host-route": "100.64.0.4/32"}, + }, + "blue": { + "node1": {"node-subnets": "11.244.2.0/24", "ts-ip": "100.88.0.2", "host-route": "100.64.0.2/32"}, + "node2": {"node-subnets": "11.244.3.0/24", "ts-ip": "100.88.0.3", "host-route": "100.64.0.3/32"}, + "node3": {"node-subnets": "11.244.4.0/24", "ts-ip": "100.88.0.4", "host-route": "100.64.0.4/32"}, + }, + } + initialNBDB = []libovsdbtest.TestData{ + newOVNClusterRouter("blue"), + newOVNClusterRouter("red"), + } + + initialSBDB = []libovsdbtest.TestData{ + &node1Chassis, &node2Chassis, &node3Chassis} + }) + + ginkgo.It("Delete remote node", func() { + app.Action = func(ctx *cli.Context) error { + dbSetup := libovsdbtest.TestSetup{ + NBData: initialNBDB, + SBData: initialSBDB, + } + + _, err := config.InitConfig(ctx, nil, nil) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + config.Kubernetes.HostNetworkNamespace = "" + + var libovsdbOvnNBClient, libovsdbOvnSBClient libovsdbclient.Client + libovsdbOvnNBClient, libovsdbOvnSBClient, libovsdbCleanup, err = libovsdbtest.NewNBSBTestHarness(dbSetup) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + zoneICHandler := map[string]*ZoneInterconnectHandler{} + for _, netName := range []string{"red", "blue"} { + err = createTransitSwitchPortBindings(libovsdbOvnSBClient, netName, &testNode1, &testNode2, &testNode3) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + netInfo, err := util.NewNetInfo(&ovncnitypes.NetConf{NetConf: cnitypes.NetConf{Name: netName}, Topology: types.Layer3Topology}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + zoneICHandler[netName] = NewZoneInterconnectHandler(netInfo, libovsdbOvnNBClient, libovsdbOvnSBClient, nil) + err = zoneICHandler[netName].createOrUpdateTransitSwitch(1) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = invokeICHandlerAddNodeFunction("global", zoneICHandler[netName], &testNode1, &testNode2, &testNode3) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = checkInterconnectResources("global", netName, libovsdbOvnNBClient, nodeRouteInfoMap[netName], &testNode1, &testNode2, &testNode3) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + + // Check the logical entities are as expected when a remote node is deleted + ginkgo.By("Delete remote node \"red\"") + delete(nodeRouteInfoMap["red"], "node3") + err = invokeICHandlerDeleteNodeFunction(zoneICHandler["red"], &testNode3) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = checkInterconnectResources("global", "red", libovsdbOvnNBClient, nodeRouteInfoMap["red"], &testNode1, &testNode2) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = checkInterconnectResources("global", "blue", libovsdbOvnNBClient, nodeRouteInfoMap["blue"], &testNode1, &testNode2, &testNode3) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + return nil + } + + err := app.Run([]string{ + app.Name, + "-cluster-subnets=" + clusterCIDR, + "-init-cluster-manager", + "-zone-join-switch-subnets=" + joinSubnetCIDR, + "-enable-interconnect", + }) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) + ginkgo.Context("Error scenarios", func() { ginkgo.It("Missing annotations and error scenarios for local node", func() { app.Action = func(ctx *cli.Context) error { From 61f57e2aff4351a9eaddec5439a97bd19d1a8f81 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 5 Jun 2025 13:12:33 -0700 Subject: [PATCH 029/278] Delete logical static routes only if they belong to the spefified router Do not try to delete the logical route static route from the specified logical router if the route does not belong to the router. Signed-off-by: Yun Zhou --- go-controller/pkg/libovsdb/ops/router.go | 37 +++---- go-controller/pkg/libovsdb/ops/router_test.go | 96 +++++++++++++++++++ 2 files changed, 109 insertions(+), 24 deletions(-) diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go index 3d5a6fc255..df87307918 100644 --- a/go-controller/pkg/libovsdb/ops/router.go +++ b/go-controller/pkg/libovsdb/ops/router.go @@ -761,8 +761,8 @@ func CreateOrReplaceLogicalRouterStaticRouteWithPredicateOps( } // DeleteLogicalRouterStaticRoutesWithPredicate looks up logical router static -// routes from the cache based on a given predicate, deletes them and removes -// them from the provided logical router +// routes from the logical router of the specified name based on a given predicate, +// deletes them and removes them from the provided logical router func DeleteLogicalRouterStaticRoutesWithPredicate(nbClient libovsdbclient.Client, routerName string, p logicalRouterStaticRoutePredicate) error { var ops []ovsdb.Operation var err error @@ -775,32 +775,21 @@ func DeleteLogicalRouterStaticRoutesWithPredicate(nbClient libovsdbclient.Client } // DeleteLogicalRouterStaticRoutesWithPredicateOps looks up logical router static -// routes from the cache based on a given predicate, and returns the ops to delete -// them and remove them from the provided logical router +// routes from the logical router of the specified name based on a given predicate, +// and returns the ops to delete them and remove them from the provided logical router func DeleteLogicalRouterStaticRoutesWithPredicateOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, routerName string, p logicalRouterStaticRoutePredicate) ([]ovsdb.Operation, error) { - router := &nbdb.LogicalRouter{ - Name: routerName, + lrsrs, err := GetRouterLogicalRouterStaticRoutesWithPredicate(nbClient, &nbdb.LogicalRouter{Name: routerName}, p) + if err != nil { + if errors.Is(err, libovsdbclient.ErrNotFound) { + return ops, nil + } + return nil, fmt.Errorf("unable to find logical router static routes with predicate on router %s: %w", routerName, err) } - deleted := []*nbdb.LogicalRouterStaticRoute{} - opModels := []operationModel{ - { - ModelPredicate: p, - ExistingResult: &deleted, - DoAfter: func() { router.StaticRoutes = extractUUIDsFromModels(deleted) }, - ErrNotFound: false, - BulkOp: true, - }, - { - Model: router, - OnModelMutations: []interface{}{&router.StaticRoutes}, - ErrNotFound: false, - BulkOp: false, - }, + if len(lrsrs) == 0 { + return ops, nil } - - m := newModelClient(nbClient) - return m.DeleteOps(ops, opModels...) + return DeleteLogicalRouterStaticRoutesOps(nbClient, ops, routerName, lrsrs...) } // DeleteLogicalRouterStaticRoutesOps deletes the logical router static routes and diff --git a/go-controller/pkg/libovsdb/ops/router_test.go b/go-controller/pkg/libovsdb/ops/router_test.go index fd4879ebd6..579814b27e 100644 --- a/go-controller/pkg/libovsdb/ops/router_test.go +++ b/go-controller/pkg/libovsdb/ops/router_test.go @@ -306,3 +306,99 @@ func TestDeleteRoutersWithPredicateOps(t *testing.T) { }) } } + +func TestDeleteLogicalRouterStaticRoutes(t *testing.T) { + fakeRouter1LRSR1 := &nbdb.LogicalRouterStaticRoute{ + UUID: buildNamedUUID(), + IPPrefix: "192.168.1.0/24", + Nexthop: "192.168.1.0", + ExternalIDs: map[string]string{"id": "v1"}, + } + + fakeRouter1LRSR2 := &nbdb.LogicalRouterStaticRoute{ + UUID: buildNamedUUID(), + IPPrefix: "192.169.1.0/24", + Nexthop: "192.169.1.0", + ExternalIDs: map[string]string{"id": "v2"}, + } + + fakeRouter2LRSR1 := &nbdb.LogicalRouterStaticRoute{ + UUID: buildNamedUUID(), + IPPrefix: "192.170.1.0/24", + Nexthop: "192.170.1.0", + ExternalIDs: map[string]string{"id": "v1"}, + } + + tests := []struct { + desc string + expectErr bool + routerName string + lrsrs []*nbdb.LogicalRouterStaticRoute + initialNbdb libovsdbtest.TestSetup + expectedNbdb libovsdbtest.TestSetup + }{ + { + desc: "delete logical router static route with predicate will only delete static route from the specified router", + initialNbdb: libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + fakeRouter1LRSR1, + fakeRouter1LRSR2, + fakeRouter2LRSR1, + &nbdb.LogicalRouter{ + Name: "rtr1", + UUID: buildNamedUUID(), + StaticRoutes: []string{fakeRouter1LRSR1.UUID, fakeRouter1LRSR2.UUID}, + }, + &nbdb.LogicalRouter{ + Name: "rtr2", + UUID: buildNamedUUID(), + StaticRoutes: []string{fakeRouter2LRSR1.UUID}, + }, + }, + }, + expectedNbdb: libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + fakeRouter1LRSR2, + fakeRouter2LRSR1, + &nbdb.LogicalRouter{ + Name: "rtr1", + UUID: buildNamedUUID(), + StaticRoutes: []string{fakeRouter1LRSR2.UUID}, + }, + &nbdb.LogicalRouter{ + Name: "rtr2", + UUID: buildNamedUUID(), + StaticRoutes: []string{fakeRouter2LRSR1.UUID}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + nbClient, cleanup, err := libovsdbtest.NewNBTestHarness(tt.initialNbdb, nil) + if err != nil { + t.Fatalf("test: \"%s\" failed to set up test harness: %v", tt.desc, err) + } + t.Cleanup(cleanup.Cleanup) + + err = DeleteLogicalRouterStaticRoutesWithPredicate(nbClient, "rtr1", func(item *nbdb.LogicalRouterStaticRoute) bool { + return item.ExternalIDs["id"] == "v1" + }) + if err != nil && !tt.expectErr { + t.Fatal(fmt.Errorf("DeleteLogicalRouterStaticRoutesWithPredicate() error = %v", err)) + } + + matcher := libovsdbtest.HaveData(tt.expectedNbdb.NBData) + success, err := matcher.Match(nbClient) + + if !success { + t.Fatal(fmt.Errorf("test: \"%s\" didn't match expected with actual, err: %v", tt.desc, matcher.FailureMessage(nbClient))) + } + if err != nil { + t.Fatal(fmt.Errorf("test: \"%s\" encountered error: %v", tt.desc, err)) + } + }) + } +} From d14d8483505700c60c808d87daa2bbe6c29efe08 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 29 May 2025 10:01:36 -0700 Subject: [PATCH 030/278] remote node deletion failure due to libovsdb integrity violation error ovnkube-controller is trying to delete logical static route from the router it does not belong, which ends with the error: "referential integrity violation: cannot delete Logical_Router_Static_Route row ... because of 1 remaining references" Signed-off-by: Yun Zhou --- .../pkg/ovn/zone_interconnect/zone_ic_handler.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index cc849b6c15..f484bc1528 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -515,7 +515,9 @@ func (zic *ZoneInterconnectHandler) cleanupNode(nodeName string) error { return err } - // Delete any static routes in the cluster router for this node + // Delete any static routes in the cluster router for this node. + // skip types.NetworkExternalID check in the predicate function as this static route may be deleted + // before types.NetworkExternalID external-ids is set correctly during upgrade. p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool { return lrsr.ExternalIDs["ic-node"] == nodeName } @@ -573,11 +575,15 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, addRoute := func(prefix, nexthop string) error { logicalRouterStaticRoute := nbdb.LogicalRouterStaticRoute{ ExternalIDs: map[string]string{ - "ic-node": node.Name, + "ic-node": node.Name, + types.NetworkExternalID: zic.GetNetworkName(), }, Nexthop: nexthop, IPPrefix: prefix, } + // Note that because logical router static routes were originally created without types.NetworkExternalID + // external-ids, skip types.NetworkExternalID check in the predicate function to replace existing static route + // with correct external-ids on an upgrade scenario. p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool { return lrsr.IPPrefix == prefix && lrsr.Nexthop == nexthop && @@ -613,6 +619,8 @@ func (zic *ZoneInterconnectHandler) addRemoteNodeStaticRoutes(node *corev1.Node, // deleteLocalNodeStaticRoutes deletes the static routes added by the function addRemoteNodeStaticRoutes func (zic *ZoneInterconnectHandler) deleteLocalNodeStaticRoutes(node *corev1.Node, nodeTransitSwitchPortIPs []*net.IPNet) error { + // skip types.NetworkExternalID check in the predicate function as this static route may be deleted + // before types.NetworkExternalID external-ids is set correctly during upgrade. deleteRoute := func(prefix, nexthop string) error { p := func(lrsr *nbdb.LogicalRouterStaticRoute) bool { return lrsr.IPPrefix == prefix && From 10be5961a8663d801ec16b34ba60633d867cfa8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 15:35:32 +0000 Subject: [PATCH 031/278] Bump golang.org/x/crypto Bumps the go_modules group with 1 update in the /test/e2e directory: [golang.org/x/crypto](https://github.com/golang/crypto). Updates `golang.org/x/crypto` from 0.24.0 to 0.31.0 - [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect dependency-group: go_modules ... Signed-off-by: dependabot[bot] --- test/e2e/go.mod | 10 +++++----- test/e2e/go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 6a865f71ee..d1d514d1f9 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -12,7 +12,7 @@ require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/pkg/errors v0.9.1 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.11.0 k8s.io/api v0.32.3 k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.32.3 @@ -145,13 +145,13 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 6fee7ac542..239bd56b7a 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -581,8 +581,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -690,8 +690,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -752,15 +752,15 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -771,8 +771,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 2412b5068f042c9ccd4a3c3e4e8a586e99877aeb Mon Sep 17 00:00:00 2001 From: Sebastian Sch Date: Mon, 3 Feb 2025 15:11:50 +0200 Subject: [PATCH 032/278] stop adding events to NAD if the network type is not ovn-k If NADs like bridge,macvlan or others exist we should not record an error event for it Also in case the NAD is not ovn-k for example multus we support chain plugins. Signed-off-by: Sebastian Sch --- go-controller/pkg/config/cni.go | 8 ++++---- .../pkg/networkmanager/nad_controller.go | 5 +++++ .../pkg/networkmanager/nad_controller_test.go | 15 +++++++++++++++ go-controller/pkg/util/multi_network.go | 3 +++ go-controller/pkg/util/multi_network_test.go | 12 +++++++++++- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/go-controller/pkg/config/cni.go b/go-controller/pkg/config/cni.go index 3d935c5c6a..3bec2d286f 100644 --- a/go-controller/pkg/config/cni.go +++ b/go-controller/pkg/config/cni.go @@ -120,10 +120,6 @@ func parseNetConfSingle(bytes []byte) (*ovncnitypes.NetConf, error) { } func parseNetConfList(confList *libcni.NetworkConfigList) (*ovncnitypes.NetConf, error) { - if len(confList.Plugins) > 1 { - return nil, ErrorChainingNotSupported - } - netconf := &ovncnitypes.NetConf{MTU: Default.MTU} if err := json.Unmarshal(confList.Plugins[0].Bytes, netconf); err != nil { return nil, err @@ -134,6 +130,10 @@ func parseNetConfList(confList *libcni.NetworkConfigList) (*ovncnitypes.NetConf, return nil, ErrorAttachDefNotOvnManaged } + if len(confList.Plugins) > 1 { + return nil, ErrorChainingNotSupported + } + netconf.Name = confList.Name netconf.CNIVersion = confList.CNIVersion diff --git a/go-controller/pkg/networkmanager/nad_controller.go b/go-controller/pkg/networkmanager/nad_controller.go index b0c6a3198a..a212566ce1 100644 --- a/go-controller/pkg/networkmanager/nad_controller.go +++ b/go-controller/pkg/networkmanager/nad_controller.go @@ -274,6 +274,11 @@ func (c *nadController) syncNAD(key string, nad *nettypes.NetworkAttachmentDefin if nad != nil { nadNetwork, err = util.ParseNADInfo(nad) if err != nil { + // in case the type for the NAD is not ovn-k we should not record the error event + if err.Error() == util.ErrorAttachDefNotOvnManaged.Error() { + return nil + } + if c.recorder != nil { c.recorder.Eventf(&corev1.ObjectReference{Kind: nad.Kind, Namespace: nad.Namespace, Name: nad.Name}, corev1.EventTypeWarning, "InvalidConfig", "Failed to parse network config: %v", err.Error()) diff --git a/go-controller/pkg/networkmanager/nad_controller_test.go b/go-controller/pkg/networkmanager/nad_controller_test.go index c8a59b30b4..1ce5ad9168 100644 --- a/go-controller/pkg/networkmanager/nad_controller_test.go +++ b/go-controller/pkg/networkmanager/nad_controller_test.go @@ -469,6 +469,21 @@ func TestNADController(t *testing.T) { }, }, }, + { + name: "non ovn-k NAD added", + args: []args{ + { + nad: "test/nad_1", + network: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{ + Name: "test", + Type: "sriov", + }, + }, + wantErr: false, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index b1679462f3..2cf3d906f6 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -1151,6 +1151,9 @@ func ParseNADInfo(nad *nettypes.NetworkAttachmentDefinition) (NetInfo, error) { func ParseNetConf(netattachdef *nettypes.NetworkAttachmentDefinition) (*ovncnitypes.NetConf, error) { netconf, err := config.ParseNetConf([]byte(netattachdef.Spec.Config)) if err != nil { + if err.Error() == ErrorAttachDefNotOvnManaged.Error() { + return nil, err + } return nil, fmt.Errorf("error parsing Network Attachment Definition %s/%s: %v", netattachdef.Namespace, netattachdef.Name, err) } diff --git a/go-controller/pkg/util/multi_network_test.go b/go-controller/pkg/util/multi_network_test.go index 56f18d058a..daaaf920a5 100644 --- a/go-controller/pkg/util/multi_network_test.go +++ b/go-controller/pkg/util/multi_network_test.go @@ -180,7 +180,7 @@ func TestParseNetconf(t *testing.T) { "netAttachDefName": "default/tenantred" } `, - expectedError: fmt.Errorf("error parsing Network Attachment Definition ns1/nad1: net-attach-def not managed by OVN"), + expectedError: fmt.Errorf("net-attach-def not managed by OVN"), }, { desc: "attachment definition with IPAM key defined, using a wrong type", @@ -1154,6 +1154,16 @@ func TestSubnetOverlapCheck(t *testing.T) { } `, }, + { + desc: "return error when the network is not ovnk", + inputNetAttachDefConfigSpec: ` + { + "name": "test", + "type": "sriov-cni" + } + `, + expectedError: ErrorAttachDefNotOvnManaged, + }, } for _, test := range tests { From 23c3b5a8ebf02a8c532f8f524b4866d5764c4e13 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 12 Jun 2025 10:17:17 +0200 Subject: [PATCH 033/278] [ACL tier] Rename BuildACL to BuildACLWithDefaultTier This helps to avoid confusion about defaulting the ACL tier. Update BuildACL to require the tier as an argument. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/libovsdb/util/acl.go | 13 ++++++++++--- .../pkg/ovn/admin_network_policy_test.go | 2 +- .../ovn/base_network_controller_multicast.go | 8 ++++---- .../pkg/ovn/base_network_controller_policy.go | 10 +++++----- go-controller/pkg/ovn/egressfirewall.go | 2 +- go-controller/pkg/ovn/gateway_test.go | 2 +- go-controller/pkg/ovn/gress_policy.go | 4 ++-- go-controller/pkg/ovn/udn_isolation.go | 18 +++++++++--------- go-controller/pkg/types/const.go | 7 +++---- 9 files changed, 36 insertions(+), 30 deletions(-) diff --git a/go-controller/pkg/libovsdb/util/acl.go b/go-controller/pkg/libovsdb/util/acl.go index dbb6c2b3e5..798c1b773a 100644 --- a/go-controller/pkg/libovsdb/util/acl.go +++ b/go-controller/pkg/libovsdb/util/acl.go @@ -88,11 +88,18 @@ func GetACLName(dbIDs *libovsdbops.DbObjectIDs) string { return fmt.Sprintf("%.63s", aclName) } +// BuildACLWithDefaultTier is used for the most ACL-related features with the default ACL tier. +// That includes egress firewall, network policy, multicast. +func BuildACLWithDefaultTier(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string, logLevels *ACLLoggingLevels, + aclT ACLPipelineType) *nbdb.ACL { + return BuildACL(dbIDs, priority, match, action, logLevels, aclT, types.DefaultACLTier) +} + // BuildACL should be used to build ACL instead of directly calling libovsdbops.BuildACL. // It can properly set and reset log settings for ACL based on ACLLoggingLevels, and // set acl.Name and acl.ExternalIDs based on given DbIDs func BuildACL(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string, logLevels *ACLLoggingLevels, - aclT ACLPipelineType) *nbdb.ACL { + aclT ACLPipelineType, tier int) *nbdb.ACL { var options map[string]string var direction string switch aclT { @@ -122,13 +129,13 @@ func BuildACL(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string log, externalIDs, options, - types.DefaultACLTier, + tier, ) return ACL } func BuildANPACL(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string, aclT ACLPipelineType, logLevels *ACLLoggingLevels) *nbdb.ACL { - anpACL := BuildACL(dbIDs, priority, match, action, logLevels, aclT) + anpACL := BuildACLWithDefaultTier(dbIDs, priority, match, action, logLevels, aclT) anpACL.Tier = GetACLTier(dbIDs) return anpACL } diff --git a/go-controller/pkg/ovn/admin_network_policy_test.go b/go-controller/pkg/ovn/admin_network_policy_test.go index 82eac3cf9d..152ee0c0a8 100644 --- a/go-controller/pkg/ovn/admin_network_policy_test.go +++ b/go-controller/pkg/ovn/admin_network_policy_test.go @@ -94,7 +94,7 @@ func getANPGressACL(action, anpName, direction string, rulePriority int32, ruleIndex int32, ports *[]anpapi.AdminNetworkPolicyPort, namedPorts map[string][]libovsdbutil.NamedNetworkPolicyPort, banp bool) []*nbdb.ACL { retACLs := []*nbdb.ACL{} - // we are not using BuildACL and instead manually building it on purpose so that the code path for BuildACL is also tested + // we are not using BuildACLWithDefaultTier and instead manually building it on purpose so that the code path for BuildACLWithDefaultTier is also tested acl := nbdb.ACL{} acl.Action = action acl.Severity = nil diff --git a/go-controller/pkg/ovn/base_network_controller_multicast.go b/go-controller/pkg/ovn/base_network_controller_multicast.go index eadb47882a..6f413177d5 100644 --- a/go-controller/pkg/ovn/base_network_controller_multicast.go +++ b/go-controller/pkg/ovn/base_network_controller_multicast.go @@ -119,13 +119,13 @@ func (bnc *BaseNetworkController) createMulticastAllowPolicy(ns string, nsInfo * egressMatch := libovsdbutil.GetACLMatch(portGroupName, bnc.getMulticastACLEgrMatch(), aclDir) dbIDs := getNamespaceMcastACLDbIDs(ns, aclDir, bnc.controllerName) aclPipeline := libovsdbutil.ACLDirectionToACLPipeline(aclDir) - egressACL := libovsdbutil.BuildACL(dbIDs, types.DefaultMcastAllowPriority, egressMatch, nbdb.ACLActionAllow, nil, aclPipeline) + egressACL := libovsdbutil.BuildACLWithDefaultTier(dbIDs, types.DefaultMcastAllowPriority, egressMatch, nbdb.ACLActionAllow, nil, aclPipeline) aclDir = libovsdbutil.ACLIngress ingressMatch := libovsdbutil.GetACLMatch(portGroupName, bnc.getMulticastACLIgrMatch(nsInfo), aclDir) dbIDs = getNamespaceMcastACLDbIDs(ns, aclDir, bnc.controllerName) aclPipeline = libovsdbutil.ACLDirectionToACLPipeline(aclDir) - ingressACL := libovsdbutil.BuildACL(dbIDs, types.DefaultMcastAllowPriority, ingressMatch, nbdb.ACLActionAllow, nil, aclPipeline) + ingressACL := libovsdbutil.BuildACLWithDefaultTier(dbIDs, types.DefaultMcastAllowPriority, ingressMatch, nbdb.ACLActionAllow, nil, aclPipeline) acls := []*nbdb.ACL{egressACL, ingressACL} ops, err := libovsdbops.CreateOrUpdateACLsOps(bnc.nbClient, nil, bnc.GetSamplingConfig(), acls...) @@ -186,7 +186,7 @@ func (bnc *BaseNetworkController) createDefaultDenyMulticastPolicy() error { for _, aclDir := range []libovsdbutil.ACLDirection{libovsdbutil.ACLEgress, libovsdbutil.ACLIngress} { dbIDs := getDefaultMcastACLDbIDs(mcastDefaultDenyID, aclDir, bnc.controllerName) aclPipeline := libovsdbutil.ACLDirectionToACLPipeline(aclDir) - acl := libovsdbutil.BuildACL(dbIDs, types.DefaultMcastDenyPriority, match, nbdb.ACLActionDrop, nil, aclPipeline) + acl := libovsdbutil.BuildACLWithDefaultTier(dbIDs, types.DefaultMcastDenyPriority, match, nbdb.ACLActionDrop, nil, aclPipeline) acls = append(acls, acl) } ops, err := libovsdbops.CreateOrUpdateACLsOps(bnc.nbClient, nil, bnc.GetSamplingConfig(), acls...) @@ -228,7 +228,7 @@ func (bnc *BaseNetworkController) createDefaultAllowMulticastPolicy() error { match := libovsdbutil.GetACLMatch(rtrPGName, mcastMatch, aclDir) dbIDs := getDefaultMcastACLDbIDs(mcastAllowInterNodeID, aclDir, bnc.controllerName) aclPipeline := libovsdbutil.ACLDirectionToACLPipeline(aclDir) - acl := libovsdbutil.BuildACL(dbIDs, types.DefaultMcastAllowPriority, match, nbdb.ACLActionAllow, nil, aclPipeline) + acl := libovsdbutil.BuildACLWithDefaultTier(dbIDs, types.DefaultMcastAllowPriority, match, nbdb.ACLActionAllow, nil, aclPipeline) acls = append(acls, acl) } diff --git a/go-controller/pkg/ovn/base_network_controller_policy.go b/go-controller/pkg/ovn/base_network_controller_policy.go index f4c10bfacf..95665068c7 100644 --- a/go-controller/pkg/ovn/base_network_controller_policy.go +++ b/go-controller/pkg/ovn/base_network_controller_policy.go @@ -246,11 +246,11 @@ func (bnc *BaseNetworkController) addHairpinAllowACL() error { } ingressACLIDs := bnc.getNetpolDefaultACLDbIDs(string(knet.PolicyTypeIngress)) - ingressACL := libovsdbutil.BuildACL(ingressACLIDs, types.DefaultAllowPriority, match, + ingressACL := libovsdbutil.BuildACLWithDefaultTier(ingressACLIDs, types.DefaultAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) egressACLIDs := bnc.getNetpolDefaultACLDbIDs(string(knet.PolicyTypeEgress)) - egressACL := libovsdbutil.BuildACL(egressACLIDs, types.DefaultAllowPriority, match, + egressACL := libovsdbutil.BuildACLWithDefaultTier(egressACLIDs, types.DefaultAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportEgressAfterLB) ops, err := libovsdbops.CreateOrUpdateACLsOps(bnc.nbClient, nil, nil, ingressACL, egressACL) @@ -329,7 +329,7 @@ func (bnc *BaseNetworkController) addAllowACLFromNode(switchName string, mgmtPor } match := fmt.Sprintf("%s.src==%s", ipFamily, mgmtPortIP.String()) dbIDs := getAllowFromNodeACLDbIDs(switchName, mgmtPortIP.String(), bnc.controllerName) - nodeACL := libovsdbutil.BuildACL(dbIDs, types.DefaultAllowPriority, match, + nodeACL := libovsdbutil.BuildACLWithDefaultTier(dbIDs, types.DefaultAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) ops, err := libovsdbops.CreateOrUpdateACLsOps(bnc.nbClient, nil, bnc.GetSamplingConfig(), nodeACL) @@ -382,9 +382,9 @@ func (bnc *BaseNetworkController) buildDenyACLs(namespace, pgName string, aclLog allowMatch := libovsdbutil.GetACLMatch(pgName, arpAllowPolicyMatch, aclDir) aclPipeline := libovsdbutil.ACLDirectionToACLPipeline(aclDir) - denyACL = libovsdbutil.BuildACL(bnc.getDefaultDenyPolicyACLIDs(namespace, aclDir, defaultDenyACL), + denyACL = libovsdbutil.BuildACLWithDefaultTier(bnc.getDefaultDenyPolicyACLIDs(namespace, aclDir, defaultDenyACL), types.DefaultDenyPriority, denyMatch, nbdb.ACLActionDrop, aclLogging, aclPipeline) - allowACL = libovsdbutil.BuildACL(bnc.getDefaultDenyPolicyACLIDs(namespace, aclDir, arpAllowACL), + allowACL = libovsdbutil.BuildACLWithDefaultTier(bnc.getDefaultDenyPolicyACLIDs(namespace, aclDir, arpAllowACL), types.DefaultAllowPriority, allowMatch, nbdb.ACLActionAllow, nil, aclPipeline) return } diff --git a/go-controller/pkg/ovn/egressfirewall.go b/go-controller/pkg/ovn/egressfirewall.go index 4e49505d04..9618c1b5a9 100644 --- a/go-controller/pkg/ovn/egressfirewall.go +++ b/go-controller/pkg/ovn/egressfirewall.go @@ -467,7 +467,7 @@ func (oc *DefaultNetworkController) addEgressFirewallRules(ef *egressFirewall, p func (oc *DefaultNetworkController) createEgressFirewallACLOps(ops []ovsdb.Operation, ruleIdx int, match, action, namespace, pgName string, aclLogging *libovsdbutil.ACLLoggingLevels) ([]ovsdb.Operation, error) { aclIDs := oc.getEgressFirewallACLDbIDs(namespace, ruleIdx) priority := types.EgressFirewallStartPriority - ruleIdx - egressFirewallACL := libovsdbutil.BuildACL( + egressFirewallACL := libovsdbutil.BuildACLWithDefaultTier( aclIDs, priority, match, diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index 57f5fb4be2..b7a29739e4 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -43,7 +43,7 @@ func generateAdvertisedUDNIsolationExpectedNB(testData []libovsdbtest.TestData, passMatches = append(passMatches, fmt.Sprintf("(%s.src == %s && %s.dst == %s)", ipPrefix, subnet, ipPrefix, subnet)) } - passACL := libovsdbutil.BuildACL( + passACL := libovsdbutil.BuildACLWithDefaultTier( GetAdvertisedNetworkSubnetsPassACLdbIDs(DefaultNetworkControllerName, networkName, networkID), types.AdvertisedNetworkPassPriority, strings.Join(passMatches, " || "), diff --git a/go-controller/pkg/ovn/gress_policy.go b/go-controller/pkg/ovn/gress_policy.go index c8445e6ed5..cb152f1e8b 100644 --- a/go-controller/pkg/ovn/gress_policy.go +++ b/go-controller/pkg/ovn/gress_policy.go @@ -281,7 +281,7 @@ func (gp *gressPolicy) buildLocalPodACLs(portGroupName string, aclLogging *libov ipBlockMatches := gp.getMatchFromIPBlock(lportMatch, l4Match) for ipBlockIdx, ipBlockMatch := range ipBlockMatches { aclIDs := gp.getNetpolACLDbIDs(ipBlockIdx, protocol) - acl := libovsdbutil.BuildACL(aclIDs, types.DefaultAllowPriority, ipBlockMatch, action, + acl := libovsdbutil.BuildACLWithDefaultTier(aclIDs, types.DefaultAllowPriority, ipBlockMatch, action, aclLogging, gp.aclPipeline) createdACLs = append(createdACLs, acl) } @@ -302,7 +302,7 @@ func (gp *gressPolicy) buildLocalPodACLs(portGroupName string, aclLogging *libov addrSetMatch = fmt.Sprintf("%s && %s && %s", l3Match, l4Match, lportMatch) } aclIDs := gp.getNetpolACLDbIDs(emptyIdx, protocol) - acl := libovsdbutil.BuildACL(aclIDs, types.DefaultAllowPriority, addrSetMatch, action, + acl := libovsdbutil.BuildACLWithDefaultTier(aclIDs, types.DefaultAllowPriority, addrSetMatch, action, aclLogging, gp.aclPipeline) if l3Match == "" { // if l3Match is empty, then no address sets are selected for a given gressPolicy. diff --git a/go-controller/pkg/ovn/udn_isolation.go b/go-controller/pkg/ovn/udn_isolation.go index 6c44489f9c..98c716d6cc 100644 --- a/go-controller/pkg/ovn/udn_isolation.go +++ b/go-controller/pkg/ovn/udn_isolation.go @@ -63,7 +63,7 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { pgName := libovsdbutil.GetPortGroupName(pgIDs) egressDenyIDs := oc.getUDNACLDbIDs(DenySecondaryACL, libovsdbutil.ACLEgress) match := libovsdbutil.GetACLMatch(pgName, "", libovsdbutil.ACLEgress) - egressDenyACL := libovsdbutil.BuildACL(egressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportEgress) + egressDenyACL := libovsdbutil.BuildACLWithDefaultTier(egressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportEgress) getARPMatch := func(direction libovsdbutil.ACLDirection) string { match := "(" @@ -89,15 +89,15 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { egressARPIDs := oc.getUDNACLDbIDs(AllowHostARPACL, libovsdbutil.ACLEgress) match = libovsdbutil.GetACLMatch(pgName, getARPMatch(libovsdbutil.ACLEgress), libovsdbutil.ACLEgress) - egressARPACL := libovsdbutil.BuildACL(egressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress) + egressARPACL := libovsdbutil.BuildACLWithDefaultTier(egressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress) ingressDenyIDs := oc.getUDNACLDbIDs(DenySecondaryACL, libovsdbutil.ACLIngress) match = libovsdbutil.GetACLMatch(pgName, "", libovsdbutil.ACLIngress) - ingressDenyACL := libovsdbutil.BuildACL(ingressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportIngress) + ingressDenyACL := libovsdbutil.BuildACLWithDefaultTier(ingressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportIngress) ingressARPIDs := oc.getUDNACLDbIDs(AllowHostARPACL, libovsdbutil.ACLIngress) match = libovsdbutil.GetACLMatch(pgName, getARPMatch(libovsdbutil.ACLIngress), libovsdbutil.ACLIngress) - ingressARPACL := libovsdbutil.BuildACL(ingressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportIngress) + ingressARPACL := libovsdbutil.BuildACLWithDefaultTier(ingressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportIngress) ingressAllowIDs := oc.getUDNACLDbIDs(AllowHostSecondaryACL, libovsdbutil.ACLIngress) match = "(" @@ -114,7 +114,7 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { } match += ")" match = libovsdbutil.GetACLMatch(pgName, match, libovsdbutil.ACLIngress) - ingressAllowACL := libovsdbutil.BuildACL(ingressAllowIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) + ingressAllowACL := libovsdbutil.BuildACLWithDefaultTier(ingressAllowIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) ops, err := libovsdbops.CreateOrUpdateACLsOps(oc.nbClient, nil, oc.GetSamplingConfig(), egressDenyACL, egressARPACL, ingressARPACL, ingressDenyACL, ingressAllowACL) if err != nil { @@ -199,11 +199,11 @@ func (oc *DefaultNetworkController) setUDNPodOpenPortsOps(podNamespacedName stri ingressMatch, egressMatch, parseErr := getPortsMatches(podAnnotations, lspName) // don't return on parseErr, as we need to cleanup potentially present ACLs from the previous config ingressIDs := oc.getUDNOpenPortDbIDs(podNamespacedName, libovsdbutil.ACLIngress) - ingressACL := libovsdbutil.BuildACL(ingressIDs, types.PrimaryUDNAllowPriority, + ingressACL := libovsdbutil.BuildACLWithDefaultTier(ingressIDs, types.PrimaryUDNAllowPriority, ingressMatch, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) egressIDs := oc.getUDNOpenPortDbIDs(podNamespacedName, libovsdbutil.ACLEgress) - egressACL := libovsdbutil.BuildACL(egressIDs, types.PrimaryUDNAllowPriority, + egressACL := libovsdbutil.BuildACLWithDefaultTier(egressIDs, types.PrimaryUDNAllowPriority, egressMatch, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress) var err error @@ -282,7 +282,7 @@ func BuildAdvertisedNetworkSubnetsDropACL(advertisedNetworkSubnetsAddressSet add dropMatches = append(dropMatches, fmt.Sprintf("(ip6.src == $%s && ip6.dst == $%s)", v6AddrSet, v6AddrSet)) } - dropACL := libovsdbutil.BuildACL( + dropACL := libovsdbutil.BuildACLWithDefaultTier( GetAdvertisedNetworkSubnetsDropACLdbIDs(), types.AdvertisedNetworkDenyPriority, strings.Join(dropMatches, " || "), @@ -325,7 +325,7 @@ func (bnc *BaseNetworkController) addAdvertisedNetworkIsolation(nodeName string) ops = append(ops, addrOps...) if len(passMatches) > 0 { - passACL := libovsdbutil.BuildACL( + passACL := libovsdbutil.BuildACLWithDefaultTier( GetAdvertisedNetworkSubnetsPassACLdbIDs(bnc.controllerName, bnc.GetNetworkName(), bnc.GetNetworkID()), types.AdvertisedNetworkPassPriority, strings.Join(passMatches, " || "), diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index 2acd2d5a23..452421d289 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -74,7 +74,7 @@ const ( TransitSwitchToRouterPrefix = "tstor-" RouterToTransitSwitchPrefix = "rtots-" - // ACL Default Tier Priorities + // DefaultACLTier Priorities // Default routed multicast allow acl rule priority DefaultRoutedMcastAllowPriority = 1013 @@ -91,7 +91,8 @@ const ( // Deny priority for isolated advertised networks AdvertisedNetworkDenyPriority = 1050 - // ACL PlaceHolderACL Tier Priorities + // PrimaryACLTier Priorities + PrimaryUDNAllowPriority = 1001 // Default deny acl rule priority PrimaryUDNDenyPriority = 1000 @@ -99,8 +100,6 @@ const ( // ACL Tiers // Tier 0 is called Primary as it is evaluated before any other feature-related Tiers. // Currently used for User Defined Network Feature. - // NOTE: When we upgrade from an OVN version without tiers to the new version with - // tiers, all values in the new ACL.Tier column will be set to 0. PrimaryACLTier = 0 // Default Tier for all ACLs DefaultACLTier = 2 From 150775e15930467140bb43a56b71fa6c42e458e1 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 12 Jun 2025 10:19:10 +0200 Subject: [PATCH 034/278] [UDN isolation] Fix ACLs tier: move to the highest-prio Primary tier. Start using new BuildACL for all functions that need non-default tier. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/libovsdb/util/acl.go | 3 +- go-controller/pkg/ovn/udn_isolation.go | 37 ++++++++------ go-controller/pkg/ovn/udn_isolation_test.go | 53 +++++++++++++++++++++ 3 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 go-controller/pkg/ovn/udn_isolation_test.go diff --git a/go-controller/pkg/libovsdb/util/acl.go b/go-controller/pkg/libovsdb/util/acl.go index 798c1b773a..71608aac15 100644 --- a/go-controller/pkg/libovsdb/util/acl.go +++ b/go-controller/pkg/libovsdb/util/acl.go @@ -135,8 +135,7 @@ func BuildACL(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string } func BuildANPACL(dbIDs *libovsdbops.DbObjectIDs, priority int, match, action string, aclT ACLPipelineType, logLevels *ACLLoggingLevels) *nbdb.ACL { - anpACL := BuildACLWithDefaultTier(dbIDs, priority, match, action, logLevels, aclT) - anpACL.Tier = GetACLTier(dbIDs) + anpACL := BuildACL(dbIDs, priority, match, action, logLevels, aclT, GetACLTier(dbIDs)) return anpACL } diff --git a/go-controller/pkg/ovn/udn_isolation.go b/go-controller/pkg/ovn/udn_isolation.go index 98c716d6cc..0a69592aa3 100644 --- a/go-controller/pkg/ovn/udn_isolation.go +++ b/go-controller/pkg/ovn/udn_isolation.go @@ -30,6 +30,8 @@ const ( DenySecondaryACL = "DenySecondary" // OpenPortACLPrefix is used to build per-pod ACLs, pod name should be added to the prefix to build a unique name OpenPortACLPrefix = "OpenPort-" + // the same tier is used for all UDN isolation ACLs + isolationTier = types.PrimaryACLTier ) // setupUDNACLs should be called after the node's management port was configured @@ -63,7 +65,8 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { pgName := libovsdbutil.GetPortGroupName(pgIDs) egressDenyIDs := oc.getUDNACLDbIDs(DenySecondaryACL, libovsdbutil.ACLEgress) match := libovsdbutil.GetACLMatch(pgName, "", libovsdbutil.ACLEgress) - egressDenyACL := libovsdbutil.BuildACLWithDefaultTier(egressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportEgress) + egressDenyACL := libovsdbutil.BuildACL(egressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, + nil, libovsdbutil.LportEgress, isolationTier) getARPMatch := func(direction libovsdbutil.ACLDirection) string { match := "(" @@ -89,15 +92,18 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { egressARPIDs := oc.getUDNACLDbIDs(AllowHostARPACL, libovsdbutil.ACLEgress) match = libovsdbutil.GetACLMatch(pgName, getARPMatch(libovsdbutil.ACLEgress), libovsdbutil.ACLEgress) - egressARPACL := libovsdbutil.BuildACLWithDefaultTier(egressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress) + egressARPACL := libovsdbutil.BuildACL(egressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, + nil, libovsdbutil.LportEgress, isolationTier) ingressDenyIDs := oc.getUDNACLDbIDs(DenySecondaryACL, libovsdbutil.ACLIngress) match = libovsdbutil.GetACLMatch(pgName, "", libovsdbutil.ACLIngress) - ingressDenyACL := libovsdbutil.BuildACLWithDefaultTier(ingressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, nil, libovsdbutil.LportIngress) + ingressDenyACL := libovsdbutil.BuildACL(ingressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, + nil, libovsdbutil.LportIngress, isolationTier) ingressARPIDs := oc.getUDNACLDbIDs(AllowHostARPACL, libovsdbutil.ACLIngress) match = libovsdbutil.GetACLMatch(pgName, getARPMatch(libovsdbutil.ACLIngress), libovsdbutil.ACLIngress) - ingressARPACL := libovsdbutil.BuildACLWithDefaultTier(ingressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, nil, libovsdbutil.LportIngress) + ingressARPACL := libovsdbutil.BuildACL(ingressARPIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllow, + nil, libovsdbutil.LportIngress, isolationTier) ingressAllowIDs := oc.getUDNACLDbIDs(AllowHostSecondaryACL, libovsdbutil.ACLIngress) match = "(" @@ -114,7 +120,8 @@ func (oc *DefaultNetworkController) setupUDNACLs(mgmtPortIPs []net.IP) error { } match += ")" match = libovsdbutil.GetACLMatch(pgName, match, libovsdbutil.ACLIngress) - ingressAllowACL := libovsdbutil.BuildACLWithDefaultTier(ingressAllowIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) + ingressAllowACL := libovsdbutil.BuildACL(ingressAllowIDs, types.PrimaryUDNAllowPriority, match, nbdb.ACLActionAllowRelated, + nil, libovsdbutil.LportIngress, isolationTier) ops, err := libovsdbops.CreateOrUpdateACLsOps(oc.nbClient, nil, oc.GetSamplingConfig(), egressDenyACL, egressARPACL, ingressARPACL, ingressDenyACL, ingressAllowACL) if err != nil { @@ -199,12 +206,12 @@ func (oc *DefaultNetworkController) setUDNPodOpenPortsOps(podNamespacedName stri ingressMatch, egressMatch, parseErr := getPortsMatches(podAnnotations, lspName) // don't return on parseErr, as we need to cleanup potentially present ACLs from the previous config ingressIDs := oc.getUDNOpenPortDbIDs(podNamespacedName, libovsdbutil.ACLIngress) - ingressACL := libovsdbutil.BuildACLWithDefaultTier(ingressIDs, types.PrimaryUDNAllowPriority, - ingressMatch, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress) + ingressACL := libovsdbutil.BuildACL(ingressIDs, types.PrimaryUDNAllowPriority, + ingressMatch, nbdb.ACLActionAllowRelated, nil, libovsdbutil.LportIngress, isolationTier) egressIDs := oc.getUDNOpenPortDbIDs(podNamespacedName, libovsdbutil.ACLEgress) - egressACL := libovsdbutil.BuildACLWithDefaultTier(egressIDs, types.PrimaryUDNAllowPriority, - egressMatch, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress) + egressACL := libovsdbutil.BuildACL(egressIDs, types.PrimaryUDNAllowPriority, + egressMatch, nbdb.ACLActionAllow, nil, libovsdbutil.LportEgress, isolationTier) var err error if ingressMatch == "" && egressMatch == "" || parseErr != nil { @@ -282,14 +289,14 @@ func BuildAdvertisedNetworkSubnetsDropACL(advertisedNetworkSubnetsAddressSet add dropMatches = append(dropMatches, fmt.Sprintf("(ip6.src == $%s && ip6.dst == $%s)", v6AddrSet, v6AddrSet)) } - dropACL := libovsdbutil.BuildACLWithDefaultTier( + dropACL := libovsdbutil.BuildACL( GetAdvertisedNetworkSubnetsDropACLdbIDs(), types.AdvertisedNetworkDenyPriority, strings.Join(dropMatches, " || "), nbdb.ACLActionDrop, nil, - libovsdbutil.LportEgressAfterLB) - dropACL.Tier = types.PrimaryACLTier + libovsdbutil.LportEgressAfterLB, + isolationTier) return dropACL } @@ -325,14 +332,14 @@ func (bnc *BaseNetworkController) addAdvertisedNetworkIsolation(nodeName string) ops = append(ops, addrOps...) if len(passMatches) > 0 { - passACL := libovsdbutil.BuildACLWithDefaultTier( + passACL := libovsdbutil.BuildACL( GetAdvertisedNetworkSubnetsPassACLdbIDs(bnc.controllerName, bnc.GetNetworkName(), bnc.GetNetworkID()), types.AdvertisedNetworkPassPriority, strings.Join(passMatches, " || "), nbdb.ACLActionPass, nil, - libovsdbutil.LportEgressAfterLB) - passACL.Tier = types.PrimaryACLTier + libovsdbutil.LportEgressAfterLB, + isolationTier) ops, err = libovsdbops.CreateOrUpdateACLsOps(bnc.nbClient, ops, nil, passACL) if err != nil { diff --git a/go-controller/pkg/ovn/udn_isolation_test.go b/go-controller/pkg/ovn/udn_isolation_test.go new file mode 100644 index 0000000000..2b3afda328 --- /dev/null +++ b/go-controller/pkg/ovn/udn_isolation_test.go @@ -0,0 +1,53 @@ +package ovn + +import ( + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" + libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" + libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("UDN Isolation", func() { + BeforeEach(func() { + Expect(config.PrepareTestConfig()).To(Succeed()) + }) + + It("ACLs should be updated to the Primary tier ", func() { + config.OVNKubernetesFeature.EnableMultiNetwork = true + config.OVNKubernetesFeature.EnableNetworkSegmentation = true + fakeController := getFakeController(DefaultNetworkControllerName) + + // build port group with one ACL that has default tier + pgIDs := fakeController.getSecondaryPodsPortGroupDbIDs() + pgName := libovsdbutil.GetPortGroupName(pgIDs) + egressDenyIDs := fakeController.getUDNACLDbIDs(DenySecondaryACL, libovsdbutil.ACLEgress) + match := libovsdbutil.GetACLMatch(pgName, "", libovsdbutil.ACLEgress) + // in the real code we use BuildACL here instead of BuildACLWithDefaultTier + egressDenyACL := libovsdbutil.BuildACLWithDefaultTier(egressDenyIDs, types.PrimaryUDNDenyPriority, match, nbdb.ACLActionDrop, + nil, libovsdbutil.LportEgress) + // required to make sure port group correctly references the ACL + egressDenyACL.UUID = egressDenyIDs.String() + "-UUID" + pg := libovsdbutil.BuildPortGroup(pgIDs, nil, []*nbdb.ACL{egressDenyACL}) + + nbClient, nbCleanup, err := libovsdbtest.NewNBTestHarness(libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{egressDenyACL, pg}, + }, nil) + Expect(err).NotTo(HaveOccurred()) + defer nbCleanup.Cleanup() + fakeController.nbClient = nbClient + + // now run the setupUDNACLs function which should create all ACLs and update the existing ACLs to the Primary tier + Expect(fakeController.setupUDNACLs(nil)).To(Succeed()) + + // verify that the egressDenyACL is updated to the Primary 0 + acls, err := libovsdbops.FindACLs(nbClient, []*nbdb.ACL{egressDenyACL}) + Expect(err).NotTo(HaveOccurred()) + Expect(acls).To(HaveLen(1)) + Expect(acls[0].Tier).To(Equal(types.PrimaryACLTier)) + }) +}) From 228d44402b2163074e3279e5e8080a9fdf8aa7d8 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Fri, 16 May 2025 04:36:39 +0100 Subject: [PATCH 035/278] GH VM: remove more items after disk space limit reached Prune volumes Delete swap file Signed-off-by: Martin Kennelly --- .github/workflows/test.yml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index faf2754076..67eb711b23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -312,6 +312,7 @@ jobs: - name: Free up disk space run: | + df -h sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ @@ -320,6 +321,17 @@ jobs: llvm-* microsoft-edge-stable mono-* \ msbuild mysql-server-core-* php-* php7* \ powershell temurin-* zulu-* + # clean unused packages + sudo apt-get autoclean + sudo apt-get autoremove -y + # clean apt cache + sudo apt-get clean + sudo docker system prune -af --volumes + df -h + sudo swapon --show + sudo swapoff -a + sudo rm -f /mnt/swapfile + df -h - name: Download test-image-master uses: actions/download-artifact@v4 @@ -503,6 +515,7 @@ jobs: - name: Free up disk space run: | + df -h sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ @@ -511,7 +524,17 @@ jobs: llvm-* microsoft-edge-stable mono-* \ msbuild mysql-server-core-* php-* php7* \ powershell temurin-* zulu-* - sudo docker system prune -af + # clean unused packages + sudo apt-get autoclean + sudo apt-get autoremove -y + # clean apt cache + sudo apt-get clean + sudo docker system prune -af --volumes + df -h + sudo swapon --show + sudo swapoff -a + sudo rm -f /mnt/swapfile + df -h - name: Setup /mnt/runner directory run: | @@ -723,6 +746,7 @@ jobs: - name: Free up disk space run: | + df -h sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ @@ -731,6 +755,17 @@ jobs: llvm-* microsoft-edge-stable mono-* \ msbuild mysql-server-core-* php-* php7* \ powershell temurin-* zulu-* + # clean unused packages + sudo apt-get autoclean + sudo apt-get autoremove -y + # clean apt cache + sudo apt-get clean + sudo docker system prune -af --volumes + df -h + sudo swapon --show + sudo swapoff -a + sudo rm -f /mnt/swapfile + df -h - name: Disable ufw # For IPv6 and Dualstack, ufw (Uncomplicated Firewall) should be disabled. From deff5e64ccc6069437bf7abf08f61522f73501a1 Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Fri, 6 Jun 2025 05:24:48 +0000 Subject: [PATCH 036/278] Add the IP rule for a UDN only when it is advertised to the default VRF When an UDN is advertised to a non default VRF, we shall not add the ip rule to the default VRF. Otherwise if another UDN is advertised to the default VRF with the same subnet. The ingress traffic intended for the second UDN cannot be correctly routed to its respective VRF. Signed-off-by: Peng Liu --- go-controller/pkg/node/gateway_udn.go | 194 ++++++++++++-------- go-controller/pkg/node/gateway_udn_test.go | 200 ++++++++++++++++++++- 2 files changed, 315 insertions(+), 79 deletions(-) diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 7b755806fd..b207a4f009 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -89,6 +89,10 @@ type UserDefinedNetworkGateway struct { // gwInterfaceIndex holds the link index of gateway interface gwInterfaceIndex int + + // save BGP state at the start of reconciliation loop run to handle it consistently throughout the run + isNetworkAdvertisedToDefaultVRF bool + isNetworkAdvertised bool } // UTILS Needed for UDN (also leveraged for default netInfo) in bridgeConfiguration @@ -366,18 +370,18 @@ func (udng *UserDefinedNetworkGateway) AddNetwork() error { return fmt.Errorf("could not add VRF %s routes for network %s, err: %v", vrfDeviceName, udng.GetNetworkName(), err) } - isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) + udng.updateAdvertisementStatus() // create the iprules for this network - if err = udng.updateUDNVRFIPRules(isNetworkAdvertised); err != nil { + if err = udng.updateUDNVRFIPRules(); err != nil { return fmt.Errorf("failed to update IP rules for network %s: %w", udng.GetNetworkName(), err) } - if err = udng.updateAdvertisedUDNIsolationRules(isNetworkAdvertised); err != nil { + if err = udng.updateAdvertisedUDNIsolationRules(); err != nil { return fmt.Errorf("failed to update isolation rules for network %s: %w", udng.GetNetworkName(), err) } - if err := udng.updateUDNVRFIPRoute(isNetworkAdvertised); err != nil { + if err := udng.updateUDNVRFIPRoute(); err != nil { return fmt.Errorf("failed to update ip routes for network %s: %w", udng.GetNetworkName(), err) } @@ -455,18 +459,16 @@ func (udng *UserDefinedNetworkGateway) DelNetwork() error { } } - if util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) { - err := udng.updateAdvertisedUDNIsolationRules(false) - if err != nil { - return fmt.Errorf("failed to remove advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) - } + err := udng.deleteAdvertisedUDNIsolationRules() + if err != nil { + return fmt.Errorf("failed to remove advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) } if err := udng.delMarkChain(); err != nil { return err } // delete the management port interface for this network - err := udng.deleteUDNManagementPort() + err = udng.deleteUDNManagementPort() if err != nil { return err } @@ -622,8 +624,7 @@ func (udng *UserDefinedNetworkGateway) computeRoutesForUDN(mpLink netlink.Link) // Route2: Add default route: default via 172.18.0.1 dev breth0 mtu 1400 // necessary for UDN CNI and host-networked pods default traffic to go to node's gatewayIP - isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) - defaultRoute, err := udng.getDefaultRoute(isNetworkAdvertised) + defaultRoute, err := udng.getDefaultRouteWithAdvertisedCheck() if err != nil { return nil, fmt.Errorf("unable to add default route for network %s, err: %v", udng.GetNetworkName(), err) } @@ -724,15 +725,7 @@ func (udng *UserDefinedNetworkGateway) computeRoutesForUDN(mpLink netlink.Link) return retVal, nil } -func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) ([]netlink.Route, error) { - vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) - // If the network is advertised on a non default VRF then we should only consider routes received from external BGP - // device and not send any traffic based on default route similar to one present in default VRF. This is more important - // for VRF-Lite usecase where we need traffic to leave from vlan device instead of default gateway interface. - if isNetworkAdvertised && !slices.Contains(vrfs, types.DefaultNetworkName) { - return nil, nil - } - +func (udng *UserDefinedNetworkGateway) getDefaultRoute() ([]netlink.Route, error) { networkMTU := udng.NetInfo.MTU() if networkMTU == 0 { networkMTU = config.Default.MTU @@ -757,6 +750,16 @@ func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) return retVal, nil } +func (udng *UserDefinedNetworkGateway) getDefaultRouteWithAdvertisedCheck() ([]netlink.Route, error) { + // If the network is advertised on a non default VRF then we should only consider routes received from external BGP + // device and not send any traffic based on default route similar to one present in default VRF. This is more important + // for VRF-Lite usecase where we need traffic to leave from vlan device instead of default gateway interface. + if udng.isNetworkAdvertised && !udng.isNetworkAdvertisedToDefaultVRF { + return nil, nil + } + return udng.getDefaultRoute() +} + // getV4MasqueradeIP returns the V4 management port masqueradeIP for this network func (udng *UserDefinedNetworkGateway) getV4MasqueradeIP() (*net.IPNet, error) { if !config.IPv4Mode { @@ -789,12 +792,15 @@ func (udng *UserDefinedNetworkGateway) getV6MasqueradeIP() (*net.IPNet, error) { // 2000: from all to 169.254.0.12 lookup 1007 // 2000: from all fwmark 0x1002 lookup 1009 // 2000: from all to 169.254.0.14 lookup 1009 -// If the network is advertised, an example of the rules we set for a network is: +// If the network is advertised to the default VRF, an example of the rules we set for a network is: // 2000: from all fwmark 0x1001 lookup 1007 // 2000: from all to 10.132.0.0/14 lookup 1007 // 2000: from all fwmark 0x1001 lookup 1009 // 2000: from all to 10.134.0.0/14 lookup 1009 -func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules(isNetworkAdvertised bool) ([]netlink.Rule, []netlink.Rule, error) { +// If the network is advertised ot a non-default VRF, an example of the rules we set for a network is: +// 2000: from all fwmark 0x1001 lookup 1007 +// 2000: from all fwmark 0x1001 lookup 1009 +func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules() ([]netlink.Rule, []netlink.Rule, error) { var addIPRules []netlink.Rule var delIPRules []netlink.Rule var masqIPRules []netlink.Rule @@ -827,12 +833,18 @@ func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules(isNetworkAdvertise } } switch { - case !isNetworkAdvertised: - addIPRules = append(addIPRules, masqIPRules...) - delIPRules = append(delIPRules, subnetIPRules...) - default: + case udng.isNetworkAdvertisedToDefaultVRF: + // the network is advertised to the default VRF + delIPRules = append(delIPRules, masqIPRules...) addIPRules = append(addIPRules, subnetIPRules...) + case udng.isNetworkAdvertised: + // the network is advertised to a non-default VRF delIPRules = append(delIPRules, masqIPRules...) + delIPRules = append(delIPRules, subnetIPRules...) + default: + // the network is not advertised + delIPRules = append(delIPRules, subnetIPRules...) + addIPRules = append(addIPRules, masqIPRules...) } return addIPRules, delIPRules, nil } @@ -928,19 +940,20 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { return fmt.Errorf("openflow manager with default bridge configuration has not been provided for network %s", udng.GetNetworkName()) } + udng.updateAdvertisementStatus() + // update bridge configuration - isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) netConfig := udng.openflowManager.defaultBridge.getNetworkBridgeConfig(udng.GetNetworkName()) if netConfig == nil { return fmt.Errorf("missing bridge configuration for network %s", udng.GetNetworkName()) } - netConfig.advertised.Store(isNetworkAdvertised) + netConfig.advertised.Store(udng.isNetworkAdvertised) - if err := udng.updateUDNVRFIPRules(isNetworkAdvertised); err != nil { + if err := udng.updateUDNVRFIPRules(); err != nil { return fmt.Errorf("error while updating ip rule for UDN %s: %s", udng.GetNetworkName(), err) } - if err := udng.updateUDNVRFIPRoute(isNetworkAdvertised); err != nil { + if err := udng.updateUDNVRFIPRoute(); err != nil { return fmt.Errorf("error while updating ip route for UDN %s: %s", udng.GetNetworkName(), err) } @@ -954,16 +967,16 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { // let's sync these flows immediately udng.openflowManager.requestFlowSync() - if err := udng.updateAdvertisedUDNIsolationRules(isNetworkAdvertised); err != nil { + if err := udng.updateAdvertisedUDNIsolationRules(); err != nil { return fmt.Errorf("error while updating advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) } return nil } // updateUDNVRFIPRules updates IP rules for a network depending on whether the -// network is advertised or not -func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules(isNetworkAdvertised bool) error { - addIPRules, deleteIPRules, err := udng.constructUDNVRFIPRules(isNetworkAdvertised) +// network is advertised to the default VRF or not +func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules() error { + addIPRules, deleteIPRules, err := udng.constructUDNVRFIPRules() if err != nil { return fmt.Errorf("unable to get iprules for network %s, err: %v", udng.GetNetworkName(), err) } @@ -982,30 +995,40 @@ func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules(isNetworkAdvertised b } // Add or remove default route from a vrf device based on the network is -// advertised on its own network or default network -func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRoute(isNetworkAdvertised bool) error { - vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) - if isNetworkAdvertised && !slices.Contains(vrfs, types.DefaultNetworkName) { +// advertised on its own network or the default network +func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRoute() error { + vrfName := util.GetNetworkVRFName(udng.NetInfo) + + switch { + case udng.isNetworkAdvertised && !udng.isNetworkAdvertisedToDefaultVRF: + // Remove default route for networks advertised to non-default VRF if err := udng.removeDefaultRouteFromVRF(); err != nil { - return fmt.Errorf("error while removing default route from VRF %s corresponding to network %s: %s", - util.GetNetworkVRFName(udng.NetInfo), udng.GetNetworkName(), err) + return fmt.Errorf("failed to remove default route from VRF %s for network %s: %v", + vrfName, udng.GetNetworkName(), err) } - } else if !isNetworkAdvertised || slices.Contains(vrfs, types.DefaultNetworkName) { - defaultRoute, err := udng.getDefaultRoute(isNetworkAdvertised) + + default: + // Add default route for networks that are either: + // - not advertised + // - advertised to default VRF + defaultRoute, err := udng.getDefaultRouteWithAdvertisedCheck() if err != nil { - return fmt.Errorf("unable to get default route for network %s, err: %v", udng.GetNetworkName(), err) + return fmt.Errorf("failed to get default route for network %s: %v", + udng.GetNetworkName(), err) } - if err = udng.vrfManager.AddVRFRoutes(util.GetNetworkVRFName(udng.NetInfo), defaultRoute); err != nil { - return fmt.Errorf("error while adding default route to VRF %s corresponding to network %s, err: %v", - util.GetNetworkVRFName(udng.NetInfo), udng.GetNetworkName(), err) + + if err = udng.vrfManager.AddVRFRoutes(vrfName, defaultRoute); err != nil { + return fmt.Errorf("failed to add default route to VRF %s for network %s: %v", + vrfName, udng.GetNetworkName(), err) } } + return nil } func (udng *UserDefinedNetworkGateway) removeDefaultRouteFromVRF() error { vrfDeviceName := util.GetNetworkVRFName(udng.NetInfo) - defaultRoute, err := udng.getDefaultRoute(false) + defaultRoute, err := udng.getDefaultRoute() if err != nil { return fmt.Errorf("unable to get default route for network %s, err: %v", udng.GetNetworkName(), err) } @@ -1034,39 +1057,22 @@ func (udng *UserDefinedNetworkGateway) removeDefaultRouteFromVRF() error { // comment "advertised UDNs V4 subnets" // elements = { 10.10.0.0/16 comment "cluster_udn_l3network" } // } -func (udng *UserDefinedNetworkGateway) updateAdvertisedUDNIsolationRules(isNetworkAdvertised bool) error { +func (udng *UserDefinedNetworkGateway) updateAdvertisedUDNIsolationRules() error { + switch { + case udng.isNetworkAdvertised: + return udng.addAdvertisedUDNIsolationRules() + default: + return udng.deleteAdvertisedUDNIsolationRules() + } +} + +func (udng *UserDefinedNetworkGateway) addAdvertisedUDNIsolationRules() error { nft, err := nodenft.GetNFTablesHelper() if err != nil { return fmt.Errorf("failed to get nftables helper: %v", err) } tx := nft.NewTransaction() - if !isNetworkAdvertised { - existingV4, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) - if err != nil { - if !knftables.IsNotFound(err) { - return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV4, err) - } - } - existingV6, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV6) - if err != nil { - if !knftables.IsNotFound(err) { - return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV6, err) - } - } - - for _, elem := range append(existingV4, existingV6...) { - if elem.Comment != nil && *elem.Comment == udng.GetNetworkName() { - tx.Delete(elem) - } - } - - if tx.NumOperations() == 0 { - return nil - } - return nft.Run(context.TODO(), tx) - } - for _, udnNet := range udng.Subnets() { set := nftablesAdvertisedUDNsSetV4 if utilnet.IsIPv6CIDR(udnNet.CIDR) { @@ -1085,3 +1091,41 @@ func (udng *UserDefinedNetworkGateway) updateAdvertisedUDNIsolationRules(isNetwo } return nft.Run(context.TODO(), tx) } + +func (udng *UserDefinedNetworkGateway) deleteAdvertisedUDNIsolationRules() error { + nft, err := nodenft.GetNFTablesHelper() + if err != nil { + return fmt.Errorf("failed to get nftables helper: %v", err) + } + tx := nft.NewTransaction() + + existingV4, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) + if err != nil { + if !knftables.IsNotFound(err) { + return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV4, err) + } + } + existingV6, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV6) + if err != nil { + if !knftables.IsNotFound(err) { + return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV6, err) + } + } + + for _, elem := range append(existingV4, existingV6...) { + if elem.Comment != nil && *elem.Comment == udng.GetNetworkName() { + tx.Delete(elem) + } + } + + if tx.NumOperations() == 0 { + return nil + } + return nft.Run(context.TODO(), tx) +} + +func (udng *UserDefinedNetworkGateway) updateAdvertisementStatus() { + vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) + udng.isNetworkAdvertised = len(vrfs) > 0 + udng.isNetworkAdvertisedToDefaultVRF = slices.Contains(vrfs, types.DefaultNetworkName) +} diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 1227163480..ac964dfeec 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -1754,7 +1754,7 @@ func TestConstructUDNVRFIPRules(t *testing.T) { }) g.Expect(err).NotTo(HaveOccurred()) udnGateway.vrfTableId = test.vrftableID - rules, delRules, err := udnGateway.constructUDNVRFIPRules(false) + rules, delRules, err := udnGateway.constructUDNVRFIPRules() g.Expect(err).ToNot(HaveOccurred()) for i, rule := range rules { g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) @@ -1776,7 +1776,7 @@ func TestConstructUDNVRFIPRules(t *testing.T) { } } -func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { +func TestConstructUDNVRFIPRulesPodNetworkAdvertisedToTheDefaultNetwork(t *testing.T) { type testRule struct { priority int family int @@ -1941,7 +1941,198 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { }) g.Expect(err).NotTo(HaveOccurred()) udnGateway.vrfTableId = test.vrftableID - rules, delRules, err := udnGateway.constructUDNVRFIPRules(true) + udnGateway.isNetworkAdvertised = true + udnGateway.isNetworkAdvertisedToDefaultVRF = true + rules, delRules, err := udnGateway.constructUDNVRFIPRules() + g.Expect(err).ToNot(HaveOccurred()) + for i, rule := range rules { + g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) + g.Expect(rule.Table).To(Equal(test.expectedRules[i].table)) + g.Expect(rule.Family).To(Equal(test.expectedRules[i].family)) + if rule.Dst != nil { + g.Expect(*rule.Dst).To(Equal(test.expectedRules[i].dst)) + } else { + g.Expect(rule.Mark).To(Equal(test.expectedRules[i].mark)) + } + } + for i, rule := range delRules { + g.Expect(rule.Priority).To(Equal(test.deleteRules[i].priority)) + g.Expect(rule.Table).To(Equal(test.deleteRules[i].table)) + g.Expect(rule.Family).To(Equal(test.deleteRules[i].family)) + g.Expect(*rule.Dst).To(Equal(test.deleteRules[i].dst)) + } + }) + } +} + +func TestConstructUDNVRFIPRulesPodNetworkAdvertisedToNoneDefaultNetwork(t *testing.T) { + type testRule struct { + priority int + family int + table int + mark uint32 + dst net.IPNet + } + type testConfig struct { + desc string + vrftableID int + v4mode bool + v6mode bool + expectedRules []testRule + deleteRules []testRule + } + + tests := []testConfig{ + { + desc: "v4 rule test", + vrftableID: 1007, + expectedRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1007, + mark: 0x1003, + }, + }, + deleteRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1007, + dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("169.254.0.16")), + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1007, + dst: *ovntest.MustParseIPNet("100.128.0.0/16"), + }, + }, + v4mode: true, + }, + { + desc: "v6 rule test", + vrftableID: 1009, + expectedRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1009, + mark: 0x1003, + }, + }, + deleteRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1009, + dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("fd69::10")), + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1009, + dst: *ovntest.MustParseIPNet("ae70::/60"), + }, + }, + v6mode: true, + }, + { + desc: "dualstack rule test", + vrftableID: 1010, + expectedRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1010, + mark: 0x1003, + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1010, + mark: 0x1003, + }, + }, + deleteRules: []testRule{ + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1010, + dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("169.254.0.16")), + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1010, + dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("fd69::10")), + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V4, + table: 1010, + dst: *ovntest.MustParseIPNet("100.128.0.0/16"), + }, + { + priority: UDNMasqueradeIPRulePriority, + family: netlink.FAMILY_V6, + table: 1010, + dst: *ovntest.MustParseIPNet("ae70::/60"), + }, + }, + v4mode: true, + v6mode: true, + }, + } + config.Gateway.V6MasqueradeSubnet = "fd69::/112" + config.Gateway.V4MasqueradeSubnet = "169.254.0.0/16" + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + g := NewWithT(t) + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + } + config.IPv4Mode = test.v4mode + config.IPv6Mode = test.v6mode + cidr := "" + if config.IPv4Mode { + cidr = "100.128.0.0/16/24" + } + if config.IPv4Mode && config.IPv6Mode { + cidr += ",ae70::/60" + } else if config.IPv6Mode { + cidr = "ae70::/60" + } + nad := ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", + types.Layer3Topology, cidr, types.NetworkRolePrimary) + ovntest.AnnotateNADWithNetworkID("3", nad) + netInfo, err := util.ParseNADInfo(nad) + g.Expect(err).ToNot(HaveOccurred()) + mutableNetInfo := util.NewMutableNetInfo(netInfo) + mutableNetInfo.SetPodNetworkAdvertisedVRFs(map[string][]string{node.Name: {"bluenet"}}) + ofm := getDummyOpenflowManager() + // create dummy gateway interface(Need to run this test as root) + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: "breth0", + }, + }) + g.Expect(err).NotTo(HaveOccurred()) + udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, nil, nil, &gateway{openflowManager: ofm}) + g.Expect(err).NotTo(HaveOccurred()) + // delete dummy gateway interface after creating UDN gateway(Need to run this test as root) + err = netlink.LinkDel(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: "breth0", + }, + }) + g.Expect(err).NotTo(HaveOccurred()) + udnGateway.vrfTableId = test.vrftableID + udnGateway.isNetworkAdvertised = true + udnGateway.isNetworkAdvertisedToDefaultVRF = false + rules, delRules, err := udnGateway.constructUDNVRFIPRules() g.Expect(err).ToNot(HaveOccurred()) for i, rule := range rules { g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) @@ -2072,7 +2263,8 @@ func TestUserDefinedNetworkGateway_updateAdvertisedUDNIsolationRules(t *testing. udng := &UserDefinedNetworkGateway{ NetInfo: netInfo, } - err = udng.updateAdvertisedUDNIsolationRules(tt.isNetworkAdvertised) + udng.isNetworkAdvertised = tt.isNetworkAdvertised + err = udng.updateAdvertisedUDNIsolationRules() g.Expect(err).NotTo(HaveOccurred()) v4Elems, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) From 424a653f38514940a99fbfe528b587fdafd2e687 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Fri, 13 Jun 2025 11:24:28 +0200 Subject: [PATCH 037/278] NodeTracker: Only update the node if the subnet changed for the specific network Previously every update `k8s.ovn.org/node-subnets` caused a call to `nt.updateNode` on every network. Signed-off-by: Patryk Diak --- go-controller/pkg/ovn/controller/services/node_tracker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go-controller/pkg/ovn/controller/services/node_tracker.go b/go-controller/pkg/ovn/controller/services/node_tracker.go index 0ee0997eda..9fecf577c1 100644 --- a/go-controller/pkg/ovn/controller/services/node_tracker.go +++ b/go-controller/pkg/ovn/controller/services/node_tracker.go @@ -119,7 +119,7 @@ func (nt *nodeTracker) Start(nodeInformer coreinformers.NodeInformer) (cache.Res // - node changes its zone // - node becomes a hybrid overlay node from a ovn node or vice verse // . No need to trigger update for any other field change. - if util.NodeSubnetAnnotationChanged(oldObj, newObj) || + if util.NodeSubnetAnnotationChangedForNetwork(oldObj, newObj, nt.netInfo.GetNetworkName()) || util.NodeL3GatewayAnnotationChanged(oldObj, newObj) || oldObj.Name != newObj.Name || util.NodeHostCIDRsAnnotationChanged(oldObj, newObj) || @@ -169,7 +169,7 @@ func (nt *nodeTracker) updateNodeInfo(nodeName, switchName, routerName, chassisI ni.podSubnets = append(ni.podSubnets, *podSubnets[i]) // de-pointer } - klog.Infof("Node %s switch + router changed, syncing services", nodeName) + klog.Infof("Node %s switch + router changed, syncing services in network %q", nodeName, nt.netInfo.GetNetworkName()) nt.Lock() defer nt.Unlock() @@ -208,7 +208,7 @@ func (nt *nodeTracker) removeNode(nodeName string) { // The switch exists when the HostSubnet annotation is set. // The gateway router will exist sometime after the L3Gateway annotation is set. func (nt *nodeTracker) updateNode(node *corev1.Node) { - klog.V(2).Infof("Processing possible switch / router updates for node %s", node.Name) + klog.V(2).Infof("Processing possible switch / router updates for node %s in network %q", node.Name, nt.netInfo.GetNetworkName()) var hsn []*net.IPNet var err error if nt.netInfo.TopologyType() == types.Layer2Topology { From 1fb898316de8119429412f0f7c2e8d44c0d3a864 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Mon, 16 Jun 2025 10:33:02 +0200 Subject: [PATCH 038/278] [e2e] kubevirt: generate test name that is compatible with file path. When collecting the logs after a failed test run, test name is used as a part of the file path, and double quotes are not allowed: ``` The following characters are not allowed in files that are uploaded due to limitations with certain file systems such as NTFS. To maintain file system agnostic behavior, these characters are intentionally not allowed to prevent potential problems with downloads on different file systems. ``` Signed-off-by: Nadia Pinaeva --- test/e2e/kubevirt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index aa0a6a246c..9587e520e9 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1947,7 +1947,7 @@ ip route add %[3]s via %[4]s if td.ingress != "" { ingress = td.ingress } - return fmt.Sprintf("after %s of %s with %s/%s with %q ingress", td.test.description, td.resource.description, role, td.topology, ingress) + return fmt.Sprintf("after %s of %s with %s/%s with %s ingress", td.test.description, td.resource.description, role, td.topology, ingress) }, Entry(nil, testData{ resource: virtualMachine, From 337cc559946a1ac129af0ba2ec58e06b826f88c9 Mon Sep 17 00:00:00 2001 From: AOS Automation Release Team Date: Wed, 18 Jun 2025 05:46:22 +0000 Subject: [PATCH 039/278] Updating ose-ovn-kubernetes-base-container image to be consistent with ART for 4.20 Reconciling with https://github.com/openshift/ocp-build-data/tree/dfb5c7d531490cfdc61a3b88bc533702b9624997/images/ovn-kubernetes-base.yml --- .ci-operator.yaml | 2 +- Dockerfile.base | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 7c15f83e3e..461415cbc5 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: release namespace: openshift - tag: rhel-9-release-golang-1.23-openshift-4.19 + tag: rhel-9-release-golang-1.24-openshift-4.20 diff --git a/Dockerfile.base b/Dockerfile.base index 4f551a411d..071f6e6a01 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -5,7 +5,7 @@ # The standard name for this image is ovn-kubernetes-base # build base image shared by both OpenShift and MicroShift -FROM registry.ci.openshift.org/ocp/4.19:base-rhel9 +FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 # install selinux-policy first to avoid a race RUN dnf --setopt=retries=2 --setopt=timeout=2 install -y --nodocs \ From 70fe56c65704da7377742d9e6de00e6d94e13754 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Tue, 4 Mar 2025 10:57:19 +0000 Subject: [PATCH 040/278] Add labels to filter e2e tests Signed-off-by: Martin Kennelly --- test/e2e/acl_logging.go | 8 +- test/e2e/e2e_suite_test.go | 27 +--- test/e2e/egress_firewall.go | 3 +- test/e2e/egress_services.go | 3 +- test/e2e/egressip.go | 3 +- test/e2e/egressqos.go | 3 +- test/e2e/external_gateways.go | 3 +- test/e2e/feature/features.go | 31 ++++ test/e2e/gateway_mtu.go | 3 +- test/e2e/kubevirt.go | 3 +- test/e2e/label/component.go | 7 + test/e2e/label/label.go | 42 +++++ test/e2e/label/override.go | 5 + test/e2e/multi_node_zones_interconnect.go | 3 +- test/e2e/multicast.go | 3 +- test/e2e/multihoming.go | 3 +- test/e2e/network_segmentation.go | 3 +- ...work_segmentation_endpointslices_mirror.go | 8 +- test/e2e/network_segmentation_policy.go | 3 +- test/e2e/network_segmentation_services.go | 3 +- test/e2e/node_ip_mac_migration.go | 3 +- test/e2e/ovspinning.go | 3 +- test/e2e/service.go | 7 +- test/e2e/status_manager.go | 3 +- test/e2e/testcontext.go | 143 ++++++++++++++++++ test/e2e/unidling.go | 3 +- 26 files changed, 279 insertions(+), 50 deletions(-) create mode 100644 test/e2e/feature/features.go create mode 100644 test/e2e/label/component.go create mode 100644 test/e2e/label/label.go create mode 100644 test/e2e/label/override.go create mode 100644 test/e2e/testcontext.go diff --git a/test/e2e/acl_logging.go b/test/e2e/acl_logging.go index 0ea81c6f71..c5c129769b 100644 --- a/test/e2e/acl_logging.go +++ b/test/e2e/acl_logging.go @@ -9,6 +9,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" + v1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,7 +27,7 @@ const ( pokeInterval = 1 * time.Second ) -var _ = Describe("ACL Logging for NetworkPolicy", func() { +var _ = Describe("ACL Logging for NetworkPolicy", feature.NetworkPolicy, func() { const ( denyAllPolicyName = "default-deny-all" initialDenyACLSeverity = "alert" @@ -172,7 +174,7 @@ var _ = Describe("ACL Logging for NetworkPolicy", func() { }) }) -var _ = Describe("ACL Logging for AdminNetworkPolicy and BaselineAdminNetworkPolicy", func() { +var _ = Describe("ACL Logging for AdminNetworkPolicy and BaselineAdminNetworkPolicy", feature.AdminNetworkPolicy, feature.BaselineNetworkPolicy, func() { const ( initialDenyACLSeverity = "alert" initialAllowACLSeverity = "notice" @@ -487,7 +489,7 @@ var _ = Describe("ACL Logging for AdminNetworkPolicy and BaselineAdminNetworkPol }) }) -var _ = Describe("ACL Logging for EgressFirewall", func() { +var _ = Describe("ACL Logging for EgressFirewall", feature.EgressFirewall, func() { const ( denyAllPolicyName = "default-deny-all" initialDenyACLSeverity = "alert" diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 0359b3461b..d96b488297 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -13,13 +13,13 @@ import ( "github.com/ovn-org/ovn-kubernetes/test/e2e/diagnostics" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" "github.com/ovn-org/ovn-kubernetes/test/e2e/ipalloc" + "github.com/ovn-org/ovn-kubernetes/test/e2e/label" + clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/klog" "k8s.io/kubernetes/test/e2e/framework" e2econfig "k8s.io/kubernetes/test/e2e/framework/config" - "k8s.io/kubernetes/test/e2e/framework/testfiles" - "k8s.io/kubernetes/test/utils/image" ) // https://github.com/kubernetes/kubernetes/blob/v1.16.4/test/e2e/e2e_test.go#L62 @@ -55,26 +55,7 @@ var _ = ginkgo.BeforeSuite(func() { func TestMain(m *testing.M) { // Register test flags, then parse flags. handleFlags() - - if framework.TestContext.ListImages { - for _, v := range image.GetImageConfigs() { - fmt.Println(v.GetE2EImage()) - } - os.Exit(0) - } - // reset provider to skeleton as Kubernetes test framework expects a supported provider - framework.TestContext.Provider = "skeleton" - framework.AfterReadingAllFlags(&framework.TestContext) - - // TODO: Deprecating repo-root over time... instead just use gobindata_util.go , see #23987. - // Right now it is still needed, for example by - // test/e2e/framework/ingress/ingress_utils.go - // for providing the optional secret.yaml file and by - // test/e2e/framework/util.go for cluster/log-dump. - if framework.TestContext.RepoRoot != "" { - testfiles.AddFileSource(testfiles.RootFileSource{Root: framework.TestContext.RepoRoot}) - } - + ProcessTestContextAndSetupLogging() os.Exit(m.Run()) } @@ -88,5 +69,5 @@ func TestE2E(t *testing.T) { } } gomega.RegisterFailHandler(framework.Fail) - ginkgo.RunSpecs(t, "E2E Suite") + ginkgo.RunSpecs(t, "E2E Suite", label.ComponentName()) } diff --git a/test/e2e/egress_firewall.go b/test/e2e/egress_firewall.go index e5a3f8518a..32974beb1c 100644 --- a/test/e2e/egress_firewall.go +++ b/test/e2e/egress_firewall.go @@ -19,6 +19,7 @@ import ( "github.com/onsi/ginkgo/extensions/table" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,7 +35,7 @@ import ( // Validate the egress firewall policies by applying a policy and verify // that both explicitly allowed traffic and implicitly denied traffic // is properly handled as defined in the crd configuration in the test. -var _ = ginkgo.Describe("e2e egress firewall policy validation", func() { +var _ = ginkgo.Describe("e2e egress firewall policy validation", feature.EgressFirewall, func() { const ( svcname string = "egress-firewall-policy" egressFirewallYamlFile string = "egress-fw.yml" diff --git a/test/e2e/egress_services.go b/test/e2e/egress_services.go index eb9cb38942..2afcb2edc8 100644 --- a/test/e2e/egress_services.go +++ b/test/e2e/egress_services.go @@ -13,6 +13,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" @@ -32,7 +33,7 @@ import ( utilnet "k8s.io/utils/net" ) -var _ = ginkgo.Describe("EgressService", func() { +var _ = ginkgo.Describe("EgressService", feature.EgressService, func() { const ( egressServiceYAML = "egress_service.yaml" externalContainerName = "external-container-for-egress-service" diff --git a/test/e2e/egressip.go b/test/e2e/egressip.go index 162af8fad0..7faad7185e 100644 --- a/test/e2e/egressip.go +++ b/test/e2e/egressip.go @@ -20,6 +20,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -377,7 +378,7 @@ type egressIPs struct { Items []egressIP `json:"items"` } -var _ = ginkgo.DescribeTableSubtree("e2e egress IP validation", func(netConfigParams networkAttachmentConfigParams) { +var _ = ginkgo.DescribeTableSubtree("e2e egress IP validation", feature.EgressIP, func(netConfigParams networkAttachmentConfigParams) { //FIXME: tests for CDN are designed for single stack clusters (IPv4 or IPv6) and must choose a single IP family for dual stack clusters. // Remove this restriction and allow the tests to detect if an IP family support is available. const ( diff --git a/test/e2e/egressqos.go b/test/e2e/egressqos.go index 4f6b282027..0d32a9a514 100644 --- a/test/e2e/egressqos.go +++ b/test/e2e/egressqos.go @@ -10,6 +10,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "golang.org/x/sync/errgroup" v1 "k8s.io/api/core/v1" @@ -19,7 +20,7 @@ import ( e2enode "k8s.io/kubernetes/test/e2e/framework/node" ) -var _ = ginkgo.Describe("e2e EgressQoS validation", func() { +var _ = ginkgo.Describe("e2e EgressQoS validation", feature.EgressQos, func() { const ( egressQoSYaml = "egressqos.yaml" srcPodName = "src-dscp-pod" diff --git a/test/e2e/external_gateways.go b/test/e2e/external_gateways.go index c7bf83d9f9..4a119ae96b 100644 --- a/test/e2e/external_gateways.go +++ b/test/e2e/external_gateways.go @@ -12,6 +12,7 @@ import ( "time" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -74,7 +75,7 @@ type gatewayTestIPs struct { targetIPs []string } -var _ = ginkgo.Describe("External Gateway", func() { +var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { const ( gwTCPPort = 80 diff --git a/test/e2e/feature/features.go b/test/e2e/feature/features.go new file mode 100644 index 0000000000..842b0474e6 --- /dev/null +++ b/test/e2e/feature/features.go @@ -0,0 +1,31 @@ +package feature + +import ( + "github.com/onsi/ginkgo/v2" + "github.com/ovn-org/ovn-kubernetes/test/e2e/label" +) + +var ( + Service = New("Service") + NetworkPolicy = New("NetworkPolicy") + AdminNetworkPolicy = New("AdminNetworkPolicy") + BaselineNetworkPolicy = New("BaselineNetworkPolicy") + NetworkSegmentation = New("NetworkSegmentation") + EgressIP = New("EgressIP") + EgressService = New("EgressService") + EgressFirewall = New("EgressFirewall") + EgressQos = New("EgressQos") + ExternalGateway = New("ExternalGateway") + DisablePacketMTUCheck = New("DisablePacketMTUCheck") + VirtualMachineSupport = New("VirtualMachineSupport") + Interconnect = New("Interconnect") + Multicast = New("Multicast") + MultiHoming = New("MultiHoming") + NodeIPMACMigration = New("NodeIPMACMigration") + OVSCPUPin = New("OVSCPUPin") + Unidle = New("Unidle") +) + +func New(name string) ginkgo.Labels { + return label.New("Feature", name).GinkgoLabel() +} diff --git a/test/e2e/gateway_mtu.go b/test/e2e/gateway_mtu.go index 386ecba5d3..ec3b3b48d9 100644 --- a/test/e2e/gateway_mtu.go +++ b/test/e2e/gateway_mtu.go @@ -5,12 +5,13 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" e2enode "k8s.io/kubernetes/test/e2e/framework/node" ) -var _ = ginkgo.Describe("Check whether gateway-mtu-support annotation on node is set based on disable-pkt-mtu-check value", func() { +var _ = ginkgo.Describe("Check whether gateway-mtu-support annotation on node is set based on disable-pkt-mtu-check value", feature.DisablePacketMTUCheck, func() { var nodes *v1.NodeList f := wrappedTestFramework("gateway-mtu-support") diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 9587e520e9..d6a774ec4d 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -24,6 +24,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" "github.com/ovn-org/ovn-kubernetes/test/e2e/diagnostics" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -97,7 +98,7 @@ func newControllerRuntimeClient() (crclient.Client, error) { }) } -var _ = Describe("Kubevirt Virtual Machines", func() { +var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, func() { var ( fr = wrappedTestFramework("kv-live-migration") d = diagnostics.New(fr) diff --git a/test/e2e/label/component.go b/test/e2e/label/component.go new file mode 100644 index 0000000000..59e61165c5 --- /dev/null +++ b/test/e2e/label/component.go @@ -0,0 +1,7 @@ +package label + +import "github.com/onsi/ginkgo/v2" + +func ComponentName() ginkgo.Labels { + return NewComponent("ovn-kubernetes") +} diff --git a/test/e2e/label/label.go b/test/e2e/label/label.go new file mode 100644 index 0000000000..6f81c9ceb1 --- /dev/null +++ b/test/e2e/label/label.go @@ -0,0 +1,42 @@ +package label + +import "github.com/onsi/ginkgo/v2" + +// Label is a wrapper for ginkgo label. We need a wrapper because we want to constrain inputs. If Key and Value are not +// empty, then it will be concatenated together seperated by ':'. If Key is not empty and Value is empty, then only the Key is used. +type Label struct { + // Key is mandatory + Key string + // Value is optional + Value string +} + +func (l Label) GinkgoLabel() ginkgo.Labels { + if l.Value == "" { + return ginkgo.Label(l.Key) + } + return ginkgo.Label(l.Key + ":" + l.Value) +} + +func NewComponent(name string) ginkgo.Labels { + return New(name, "").GinkgoLabel() +} + +func New(parts ...string) Label { + if len(parts) == 0 || len(parts) > 2 { + panic("invalid number of label constituents") + } + key, val := processOverrides(parts[0]), processOverrides(parts[1]) + return Label{ + Key: key, + Value: val, + } +} + +func processOverrides(s string) string { + overRide, ok := overrideMap[s] + if !ok { + return s + } + return overRide +} diff --git a/test/e2e/label/override.go b/test/e2e/label/override.go new file mode 100644 index 0000000000..31aa0fa0cd --- /dev/null +++ b/test/e2e/label/override.go @@ -0,0 +1,5 @@ +package label + +// overrideMap is used to rewrite label key and/or values. For example, if you want to rewrite Feature to a downstream specific name, +// therefore youd add "Feature" as a key to the overrides map and value to be what you wish to rewrite it to. +var overrideMap = map[string]string{} diff --git a/test/e2e/multi_node_zones_interconnect.go b/test/e2e/multi_node_zones_interconnect.go index 5737ec3680..0a358cd7ea 100644 --- a/test/e2e/multi_node_zones_interconnect.go +++ b/test/e2e/multi_node_zones_interconnect.go @@ -9,6 +9,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -87,7 +88,7 @@ func checkPodsInterconnectivity(clientPod, serverPod *v1.Pod, namespace string, return nil } -var _ = ginkgo.Describe("Multi node zones interconnect", func() { +var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, func() { const ( serverPodNodeName = "ovn-control-plane" diff --git a/test/e2e/multicast.go b/test/e2e/multicast.go index f90cf37b5f..d9b2bc3d9c 100644 --- a/test/e2e/multicast.go +++ b/test/e2e/multicast.go @@ -8,6 +8,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,7 +25,7 @@ type nodeInfo struct { nodeIP string } -var _ = ginkgo.Describe("Multicast", func() { +var _ = ginkgo.Describe("Multicast", feature.Multicast, func() { fr := wrappedTestFramework("multicast") diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index 49884ab548..a2f611676b 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -14,6 +14,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/docker/docker/client" v1 "k8s.io/api/core/v1" @@ -37,7 +38,7 @@ const ( nodeHostnameKey = "kubernetes.io/hostname" ) -var _ = Describe("Multi Homing", func() { +var _ = Describe("Multi Homing", feature.MultiHoming, func() { const ( podName = "tinypod" secondaryNetworkCIDR = "10.128.0.0/16" diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index a3105f2ab0..83fc059678 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -20,6 +20,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -44,7 +45,7 @@ const openDefaultPortsAnnotation = "k8s.ovn.org/open-default-ports" const RequiredUDNNamespaceLabel = "k8s.ovn.org/primary-user-defined-network" const OvnPodAnnotationName = "k8s.ovn.org/pod-networks" -var _ = Describe("Network Segmentation", func() { +var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { f := wrappedTestFramework("network-segmentation") // disable automatic namespace creation, we need to add the required UDN label f.SkipNamespaceCreation = true diff --git a/test/e2e/network_segmentation_endpointslices_mirror.go b/test/e2e/network_segmentation_endpointslices_mirror.go index 171073bdae..3790b2d568 100644 --- a/test/e2e/network_segmentation_endpointslices_mirror.go +++ b/test/e2e/network_segmentation_endpointslices_mirror.go @@ -5,14 +5,14 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" nadclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -23,7 +23,7 @@ import ( e2eservice "k8s.io/kubernetes/test/e2e/framework/service" ) -var _ = Describe("Network Segmentation EndpointSlices mirroring", func() { +var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.NetworkSegmentation, func() { f := wrappedTestFramework("endpointslices-mirror") f.SkipNamespaceCreation = true Context("a user defined primary network", func() { diff --git a/test/e2e/network_segmentation_policy.go b/test/e2e/network_segmentation_policy.go index 10e2b0f0e7..30bc1dc0a5 100644 --- a/test/e2e/network_segmentation_policy.go +++ b/test/e2e/network_segmentation_policy.go @@ -9,6 +9,7 @@ import ( nadclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" @@ -18,7 +19,7 @@ import ( "k8s.io/kubernetes/test/e2e/framework" ) -var _ = ginkgo.Describe("Network Segmentation: Network Policies", func() { +var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.NetworkSegmentation, func() { f := wrappedTestFramework("network-segmentation") f.SkipNamespaceCreation = true diff --git a/test/e2e/network_segmentation_services.go b/test/e2e/network_segmentation_services.go index d580bc190f..6f0822064f 100644 --- a/test/e2e/network_segmentation_services.go +++ b/test/e2e/network_segmentation_services.go @@ -12,6 +12,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -30,7 +31,7 @@ import ( utilnet "k8s.io/utils/net" ) -var _ = Describe("Network Segmentation: services", func() { +var _ = Describe("Network Segmentation: services", feature.NetworkSegmentation, func() { f := wrappedTestFramework("udn-services") f.SkipNamespaceCreation = true diff --git a/test/e2e/node_ip_mac_migration.go b/test/e2e/node_ip_mac_migration.go index d84ce6d737..a74d161c0d 100644 --- a/test/e2e/node_ip_mac_migration.go +++ b/test/e2e/node_ip_mac_migration.go @@ -18,6 +18,7 @@ import ( . "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -35,7 +36,7 @@ import ( utilnet "k8s.io/utils/net" ) -var _ = Describe("Node IP and MAC address migration", func() { +var _ = Describe("Node IP and MAC address migration", feature.NodeIPMACMigration, func() { const ( namespacePrefix = "node-ip-migration" podWorkerNodeName = "primary" diff --git a/test/e2e/ovspinning.go b/test/e2e/ovspinning.go index af72285ead..f3d94b530b 100644 --- a/test/e2e/ovspinning.go +++ b/test/e2e/ovspinning.go @@ -7,13 +7,14 @@ import ( "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" "k8s.io/kubernetes/test/e2e/framework" e2enode "k8s.io/kubernetes/test/e2e/framework/node" ) -var _ = ginkgo.Describe("OVS CPU affinity pinning", func() { +var _ = ginkgo.Describe("OVS CPU affinity pinning", feature.OVSCPUPin, func() { f := wrappedTestFramework("ovspinning") diff --git a/test/e2e/service.go b/test/e2e/service.go index 664a01e8ea..0df017d523 100644 --- a/test/e2e/service.go +++ b/test/e2e/service.go @@ -17,6 +17,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -52,7 +53,7 @@ var ( reportPath string ) -var _ = ginkgo.Describe("Services", func() { +var _ = ginkgo.Describe("Services", feature.Service, func() { const ( serviceName = "testservice" echoServerPodNameTemplate = "echo-server-pod-%d" @@ -1424,7 +1425,7 @@ func getServiceBackendsFromPod(execPod *v1.Pod, serviceIP string, servicePort in // service ip; if the traffic was DNAT-ed to the same src pod (hairpin/loopback case) - // the srcIP of reply traffic is SNATed to the special masqurade IP 169.254.0.5 // or "fd69::5" -var _ = ginkgo.Describe("Service Hairpin SNAT", func() { +var _ = ginkgo.Describe("Service Hairpin SNAT", feature.Service, func() { const ( svcName = "service-hairpin-test" backendName = "hairpin-backend-pod" @@ -1522,7 +1523,7 @@ var _ = ginkgo.Describe("Service Hairpin SNAT", func() { }) -var _ = ginkgo.Describe("Load Balancer Service Tests with MetalLB", func() { +var _ = ginkgo.Describe("Load Balancer Service Tests with MetalLB", feature.Service, func() { const ( svcName = "lbservice-test" diff --git a/test/e2e/status_manager.go b/test/e2e/status_manager.go index b6e7a9bfeb..bae96224ae 100644 --- a/test/e2e/status_manager.go +++ b/test/e2e/status_manager.go @@ -9,6 +9,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,7 +17,7 @@ import ( e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" ) -var _ = ginkgo.Describe("Status manager validation", func() { +var _ = ginkgo.Describe("Status manager validation", feature.EgressFirewall, func() { const ( svcname string = "status-manager" egressFirewallYamlFile string = "egress-fw.yml" diff --git a/test/e2e/testcontext.go b/test/e2e/testcontext.go new file mode 100644 index 0000000000..1b8104ab44 --- /dev/null +++ b/test/e2e/testcontext.go @@ -0,0 +1,143 @@ +package e2e + +import ( + "errors" + "os" + "path" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/reporters" + ginkgotypes "github.com/onsi/ginkgo/v2/types" + "github.com/onsi/gomega" + + "k8s.io/klog/v2" + "k8s.io/kubernetes/test/e2e/framework" +) + +// ProcessTestContextAndSetupLogging copied up k8 e2e test framework pkg because we need to remove the label check. +func ProcessTestContextAndSetupLogging() { + t := &framework.TestContext + // default copied from k8 e2e test framework pkg + // Reconfigure gomega defaults. The poll interval should be suitable + // for most tests. The timeouts are more subjective and tests may want + // to override them, but these defaults are still better for E2E than the + // ones from Gomega (1s timeout, 10ms interval). + var defaultTimeouts = framework.TimeoutContext{ + Poll: 2 * time.Second, // from the former e2e/framework/pod poll interval + PodStart: 5 * time.Minute, + PodStartShort: 2 * time.Minute, + PodStartSlow: 15 * time.Minute, + PodDelete: 5 * time.Minute, + ClaimProvision: 5 * time.Minute, + ClaimProvisionShort: 1 * time.Minute, + DataSourceProvision: 5 * time.Minute, + ClaimBound: 3 * time.Minute, + PVReclaim: 3 * time.Minute, + PVBound: 3 * time.Minute, + PVCreate: 3 * time.Minute, + PVDelete: 5 * time.Minute, + PVDeleteSlow: 20 * time.Minute, + SnapshotCreate: 5 * time.Minute, + SnapshotDelete: 5 * time.Minute, + SnapshotControllerMetrics: 5 * time.Minute, + SystemPodsStartup: 10 * time.Minute, + NodeSchedulable: 30 * time.Minute, + SystemDaemonsetStartup: 5 * time.Minute, + NodeNotReady: 3 * time.Minute, + } + gomega.SetDefaultEventuallyPollingInterval(defaultTimeouts.Poll) + gomega.SetDefaultConsistentlyPollingInterval(defaultTimeouts.Poll) + gomega.SetDefaultEventuallyTimeout(defaultTimeouts.PodStart) + gomega.SetDefaultConsistentlyDuration(defaultTimeouts.PodStartShort) + + // Allow 1% of nodes to be unready (statistically) - relevant for large clusters. + if t.AllowedNotReadyNodes == 0 { + t.AllowedNotReadyNodes = t.CloudConfig.NumNodes / 100 + } + + // Make sure that all test runs have a valid TestContext.CloudConfig.Provider. + // TODO: whether and how long this code is needed is getting discussed + // in https://github.com/kubernetes/kubernetes/issues/70194. + if t.Provider == "" { + t.Provider = "skeleton" + } + + var err error + t.CloudConfig.Provider, err = framework.SetupProviderConfig(t.Provider) + if err != nil { + if os.IsNotExist(errors.Unwrap(err)) { + klog.Errorf("Unknown provider %q. ", t.Provider) + } else { + klog.Errorf("Failed to setup provider config for %q: %v", t.Provider, err) + } + os.Exit(1) + } + + if t.ReportDir != "" { + // Create the directory before running the suite. If + // --report-dir is not unusable, we should report + // that as soon as possible. This will be done by each worker + // in parallel, so we will get "exists" error in most of them. + if err := os.MkdirAll(t.ReportDir, 0777); err != nil && !os.IsExist(err) { + klog.Errorf("Create report dir: %v", err) + os.Exit(1) + } + ginkgoDir := path.Join(t.ReportDir, "ginkgo") + if t.ReportCompleteGinkgo || t.ReportCompleteJUnit { + if err := os.MkdirAll(ginkgoDir, 0777); err != nil && !os.IsExist(err) { + klog.Errorf("Create /ginkgo: %v", err) + os.Exit(1) + } + } + + if t.ReportCompleteGinkgo { + ginkgo.ReportAfterSuite("Ginkgo JSON report", func(report ginkgo.Report) { + gomega.Expect(reporters.GenerateJSONReport(report, path.Join(ginkgoDir, "report.json"))).NotTo(gomega.HaveOccurred()) + }) + ginkgo.ReportAfterSuite("JUnit XML report", func(report ginkgo.Report) { + gomega.Expect(reporters.GenerateJUnitReport(report, path.Join(ginkgoDir, "report.xml"))).NotTo(gomega.HaveOccurred()) + }) + } + + ginkgo.ReportAfterSuite("OVN-Kubernetes e2e JUnit report", func(report ginkgo.Report) { + // With Ginkgo v1, we used to write one file per + // parallel node. Now Ginkgo v2 automatically merges + // all results into a report for us. The 01 suffix is + // kept in case that users expect files to be called + // "junit_.xml". + junitReport := path.Join(t.ReportDir, "junit_"+t.ReportPrefix+"01.xml") + + // writeJUnitReport generates a JUnit file in the e2e + // report directory that is shorter than the one + // normally written by `ginkgo --junit-report`. This is + // needed because the full report can become too large + // for tools like Spyglass + // (https://github.com/kubernetes/kubernetes/issues/111510). + gomega.Expect(writeJUnitReport(report, junitReport)).NotTo(gomega.HaveOccurred()) + }) + } +} + +// writeJUnitReport generates a JUnit file that is shorter than the one +// normally written by `ginkgo --junit-report`. This is needed because the full +// report can become too large for tools like Spyglass +// (https://github.com/kubernetes/kubernetes/issues/111510). +func writeJUnitReport(report ginkgo.Report, filename string) error { + config := reporters.JunitReportConfig{ + // Remove details for specs where we don't care. + OmitTimelinesForSpecState: ginkgotypes.SpecStatePassed | ginkgotypes.SpecStateSkipped, + + // Don't write . The same text is + // also in the full text for the failure. If we were to write + // both, then tools like kettle and spyglass would concatenate + // the two strings and thus show duplicated information. + OmitFailureMessageAttr: true, + + // All labels are also part of the spec texts in inline [] tags, + // so we don't need to write them separately. + OmitSpecLabels: true, + } + + return reporters.GenerateJUnitReportWithConfig(report, filename, config) +} diff --git a/test/e2e/unidling.go b/test/e2e/unidling.go index 9566b3190f..9f7535a9b2 100644 --- a/test/e2e/unidling.go +++ b/test/e2e/unidling.go @@ -14,6 +14,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,7 +38,7 @@ const ( // Validate that Services with the well-known annotation k8s.ovn.org/idled-at // generate a NeedPods Event if the service doesn“t have endpoints and // OVN EmptyLB-Backends feature is enabled -var _ = ginkgo.Describe("Unidling", func() { +var _ = ginkgo.Describe("Unidling", feature.Unidle, func() { const ( serviceName = "empty-service" From 19be786c5ae7074f8e0885fe81898642ef7b7f8e Mon Sep 17 00:00:00 2001 From: Artyom Babiy Date: Thu, 19 Jun 2025 20:05:23 +0300 Subject: [PATCH 041/278] use slash as path separator for some sysctl commands Convert `.` path separators to `/` when enabling forwarding for bridgeName, interfaceName and mgmtPortName to avoid errors when those names contain `.` characters e.g. `foo.200` Fixes: #5283 Signed-off-by: Artyom Babiy --- go-controller/pkg/node/gateway_init.go | 6 ++++-- go-controller/pkg/node/gateway_init_linux_test.go | 6 +++--- go-controller/pkg/node/gateway_udn.go | 13 +++++++++---- go-controller/pkg/node/gateway_udn_test.go | 6 +++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/go-controller/pkg/node/gateway_init.go b/go-controller/pkg/node/gateway_init.go index c7553f7d0d..28e0fa669b 100644 --- a/go-controller/pkg/node/gateway_init.go +++ b/go-controller/pkg/node/gateway_init.go @@ -27,8 +27,10 @@ import ( func bridgedGatewayNodeSetup(nodeName, bridgeName, physicalNetworkName string) (string, error) { // IPv6 forwarding is enabled globally if config.IPv4Mode { - stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.forwarding=1", bridgeName)) - if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", bridgeName) { + // we use forward slash as path separator to allow dotted bridgeName e.g. foo.200 + stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", bridgeName)) + // systctl output enforces dot as path separator + if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(bridgeName, ".", "/")) { return "", fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v", bridgeName, stdout, stderr, err) } diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index e9f248c419..0f6eab05ce 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -166,7 +166,7 @@ func shareGatewayInterfaceTest(app *cli.App, testNS ns.NetNS, }) if config.IPv4Mode { fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1", + Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1", Output: "net.ipv4.conf.breth0.forwarding = 1", }) } @@ -595,7 +595,7 @@ func shareGatewayInterfaceDPUTest(app *cli.App, testNS ns.NetNS, }) if config.IPv4Mode { fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf.brp0.forwarding=1", + Cmd: "sysctl -w net/ipv4/conf/brp0/forwarding=1", Output: "net.ipv4.conf.brp0.forwarding = 1", }) } @@ -1057,7 +1057,7 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0` }) if config.IPv4Mode { fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1", + Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1", Output: "net.ipv4.conf.breth0.forwarding = 1", }) } diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index b207a4f009..7ab5b50cc9 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "slices" + "strings" "sync/atomic" "time" @@ -522,8 +523,10 @@ func (udng *UserDefinedNetworkGateway) addUDNManagementPort() (netlink.Link, err // STEP3 // IPv6 forwarding is enabled globally if ipv4, _ := udng.IPMode(); ipv4 { - stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.forwarding=1", interfaceName)) - if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", interfaceName) { + // we use forward slash as path separator to allow dotted interfaceName e.g. foo.200 + stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", interfaceName)) + // systctl output enforces dot as path separator + if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(interfaceName, ".", "/")) { return nil, fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v", interfaceName, stdout, stderr, err) } @@ -891,8 +894,10 @@ func addRPFilterLooseModeForManagementPort(mgmtPortName string) error { rpFilterLooseMode := "2" // TODO: Convert testing framework to mock golang module utilities. Example: // result, err := sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/rp_filter", types.K8sMgmtIntfName), rpFilterLooseMode) - stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net.ipv4.conf.%s.rp_filter=%s", mgmtPortName, rpFilterLooseMode)) - if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.rp_filter = %s", mgmtPortName, rpFilterLooseMode) { + // we use forward slash as path separator to allow dotted mgmtPortName e.g. foo.200 + stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/rp_filter=%s", mgmtPortName, rpFilterLooseMode)) + // systctl output enforces dot as path separator + if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.rp_filter = %s", strings.ReplaceAll(mgmtPortName, ".", "/"), rpFilterLooseMode) { return fmt.Errorf("could not set the correct rp_filter value for interface %s: stdout: %v, stderr: %v, err: %v", mgmtPortName, stdout, stderr, err) } diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index ac964dfeec..8c38c7ec5b 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -59,14 +59,14 @@ func getCreationFakeCommands(fexec *ovntest.FakeExec, mgtPort, mgtPortMAC, netNa }) fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf." + mgtPort + ".forwarding=1", + Cmd: "sysctl -w net/ipv4/conf/" + mgtPort + "/forwarding=1", Output: "net.ipv4.conf." + mgtPort + ".forwarding = 1", }) } func getRPFilterLooseModeFakeCommands(fexec *ovntest.FakeExec) { fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf.ovn-k8s-mp3.rp_filter=2", + Cmd: "sysctl -w net/ipv4/conf/ovn-k8s-mp3/rp_filter=2", Output: "net.ipv4.conf.ovn-k8s-mp3.rp_filter = 2", }) } @@ -148,7 +148,7 @@ func setUpGatewayFakeOVSCommands(fexec *ovntest.FakeExec) { }) if config.IPv4Mode { fexec.AddFakeCmd(&ovntest.ExpectedCmd{ - Cmd: "sysctl -w net.ipv4.conf.breth0.forwarding=1", + Cmd: "sysctl -w net/ipv4/conf/breth0/forwarding=1", Output: "net.ipv4.conf.breth0.forwarding = 1", }) } From ffadcff5d6fe0130758d5b76b85b7dbb9dcaa93e Mon Sep 17 00:00:00 2001 From: AOS Automation Release Team Date: Fri, 20 Jun 2025 11:12:09 +0000 Subject: [PATCH 042/278] Updating ose-ovn-kubernetes-container image to be consistent with ART for 4.20 Reconciling with https://github.com/openshift/ocp-build-data/tree/8f77fc475c95f9d98c348deb2feb88f5952d7357/images/ose-ovn-kubernetes.yml --- .ci-operator.yaml | 2 +- Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 7c15f83e3e..461415cbc5 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: release namespace: openshift - tag: rhel-9-release-golang-1.23-openshift-4.19 + tag: rhel-9-release-golang-1.24-openshift-4.20 diff --git a/Dockerfile b/Dockerfile index 46ad2ccf60..74e10a832a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ # The standard name for this image is ovn-kube # Build RHEL-9 binaries -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.23-openshift-4.19 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.24-openshift-4.20 AS builder WORKDIR /go/src/github.com/openshift/ovn-kubernetes COPY . . @@ -13,7 +13,7 @@ RUN cd go-controller; CGO_ENABLED=1 make RUN cd go-controller; CGO_ENABLED=0 make windows # Build RHEL-8 binaries (for upgrades from 4.12 and earlier) -FROM registry.ci.openshift.org/ocp/builder:rhel-8-golang-1.23-openshift-4.19 AS rhel8 +FROM registry.ci.openshift.org/ocp/builder:rhel-8-golang-1.24-openshift-4.20 AS rhel8 WORKDIR /go/src/github.com/openshift/ovn-kubernetes COPY . . RUN cd go-controller; CGO_ENABLED=1 make @@ -26,7 +26,7 @@ RUN cd go-controller; CGO_ENABLED=1 make # - creating directories required by ovn-kubernetes # - git commit number # - ovnkube.sh script -FROM registry.ci.openshift.org/ocp/4.19:ovn-kubernetes-base +FROM registry.ci.openshift.org/ocp/4.20:ovn-kubernetes-base USER root From c770ecdea77be4feb84e05a1450cdcad8ab6570a Mon Sep 17 00:00:00 2001 From: AOS Automation Release Team Date: Fri, 20 Jun 2025 11:26:29 +0000 Subject: [PATCH 043/278] Updating ovn-kubernetes-microshift-container image to be consistent with ART for 4.20 Reconciling with https://github.com/openshift/ocp-build-data/tree/8f77fc475c95f9d98c348deb2feb88f5952d7357/images/ovn-kubernetes-microshift.yml --- .ci-operator.yaml | 2 +- Dockerfile.microshift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 7c15f83e3e..461415cbc5 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: release namespace: openshift - tag: rhel-9-release-golang-1.23-openshift-4.19 + tag: rhel-9-release-golang-1.24-openshift-4.20 diff --git a/Dockerfile.microshift b/Dockerfile.microshift index 4846e332a4..e11933d1b4 100644 --- a/Dockerfile.microshift +++ b/Dockerfile.microshift @@ -12,7 +12,7 @@ # openvswitch-devel, openvswitch-ipsec, libpcap, iproute etc # ovn-kube-util, hybrid-overlay-node.exe, ovndbchecker and ovnkube-trace -FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.23-openshift-4.19 AS builder +FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.24-openshift-4.20 AS builder WORKDIR /go/src/github.com/openshift/ovn-kubernetes COPY . . @@ -20,7 +20,7 @@ COPY . . # build the binaries RUN cd go-controller; CGO_ENABLED=0 make -FROM registry.ci.openshift.org/ocp/4.19:ovn-kubernetes-base +FROM registry.ci.openshift.org/ocp/4.20:ovn-kubernetes-base USER root From 21e4f0eb5e2e3467bbf33487ddafd8ea0e1dbda0 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Sun, 8 Jun 2025 22:06:38 +0200 Subject: [PATCH 044/278] Fix the host drop rules to match on new state When we did the NFT rules to block traffic going from host to advertised UDN pod subnets, we did not mean to also block replies from host to advertised UDN pod subnets for traffic initiated by UDN pods. Given the rules lie in OUTPUT table this would match on replies as well, so traffic like pod to kube-apiserver host-networked pod backend is broken because of this. Let's change the rule to only match on NEW state which is what we wanted to do in the original change. The current rules unintentionally block traffic in reverse direction. Signed-off-by: Surya Seetharaman --- go-controller/pkg/node/gateway_shared_intf.go | 8 +- test/e2e/route_advertisements.go | 75 ++++++++++++++++++- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 2654291850..d763089082 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -3040,8 +3040,8 @@ func getIPv(ipnet *net.IPNet) string { // chain udn-bgp-drop { // comment "Drop traffic generated locally towards advertised UDN subnets" // type filter hook output priority filter; policy accept; -// ip daddr @advertised-udn-subnets-v4 counter packets 0 bytes 0 drop -// ip6 daddr @advertised-udn-subnets-v6 counter packets 0 bytes 0 drop +// ct state new ip daddr @advertised-udn-subnets-v4 counter packets 0 bytes 0 drop +// ct state new ip6 daddr @advertised-udn-subnets-v6 counter packets 0 bytes 0 drop // } func configureAdvertisedUDNIsolationNFTables() error { counterIfDebug := "" @@ -3083,11 +3083,11 @@ func configureAdvertisedUDNIsolationNFTables() error { tx.Add(&knftables.Rule{ Chain: nftablesUDNBGPOutputChain, - Rule: knftables.Concat(fmt.Sprintf("ip daddr @%s", nftablesAdvertisedUDNsSetV4), counterIfDebug, "drop"), + Rule: knftables.Concat("ct state new", fmt.Sprintf("ip daddr @%s", nftablesAdvertisedUDNsSetV4), counterIfDebug, "drop"), }) tx.Add(&knftables.Rule{ Chain: nftablesUDNBGPOutputChain, - Rule: knftables.Concat(fmt.Sprintf("ip6 daddr @%s", nftablesAdvertisedUDNsSetV6), counterIfDebug, "drop"), + Rule: knftables.Concat("ct state new", fmt.Sprintf("ip6 daddr @%s", nftablesAdvertisedUDNsSetV6), counterIfDebug, "drop"), }) return nft.Run(context.TODO(), tx) } diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index bee77d639f..f6dcdfc800 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -3,6 +3,7 @@ package e2e import ( "context" "fmt" + "math/rand" "net" "strings" @@ -19,6 +20,7 @@ import ( infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -532,7 +534,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" var svcNetA, svcNetB, svcNetDefault *corev1.Service var cudnA, cudnB *udnv1.ClusterUserDefinedNetwork var ra *rav1.RouteAdvertisements - + var hostNetworkPort int ginkgo.BeforeEach(func() { if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() { e2eskipper.Skipf("Advertising Layer2 UDNs is not currently supported in LGW") @@ -584,6 +586,30 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" nodes, err = e2enode.GetReadySchedulableNodes(context.TODO(), f.ClientSet) gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Expect(len(nodes.Items)).To(gomega.BeNumerically(">", 2)) + // create host networked pod + ginkgo.By("Creating host network pods on each node") + // get random port in case the test retries and port is already in use on host node + min := 25000 + max := 25999 + hostNetworkPort = rand.Intn(max-min+1) + min + framework.Logf("Random host networked port chosen: %d", hostNetworkPort) + for _, node := range nodes.Items { + // this creates a udp / http netexec listener which is able to receive the "hostname" + // command. We use this to validate that each endpoint is received at least once + args := []string{ + "netexec", + fmt.Sprintf("--http-port=%d", hostNetworkPort), + fmt.Sprintf("--udp-port=%d", hostNetworkPort), + } + + // create host networked Pods + _, err := createPod(f, node.Name+"-hostnet-ep", node.Name, f.Namespace.Name, []string{}, map[string]string{}, func(p *v1.Pod) { + p.Spec.Containers[0].Args = args + p.Spec.HostNetwork = true + }) + + framework.ExpectNoError(err) + } ginkgo.By("Setting up pods and services") podsNetA = []*corev1.Pod{} @@ -901,6 +927,53 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" framework.ExpectNoError(err) return clientNode, "", net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), + ginkgo.Entry("UDN pod to local node should not work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), clientPod.Spec.NodeName, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + // FIXME: add the host process socket to the VRF for this test to work. + // This scenario is something that is not supported yet. So the test will continue to fail. + // This works the same on both normal UDNs and advertised UDNs. + // So because the process is not bound to the VRF, packet reaches the host but kernel sends a RESET. So its not code 28 but code7. + // 10:59:55.351067 319594f193d4d_3 P ifindex 191 0a:58:5d:5d:01:05 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 64, id 57264, + // offset 0, flags [DF], proto TCP (6), length 60) + // 93.93.1.5.36363 > 172.18.0.2.25022: Flags [S], cksum 0x0aa5 (incorrect -> 0xe0b7), seq 3879759281, win 65280, + // options [mss 1360,sackOK,TS val 3006752321 ecr 0,nop,wscale 7], length 0 + // 10:59:55.352404 ovn-k8s-mp87 In ifindex 186 0a:58:5d:5d:01:01 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 63, id 57264, + // offset 0, flags [DF], proto TCP (6), length 60) + // 93.93.1.5.36363 > 172.18.0.2.25022: Flags [S], cksum 0xe0b7 (correct), seq 3879759281, win 65280, + // options [mss 1360,sackOK,TS val 3006752321 ecr 0,nop,wscale 7], length 0 + // 10:59:55.352461 ovn-k8s-mp87 Out ifindex 186 0a:58:5d:5d:01:02 ethertype IPv4 (0x0800), length 60: (tos 0x0, ttl 64, id 0, + // offset 0, flags [DF], proto TCP (6), length 40) + // 172.18.0.2.25022 > 93.93.1.5.36363: Flags [R.], cksum 0x609d (correct), seq 0, ack 3879759282, win 0, length 0 + // 10:59:55.352927 319594f193d4d_3 Out ifindex 191 0a:58:5d:5d:01:02 ethertype IPv4 (0x0800), length 60: (tos 0x0, ttl 64, id 0, + // offset 0, flags [DF], proto TCP (6), length 40) + // 172.18.0.2.25022 > 93.93.1.5.36363: Flags [R.], cksum 0x609d (correct), seq 0, ack 1, win 0, length 0 + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/hostname", "", true + }), + ginkgo.Entry("UDN pod to a different node should work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // podsNetA[0] and podsNetA[2] are on different nodes so we can pick the node of podsNetA[2] as the different node destination + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), podsNetA[2].Spec.NodeName, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + errBool := false + out := "" + if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { + // FIXME: fix assymmetry in L2 UDNs + // bad behaviour: packet is coming from other node -> entering eth0 -> bretho and here kernel drops the packet since + // rp_filter is set to 1 in breth0 and there is an iprule that sends the packet to mpX interface so kernel sees the packet + // having return path different from the incoming interface. + // The SNAT to nodeIP should fix this. + // this causes curl timeout with code 28 + errBool = true + out = curlConnectionTimeoutCode + } + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/hostname", out, errBool + }), ) }, From abd8e0fd8700d60c0902543f1e6b92b564e2c1e7 Mon Sep 17 00:00:00 2001 From: PGhiorzo Date: Mon, 23 Jun 2025 16:22:40 +0200 Subject: [PATCH 045/278] Modified line 277 to let kind-helm.sh run also behind a proxy Signed-off-by: PGhiorzo --- contrib/kind-helm.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/kind-helm.sh b/contrib/kind-helm.sh index c682c94ac7..8a22c0a234 100755 --- a/contrib/kind-helm.sh +++ b/contrib/kind-helm.sh @@ -274,7 +274,12 @@ build_ovn_image() { # Find all built executables, but ignore the 'windows' directory if it exists find ../../go-controller/_output/go/bin/ -maxdepth 1 -type f -exec cp -f {} . \; echo "ref: $(git rev-parse --symbolic-full-name HEAD) commit: $(git rev-parse HEAD)" > git_info - $OCI_BIN build -t "${OVN_IMAGE}" -f Dockerfile.fedora . + $OCI_BIN build \ + --build-arg http_proxy="$http_proxy" \ + --build-arg https_proxy="$https_proxy" \ + --network=host \ + -t "${OVN_IMAGE}" \ + -f Dockerfile.fedora . popd } From ff1b163cc83b072a1c534f912a92a0f4a0b0b9c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 04:47:39 +0000 Subject: [PATCH 046/278] Bump the go_modules group across 3 directories with 4 updates Bumps the go_modules group with 2 updates in the /go-controller directory: [golang.org/x/net](https://github.com/golang/net) and [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes). Bumps the go_modules group with 1 update in the /test/conformance directory: [golang.org/x/net](https://github.com/golang/net). Bumps the go_modules group with 3 updates in the /test/e2e directory: [golang.org/x/net](https://github.com/golang/net), [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) and [github.com/docker/docker](https://github.com/docker/docker). Updates `golang.org/x/net` from 0.30.0 to 0.38.0 - [Commits](https://github.com/golang/net/compare/v0.30.0...v0.38.0) Updates `k8s.io/kubernetes` from 1.32.3 to 1.32.6 - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.32.3...v1.32.6) Updates `golang.org/x/crypto` from 0.28.0 to 0.36.0 - [Commits](https://github.com/golang/crypto/compare/v0.28.0...v0.36.0) Updates `golang.org/x/net` from 0.23.0 to 0.38.0 - [Commits](https://github.com/golang/net/compare/v0.30.0...v0.38.0) Updates `golang.org/x/net` from 0.30.0 to 0.38.0 - [Commits](https://github.com/golang/net/compare/v0.30.0...v0.38.0) Updates `k8s.io/kubernetes` from 1.32.3 to 1.32.6 - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.32.3...v1.32.6) Updates `golang.org/x/crypto` from 0.35.0 to 0.36.0 - [Commits](https://github.com/golang/crypto/compare/v0.28.0...v0.36.0) Updates `github.com/docker/docker` from 26.1.4+incompatible to 26.1.5+incompatible - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v26.1.4...v26.1.5) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: direct:production dependency-group: go_modules - dependency-name: k8s.io/kubernetes dependency-version: 1.32.6 dependency-type: direct:production dependency-group: go_modules - dependency-name: golang.org/x/crypto dependency-version: 0.36.0 dependency-type: indirect dependency-group: go_modules - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: indirect dependency-group: go_modules - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: indirect dependency-group: go_modules - dependency-name: k8s.io/kubernetes dependency-version: 1.32.6 dependency-type: direct:production dependency-group: go_modules - dependency-name: golang.org/x/crypto dependency-version: 0.36.0 dependency-type: indirect dependency-group: go_modules - dependency-name: github.com/docker/docker dependency-version: 26.1.5+incompatible dependency-type: direct:production dependency-group: go_modules ... Signed-off-by: dependabot[bot] --- go-controller/go.mod | 14 +- go-controller/go.sum | 28 +- .../golang.org/x/net/context/context.go | 112 +- .../vendor/golang.org/x/net/context/go17.go | 72 - .../vendor/golang.org/x/net/context/go19.go | 20 - .../golang.org/x/net/context/pre_go17.go | 300 ---- .../golang.org/x/net/context/pre_go19.go | 109 -- .../golang.org/x/net/html/atom/table.go | 1256 +++++++++-------- .../vendor/golang.org/x/net/html/doc.go | 7 +- .../vendor/golang.org/x/net/html/doctype.go | 2 +- .../vendor/golang.org/x/net/html/foreign.go | 3 +- .../vendor/golang.org/x/net/html/iter.go | 56 + .../vendor/golang.org/x/net/html/node.go | 4 + .../vendor/golang.org/x/net/html/parse.go | 12 +- .../vendor/golang.org/x/net/html/token.go | 18 +- .../x/net/http2/client_conn_pool.go | 8 +- .../vendor/golang.org/x/net/http2/config.go | 2 +- .../golang.org/x/net/http2/config_go124.go | 2 +- .../vendor/golang.org/x/net/http2/frame.go | 15 +- .../vendor/golang.org/x/net/http2/http2.go | 59 +- .../vendor/golang.org/x/net/http2/server.go | 185 ++- .../golang.org/x/net/http2/transport.go | 690 +++++---- .../golang.org/x/net/http2/unencrypted.go | 32 + .../vendor/golang.org/x/net/http2/write.go | 3 +- .../x/net/internal/httpcommon/ascii.go | 53 + .../httpcommon}/headermap.go | 24 +- .../x/net/internal/httpcommon/request.go | 467 ++++++ .../net/internal/socket/zsys_openbsd_ppc64.go | 28 +- .../internal/socket/zsys_openbsd_riscv64.go | 28 +- .../vendor/golang.org/x/net/proxy/per_host.go | 8 +- .../golang.org/x/net/websocket/websocket.go | 5 +- .../golang.org/x/sync/errgroup/errgroup.go | 3 +- .../golang.org/x/sync/errgroup/go120.go | 13 - .../golang.org/x/sync/errgroup/pre_go120.go | 14 - .../vendor/golang.org/x/sys/unix/auxv.go | 36 + .../golang.org/x/sys/unix/auxv_unsupported.go | 13 + .../golang.org/x/sys/unix/ioctl_linux.go | 96 ++ .../vendor/golang.org/x/sys/unix/mkerrors.sh | 12 + .../x/sys/unix/syscall_dragonfly.go | 12 + .../golang.org/x/sys/unix/syscall_linux.go | 1 + .../golang.org/x/sys/unix/syscall_solaris.go | 87 ++ .../x/sys/unix/syscall_zos_s390x.go | 104 +- .../golang.org/x/sys/unix/zerrors_linux.go | 51 +- .../x/sys/unix/zerrors_linux_386.go | 23 + .../x/sys/unix/zerrors_linux_amd64.go | 23 + .../x/sys/unix/zerrors_linux_arm.go | 23 + .../x/sys/unix/zerrors_linux_arm64.go | 25 + .../x/sys/unix/zerrors_linux_loong64.go | 23 + .../x/sys/unix/zerrors_linux_mips.go | 23 + .../x/sys/unix/zerrors_linux_mips64.go | 23 + .../x/sys/unix/zerrors_linux_mips64le.go | 23 + .../x/sys/unix/zerrors_linux_mipsle.go | 23 + .../x/sys/unix/zerrors_linux_ppc.go | 23 + .../x/sys/unix/zerrors_linux_ppc64.go | 23 + .../x/sys/unix/zerrors_linux_ppc64le.go | 23 + .../x/sys/unix/zerrors_linux_riscv64.go | 23 + .../x/sys/unix/zerrors_linux_s390x.go | 23 + .../x/sys/unix/zerrors_linux_sparc64.go | 23 + .../golang.org/x/sys/unix/zsyscall_linux.go | 10 + .../x/sys/unix/zsyscall_solaris_amd64.go | 114 ++ .../x/sys/unix/zsysnum_linux_386.go | 4 + .../x/sys/unix/zsysnum_linux_amd64.go | 4 + .../x/sys/unix/zsysnum_linux_arm.go | 4 + .../x/sys/unix/zsysnum_linux_arm64.go | 4 + .../x/sys/unix/zsysnum_linux_loong64.go | 4 + .../x/sys/unix/zsysnum_linux_mips.go | 4 + .../x/sys/unix/zsysnum_linux_mips64.go | 4 + .../x/sys/unix/zsysnum_linux_mips64le.go | 4 + .../x/sys/unix/zsysnum_linux_mipsle.go | 4 + .../x/sys/unix/zsysnum_linux_ppc.go | 4 + .../x/sys/unix/zsysnum_linux_ppc64.go | 4 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 4 + .../x/sys/unix/zsysnum_linux_riscv64.go | 4 + .../x/sys/unix/zsysnum_linux_s390x.go | 4 + .../x/sys/unix/zsysnum_linux_sparc64.go | 4 + .../x/sys/unix/ztypes_darwin_amd64.go | 60 + .../x/sys/unix/ztypes_darwin_arm64.go | 60 + .../golang.org/x/sys/unix/ztypes_linux.go | 144 +- .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 6 + .../golang.org/x/sys/windows/dll_windows.go | 11 +- .../x/sys/windows/syscall_windows.go | 36 +- .../golang.org/x/sys/windows/types_windows.go | 127 ++ .../x/sys/windows/zsyscall_windows.go | 71 + .../vendor/golang.org/x/term/README.md | 11 +- .../golang.org/x/text/language/parse.go | 2 +- go-controller/vendor/modules.txt | 27 +- test/conformance/go.mod | 10 +- test/conformance/go.sum | 20 +- test/e2e/go.mod | 16 +- test/e2e/go.sum | 32 +- 90 files changed, 3399 insertions(+), 1829 deletions(-) delete mode 100644 go-controller/vendor/golang.org/x/net/context/go17.go delete mode 100644 go-controller/vendor/golang.org/x/net/context/go19.go delete mode 100644 go-controller/vendor/golang.org/x/net/context/pre_go17.go delete mode 100644 go-controller/vendor/golang.org/x/net/context/pre_go19.go create mode 100644 go-controller/vendor/golang.org/x/net/html/iter.go create mode 100644 go-controller/vendor/golang.org/x/net/http2/unencrypted.go create mode 100644 go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go rename go-controller/vendor/golang.org/x/net/{http2 => internal/httpcommon}/headermap.go (74%) create mode 100644 go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go delete mode 100644 go-controller/vendor/golang.org/x/sync/errgroup/go120.go delete mode 100644 go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go create mode 100644 go-controller/vendor/golang.org/x/sys/unix/auxv.go create mode 100644 go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go diff --git a/go-controller/go.mod b/go-controller/go.mod index a7b86b1ed1..7868b6ca26 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -47,9 +47,9 @@ require ( github.com/urfave/cli/v2 v2.27.2 github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/net v0.30.0 - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.26.0 + golang.org/x/net v0.38.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 golang.org/x/time v0.7.0 google.golang.org/grpc v1.65.0 google.golang.org/grpc/security/advancedtls v0.0.0-20240425232638-1e8b9b7fc655 @@ -62,7 +62,7 @@ require ( k8s.io/client-go v0.32.3 k8s.io/component-helpers v0.32.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubernetes v1.32.3 + k8s.io/kubernetes v1.32.6 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 kubevirt.io/api v1.0.0-alpha.0 sigs.k8s.io/controller-runtime v0.20.3 @@ -124,10 +124,10 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect diff --git a/go-controller/go.sum b/go-controller/go.sum index 93bf3489f5..3dcc3208b3 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -841,8 +841,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -934,8 +934,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -958,8 +958,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1047,14 +1047,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1064,8 +1064,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1368,8 +1368,8 @@ k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lV k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA= -k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k= +k8s.io/kubernetes v1.32.6 h1:tp1gRjOqZjaoFBek5PN6eSmODdS1QRrH5UKiFP8ZByg= +k8s.io/kubernetes v1.32.6/go.mod h1:REY0Gok66BTTrbGyZaFMNKO9JhxvgBDW9B7aksWRFoY= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/go-controller/vendor/golang.org/x/net/context/context.go b/go-controller/vendor/golang.org/x/net/context/context.go index cf66309c4a..db1c95fab1 100644 --- a/go-controller/vendor/golang.org/x/net/context/context.go +++ b/go-controller/vendor/golang.org/x/net/context/context.go @@ -3,29 +3,31 @@ // license that can be found in the LICENSE file. // Package context defines the Context type, which carries deadlines, -// cancelation signals, and other request-scoped values across API boundaries +// cancellation signals, and other request-scoped values across API boundaries // and between processes. // As of Go 1.7 this package is available in the standard library under the -// name context. https://golang.org/pkg/context. +// name [context], and migrating to it can be done automatically with [go fix]. // -// Incoming requests to a server should create a Context, and outgoing calls to -// servers should accept a Context. The chain of function calls between must -// propagate the Context, optionally replacing it with a modified copy created -// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// Incoming requests to a server should create a [Context], and outgoing +// calls to servers should accept a Context. The chain of function +// calls between them must propagate the Context, optionally replacing +// it with a derived Context created using [WithCancel], [WithDeadline], +// [WithTimeout], or [WithValue]. // // Programs that use Contexts should follow these rules to keep interfaces // consistent across packages and enable static analysis tools to check context // propagation: // // Do not store Contexts inside a struct type; instead, pass a Context -// explicitly to each function that needs it. The Context should be the first +// explicitly to each function that needs it. This is discussed further in +// https://go.dev/blog/context-and-structs. The Context should be the first // parameter, typically named ctx: // // func DoSomething(ctx context.Context, arg Arg) error { // // ... use ctx ... // } // -// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO] // if you are unsure about which Context to use. // // Use context Values only for request-scoped data that transits processes and @@ -34,9 +36,30 @@ // The same Context may be passed to functions running in different goroutines; // Contexts are safe for simultaneous use by multiple goroutines. // -// See http://blog.golang.org/context for example code for a server that uses +// See https://go.dev/blog/context for example code for a server that uses // Contexts. -package context // import "golang.org/x/net/context" +// +// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs +package context + +import ( + "context" // standard library's context, as of Go 1.7 + "time" +) + +// A Context carries a deadline, a cancellation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context = context.Context + +// Canceled is the error returned by [Context.Err] when the context is canceled +// for some reason other than its deadline passing. +var Canceled = context.Canceled + +// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled +// due to its deadline passing. +var DeadlineExceeded = context.DeadlineExceeded // Background returns a non-nil, empty Context. It is never canceled, has no // values, and has no deadline. It is typically used by the main function, @@ -49,8 +72,73 @@ func Background() Context { // TODO returns a non-nil, empty Context. Code should use context.TODO when // it's unclear which Context to use or it is not yet available (because the // surrounding function has not yet been extended to accept a Context -// parameter). TODO is recognized by static analysis tools that determine -// whether Contexts are propagated correctly in a program. +// parameter). func TODO() Context { return todo } + +var ( + background = context.Background() + todo = context.TODO() +) + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// A CancelFunc may be called by multiple goroutines simultaneously. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc = context.CancelFunc + +// WithCancel returns a derived context that points to the parent context +// but has a new Done channel. The returned context's Done channel is closed +// when the returned cancel function is called or when the parent context's +// Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this [Context] complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + return context.WithCancel(parent) +} + +// WithDeadline returns a derived context that points to the parent context +// but has the deadline adjusted to be no later than d. If the parent's +// deadline is already earlier than d, WithDeadline(parent, d) is semantically +// equivalent to parent. The returned [Context.Done] channel is closed when +// the deadline expires, when the returned cancel function is called, +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this [Context] complete. +func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { + return context.WithDeadline(parent, d) +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this [Context] complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return context.WithTimeout(parent, timeout) +} + +// WithValue returns a derived context that points to the parent Context. +// In the derived context, the value associated with key is val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The provided key must be comparable and should not be of type +// string or any other built-in type to avoid collisions between +// packages using context. Users of WithValue should define their own +// types for keys. To avoid allocating when assigning to an +// interface{}, context keys often have concrete type +// struct{}. Alternatively, exported context key variables' static +// type should be a pointer or interface. +func WithValue(parent Context, key, val interface{}) Context { + return context.WithValue(parent, key, val) +} diff --git a/go-controller/vendor/golang.org/x/net/context/go17.go b/go-controller/vendor/golang.org/x/net/context/go17.go deleted file mode 100644 index 0c1b867937..0000000000 --- a/go-controller/vendor/golang.org/x/net/context/go17.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.7 - -package context - -import ( - "context" // standard library's context, as of Go 1.7 - "time" -) - -var ( - todo = context.TODO() - background = context.Background() -) - -// Canceled is the error returned by Context.Err when the context is canceled. -var Canceled = context.Canceled - -// DeadlineExceeded is the error returned by Context.Err when the context's -// deadline passes. -var DeadlineExceeded = context.DeadlineExceeded - -// WithCancel returns a copy of parent with a new Done channel. The returned -// context's Done channel is closed when the returned cancel function is called -// or when the parent context's Done channel is closed, whichever happens first. -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete. -func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { - ctx, f := context.WithCancel(parent) - return ctx, f -} - -// WithDeadline returns a copy of the parent context with the deadline adjusted -// to be no later than d. If the parent's deadline is already earlier than d, -// WithDeadline(parent, d) is semantically equivalent to parent. The returned -// context's Done channel is closed when the deadline expires, when the returned -// cancel function is called, or when the parent context's Done channel is -// closed, whichever happens first. -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete. -func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { - ctx, f := context.WithDeadline(parent, deadline) - return ctx, f -} - -// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete: -// -// func slowOperationWithTimeout(ctx context.Context) (Result, error) { -// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) -// defer cancel() // releases resources if slowOperation completes before timeout elapses -// return slowOperation(ctx) -// } -func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { - return WithDeadline(parent, time.Now().Add(timeout)) -} - -// WithValue returns a copy of parent in which the value associated with key is -// val. -// -// Use context Values only for request-scoped data that transits processes and -// APIs, not for passing optional parameters to functions. -func WithValue(parent Context, key interface{}, val interface{}) Context { - return context.WithValue(parent, key, val) -} diff --git a/go-controller/vendor/golang.org/x/net/context/go19.go b/go-controller/vendor/golang.org/x/net/context/go19.go deleted file mode 100644 index e31e35a904..0000000000 --- a/go-controller/vendor/golang.org/x/net/context/go19.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.9 - -package context - -import "context" // standard library's context, as of Go 1.7 - -// A Context carries a deadline, a cancelation signal, and other values across -// API boundaries. -// -// Context's methods may be called by multiple goroutines simultaneously. -type Context = context.Context - -// A CancelFunc tells an operation to abandon its work. -// A CancelFunc does not wait for the work to stop. -// After the first call, subsequent calls to a CancelFunc do nothing. -type CancelFunc = context.CancelFunc diff --git a/go-controller/vendor/golang.org/x/net/context/pre_go17.go b/go-controller/vendor/golang.org/x/net/context/pre_go17.go deleted file mode 100644 index 065ff3dfa5..0000000000 --- a/go-controller/vendor/golang.org/x/net/context/pre_go17.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.7 - -package context - -import ( - "errors" - "fmt" - "sync" - "time" -) - -// An emptyCtx is never canceled, has no values, and has no deadline. It is not -// struct{}, since vars of this type must have distinct addresses. -type emptyCtx int - -func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { - return -} - -func (*emptyCtx) Done() <-chan struct{} { - return nil -} - -func (*emptyCtx) Err() error { - return nil -} - -func (*emptyCtx) Value(key interface{}) interface{} { - return nil -} - -func (e *emptyCtx) String() string { - switch e { - case background: - return "context.Background" - case todo: - return "context.TODO" - } - return "unknown empty Context" -} - -var ( - background = new(emptyCtx) - todo = new(emptyCtx) -) - -// Canceled is the error returned by Context.Err when the context is canceled. -var Canceled = errors.New("context canceled") - -// DeadlineExceeded is the error returned by Context.Err when the context's -// deadline passes. -var DeadlineExceeded = errors.New("context deadline exceeded") - -// WithCancel returns a copy of parent with a new Done channel. The returned -// context's Done channel is closed when the returned cancel function is called -// or when the parent context's Done channel is closed, whichever happens first. -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete. -func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { - c := newCancelCtx(parent) - propagateCancel(parent, c) - return c, func() { c.cancel(true, Canceled) } -} - -// newCancelCtx returns an initialized cancelCtx. -func newCancelCtx(parent Context) *cancelCtx { - return &cancelCtx{ - Context: parent, - done: make(chan struct{}), - } -} - -// propagateCancel arranges for child to be canceled when parent is. -func propagateCancel(parent Context, child canceler) { - if parent.Done() == nil { - return // parent is never canceled - } - if p, ok := parentCancelCtx(parent); ok { - p.mu.Lock() - if p.err != nil { - // parent has already been canceled - child.cancel(false, p.err) - } else { - if p.children == nil { - p.children = make(map[canceler]bool) - } - p.children[child] = true - } - p.mu.Unlock() - } else { - go func() { - select { - case <-parent.Done(): - child.cancel(false, parent.Err()) - case <-child.Done(): - } - }() - } -} - -// parentCancelCtx follows a chain of parent references until it finds a -// *cancelCtx. This function understands how each of the concrete types in this -// package represents its parent. -func parentCancelCtx(parent Context) (*cancelCtx, bool) { - for { - switch c := parent.(type) { - case *cancelCtx: - return c, true - case *timerCtx: - return c.cancelCtx, true - case *valueCtx: - parent = c.Context - default: - return nil, false - } - } -} - -// removeChild removes a context from its parent. -func removeChild(parent Context, child canceler) { - p, ok := parentCancelCtx(parent) - if !ok { - return - } - p.mu.Lock() - if p.children != nil { - delete(p.children, child) - } - p.mu.Unlock() -} - -// A canceler is a context type that can be canceled directly. The -// implementations are *cancelCtx and *timerCtx. -type canceler interface { - cancel(removeFromParent bool, err error) - Done() <-chan struct{} -} - -// A cancelCtx can be canceled. When canceled, it also cancels any children -// that implement canceler. -type cancelCtx struct { - Context - - done chan struct{} // closed by the first cancel call. - - mu sync.Mutex - children map[canceler]bool // set to nil by the first cancel call - err error // set to non-nil by the first cancel call -} - -func (c *cancelCtx) Done() <-chan struct{} { - return c.done -} - -func (c *cancelCtx) Err() error { - c.mu.Lock() - defer c.mu.Unlock() - return c.err -} - -func (c *cancelCtx) String() string { - return fmt.Sprintf("%v.WithCancel", c.Context) -} - -// cancel closes c.done, cancels each of c's children, and, if -// removeFromParent is true, removes c from its parent's children. -func (c *cancelCtx) cancel(removeFromParent bool, err error) { - if err == nil { - panic("context: internal error: missing cancel error") - } - c.mu.Lock() - if c.err != nil { - c.mu.Unlock() - return // already canceled - } - c.err = err - close(c.done) - for child := range c.children { - // NOTE: acquiring the child's lock while holding parent's lock. - child.cancel(false, err) - } - c.children = nil - c.mu.Unlock() - - if removeFromParent { - removeChild(c.Context, c) - } -} - -// WithDeadline returns a copy of the parent context with the deadline adjusted -// to be no later than d. If the parent's deadline is already earlier than d, -// WithDeadline(parent, d) is semantically equivalent to parent. The returned -// context's Done channel is closed when the deadline expires, when the returned -// cancel function is called, or when the parent context's Done channel is -// closed, whichever happens first. -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete. -func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { - if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { - // The current deadline is already sooner than the new one. - return WithCancel(parent) - } - c := &timerCtx{ - cancelCtx: newCancelCtx(parent), - deadline: deadline, - } - propagateCancel(parent, c) - d := deadline.Sub(time.Now()) - if d <= 0 { - c.cancel(true, DeadlineExceeded) // deadline has already passed - return c, func() { c.cancel(true, Canceled) } - } - c.mu.Lock() - defer c.mu.Unlock() - if c.err == nil { - c.timer = time.AfterFunc(d, func() { - c.cancel(true, DeadlineExceeded) - }) - } - return c, func() { c.cancel(true, Canceled) } -} - -// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to -// implement Done and Err. It implements cancel by stopping its timer then -// delegating to cancelCtx.cancel. -type timerCtx struct { - *cancelCtx - timer *time.Timer // Under cancelCtx.mu. - - deadline time.Time -} - -func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { - return c.deadline, true -} - -func (c *timerCtx) String() string { - return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) -} - -func (c *timerCtx) cancel(removeFromParent bool, err error) { - c.cancelCtx.cancel(false, err) - if removeFromParent { - // Remove this timerCtx from its parent cancelCtx's children. - removeChild(c.cancelCtx.Context, c) - } - c.mu.Lock() - if c.timer != nil { - c.timer.Stop() - c.timer = nil - } - c.mu.Unlock() -} - -// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). -// -// Canceling this context releases resources associated with it, so code should -// call cancel as soon as the operations running in this Context complete: -// -// func slowOperationWithTimeout(ctx context.Context) (Result, error) { -// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) -// defer cancel() // releases resources if slowOperation completes before timeout elapses -// return slowOperation(ctx) -// } -func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { - return WithDeadline(parent, time.Now().Add(timeout)) -} - -// WithValue returns a copy of parent in which the value associated with key is -// val. -// -// Use context Values only for request-scoped data that transits processes and -// APIs, not for passing optional parameters to functions. -func WithValue(parent Context, key interface{}, val interface{}) Context { - return &valueCtx{parent, key, val} -} - -// A valueCtx carries a key-value pair. It implements Value for that key and -// delegates all other calls to the embedded Context. -type valueCtx struct { - Context - key, val interface{} -} - -func (c *valueCtx) String() string { - return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) -} - -func (c *valueCtx) Value(key interface{}) interface{} { - if c.key == key { - return c.val - } - return c.Context.Value(key) -} diff --git a/go-controller/vendor/golang.org/x/net/context/pre_go19.go b/go-controller/vendor/golang.org/x/net/context/pre_go19.go deleted file mode 100644 index ec5a638033..0000000000 --- a/go-controller/vendor/golang.org/x/net/context/pre_go19.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.9 - -package context - -import "time" - -// A Context carries a deadline, a cancelation signal, and other values across -// API boundaries. -// -// Context's methods may be called by multiple goroutines simultaneously. -type Context interface { - // Deadline returns the time when work done on behalf of this context - // should be canceled. Deadline returns ok==false when no deadline is - // set. Successive calls to Deadline return the same results. - Deadline() (deadline time.Time, ok bool) - - // Done returns a channel that's closed when work done on behalf of this - // context should be canceled. Done may return nil if this context can - // never be canceled. Successive calls to Done return the same value. - // - // WithCancel arranges for Done to be closed when cancel is called; - // WithDeadline arranges for Done to be closed when the deadline - // expires; WithTimeout arranges for Done to be closed when the timeout - // elapses. - // - // Done is provided for use in select statements: - // - // // Stream generates values with DoSomething and sends them to out - // // until DoSomething returns an error or ctx.Done is closed. - // func Stream(ctx context.Context, out chan<- Value) error { - // for { - // v, err := DoSomething(ctx) - // if err != nil { - // return err - // } - // select { - // case <-ctx.Done(): - // return ctx.Err() - // case out <- v: - // } - // } - // } - // - // See http://blog.golang.org/pipelines for more examples of how to use - // a Done channel for cancelation. - Done() <-chan struct{} - - // Err returns a non-nil error value after Done is closed. Err returns - // Canceled if the context was canceled or DeadlineExceeded if the - // context's deadline passed. No other values for Err are defined. - // After Done is closed, successive calls to Err return the same value. - Err() error - - // Value returns the value associated with this context for key, or nil - // if no value is associated with key. Successive calls to Value with - // the same key returns the same result. - // - // Use context values only for request-scoped data that transits - // processes and API boundaries, not for passing optional parameters to - // functions. - // - // A key identifies a specific value in a Context. Functions that wish - // to store values in Context typically allocate a key in a global - // variable then use that key as the argument to context.WithValue and - // Context.Value. A key can be any type that supports equality; - // packages should define keys as an unexported type to avoid - // collisions. - // - // Packages that define a Context key should provide type-safe accessors - // for the values stores using that key: - // - // // Package user defines a User type that's stored in Contexts. - // package user - // - // import "golang.org/x/net/context" - // - // // User is the type of value stored in the Contexts. - // type User struct {...} - // - // // key is an unexported type for keys defined in this package. - // // This prevents collisions with keys defined in other packages. - // type key int - // - // // userKey is the key for user.User values in Contexts. It is - // // unexported; clients use user.NewContext and user.FromContext - // // instead of using this key directly. - // var userKey key = 0 - // - // // NewContext returns a new Context that carries value u. - // func NewContext(ctx context.Context, u *User) context.Context { - // return context.WithValue(ctx, userKey, u) - // } - // - // // FromContext returns the User value stored in ctx, if any. - // func FromContext(ctx context.Context) (*User, bool) { - // u, ok := ctx.Value(userKey).(*User) - // return u, ok - // } - Value(key interface{}) interface{} -} - -// A CancelFunc tells an operation to abandon its work. -// A CancelFunc does not wait for the work to stop. -// After the first call, subsequent calls to a CancelFunc do nothing. -type CancelFunc func() diff --git a/go-controller/vendor/golang.org/x/net/html/atom/table.go b/go-controller/vendor/golang.org/x/net/html/atom/table.go index 2a938864cb..b460e6f722 100644 --- a/go-controller/vendor/golang.org/x/net/html/atom/table.go +++ b/go-controller/vendor/golang.org/x/net/html/atom/table.go @@ -11,23 +11,23 @@ const ( AcceptCharset Atom = 0x1a0e Accesskey Atom = 0x2c09 Acronym Atom = 0xaa07 - Action Atom = 0x27206 - Address Atom = 0x6f307 + Action Atom = 0x26506 + Address Atom = 0x6f107 Align Atom = 0xb105 - Allowfullscreen Atom = 0x2080f + Allowfullscreen Atom = 0x3280f Allowpaymentrequest Atom = 0xc113 Allowusermedia Atom = 0xdd0e Alt Atom = 0xf303 Annotation Atom = 0x1c90a AnnotationXml Atom = 0x1c90e - Applet Atom = 0x31906 - Area Atom = 0x35604 - Article Atom = 0x3fc07 + Applet Atom = 0x30806 + Area Atom = 0x35004 + Article Atom = 0x3f607 As Atom = 0x3c02 Aside Atom = 0x10705 Async Atom = 0xff05 Audio Atom = 0x11505 - Autocomplete Atom = 0x2780c + Autocomplete Atom = 0x26b0c Autofocus Atom = 0x12109 Autoplay Atom = 0x13c08 B Atom = 0x101 @@ -43,34 +43,34 @@ const ( Br Atom = 0x202 Button Atom = 0x19106 Canvas Atom = 0x10306 - Caption Atom = 0x23107 - Center Atom = 0x22006 - Challenge Atom = 0x29b09 + Caption Atom = 0x22407 + Center Atom = 0x21306 + Challenge Atom = 0x28e09 Charset Atom = 0x2107 - Checked Atom = 0x47907 + Checked Atom = 0x5b507 Cite Atom = 0x19c04 - Class Atom = 0x56405 - Code Atom = 0x5c504 + Class Atom = 0x55805 + Code Atom = 0x5ee04 Col Atom = 0x1ab03 Colgroup Atom = 0x1ab08 Color Atom = 0x1bf05 Cols Atom = 0x1c404 Colspan Atom = 0x1c407 Command Atom = 0x1d707 - Content Atom = 0x58b07 - Contenteditable Atom = 0x58b0f - Contextmenu Atom = 0x3800b + Content Atom = 0x57b07 + Contenteditable Atom = 0x57b0f + Contextmenu Atom = 0x37a0b Controls Atom = 0x1de08 - Coords Atom = 0x1ea06 - Crossorigin Atom = 0x1fb0b - Data Atom = 0x4a504 - Datalist Atom = 0x4a508 - Datetime Atom = 0x2b808 - Dd Atom = 0x2d702 + Coords Atom = 0x1f006 + Crossorigin Atom = 0x1fa0b + Data Atom = 0x49904 + Datalist Atom = 0x49908 + Datetime Atom = 0x2ab08 + Dd Atom = 0x2bf02 Default Atom = 0x10a07 - Defer Atom = 0x5c705 - Del Atom = 0x45203 - Desc Atom = 0x56104 + Defer Atom = 0x5f005 + Del Atom = 0x44c03 + Desc Atom = 0x55504 Details Atom = 0x7207 Dfn Atom = 0x8703 Dialog Atom = 0xbb06 @@ -78,106 +78,106 @@ const ( Dirname Atom = 0x9307 Disabled Atom = 0x16408 Div Atom = 0x16b03 - Dl Atom = 0x5e602 - Download Atom = 0x46308 + Dl Atom = 0x5d602 + Download Atom = 0x45d08 Draggable Atom = 0x17a09 - Dropzone Atom = 0x40508 - Dt Atom = 0x64b02 + Dropzone Atom = 0x3ff08 + Dt Atom = 0x64002 Em Atom = 0x6e02 Embed Atom = 0x6e05 - Enctype Atom = 0x28d07 - Face Atom = 0x21e04 - Fieldset Atom = 0x22608 - Figcaption Atom = 0x22e0a - Figure Atom = 0x24806 + Enctype Atom = 0x28007 + Face Atom = 0x21104 + Fieldset Atom = 0x21908 + Figcaption Atom = 0x2210a + Figure Atom = 0x23b06 Font Atom = 0x3f04 Footer Atom = 0xf606 - For Atom = 0x25403 - ForeignObject Atom = 0x2540d - Foreignobject Atom = 0x2610d - Form Atom = 0x26e04 - Formaction Atom = 0x26e0a - Formenctype Atom = 0x2890b - Formmethod Atom = 0x2a40a - Formnovalidate Atom = 0x2ae0e - Formtarget Atom = 0x2c00a + For Atom = 0x24703 + ForeignObject Atom = 0x2470d + Foreignobject Atom = 0x2540d + Form Atom = 0x26104 + Formaction Atom = 0x2610a + Formenctype Atom = 0x27c0b + Formmethod Atom = 0x2970a + Formnovalidate Atom = 0x2a10e + Formtarget Atom = 0x2b30a Frame Atom = 0x8b05 Frameset Atom = 0x8b08 H1 Atom = 0x15c02 - H2 Atom = 0x2de02 - H3 Atom = 0x30d02 - H4 Atom = 0x34502 - H5 Atom = 0x34f02 - H6 Atom = 0x64d02 - Head Atom = 0x33104 - Header Atom = 0x33106 - Headers Atom = 0x33107 + H2 Atom = 0x56102 + H3 Atom = 0x2cd02 + H4 Atom = 0x2fc02 + H5 Atom = 0x33f02 + H6 Atom = 0x34902 + Head Atom = 0x32004 + Header Atom = 0x32006 + Headers Atom = 0x32007 Height Atom = 0x5206 - Hgroup Atom = 0x2ca06 - Hidden Atom = 0x2d506 - High Atom = 0x2db04 + Hgroup Atom = 0x64206 + Hidden Atom = 0x2bd06 + High Atom = 0x2ca04 Hr Atom = 0x15702 - Href Atom = 0x2e004 - Hreflang Atom = 0x2e008 + Href Atom = 0x2cf04 + Hreflang Atom = 0x2cf08 Html Atom = 0x5604 - HttpEquiv Atom = 0x2e80a + HttpEquiv Atom = 0x2d70a I Atom = 0x601 - Icon Atom = 0x58a04 + Icon Atom = 0x57a04 Id Atom = 0x10902 - Iframe Atom = 0x2fc06 - Image Atom = 0x30205 - Img Atom = 0x30703 - Input Atom = 0x44b05 - Inputmode Atom = 0x44b09 - Ins Atom = 0x20403 - Integrity Atom = 0x23f09 + Iframe Atom = 0x2eb06 + Image Atom = 0x2f105 + Img Atom = 0x2f603 + Input Atom = 0x44505 + Inputmode Atom = 0x44509 + Ins Atom = 0x20303 + Integrity Atom = 0x23209 Is Atom = 0x16502 - Isindex Atom = 0x30f07 - Ismap Atom = 0x31605 - Itemid Atom = 0x38b06 + Isindex Atom = 0x2fe07 + Ismap Atom = 0x30505 + Itemid Atom = 0x38506 Itemprop Atom = 0x19d08 - Itemref Atom = 0x3cd07 - Itemscope Atom = 0x67109 - Itemtype Atom = 0x31f08 + Itemref Atom = 0x3c707 + Itemscope Atom = 0x66f09 + Itemtype Atom = 0x30e08 Kbd Atom = 0xb903 Keygen Atom = 0x3206 Keytype Atom = 0xd607 Kind Atom = 0x17704 Label Atom = 0x5905 - Lang Atom = 0x2e404 + Lang Atom = 0x2d304 Legend Atom = 0x18106 Li Atom = 0xb202 Link Atom = 0x17404 - List Atom = 0x4a904 - Listing Atom = 0x4a907 + List Atom = 0x49d04 + Listing Atom = 0x49d07 Loop Atom = 0x5d04 Low Atom = 0xc303 Main Atom = 0x1004 Malignmark Atom = 0xb00a - Manifest Atom = 0x6d708 - Map Atom = 0x31803 + Manifest Atom = 0x6d508 + Map Atom = 0x30703 Mark Atom = 0xb604 - Marquee Atom = 0x32707 - Math Atom = 0x32e04 - Max Atom = 0x33d03 - Maxlength Atom = 0x33d09 + Marquee Atom = 0x31607 + Math Atom = 0x31d04 + Max Atom = 0x33703 + Maxlength Atom = 0x33709 Media Atom = 0xe605 Mediagroup Atom = 0xe60a - Menu Atom = 0x38704 - Menuitem Atom = 0x38708 - Meta Atom = 0x4b804 + Menu Atom = 0x38104 + Menuitem Atom = 0x38108 + Meta Atom = 0x4ac04 Meter Atom = 0x9805 - Method Atom = 0x2a806 - Mglyph Atom = 0x30806 - Mi Atom = 0x34702 - Min Atom = 0x34703 - Minlength Atom = 0x34709 - Mn Atom = 0x2b102 + Method Atom = 0x29b06 + Mglyph Atom = 0x2f706 + Mi Atom = 0x34102 + Min Atom = 0x34103 + Minlength Atom = 0x34109 + Mn Atom = 0x2a402 Mo Atom = 0xa402 - Ms Atom = 0x67402 - Mtext Atom = 0x35105 - Multiple Atom = 0x35f08 - Muted Atom = 0x36705 + Ms Atom = 0x67202 + Mtext Atom = 0x34b05 + Multiple Atom = 0x35908 + Muted Atom = 0x36105 Name Atom = 0x9604 Nav Atom = 0x1303 Nobr Atom = 0x3704 @@ -185,101 +185,101 @@ const ( Noframes Atom = 0x8908 Nomodule Atom = 0xa208 Nonce Atom = 0x1a605 - Noscript Atom = 0x21608 - Novalidate Atom = 0x2b20a - Object Atom = 0x26806 + Noscript Atom = 0x2c208 + Novalidate Atom = 0x2a50a + Object Atom = 0x25b06 Ol Atom = 0x13702 Onabort Atom = 0x19507 - Onafterprint Atom = 0x2360c - Onautocomplete Atom = 0x2760e - Onautocompleteerror Atom = 0x27613 - Onauxclick Atom = 0x61f0a - Onbeforeprint Atom = 0x69e0d - Onbeforeunload Atom = 0x6e70e - Onblur Atom = 0x56d06 + Onafterprint Atom = 0x2290c + Onautocomplete Atom = 0x2690e + Onautocompleteerror Atom = 0x26913 + Onauxclick Atom = 0x6140a + Onbeforeprint Atom = 0x69c0d + Onbeforeunload Atom = 0x6e50e + Onblur Atom = 0x1ea06 Oncancel Atom = 0x11908 Oncanplay Atom = 0x14d09 Oncanplaythrough Atom = 0x14d10 - Onchange Atom = 0x41b08 - Onclick Atom = 0x2f507 - Onclose Atom = 0x36c07 - Oncontextmenu Atom = 0x37e0d - Oncopy Atom = 0x39106 - Oncuechange Atom = 0x3970b - Oncut Atom = 0x3a205 - Ondblclick Atom = 0x3a70a - Ondrag Atom = 0x3b106 - Ondragend Atom = 0x3b109 - Ondragenter Atom = 0x3ba0b - Ondragexit Atom = 0x3c50a - Ondragleave Atom = 0x3df0b - Ondragover Atom = 0x3ea0a - Ondragstart Atom = 0x3f40b - Ondrop Atom = 0x40306 - Ondurationchange Atom = 0x41310 - Onemptied Atom = 0x40a09 - Onended Atom = 0x42307 - Onerror Atom = 0x42a07 - Onfocus Atom = 0x43107 - Onhashchange Atom = 0x43d0c - Oninput Atom = 0x44907 - Oninvalid Atom = 0x45509 - Onkeydown Atom = 0x45e09 - Onkeypress Atom = 0x46b0a - Onkeyup Atom = 0x48007 - Onlanguagechange Atom = 0x48d10 - Onload Atom = 0x49d06 - Onloadeddata Atom = 0x49d0c - Onloadedmetadata Atom = 0x4b010 - Onloadend Atom = 0x4c609 - Onloadstart Atom = 0x4cf0b - Onmessage Atom = 0x4da09 - Onmessageerror Atom = 0x4da0e - Onmousedown Atom = 0x4e80b - Onmouseenter Atom = 0x4f30c - Onmouseleave Atom = 0x4ff0c - Onmousemove Atom = 0x50b0b - Onmouseout Atom = 0x5160a - Onmouseover Atom = 0x5230b - Onmouseup Atom = 0x52e09 - Onmousewheel Atom = 0x53c0c - Onoffline Atom = 0x54809 - Ononline Atom = 0x55108 - Onpagehide Atom = 0x5590a - Onpageshow Atom = 0x5730a - Onpaste Atom = 0x57f07 - Onpause Atom = 0x59a07 - Onplay Atom = 0x5a406 - Onplaying Atom = 0x5a409 - Onpopstate Atom = 0x5ad0a - Onprogress Atom = 0x5b70a - Onratechange Atom = 0x5cc0c - Onrejectionhandled Atom = 0x5d812 - Onreset Atom = 0x5ea07 - Onresize Atom = 0x5f108 - Onscroll Atom = 0x60008 - Onsecuritypolicyviolation Atom = 0x60819 - Onseeked Atom = 0x62908 - Onseeking Atom = 0x63109 - Onselect Atom = 0x63a08 - Onshow Atom = 0x64406 - Onsort Atom = 0x64f06 - Onstalled Atom = 0x65909 - Onstorage Atom = 0x66209 - Onsubmit Atom = 0x66b08 - Onsuspend Atom = 0x67b09 + Onchange Atom = 0x41508 + Onclick Atom = 0x2e407 + Onclose Atom = 0x36607 + Oncontextmenu Atom = 0x3780d + Oncopy Atom = 0x38b06 + Oncuechange Atom = 0x3910b + Oncut Atom = 0x39c05 + Ondblclick Atom = 0x3a10a + Ondrag Atom = 0x3ab06 + Ondragend Atom = 0x3ab09 + Ondragenter Atom = 0x3b40b + Ondragexit Atom = 0x3bf0a + Ondragleave Atom = 0x3d90b + Ondragover Atom = 0x3e40a + Ondragstart Atom = 0x3ee0b + Ondrop Atom = 0x3fd06 + Ondurationchange Atom = 0x40d10 + Onemptied Atom = 0x40409 + Onended Atom = 0x41d07 + Onerror Atom = 0x42407 + Onfocus Atom = 0x42b07 + Onhashchange Atom = 0x4370c + Oninput Atom = 0x44307 + Oninvalid Atom = 0x44f09 + Onkeydown Atom = 0x45809 + Onkeypress Atom = 0x4650a + Onkeyup Atom = 0x47407 + Onlanguagechange Atom = 0x48110 + Onload Atom = 0x49106 + Onloadeddata Atom = 0x4910c + Onloadedmetadata Atom = 0x4a410 + Onloadend Atom = 0x4ba09 + Onloadstart Atom = 0x4c30b + Onmessage Atom = 0x4ce09 + Onmessageerror Atom = 0x4ce0e + Onmousedown Atom = 0x4dc0b + Onmouseenter Atom = 0x4e70c + Onmouseleave Atom = 0x4f30c + Onmousemove Atom = 0x4ff0b + Onmouseout Atom = 0x50a0a + Onmouseover Atom = 0x5170b + Onmouseup Atom = 0x52209 + Onmousewheel Atom = 0x5300c + Onoffline Atom = 0x53c09 + Ononline Atom = 0x54508 + Onpagehide Atom = 0x54d0a + Onpageshow Atom = 0x5630a + Onpaste Atom = 0x56f07 + Onpause Atom = 0x58a07 + Onplay Atom = 0x59406 + Onplaying Atom = 0x59409 + Onpopstate Atom = 0x59d0a + Onprogress Atom = 0x5a70a + Onratechange Atom = 0x5bc0c + Onrejectionhandled Atom = 0x5c812 + Onreset Atom = 0x5da07 + Onresize Atom = 0x5e108 + Onscroll Atom = 0x5f508 + Onsecuritypolicyviolation Atom = 0x5fd19 + Onseeked Atom = 0x61e08 + Onseeking Atom = 0x62609 + Onselect Atom = 0x62f08 + Onshow Atom = 0x63906 + Onsort Atom = 0x64d06 + Onstalled Atom = 0x65709 + Onstorage Atom = 0x66009 + Onsubmit Atom = 0x66908 + Onsuspend Atom = 0x67909 Ontimeupdate Atom = 0x400c - Ontoggle Atom = 0x68408 - Onunhandledrejection Atom = 0x68c14 - Onunload Atom = 0x6ab08 - Onvolumechange Atom = 0x6b30e - Onwaiting Atom = 0x6c109 - Onwheel Atom = 0x6ca07 + Ontoggle Atom = 0x68208 + Onunhandledrejection Atom = 0x68a14 + Onunload Atom = 0x6a908 + Onvolumechange Atom = 0x6b10e + Onwaiting Atom = 0x6bf09 + Onwheel Atom = 0x6c807 Open Atom = 0x1a304 Optgroup Atom = 0x5f08 - Optimum Atom = 0x6d107 - Option Atom = 0x6e306 - Output Atom = 0x51d06 + Optimum Atom = 0x6cf07 + Option Atom = 0x6e106 + Output Atom = 0x51106 P Atom = 0xc01 Param Atom = 0xc05 Pattern Atom = 0x6607 @@ -288,466 +288,468 @@ const ( Placeholder Atom = 0x1310b Plaintext Atom = 0x1b209 Playsinline Atom = 0x1400b - Poster Atom = 0x2cf06 - Pre Atom = 0x47003 - Preload Atom = 0x48607 - Progress Atom = 0x5b908 - Prompt Atom = 0x53606 - Public Atom = 0x58606 + Poster Atom = 0x64706 + Pre Atom = 0x46a03 + Preload Atom = 0x47a07 + Progress Atom = 0x5a908 + Prompt Atom = 0x52a06 + Public Atom = 0x57606 Q Atom = 0xcf01 Radiogroup Atom = 0x30a Rb Atom = 0x3a02 - Readonly Atom = 0x35708 - Referrerpolicy Atom = 0x3d10e - Rel Atom = 0x48703 - Required Atom = 0x24c08 + Readonly Atom = 0x35108 + Referrerpolicy Atom = 0x3cb0e + Rel Atom = 0x47b03 + Required Atom = 0x23f08 Reversed Atom = 0x8008 Rows Atom = 0x9c04 Rowspan Atom = 0x9c07 - Rp Atom = 0x23c02 + Rp Atom = 0x22f02 Rt Atom = 0x19a02 Rtc Atom = 0x19a03 Ruby Atom = 0xfb04 S Atom = 0x2501 Samp Atom = 0x7804 Sandbox Atom = 0x12907 - Scope Atom = 0x67505 - Scoped Atom = 0x67506 - Script Atom = 0x21806 - Seamless Atom = 0x37108 - Section Atom = 0x56807 - Select Atom = 0x63c06 - Selected Atom = 0x63c08 - Shape Atom = 0x1e505 - Size Atom = 0x5f504 - Sizes Atom = 0x5f505 - Slot Atom = 0x1ef04 - Small Atom = 0x20605 - Sortable Atom = 0x65108 - Sorted Atom = 0x33706 - Source Atom = 0x37806 - Spacer Atom = 0x43706 + Scope Atom = 0x67305 + Scoped Atom = 0x67306 + Script Atom = 0x2c406 + Seamless Atom = 0x36b08 + Search Atom = 0x55c06 + Section Atom = 0x1e507 + Select Atom = 0x63106 + Selected Atom = 0x63108 + Shape Atom = 0x1f505 + Size Atom = 0x5e504 + Sizes Atom = 0x5e505 + Slot Atom = 0x20504 + Small Atom = 0x32605 + Sortable Atom = 0x64f08 + Sorted Atom = 0x37206 + Source Atom = 0x43106 + Spacer Atom = 0x46e06 Span Atom = 0x9f04 - Spellcheck Atom = 0x4740a - Src Atom = 0x5c003 - Srcdoc Atom = 0x5c006 - Srclang Atom = 0x5f907 - Srcset Atom = 0x6f906 - Start Atom = 0x3fa05 - Step Atom = 0x58304 + Spellcheck Atom = 0x5b00a + Src Atom = 0x5e903 + Srcdoc Atom = 0x5e906 + Srclang Atom = 0x6f707 + Srcset Atom = 0x6fe06 + Start Atom = 0x3f405 + Step Atom = 0x57304 Strike Atom = 0xd206 - Strong Atom = 0x6dd06 - Style Atom = 0x6ff05 - Sub Atom = 0x66d03 - Summary Atom = 0x70407 - Sup Atom = 0x70b03 - Svg Atom = 0x70e03 - System Atom = 0x71106 - Tabindex Atom = 0x4be08 - Table Atom = 0x59505 - Target Atom = 0x2c406 + Strong Atom = 0x6db06 + Style Atom = 0x70405 + Sub Atom = 0x66b03 + Summary Atom = 0x70907 + Sup Atom = 0x71003 + Svg Atom = 0x71303 + System Atom = 0x71606 + Tabindex Atom = 0x4b208 + Table Atom = 0x58505 + Target Atom = 0x2b706 Tbody Atom = 0x2705 Td Atom = 0x9202 - Template Atom = 0x71408 - Textarea Atom = 0x35208 + Template Atom = 0x71908 + Textarea Atom = 0x34c08 Tfoot Atom = 0xf505 Th Atom = 0x15602 - Thead Atom = 0x33005 + Thead Atom = 0x31f05 Time Atom = 0x4204 Title Atom = 0x11005 Tr Atom = 0xcc02 Track Atom = 0x1ba05 - Translate Atom = 0x1f209 + Translate Atom = 0x20809 Tt Atom = 0x6802 Type Atom = 0xd904 - Typemustmatch Atom = 0x2900d + Typemustmatch Atom = 0x2830d U Atom = 0xb01 Ul Atom = 0xa702 Updateviacache Atom = 0x460e - Usemap Atom = 0x59e06 + Usemap Atom = 0x58e06 Value Atom = 0x1505 Var Atom = 0x16d03 - Video Atom = 0x2f105 - Wbr Atom = 0x57c03 - Width Atom = 0x64905 - Workertype Atom = 0x71c0a - Wrap Atom = 0x72604 + Video Atom = 0x2e005 + Wbr Atom = 0x56c03 + Width Atom = 0x63e05 + Workertype Atom = 0x7210a + Wrap Atom = 0x72b04 Xmp Atom = 0x12f03 ) -const hash0 = 0x81cdf10e +const hash0 = 0x84f70e16 const maxAtomLen = 25 var table = [1 << 9]Atom{ - 0x1: 0xe60a, // mediagroup - 0x2: 0x2e404, // lang - 0x4: 0x2c09, // accesskey - 0x5: 0x8b08, // frameset - 0x7: 0x63a08, // onselect - 0x8: 0x71106, // system - 0xa: 0x64905, // width - 0xc: 0x2890b, // formenctype - 0xd: 0x13702, // ol - 0xe: 0x3970b, // oncuechange - 0x10: 0x14b03, // bdo - 0x11: 0x11505, // audio - 0x12: 0x17a09, // draggable - 0x14: 0x2f105, // video - 0x15: 0x2b102, // mn - 0x16: 0x38704, // menu - 0x17: 0x2cf06, // poster - 0x19: 0xf606, // footer - 0x1a: 0x2a806, // method - 0x1b: 0x2b808, // datetime - 0x1c: 0x19507, // onabort - 0x1d: 0x460e, // updateviacache - 0x1e: 0xff05, // async - 0x1f: 0x49d06, // onload - 0x21: 0x11908, // oncancel - 0x22: 0x62908, // onseeked - 0x23: 0x30205, // image - 0x24: 0x5d812, // onrejectionhandled - 0x26: 0x17404, // link - 0x27: 0x51d06, // output - 0x28: 0x33104, // head - 0x29: 0x4ff0c, // onmouseleave - 0x2a: 0x57f07, // onpaste - 0x2b: 0x5a409, // onplaying - 0x2c: 0x1c407, // colspan - 0x2f: 0x1bf05, // color - 0x30: 0x5f504, // size - 0x31: 0x2e80a, // http-equiv - 0x33: 0x601, // i - 0x34: 0x5590a, // onpagehide - 0x35: 0x68c14, // onunhandledrejection - 0x37: 0x42a07, // onerror - 0x3a: 0x3b08, // basefont - 0x3f: 0x1303, // nav - 0x40: 0x17704, // kind - 0x41: 0x35708, // readonly - 0x42: 0x30806, // mglyph - 0x44: 0xb202, // li - 0x46: 0x2d506, // hidden - 0x47: 0x70e03, // svg - 0x48: 0x58304, // step - 0x49: 0x23f09, // integrity - 0x4a: 0x58606, // public - 0x4c: 0x1ab03, // col - 0x4d: 0x1870a, // blockquote - 0x4e: 0x34f02, // h5 - 0x50: 0x5b908, // progress - 0x51: 0x5f505, // sizes - 0x52: 0x34502, // h4 - 0x56: 0x33005, // thead - 0x57: 0xd607, // keytype - 0x58: 0x5b70a, // onprogress - 0x59: 0x44b09, // inputmode - 0x5a: 0x3b109, // ondragend - 0x5d: 0x3a205, // oncut - 0x5e: 0x43706, // spacer - 0x5f: 0x1ab08, // colgroup - 0x62: 0x16502, // is - 0x65: 0x3c02, // as - 0x66: 0x54809, // onoffline - 0x67: 0x33706, // sorted - 0x69: 0x48d10, // onlanguagechange - 0x6c: 0x43d0c, // onhashchange - 0x6d: 0x9604, // name - 0x6e: 0xf505, // tfoot - 0x6f: 0x56104, // desc - 0x70: 0x33d03, // max - 0x72: 0x1ea06, // coords - 0x73: 0x30d02, // h3 - 0x74: 0x6e70e, // onbeforeunload - 0x75: 0x9c04, // rows - 0x76: 0x63c06, // select - 0x77: 0x9805, // meter - 0x78: 0x38b06, // itemid - 0x79: 0x53c0c, // onmousewheel - 0x7a: 0x5c006, // srcdoc - 0x7d: 0x1ba05, // track - 0x7f: 0x31f08, // itemtype - 0x82: 0xa402, // mo - 0x83: 0x41b08, // onchange - 0x84: 0x33107, // headers - 0x85: 0x5cc0c, // onratechange - 0x86: 0x60819, // onsecuritypolicyviolation - 0x88: 0x4a508, // datalist - 0x89: 0x4e80b, // onmousedown - 0x8a: 0x1ef04, // slot - 0x8b: 0x4b010, // onloadedmetadata - 0x8c: 0x1a06, // accept - 0x8d: 0x26806, // object - 0x91: 0x6b30e, // onvolumechange - 0x92: 0x2107, // charset - 0x93: 0x27613, // onautocompleteerror - 0x94: 0xc113, // allowpaymentrequest - 0x95: 0x2804, // body - 0x96: 0x10a07, // default - 0x97: 0x63c08, // selected - 0x98: 0x21e04, // face - 0x99: 0x1e505, // shape - 0x9b: 0x68408, // ontoggle - 0x9e: 0x64b02, // dt - 0x9f: 0xb604, // mark - 0xa1: 0xb01, // u - 0xa4: 0x6ab08, // onunload - 0xa5: 0x5d04, // loop - 0xa6: 0x16408, // disabled - 0xaa: 0x42307, // onended - 0xab: 0xb00a, // malignmark - 0xad: 0x67b09, // onsuspend - 0xae: 0x35105, // mtext - 0xaf: 0x64f06, // onsort - 0xb0: 0x19d08, // itemprop - 0xb3: 0x67109, // itemscope - 0xb4: 0x17305, // blink - 0xb6: 0x3b106, // ondrag - 0xb7: 0xa702, // ul - 0xb8: 0x26e04, // form - 0xb9: 0x12907, // sandbox - 0xba: 0x8b05, // frame - 0xbb: 0x1505, // value - 0xbc: 0x66209, // onstorage - 0xbf: 0xaa07, // acronym - 0xc0: 0x19a02, // rt - 0xc2: 0x202, // br - 0xc3: 0x22608, // fieldset - 0xc4: 0x2900d, // typemustmatch - 0xc5: 0xa208, // nomodule - 0xc6: 0x6c07, // noembed - 0xc7: 0x69e0d, // onbeforeprint - 0xc8: 0x19106, // button - 0xc9: 0x2f507, // onclick - 0xca: 0x70407, // summary - 0xcd: 0xfb04, // ruby - 0xce: 0x56405, // class - 0xcf: 0x3f40b, // ondragstart - 0xd0: 0x23107, // caption - 0xd4: 0xdd0e, // allowusermedia - 0xd5: 0x4cf0b, // onloadstart - 0xd9: 0x16b03, // div - 0xda: 0x4a904, // list - 0xdb: 0x32e04, // math - 0xdc: 0x44b05, // input - 0xdf: 0x3ea0a, // ondragover - 0xe0: 0x2de02, // h2 - 0xe2: 0x1b209, // plaintext - 0xe4: 0x4f30c, // onmouseenter - 0xe7: 0x47907, // checked - 0xe8: 0x47003, // pre - 0xea: 0x35f08, // multiple - 0xeb: 0xba03, // bdi - 0xec: 0x33d09, // maxlength - 0xed: 0xcf01, // q - 0xee: 0x61f0a, // onauxclick - 0xf0: 0x57c03, // wbr - 0xf2: 0x3b04, // base - 0xf3: 0x6e306, // option - 0xf5: 0x41310, // ondurationchange - 0xf7: 0x8908, // noframes - 0xf9: 0x40508, // dropzone - 0xfb: 0x67505, // scope - 0xfc: 0x8008, // reversed - 0xfd: 0x3ba0b, // ondragenter - 0xfe: 0x3fa05, // start - 0xff: 0x12f03, // xmp - 0x100: 0x5f907, // srclang - 0x101: 0x30703, // img - 0x104: 0x101, // b - 0x105: 0x25403, // for - 0x106: 0x10705, // aside - 0x107: 0x44907, // oninput - 0x108: 0x35604, // area - 0x109: 0x2a40a, // formmethod - 0x10a: 0x72604, // wrap - 0x10c: 0x23c02, // rp - 0x10d: 0x46b0a, // onkeypress - 0x10e: 0x6802, // tt - 0x110: 0x34702, // mi - 0x111: 0x36705, // muted - 0x112: 0xf303, // alt - 0x113: 0x5c504, // code - 0x114: 0x6e02, // em - 0x115: 0x3c50a, // ondragexit - 0x117: 0x9f04, // span - 0x119: 0x6d708, // manifest - 0x11a: 0x38708, // menuitem - 0x11b: 0x58b07, // content - 0x11d: 0x6c109, // onwaiting - 0x11f: 0x4c609, // onloadend - 0x121: 0x37e0d, // oncontextmenu - 0x123: 0x56d06, // onblur - 0x124: 0x3fc07, // article - 0x125: 0x9303, // dir - 0x126: 0xef04, // ping - 0x127: 0x24c08, // required - 0x128: 0x45509, // oninvalid - 0x129: 0xb105, // align - 0x12b: 0x58a04, // icon - 0x12c: 0x64d02, // h6 - 0x12d: 0x1c404, // cols - 0x12e: 0x22e0a, // figcaption - 0x12f: 0x45e09, // onkeydown - 0x130: 0x66b08, // onsubmit - 0x131: 0x14d09, // oncanplay - 0x132: 0x70b03, // sup - 0x133: 0xc01, // p - 0x135: 0x40a09, // onemptied - 0x136: 0x39106, // oncopy - 0x137: 0x19c04, // cite - 0x138: 0x3a70a, // ondblclick - 0x13a: 0x50b0b, // onmousemove - 0x13c: 0x66d03, // sub - 0x13d: 0x48703, // rel - 0x13e: 0x5f08, // optgroup - 0x142: 0x9c07, // rowspan - 0x143: 0x37806, // source - 0x144: 0x21608, // noscript - 0x145: 0x1a304, // open - 0x146: 0x20403, // ins - 0x147: 0x2540d, // foreignObject - 0x148: 0x5ad0a, // onpopstate - 0x14a: 0x28d07, // enctype - 0x14b: 0x2760e, // onautocomplete - 0x14c: 0x35208, // textarea - 0x14e: 0x2780c, // autocomplete - 0x14f: 0x15702, // hr - 0x150: 0x1de08, // controls - 0x151: 0x10902, // id - 0x153: 0x2360c, // onafterprint - 0x155: 0x2610d, // foreignobject - 0x156: 0x32707, // marquee - 0x157: 0x59a07, // onpause - 0x158: 0x5e602, // dl - 0x159: 0x5206, // height - 0x15a: 0x34703, // min - 0x15b: 0x9307, // dirname - 0x15c: 0x1f209, // translate - 0x15d: 0x5604, // html - 0x15e: 0x34709, // minlength - 0x15f: 0x48607, // preload - 0x160: 0x71408, // template - 0x161: 0x3df0b, // ondragleave - 0x162: 0x3a02, // rb - 0x164: 0x5c003, // src - 0x165: 0x6dd06, // strong - 0x167: 0x7804, // samp - 0x168: 0x6f307, // address - 0x169: 0x55108, // ononline - 0x16b: 0x1310b, // placeholder - 0x16c: 0x2c406, // target - 0x16d: 0x20605, // small - 0x16e: 0x6ca07, // onwheel - 0x16f: 0x1c90a, // annotation - 0x170: 0x4740a, // spellcheck - 0x171: 0x7207, // details - 0x172: 0x10306, // canvas - 0x173: 0x12109, // autofocus - 0x174: 0xc05, // param - 0x176: 0x46308, // download - 0x177: 0x45203, // del - 0x178: 0x36c07, // onclose - 0x179: 0xb903, // kbd - 0x17a: 0x31906, // applet - 0x17b: 0x2e004, // href - 0x17c: 0x5f108, // onresize - 0x17e: 0x49d0c, // onloadeddata - 0x180: 0xcc02, // tr - 0x181: 0x2c00a, // formtarget - 0x182: 0x11005, // title - 0x183: 0x6ff05, // style - 0x184: 0xd206, // strike - 0x185: 0x59e06, // usemap - 0x186: 0x2fc06, // iframe - 0x187: 0x1004, // main - 0x189: 0x7b07, // picture - 0x18c: 0x31605, // ismap - 0x18e: 0x4a504, // data - 0x18f: 0x5905, // label - 0x191: 0x3d10e, // referrerpolicy - 0x192: 0x15602, // th - 0x194: 0x53606, // prompt - 0x195: 0x56807, // section - 0x197: 0x6d107, // optimum - 0x198: 0x2db04, // high - 0x199: 0x15c02, // h1 - 0x19a: 0x65909, // onstalled - 0x19b: 0x16d03, // var - 0x19c: 0x4204, // time - 0x19e: 0x67402, // ms - 0x19f: 0x33106, // header - 0x1a0: 0x4da09, // onmessage - 0x1a1: 0x1a605, // nonce - 0x1a2: 0x26e0a, // formaction - 0x1a3: 0x22006, // center - 0x1a4: 0x3704, // nobr - 0x1a5: 0x59505, // table - 0x1a6: 0x4a907, // listing - 0x1a7: 0x18106, // legend - 0x1a9: 0x29b09, // challenge - 0x1aa: 0x24806, // figure - 0x1ab: 0xe605, // media - 0x1ae: 0xd904, // type - 0x1af: 0x3f04, // font - 0x1b0: 0x4da0e, // onmessageerror - 0x1b1: 0x37108, // seamless - 0x1b2: 0x8703, // dfn - 0x1b3: 0x5c705, // defer - 0x1b4: 0xc303, // low - 0x1b5: 0x19a03, // rtc - 0x1b6: 0x5230b, // onmouseover - 0x1b7: 0x2b20a, // novalidate - 0x1b8: 0x71c0a, // workertype - 0x1ba: 0x3cd07, // itemref - 0x1bd: 0x1, // a - 0x1be: 0x31803, // map - 0x1bf: 0x400c, // ontimeupdate - 0x1c0: 0x15e07, // bgsound - 0x1c1: 0x3206, // keygen - 0x1c2: 0x2705, // tbody - 0x1c5: 0x64406, // onshow - 0x1c7: 0x2501, // s - 0x1c8: 0x6607, // pattern - 0x1cc: 0x14d10, // oncanplaythrough - 0x1ce: 0x2d702, // dd - 0x1cf: 0x6f906, // srcset - 0x1d0: 0x17003, // big - 0x1d2: 0x65108, // sortable - 0x1d3: 0x48007, // onkeyup - 0x1d5: 0x5a406, // onplay - 0x1d7: 0x4b804, // meta - 0x1d8: 0x40306, // ondrop - 0x1da: 0x60008, // onscroll - 0x1db: 0x1fb0b, // crossorigin - 0x1dc: 0x5730a, // onpageshow - 0x1dd: 0x4, // abbr - 0x1de: 0x9202, // td - 0x1df: 0x58b0f, // contenteditable - 0x1e0: 0x27206, // action - 0x1e1: 0x1400b, // playsinline - 0x1e2: 0x43107, // onfocus - 0x1e3: 0x2e008, // hreflang - 0x1e5: 0x5160a, // onmouseout - 0x1e6: 0x5ea07, // onreset - 0x1e7: 0x13c08, // autoplay - 0x1e8: 0x63109, // onseeking - 0x1ea: 0x67506, // scoped - 0x1ec: 0x30a, // radiogroup - 0x1ee: 0x3800b, // contextmenu - 0x1ef: 0x52e09, // onmouseup - 0x1f1: 0x2ca06, // hgroup - 0x1f2: 0x2080f, // allowfullscreen - 0x1f3: 0x4be08, // tabindex - 0x1f6: 0x30f07, // isindex - 0x1f7: 0x1a0e, // accept-charset - 0x1f8: 0x2ae0e, // formnovalidate - 0x1fb: 0x1c90e, // annotation-xml - 0x1fc: 0x6e05, // embed - 0x1fd: 0x21806, // script - 0x1fe: 0xbb06, // dialog - 0x1ff: 0x1d707, // command + 0x1: 0x3ff08, // dropzone + 0x2: 0x3b08, // basefont + 0x3: 0x23209, // integrity + 0x4: 0x43106, // source + 0x5: 0x2c09, // accesskey + 0x6: 0x1a06, // accept + 0x7: 0x6c807, // onwheel + 0xb: 0x47407, // onkeyup + 0xc: 0x32007, // headers + 0xd: 0x67306, // scoped + 0xe: 0x67909, // onsuspend + 0xf: 0x8908, // noframes + 0x10: 0x1fa0b, // crossorigin + 0x11: 0x2e407, // onclick + 0x12: 0x3f405, // start + 0x13: 0x37a0b, // contextmenu + 0x14: 0x5e903, // src + 0x15: 0x1c404, // cols + 0x16: 0xbb06, // dialog + 0x17: 0x47a07, // preload + 0x18: 0x3c707, // itemref + 0x1b: 0x2f105, // image + 0x1d: 0x4ba09, // onloadend + 0x1e: 0x45d08, // download + 0x1f: 0x46a03, // pre + 0x23: 0x2970a, // formmethod + 0x24: 0x71303, // svg + 0x25: 0xcf01, // q + 0x26: 0x64002, // dt + 0x27: 0x1de08, // controls + 0x2a: 0x2804, // body + 0x2b: 0xd206, // strike + 0x2c: 0x3910b, // oncuechange + 0x2d: 0x4c30b, // onloadstart + 0x2e: 0x2fe07, // isindex + 0x2f: 0xb202, // li + 0x30: 0x1400b, // playsinline + 0x31: 0x34102, // mi + 0x32: 0x30806, // applet + 0x33: 0x4ce09, // onmessage + 0x35: 0x13702, // ol + 0x36: 0x1a304, // open + 0x39: 0x14d09, // oncanplay + 0x3a: 0x6bf09, // onwaiting + 0x3b: 0x11908, // oncancel + 0x3c: 0x6a908, // onunload + 0x3e: 0x53c09, // onoffline + 0x3f: 0x1a0e, // accept-charset + 0x40: 0x32004, // head + 0x42: 0x3ab09, // ondragend + 0x43: 0x1310b, // placeholder + 0x44: 0x2b30a, // formtarget + 0x45: 0x2540d, // foreignobject + 0x47: 0x400c, // ontimeupdate + 0x48: 0xdd0e, // allowusermedia + 0x4a: 0x69c0d, // onbeforeprint + 0x4b: 0x5604, // html + 0x4c: 0x9f04, // span + 0x4d: 0x64206, // hgroup + 0x4e: 0x16408, // disabled + 0x4f: 0x4204, // time + 0x51: 0x42b07, // onfocus + 0x53: 0xb00a, // malignmark + 0x55: 0x4650a, // onkeypress + 0x56: 0x55805, // class + 0x57: 0x1ab08, // colgroup + 0x58: 0x33709, // maxlength + 0x59: 0x5a908, // progress + 0x5b: 0x70405, // style + 0x5c: 0x2a10e, // formnovalidate + 0x5e: 0x38b06, // oncopy + 0x60: 0x26104, // form + 0x61: 0xf606, // footer + 0x64: 0x30a, // radiogroup + 0x66: 0xfb04, // ruby + 0x67: 0x4ff0b, // onmousemove + 0x68: 0x19d08, // itemprop + 0x69: 0x2d70a, // http-equiv + 0x6a: 0x15602, // th + 0x6c: 0x6e02, // em + 0x6d: 0x38108, // menuitem + 0x6e: 0x63106, // select + 0x6f: 0x48110, // onlanguagechange + 0x70: 0x31f05, // thead + 0x71: 0x15c02, // h1 + 0x72: 0x5e906, // srcdoc + 0x75: 0x9604, // name + 0x76: 0x19106, // button + 0x77: 0x55504, // desc + 0x78: 0x17704, // kind + 0x79: 0x1bf05, // color + 0x7c: 0x58e06, // usemap + 0x7d: 0x30e08, // itemtype + 0x7f: 0x6d508, // manifest + 0x81: 0x5300c, // onmousewheel + 0x82: 0x4dc0b, // onmousedown + 0x84: 0xc05, // param + 0x85: 0x2e005, // video + 0x86: 0x4910c, // onloadeddata + 0x87: 0x6f107, // address + 0x8c: 0xef04, // ping + 0x8d: 0x24703, // for + 0x8f: 0x62f08, // onselect + 0x90: 0x30703, // map + 0x92: 0xc01, // p + 0x93: 0x8008, // reversed + 0x94: 0x54d0a, // onpagehide + 0x95: 0x3206, // keygen + 0x96: 0x34109, // minlength + 0x97: 0x3e40a, // ondragover + 0x98: 0x42407, // onerror + 0x9a: 0x2107, // charset + 0x9b: 0x29b06, // method + 0x9c: 0x101, // b + 0x9d: 0x68208, // ontoggle + 0x9e: 0x2bd06, // hidden + 0xa0: 0x3f607, // article + 0xa2: 0x63906, // onshow + 0xa3: 0x64d06, // onsort + 0xa5: 0x57b0f, // contenteditable + 0xa6: 0x66908, // onsubmit + 0xa8: 0x44f09, // oninvalid + 0xaa: 0x202, // br + 0xab: 0x10902, // id + 0xac: 0x5d04, // loop + 0xad: 0x5630a, // onpageshow + 0xb0: 0x2cf04, // href + 0xb2: 0x2210a, // figcaption + 0xb3: 0x2690e, // onautocomplete + 0xb4: 0x49106, // onload + 0xb6: 0x9c04, // rows + 0xb7: 0x1a605, // nonce + 0xb8: 0x68a14, // onunhandledrejection + 0xbb: 0x21306, // center + 0xbc: 0x59406, // onplay + 0xbd: 0x33f02, // h5 + 0xbe: 0x49d07, // listing + 0xbf: 0x57606, // public + 0xc2: 0x23b06, // figure + 0xc3: 0x57a04, // icon + 0xc4: 0x1ab03, // col + 0xc5: 0x47b03, // rel + 0xc6: 0xe605, // media + 0xc7: 0x12109, // autofocus + 0xc8: 0x19a02, // rt + 0xca: 0x2d304, // lang + 0xcc: 0x49908, // datalist + 0xce: 0x2eb06, // iframe + 0xcf: 0x36105, // muted + 0xd0: 0x6140a, // onauxclick + 0xd2: 0x3c02, // as + 0xd6: 0x3fd06, // ondrop + 0xd7: 0x1c90a, // annotation + 0xd8: 0x21908, // fieldset + 0xdb: 0x2cf08, // hreflang + 0xdc: 0x4e70c, // onmouseenter + 0xdd: 0x2a402, // mn + 0xde: 0xe60a, // mediagroup + 0xdf: 0x9805, // meter + 0xe0: 0x56c03, // wbr + 0xe2: 0x63e05, // width + 0xe3: 0x2290c, // onafterprint + 0xe4: 0x30505, // ismap + 0xe5: 0x1505, // value + 0xe7: 0x1303, // nav + 0xe8: 0x54508, // ononline + 0xe9: 0xb604, // mark + 0xea: 0xc303, // low + 0xeb: 0x3ee0b, // ondragstart + 0xef: 0x12f03, // xmp + 0xf0: 0x22407, // caption + 0xf1: 0xd904, // type + 0xf2: 0x70907, // summary + 0xf3: 0x6802, // tt + 0xf4: 0x20809, // translate + 0xf5: 0x1870a, // blockquote + 0xf8: 0x15702, // hr + 0xfa: 0x2705, // tbody + 0xfc: 0x7b07, // picture + 0xfd: 0x5206, // height + 0xfe: 0x19c04, // cite + 0xff: 0x2501, // s + 0x101: 0xff05, // async + 0x102: 0x56f07, // onpaste + 0x103: 0x19507, // onabort + 0x104: 0x2b706, // target + 0x105: 0x14b03, // bdo + 0x106: 0x1f006, // coords + 0x107: 0x5e108, // onresize + 0x108: 0x71908, // template + 0x10a: 0x3a02, // rb + 0x10b: 0x2a50a, // novalidate + 0x10c: 0x460e, // updateviacache + 0x10d: 0x71003, // sup + 0x10e: 0x6c07, // noembed + 0x10f: 0x16b03, // div + 0x110: 0x6f707, // srclang + 0x111: 0x17a09, // draggable + 0x112: 0x67305, // scope + 0x113: 0x5905, // label + 0x114: 0x22f02, // rp + 0x115: 0x23f08, // required + 0x116: 0x3780d, // oncontextmenu + 0x117: 0x5e504, // size + 0x118: 0x5b00a, // spellcheck + 0x119: 0x3f04, // font + 0x11a: 0x9c07, // rowspan + 0x11b: 0x10a07, // default + 0x11d: 0x44307, // oninput + 0x11e: 0x38506, // itemid + 0x11f: 0x5ee04, // code + 0x120: 0xaa07, // acronym + 0x121: 0x3b04, // base + 0x125: 0x2470d, // foreignObject + 0x126: 0x2ca04, // high + 0x127: 0x3cb0e, // referrerpolicy + 0x128: 0x33703, // max + 0x129: 0x59d0a, // onpopstate + 0x12a: 0x2fc02, // h4 + 0x12b: 0x4ac04, // meta + 0x12c: 0x17305, // blink + 0x12e: 0x5f508, // onscroll + 0x12f: 0x59409, // onplaying + 0x130: 0xc113, // allowpaymentrequest + 0x131: 0x19a03, // rtc + 0x132: 0x72b04, // wrap + 0x134: 0x8b08, // frameset + 0x135: 0x32605, // small + 0x137: 0x32006, // header + 0x138: 0x40409, // onemptied + 0x139: 0x34902, // h6 + 0x13a: 0x35908, // multiple + 0x13c: 0x52a06, // prompt + 0x13f: 0x28e09, // challenge + 0x141: 0x4370c, // onhashchange + 0x142: 0x57b07, // content + 0x143: 0x1c90e, // annotation-xml + 0x144: 0x36607, // onclose + 0x145: 0x14d10, // oncanplaythrough + 0x148: 0x5170b, // onmouseover + 0x149: 0x64f08, // sortable + 0x14a: 0xa402, // mo + 0x14b: 0x2cd02, // h3 + 0x14c: 0x2c406, // script + 0x14d: 0x41d07, // onended + 0x14f: 0x64706, // poster + 0x150: 0x7210a, // workertype + 0x153: 0x1f505, // shape + 0x154: 0x4, // abbr + 0x155: 0x1, // a + 0x156: 0x2bf02, // dd + 0x157: 0x71606, // system + 0x158: 0x4ce0e, // onmessageerror + 0x159: 0x36b08, // seamless + 0x15a: 0x2610a, // formaction + 0x15b: 0x6e106, // option + 0x15c: 0x31d04, // math + 0x15d: 0x62609, // onseeking + 0x15e: 0x39c05, // oncut + 0x15f: 0x44c03, // del + 0x160: 0x11005, // title + 0x161: 0x11505, // audio + 0x162: 0x63108, // selected + 0x165: 0x3b40b, // ondragenter + 0x166: 0x46e06, // spacer + 0x167: 0x4a410, // onloadedmetadata + 0x168: 0x44505, // input + 0x16a: 0x58505, // table + 0x16b: 0x41508, // onchange + 0x16e: 0x5f005, // defer + 0x171: 0x50a0a, // onmouseout + 0x172: 0x20504, // slot + 0x175: 0x3704, // nobr + 0x177: 0x1d707, // command + 0x17a: 0x7207, // details + 0x17b: 0x38104, // menu + 0x17c: 0xb903, // kbd + 0x17d: 0x57304, // step + 0x17e: 0x20303, // ins + 0x17f: 0x13c08, // autoplay + 0x182: 0x34103, // min + 0x183: 0x17404, // link + 0x185: 0x40d10, // ondurationchange + 0x186: 0x9202, // td + 0x187: 0x8b05, // frame + 0x18a: 0x2ab08, // datetime + 0x18b: 0x44509, // inputmode + 0x18c: 0x35108, // readonly + 0x18d: 0x21104, // face + 0x18f: 0x5e505, // sizes + 0x191: 0x4b208, // tabindex + 0x192: 0x6db06, // strong + 0x193: 0xba03, // bdi + 0x194: 0x6fe06, // srcset + 0x196: 0x67202, // ms + 0x197: 0x5b507, // checked + 0x198: 0xb105, // align + 0x199: 0x1e507, // section + 0x19b: 0x6e05, // embed + 0x19d: 0x15e07, // bgsound + 0x1a2: 0x49d04, // list + 0x1a3: 0x61e08, // onseeked + 0x1a4: 0x66009, // onstorage + 0x1a5: 0x2f603, // img + 0x1a6: 0xf505, // tfoot + 0x1a9: 0x26913, // onautocompleteerror + 0x1aa: 0x5fd19, // onsecuritypolicyviolation + 0x1ad: 0x9303, // dir + 0x1ae: 0x9307, // dirname + 0x1b0: 0x5a70a, // onprogress + 0x1b2: 0x65709, // onstalled + 0x1b5: 0x66f09, // itemscope + 0x1b6: 0x49904, // data + 0x1b7: 0x3d90b, // ondragleave + 0x1b8: 0x56102, // h2 + 0x1b9: 0x2f706, // mglyph + 0x1ba: 0x16502, // is + 0x1bb: 0x6e50e, // onbeforeunload + 0x1bc: 0x2830d, // typemustmatch + 0x1bd: 0x3ab06, // ondrag + 0x1be: 0x5da07, // onreset + 0x1c0: 0x51106, // output + 0x1c1: 0x12907, // sandbox + 0x1c2: 0x1b209, // plaintext + 0x1c4: 0x34c08, // textarea + 0x1c7: 0xd607, // keytype + 0x1c8: 0x34b05, // mtext + 0x1c9: 0x6b10e, // onvolumechange + 0x1ca: 0x1ea06, // onblur + 0x1cb: 0x58a07, // onpause + 0x1cd: 0x5bc0c, // onratechange + 0x1ce: 0x10705, // aside + 0x1cf: 0x6cf07, // optimum + 0x1d1: 0x45809, // onkeydown + 0x1d2: 0x1c407, // colspan + 0x1d3: 0x1004, // main + 0x1d4: 0x66b03, // sub + 0x1d5: 0x25b06, // object + 0x1d6: 0x55c06, // search + 0x1d7: 0x37206, // sorted + 0x1d8: 0x17003, // big + 0x1d9: 0xb01, // u + 0x1db: 0x26b0c, // autocomplete + 0x1dc: 0xcc02, // tr + 0x1dd: 0xf303, // alt + 0x1df: 0x7804, // samp + 0x1e0: 0x5c812, // onrejectionhandled + 0x1e1: 0x4f30c, // onmouseleave + 0x1e2: 0x28007, // enctype + 0x1e3: 0xa208, // nomodule + 0x1e5: 0x3280f, // allowfullscreen + 0x1e6: 0x5f08, // optgroup + 0x1e8: 0x27c0b, // formenctype + 0x1e9: 0x18106, // legend + 0x1ea: 0x10306, // canvas + 0x1eb: 0x6607, // pattern + 0x1ec: 0x2c208, // noscript + 0x1ed: 0x601, // i + 0x1ee: 0x5d602, // dl + 0x1ef: 0xa702, // ul + 0x1f2: 0x52209, // onmouseup + 0x1f4: 0x1ba05, // track + 0x1f7: 0x3a10a, // ondblclick + 0x1f8: 0x3bf0a, // ondragexit + 0x1fa: 0x8703, // dfn + 0x1fc: 0x26506, // action + 0x1fd: 0x35004, // area + 0x1fe: 0x31607, // marquee + 0x1ff: 0x16d03, // var } const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" + @@ -758,26 +760,26 @@ const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" "dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" + "bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" + "penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" + - "ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" + - "ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" + - "ignObjectforeignobjectformactionautocompleteerrorformenctype" + - "mustmatchallengeformmethodformnovalidatetimeformtargethgroup" + - "osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" + - "ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" + - "inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" + - "extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" + - "enterondragexitemreferrerpolicyondragleaveondragoverondragst" + - "articleondropzonemptiedondurationchangeonendedonerroronfocus" + - "paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" + - "spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" + - "onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" + - "usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" + - "seoveronmouseupromptonmousewheelonofflineononlineonpagehides" + - "classectionbluronpageshowbronpastepublicontenteditableonpaus" + - "emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" + - "jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" + - "violationauxclickonseekedonseekingonselectedonshowidth6onsor" + - "tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" + - "handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" + - "wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" + - "arysupsvgsystemplateworkertypewrap" + "ntrolsectionblurcoordshapecrossoriginslotranslatefacenterfie" + + "ldsetfigcaptionafterprintegrityfigurequiredforeignObjectfore" + + "ignobjectformactionautocompleteerrorformenctypemustmatchalle" + + "ngeformmethodformnovalidatetimeformtargethiddenoscripthigh3h" + + "reflanghttp-equivideonclickiframeimageimglyph4isindexismappl" + + "etitemtypemarqueematheadersmallowfullscreenmaxlength5minleng" + + "th6mtextareadonlymultiplemutedoncloseamlessortedoncontextmen" + + "uitemidoncopyoncuechangeoncutondblclickondragendondragentero" + + "ndragexitemreferrerpolicyondragleaveondragoverondragstarticl" + + "eondropzonemptiedondurationchangeonendedonerroronfocusourceo" + + "nhashchangeoninputmodeloninvalidonkeydownloadonkeypresspacer" + + "onkeyupreloadonlanguagechangeonloadeddatalistingonloadedmeta" + + "databindexonloadendonloadstartonmessageerroronmousedownonmou" + + "seenteronmouseleaveonmousemoveonmouseoutputonmouseoveronmous" + + "eupromptonmousewheelonofflineononlineonpagehidesclassearch2o" + + "npageshowbronpastepublicontenteditableonpausemaponplayingonp" + + "opstateonprogresspellcheckedonratechangeonrejectionhandledon" + + "resetonresizesrcdocodeferonscrollonsecuritypolicyviolationau" + + "xclickonseekedonseekingonselectedonshowidthgrouposteronsorta" + + "bleonstalledonstorageonsubmitemscopedonsuspendontoggleonunha" + + "ndledrejectionbeforeprintonunloadonvolumechangeonwaitingonwh" + + "eeloptimumanifestrongoptionbeforeunloaddressrclangsrcsetstyl" + + "esummarysupsvgsystemplateworkertypewrap" diff --git a/go-controller/vendor/golang.org/x/net/html/doc.go b/go-controller/vendor/golang.org/x/net/html/doc.go index 3a7e5ab176..885c4c5936 100644 --- a/go-controller/vendor/golang.org/x/net/html/doc.go +++ b/go-controller/vendor/golang.org/x/net/html/doc.go @@ -78,16 +78,11 @@ example, to process each anchor node in depth-first order: if err != nil { // ... } - var f func(*html.Node) - f = func(n *html.Node) { + for n := range doc.Descendants() { if n.Type == html.ElementNode && n.Data == "a" { // Do something with n... } - for c := n.FirstChild; c != nil; c = c.NextSibling { - f(c) - } } - f(doc) The relevant specifications include: https://html.spec.whatwg.org/multipage/syntax.html and diff --git a/go-controller/vendor/golang.org/x/net/html/doctype.go b/go-controller/vendor/golang.org/x/net/html/doctype.go index c484e5a94f..bca3ae9a0c 100644 --- a/go-controller/vendor/golang.org/x/net/html/doctype.go +++ b/go-controller/vendor/golang.org/x/net/html/doctype.go @@ -87,7 +87,7 @@ func parseDoctype(s string) (n *Node, quirks bool) { } } if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && - strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { + strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") { quirks = true } } diff --git a/go-controller/vendor/golang.org/x/net/html/foreign.go b/go-controller/vendor/golang.org/x/net/html/foreign.go index 9da9e9dc42..e8515d8e88 100644 --- a/go-controller/vendor/golang.org/x/net/html/foreign.go +++ b/go-controller/vendor/golang.org/x/net/html/foreign.go @@ -40,8 +40,7 @@ func htmlIntegrationPoint(n *Node) bool { if n.Data == "annotation-xml" { for _, a := range n.Attr { if a.Key == "encoding" { - val := strings.ToLower(a.Val) - if val == "text/html" || val == "application/xhtml+xml" { + if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") { return true } } diff --git a/go-controller/vendor/golang.org/x/net/html/iter.go b/go-controller/vendor/golang.org/x/net/html/iter.go new file mode 100644 index 0000000000..54be8fd30f --- /dev/null +++ b/go-controller/vendor/golang.org/x/net/html/iter.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.23 + +package html + +import "iter" + +// Ancestors returns an iterator over the ancestors of n, starting with n.Parent. +// +// Mutating a Node or its parents while iterating may have unexpected results. +func (n *Node) Ancestors() iter.Seq[*Node] { + _ = n.Parent // eager nil check + + return func(yield func(*Node) bool) { + for p := n.Parent; p != nil && yield(p); p = p.Parent { + } + } +} + +// ChildNodes returns an iterator over the immediate children of n, +// starting with n.FirstChild. +// +// Mutating a Node or its children while iterating may have unexpected results. +func (n *Node) ChildNodes() iter.Seq[*Node] { + _ = n.FirstChild // eager nil check + + return func(yield func(*Node) bool) { + for c := n.FirstChild; c != nil && yield(c); c = c.NextSibling { + } + } + +} + +// Descendants returns an iterator over all nodes recursively beneath +// n, excluding n itself. Nodes are visited in depth-first preorder. +// +// Mutating a Node or its descendants while iterating may have unexpected results. +func (n *Node) Descendants() iter.Seq[*Node] { + _ = n.FirstChild // eager nil check + + return func(yield func(*Node) bool) { + n.descendants(yield) + } +} + +func (n *Node) descendants(yield func(*Node) bool) bool { + for c := range n.ChildNodes() { + if !yield(c) || !c.descendants(yield) { + return false + } + } + return true +} diff --git a/go-controller/vendor/golang.org/x/net/html/node.go b/go-controller/vendor/golang.org/x/net/html/node.go index 1350eef22c..77741a1950 100644 --- a/go-controller/vendor/golang.org/x/net/html/node.go +++ b/go-controller/vendor/golang.org/x/net/html/node.go @@ -38,6 +38,10 @@ var scopeMarker = Node{Type: scopeMarkerNode} // that it looks like "a". - if z.err == nil && z.buf[z.raw.end-2] == '/' { + // Look for a self-closing token (e.g.
). + // + // Originally, we did this by just checking that the last character of the + // tag (ignoring the closing bracket) was a solidus (/) character, but this + // is not always accurate. + // + // We need to be careful that we don't misinterpret a non-self-closing tag + // as self-closing, as can happen if the tag contains unquoted attribute + // values (i.e.

). + // + // To avoid this, we check that the last non-bracket character of the tag + // (z.raw.end-2) isn't the same character as the last non-quote character of + // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has + // attributes. + nAttrs := len(z.attr) + if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) { return SelfClosingTagToken } return StartTagToken diff --git a/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go b/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go index 780968d6c1..e81b73e6a7 100644 --- a/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go +++ b/go-controller/vendor/golang.org/x/net/http2/client_conn_pool.go @@ -8,8 +8,8 @@ package http2 import ( "context" - "crypto/tls" "errors" + "net" "net/http" "sync" ) @@ -158,7 +158,7 @@ func (c *dialCall) dial(ctx context.Context, addr string) { // This code decides which ones live or die. // The return value used is whether c was used. // c is never closed. -func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) { +func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c net.Conn) (used bool, err error) { p.mu.Lock() for _, cc := range p.conns[key] { if cc.CanTakeNewRequest() { @@ -194,8 +194,8 @@ type addConnCall struct { err error } -func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) { - cc, err := t.NewClientConn(tc) +func (c *addConnCall) run(t *Transport, key string, nc net.Conn) { + cc, err := t.NewClientConn(nc) p := c.p p.mu.Lock() diff --git a/go-controller/vendor/golang.org/x/net/http2/config.go b/go-controller/vendor/golang.org/x/net/http2/config.go index de58dfb8dc..ca645d9a1a 100644 --- a/go-controller/vendor/golang.org/x/net/http2/config.go +++ b/go-controller/vendor/golang.org/x/net/http2/config.go @@ -60,7 +60,7 @@ func configFromServer(h1 *http.Server, h2 *Server) http2Config { return conf } -// configFromServer merges configuration settings from h2 and h2.t1.HTTP2 +// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2 // (the net/http Transport). func configFromTransport(h2 *Transport) http2Config { conf := http2Config{ diff --git a/go-controller/vendor/golang.org/x/net/http2/config_go124.go b/go-controller/vendor/golang.org/x/net/http2/config_go124.go index e3784123c8..5b516c55ff 100644 --- a/go-controller/vendor/golang.org/x/net/http2/config_go124.go +++ b/go-controller/vendor/golang.org/x/net/http2/config_go124.go @@ -13,7 +13,7 @@ func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) { fillNetHTTPConfig(conf, srv.HTTP2) } -// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2. +// fillNetHTTPTransportConfig sets fields in conf from tr.HTTP2. func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) { fillNetHTTPConfig(conf, tr.HTTP2) } diff --git a/go-controller/vendor/golang.org/x/net/http2/frame.go b/go-controller/vendor/golang.org/x/net/http2/frame.go index 105c3b279c..97bd8b06f7 100644 --- a/go-controller/vendor/golang.org/x/net/http2/frame.go +++ b/go-controller/vendor/golang.org/x/net/http2/frame.go @@ -225,6 +225,11 @@ var fhBytes = sync.Pool{ }, } +func invalidHTTP1LookingFrameHeader() FrameHeader { + fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 ")) + return fh +} + // ReadFrameHeader reads 9 bytes from r and returns a FrameHeader. // Most users should use Framer.ReadFrame instead. func ReadFrameHeader(r io.Reader) (FrameHeader, error) { @@ -503,10 +508,16 @@ func (fr *Framer) ReadFrame() (Frame, error) { return nil, err } if fh.Length > fr.maxReadSize { + if fh == invalidHTTP1LookingFrameHeader() { + return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err) + } return nil, ErrFrameTooLarge } payload := fr.getReadBuf(fh.Length) if _, err := io.ReadFull(fr.r, payload); err != nil { + if fh == invalidHTTP1LookingFrameHeader() { + return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err) + } return nil, err } f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload) @@ -1490,7 +1501,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error { pf := mh.PseudoFields() for i, hf := range pf { switch hf.Name { - case ":method", ":path", ":scheme", ":authority": + case ":method", ":path", ":scheme", ":authority", ":protocol": isRequest = true case ":status": isResponse = true @@ -1498,7 +1509,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error { return pseudoHeaderError(hf.Name) } // Check for duplicates. - // This would be a bad algorithm, but N is 4. + // This would be a bad algorithm, but N is 5. // And this doesn't allocate. for _, hf2 := range pf[:i] { if hf.Name == hf2.Name { diff --git a/go-controller/vendor/golang.org/x/net/http2/http2.go b/go-controller/vendor/golang.org/x/net/http2/http2.go index 7688c356b7..6c18ea230b 100644 --- a/go-controller/vendor/golang.org/x/net/http2/http2.go +++ b/go-controller/vendor/golang.org/x/net/http2/http2.go @@ -38,6 +38,15 @@ var ( logFrameWrites bool logFrameReads bool inTests bool + + // Enabling extended CONNECT by causes browsers to attempt to use + // WebSockets-over-HTTP/2. This results in problems when the server's websocket + // package doesn't support extended CONNECT. + // + // Disable extended CONNECT by default for now. + // + // Issue #71128. + disableExtendedConnectProtocol = true ) func init() { @@ -50,6 +59,9 @@ func init() { logFrameWrites = true logFrameReads = true } + if strings.Contains(e, "http2xconnect=1") { + disableExtendedConnectProtocol = false + } } const ( @@ -141,6 +153,10 @@ func (s Setting) Valid() error { if s.Val < 16384 || s.Val > 1<<24-1 { return ConnectionError(ErrCodeProtocol) } + case SettingEnableConnectProtocol: + if s.Val != 1 && s.Val != 0 { + return ConnectionError(ErrCodeProtocol) + } } return nil } @@ -150,21 +166,23 @@ func (s Setting) Valid() error { type SettingID uint16 const ( - SettingHeaderTableSize SettingID = 0x1 - SettingEnablePush SettingID = 0x2 - SettingMaxConcurrentStreams SettingID = 0x3 - SettingInitialWindowSize SettingID = 0x4 - SettingMaxFrameSize SettingID = 0x5 - SettingMaxHeaderListSize SettingID = 0x6 + SettingHeaderTableSize SettingID = 0x1 + SettingEnablePush SettingID = 0x2 + SettingMaxConcurrentStreams SettingID = 0x3 + SettingInitialWindowSize SettingID = 0x4 + SettingMaxFrameSize SettingID = 0x5 + SettingMaxHeaderListSize SettingID = 0x6 + SettingEnableConnectProtocol SettingID = 0x8 ) var settingName = map[SettingID]string{ - SettingHeaderTableSize: "HEADER_TABLE_SIZE", - SettingEnablePush: "ENABLE_PUSH", - SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS", - SettingInitialWindowSize: "INITIAL_WINDOW_SIZE", - SettingMaxFrameSize: "MAX_FRAME_SIZE", - SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE", + SettingHeaderTableSize: "HEADER_TABLE_SIZE", + SettingEnablePush: "ENABLE_PUSH", + SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS", + SettingInitialWindowSize: "INITIAL_WINDOW_SIZE", + SettingMaxFrameSize: "MAX_FRAME_SIZE", + SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE", + SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL", } func (s SettingID) String() string { @@ -397,23 +415,6 @@ func (s *sorter) SortStrings(ss []string) { s.v = save } -// validPseudoPath reports whether v is a valid :path pseudo-header -// value. It must be either: -// -// - a non-empty string starting with '/' -// - the string '*', for OPTIONS requests. -// -// For now this is only used a quick check for deciding when to clean -// up Opaque URLs before sending requests from the Transport. -// See golang.org/issue/16847 -// -// We used to enforce that the path also didn't start with "//", but -// Google's GFE accepts such paths and Chrome sends them, so ignore -// that part of the spec. See golang.org/issue/19103. -func validPseudoPath(v string) bool { - return (len(v) > 0 && v[0] == '/') || v == "*" -} - // incomparable is a zero-width, non-comparable type. Adding it to a struct // makes that struct also non-comparable, and generally doesn't add // any size (as long as it's first). diff --git a/go-controller/vendor/golang.org/x/net/http2/server.go b/go-controller/vendor/golang.org/x/net/http2/server.go index 617b4a4762..51fca38f61 100644 --- a/go-controller/vendor/golang.org/x/net/http2/server.go +++ b/go-controller/vendor/golang.org/x/net/http2/server.go @@ -50,6 +50,7 @@ import ( "golang.org/x/net/http/httpguts" "golang.org/x/net/http2/hpack" + "golang.org/x/net/internal/httpcommon" ) const ( @@ -306,7 +307,7 @@ func ConfigureServer(s *http.Server, conf *Server) error { if s.TLSNextProto == nil { s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){} } - protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) { + protoHandler := func(hs *http.Server, c net.Conn, h http.Handler, sawClientPreface bool) { if testHookOnConn != nil { testHookOnConn() } @@ -323,12 +324,31 @@ func ConfigureServer(s *http.Server, conf *Server) error { ctx = bc.BaseContext() } conf.ServeConn(c, &ServeConnOpts{ - Context: ctx, - Handler: h, - BaseConfig: hs, + Context: ctx, + Handler: h, + BaseConfig: hs, + SawClientPreface: sawClientPreface, }) } - s.TLSNextProto[NextProtoTLS] = protoHandler + s.TLSNextProto[NextProtoTLS] = func(hs *http.Server, c *tls.Conn, h http.Handler) { + protoHandler(hs, c, h, false) + } + // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. + // + // A connection passed in this method has already had the HTTP/2 preface read from it. + s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *http.Server, c *tls.Conn, h http.Handler) { + nc, err := unencryptedNetConnFromTLSConn(c) + if err != nil { + if lg := hs.ErrorLog; lg != nil { + lg.Print(err) + } else { + log.Print(err) + } + go c.Close() + return + } + protoHandler(hs, nc, h, true) + } return nil } @@ -793,8 +813,7 @@ const maxCachedCanonicalHeadersKeysSize = 2048 func (sc *serverConn) canonicalHeader(v string) string { sc.serveG.check() - buildCommonHeaderMapsOnce() - cv, ok := commonCanonHeader[v] + cv, ok := httpcommon.CachedCanonicalHeader(v) if ok { return cv } @@ -913,14 +932,18 @@ func (sc *serverConn) serve(conf http2Config) { sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs) } + settings := writeSettings{ + {SettingMaxFrameSize, conf.MaxReadFrameSize}, + {SettingMaxConcurrentStreams, sc.advMaxStreams}, + {SettingMaxHeaderListSize, sc.maxHeaderListSize()}, + {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, + {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, + } + if !disableExtendedConnectProtocol { + settings = append(settings, Setting{SettingEnableConnectProtocol, 1}) + } sc.writeFrame(FrameWriteRequest{ - write: writeSettings{ - {SettingMaxFrameSize, conf.MaxReadFrameSize}, - {SettingMaxConcurrentStreams, sc.advMaxStreams}, - {SettingMaxHeaderListSize, sc.maxHeaderListSize()}, - {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, - {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, - }, + write: settings, }) sc.unackedSettings++ @@ -1045,7 +1068,10 @@ func (sc *serverConn) serve(conf http2Config) { func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) { if sc.pingSent { - sc.vlogf("timeout waiting for PING response") + sc.logf("timeout waiting for PING response") + if f := sc.countErrorFunc; f != nil { + f("conn_close_lost_ping") + } sc.conn.Close() return } @@ -1782,6 +1808,9 @@ func (sc *serverConn) processSetting(s Setting) error { sc.maxFrameSize = int32(s.Val) // the maximum valid s.Val is < 2^31 case SettingMaxHeaderListSize: sc.peerMaxHeaderListSize = s.Val + case SettingEnableConnectProtocol: + // Receipt of this parameter by a server does not + // have any impact default: // Unknown setting: "An endpoint that receives a SETTINGS // frame with any unknown or unsupported identifier MUST @@ -2207,19 +2236,25 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) { sc.serveG.check() - rp := requestParam{ - method: f.PseudoValue("method"), - scheme: f.PseudoValue("scheme"), - authority: f.PseudoValue("authority"), - path: f.PseudoValue("path"), + rp := httpcommon.ServerRequestParam{ + Method: f.PseudoValue("method"), + Scheme: f.PseudoValue("scheme"), + Authority: f.PseudoValue("authority"), + Path: f.PseudoValue("path"), + Protocol: f.PseudoValue("protocol"), + } + + // extended connect is disabled, so we should not see :protocol + if disableExtendedConnectProtocol && rp.Protocol != "" { + return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol)) } - isConnect := rp.method == "CONNECT" + isConnect := rp.Method == "CONNECT" if isConnect { - if rp.path != "" || rp.scheme != "" || rp.authority == "" { + if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") { return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol)) } - } else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") { + } else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") { // See 8.1.2.6 Malformed Requests and Responses: // // Malformed requests or responses that are detected @@ -2233,12 +2268,16 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol)) } - rp.header = make(http.Header) + header := make(http.Header) + rp.Header = header for _, hf := range f.RegularFields() { - rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value) + header.Add(sc.canonicalHeader(hf.Name), hf.Value) } - if rp.authority == "" { - rp.authority = rp.header.Get("Host") + if rp.Authority == "" { + rp.Authority = header.Get("Host") + } + if rp.Protocol != "" { + header.Set(":protocol", rp.Protocol) } rw, req, err := sc.newWriterAndRequestNoBody(st, rp) @@ -2247,7 +2286,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res } bodyOpen := !f.StreamEnded() if bodyOpen { - if vv, ok := rp.header["Content-Length"]; ok { + if vv, ok := rp.Header["Content-Length"]; ok { if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil { req.ContentLength = int64(cl) } else { @@ -2263,83 +2302,38 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res return rw, req, nil } -type requestParam struct { - method string - scheme, authority, path string - header http.Header -} - -func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*responseWriter, *http.Request, error) { +func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) { sc.serveG.check() var tlsState *tls.ConnectionState // nil if not scheme https - if rp.scheme == "https" { + if rp.Scheme == "https" { tlsState = sc.tlsState } - needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue") - if needsContinue { - rp.header.Del("Expect") - } - // Merge Cookie headers into one "; "-delimited value. - if cookies := rp.header["Cookie"]; len(cookies) > 1 { - rp.header.Set("Cookie", strings.Join(cookies, "; ")) - } - - // Setup Trailers - var trailer http.Header - for _, v := range rp.header["Trailer"] { - for _, key := range strings.Split(v, ",") { - key = http.CanonicalHeaderKey(textproto.TrimString(key)) - switch key { - case "Transfer-Encoding", "Trailer", "Content-Length": - // Bogus. (copy of http1 rules) - // Ignore. - default: - if trailer == nil { - trailer = make(http.Header) - } - trailer[key] = nil - } - } - } - delete(rp.header, "Trailer") - - var url_ *url.URL - var requestURI string - if rp.method == "CONNECT" { - url_ = &url.URL{Host: rp.authority} - requestURI = rp.authority // mimic HTTP/1 server behavior - } else { - var err error - url_, err = url.ParseRequestURI(rp.path) - if err != nil { - return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol)) - } - requestURI = rp.path + res := httpcommon.NewServerRequest(rp) + if res.InvalidReason != "" { + return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol)) } body := &requestBody{ conn: sc, stream: st, - needsContinue: needsContinue, + needsContinue: res.NeedsContinue, } - req := &http.Request{ - Method: rp.method, - URL: url_, + req := (&http.Request{ + Method: rp.Method, + URL: res.URL, RemoteAddr: sc.remoteAddrStr, - Header: rp.header, - RequestURI: requestURI, + Header: rp.Header, + RequestURI: res.RequestURI, Proto: "HTTP/2.0", ProtoMajor: 2, ProtoMinor: 0, TLS: tlsState, - Host: rp.authority, + Host: rp.Authority, Body: body, - Trailer: trailer, - } - req = req.WithContext(st.ctx) - + Trailer: res.Trailer, + }).WithContext(st.ctx) rw := sc.newResponseWriter(st, req) return rw, req, nil } @@ -2880,6 +2874,11 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error { return nil } +func (w *responseWriter) EnableFullDuplex() error { + // We always support full duplex responses, so this is a no-op. + return nil +} + func (w *responseWriter) Flush() { w.FlushError() } @@ -3229,12 +3228,12 @@ func (sc *serverConn) startPush(msg *startPushRequest) { // we start in "half closed (remote)" for simplicity. // See further comments at the definition of stateHalfClosedRemote. promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote) - rw, req, err := sc.newWriterAndRequestNoBody(promised, requestParam{ - method: msg.method, - scheme: msg.url.Scheme, - authority: msg.url.Host, - path: msg.url.RequestURI(), - header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE + rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{ + Method: msg.method, + Scheme: msg.url.Scheme, + Authority: msg.url.Host, + Path: msg.url.RequestURI(), + Header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE }) if err != nil { // Should not happen, since we've already validated msg.url. diff --git a/go-controller/vendor/golang.org/x/net/http2/transport.go b/go-controller/vendor/golang.org/x/net/http2/transport.go index 0c5f64aa8b..f26356b9cd 100644 --- a/go-controller/vendor/golang.org/x/net/http2/transport.go +++ b/go-controller/vendor/golang.org/x/net/http2/transport.go @@ -25,7 +25,6 @@ import ( "net/http" "net/http/httptrace" "net/textproto" - "sort" "strconv" "strings" "sync" @@ -35,6 +34,7 @@ import ( "golang.org/x/net/http/httpguts" "golang.org/x/net/http2/hpack" "golang.org/x/net/idna" + "golang.org/x/net/internal/httpcommon" ) const ( @@ -202,6 +202,20 @@ func (t *Transport) markNewGoroutine() { } } +func (t *Transport) now() time.Time { + if t != nil && t.transportTestHooks != nil { + return t.transportTestHooks.group.Now() + } + return time.Now() +} + +func (t *Transport) timeSince(when time.Time) time.Duration { + if t != nil && t.transportTestHooks != nil { + return t.now().Sub(when) + } + return time.Since(when) +} + // newTimer creates a new time.Timer, or a synthetic timer in tests. func (t *Transport) newTimer(d time.Duration) timer { if t.transportTestHooks != nil { @@ -281,8 +295,8 @@ func configureTransports(t1 *http.Transport) (*Transport, error) { if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") { t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1") } - upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper { - addr := authorityAddr("https", authority) + upgradeFn := func(scheme, authority string, c net.Conn) http.RoundTripper { + addr := authorityAddr(scheme, authority) if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil { go c.Close() return erringRoundTripper{err} @@ -293,18 +307,37 @@ func configureTransports(t1 *http.Transport) (*Transport, error) { // was unknown) go c.Close() } + if scheme == "http" { + return (*unencryptedTransport)(t2) + } return t2 } - if m := t1.TLSNextProto; len(m) == 0 { - t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{ - "h2": upgradeFn, + if t1.TLSNextProto == nil { + t1.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper) + } + t1.TLSNextProto[NextProtoTLS] = func(authority string, c *tls.Conn) http.RoundTripper { + return upgradeFn("https", authority, c) + } + // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. + t1.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) http.RoundTripper { + nc, err := unencryptedNetConnFromTLSConn(c) + if err != nil { + go c.Close() + return erringRoundTripper{err} } - } else { - m["h2"] = upgradeFn + return upgradeFn("http", authority, nc) } return t2, nil } +// unencryptedTransport is a Transport with a RoundTrip method that +// always permits http:// URLs. +type unencryptedTransport Transport + +func (t *unencryptedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return (*Transport)(t).RoundTripOpt(req, RoundTripOpt{allowHTTP: true}) +} + func (t *Transport) connPool() ClientConnPool { t.connPoolOnce.Do(t.initConnPool) return t.connPoolOrDef @@ -324,7 +357,7 @@ type ClientConn struct { t *Transport tconn net.Conn // usually *tls.Conn, except specialized impls tlsState *tls.ConnectionState // nil only for specialized impls - reused uint32 // whether conn is being reused; atomic + atomicReused uint32 // whether conn is being reused; atomic singleUse bool // whether being used for a single http.Request getConnCalled bool // used by clientConnPool @@ -335,25 +368,27 @@ type ClientConn struct { idleTimeout time.Duration // or 0 for never idleTimer timer - mu sync.Mutex // guards following - cond *sync.Cond // hold mu; broadcast on flow/closed changes - flow outflow // our conn-level flow control quota (cs.outflow is per stream) - inflow inflow // peer's conn-level flow control - doNotReuse bool // whether conn is marked to not be reused for any future requests - closing bool - closed bool - seenSettings bool // true if we've seen a settings frame, false otherwise - wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back - goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received - goAwayDebug string // goAway frame's debug data, retained as a string - streams map[uint32]*clientStream // client-initiated - streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip - nextStreamID uint32 - pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams - pings map[[8]byte]chan struct{} // in flight ping data to notification channel - br *bufio.Reader - lastActive time.Time - lastIdle time.Time // time last idle + mu sync.Mutex // guards following + cond *sync.Cond // hold mu; broadcast on flow/closed changes + flow outflow // our conn-level flow control quota (cs.outflow is per stream) + inflow inflow // peer's conn-level flow control + doNotReuse bool // whether conn is marked to not be reused for any future requests + closing bool + closed bool + closedOnIdle bool // true if conn was closed for idleness + seenSettings bool // true if we've seen a settings frame, false otherwise + seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails + wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back + goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received + goAwayDebug string // goAway frame's debug data, retained as a string + streams map[uint32]*clientStream // client-initiated + streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip + nextStreamID uint32 + pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams + pings map[[8]byte]chan struct{} // in flight ping data to notification channel + br *bufio.Reader + lastActive time.Time + lastIdle time.Time // time last idle // Settings from peer: (also guarded by wmu) maxFrameSize uint32 maxConcurrentStreams uint32 @@ -363,6 +398,25 @@ type ClientConn struct { initialStreamRecvWindowSize int32 readIdleTimeout time.Duration pingTimeout time.Duration + extendedConnectAllowed bool + + // rstStreamPingsBlocked works around an unfortunate gRPC behavior. + // gRPC strictly limits the number of PING frames that it will receive. + // The default is two pings per two hours, but the limit resets every time + // the gRPC endpoint sends a HEADERS or DATA frame. See golang/go#70575. + // + // rstStreamPingsBlocked is set after receiving a response to a PING frame + // bundled with an RST_STREAM (see pendingResets below), and cleared after + // receiving a HEADERS or DATA frame. + rstStreamPingsBlocked bool + + // pendingResets is the number of RST_STREAM frames we have sent to the peer, + // without confirming that the peer has received them. When we send a RST_STREAM, + // we bundle it with a PING frame, unless a PING is already in flight. We count + // the reset stream against the connection's concurrency limit until we get + // a PING response. This limits the number of requests we'll try to send to a + // completely unresponsive connection. + pendingResets int // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. // Write to reqHeaderMu to lock it, read from it to unlock. @@ -420,12 +474,12 @@ type clientStream struct { sentHeaders bool // owned by clientConnReadLoop: - firstByte bool // got the first response byte - pastHeaders bool // got first MetaHeadersFrame (actual headers) - pastTrailers bool // got optional second MetaHeadersFrame (trailers) - num1xx uint8 // number of 1xx responses seen - readClosed bool // peer sent an END_STREAM flag - readAborted bool // read loop reset the stream + firstByte bool // got the first response byte + pastHeaders bool // got first MetaHeadersFrame (actual headers) + pastTrailers bool // got optional second MetaHeadersFrame (trailers) + readClosed bool // peer sent an END_STREAM flag + readAborted bool // read loop reset the stream + totalHeaderSize int64 // total size of 1xx headers seen trailer http.Header // accumulated trailers resTrailer *http.Header // client's Response.Trailer @@ -530,6 +584,8 @@ type RoundTripOpt struct { // no cached connection is available, RoundTripOpt // will return ErrNoCachedConn. OnlyCachedConn bool + + allowHTTP bool // allow http:// URLs } func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { @@ -562,7 +618,14 @@ func authorityAddr(scheme string, authority string) (addr string) { // RoundTripOpt is like RoundTrip, but takes options. func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { - if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) { + switch req.URL.Scheme { + case "https": + // Always okay. + case "http": + if !t.AllowHTTP && !opt.allowHTTP { + return nil, errors.New("http2: unencrypted HTTP/2 not enabled") + } + default: return nil, errors.New("http2: unsupported scheme") } @@ -573,7 +636,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) return nil, err } - reused := !atomic.CompareAndSwapUint32(&cc.reused, 0, 1) + reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1) traceGotConn(req, cc, reused) res, err := cc.RoundTrip(req) if err != nil && retry <= 6 { @@ -598,6 +661,22 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res } } } + if err == errClientConnNotEstablished { + // This ClientConn was created recently, + // this is the first request to use it, + // and the connection is closed and not usable. + // + // In this state, cc.idleTimer will remove the conn from the pool + // when it fires. Stop the timer and remove it here so future requests + // won't try to use this connection. + // + // If the timer has already fired and we're racing it, the redundant + // call to MarkDead is harmless. + if cc.idleTimer != nil { + cc.idleTimer.Stop() + } + t.connPool().MarkDead(cc) + } if err != nil { t.vlogf("RoundTrip failure: %v", err) return nil, err @@ -616,9 +695,10 @@ func (t *Transport) CloseIdleConnections() { } var ( - errClientConnClosed = errors.New("http2: client conn is closed") - errClientConnUnusable = errors.New("http2: client conn not usable") - errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY") + errClientConnClosed = errors.New("http2: client conn is closed") + errClientConnUnusable = errors.New("http2: client conn not usable") + errClientConnNotEstablished = errors.New("http2: client conn could not be established") + errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY") ) // shouldRetryRequest is called by RoundTrip when a request fails to get @@ -752,11 +832,13 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. streams: make(map[uint32]*clientStream), singleUse: singleUse, + seenSettingsChan: make(chan struct{}), wantSettingsAck: true, readIdleTimeout: conf.SendPingTimeout, pingTimeout: conf.PingTimeout, pings: make(map[[8]byte]chan struct{}), reqHeaderMu: make(chan struct{}, 1), + lastActive: t.now(), } var group synctestGroupInterface if t.transportTestHooks != nil { @@ -960,7 +1042,7 @@ func (cc *ClientConn) State() ClientConnState { return ClientConnState{ Closed: cc.closed, Closing: cc.closing || cc.singleUse || cc.doNotReuse || cc.goAway != nil, - StreamsActive: len(cc.streams), + StreamsActive: len(cc.streams) + cc.pendingResets, StreamsReserved: cc.streamsReserved, StreamsPending: cc.pendingRequests, LastIdle: cc.lastIdle, @@ -992,16 +1074,40 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) { // writing it. maxConcurrentOkay = true } else { - maxConcurrentOkay = int64(len(cc.streams)+cc.streamsReserved+1) <= int64(cc.maxConcurrentStreams) + // We can take a new request if the total of + // - active streams; + // - reservation slots for new streams; and + // - streams for which we have sent a RST_STREAM and a PING, + // but received no subsequent frame + // is less than the concurrency limit. + maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) } st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay && !cc.doNotReuse && int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 && !cc.tooIdleLocked() + + // If this connection has never been used for a request and is closed, + // then let it take a request (which will fail). + // If the conn was closed for idleness, we're racing the idle timer; + // don't try to use the conn. (Issue #70515.) + // + // This avoids a situation where an error early in a connection's lifetime + // goes unreported. + if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle { + st.canTakeNewRequest = true + } + return } +// currentRequestCountLocked reports the number of concurrency slots currently in use, +// including active streams, reserved slots, and reset streams waiting for acknowledgement. +func (cc *ClientConn) currentRequestCountLocked() int { + return len(cc.streams) + cc.streamsReserved + cc.pendingResets +} + func (cc *ClientConn) canTakeNewRequestLocked() bool { st := cc.idleStateLocked() return st.canTakeNewRequest @@ -1014,7 +1120,7 @@ func (cc *ClientConn) tooIdleLocked() bool { // times are compared based on their wall time. We don't want // to reuse a connection that's been sitting idle during // VM/laptop suspend if monotonic time was also frozen. - return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && time.Since(cc.lastIdle.Round(0)) > cc.idleTimeout + return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && cc.t.timeSince(cc.lastIdle.Round(0)) > cc.idleTimeout } // onIdleTimeout is called from a time.AfterFunc goroutine. It will @@ -1052,6 +1158,7 @@ func (cc *ClientConn) closeIfIdle() { return } cc.closed = true + cc.closedOnIdle = true nextID := cc.nextStreamID // TODO: do clients send GOAWAY too? maybe? Just Close: cc.mu.Unlock() @@ -1168,23 +1275,6 @@ func (cc *ClientConn) closeForLostPing() { // exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests. var errRequestCanceled = errors.New("net/http: request canceled") -func commaSeparatedTrailers(req *http.Request) (string, error) { - keys := make([]string, 0, len(req.Trailer)) - for k := range req.Trailer { - k = canonicalHeader(k) - switch k { - case "Transfer-Encoding", "Trailer", "Content-Length": - return "", fmt.Errorf("invalid Trailer key %q", k) - } - keys = append(keys, k) - } - if len(keys) > 0 { - sort.Strings(keys) - return strings.Join(keys, ","), nil - } - return "", nil -} - func (cc *ClientConn) responseHeaderTimeout() time.Duration { if cc.t.t1 != nil { return cc.t.t1.ResponseHeaderTimeout @@ -1196,22 +1286,6 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration { return 0 } -// checkConnHeaders checks whether req has any invalid connection-level headers. -// per RFC 7540 section 8.1.2.2: Connection-Specific Header Fields. -// Certain headers are special-cased as okay but not transmitted later. -func checkConnHeaders(req *http.Request) error { - if v := req.Header.Get("Upgrade"); v != "" { - return fmt.Errorf("http2: invalid Upgrade request header: %q", req.Header["Upgrade"]) - } - if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") { - return fmt.Errorf("http2: invalid Transfer-Encoding request header: %q", vv) - } - if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) { - return fmt.Errorf("http2: invalid Connection request header: %q", vv) - } - return nil -} - // actualContentLength returns a sanitized version of // req.ContentLength, where 0 actually means zero (not unknown) and -1 // means unknown. @@ -1257,25 +1331,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) donec: make(chan struct{}), } - // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? - if !cc.t.disableCompression() && - req.Header.Get("Accept-Encoding") == "" && - req.Header.Get("Range") == "" && - !cs.isHead { - // Request gzip only, not deflate. Deflate is ambiguous and - // not as universally supported anyway. - // See: https://zlib.net/zlib_faq.html#faq39 - // - // Note that we don't request this for HEAD requests, - // due to a bug in nginx: - // http://trac.nginx.org/nginx/ticket/358 - // https://golang.org/issue/5522 - // - // We don't request gzip if the request is for a range, since - // auto-decoding a portion of a gzipped document will just fail - // anyway. See https://golang.org/issue/8923 - cs.requestedGzip = true - } + cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression()) go cs.doRequest(req, streamf) @@ -1376,6 +1432,8 @@ func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream) cs.cleanupWriteRequest(err) } +var errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer") + // writeRequest sends a request. // // It returns nil after the request is written, the response read, @@ -1387,8 +1445,11 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre cc := cs.cc ctx := cs.ctx - if err := checkConnHeaders(req); err != nil { - return err + // wait for setting frames to be received, a server can change this value later, + // but we just wait for the first settings frame + var isExtendedConnect bool + if req.Method == "CONNECT" && req.Header.Get(":protocol") != "" { + isExtendedConnect = true } // Acquire the new-request lock by writing to reqHeaderMu. @@ -1397,6 +1458,18 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre if cc.reqHeaderMu == nil { panic("RoundTrip on uninitialized ClientConn") // for tests } + if isExtendedConnect { + select { + case <-cs.reqCancel: + return errRequestCanceled + case <-ctx.Done(): + return ctx.Err() + case <-cc.seenSettingsChan: + if !cc.extendedConnectAllowed { + return errExtendedConnectNotSupported + } + } + } select { case cc.reqHeaderMu <- struct{}{}: case <-cs.reqCancel: @@ -1535,26 +1608,39 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error { // we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is // sent by writeRequestBody below, along with any Trailers, // again in form HEADERS{1}, CONTINUATION{0,}) - trailers, err := commaSeparatedTrailers(req) - if err != nil { - return err - } - hasTrailers := trailers != "" - contentLen := actualContentLength(req) - hasBody := contentLen != 0 - hdrs, err := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen) + cc.hbuf.Reset() + res, err := encodeRequestHeaders(req, cs.requestedGzip, cc.peerMaxHeaderListSize, func(name, value string) { + cc.writeHeader(name, value) + }) if err != nil { - return err + return fmt.Errorf("http2: %w", err) } + hdrs := cc.hbuf.Bytes() // Write the request. - endStream := !hasBody && !hasTrailers + endStream := !res.HasBody && !res.HasTrailers cs.sentHeaders = true err = cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs) traceWroteHeaders(cs.trace) return err } +func encodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { + return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{ + Request: httpcommon.Request{ + Header: req.Header, + Trailer: req.Trailer, + URL: req.URL, + Host: req.Host, + Method: req.Method, + ActualContentLength: actualContentLength(req), + }, + AddGzipHeader: addGzipHeader, + PeerMaxHeaderListSize: peerMaxHeaderListSize, + DefaultUserAgent: defaultUserAgent, + }, headerf) +} + // cleanupWriteRequest performs post-request tasks. // // If err (the result of writeRequest) is non-nil and the stream is not closed, @@ -1578,6 +1664,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) { cs.reqBodyClosed = make(chan struct{}) } bodyClosed := cs.reqBodyClosed + closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil cc.mu.Unlock() if mustCloseBody { cs.reqBody.Close() @@ -1602,16 +1689,44 @@ func (cs *clientStream) cleanupWriteRequest(err error) { if cs.sentHeaders { if se, ok := err.(StreamError); ok { if se.Cause != errFromPeer { - cc.writeStreamReset(cs.ID, se.Code, err) + cc.writeStreamReset(cs.ID, se.Code, false, err) } } else { - cc.writeStreamReset(cs.ID, ErrCodeCancel, err) + // We're cancelling an in-flight request. + // + // This could be due to the server becoming unresponsive. + // To avoid sending too many requests on a dead connection, + // we let the request continue to consume a concurrency slot + // until we can confirm the server is still responding. + // We do this by sending a PING frame along with the RST_STREAM + // (unless a ping is already in flight). + // + // For simplicity, we don't bother tracking the PING payload: + // We reset cc.pendingResets any time we receive a PING ACK. + // + // We skip this if the conn is going to be closed on idle, + // because it's short lived and will probably be closed before + // we get the ping response. + ping := false + if !closeOnIdle { + cc.mu.Lock() + // rstStreamPingsBlocked works around a gRPC behavior: + // see comment on the field for details. + if !cc.rstStreamPingsBlocked { + if cc.pendingResets == 0 { + ping = true + } + cc.pendingResets++ + } + cc.mu.Unlock() + } + cc.writeStreamReset(cs.ID, ErrCodeCancel, ping, err) } } cs.bufPipe.CloseWithError(err) // no-op if already closed } else { if cs.sentHeaders && !cs.sentEndStream { - cc.writeStreamReset(cs.ID, ErrCodeNo, nil) + cc.writeStreamReset(cs.ID, ErrCodeNo, false, nil) } cs.bufPipe.CloseWithError(errRequestCanceled) } @@ -1633,12 +1748,17 @@ func (cs *clientStream) cleanupWriteRequest(err error) { // Must hold cc.mu. func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error { for { - cc.lastActive = time.Now() + if cc.closed && cc.nextStreamID == 1 && cc.streamsReserved == 0 { + // This is the very first request sent to this connection. + // Return a fatal error which aborts the retry loop. + return errClientConnNotEstablished + } + cc.lastActive = cc.t.now() if cc.closed || !cc.canTakeNewRequestLocked() { return errClientConnUnusable } cc.lastIdle = time.Time{} - if int64(len(cc.streams)) < int64(cc.maxConcurrentStreams) { + if cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) { return nil } cc.pendingRequests++ @@ -1908,214 +2028,6 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) } } -func validateHeaders(hdrs http.Header) string { - for k, vv := range hdrs { - if !httpguts.ValidHeaderFieldName(k) { - return fmt.Sprintf("name %q", k) - } - for _, v := range vv { - if !httpguts.ValidHeaderFieldValue(v) { - // Don't include the value in the error, - // because it may be sensitive. - return fmt.Sprintf("value for header %q", k) - } - } - } - return "" -} - -var errNilRequestURL = errors.New("http2: Request.URI is nil") - -// requires cc.wmu be held. -func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) { - cc.hbuf.Reset() - if req.URL == nil { - return nil, errNilRequestURL - } - - host := req.Host - if host == "" { - host = req.URL.Host - } - host, err := httpguts.PunycodeHostPort(host) - if err != nil { - return nil, err - } - if !httpguts.ValidHostHeader(host) { - return nil, errors.New("http2: invalid Host header") - } - - var path string - if req.Method != "CONNECT" { - path = req.URL.RequestURI() - if !validPseudoPath(path) { - orig := path - path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) - if !validPseudoPath(path) { - if req.URL.Opaque != "" { - return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) - } else { - return nil, fmt.Errorf("invalid request :path %q", orig) - } - } - } - } - - // Check for any invalid headers+trailers and return an error before we - // potentially pollute our hpack state. (We want to be able to - // continue to reuse the hpack encoder for future requests) - if err := validateHeaders(req.Header); err != "" { - return nil, fmt.Errorf("invalid HTTP header %s", err) - } - if err := validateHeaders(req.Trailer); err != "" { - return nil, fmt.Errorf("invalid HTTP trailer %s", err) - } - - enumerateHeaders := func(f func(name, value string)) { - // 8.1.2.3 Request Pseudo-Header Fields - // The :path pseudo-header field includes the path and query parts of the - // target URI (the path-absolute production and optionally a '?' character - // followed by the query production, see Sections 3.3 and 3.4 of - // [RFC3986]). - f(":authority", host) - m := req.Method - if m == "" { - m = http.MethodGet - } - f(":method", m) - if req.Method != "CONNECT" { - f(":path", path) - f(":scheme", req.URL.Scheme) - } - if trailers != "" { - f("trailer", trailers) - } - - var didUA bool - for k, vv := range req.Header { - if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") { - // Host is :authority, already sent. - // Content-Length is automatic, set below. - continue - } else if asciiEqualFold(k, "connection") || - asciiEqualFold(k, "proxy-connection") || - asciiEqualFold(k, "transfer-encoding") || - asciiEqualFold(k, "upgrade") || - asciiEqualFold(k, "keep-alive") { - // Per 8.1.2.2 Connection-Specific Header - // Fields, don't send connection-specific - // fields. We have already checked if any - // are error-worthy so just ignore the rest. - continue - } else if asciiEqualFold(k, "user-agent") { - // Match Go's http1 behavior: at most one - // User-Agent. If set to nil or empty string, - // then omit it. Otherwise if not mentioned, - // include the default (below). - didUA = true - if len(vv) < 1 { - continue - } - vv = vv[:1] - if vv[0] == "" { - continue - } - } else if asciiEqualFold(k, "cookie") { - // Per 8.1.2.5 To allow for better compression efficiency, the - // Cookie header field MAY be split into separate header fields, - // each with one or more cookie-pairs. - for _, v := range vv { - for { - p := strings.IndexByte(v, ';') - if p < 0 { - break - } - f("cookie", v[:p]) - p++ - // strip space after semicolon if any. - for p+1 <= len(v) && v[p] == ' ' { - p++ - } - v = v[p:] - } - if len(v) > 0 { - f("cookie", v) - } - } - continue - } - - for _, v := range vv { - f(k, v) - } - } - if shouldSendReqContentLength(req.Method, contentLength) { - f("content-length", strconv.FormatInt(contentLength, 10)) - } - if addGzipHeader { - f("accept-encoding", "gzip") - } - if !didUA { - f("user-agent", defaultUserAgent) - } - } - - // Do a first pass over the headers counting bytes to ensure - // we don't exceed cc.peerMaxHeaderListSize. This is done as a - // separate pass before encoding the headers to prevent - // modifying the hpack state. - hlSize := uint64(0) - enumerateHeaders(func(name, value string) { - hf := hpack.HeaderField{Name: name, Value: value} - hlSize += uint64(hf.Size()) - }) - - if hlSize > cc.peerMaxHeaderListSize { - return nil, errRequestHeaderListSize - } - - trace := httptrace.ContextClientTrace(req.Context()) - traceHeaders := traceHasWroteHeaderField(trace) - - // Header list size is ok. Write the headers. - enumerateHeaders(func(name, value string) { - name, ascii := lowerHeader(name) - if !ascii { - // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header - // field names have to be ASCII characters (just as in HTTP/1.x). - return - } - cc.writeHeader(name, value) - if traceHeaders { - traceWroteHeaderField(trace, name, value) - } - }) - - return cc.hbuf.Bytes(), nil -} - -// shouldSendReqContentLength reports whether the http2.Transport should send -// a "content-length" request header. This logic is basically a copy of the net/http -// transferWriter.shouldSendContentLength. -// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). -// -1 means unknown. -func shouldSendReqContentLength(method string, contentLength int64) bool { - if contentLength > 0 { - return true - } - if contentLength < 0 { - return false - } - // For zero bodies, whether we send a content-length depends on the method. - // It also kinda doesn't matter for http2 either way, with END_STREAM. - switch method { - case "POST", "PUT", "PATCH": - return true - default: - return false - } -} - // requires cc.wmu be held. func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) { cc.hbuf.Reset() @@ -2132,7 +2044,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) { } for k, vv := range trailer { - lowKey, ascii := lowerHeader(k) + lowKey, ascii := httpcommon.LowerHeader(k) if !ascii { // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header // field names have to be ASCII characters (just as in HTTP/1.x). @@ -2180,10 +2092,10 @@ func (cc *ClientConn) forgetStreamID(id uint32) { if len(cc.streams) != slen-1 { panic("forgetting unknown stream id") } - cc.lastActive = time.Now() + cc.lastActive = cc.t.now() if len(cc.streams) == 0 && cc.idleTimer != nil { cc.idleTimer.Reset(cc.idleTimeout) - cc.lastIdle = time.Now() + cc.lastIdle = cc.t.now() } // Wake up writeRequestBody via clientStream.awaitFlowControl and // wake up RoundTrip if there is a pending request. @@ -2243,7 +2155,6 @@ func isEOFOrNetReadError(err error) bool { func (rl *clientConnReadLoop) cleanup() { cc := rl.cc - cc.t.connPool().MarkDead(cc) defer cc.closeConn() defer close(cc.readerDone) @@ -2267,6 +2178,27 @@ func (rl *clientConnReadLoop) cleanup() { } cc.closed = true + // If the connection has never been used, and has been open for only a short time, + // leave it in the connection pool for a little while. + // + // This avoids a situation where new connections are constantly created, + // added to the pool, fail, and are removed from the pool, without any error + // being surfaced to the user. + unusedWaitTime := 5 * time.Second + if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout { + unusedWaitTime = cc.idleTimeout + } + idleTime := cc.t.now().Sub(cc.lastActive) + if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle { + cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() { + cc.t.connPool().MarkDead(cc) + }) + } else { + cc.mu.Unlock() // avoid any deadlocks in MarkDead + cc.t.connPool().MarkDead(cc) + cc.mu.Lock() + } + for _, cs := range cc.streams { select { case <-cs.peerClosed: @@ -2278,6 +2210,13 @@ func (rl *clientConnReadLoop) cleanup() { } cc.cond.Broadcast() cc.mu.Unlock() + + if !cc.seenSettings { + // If we have a pending request that wants extended CONNECT, + // let it continue and fail with the connection error. + cc.extendedConnectAllowed = true + close(cc.seenSettingsChan) + } } // countReadFrameError calls Transport.CountError with a string @@ -2324,7 +2263,7 @@ func (rl *clientConnReadLoop) run() error { cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err) } if se, ok := err.(StreamError); ok { - if cs := rl.streamByID(se.StreamID); cs != nil { + if cs := rl.streamByID(se.StreamID, notHeaderOrDataFrame); cs != nil { if se.Cause == nil { se.Cause = cc.fr.errDetail } @@ -2376,7 +2315,7 @@ func (rl *clientConnReadLoop) run() error { } func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error { - cs := rl.streamByID(f.StreamID) + cs := rl.streamByID(f.StreamID, headerOrDataFrame) if cs == nil { // We'd get here if we canceled a request while the // server had its response still in flight. So if this @@ -2464,7 +2403,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra Status: status + " " + http.StatusText(statusCode), } for _, hf := range regularFields { - key := canonicalHeader(hf.Name) + key := httpcommon.CanonicalHeader(hf.Name) if key == "Trailer" { t := res.Trailer if t == nil { @@ -2472,7 +2411,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra res.Trailer = t } foreachHeaderElement(hf.Value, func(v string) { - t[canonicalHeader(v)] = nil + t[httpcommon.CanonicalHeader(v)] = nil }) } else { vv := header[key] @@ -2494,15 +2433,34 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra if f.StreamEnded() { return nil, errors.New("1xx informational response with END_STREAM flag") } - cs.num1xx++ - const max1xxResponses = 5 // arbitrary bound on number of informational responses, same as net/http - if cs.num1xx > max1xxResponses { - return nil, errors.New("http2: too many 1xx informational responses") - } if fn := cs.get1xxTraceFunc(); fn != nil { + // If the 1xx response is being delivered to the user, + // then they're responsible for limiting the number + // of responses. if err := fn(statusCode, textproto.MIMEHeader(header)); err != nil { return nil, err } + } else { + // If the user didn't examine the 1xx response, then we + // limit the size of all 1xx headers. + // + // This differs a bit from the HTTP/1 implementation, which + // limits the size of all 1xx headers plus the final response. + // Use the larger limit of MaxHeaderListSize and + // net/http.Transport.MaxResponseHeaderBytes. + limit := int64(cs.cc.t.maxHeaderListSize()) + if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit { + limit = t1.MaxResponseHeaderBytes + } + for _, h := range f.Fields { + cs.totalHeaderSize += int64(h.Size()) + } + if cs.totalHeaderSize > limit { + if VerboseLogs { + log.Printf("http2: 1xx informational responses too large") + } + return nil, errors.New("header list too large") + } } if statusCode == 100 { traceGot100Continue(cs.trace) @@ -2577,7 +2535,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr trailer := make(http.Header) for _, hf := range f.RegularFields() { - key := canonicalHeader(hf.Name) + key := httpcommon.CanonicalHeader(hf.Name) trailer[key] = append(trailer[key], hf.Value) } cs.trailer = trailer @@ -2686,7 +2644,7 @@ func (b transportResponseBody) Close() error { func (rl *clientConnReadLoop) processData(f *DataFrame) error { cc := rl.cc - cs := rl.streamByID(f.StreamID) + cs := rl.streamByID(f.StreamID, headerOrDataFrame) data := f.Data() if cs == nil { cc.mu.Lock() @@ -2821,9 +2779,22 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) { cs.abortStream(err) } -func (rl *clientConnReadLoop) streamByID(id uint32) *clientStream { +// Constants passed to streamByID for documentation purposes. +const ( + headerOrDataFrame = true + notHeaderOrDataFrame = false +) + +// streamByID returns the stream with the given id, or nil if no stream has that id. +// If headerOrData is true, it clears rst.StreamPingsBlocked. +func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream { rl.cc.mu.Lock() defer rl.cc.mu.Unlock() + if headerOrData { + // Work around an unfortunate gRPC behavior. + // See comment on ClientConn.rstStreamPingsBlocked for details. + rl.cc.rstStreamPingsBlocked = false + } cs := rl.cc.streams[id] if cs != nil && !cs.readAborted { return cs @@ -2917,6 +2888,21 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { case SettingHeaderTableSize: cc.henc.SetMaxDynamicTableSize(s.Val) cc.peerMaxHeaderTableSize = s.Val + case SettingEnableConnectProtocol: + if err := s.Valid(); err != nil { + return err + } + // If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL, + // we require that it do so in the first SETTINGS frame. + // + // When we attempt to use extended CONNECT, we wait for the first + // SETTINGS frame to see if the server supports it. If we let the + // server enable the feature with a later SETTINGS frame, then + // users will see inconsistent results depending on whether we've + // seen that frame or not. + if !cc.seenSettings { + cc.extendedConnectAllowed = s.Val == 1 + } default: cc.vlogf("Unhandled Setting: %v", s) } @@ -2934,6 +2920,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { // connection can establish to our default. cc.maxConcurrentStreams = defaultMaxConcurrentStreams } + close(cc.seenSettingsChan) cc.seenSettings = true } @@ -2942,7 +2929,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { cc := rl.cc - cs := rl.streamByID(f.StreamID) + cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame) if f.StreamID != 0 && cs == nil { return nil } @@ -2971,7 +2958,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { } func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error { - cs := rl.streamByID(f.StreamID) + cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame) if cs == nil { // TODO: return error if server tries to RST_STREAM an idle stream return nil @@ -3046,6 +3033,12 @@ func (rl *clientConnReadLoop) processPing(f *PingFrame) error { close(c) delete(cc.pings, f.Data) } + if cc.pendingResets > 0 { + // See clientStream.cleanupWriteRequest. + cc.pendingResets = 0 + cc.rstStreamPingsBlocked = true + cc.cond.Broadcast() + } return nil } cc := rl.cc @@ -3068,20 +3061,27 @@ func (rl *clientConnReadLoop) processPushPromise(f *PushPromiseFrame) error { return ConnectionError(ErrCodeProtocol) } -func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error) { +// writeStreamReset sends a RST_STREAM frame. +// When ping is true, it also sends a PING frame with a random payload. +func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, ping bool, err error) { // TODO: map err to more interesting error codes, once the // HTTP community comes up with some. But currently for // RST_STREAM there's no equivalent to GOAWAY frame's debug // data, and the error codes are all pretty vague ("cancel"). cc.wmu.Lock() cc.fr.WriteRSTStream(streamID, code) + if ping { + var payload [8]byte + rand.Read(payload[:]) + cc.fr.WritePing(false, payload) + } cc.bw.Flush() cc.wmu.Unlock() } var ( errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") - errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit") + errRequestHeaderListSize = httpcommon.ErrRequestHeaderListSize ) func (cc *ClientConn) logf(format string, args ...interface{}) { @@ -3228,7 +3228,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) { cc.mu.Lock() ci.WasIdle = len(cc.streams) == 0 && reused if ci.WasIdle && !cc.lastActive.IsZero() { - ci.IdleTime = time.Since(cc.lastActive) + ci.IdleTime = cc.t.timeSince(cc.lastActive) } cc.mu.Unlock() @@ -3265,16 +3265,6 @@ func traceFirstResponseByte(trace *httptrace.ClientTrace) { } } -func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { - return trace != nil && trace.WroteHeaderField != nil -} - -func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) { - if trace != nil && trace.WroteHeaderField != nil { - trace.WroteHeaderField(k, []string{v}) - } -} - func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { if trace != nil { return trace.Got1xxResponse diff --git a/go-controller/vendor/golang.org/x/net/http2/unencrypted.go b/go-controller/vendor/golang.org/x/net/http2/unencrypted.go new file mode 100644 index 0000000000..b2de211613 --- /dev/null +++ b/go-controller/vendor/golang.org/x/net/http2/unencrypted.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "crypto/tls" + "errors" + "net" +) + +const nextProtoUnencryptedHTTP2 = "unencrypted_http2" + +// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn. +// +// TLSNextProto functions accept a *tls.Conn. +// +// When passing an unencrypted HTTP/2 connection to a TLSNextProto function, +// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection. +// To be extra careful about mistakes (accidentally dropping TLS encryption in a place +// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method +// that returns the actual connection we want to use. +func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) { + conner, ok := tc.NetConn().(interface { + UnencryptedNetConn() net.Conn + }) + if !ok { + return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff") + } + return conner.UnencryptedNetConn(), nil +} diff --git a/go-controller/vendor/golang.org/x/net/http2/write.go b/go-controller/vendor/golang.org/x/net/http2/write.go index 6ff6bee7e9..fdb35b9477 100644 --- a/go-controller/vendor/golang.org/x/net/http2/write.go +++ b/go-controller/vendor/golang.org/x/net/http2/write.go @@ -13,6 +13,7 @@ import ( "golang.org/x/net/http/httpguts" "golang.org/x/net/http2/hpack" + "golang.org/x/net/internal/httpcommon" ) // writeFramer is implemented by any type that is used to write frames. @@ -351,7 +352,7 @@ func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) { } for _, k := range keys { vv := h[k] - k, ascii := lowerHeader(k) + k, ascii := httpcommon.LowerHeader(k) if !ascii { // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header // field names have to be ASCII characters (just as in HTTP/1.x). diff --git a/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go new file mode 100644 index 0000000000..ed14da5afc --- /dev/null +++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/ascii.go @@ -0,0 +1,53 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import "strings" + +// The HTTP protocols are defined in terms of ASCII, not Unicode. This file +// contains helper functions which may use Unicode-aware functions which would +// otherwise be unsafe and could introduce vulnerabilities if used improperly. + +// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t +// are equal, ASCII-case-insensitively. +func asciiEqualFold(s, t string) bool { + if len(s) != len(t) { + return false + } + for i := 0; i < len(s); i++ { + if lower(s[i]) != lower(t[i]) { + return false + } + } + return true +} + +// lower returns the ASCII lowercase version of b. +func lower(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// isASCIIPrint returns whether s is ASCII and printable according to +// https://tools.ietf.org/html/rfc20#section-4.2. +func isASCIIPrint(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > '~' { + return false + } + } + return true +} + +// asciiToLower returns the lowercase version of s if s is ASCII and printable, +// and whether or not it was. +func asciiToLower(s string) (lower string, ok bool) { + if !isASCIIPrint(s) { + return "", false + } + return strings.ToLower(s), true +} diff --git a/go-controller/vendor/golang.org/x/net/http2/headermap.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go similarity index 74% rename from go-controller/vendor/golang.org/x/net/http2/headermap.go rename to go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go index 149b3dd20e..92483d8e41 100644 --- a/go-controller/vendor/golang.org/x/net/http2/headermap.go +++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/headermap.go @@ -1,11 +1,11 @@ -// Copyright 2014 The Go Authors. All rights reserved. +// Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package http2 +package httpcommon import ( - "net/http" + "net/textproto" "sync" ) @@ -82,13 +82,15 @@ func buildCommonHeaderMaps() { commonLowerHeader = make(map[string]string, len(common)) commonCanonHeader = make(map[string]string, len(common)) for _, v := range common { - chk := http.CanonicalHeaderKey(v) + chk := textproto.CanonicalMIMEHeaderKey(v) commonLowerHeader[chk] = v commonCanonHeader[v] = chk } } -func lowerHeader(v string) (lower string, ascii bool) { +// LowerHeader returns the lowercase form of a header name, +// used on the wire for HTTP/2 and HTTP/3 requests. +func LowerHeader(v string) (lower string, ascii bool) { buildCommonHeaderMapsOnce() if s, ok := commonLowerHeader[v]; ok { return s, true @@ -96,10 +98,18 @@ func lowerHeader(v string) (lower string, ascii bool) { return asciiToLower(v) } -func canonicalHeader(v string) string { +// CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".) +func CanonicalHeader(v string) string { buildCommonHeaderMapsOnce() if s, ok := commonCanonHeader[v]; ok { return s } - return http.CanonicalHeaderKey(v) + return textproto.CanonicalMIMEHeaderKey(v) +} + +// CachedCanonicalHeader returns the canonical form of a well-known header name. +func CachedCanonicalHeader(v string) (string, bool) { + buildCommonHeaderMapsOnce() + s, ok := commonCanonHeader[v] + return s, ok } diff --git a/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go b/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go new file mode 100644 index 0000000000..4b70553179 --- /dev/null +++ b/go-controller/vendor/golang.org/x/net/internal/httpcommon/request.go @@ -0,0 +1,467 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "context" + "errors" + "fmt" + "net/http/httptrace" + "net/textproto" + "net/url" + "sort" + "strconv" + "strings" + + "golang.org/x/net/http/httpguts" + "golang.org/x/net/http2/hpack" +) + +var ( + ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit") +) + +// Request is a subset of http.Request. +// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http +// without creating a dependency cycle. +type Request struct { + URL *url.URL + Method string + Host string + Header map[string][]string + Trailer map[string][]string + ActualContentLength int64 // 0 means 0, -1 means unknown +} + +// EncodeHeadersParam is parameters to EncodeHeaders. +type EncodeHeadersParam struct { + Request Request + + // AddGzipHeader indicates that an "accept-encoding: gzip" header should be + // added to the request. + AddGzipHeader bool + + // PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting. + PeerMaxHeaderListSize uint64 + + // DefaultUserAgent is the User-Agent header to send when the request + // neither contains a User-Agent nor disables it. + DefaultUserAgent string +} + +// EncodeHeadersParam is the result of EncodeHeaders. +type EncodeHeadersResult struct { + HasBody bool + HasTrailers bool +} + +// EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3. +// It validates a request and calls headerf with each pseudo-header and header +// for the request. +// The headerf function is called with the validated, canonicalized header name. +func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) { + req := param.Request + + // Check for invalid connection-level headers. + if err := checkConnHeaders(req.Header); err != nil { + return res, err + } + + if req.URL == nil { + return res, errors.New("Request.URL is nil") + } + + host := req.Host + if host == "" { + host = req.URL.Host + } + host, err := httpguts.PunycodeHostPort(host) + if err != nil { + return res, err + } + if !httpguts.ValidHostHeader(host) { + return res, errors.New("invalid Host header") + } + + // isNormalConnect is true if this is a non-extended CONNECT request. + isNormalConnect := false + var protocol string + if vv := req.Header[":protocol"]; len(vv) > 0 { + protocol = vv[0] + } + if req.Method == "CONNECT" && protocol == "" { + isNormalConnect = true + } else if protocol != "" && req.Method != "CONNECT" { + return res, errors.New("invalid :protocol header in non-CONNECT request") + } + + // Validate the path, except for non-extended CONNECT requests which have no path. + var path string + if !isNormalConnect { + path = req.URL.RequestURI() + if !validPseudoPath(path) { + orig := path + path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) + if !validPseudoPath(path) { + if req.URL.Opaque != "" { + return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) + } else { + return res, fmt.Errorf("invalid request :path %q", orig) + } + } + } + } + + // Check for any invalid headers+trailers and return an error before we + // potentially pollute our hpack state. (We want to be able to + // continue to reuse the hpack encoder for future requests) + if err := validateHeaders(req.Header); err != "" { + return res, fmt.Errorf("invalid HTTP header %s", err) + } + if err := validateHeaders(req.Trailer); err != "" { + return res, fmt.Errorf("invalid HTTP trailer %s", err) + } + + trailers, err := commaSeparatedTrailers(req.Trailer) + if err != nil { + return res, err + } + + enumerateHeaders := func(f func(name, value string)) { + // 8.1.2.3 Request Pseudo-Header Fields + // The :path pseudo-header field includes the path and query parts of the + // target URI (the path-absolute production and optionally a '?' character + // followed by the query production, see Sections 3.3 and 3.4 of + // [RFC3986]). + f(":authority", host) + m := req.Method + if m == "" { + m = "GET" + } + f(":method", m) + if !isNormalConnect { + f(":path", path) + f(":scheme", req.URL.Scheme) + } + if protocol != "" { + f(":protocol", protocol) + } + if trailers != "" { + f("trailer", trailers) + } + + var didUA bool + for k, vv := range req.Header { + if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") { + // Host is :authority, already sent. + // Content-Length is automatic, set below. + continue + } else if asciiEqualFold(k, "connection") || + asciiEqualFold(k, "proxy-connection") || + asciiEqualFold(k, "transfer-encoding") || + asciiEqualFold(k, "upgrade") || + asciiEqualFold(k, "keep-alive") { + // Per 8.1.2.2 Connection-Specific Header + // Fields, don't send connection-specific + // fields. We have already checked if any + // are error-worthy so just ignore the rest. + continue + } else if asciiEqualFold(k, "user-agent") { + // Match Go's http1 behavior: at most one + // User-Agent. If set to nil or empty string, + // then omit it. Otherwise if not mentioned, + // include the default (below). + didUA = true + if len(vv) < 1 { + continue + } + vv = vv[:1] + if vv[0] == "" { + continue + } + } else if asciiEqualFold(k, "cookie") { + // Per 8.1.2.5 To allow for better compression efficiency, the + // Cookie header field MAY be split into separate header fields, + // each with one or more cookie-pairs. + for _, v := range vv { + for { + p := strings.IndexByte(v, ';') + if p < 0 { + break + } + f("cookie", v[:p]) + p++ + // strip space after semicolon if any. + for p+1 <= len(v) && v[p] == ' ' { + p++ + } + v = v[p:] + } + if len(v) > 0 { + f("cookie", v) + } + } + continue + } else if k == ":protocol" { + // :protocol pseudo-header was already sent above. + continue + } + + for _, v := range vv { + f(k, v) + } + } + if shouldSendReqContentLength(req.Method, req.ActualContentLength) { + f("content-length", strconv.FormatInt(req.ActualContentLength, 10)) + } + if param.AddGzipHeader { + f("accept-encoding", "gzip") + } + if !didUA { + f("user-agent", param.DefaultUserAgent) + } + } + + // Do a first pass over the headers counting bytes to ensure + // we don't exceed cc.peerMaxHeaderListSize. This is done as a + // separate pass before encoding the headers to prevent + // modifying the hpack state. + if param.PeerMaxHeaderListSize > 0 { + hlSize := uint64(0) + enumerateHeaders(func(name, value string) { + hf := hpack.HeaderField{Name: name, Value: value} + hlSize += uint64(hf.Size()) + }) + + if hlSize > param.PeerMaxHeaderListSize { + return res, ErrRequestHeaderListSize + } + } + + trace := httptrace.ContextClientTrace(ctx) + + // Header list size is ok. Write the headers. + enumerateHeaders(func(name, value string) { + name, ascii := LowerHeader(name) + if !ascii { + // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header + // field names have to be ASCII characters (just as in HTTP/1.x). + return + } + + headerf(name, value) + + if trace != nil && trace.WroteHeaderField != nil { + trace.WroteHeaderField(name, []string{value}) + } + }) + + res.HasBody = req.ActualContentLength != 0 + res.HasTrailers = trailers != "" + return res, nil +} + +// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header +// for a request. +func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool { + // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? + if !disableCompression && + len(header["Accept-Encoding"]) == 0 && + len(header["Range"]) == 0 && + method != "HEAD" { + // Request gzip only, not deflate. Deflate is ambiguous and + // not as universally supported anyway. + // See: https://zlib.net/zlib_faq.html#faq39 + // + // Note that we don't request this for HEAD requests, + // due to a bug in nginx: + // http://trac.nginx.org/nginx/ticket/358 + // https://golang.org/issue/5522 + // + // We don't request gzip if the request is for a range, since + // auto-decoding a portion of a gzipped document will just fail + // anyway. See https://golang.org/issue/8923 + return true + } + return false +} + +// checkConnHeaders checks whether req has any invalid connection-level headers. +// +// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3 +// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1 +// +// Certain headers are special-cased as okay but not transmitted later. +// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding. +func checkConnHeaders(h map[string][]string) error { + if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") { + return fmt.Errorf("invalid Upgrade request header: %q", vv) + } + if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") { + return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv) + } + if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) { + return fmt.Errorf("invalid Connection request header: %q", vv) + } + return nil +} + +func commaSeparatedTrailers(trailer map[string][]string) (string, error) { + keys := make([]string, 0, len(trailer)) + for k := range trailer { + k = CanonicalHeader(k) + switch k { + case "Transfer-Encoding", "Trailer", "Content-Length": + return "", fmt.Errorf("invalid Trailer key %q", k) + } + keys = append(keys, k) + } + if len(keys) > 0 { + sort.Strings(keys) + return strings.Join(keys, ","), nil + } + return "", nil +} + +// validPseudoPath reports whether v is a valid :path pseudo-header +// value. It must be either: +// +// - a non-empty string starting with '/' +// - the string '*', for OPTIONS requests. +// +// For now this is only used a quick check for deciding when to clean +// up Opaque URLs before sending requests from the Transport. +// See golang.org/issue/16847 +// +// We used to enforce that the path also didn't start with "//", but +// Google's GFE accepts such paths and Chrome sends them, so ignore +// that part of the spec. See golang.org/issue/19103. +func validPseudoPath(v string) bool { + return (len(v) > 0 && v[0] == '/') || v == "*" +} + +func validateHeaders(hdrs map[string][]string) string { + for k, vv := range hdrs { + if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" { + return fmt.Sprintf("name %q", k) + } + for _, v := range vv { + if !httpguts.ValidHeaderFieldValue(v) { + // Don't include the value in the error, + // because it may be sensitive. + return fmt.Sprintf("value for header %q", k) + } + } + } + return "" +} + +// shouldSendReqContentLength reports whether we should send +// a "content-length" request header. This logic is basically a copy of the net/http +// transferWriter.shouldSendContentLength. +// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). +// -1 means unknown. +func shouldSendReqContentLength(method string, contentLength int64) bool { + if contentLength > 0 { + return true + } + if contentLength < 0 { + return false + } + // For zero bodies, whether we send a content-length depends on the method. + // It also kinda doesn't matter for http2 either way, with END_STREAM. + switch method { + case "POST", "PUT", "PATCH": + return true + default: + return false + } +} + +// ServerRequestParam is parameters to NewServerRequest. +type ServerRequestParam struct { + Method string + Scheme, Authority, Path string + Protocol string + Header map[string][]string +} + +// ServerRequestResult is the result of NewServerRequest. +type ServerRequestResult struct { + // Various http.Request fields. + URL *url.URL + RequestURI string + Trailer map[string][]string + + NeedsContinue bool // client provided an "Expect: 100-continue" header + + // If the request should be rejected, this is a short string suitable for passing + // to the http2 package's CountError function. + // It might be a bit odd to return errors this way rather than returing an error, + // but this ensures we don't forget to include a CountError reason. + InvalidReason string +} + +func NewServerRequest(rp ServerRequestParam) ServerRequestResult { + needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue") + if needsContinue { + delete(rp.Header, "Expect") + } + // Merge Cookie headers into one "; "-delimited value. + if cookies := rp.Header["Cookie"]; len(cookies) > 1 { + rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")} + } + + // Setup Trailers + var trailer map[string][]string + for _, v := range rp.Header["Trailer"] { + for _, key := range strings.Split(v, ",") { + key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key)) + switch key { + case "Transfer-Encoding", "Trailer", "Content-Length": + // Bogus. (copy of http1 rules) + // Ignore. + default: + if trailer == nil { + trailer = make(map[string][]string) + } + trailer[key] = nil + } + } + } + delete(rp.Header, "Trailer") + + // "':authority' MUST NOT include the deprecated userinfo subcomponent + // for "http" or "https" schemed URIs." + // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8 + if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") { + return ServerRequestResult{ + InvalidReason: "userinfo_in_authority", + } + } + + var url_ *url.URL + var requestURI string + if rp.Method == "CONNECT" && rp.Protocol == "" { + url_ = &url.URL{Host: rp.Authority} + requestURI = rp.Authority // mimic HTTP/1 server behavior + } else { + var err error + url_, err = url.ParseRequestURI(rp.Path) + if err != nil { + return ServerRequestResult{ + InvalidReason: "bad_path", + } + } + requestURI = rp.Path + } + + return ServerRequestResult{ + URL: url_, + NeedsContinue: needsContinue, + RequestURI: requestURI, + Trailer: trailer, + } +} diff --git a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go index cebde7634f..3c9576e2d8 100644 --- a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go +++ b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_ppc64.go @@ -4,27 +4,27 @@ package socket type iovec struct { - Base *byte - Len uint64 + Base *byte + Len uint64 } type msghdr struct { - Name *byte - Namelen uint32 - Iov *iovec - Iovlen uint32 - Control *byte - Controllen uint32 - Flags int32 + Name *byte + Namelen uint32 + Iov *iovec + Iovlen uint32 + Control *byte + Controllen uint32 + Flags int32 } type cmsghdr struct { - Len uint32 - Level int32 - Type int32 + Len uint32 + Level int32 + Type int32 } const ( - sizeofIovec = 0x10 - sizeofMsghdr = 0x30 + sizeofIovec = 0x10 + sizeofMsghdr = 0x30 ) diff --git a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go index cebde7634f..3c9576e2d8 100644 --- a/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go +++ b/go-controller/vendor/golang.org/x/net/internal/socket/zsys_openbsd_riscv64.go @@ -4,27 +4,27 @@ package socket type iovec struct { - Base *byte - Len uint64 + Base *byte + Len uint64 } type msghdr struct { - Name *byte - Namelen uint32 - Iov *iovec - Iovlen uint32 - Control *byte - Controllen uint32 - Flags int32 + Name *byte + Namelen uint32 + Iov *iovec + Iovlen uint32 + Control *byte + Controllen uint32 + Flags int32 } type cmsghdr struct { - Len uint32 - Level int32 - Type int32 + Len uint32 + Level int32 + Type int32 } const ( - sizeofIovec = 0x10 - sizeofMsghdr = 0x30 + sizeofIovec = 0x10 + sizeofMsghdr = 0x30 ) diff --git a/go-controller/vendor/golang.org/x/net/proxy/per_host.go b/go-controller/vendor/golang.org/x/net/proxy/per_host.go index d7d4b8b6e3..32bdf435ec 100644 --- a/go-controller/vendor/golang.org/x/net/proxy/per_host.go +++ b/go-controller/vendor/golang.org/x/net/proxy/per_host.go @@ -7,6 +7,7 @@ package proxy import ( "context" "net" + "net/netip" "strings" ) @@ -57,7 +58,8 @@ func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net. } func (p *PerHost) dialerForRequest(host string) Dialer { - if ip := net.ParseIP(host); ip != nil { + if nip, err := netip.ParseAddr(host); err == nil { + ip := net.IP(nip.AsSlice()) for _, net := range p.bypassNetworks { if net.Contains(ip) { return p.bypass @@ -108,8 +110,8 @@ func (p *PerHost) AddFromString(s string) { } continue } - if ip := net.ParseIP(host); ip != nil { - p.AddIP(ip) + if nip, err := netip.ParseAddr(host); err == nil { + p.AddIP(net.IP(nip.AsSlice())) continue } if strings.HasPrefix(host, "*.") { diff --git a/go-controller/vendor/golang.org/x/net/websocket/websocket.go b/go-controller/vendor/golang.org/x/net/websocket/websocket.go index ac76165ceb..3448d20395 100644 --- a/go-controller/vendor/golang.org/x/net/websocket/websocket.go +++ b/go-controller/vendor/golang.org/x/net/websocket/websocket.go @@ -6,9 +6,10 @@ // as specified in RFC 6455. // // This package currently lacks some features found in an alternative -// and more actively maintained WebSocket package: +// and more actively maintained WebSocket packages: // -// https://pkg.go.dev/github.com/coder/websocket +// - [github.com/gorilla/websocket] +// - [github.com/coder/websocket] package websocket // import "golang.org/x/net/websocket" import ( diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go b/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go index 948a3ee63d..a4ea5d14f1 100644 --- a/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/go-controller/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -46,7 +46,7 @@ func (g *Group) done() { // returns a non-nil error or the first time Wait returns, whichever occurs // first. func WithContext(ctx context.Context) (*Group, context.Context) { - ctx, cancel := withCancelCause(ctx) + ctx, cancel := context.WithCancelCause(ctx) return &Group{cancel: cancel}, ctx } @@ -118,6 +118,7 @@ func (g *Group) TryGo(f func() error) bool { // SetLimit limits the number of active goroutines in this group to at most n. // A negative value indicates no limit. +// A limit of zero will prevent any new goroutines from being added. // // Any subsequent call to the Go method will block until it can add an active // goroutine without exceeding the configured limit. diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/go120.go b/go-controller/vendor/golang.org/x/sync/errgroup/go120.go deleted file mode 100644 index f93c740b63..0000000000 --- a/go-controller/vendor/golang.org/x/sync/errgroup/go120.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.20 - -package errgroup - -import "context" - -func withCancelCause(parent context.Context) (context.Context, func(error)) { - return context.WithCancelCause(parent) -} diff --git a/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go b/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go deleted file mode 100644 index 88ce33434e..0000000000 --- a/go-controller/vendor/golang.org/x/sync/errgroup/pre_go120.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.20 - -package errgroup - -import "context" - -func withCancelCause(parent context.Context) (context.Context, func(error)) { - ctx, cancel := context.WithCancel(parent) - return ctx, func(error) { cancel() } -} diff --git a/go-controller/vendor/golang.org/x/sys/unix/auxv.go b/go-controller/vendor/golang.org/x/sys/unix/auxv.go new file mode 100644 index 0000000000..37a82528f5 --- /dev/null +++ b/go-controller/vendor/golang.org/x/sys/unix/auxv.go @@ -0,0 +1,36 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) + +package unix + +import ( + "syscall" + "unsafe" +) + +//go:linkname runtime_getAuxv runtime.getAuxv +func runtime_getAuxv() []uintptr + +// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs. +// The returned slice is always a fresh copy, owned by the caller. +// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed, +// which happens in some locked-down environments and build modes. +func Auxv() ([][2]uintptr, error) { + vec := runtime_getAuxv() + vecLen := len(vec) + + if vecLen == 0 { + return nil, syscall.ENOENT + } + + if vecLen%2 != 0 { + return nil, syscall.EINVAL + } + + result := make([]uintptr, vecLen) + copy(result, vec) + return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil +} diff --git a/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go b/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go new file mode 100644 index 0000000000..1200487f2e --- /dev/null +++ b/go-controller/vendor/golang.org/x/sys/unix/auxv_unsupported.go @@ -0,0 +1,13 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) + +package unix + +import "syscall" + +func Auxv() ([][2]uintptr, error) { + return nil, syscall.ENOTSUP +} diff --git a/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go b/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go index dbe680eab8..7ca4fa12aa 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go +++ b/go-controller/vendor/golang.org/x/sys/unix/ioctl_linux.go @@ -58,6 +58,102 @@ func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) { return &value, err } +// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC +// association for the network device specified by ifname. +func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) { + ifr, err := NewIfreq(ifname) + if err != nil { + return nil, err + } + + value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO} + ifrd := ifr.withData(unsafe.Pointer(&value)) + + err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd) + return &value, err +} + +// IoctlGetHwTstamp retrieves the hardware timestamping configuration +// for the network device specified by ifname. +func IoctlGetHwTstamp(fd int, ifname string) (*HwTstampConfig, error) { + ifr, err := NewIfreq(ifname) + if err != nil { + return nil, err + } + + value := HwTstampConfig{} + ifrd := ifr.withData(unsafe.Pointer(&value)) + + err = ioctlIfreqData(fd, SIOCGHWTSTAMP, &ifrd) + return &value, err +} + +// IoctlSetHwTstamp updates the hardware timestamping configuration for +// the network device specified by ifname. +func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error { + ifr, err := NewIfreq(ifname) + if err != nil { + return err + } + ifrd := ifr.withData(unsafe.Pointer(cfg)) + return ioctlIfreqData(fd, SIOCSHWTSTAMP, &ifrd) +} + +// FdToClockID derives the clock ID from the file descriptor number +// - see clock_gettime(3), FD_TO_CLOCKID macros. The resulting ID is +// suitable for system calls like ClockGettime. +func FdToClockID(fd int) int32 { return int32((int(^fd) << 3) | 3) } + +// IoctlPtpClockGetcaps returns the description of a given PTP device. +func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) { + var value PtpClockCaps + err := ioctlPtr(fd, PTP_CLOCK_GETCAPS2, unsafe.Pointer(&value)) + return &value, err +} + +// IoctlPtpSysOffsetPrecise returns a description of the clock +// offset compared to the system clock. +func IoctlPtpSysOffsetPrecise(fd int) (*PtpSysOffsetPrecise, error) { + var value PtpSysOffsetPrecise + err := ioctlPtr(fd, PTP_SYS_OFFSET_PRECISE2, unsafe.Pointer(&value)) + return &value, err +} + +// IoctlPtpSysOffsetExtended returns an extended description of the +// clock offset compared to the system clock. The samples parameter +// specifies the desired number of measurements. +func IoctlPtpSysOffsetExtended(fd int, samples uint) (*PtpSysOffsetExtended, error) { + value := PtpSysOffsetExtended{Samples: uint32(samples)} + err := ioctlPtr(fd, PTP_SYS_OFFSET_EXTENDED2, unsafe.Pointer(&value)) + return &value, err +} + +// IoctlPtpPinGetfunc returns the configuration of the specified +// I/O pin on given PTP device. +func IoctlPtpPinGetfunc(fd int, index uint) (*PtpPinDesc, error) { + value := PtpPinDesc{Index: uint32(index)} + err := ioctlPtr(fd, PTP_PIN_GETFUNC2, unsafe.Pointer(&value)) + return &value, err +} + +// IoctlPtpPinSetfunc updates configuration of the specified PTP +// I/O pin. +func IoctlPtpPinSetfunc(fd int, pd *PtpPinDesc) error { + return ioctlPtr(fd, PTP_PIN_SETFUNC2, unsafe.Pointer(pd)) +} + +// IoctlPtpPeroutRequest configures the periodic output mode of the +// PTP I/O pins. +func IoctlPtpPeroutRequest(fd int, r *PtpPeroutRequest) error { + return ioctlPtr(fd, PTP_PEROUT_REQUEST2, unsafe.Pointer(r)) +} + +// IoctlPtpExttsRequest configures the external timestamping mode +// of the PTP I/O pins. +func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error { + return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r)) +} + // IoctlGetWatchdogInfo fetches information about a watchdog device from the // Linux watchdog API. For more information, see: // https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html. diff --git a/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh b/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh index ac54ecaba0..6ab02b6c31 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/go-controller/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -158,6 +158,16 @@ includes_Linux=' #endif #define _GNU_SOURCE +// See the description in unix/linux/types.go +#if defined(__ARM_EABI__) || \ + (defined(__mips__) && (_MIPS_SIM == _ABIO32)) || \ + (defined(__powerpc__) && (!defined(__powerpc64__))) +# ifdef _TIME_BITS +# undef _TIME_BITS +# endif +# define _TIME_BITS 32 +#endif + // is broken on powerpc64, as it fails to include definitions of // these structures. We just include them copied from . #if defined(__powerpc__) @@ -256,6 +266,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -527,6 +538,7 @@ ccflags="$@" $2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ || $2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ || $2 ~ /^NFC_.*_(MAX)?SIZE$/ || + $2 ~ /^PTP_/ || $2 ~ /^RAW_PAYLOAD_/ || $2 ~ /^[US]F_/ || $2 ~ /^TP_STATUS_/ || diff --git a/go-controller/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/go-controller/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index 97cb916f2c..be8c002070 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/go-controller/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -246,6 +246,18 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e return sendfile(outfd, infd, offset, count) } +func Dup3(oldfd, newfd, flags int) error { + if oldfd == newfd || flags&^O_CLOEXEC != 0 { + return EINVAL + } + how := F_DUP2FD + if flags&O_CLOEXEC != 0 { + how = F_DUP2FD_CLOEXEC + } + _, err := fcntl(oldfd, how, newfd) + return err +} + /* * Exposed directly */ diff --git a/go-controller/vendor/golang.org/x/sys/unix/syscall_linux.go b/go-controller/vendor/golang.org/x/sys/unix/syscall_linux.go index f08abd434f..230a94549a 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/go-controller/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1860,6 +1860,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e //sys ClockAdjtime(clockid int32, buf *Timex) (state int, err error) //sys ClockGetres(clockid int32, res *Timespec) (err error) //sys ClockGettime(clockid int32, time *Timespec) (err error) +//sys ClockSettime(clockid int32, time *Timespec) (err error) //sys ClockNanosleep(clockid int32, flags int, request *Timespec, remain *Timespec) (err error) //sys Close(fd int) (err error) //sys CloseRange(first uint, last uint, flags uint) (err error) diff --git a/go-controller/vendor/golang.org/x/sys/unix/syscall_solaris.go b/go-controller/vendor/golang.org/x/sys/unix/syscall_solaris.go index 21974af064..abc3955477 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/go-controller/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -1102,3 +1102,90 @@ func (s *Strioctl) SetInt(i int) { func IoctlSetStrioctlRetInt(fd int, req int, s *Strioctl) (int, error) { return ioctlPtrRet(fd, req, unsafe.Pointer(s)) } + +// Ucred Helpers +// See ucred(3c) and getpeerucred(3c) + +//sys getpeerucred(fd uintptr, ucred *uintptr) (err error) +//sys ucredFree(ucred uintptr) = ucred_free +//sys ucredGet(pid int) (ucred uintptr, err error) = ucred_get +//sys ucredGeteuid(ucred uintptr) (uid int) = ucred_geteuid +//sys ucredGetegid(ucred uintptr) (gid int) = ucred_getegid +//sys ucredGetruid(ucred uintptr) (uid int) = ucred_getruid +//sys ucredGetrgid(ucred uintptr) (gid int) = ucred_getrgid +//sys ucredGetsuid(ucred uintptr) (uid int) = ucred_getsuid +//sys ucredGetsgid(ucred uintptr) (gid int) = ucred_getsgid +//sys ucredGetpid(ucred uintptr) (pid int) = ucred_getpid + +// Ucred is an opaque struct that holds user credentials. +type Ucred struct { + ucred uintptr +} + +// We need to ensure that ucredFree is called on the underlying ucred +// when the Ucred is garbage collected. +func ucredFinalizer(u *Ucred) { + ucredFree(u.ucred) +} + +func GetPeerUcred(fd uintptr) (*Ucred, error) { + var ucred uintptr + err := getpeerucred(fd, &ucred) + if err != nil { + return nil, err + } + result := &Ucred{ + ucred: ucred, + } + // set the finalizer on the result so that the ucred will be freed + runtime.SetFinalizer(result, ucredFinalizer) + return result, nil +} + +func UcredGet(pid int) (*Ucred, error) { + ucred, err := ucredGet(pid) + if err != nil { + return nil, err + } + result := &Ucred{ + ucred: ucred, + } + // set the finalizer on the result so that the ucred will be freed + runtime.SetFinalizer(result, ucredFinalizer) + return result, nil +} + +func (u *Ucred) Geteuid() int { + defer runtime.KeepAlive(u) + return ucredGeteuid(u.ucred) +} + +func (u *Ucred) Getruid() int { + defer runtime.KeepAlive(u) + return ucredGetruid(u.ucred) +} + +func (u *Ucred) Getsuid() int { + defer runtime.KeepAlive(u) + return ucredGetsuid(u.ucred) +} + +func (u *Ucred) Getegid() int { + defer runtime.KeepAlive(u) + return ucredGetegid(u.ucred) +} + +func (u *Ucred) Getrgid() int { + defer runtime.KeepAlive(u) + return ucredGetrgid(u.ucred) +} + +func (u *Ucred) Getsgid() int { + defer runtime.KeepAlive(u) + return ucredGetsgid(u.ucred) +} + +func (u *Ucred) Getpid() int { + defer runtime.KeepAlive(u) + return ucredGetpid(u.ucred) +} diff --git a/go-controller/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/go-controller/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go index 312ae6ac1d..7bf5c04bb0 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go +++ b/go-controller/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go @@ -768,6 +768,15 @@ func Munmap(b []byte) (err error) { return mapper.Munmap(b) } +func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) { + xaddr, err := mapper.mmap(uintptr(addr), length, prot, flags, fd, offset) + return unsafe.Pointer(xaddr), err +} + +func MunmapPtr(addr unsafe.Pointer, length uintptr) (err error) { + return mapper.munmap(uintptr(addr), length) +} + //sys Gethostname(buf []byte) (err error) = SYS___GETHOSTNAME_A //sysnb Getgid() (gid int) //sysnb Getpid() (pid int) @@ -816,10 +825,10 @@ func Lstat(path string, stat *Stat_t) (err error) { // for checking symlinks begins with $VERSION/ $SYSNAME/ $SYSSYMR/ $SYSSYMA/ func isSpecialPath(path []byte) (v bool) { var special = [4][8]byte{ - [8]byte{'V', 'E', 'R', 'S', 'I', 'O', 'N', '/'}, - [8]byte{'S', 'Y', 'S', 'N', 'A', 'M', 'E', '/'}, - [8]byte{'S', 'Y', 'S', 'S', 'Y', 'M', 'R', '/'}, - [8]byte{'S', 'Y', 'S', 'S', 'Y', 'M', 'A', '/'}} + {'V', 'E', 'R', 'S', 'I', 'O', 'N', '/'}, + {'S', 'Y', 'S', 'N', 'A', 'M', 'E', '/'}, + {'S', 'Y', 'S', 'S', 'Y', 'M', 'R', '/'}, + {'S', 'Y', 'S', 'S', 'Y', 'M', 'A', '/'}} var i, j int for i = 0; i < len(special); i++ { @@ -3115,3 +3124,90 @@ func legacy_Mkfifoat(dirfd int, path string, mode uint32) (err error) { //sys Posix_openpt(oflag int) (fd int, err error) = SYS_POSIX_OPENPT //sys Grantpt(fildes int) (rc int, err error) = SYS_GRANTPT //sys Unlockpt(fildes int) (rc int, err error) = SYS_UNLOCKPT + +func fcntlAsIs(fd uintptr, cmd int, arg uintptr) (val int, err error) { + runtime.EnterSyscall() + r0, e2, e1 := CallLeFuncWithErr(GetZosLibVec()+SYS_FCNTL<<4, uintptr(fd), uintptr(cmd), arg) + runtime.ExitSyscall() + val = int(r0) + if int64(r0) == -1 { + err = errnoErr2(e1, e2) + } + return +} + +func Fcntl(fd uintptr, cmd int, op interface{}) (ret int, err error) { + switch op.(type) { + case *Flock_t: + err = FcntlFlock(fd, cmd, op.(*Flock_t)) + if err != nil { + ret = -1 + } + return + case int: + return FcntlInt(fd, cmd, op.(int)) + case *F_cnvrt: + return fcntlAsIs(fd, cmd, uintptr(unsafe.Pointer(op.(*F_cnvrt)))) + case unsafe.Pointer: + return fcntlAsIs(fd, cmd, uintptr(op.(unsafe.Pointer))) + default: + return -1, EINVAL + } + return +} + +func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { + if raceenabled { + raceReleaseMerge(unsafe.Pointer(&ioSync)) + } + return sendfile(outfd, infd, offset, count) +} + +func sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { + // TODO: use LE call instead if the call is implemented + originalOffset, err := Seek(infd, 0, SEEK_CUR) + if err != nil { + return -1, err + } + //start reading data from in_fd + if offset != nil { + _, err := Seek(infd, *offset, SEEK_SET) + if err != nil { + return -1, err + } + } + + buf := make([]byte, count) + readBuf := make([]byte, 0) + var n int = 0 + for i := 0; i < count; i += n { + n, err := Read(infd, buf) + if n == 0 { + if err != nil { + return -1, err + } else { // EOF + break + } + } + readBuf = append(readBuf, buf...) + buf = buf[0:0] + } + + n2, err := Write(outfd, readBuf) + if err != nil { + return -1, err + } + + //When sendfile() returns, this variable will be set to the + // offset of the byte following the last byte that was read. + if offset != nil { + *offset = *offset + int64(n) + // If offset is not NULL, then sendfile() does not modify the file + // offset of in_fd + _, err := Seek(infd, originalOffset, SEEK_SET) + if err != nil { + return -1, err + } + } + return n2, nil +} diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux.go index de3b462489..4f432bfe8f 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -321,6 +321,9 @@ const ( AUDIT_INTEGRITY_STATUS = 0x70a AUDIT_IPC = 0x517 AUDIT_IPC_SET_PERM = 0x51f + AUDIT_IPE_ACCESS = 0x58c + AUDIT_IPE_CONFIG_CHANGE = 0x58d + AUDIT_IPE_POLICY_LOAD = 0x58e AUDIT_KERNEL = 0x7d0 AUDIT_KERNEL_OTHER = 0x524 AUDIT_KERN_MODULE = 0x532 @@ -489,6 +492,7 @@ const ( BPF_F_ID = 0x20 BPF_F_NETFILTER_IP_DEFRAG = 0x1 BPF_F_QUERY_EFFECTIVE = 0x1 + BPF_F_REDIRECT_FLAGS = 0x19 BPF_F_REPLACE = 0x4 BPF_F_SLEEPABLE = 0x10 BPF_F_STRICT_ALIGNMENT = 0x1 @@ -1166,6 +1170,7 @@ const ( EXTA = 0xe EXTB = 0xf F2FS_SUPER_MAGIC = 0xf2f52010 + FALLOC_FL_ALLOCATE_RANGE = 0x0 FALLOC_FL_COLLAPSE_RANGE = 0x8 FALLOC_FL_INSERT_RANGE = 0x20 FALLOC_FL_KEEP_SIZE = 0x1 @@ -1240,6 +1245,7 @@ const ( FAN_REPORT_DFID_NAME = 0xc00 FAN_REPORT_DFID_NAME_TARGET = 0x1e00 FAN_REPORT_DIR_FID = 0x400 + FAN_REPORT_FD_ERROR = 0x2000 FAN_REPORT_FID = 0x200 FAN_REPORT_NAME = 0x800 FAN_REPORT_PIDFD = 0x80 @@ -1325,8 +1331,10 @@ const ( FUSE_SUPER_MAGIC = 0x65735546 FUTEXFS_SUPER_MAGIC = 0xbad1dea F_ADD_SEALS = 0x409 + F_CREATED_QUERY = 0x404 F_DUPFD = 0x0 F_DUPFD_CLOEXEC = 0x406 + F_DUPFD_QUERY = 0x403 F_EXLCK = 0x4 F_GETFD = 0x1 F_GETFL = 0x3 @@ -1546,6 +1554,7 @@ const ( IPPROTO_ROUTING = 0x2b IPPROTO_RSVP = 0x2e IPPROTO_SCTP = 0x84 + IPPROTO_SMC = 0x100 IPPROTO_TCP = 0x6 IPPROTO_TP = 0x1d IPPROTO_UDP = 0x11 @@ -1618,6 +1627,8 @@ const ( IPV6_UNICAST_IF = 0x4c IPV6_USER_FLOW = 0xe IPV6_V6ONLY = 0x1a + IPV6_VERSION = 0x60 + IPV6_VERSION_MASK = 0xf0 IPV6_XFRM_POLICY = 0x23 IP_ADD_MEMBERSHIP = 0x23 IP_ADD_SOURCE_MEMBERSHIP = 0x27 @@ -1799,6 +1810,8 @@ const ( LANDLOCK_ACCESS_NET_BIND_TCP = 0x1 LANDLOCK_ACCESS_NET_CONNECT_TCP = 0x2 LANDLOCK_CREATE_RULESET_VERSION = 0x1 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = 0x1 + LANDLOCK_SCOPE_SIGNAL = 0x2 LINUX_REBOOT_CMD_CAD_OFF = 0x0 LINUX_REBOOT_CMD_CAD_ON = 0x89abcdef LINUX_REBOOT_CMD_HALT = 0xcdef0123 @@ -1860,6 +1873,7 @@ const ( MADV_UNMERGEABLE = 0xd MADV_WILLNEED = 0x3 MADV_WIPEONFORK = 0x12 + MAP_DROPPABLE = 0x8 MAP_FILE = 0x0 MAP_FIXED = 0x10 MAP_FIXED_NOREPLACE = 0x100000 @@ -1924,6 +1938,7 @@ const ( MNT_FORCE = 0x1 MNT_ID_REQ_SIZE_VER0 = 0x18 MNT_ID_REQ_SIZE_VER1 = 0x20 + MNT_NS_INFO_SIZE_VER0 = 0x10 MODULE_INIT_COMPRESSED_FILE = 0x4 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_VERMAGIC = 0x2 @@ -1959,6 +1974,7 @@ const ( MSG_PEEK = 0x2 MSG_PROXY = 0x10 MSG_RST = 0x1000 + MSG_SOCK_DEVMEM = 0x2000000 MSG_SYN = 0x400 MSG_TRUNC = 0x20 MSG_TRYHARD = 0x4 @@ -2075,6 +2091,7 @@ const ( NFC_ATR_REQ_MAXSIZE = 0x40 NFC_ATR_RES_GB_MAXSIZE = 0x2f NFC_ATR_RES_MAXSIZE = 0x40 + NFC_ATS_MAXSIZE = 0x14 NFC_COMM_ACTIVE = 0x0 NFC_COMM_PASSIVE = 0x1 NFC_DEVICE_NAME_MAXSIZE = 0x8 @@ -2155,6 +2172,7 @@ const ( NFNL_SUBSYS_QUEUE = 0x3 NFNL_SUBSYS_ULOG = 0x4 NFS_SUPER_MAGIC = 0x6969 + NFT_BITWISE_BOOL = 0x0 NFT_CHAIN_FLAGS = 0x7 NFT_CHAIN_MAXNAMELEN = 0x100 NFT_CT_MAX = 0x17 @@ -2483,6 +2501,7 @@ const ( PR_GET_PDEATHSIG = 0x2 PR_GET_SECCOMP = 0x15 PR_GET_SECUREBITS = 0x1b + PR_GET_SHADOW_STACK_STATUS = 0x4a PR_GET_SPECULATION_CTRL = 0x34 PR_GET_TAGGED_ADDR_CTRL = 0x38 PR_GET_THP_DISABLE = 0x2a @@ -2491,6 +2510,7 @@ const ( PR_GET_TIMING = 0xd PR_GET_TSC = 0x19 PR_GET_UNALIGN = 0x5 + PR_LOCK_SHADOW_STACK_STATUS = 0x4c PR_MCE_KILL = 0x21 PR_MCE_KILL_CLEAR = 0x0 PR_MCE_KILL_DEFAULT = 0x2 @@ -2517,6 +2537,8 @@ const ( PR_PAC_GET_ENABLED_KEYS = 0x3d PR_PAC_RESET_KEYS = 0x36 PR_PAC_SET_ENABLED_KEYS = 0x3c + PR_PMLEN_MASK = 0x7f000000 + PR_PMLEN_SHIFT = 0x18 PR_PPC_DEXCR_CTRL_CLEAR = 0x4 PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC = 0x10 PR_PPC_DEXCR_CTRL_EDITABLE = 0x1 @@ -2584,6 +2606,7 @@ const ( PR_SET_PTRACER = 0x59616d61 PR_SET_SECCOMP = 0x16 PR_SET_SECUREBITS = 0x1c + PR_SET_SHADOW_STACK_STATUS = 0x4b PR_SET_SPECULATION_CTRL = 0x35 PR_SET_SYSCALL_USER_DISPATCH = 0x3b PR_SET_TAGGED_ADDR_CTRL = 0x37 @@ -2594,6 +2617,9 @@ const ( PR_SET_UNALIGN = 0x6 PR_SET_VMA = 0x53564d41 PR_SET_VMA_ANON_NAME = 0x0 + PR_SHADOW_STACK_ENABLE = 0x1 + PR_SHADOW_STACK_PUSH = 0x4 + PR_SHADOW_STACK_WRITE = 0x2 PR_SME_GET_VL = 0x40 PR_SME_SET_VL = 0x3f PR_SME_SET_VL_ONEXEC = 0x40000 @@ -2625,6 +2651,28 @@ const ( PR_UNALIGN_NOPRINT = 0x1 PR_UNALIGN_SIGBUS = 0x2 PSTOREFS_MAGIC = 0x6165676c + PTP_CLK_MAGIC = '=' + PTP_ENABLE_FEATURE = 0x1 + PTP_EXTTS_EDGES = 0x6 + PTP_EXTTS_EVENT_VALID = 0x1 + PTP_EXTTS_V1_VALID_FLAGS = 0x7 + PTP_EXTTS_VALID_FLAGS = 0x1f + PTP_EXT_OFFSET = 0x10 + PTP_FALLING_EDGE = 0x4 + PTP_MAX_SAMPLES = 0x19 + PTP_PEROUT_DUTY_CYCLE = 0x2 + PTP_PEROUT_ONE_SHOT = 0x1 + PTP_PEROUT_PHASE = 0x4 + PTP_PEROUT_V1_VALID_FLAGS = 0x0 + PTP_PEROUT_VALID_FLAGS = 0x7 + PTP_PIN_GETFUNC = 0xc0603d06 + PTP_PIN_GETFUNC2 = 0xc0603d0f + PTP_RISING_EDGE = 0x2 + PTP_STRICT_FLAGS = 0x8 + PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09 + PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12 + PTP_SYS_OFFSET_PRECISE = 0xc0403d08 + PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11 PTRACE_ATTACH = 0x10 PTRACE_CONT = 0x7 PTRACE_DETACH = 0x11 @@ -2881,7 +2929,6 @@ const ( RTM_NEWNEXTHOP = 0x68 RTM_NEWNEXTHOPBUCKET = 0x74 RTM_NEWNSID = 0x58 - RTM_NEWNVLAN = 0x70 RTM_NEWPREFIX = 0x34 RTM_NEWQDISC = 0x24 RTM_NEWROUTE = 0x18 @@ -2890,6 +2937,7 @@ const ( RTM_NEWTCLASS = 0x28 RTM_NEWTFILTER = 0x2c RTM_NEWTUNNEL = 0x78 + RTM_NEWVLAN = 0x70 RTM_NR_FAMILIES = 0x1b RTM_NR_MSGTYPES = 0x6c RTM_SETDCB = 0x4f @@ -2948,6 +2996,7 @@ const ( RWF_WRITE_LIFE_NOT_SET = 0x0 SCHED_BATCH = 0x3 SCHED_DEADLINE = 0x6 + SCHED_EXT = 0x7 SCHED_FIFO = 0x1 SCHED_FLAG_ALL = 0x7f SCHED_FLAG_DL_OVERRUN = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 8aa6d77c01..75207613c7 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -109,12 +109,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -237,6 +240,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_GETFPREGS = 0xe PTRACE_GETFPXREGS = 0x12 PTRACE_GET_THREAD_AREA = 0x19 @@ -283,10 +300,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -321,6 +341,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index da428f4253..c68acda535 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -109,12 +109,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -237,6 +240,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_ARCH_PRCTL = 0x1e PTRACE_GETFPREGS = 0xe PTRACE_GETFPXREGS = 0x12 @@ -284,10 +301,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -322,6 +342,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index bf45bfec78..a8c607ab86 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_GETCRUNCHREGS = 0x19 PTRACE_GETFDPIC = 0x1f PTRACE_GETFDPIC_EXEC = 0x0 @@ -289,10 +306,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -327,6 +347,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 71c67162b7..18563dd8d3 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -109,15 +109,19 @@ const ( F_SETOWN = 0x8 F_UNLCK = 0x2 F_WRLCK = 0x1 + GCS_MAGIC = 0x47435300 HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -205,6 +209,7 @@ const ( PERF_EVENT_IOC_SET_BPF = 0x40042408 PERF_EVENT_IOC_SET_FILTER = 0x40082406 PERF_EVENT_IOC_SET_OUTPUT = 0x2405 + POE_MAGIC = 0x504f4530 PPPIOCATTACH = 0x4004743d PPPIOCATTCHAN = 0x40047438 PPPIOCBRIDGECHAN = 0x40047435 @@ -240,6 +245,20 @@ const ( PROT_BTI = 0x10 PROT_MTE = 0x20 PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_PEEKMTETAGS = 0x21 PTRACE_POKEMTETAGS = 0x22 PTRACE_SYSEMU = 0x1f @@ -280,10 +299,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -318,6 +340,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 9476628fa0..22912cdaa9 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -109,12 +109,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -238,6 +241,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_SYSEMU = 0x1f PTRACE_SYSEMU_SINGLESTEP = 0x20 RLIMIT_AS = 0x9 @@ -276,10 +293,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -314,6 +334,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index b9e85f3cf0..29344eb37a 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x20007434 PPPIOCXFERUNIT = 0x2000744e PR_SET_PTRACER_ANY = 0xffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETFPREGS = 0xe PTRACE_GET_THREAD_AREA = 0x19 PTRACE_GET_THREAD_AREA_3264 = 0xc4 @@ -282,10 +299,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -320,6 +340,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x1029 SO_DONTROUTE = 0x10 SO_ERROR = 0x1007 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index a48b68a764..20d51fb96a 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x20007434 PPPIOCXFERUNIT = 0x2000744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETFPREGS = 0xe PTRACE_GET_THREAD_AREA = 0x19 PTRACE_GET_THREAD_AREA_3264 = 0xc4 @@ -282,10 +299,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -320,6 +340,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x1029 SO_DONTROUTE = 0x10 SO_ERROR = 0x1007 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index ea00e8522a..321b60902a 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x20007434 PPPIOCXFERUNIT = 0x2000744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETFPREGS = 0xe PTRACE_GET_THREAD_AREA = 0x19 PTRACE_GET_THREAD_AREA_3264 = 0xc4 @@ -282,10 +299,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -320,6 +340,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x1029 SO_DONTROUTE = 0x10 SO_ERROR = 0x1007 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 91c6468717..9bacdf1e27 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x20007434 PPPIOCXFERUNIT = 0x2000744e PR_SET_PTRACER_ANY = 0xffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETFPREGS = 0xe PTRACE_GET_THREAD_AREA = 0x19 PTRACE_GET_THREAD_AREA_3264 = 0xc4 @@ -282,10 +299,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -320,6 +340,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x1029 SO_DONTROUTE = 0x10 SO_ERROR = 0x1007 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 8cbf38d639..c224272615 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x4000 ICANON = 0x100 IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -237,6 +240,20 @@ const ( PPPIOCXFERUNIT = 0x2000744e PROT_SAO = 0x10 PR_SET_PTRACER_ANY = 0xffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETEVRREGS = 0x14 PTRACE_GETFPREGS = 0xe PTRACE_GETREGS64 = 0x16 @@ -337,10 +354,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -375,6 +395,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index a2df734191..6270c8ee13 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x4000 ICANON = 0x100 IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -237,6 +240,20 @@ const ( PPPIOCXFERUNIT = 0x2000744e PROT_SAO = 0x10 PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETEVRREGS = 0x14 PTRACE_GETFPREGS = 0xe PTRACE_GETREGS64 = 0x16 @@ -341,10 +358,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -379,6 +399,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index 2479137923..9966c1941f 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x4000 ICANON = 0x100 IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -237,6 +240,20 @@ const ( PPPIOCXFERUNIT = 0x2000744e PROT_SAO = 0x10 PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETEVRREGS = 0x14 PTRACE_GETFPREGS = 0xe PTRACE_GETREGS64 = 0x16 @@ -341,10 +358,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -379,6 +399,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index d265f146ee..848e5fcc42 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_GETFDPIC = 0x21 PTRACE_GETFDPIC_EXEC = 0x0 PTRACE_GETFDPIC_INTERP = 0x1 @@ -273,10 +290,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -311,6 +331,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 3f2d644396..669b2adb80 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -108,12 +108,15 @@ const ( HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 + HIDIOCREVOKE = 0x4004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -234,6 +237,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x7434 PPPIOCXFERUNIT = 0x744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x80503d01 + PTP_CLOCK_GETCAPS2 = 0x80503d0a + PTP_ENABLE_PPS = 0x40043d04 + PTP_ENABLE_PPS2 = 0x40043d0d + PTP_EXTTS_REQUEST = 0x40103d02 + PTP_EXTTS_REQUEST2 = 0x40103d0b + PTP_MASK_CLEAR_ALL = 0x3d13 + PTP_MASK_EN_SINGLE = 0x40043d14 + PTP_PEROUT_REQUEST = 0x40383d03 + PTP_PEROUT_REQUEST2 = 0x40383d0c + PTP_PIN_SETFUNC = 0x40603d07 + PTP_PIN_SETFUNC2 = 0x40603d10 + PTP_SYS_OFFSET = 0x43403d05 + PTP_SYS_OFFSET2 = 0x43403d0e PTRACE_DISABLE_TE = 0x5010 PTRACE_ENABLE_TE = 0x5009 PTRACE_GET_LAST_BREAK = 0x5006 @@ -345,10 +362,13 @@ const ( RTC_WIE_ON = 0x700f RTC_WKALM_RD = 0x80287010 RTC_WKALM_SET = 0x4028700f + SCM_DEVMEM_DMABUF = 0x4f + SCM_DEVMEM_LINEAR = 0x4e SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 @@ -383,6 +403,9 @@ const ( SO_CNX_ADVICE = 0x35 SO_COOKIE = 0x39 SO_DETACH_REUSEPORT_BPF = 0x44 + SO_DEVMEM_DMABUF = 0x4f + SO_DEVMEM_DONTNEED = 0x50 + SO_DEVMEM_LINEAR = 0x4e SO_DOMAIN = 0x27 SO_DONTROUTE = 0x5 SO_ERROR = 0x4 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 5d8b727a1c..4834e57514 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -112,12 +112,15 @@ const ( HIDIOCGRAWINFO = 0x40084803 HIDIOCGRDESC = 0x50044802 HIDIOCGRDESCSIZE = 0x40044801 + HIDIOCREVOKE = 0x8004480d HUPCL = 0x400 ICANON = 0x2 IEXTEN = 0x8000 IN_CLOEXEC = 0x400000 IN_NONBLOCK = 0x4000 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -239,6 +242,20 @@ const ( PPPIOCUNBRIDGECHAN = 0x20007434 PPPIOCXFERUNIT = 0x2000744e PR_SET_PTRACER_ANY = 0xffffffffffffffff + PTP_CLOCK_GETCAPS = 0x40503d01 + PTP_CLOCK_GETCAPS2 = 0x40503d0a + PTP_ENABLE_PPS = 0x80043d04 + PTP_ENABLE_PPS2 = 0x80043d0d + PTP_EXTTS_REQUEST = 0x80103d02 + PTP_EXTTS_REQUEST2 = 0x80103d0b + PTP_MASK_CLEAR_ALL = 0x20003d13 + PTP_MASK_EN_SINGLE = 0x80043d14 + PTP_PEROUT_REQUEST = 0x80383d03 + PTP_PEROUT_REQUEST2 = 0x80383d0c + PTP_PIN_SETFUNC = 0x80603d07 + PTP_PIN_SETFUNC2 = 0x80603d10 + PTP_SYS_OFFSET = 0x83403d05 + PTP_SYS_OFFSET2 = 0x83403d0e PTRACE_GETFPAREGS = 0x14 PTRACE_GETFPREGS = 0xe PTRACE_GETFPREGS64 = 0x19 @@ -336,10 +353,13 @@ const ( RTC_WIE_ON = 0x2000700f RTC_WKALM_RD = 0x40287010 RTC_WKALM_SET = 0x8028700f + SCM_DEVMEM_DMABUF = 0x58 + SCM_DEVMEM_LINEAR = 0x57 SCM_TIMESTAMPING = 0x23 SCM_TIMESTAMPING_OPT_STATS = 0x38 SCM_TIMESTAMPING_PKTINFO = 0x3c SCM_TIMESTAMPNS = 0x21 + SCM_TS_OPT_ID = 0x5a SCM_TXTIME = 0x3f SCM_WIFI_STATUS = 0x25 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 @@ -422,6 +442,9 @@ const ( SO_CNX_ADVICE = 0x37 SO_COOKIE = 0x3b SO_DETACH_REUSEPORT_BPF = 0x47 + SO_DEVMEM_DMABUF = 0x58 + SO_DEVMEM_DONTNEED = 0x59 + SO_DEVMEM_LINEAR = 0x57 SO_DOMAIN = 0x1029 SO_DONTROUTE = 0x10 SO_ERROR = 0x1007 diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/go-controller/vendor/golang.org/x/sys/unix/zsyscall_linux.go index af30da5578..5cc1e8eb2f 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -592,6 +592,16 @@ func ClockGettime(clockid int32, time *Timespec) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func ClockSettime(clockid int32, time *Timespec) (err error) { + _, _, e1 := Syscall(SYS_CLOCK_SETTIME, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func ClockNanosleep(clockid int32, flags int, request *Timespec, remain *Timespec) (err error) { _, _, e1 := Syscall6(SYS_CLOCK_NANOSLEEP, uintptr(clockid), uintptr(flags), uintptr(unsafe.Pointer(request)), uintptr(unsafe.Pointer(remain)), 0, 0) if e1 != 0 { diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go b/go-controller/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go index 829b87feb8..c6545413c4 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go @@ -141,6 +141,16 @@ import ( //go:cgo_import_dynamic libc_getpeername getpeername "libsocket.so" //go:cgo_import_dynamic libc_setsockopt setsockopt "libsocket.so" //go:cgo_import_dynamic libc_recvfrom recvfrom "libsocket.so" +//go:cgo_import_dynamic libc_getpeerucred getpeerucred "libc.so" +//go:cgo_import_dynamic libc_ucred_get ucred_get "libc.so" +//go:cgo_import_dynamic libc_ucred_geteuid ucred_geteuid "libc.so" +//go:cgo_import_dynamic libc_ucred_getegid ucred_getegid "libc.so" +//go:cgo_import_dynamic libc_ucred_getruid ucred_getruid "libc.so" +//go:cgo_import_dynamic libc_ucred_getrgid ucred_getrgid "libc.so" +//go:cgo_import_dynamic libc_ucred_getsuid ucred_getsuid "libc.so" +//go:cgo_import_dynamic libc_ucred_getsgid ucred_getsgid "libc.so" +//go:cgo_import_dynamic libc_ucred_getpid ucred_getpid "libc.so" +//go:cgo_import_dynamic libc_ucred_free ucred_free "libc.so" //go:cgo_import_dynamic libc_port_create port_create "libc.so" //go:cgo_import_dynamic libc_port_associate port_associate "libc.so" //go:cgo_import_dynamic libc_port_dissociate port_dissociate "libc.so" @@ -280,6 +290,16 @@ import ( //go:linkname procgetpeername libc_getpeername //go:linkname procsetsockopt libc_setsockopt //go:linkname procrecvfrom libc_recvfrom +//go:linkname procgetpeerucred libc_getpeerucred +//go:linkname procucred_get libc_ucred_get +//go:linkname procucred_geteuid libc_ucred_geteuid +//go:linkname procucred_getegid libc_ucred_getegid +//go:linkname procucred_getruid libc_ucred_getruid +//go:linkname procucred_getrgid libc_ucred_getrgid +//go:linkname procucred_getsuid libc_ucred_getsuid +//go:linkname procucred_getsgid libc_ucred_getsgid +//go:linkname procucred_getpid libc_ucred_getpid +//go:linkname procucred_free libc_ucred_free //go:linkname procport_create libc_port_create //go:linkname procport_associate libc_port_associate //go:linkname procport_dissociate libc_port_dissociate @@ -420,6 +440,16 @@ var ( procgetpeername, procsetsockopt, procrecvfrom, + procgetpeerucred, + procucred_get, + procucred_geteuid, + procucred_getegid, + procucred_getruid, + procucred_getrgid, + procucred_getsuid, + procucred_getsgid, + procucred_getpid, + procucred_free, procport_create, procport_associate, procport_dissociate, @@ -2029,6 +2059,90 @@ func recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Sockl // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getpeerucred(fd uintptr, ucred *uintptr) (err error) { + _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgetpeerucred)), 2, uintptr(fd), uintptr(unsafe.Pointer(ucred)), 0, 0, 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGet(pid int) (ucred uintptr, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procucred_get)), 1, uintptr(pid), 0, 0, 0, 0, 0) + ucred = uintptr(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGeteuid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_geteuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetegid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getegid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetruid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getruid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetrgid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getrgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetsuid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetsgid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetpid(ucred uintptr) (pid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getpid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + pid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredFree(ucred uintptr) { + sysvicall6(uintptr(unsafe.Pointer(&procucred_free)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func port_create() (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_create)), 0, 0, 0, 0, 0, 0, 0) n = int(r0) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index 524b0820cb..c79aaff306 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -458,4 +458,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index f485dbf456..5eb450695e 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -381,4 +381,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index 70b35bf3b0..05e5029744 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -422,4 +422,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 1893e2fe88..38c53ec51b 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -325,4 +325,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 16a4017da0..31d2e71a18 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -321,4 +321,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index 7e567f1eff..f4184a336b 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -442,4 +442,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 SYS_MSEAL = 4462 + SYS_SETXATTRAT = 4463 + SYS_GETXATTRAT = 4464 + SYS_LISTXATTRAT = 4465 + SYS_REMOVEXATTRAT = 4466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index 38ae55e5ef..05b9962278 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -372,4 +372,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 SYS_MSEAL = 5462 + SYS_SETXATTRAT = 5463 + SYS_GETXATTRAT = 5464 + SYS_LISTXATTRAT = 5465 + SYS_REMOVEXATTRAT = 5466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index 55e92e60a8..43a256e9e6 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -372,4 +372,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 SYS_MSEAL = 5462 + SYS_SETXATTRAT = 5463 + SYS_GETXATTRAT = 5464 + SYS_LISTXATTRAT = 5465 + SYS_REMOVEXATTRAT = 5466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index 60658d6a02..eea5ddfc22 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -442,4 +442,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 SYS_MSEAL = 4462 + SYS_SETXATTRAT = 4463 + SYS_GETXATTRAT = 4464 + SYS_LISTXATTRAT = 4465 + SYS_REMOVEXATTRAT = 4466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index e203e8a7ed..0d777bfbb1 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -449,4 +449,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index 5944b97d54..b446365025 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -421,4 +421,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index c66d416dad..0c7d21c188 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -421,4 +421,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index a5459e766f..8405391698 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -326,4 +326,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index 01d86825bb..fcf1b790d6 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -387,4 +387,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index 7b703e77cd..52d15b5f9d 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -400,4 +400,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go b/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go index d003c3d437..17c53bd9b3 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go @@ -462,11 +462,14 @@ type FdSet struct { const ( SizeofIfMsghdr = 0x70 + SizeofIfMsghdr2 = 0xa0 SizeofIfData = 0x60 + SizeofIfData64 = 0x80 SizeofIfaMsghdr = 0x14 SizeofIfmaMsghdr = 0x10 SizeofIfmaMsghdr2 = 0x14 SizeofRtMsghdr = 0x5c + SizeofRtMsghdr2 = 0x5c SizeofRtMetrics = 0x38 ) @@ -480,6 +483,20 @@ type IfMsghdr struct { Data IfData } +type IfMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Addrs int32 + Flags int32 + Index uint16 + Snd_len int32 + Snd_maxlen int32 + Snd_drops int32 + Timer int32 + Data IfData64 +} + type IfData struct { Type uint8 Typelen uint8 @@ -512,6 +529,34 @@ type IfData struct { Reserved2 uint32 } +type IfData64 struct { + Type uint8 + Typelen uint8 + Physical uint8 + Addrlen uint8 + Hdrlen uint8 + Recvquota uint8 + Xmitquota uint8 + Unused1 uint8 + Mtu uint32 + Metric uint32 + Baudrate uint64 + Ipackets uint64 + Ierrors uint64 + Opackets uint64 + Oerrors uint64 + Collisions uint64 + Ibytes uint64 + Obytes uint64 + Imcasts uint64 + Omcasts uint64 + Iqdrops uint64 + Noproto uint64 + Recvtiming uint32 + Xmittiming uint32 + Lastchange Timeval32 +} + type IfaMsghdr struct { Msglen uint16 Version uint8 @@ -557,6 +602,21 @@ type RtMsghdr struct { Rmx RtMetrics } +type RtMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Index uint16 + Flags int32 + Addrs int32 + Refcnt int32 + Parentflags int32 + Reserved int32 + Use int32 + Inits uint32 + Rmx RtMetrics +} + type RtMetrics struct { Locks uint32 Mtu uint32 diff --git a/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go b/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go index 0d45a941aa..2392226a74 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go +++ b/go-controller/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go @@ -462,11 +462,14 @@ type FdSet struct { const ( SizeofIfMsghdr = 0x70 + SizeofIfMsghdr2 = 0xa0 SizeofIfData = 0x60 + SizeofIfData64 = 0x80 SizeofIfaMsghdr = 0x14 SizeofIfmaMsghdr = 0x10 SizeofIfmaMsghdr2 = 0x14 SizeofRtMsghdr = 0x5c + SizeofRtMsghdr2 = 0x5c SizeofRtMetrics = 0x38 ) @@ -480,6 +483,20 @@ type IfMsghdr struct { Data IfData } +type IfMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Addrs int32 + Flags int32 + Index uint16 + Snd_len int32 + Snd_maxlen int32 + Snd_drops int32 + Timer int32 + Data IfData64 +} + type IfData struct { Type uint8 Typelen uint8 @@ -512,6 +529,34 @@ type IfData struct { Reserved2 uint32 } +type IfData64 struct { + Type uint8 + Typelen uint8 + Physical uint8 + Addrlen uint8 + Hdrlen uint8 + Recvquota uint8 + Xmitquota uint8 + Unused1 uint8 + Mtu uint32 + Metric uint32 + Baudrate uint64 + Ipackets uint64 + Ierrors uint64 + Opackets uint64 + Oerrors uint64 + Collisions uint64 + Ibytes uint64 + Obytes uint64 + Imcasts uint64 + Omcasts uint64 + Iqdrops uint64 + Noproto uint64 + Recvtiming uint32 + Xmittiming uint32 + Lastchange Timeval32 +} + type IfaMsghdr struct { Msglen uint16 Version uint8 @@ -557,6 +602,21 @@ type RtMsghdr struct { Rmx RtMetrics } +type RtMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Index uint16 + Flags int32 + Addrs int32 + Refcnt int32 + Parentflags int32 + Reserved int32 + Use int32 + Inits uint32 + Rmx RtMetrics +} + type RtMetrics struct { Locks uint32 Mtu uint32 diff --git a/go-controller/vendor/golang.org/x/sys/unix/ztypes_linux.go b/go-controller/vendor/golang.org/x/sys/unix/ztypes_linux.go index 3a69e45496..a46abe6472 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/go-controller/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -1752,12 +1752,6 @@ const ( IFLA_IPVLAN_UNSPEC = 0x0 IFLA_IPVLAN_MODE = 0x1 IFLA_IPVLAN_FLAGS = 0x2 - NETKIT_NEXT = -0x1 - NETKIT_PASS = 0x0 - NETKIT_DROP = 0x2 - NETKIT_REDIRECT = 0x7 - NETKIT_L2 = 0x0 - NETKIT_L3 = 0x1 IFLA_NETKIT_UNSPEC = 0x0 IFLA_NETKIT_PEER_INFO = 0x1 IFLA_NETKIT_PRIMARY = 0x2 @@ -1796,6 +1790,7 @@ const ( IFLA_VXLAN_DF = 0x1d IFLA_VXLAN_VNIFILTER = 0x1e IFLA_VXLAN_LOCALBYPASS = 0x1f + IFLA_VXLAN_LABEL_POLICY = 0x20 IFLA_GENEVE_UNSPEC = 0x0 IFLA_GENEVE_ID = 0x1 IFLA_GENEVE_REMOTE = 0x2 @@ -1825,6 +1820,8 @@ const ( IFLA_GTP_ROLE = 0x4 IFLA_GTP_CREATE_SOCKETS = 0x5 IFLA_GTP_RESTART_COUNT = 0x6 + IFLA_GTP_LOCAL = 0x7 + IFLA_GTP_LOCAL6 = 0x8 IFLA_BOND_UNSPEC = 0x0 IFLA_BOND_MODE = 0x1 IFLA_BOND_ACTIVE_SLAVE = 0x2 @@ -1857,6 +1854,7 @@ const ( IFLA_BOND_AD_LACP_ACTIVE = 0x1d IFLA_BOND_MISSED_MAX = 0x1e IFLA_BOND_NS_IP6_TARGET = 0x1f + IFLA_BOND_COUPLED_CONTROL = 0x20 IFLA_BOND_AD_INFO_UNSPEC = 0x0 IFLA_BOND_AD_INFO_AGGREGATOR = 0x1 IFLA_BOND_AD_INFO_NUM_PORTS = 0x2 @@ -1925,6 +1923,7 @@ const ( IFLA_HSR_SEQ_NR = 0x5 IFLA_HSR_VERSION = 0x6 IFLA_HSR_PROTOCOL = 0x7 + IFLA_HSR_INTERLINK = 0x8 IFLA_STATS_UNSPEC = 0x0 IFLA_STATS_LINK_64 = 0x1 IFLA_STATS_LINK_XSTATS = 0x2 @@ -1977,6 +1976,15 @@ const ( IFLA_DSA_MASTER = 0x1 ) +const ( + NETKIT_NEXT = -0x1 + NETKIT_PASS = 0x0 + NETKIT_DROP = 0x2 + NETKIT_REDIRECT = 0x7 + NETKIT_L2 = 0x0 + NETKIT_L3 = 0x1 +) + const ( NF_INET_PRE_ROUTING = 0x0 NF_INET_LOCAL_IN = 0x1 @@ -2586,8 +2594,8 @@ const ( SOF_TIMESTAMPING_BIND_PHC = 0x8000 SOF_TIMESTAMPING_OPT_ID_TCP = 0x10000 - SOF_TIMESTAMPING_LAST = 0x10000 - SOF_TIMESTAMPING_MASK = 0x1ffff + SOF_TIMESTAMPING_LAST = 0x20000 + SOF_TIMESTAMPING_MASK = 0x3ffff SCM_TSTAMP_SND = 0x0 SCM_TSTAMP_SCHED = 0x1 @@ -3533,7 +3541,7 @@ type Nhmsg struct { type NexthopGrp struct { Id uint32 Weight uint8 - Resvd1 uint8 + High uint8 Resvd2 uint16 } @@ -3794,7 +3802,7 @@ const ( ETHTOOL_MSG_PSE_GET = 0x24 ETHTOOL_MSG_PSE_SET = 0x25 ETHTOOL_MSG_RSS_GET = 0x26 - ETHTOOL_MSG_USER_MAX = 0x2c + ETHTOOL_MSG_USER_MAX = 0x2d ETHTOOL_MSG_KERNEL_NONE = 0x0 ETHTOOL_MSG_STRSET_GET_REPLY = 0x1 ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2 @@ -3834,7 +3842,7 @@ const ( ETHTOOL_MSG_MODULE_NTF = 0x24 ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 - ETHTOOL_MSG_KERNEL_MAX = 0x2c + ETHTOOL_MSG_KERNEL_MAX = 0x2e ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 ETHTOOL_FLAG_OMIT_REPLY = 0x2 ETHTOOL_FLAG_STATS = 0x4 @@ -3842,7 +3850,7 @@ const ( ETHTOOL_A_HEADER_DEV_INDEX = 0x1 ETHTOOL_A_HEADER_DEV_NAME = 0x2 ETHTOOL_A_HEADER_FLAGS = 0x3 - ETHTOOL_A_HEADER_MAX = 0x3 + ETHTOOL_A_HEADER_MAX = 0x4 ETHTOOL_A_BITSET_BIT_UNSPEC = 0x0 ETHTOOL_A_BITSET_BIT_INDEX = 0x1 ETHTOOL_A_BITSET_BIT_NAME = 0x2 @@ -4023,11 +4031,11 @@ const ( ETHTOOL_A_CABLE_RESULT_UNSPEC = 0x0 ETHTOOL_A_CABLE_RESULT_PAIR = 0x1 ETHTOOL_A_CABLE_RESULT_CODE = 0x2 - ETHTOOL_A_CABLE_RESULT_MAX = 0x2 + ETHTOOL_A_CABLE_RESULT_MAX = 0x3 ETHTOOL_A_CABLE_FAULT_LENGTH_UNSPEC = 0x0 ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR = 0x1 ETHTOOL_A_CABLE_FAULT_LENGTH_CM = 0x2 - ETHTOOL_A_CABLE_FAULT_LENGTH_MAX = 0x2 + ETHTOOL_A_CABLE_FAULT_LENGTH_MAX = 0x3 ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC = 0x0 ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED = 0x1 ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED = 0x2 @@ -4110,6 +4118,107 @@ type EthtoolDrvinfo struct { Regdump_len uint32 } +type EthtoolTsInfo struct { + Cmd uint32 + So_timestamping uint32 + Phc_index int32 + Tx_types uint32 + Tx_reserved [3]uint32 + Rx_filters uint32 + Rx_reserved [3]uint32 +} + +type HwTstampConfig struct { + Flags int32 + Tx_type int32 + Rx_filter int32 +} + +const ( + HWTSTAMP_FILTER_NONE = 0x0 + HWTSTAMP_FILTER_ALL = 0x1 + HWTSTAMP_FILTER_SOME = 0x2 + HWTSTAMP_FILTER_PTP_V1_L4_EVENT = 0x3 + HWTSTAMP_FILTER_PTP_V2_L4_EVENT = 0x6 + HWTSTAMP_FILTER_PTP_V2_L2_EVENT = 0x9 + HWTSTAMP_FILTER_PTP_V2_EVENT = 0xc +) + +const ( + HWTSTAMP_TX_OFF = 0x0 + HWTSTAMP_TX_ON = 0x1 + HWTSTAMP_TX_ONESTEP_SYNC = 0x2 +) + +type ( + PtpClockCaps struct { + Max_adj int32 + N_alarm int32 + N_ext_ts int32 + N_per_out int32 + Pps int32 + N_pins int32 + Cross_timestamping int32 + Adjust_phase int32 + Max_phase_adj int32 + Rsv [11]int32 + } + PtpClockTime struct { + Sec int64 + Nsec uint32 + Reserved uint32 + } + PtpExttsEvent struct { + T PtpClockTime + Index uint32 + Flags uint32 + Rsv [2]uint32 + } + PtpExttsRequest struct { + Index uint32 + Flags uint32 + Rsv [2]uint32 + } + PtpPeroutRequest struct { + StartOrPhase PtpClockTime + Period PtpClockTime + Index uint32 + Flags uint32 + On PtpClockTime + } + PtpPinDesc struct { + Name [64]byte + Index uint32 + Func uint32 + Chan uint32 + Rsv [5]uint32 + } + PtpSysOffset struct { + Samples uint32 + Rsv [3]uint32 + Ts [51]PtpClockTime + } + PtpSysOffsetExtended struct { + Samples uint32 + Clockid int32 + Rsv [2]uint32 + Ts [25][3]PtpClockTime + } + PtpSysOffsetPrecise struct { + Device PtpClockTime + Realtime PtpClockTime + Monoraw PtpClockTime + Rsv [4]uint32 + } +) + +const ( + PTP_PF_NONE = 0x0 + PTP_PF_EXTTS = 0x1 + PTP_PF_PEROUT = 0x2 + PTP_PF_PHYSYNC = 0x3 +) + type ( HIDRawReportDescriptor struct { Size uint32 @@ -4291,6 +4400,7 @@ const ( type LandlockRulesetAttr struct { Access_fs uint64 Access_net uint64 + Scoped uint64 } type LandlockPathBeneathAttr struct { @@ -4637,7 +4747,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14c + NL80211_ATTR_MAX = 0x14d NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -5409,7 +5519,7 @@ const ( NL80211_MNTR_FLAG_CONTROL = 0x3 NL80211_MNTR_FLAG_COOK_FRAMES = 0x5 NL80211_MNTR_FLAG_FCSFAIL = 0x1 - NL80211_MNTR_FLAG_MAX = 0x6 + NL80211_MNTR_FLAG_MAX = 0x7 NL80211_MNTR_FLAG_OTHER_BSS = 0x4 NL80211_MNTR_FLAG_PLCPFAIL = 0x2 NL80211_MPATH_FLAG_ACTIVE = 0x1 @@ -6064,3 +6174,5 @@ type SockDiagReq struct { Family uint8 Protocol uint8 } + +const RTM_NEWNVLAN = 0x70 diff --git a/go-controller/vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go b/go-controller/vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go index d9a13af468..2e5d5a4435 100644 --- a/go-controller/vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go +++ b/go-controller/vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go @@ -377,6 +377,12 @@ type Flock_t struct { Pid int32 } +type F_cnvrt struct { + Cvtcmd int32 + Pccsid int16 + Fccsid int16 +} + type Termios struct { Cflag uint32 Iflag uint32 diff --git a/go-controller/vendor/golang.org/x/sys/windows/dll_windows.go b/go-controller/vendor/golang.org/x/sys/windows/dll_windows.go index 4e613cf633..3ca814f54d 100644 --- a/go-controller/vendor/golang.org/x/sys/windows/dll_windows.go +++ b/go-controller/vendor/golang.org/x/sys/windows/dll_windows.go @@ -43,8 +43,8 @@ type DLL struct { // LoadDLL loads DLL file into memory. // // Warning: using LoadDLL without an absolute path name is subject to -// DLL preloading attacks. To safely load a system DLL, use LazyDLL -// with System set to true, or use LoadLibraryEx directly. +// DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL], +// or use [LoadLibraryEx] directly. func LoadDLL(name string) (dll *DLL, err error) { namep, err := UTF16PtrFromString(name) if err != nil { @@ -271,6 +271,9 @@ func (d *LazyDLL) NewProc(name string) *LazyProc { } // NewLazyDLL creates new LazyDLL associated with DLL file. +// +// Warning: using NewLazyDLL without an absolute path name is subject to +// DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL]. func NewLazyDLL(name string) *LazyDLL { return &LazyDLL{Name: name} } @@ -410,7 +413,3 @@ func loadLibraryEx(name string, system bool) (*DLL, error) { } return &DLL{Name: name, Handle: h}, nil } - -type errString string - -func (s errString) Error() string { return string(s) } diff --git a/go-controller/vendor/golang.org/x/sys/windows/syscall_windows.go b/go-controller/vendor/golang.org/x/sys/windows/syscall_windows.go index 5cee9a3143..4a32543868 100644 --- a/go-controller/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/go-controller/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -168,6 +168,8 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW //sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) //sys DisconnectNamedPipe(pipe Handle) (err error) +//sys GetNamedPipeClientProcessId(pipe Handle, clientProcessID *uint32) (err error) +//sys GetNamedPipeServerProcessId(pipe Handle, serverProcessID *uint32) (err error) //sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) //sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState @@ -725,20 +727,12 @@ func DurationSinceBoot() time.Duration { } func Ftruncate(fd Handle, length int64) (err error) { - curoffset, e := Seek(fd, 0, 1) - if e != nil { - return e - } - defer Seek(fd, curoffset, 0) - _, e = Seek(fd, length, 0) - if e != nil { - return e + type _FILE_END_OF_FILE_INFO struct { + EndOfFile int64 } - e = SetEndOfFile(fd) - if e != nil { - return e - } - return nil + var info _FILE_END_OF_FILE_INFO + info.EndOfFile = length + return SetFileInformationByHandle(fd, FileEndOfFileInfo, (*byte)(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info))) } func Gettimeofday(tv *Timeval) (err error) { @@ -894,6 +888,11 @@ const socket_error = uintptr(^uint32(0)) //sys GetACP() (acp uint32) = kernel32.GetACP //sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar //sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx +//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex +//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry +//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange +//sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange +//sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2 // For testing: clients can set this flag to force // creation of IPv6 sockets to return EAFNOSUPPORT. @@ -1685,13 +1684,16 @@ func (s NTStatus) Error() string { // do not use NTUnicodeString, and instead UTF16PtrFromString should be used for // the more common *uint16 string type. func NewNTUnicodeString(s string) (*NTUnicodeString, error) { - var u NTUnicodeString - s16, err := UTF16PtrFromString(s) + s16, err := UTF16FromString(s) if err != nil { return nil, err } - RtlInitUnicodeString(&u, s16) - return &u, nil + n := uint16(len(s16) * 2) + return &NTUnicodeString{ + Length: n - 2, // subtract 2 bytes for the NULL terminator + MaximumLength: n, + Buffer: &s16[0], + }, nil } // Slice returns a uint16 slice that aliases the data in the NTUnicodeString. diff --git a/go-controller/vendor/golang.org/x/sys/windows/types_windows.go b/go-controller/vendor/golang.org/x/sys/windows/types_windows.go index 7b97a154c9..9d138de5fe 100644 --- a/go-controller/vendor/golang.org/x/sys/windows/types_windows.go +++ b/go-controller/vendor/golang.org/x/sys/windows/types_windows.go @@ -176,6 +176,7 @@ const ( WAIT_FAILED = 0xFFFFFFFF // Access rights for process. + PROCESS_ALL_ACCESS = 0xFFFF PROCESS_CREATE_PROCESS = 0x0080 PROCESS_CREATE_THREAD = 0x0002 PROCESS_DUP_HANDLE = 0x0040 @@ -2203,6 +2204,132 @@ const ( IfOperStatusLowerLayerDown = 7 ) +const ( + IF_MAX_PHYS_ADDRESS_LENGTH = 32 + IF_MAX_STRING_SIZE = 256 +) + +// MIB_IF_ENTRY_LEVEL enumeration from netioapi.h or +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex. +const ( + MibIfEntryNormal = 0 + MibIfEntryNormalWithoutStatistics = 2 +) + +// MIB_NOTIFICATION_TYPE enumeration from netioapi.h or +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ne-netioapi-mib_notification_type. +const ( + MibParameterNotification = 0 + MibAddInstance = 1 + MibDeleteInstance = 2 + MibInitialNotification = 3 +) + +// MibIfRow2 stores information about a particular interface. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_row2. +type MibIfRow2 struct { + InterfaceLuid uint64 + InterfaceIndex uint32 + InterfaceGuid GUID + Alias [IF_MAX_STRING_SIZE + 1]uint16 + Description [IF_MAX_STRING_SIZE + 1]uint16 + PhysicalAddressLength uint32 + PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + Mtu uint32 + Type uint32 + TunnelType uint32 + MediaType uint32 + PhysicalMediumType uint32 + AccessType uint32 + DirectionType uint32 + InterfaceAndOperStatusFlags uint8 + OperStatus uint32 + AdminStatus uint32 + MediaConnectState uint32 + NetworkGuid GUID + ConnectionType uint32 + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + +// MIB_UNICASTIPADDRESS_ROW stores information about a unicast IP address. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_row. +type MibUnicastIpAddressRow struct { + Address RawSockaddrInet6 // SOCKADDR_INET union + InterfaceLuid uint64 + InterfaceIndex uint32 + PrefixOrigin uint32 + SuffixOrigin uint32 + ValidLifetime uint32 + PreferredLifetime uint32 + OnLinkPrefixLength uint8 + SkipAsSource uint8 + DadState uint32 + ScopeId uint32 + CreationTimeStamp Filetime +} + +const ScopeLevelCount = 16 + +// MIB_IPINTERFACE_ROW stores interface management information for a particular IP address family on a network interface. +// See https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_row. +type MibIpInterfaceRow struct { + Family uint16 + InterfaceLuid uint64 + InterfaceIndex uint32 + MaxReassemblySize uint32 + InterfaceIdentifier uint64 + MinRouterAdvertisementInterval uint32 + MaxRouterAdvertisementInterval uint32 + AdvertisingEnabled uint8 + ForwardingEnabled uint8 + WeakHostSend uint8 + WeakHostReceive uint8 + UseAutomaticMetric uint8 + UseNeighborUnreachabilityDetection uint8 + ManagedAddressConfigurationSupported uint8 + OtherStatefulConfigurationSupported uint8 + AdvertiseDefaultRoute uint8 + RouterDiscoveryBehavior uint32 + DadTransmits uint32 + BaseReachableTime uint32 + RetransmitTime uint32 + PathMtuDiscoveryTimeout uint32 + LinkLocalAddressBehavior uint32 + LinkLocalAddressTimeout uint32 + ZoneIndices [ScopeLevelCount]uint32 + SitePrefixLength uint32 + Metric uint32 + NlMtu uint32 + Connected uint8 + SupportsWakeUpPatterns uint8 + SupportsNeighborDiscovery uint8 + SupportsRouterDiscovery uint8 + ReachableTime uint32 + TransmitOffload uint32 + ReceiveOffload uint32 + DisableDefaultRoutes uint8 +} + // Console related constants used for the mode parameter to SetConsoleMode. See // https://docs.microsoft.com/en-us/windows/console/setconsolemode for details. diff --git a/go-controller/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/go-controller/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 4c2e1bdc01..01c0716c2c 100644 --- a/go-controller/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/go-controller/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -181,10 +181,15 @@ var ( procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree") procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute") procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") + procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2") procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo") procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx") procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") + procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") + procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry") + procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange") + procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange") procAddDllDirectory = modkernel32.NewProc("AddDllDirectory") procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") procCancelIo = modkernel32.NewProc("CancelIo") @@ -275,8 +280,10 @@ var ( procGetMaximumProcessorCount = modkernel32.NewProc("GetMaximumProcessorCount") procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") procGetModuleHandleExW = modkernel32.NewProc("GetModuleHandleExW") + procGetNamedPipeClientProcessId = modkernel32.NewProc("GetNamedPipeClientProcessId") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") + procGetNamedPipeServerProcessId = modkernel32.NewProc("GetNamedPipeServerProcessId") procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult") procGetPriorityClass = modkernel32.NewProc("GetPriorityClass") procGetProcAddress = modkernel32.NewProc("GetProcAddress") @@ -1606,6 +1613,14 @@ func DwmSetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, si return } +func CancelMibChangeNotify2(notificationHandle Handle) (errcode error) { + r0, _, _ := syscall.Syscall(procCancelMibChangeNotify2.Addr(), 1, uintptr(notificationHandle), 0, 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0) if r0 != 0 { @@ -1638,6 +1653,46 @@ func GetIfEntry(pIfRow *MibIfRow) (errcode error) { return } +func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) { + r0, _, _ := syscall.Syscall(procGetIfEntry2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(row)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) { + r0, _, _ := syscall.Syscall(procGetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.Syscall6(procNotifyIpInterfaceChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.Syscall6(procNotifyUnicastIpAddressChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func AddDllDirectory(path *uint16) (cookie uintptr, err error) { r0, _, e1 := syscall.Syscall(procAddDllDirectory.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) cookie = uintptr(r0) @@ -2393,6 +2448,14 @@ func GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err er return } +func GetNamedPipeClientProcessId(pipe Handle, clientProcessID *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetNamedPipeClientProcessId.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(clientProcessID)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0) if r1 == 0 { @@ -2409,6 +2472,14 @@ func GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint3 return } +func GetNamedPipeServerProcessId(pipe Handle, serverProcessID *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetNamedPipeServerProcessId.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(serverProcessID)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetOverlappedResult(handle Handle, overlapped *Overlapped, done *uint32, wait bool) (err error) { var _p0 uint32 if wait { diff --git a/go-controller/vendor/golang.org/x/term/README.md b/go-controller/vendor/golang.org/x/term/README.md index d03d0aefef..05ff623f94 100644 --- a/go-controller/vendor/golang.org/x/term/README.md +++ b/go-controller/vendor/golang.org/x/term/README.md @@ -4,16 +4,13 @@ This repository provides Go terminal and console support packages. -## Download/Install - -The easiest way to install is to run `go get -u golang.org/x/term`. You can -also manually git clone the repository to `$GOPATH/src/golang.org/x/term`. - ## Report Issues / Send Patches This repository uses Gerrit for code changes. To learn how to submit changes to -this repository, see https://golang.org/doc/contribute.html. +this repository, see https://go.dev/doc/contribute. + +The git repository is https://go.googlesource.com/term. The main issue tracker for the term repository is located at -https://github.com/golang/go/issues. Prefix your issue with "x/term:" in the +https://go.dev/issues. Prefix your issue with "x/term:" in the subject line, so it is easy to find. diff --git a/go-controller/vendor/golang.org/x/text/language/parse.go b/go-controller/vendor/golang.org/x/text/language/parse.go index 4d57222e77..053336e286 100644 --- a/go-controller/vendor/golang.org/x/text/language/parse.go +++ b/go-controller/vendor/golang.org/x/text/language/parse.go @@ -59,7 +59,7 @@ func (c CanonType) Parse(s string) (t Tag, err error) { if changed { tt.RemakeString() } - return makeTag(tt), err + return makeTag(tt), nil } // Compose creates a Tag from individual parts, which may be of type Tag, Base, diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index 9ee1b0e2b4..a0ecf2cb4a 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -473,8 +473,8 @@ go.opencensus.io/internal go.opencensus.io/trace go.opencensus.io/trace/internal go.opencensus.io/trace/tracestate -# golang.org/x/crypto v0.28.0 -## explicit; go 1.20 +# golang.org/x/crypto v0.36.0 +## explicit; go 1.23.0 golang.org/x/crypto/cryptobyte golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/ed25519 @@ -482,8 +482,8 @@ golang.org/x/crypto/ed25519 ## explicit; go 1.20 golang.org/x/exp/constraints golang.org/x/exp/maps -# golang.org/x/net v0.30.0 -## explicit; go 1.18 +# golang.org/x/net v0.38.0 +## explicit; go 1.23.0 golang.org/x/net/bpf golang.org/x/net/context golang.org/x/net/html @@ -494,6 +494,7 @@ golang.org/x/net/http2 golang.org/x/net/http2/hpack golang.org/x/net/icmp golang.org/x/net/idna +golang.org/x/net/internal/httpcommon golang.org/x/net/internal/iana golang.org/x/net/internal/socket golang.org/x/net/internal/socks @@ -507,21 +508,21 @@ golang.org/x/net/websocket ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.8.0 -## explicit; go 1.18 +# golang.org/x/sync v0.12.0 +## explicit; go 1.23.0 golang.org/x/sync/errgroup -# golang.org/x/sys v0.26.0 -## explicit; go 1.18 +# golang.org/x/sys v0.31.0 +## explicit; go 1.23.0 golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc -# golang.org/x/term v0.25.0 -## explicit; go 1.18 +# golang.org/x/term v0.30.0 +## explicit; go 1.23.0 golang.org/x/term -# golang.org/x/text v0.19.0 -## explicit; go 1.18 +# golang.org/x/text v0.23.0 +## explicit; go 1.23.0 golang.org/x/text/encoding golang.org/x/text/encoding/charmap golang.org/x/text/encoding/htmlindex @@ -1173,7 +1174,7 @@ k8s.io/kube-openapi/pkg/schemaconv k8s.io/kube-openapi/pkg/spec3 k8s.io/kube-openapi/pkg/util/proto k8s.io/kube-openapi/pkg/validation/spec -# k8s.io/kubernetes v1.32.3 +# k8s.io/kubernetes v1.32.6 ## explicit; go 1.23.0 k8s.io/kubernetes/pkg/apis/core k8s.io/kubernetes/pkg/probe diff --git a/test/conformance/go.mod b/test/conformance/go.mod index 65883ef719..b3763a3068 100644 --- a/test/conformance/go.mod +++ b/test/conformance/go.mod @@ -1,6 +1,6 @@ module github.com/ovn-org/ovn-kubernetes/test/conformance -go 1.21 +go 1.23.0 require ( gopkg.in/yaml.v3 v3.0.1 @@ -38,11 +38,11 @@ require ( github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/test/conformance/go.sum b/test/conformance/go.sum index 14a3443c7f..1e5b55a8e9 100644 --- a/test/conformance/go.sum +++ b/test/conformance/go.sum @@ -110,8 +110,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -120,23 +120,23 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/test/e2e/go.mod b/test/e2e/go.mod index d1d514d1f9..95ac4ff6ae 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -12,12 +12,12 @@ require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/pkg/errors v0.9.1 - golang.org/x/sync v0.11.0 + golang.org/x/sync v0.12.0 k8s.io/api v0.32.3 k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.32.3 k8s.io/klog v1.0.0 - k8s.io/kubernetes v1.32.3 + k8s.io/kubernetes v1.32.6 k8s.io/pod-security-admission v0.32.3 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 ) @@ -145,13 +145,13 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.35.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect @@ -192,7 +192,7 @@ require ( require ( github.com/containernetworking/plugins v1.2.0 github.com/coreos/butane v0.18.0 - github.com/docker/docker v26.1.4+incompatible + github.com/docker/docker v26.1.5+incompatible github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f github.com/onsi/ginkgo v1.16.5 github.com/openshift-kni/k8sreporter v1.0.6 diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 239bd56b7a..6838af0973 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -122,8 +122,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= +github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -581,8 +581,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -665,8 +665,8 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -690,8 +690,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -752,15 +752,15 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -771,8 +771,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1043,8 +1043,8 @@ k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= k8s.io/kubelet v0.32.3 h1:B9HzW4yB67flx8tN2FYuDwZvxnmK3v5EjxxFvOYjmc8= k8s.io/kubelet v0.32.3/go.mod h1:yyAQSCKC+tjSlaFw4HQG7Jein+vo+GeKBGdXdQGvL1U= -k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA= -k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k= +k8s.io/kubernetes v1.32.6 h1:tp1gRjOqZjaoFBek5PN6eSmODdS1QRrH5UKiFP8ZByg= +k8s.io/kubernetes v1.32.6/go.mod h1:REY0Gok66BTTrbGyZaFMNKO9JhxvgBDW9B7aksWRFoY= k8s.io/mount-utils v0.32.3 h1:ZPXXHblfBhYP89OnaozpFg9Ojl6HhDfxBLcdWNkaxW8= k8s.io/mount-utils v0.32.3/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= k8s.io/pod-security-admission v0.32.3 h1:scV0PQc3PdD6sXOMHukPZOCzGCGZeVN5z999gHBpkOc= From 116ba5222c543f9ea666d09127bce9ed2f8fd1c1 Mon Sep 17 00:00:00 2001 From: Alin Gabriel Serdean Date: Thu, 12 Jun 2025 14:14:52 +0000 Subject: [PATCH 047/278] ovnkube.sh: Add new overwriting options for the gateway options and kubernetes node name This commit adds: a) options to change ovn_gateway_opts and ovn_gateway_router_subnet by a container inside the same POD. the idea is that a init container can do an IP allocation write the output to a file and we will consume those values from the file. b) in case of ovnkube in DPU mode, we are running ovnkube on behalf of a different host, however the way we identify that is using the DPU hostname. to bypass the latter we will use the OVS metadata external_ids:host-k8s-nodename. This is already used by the ovn-node (OVN central where we have a single global zone). c) extend stateless network policies for ovnkube running in different mode types: ovn-master, ovnkube-controller and ovnkube-controller-with-node. this is useful for offloading RDMA traffic. Signed-off-by: Alin Gabriel Serdean --- dist/images/ovnkube.sh | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index ae77d2f13b..bbe7f9d929 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -324,6 +324,17 @@ ovn_nohostsubnet_label=${OVN_NOHOSTSUBNET_LABEL:-""} # should be set to true when dpu nodes are in the cluster ovn_disable_requestedchassis=${OVN_DISABLE_REQUESTEDCHASSIS:-false} +# external_ids:host-k8s-nodename is set on an Open_vSwitch enabled system if the ovnkube pod +# should function on behalf of a different host than external_ids:host +# overwrite the K8S_NODE env var with the one found within the OVS metadata in this case +if [[ ${ovnkube_node_mode} == "dpu" ]]; then + K8S_NODE=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"') + if [[ ${K8S_NODE} == "" ]]; then + echo "Trying to run in DPU mode and couldn't get the required Host K8s Nodename. Exiting..." + exit 1 + fi +fi + # Determine the ovn rundir. if [[ -f /usr/bin/ovn-appctl ]]; then # ovn-appctl is present. Use new ovn run dir path. @@ -1356,6 +1367,7 @@ ovn-master() { ${network_qos_enabled_flag} \ ${ovn_enable_dnsnameresolver_flag} \ ${nohostsubnet_label_option} \ + ${ovn_stateless_netpol_enable_flag} \ ${ovn_disable_requestedchassis_flag} \ --cluster-subnets ${net_cidr} --k8s-service-cidr=${svc_cidr} \ --gateway-mode=${ovn_gateway_mode} ${ovn_gateway_opts} \ @@ -1626,6 +1638,13 @@ ovnkube-controller() { fi echo "ovn_observ_enable_flag=${ovn_observ_enable_flag}" + + ovn_stateless_netpol_enable_flag= + if [[ ${ovn_stateless_netpol_enable} == "true" ]]; then + ovn_stateless_netpol_enable_flag="--enable-stateless-netpol" + fi + echo "ovn_stateless_netpol_enable_flag: ${ovn_stateless_netpol_enable_flag}" + echo "=============== ovnkube-controller ========== MASTER ONLY" /usr/bin/ovnkube --init-ovnkube-controller ${K8S_NODE} \ ${anp_enabled_flag} \ @@ -2054,6 +2073,11 @@ ovnkube-controller-with-node() { fi echo "ovn_observ_enable_flag=${ovn_observ_enable_flag}" + ovn_stateless_netpol_enable_flag= + if [[ ${ovn_stateless_netpol_enable} == "true" ]]; then + ovn_stateless_netpol_enable_flag="--enable-stateless-netpol" + fi + echo "=============== ovnkube-controller-with-node --init-ovnkube-controller-with-node==========" /usr/bin/ovnkube --init-ovnkube-controller ${K8S_NODE} --init-node ${K8S_NODE} \ ${anp_enabled_flag} \ @@ -2399,8 +2423,13 @@ ovn-node() { wait_for_event ovs_ready fi - echo "=============== ovn-node - (wait for ready_to_start_node)" - wait_for_event ready_to_start_node + if [[ ${ovnkube_node_mode} != "dpu-host" ]] && [[ ${ovn_enable_interconnect} != "true" ]]; then + # ready_to_start_node checks for the NB/SB readiness state. + # This is not available on the DPU host when interconnect is enabled, + # because the DBs will run locally on the DPU + echo "=============== ovn-node - (wait for ready_to_start_node)" + wait_for_event ready_to_start_node + fi echo "ovn_nbdb ${ovn_nbdb} ovn_sbdb ${ovn_sbdb} ovn_nbdb_conn ${ovn_nbdb_conn}" @@ -2578,12 +2607,6 @@ ovn-node() { fi if [[ ${ovnkube_node_mode} == "dpu" ]]; then - # in the case of dpu mode we want the host K8s Node Name and not the DPU K8s Node Name - K8S_NODE=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"') - if [[ ${K8S_NODE} == "" ]]; then - echo "Couldn't get the required Host K8s Nodename. Exiting..." - exit 1 - fi if [[ ${ovn_gateway_opts} == "" ]]; then # get the gateway interface gw_iface=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:ovn-gw-interface | tr -d \") From 05f8d8f001974c683b1686e1a24fc8646753be67 Mon Sep 17 00:00:00 2001 From: Alin Gabriel Serdean Date: Mon, 16 Jun 2025 16:51:11 +0000 Subject: [PATCH 048/278] Add short doc update with the ovn-ic components on the DPU Signed-off-by: Alin Gabriel Serdean --- dist/images/ovnkube.sh | 19 +++++----- docs/features/hardware-offload/dpu-support.md | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index bbe7f9d929..85b8eeab14 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -324,15 +324,14 @@ ovn_nohostsubnet_label=${OVN_NOHOSTSUBNET_LABEL:-""} # should be set to true when dpu nodes are in the cluster ovn_disable_requestedchassis=${OVN_DISABLE_REQUESTEDCHASSIS:-false} -# external_ids:host-k8s-nodename is set on an Open_vSwitch enabled system if the ovnkube pod -# should function on behalf of a different host than external_ids:host +# external_ids:host-k8s-nodename is set on an Open_vSwitch enabled system if the ovnkube stack +# should function on behalf of a different host than external_ids:hostname. This includes +# all the components that belond in an ovnkube stack (i.e. NB DB, SB DB, ovnkube etc) # overwrite the K8S_NODE env var with the one found within the OVS metadata in this case -if [[ ${ovnkube_node_mode} == "dpu" ]]; then - K8S_NODE=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"') - if [[ ${K8S_NODE} == "" ]]; then - echo "Trying to run in DPU mode and couldn't get the required Host K8s Nodename. Exiting..." - exit 1 - fi +ovn_k8s_node=$(ovs-vsctl --if-exists get Open_vSwitch . external_ids:host-k8s-nodename | tr -d '\"') +if [[ ! -z $ovn_k8s_node ]]; then + echo "host-k8s-nodename is set, overriding K8S_NODE with $ovn_k8s_node" + K8S_NODE=$ovn_k8s_node fi # Determine the ovn rundir. @@ -2423,10 +2422,12 @@ ovn-node() { wait_for_event ovs_ready fi - if [[ ${ovnkube_node_mode} != "dpu-host" ]] && [[ ${ovn_enable_interconnect} != "true" ]]; then + if [[ ${ovnkube_node_mode} == "dpu-host" ]] && [[ ${ovn_enable_interconnect} == "true" ]]; then # ready_to_start_node checks for the NB/SB readiness state. # This is not available on the DPU host when interconnect is enabled, # because the DBs will run locally on the DPU + echo "skipping ready_to_start_node on DPU Host and when interconnect is true" + else echo "=============== ovn-node - (wait for ready_to_start_node)" wait_for_event ready_to_start_node fi diff --git a/docs/features/hardware-offload/dpu-support.md b/docs/features/hardware-offload/dpu-support.md index 6c098de727..bc9d731a39 100644 --- a/docs/features/hardware-offload/dpu-support.md +++ b/docs/features/hardware-offload/dpu-support.md @@ -17,3 +17,39 @@ on the embedded CPU. Any vendor that manufactures a DPU which supports the above model should work with current design. Design document can be found [here](https://docs.google.com/document/d/11IoMKiohK7hIyIE36FJmwJv46DEBx52a4fqvrpCBBcg/edit?usp=sharing). + +## OVN Kubernetes in a DPU-Accelerated Environment + +The **ovn-kubernetes** deployment will have two parts one on the host and another on the DPU side. + + +These aforementioned parts are expected to be deployed also on two different Kubernetes clusters, one for the host and another for the DPUs. + + +### Host Cluster +--- + +#### OVN Kubernetes control plane related component +- ovn-cluster-manager + +#### OVN Kubernetes components on a Standard Host (Non-DPU) +- local-nb-ovsdb +- local-sb-ovsdb +- run-ovn-northd +- ovnkube-controller-with-node +- ovn-controller +- ovs-metrics + +#### OVN Kubernetes component on a DPU-Enabled Host +- ovn-node + +### DPU Cluster +--- + +#### OVN Kubernetes components +- local-nb-ovsdb +- local-sb-ovsdb +- run-ovn-northd +- ovnkube-controller-with-node +- ovn-controller +- ovs-metrics From ee962e5cc31cd90db5bf63dafc0d41f39900ed20 Mon Sep 17 00:00:00 2001 From: Geo Turcsanyi Date: Thu, 26 Jun 2025 22:02:48 +0200 Subject: [PATCH 049/278] update documentation on deploying OVN K8s with KIND Signed-off-by: Geo Turcsanyi --- .../launching-ovn-kubernetes-on-kind.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/installation/launching-ovn-kubernetes-on-kind.md b/docs/installation/launching-ovn-kubernetes-on-kind.md index c3a49ddde7..5c61f3a9cd 100644 --- a/docs/installation/launching-ovn-kubernetes-on-kind.md +++ b/docs/installation/launching-ovn-kubernetes-on-kind.md @@ -18,15 +18,16 @@ KIND (Kubernetes in Docker) deployment of OVN kubernetes is a fast and easy mean - jq - openssl - openvswitch +- Go 1.23.0 or above -**NOTE :** In certain operating systems such as CentOS 8.x, pip2 and pip3 binaries are installed instead of pip. In such situations create a softlink for "pip" that points to "pip2". +**NOTE :** In certain operating systems such as CentOS 8.x, pip2 and pip3 binaries are installed instead of pip. In such situations create a softlink for "pip" that points to "pip2". For OVN kubernetes KIND deployment, use the `kind.sh` script. First Download and build the OVN-Kubernetes repo: ``` -git clone github.com/ovn-org/ovn-kubernetes; +git clone https://github.com/ovn-kubernetes/ovn-kubernetes.git; cd ovn-kubernetes ``` The `kind.sh` script builds OVN-Kubernetes into a container image. To verify @@ -53,6 +54,13 @@ $ ./kind.sh $ popd ``` +**NOTE:** If you run into issues with installing jinjanate on Ubuntu due to [PEP-0668](https://peps.python.org/pep-0668/) you can work around via: +``` +sudo apt-get install pipx +pipx install jinjanator[yaml] +pipx ensurepath +``` + ### Run the KIND deployment with podman To verify local changes, the steps are mostly the same as with docker, except the `fedora` make target: @@ -80,12 +88,14 @@ To deploy KIND however, you need to start it as root and then copy root's kube c $ pushd contrib $ sudo ./kind.sh -ep podman $ sudo cp /root/ovn.conf ~/.kube/kind-config -$ sudo chown $(id -u):$(id -g) ~/.kube/kind-config +$ sudo chown $(id -u):$(id -g) -R ~/.kube $ export KUBECONFIG=~/.kube/kind-config $ popd ``` -This will launch a KIND deployment. By default the cluster is named `ovn`. +**NOTE:** If you installed go via the official path on Linux and have encountered the "go: command not found" issue, you can preserve your environment when doing sudo: `sudo --preserve-env=PATH ./kind.sh -ep podman` + +This will launch a KIND deployment. By default, the cluster is named `ovn`. ``` $ kubectl get nodes From f1a31ed31a6444418b49f21f0d4902dd951f7258 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 7 May 2025 13:21:36 -0400 Subject: [PATCH 050/278] Add static FDB entry to OVS for shared MAC The FDB lookup is only used for non-destined shared MAC traffic. When OVN or the host send a packet that hits a NORMAL action it will initate MAC learning and can drive up the CPU of OVS. We still need NORMAL action to account for sending to unknown ports like localnet ports, but we do not want to learn the shared MAC. Therefore create a static entry binding it to the LOCAL port. Signed-off-by: Tim Rozet --- go-controller/pkg/node/gateway.go | 5 +++++ go-controller/pkg/node/gateway_init_linux_test.go | 9 +++++++++ go-controller/pkg/node/gateway_udn_test.go | 3 +++ go-controller/pkg/util/ovs.go | 13 +++++++++++++ 4 files changed, 30 insertions(+) diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 1b4544f89b..38a7ad2910 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -424,6 +424,11 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops } } + // Set static FDB entry for LOCAL port + if err := util.SetStaticFDBEntry(gatewayBridge.bridgeName, gatewayBridge.bridgeName, gatewayBridge.macAddress); err != nil { + return nil, nil, err + } + l3GwConfig := util.L3GatewayConfig{ Mode: config.Gateway.Mode, ChassisID: chassisID, diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 0f6eab05ce..8bc38dcbf7 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -195,6 +195,9 @@ func shareGatewayInterfaceTest(app *cli.App, testNS ns.NetNS, Cmd: "ovs-vsctl --timeout=15 --if-exists get Open_vSwitch . other_config:hw-offload", Output: fmt.Sprintf("%t", hwOffload), }) + fexec.AddFakeCmdsNoOutputNoError([]string{ + "ovs-appctl --timeout=15 fdb/add breth0 breth0 0 " + eth0MAC, + }) fexec.AddFakeCmd(&ovntest.ExpectedCmd{ Cmd: "ovs-vsctl --timeout=15 get Interface patch-breth0_node1-to-br-int ofport", Output: "5", @@ -633,6 +636,9 @@ func shareGatewayInterfaceDPUTest(app *cli.App, testNS ns.NetNS, Cmd: "ovs-vsctl --timeout=15 --if-exists get Open_vSwitch . other_config:hw-offload", Output: "false", }) + fexec.AddFakeCmdsNoOutputNoError([]string{ + fmt.Sprintf("ovs-appctl --timeout=15 fdb/add %s %s 0 %s", brphys, brphys, hostMAC), + }) // GetDPUHostInterface fexec.AddFakeCmd(&ovntest.ExpectedCmd{ Cmd: "ovs-vsctl --timeout=15 list-ports " + brphys, @@ -1086,6 +1092,9 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0` Cmd: "ovs-vsctl --timeout=15 --if-exists get Open_vSwitch . other_config:hw-offload", Output: "false", }) + fexec.AddFakeCmdsNoOutputNoError([]string{ + "ovs-appctl --timeout=15 fdb/add breth0 breth0 0 " + eth0MAC, + }) fexec.AddFakeCmd(&ovntest.ExpectedCmd{ Cmd: "ovs-vsctl --timeout=15 get Interface patch-breth0_node1-to-br-int ofport", Output: "5", diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 8c38c7ec5b..4f3e4efa67 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -171,6 +171,9 @@ func setUpGatewayFakeOVSCommands(fexec *ovntest.FakeExec) { Cmd: "ovs-vsctl --timeout=15 --if-exists get Open_vSwitch . other_config:hw-offload", Output: "false", }) + fexec.AddFakeCmdsNoOutputNoError([]string{ + "ovs-appctl --timeout=15 fdb/add breth0 breth0 0 00:00:00:55:66:99", + }) fexec.AddFakeCmd(&ovntest.ExpectedCmd{ Cmd: "ovs-vsctl --timeout=15 get Interface patch-breth0_worker1-to-br-int ofport", Output: "5", diff --git a/go-controller/pkg/util/ovs.go b/go-controller/pkg/util/ovs.go index ff21e828db..e6322fd710 100644 --- a/go-controller/pkg/util/ovs.go +++ b/go-controller/pkg/util/ovs.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "net" "regexp" "runtime" "strings" @@ -819,6 +820,18 @@ func DetectCheckPktLengthSupport(bridge string) (bool, error) { return false, nil } +// SetStaticFDBEntry programs a static MAC entry into the OVS FIB and disables MAC learning for this entry +func SetStaticFDBEntry(bridge, port string, mac net.HardwareAddr) error { + // Assume default VLAN for local port + vlan := "0" + stdout, stderr, err := RunOVSAppctl("fdb/add", bridge, port, vlan, mac.String()) + if err != nil { + return fmt.Errorf("failed to add FDB entry to OVS for LOCAL port, "+ + "stdout: %q, stderr: %q, error: %v", stdout, stderr, err) + } + return nil +} + // IsOvsHwOffloadEnabled checks if OvS Hardware Offload is enabled. func IsOvsHwOffloadEnabled() (bool, error) { stdout, stderr, err := RunOVSVsctl("--if-exists", "get", From 813e2800dd840c8d678c5bb79f116c5ebda2ec0d Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Thu, 26 Jun 2025 18:27:38 -0400 Subject: [PATCH 051/278] Fixes FDB learning Commit f978967 caused a regression in performance. As the below issue describes, the egress traffic from OVN will now use NORMAL action, which will cause an FDB lookup and then FLOOD if not found. This always ends up being the case because the reply ARP packet from the physical port is flooded to the patch port and the LOCAL port. This causes an increase in CPU and unnecessarily flooding packets. We need layer 2 packets destined to the shared gateway mac to go to both the host and OVN. This is so both can receive ARP replies, etc. However, we also need the FDB entry in OVS to get updated, for our new functionality with using the NORMAL action. To fix this, add a static FDB entry for LOCAL, then modify the layer 2 flooding flow actions from "output:patch,LOCAL" to "output:patch,NORMAL". Since the FDB entry is bound in the table to LOCAL, it is effectively forwarding the packets the same as before, but with the added bonus of FDB learning on ingress. Fixes: #5318 Signed-off-by: Tim Rozet --- go-controller/pkg/node/gateway_shared_intf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index d763089082..0a1dbdd0d3 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1897,7 +1897,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin for _, netConfig := range bridge.patchedNetConfigs() { actions += "output:" + netConfig.ofPortPatch + "," } - actions += strip_vlan + "output:" + ofPortHost + actions += strip_vlan + "NORMAL" dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, %s dl_dst=%s, actions=%s", defaultOpenFlowCookie, ofPortPhys, match_vlan, bridgeMacAddress, actions)) From 098a3aa7728b45a739ab569587d47e105a54d486 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Thu, 20 Mar 2025 10:07:11 +0100 Subject: [PATCH 052/278] Fix UDN nftables mark chain cleanup There is no need to flush the chain before removing. Additionaly handle the case where the chain was already removed. Signed-off-by: Patryk Diak --- go-controller/pkg/node/gateway_udn.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 7ab5b50cc9..ba299e60fd 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -310,7 +310,9 @@ func (udng *UserDefinedNetworkGateway) delMarkChain() error { chain := &knftables.Chain{ Name: GetUDNMarkChain(fmt.Sprintf("0x%x", udng.pktMark)), } - tx.Flush(chain) + // Delete would return an error if we tried to delete a chain that didn't exist, so + // we do an Add first (which is a no-op if the chain already exists) and then Delete. + tx.Add(chain) tx.Delete(chain) return nft.Run(context.TODO(), tx) } From 3735ec2d3d539999465018e36d2f6c26905d9203 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Fri, 27 Jun 2025 12:53:05 -0400 Subject: [PATCH 053/278] Remove physical port from l2 flow This allows a localnet VM arp reply to go to OVN, rather than a lookup that only hits the LOCAL port in the fdb table. Signed-off-by: Tim Rozet --- go-controller/pkg/node/gateway_shared_intf.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 0a1dbdd0d3..00c96cef1a 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1899,8 +1899,8 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin } actions += strip_vlan + "NORMAL" dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, %s dl_dst=%s, actions=%s", - defaultOpenFlowCookie, ofPortPhys, match_vlan, bridgeMacAddress, actions)) + fmt.Sprintf("cookie=%s, priority=10, table=0, %s dl_dst=%s, actions=%s", + defaultOpenFlowCookie, match_vlan, bridgeMacAddress, actions)) } // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all From 9a6e8e337c39a7185ad336dc50241f63c347729b Mon Sep 17 00:00:00 2001 From: Flavio Fernandes Date: Thu, 8 May 2025 21:04:04 +0000 Subject: [PATCH 054/278] ovnkube.sh: use node name as zone as default if ovn-ic If the `k8s.ovn.org/zone-name` label is not set on the node, the fallback logic now uses the node name as the zone when `ovn_enable_interconnect` is true. Otherwise, the zone defaults to "global" as before. Also updated the empty string check to use `-z`, which is more idiomatic in Bash. Signed-off-by: Flavio Fernandes --- dist/images/ovnkube.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 85b8eeab14..287b7a1f55 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -813,8 +813,12 @@ function memory_trim_on_compaction_supported { function get_node_zone() { zone=$(kubectl --subresource=status --server=${K8S_APISERVER} --token=${k8s_token} --certificate-authority=${K8S_CACERT} \ get node ${K8S_NODE} -o=jsonpath={'.metadata.labels.k8s\.ovn\.org/zone-name'}) - if [ "$zone" == "" ]; then - zone="global" + if [ -z "$zone" ]; then + if [[ ${ovn_enable_interconnect} == "true" ]]; then + zone="${K8S_NODE}" + else + zone="global" + fi fi echo "$zone" } From 0b38e623e52d6425ce639f420f7671fe0ec15371 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Mon, 30 Jun 2025 13:50:32 +0200 Subject: [PATCH 055/278] [svc controller] Stop handlers on shutdown. Before UDN services controller was only stopped together with the whole watchFactory, so there was no need to explicitly stop added event handlers. With UDN we create and delete this controller per UDN, so an explicit handler is required. Otherwise it will cause a memory leak. Signed-off-by: Nadia Pinaeva --- .../services/services_controller.go | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/go-controller/pkg/ovn/controller/services/services_controller.go b/go-controller/pkg/ovn/controller/services/services_controller.go index e03ad40b5c..b97802c85a 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller.go +++ b/go-controller/pkg/ovn/controller/services/services_controller.go @@ -168,6 +168,11 @@ type Controller struct { useTemplates bool netInfo util.NetInfo + + // handlers stored for shutdown + nodeHandler cache.ResourceEventHandlerRegistration + svcHandler cache.ResourceEventHandlerRegistration + endpointHandler cache.ResourceEventHandlerRegistration } // Run will not return until stopCh is closed. workers determines how many @@ -180,15 +185,15 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, wg *sync.WaitGroup // wait until we're told to stop <-stopCh - klog.Infof("Shutting down controller %s for network=%s", controllerName, c.netInfo.GetNetworkName()) - c.queue.ShutDown() + c.Cleanup() }() c.useLBGroups = useLBGroups c.useTemplates = useTemplates klog.Infof("Starting controller %s for network=%s", controllerName, c.netInfo.GetNetworkName()) - nodeHandler, err := c.nodeTracker.Start(c.nodeInformer) + var err error + c.nodeHandler, err = c.nodeTracker.Start(c.nodeInformer) if err != nil { return err } @@ -197,12 +202,12 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, wg *sync.WaitGroup c.startupDoneLock.Lock() c.startupDone = false c.startupDoneLock.Unlock() - if !util.WaitForHandlerSyncWithTimeout(nodeControllerName, stopCh, types.HandlerSyncTimeout, nodeHandler.HasSynced) { + if !util.WaitForHandlerSyncWithTimeout(nodeControllerName, stopCh, types.HandlerSyncTimeout, c.nodeHandler.HasSynced) { return fmt.Errorf("error syncing node tracker handler") } klog.Infof("Setting up event handlers for services for network=%s", c.netInfo.GetNetworkName()) - svcHandler, err := c.serviceInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace(cache.ResourceEventHandlerFuncs{ + c.svcHandler, err = c.serviceInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace(cache.ResourceEventHandlerFuncs{ AddFunc: c.onServiceAdd, UpdateFunc: c.onServiceUpdate, DeleteFunc: c.onServiceDelete, @@ -212,7 +217,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, wg *sync.WaitGroup } klog.Infof("Setting up event handlers for endpoint slices for network=%s", c.netInfo.GetNetworkName()) - endpointHandler, err := c.endpointSliceInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace( + c.endpointHandler, err = c.endpointSliceInformer.Informer().AddEventHandler(factory.WithUpdateHandlingForObjReplace( // Filter out endpointslices that don't belong to this network (i.e. keep only kube-generated endpointslices if // on default network, keep only mirrored endpointslices for this network if on UDN) util.GetEndpointSlicesEventHandlerForNetwork( @@ -227,7 +232,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, wg *sync.WaitGroup } klog.Infof("Waiting for service and endpoint handlers to sync for network=%s", c.netInfo.GetNetworkName()) - if !util.WaitForHandlerSyncWithTimeout(controllerName, stopCh, types.HandlerSyncTimeout, svcHandler.HasSynced, endpointHandler.HasSynced) { + if !util.WaitForHandlerSyncWithTimeout(controllerName, stopCh, types.HandlerSyncTimeout, c.svcHandler.HasSynced, c.endpointHandler.HasSynced) { return fmt.Errorf("error syncing service and endpoint handlers") } @@ -255,6 +260,27 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}, wg *sync.WaitGroup return nil } +func (c *Controller) Cleanup() { + klog.Infof("Shutting down controller %s for network=%s", controllerName, c.netInfo.GetNetworkName()) + c.queue.ShutDown() + + if c.nodeHandler != nil { + if err := c.nodeInformer.Informer().RemoveEventHandler(c.nodeHandler); err != nil { + klog.Errorf("Failed to remove node handler for network %s: %v", c.netInfo.GetNetworkName(), err) + } + } + if c.svcHandler != nil { + if err := c.serviceInformer.Informer().RemoveEventHandler(c.svcHandler); err != nil { + klog.Errorf("Failed to remove service handler for network %s: %v", c.netInfo.GetNetworkName(), err) + } + } + if c.endpointHandler != nil { + if err := c.endpointSliceInformer.Informer().RemoveEventHandler(c.endpointHandler); err != nil { + klog.Errorf("Failed to remove endpoint handler for network %s: %v", c.netInfo.GetNetworkName(), err) + } + } +} + // worker runs a worker thread that just dequeues items, processes them, and // marks them done. You may run as many of these in parallel as you wish; the // workqueue guarantees that they will not end up processing the same service From fa83c31b5ad6506f587434dae0cd55d7c872447f Mon Sep 17 00:00:00 2001 From: Flavio Fernandes Date: Sun, 29 Jun 2025 18:08:16 -0400 Subject: [PATCH 056/278] Add network QoS guide to docs navigation This commit adds a user guide doc for the OKEP-4380: Network QoS Support https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/docs/okeps/okep-4380-network-qos.md Signed-off-by: Flavio Fernandes --- docs/features/network-qos-guide.md | 329 +++++++++++++++++++++++++++++ mkdocs.yml | 4 +- 2 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 docs/features/network-qos-guide.md diff --git a/docs/features/network-qos-guide.md b/docs/features/network-qos-guide.md new file mode 100644 index 0000000000..586368fb32 --- /dev/null +++ b/docs/features/network-qos-guide.md @@ -0,0 +1,329 @@ +# Guide to Using Network QoS + +## Contents + +1. [Overview](#1-overview) +2. [Create a Secondary Network (NAD)](#2-create-a-secondary-network) +3. [Define a NetworkQoS Policy](#3-define-a-networkqos-policy) +4. [Create Sample Pods and Verify the Configuration](#4-create-sample-pods-and-verify-the-configuration) +5. [Explain the NetworkQoS Object](#5-explain-the-networkqos-object) + +## **1 Overview** + +Differentiated Services Code Point (DSCP) marking and egress bandwidth metering let you prioritize or police specific traffic flows. The new **NetworkQoS** Custom Resource Definition (CRD) in [ovn-kubernetes](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/dist/templates/k8s.ovn.org_networkqoses.yaml.j2) makes both features available to Kubernetes users on **all** pod interfaces—primary or secondary—without touching pod manifests. + +This guide provides a step-by-step example of how to use this feature. Before you begin, ensure that you have a Kubernetes cluster configured with the ovn-kubernetes CNI. Since the examples use network attachments, you must run the cluster with multiple network support enabled. In a kind cluster, you would use the following flags: + +```bash +cd contrib +./kind-helm.sh -nqe -mne ; # --enable-network-qos --enable-multi-network +``` + +## **2 Create a Secondary Network** + +File: nad.yaml + +```yaml +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: ovn-stream + namespace: default + labels: # label needed for NetworkQoS selector + nad-type: ovn-kubernetes-nqos +spec: + config: |2 + { + "cniVersion": "1.0.0", + "name": "ovn-stream", + "type": "ovn-k8s-cni-overlay", + "topology": "layer3", + "subnets": "10.245.0.0/16/24", + "mtu": 1300, + "master": "eth1", + "netAttachDefName": "default/ovn-stream" + } +``` +*Why the label?* `NetworkQoS` uses a label selector to find matching NADs. Without at least one label, the selector cannot match. + +## **3 Define a NetworkQoS Policy** + +File: nqos.yaml + +```yaml +apiVersion: k8s.ovn.org/v1alpha1 +kind: NetworkQoS +metadata: + name: qos-external + namespace: default +spec: + networkSelectors: + - networkSelectionType: NetworkAttachmentDefinitions + networkAttachmentDefinitionSelector: + namespaceSelector: {} # any namespace + networkSelector: + matchLabels: + nad-type: ovn-kubernetes-nqos + podSelector: + matchLabels: + nqos-app: bw-limited + priority: 10 # higher value wins in a tie-break + egress: + - dscp: 20 + bandwidth: + burst: 100 # kilobits + rate: 20000 # kbps + classifier: + to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.11.12.13/32 + - 172.16.0.0/12 + - 192.168.0.0/16 +``` +A full CRD template lives [here](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/dist/templates/k8s.ovn.org_networkqoses.yaml.j2). + +The `egress` field is a list, allowing you to define multiple markings and bandwidth limits based on different classifiers. + +Note that this configuration will apply to the NAD of pods based on the network selector, and only on pods that have the label `nqos-app: bw-limited`. + +```bash +$ kubectl create -f nad.yaml && \ + kubectl create -f nqos.yaml + +networkattachmentdefinition.k8s.cni.cncf.io/ovn-stream created +networkqos.k8s.ovn.org/qos-external created +``` +At this point, the output from `kubectl get networkqoses` will look like this: + +```bash +$ kubectl api-resources -owide | head -1 ; \ + kubectl api-resources -owide | grep NetworkQoS +NAME SHORTNAMES APIVERSION NAMESPACED KIND VERBS CATEGORIES +networkqoses k8s.ovn.org/v1alpha1 true NetworkQoS delete,deletecollection,get,list,patch,create,update,watch + +$ kubectl get networkqoses qos-external -n default -owide +NAME STATUS +qos-external NetworkQoS Destinations applied +``` + +## **4 Create Sample Pods and Verify the Configuration** + +### **4.1 Launch Test Pods** + +To test this, let's create a pod using a helper function that allows us to add labels to it. + +File: create_pod.source + +```bash +create_pod() { + local pod_name=${1:-pod0} + local node_name=${2:-ovn-worker} + local extra_labels=${3:-} + + NAMESPACE=$(kubectl config view --minify --output 'jsonpath={..namespace}') + NAMESPACE=${NAMESPACE:-default} + + if ! kubectl get pod "$pod_name" -n "$NAMESPACE" &>/dev/null; then + echo "Creating pod $pod_name in namespace $NAMESPACE..." + + # Prepare labels block + labels_block=" name: $pod_name" + if [[ -n "$extra_labels" ]]; then + # Convert JSON string to YAML-compatible lines + while IFS="=" read -r k v; do + labels_block+=" + $k: $v" + done < <(echo "$extra_labels" | jq -r 'to_entries|map("\(.key)=\(.value)")|.[]') + fi + + # Generate the manifest + cat </dev/null 2>&1 & +# pod1 to pod2 +nohup kubectl exec -i pod1 -- ping -c 3600 -q $DST_IP_POD2 >/dev/null 2>&1 & + +sudo dnf install -y --quiet tcpdump ; # Install tcpdump, if needed + +IPNS=$(docker inspect --format '{{ '{{' }} .State.Pid }}' ovn-worker) +sudo nsenter -t ${IPNS} -n tcpdump -envvi eth0 geneve +``` + +``` +tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes + +**Pod0 to Pod2**: Notice that since pod0 does not have the label to match against NetworkQoS, its TOS is 0. However, pod2's response is DSCP marked (tos 0x50), since pod2 matches the NetworkQoS criteria with the label `nqos-app: bw-limited`. + +12:46:30.755551 02:42:ac:12:00:06 > 02:42:ac:12:00:05, ethertype IPv4 (0x0800), length 156: (tos 0x0, ttl 64, id 26896, offset 0, flags [DF], proto UDP (17), length 142) + 172.18.0.6.38210 > 172.18.0.5.geneve: [bad udp cksum 0x58bb -> 0xc87d!] Geneve, Flags [C], vni 0x12, proto TEB (0x6558), options [class Open Virtual Networking (OVN) (0x102) type 0x80(C) len 8 data 00090006] + 0a:58:0a:f5:02:01 > 0a:58:0a:f5:02:03, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 63, id 61037, offset 0, flags [DF], proto ICMP (1), length 84) + 10.245.4.4 > 10.245.2.3: ICMP echo request, id 14, seq 44, length 64 + +— + +12:46:30.755694 02:42:ac:12:00:05 > 02:42:ac:12:00:06, ethertype IPv4 (0x0800), length 156: (tos 0x50, ttl 64, id 46220, offset 0, flags [DF], proto UDP (17), length 142) + 172.18.0.5.38210 > 172.18.0.6.geneve: [bad udp cksum 0x58bb -> 0xc47d!] Geneve, Flags [C], vni 0x12, proto TEB (0x6558), options [class Open Virtual Networking (OVN) (0x102) type 0x80(C) len 8 data 0004000a] + 0a:58:0a:f5:04:01 > 0a:58:0a:f5:04:04, ethertype IPv4 (0x0800), length 98: (tos 0x50, ttl 63, id 45002, offset 0, flags [none], proto ICMP (1), length 84) + 10.245.2.3 > 10.245.4.4: ICMP echo reply, id 14, seq 44, length 64 + +—--------- + +**Pod1 to Pod2**: Traffic is marked both ways (both pods have the matching label) + +12:46:30.497289 02:42:ac:12:00:06 > 02:42:ac:12:00:05, ethertype IPv4 (0x0800), length 156: (tos 0x50, ttl 64, id 26752, offset 0, flags [DF], proto UDP (17), length 142) + 172.18.0.6.7856 > 172.18.0.5.geneve: [bad udp cksum 0x58bb -> 0x3f10!] Geneve, Flags [C], vni 0x12, proto TEB (0x6558), options [class Open Virtual Networking (OVN) (0x102) type 0x80(C) len 8 data 00090006] + 0a:58:0a:f5:02:01 > 0a:58:0a:f5:02:03, ethertype IPv4 (0x0800), length 98: (tos 0x50, ttl 63, id 21760, offset 0, flags [DF], proto ICMP (1), length 84) + 10.245.4.3 > 10.245.2.3: ICMP echo request, id 14, seq 56, length 64 + +— + +12:46:30.497381 02:42:ac:12:00:05 > 02:42:ac:12:00:06, ethertype IPv4 (0x0800), length 156: (tos 0x50, ttl 64, id 46019, offset 0, flags [DF], proto UDP (17), length 142) + 172.18.0.5.7856 > 172.18.0.6.geneve: [bad udp cksum 0x58bb -> 0x3b11!] Geneve, Flags [C], vni 0x12, proto TEB (0x6558), options [class Open Virtual Networking (OVN) (0x102) type 0x80(C) len 8 data 0004000a] + 0a:58:0a:f5:04:01 > 0a:58:0a:f5:04:03, ethertype IPv4 (0x0800), length 98: (tos 0x50, ttl 63, id 3850, offset 0, flags [none], proto ICMP (1), length 84) + 10.245.2.3 > 10.245.4.3: ICMP echo reply, id 14, seq 56, length 64 +``` + +## **5 Explain the NetworkQoS Object** + +Below is an *abbreviated* map of the CRD schema returned by `kubectl explain networkqos --recursive` (v1alpha1). Use this as a quick reference. For the definitive specification, always consult the `kubectl explain` output or the CRD YAML in the ovn-kubernetes repository. + +### **5.1 Top‑level `spec` keys** + +| Field | Type | Required | Purpose | +| ----- | ----- | ----- | ----- | +| **podSelector** | `LabelSelector` | No | Selects pods whose traffic will be evaluated by the QoS rules. If empty, all pods in the namespace are selected. | +| **networkSelectors[]** | list `NetworkSelector` | No | Restricts the rule to traffic on specific networks. If absent, the rule matches any interface. *(See §5.2)* | +| **priority** | `int` | **Yes** | Higher number → chosen first when multiple `NetworkQoS` objects match the same packet. | +| **egress[]** | list `EgressRule` | **Yes** | One or more marking / policing rules. Evaluated in the order listed. *(See §5.3)* | + +Note the square-bracket notation (`[]`) for **both** `egress` and `networkSelectors`—each is an array in the CRD. + +--- + +### **5.2 Inside a `networkSelectors[]` entry** + +Each list element tells the controller **where** the pods' egress traffic must flow in order to apply the rule. Exactly **one** selector type must be set. + +| Key | Required | Description | +| :---- | :---- | :---- | +| `networkSelectionType` | **Yes** | Enum that declares which selector below is populated. Common values: `NetworkAttachmentDefinitions`, `DefaultNetwork`, `SecondaryUserDefinedNetworks`, … | +| `networkAttachmentDefinitionSelector` | conditional | When `networkSelectionType=NetworkAttachmentDefinitions`. Selects NADs by **namespaceSelector** (required) *and* **networkSelector** (required). Both are ordinary `LabelSelectors`. | +| `secondaryUserDefinedNetworkSelector` | conditional | Used when `networkSelectionType=SecondaryUserDefinedNetworks`. Similar structure: required **namespaceSelector** & **networkSelector**. | +| `clusterUserDefinedNetworkSelector`, `primaryUserDefinedNetworkSelector` | conditional | Additional selector styles, each with required sub‑selectors as per the CRD. | + +**Typical usage** – `networkSelectionType: NetworkAttachmentDefinitions` + `networkAttachmentDefinitionSelector`. + +--- + +### **5.3 Inside an `egress[]` rule** + +| Field | Type | Required | Description | +| :---- | :---- | :---- | :---- | +| `dscp` | `int` (0 – 63) | **Yes** | DSCP value to stamp on the **inner** IP header. This value determines the traffic priority. | +| `bandwidth.rate` | `int` (kbps) | No | Sustained rate for the token-bucket policer (in kilobits per second). | +| `bandwidth.burst` | `int` (kilobits) | No | Maximum burst size that can accrue (in kilobits). | +| `classifier.to` / `classifier.from` | list `TrafficSelector` | No | CIDRs the packet destination (or source) must match. Each entry is an `ipBlock` supporting an `except` list. | +| `classifier.ports[]` | list | No | List of `{protocol, port}` tuples the packet must match; protocol is `TCP`, `UDP`, or `SCTP`. | + +If **all** specified classifier conditions match, the packet gets the DSCP mark and/or bandwidth policer defined above. This allows for fine-grained control over which traffic flows receive QoS treatment. diff --git a/mkdocs.yml b/mkdocs.yml index 87edfc2ba5..9fd08b2c08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,7 +126,9 @@ nav: - MultiNetworkPolicies: features/multiple-networks/multi-network-policies.md - MultiNetworkRails: features/multiple-networks/multi-vtep.md - Multicast: features/multicast.md - - NetworkQoS: features/network-qos.md + - NetworkQoS: + - Overview: features/network-qos.md + - Usage Guide: features/network-qos-guide.md - LiveMigration: features/live-migration.md - HybridOverlay: features/hybrid-overlay.md - Hardware Acceleration: From 5ea894c86885e777983f95ac1245eff4805bbe64 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Thu, 15 May 2025 17:01:48 +0300 Subject: [PATCH 057/278] contrib, kind.sh: Fix local registry when using podman On podman push, it defaults to secure connection. In our case the local registry uses an insecure connection result in podman push failures making it impossible to work with the local registry when podman is installed. Set podman to skip secure connection check when pushing OVN-K images to the local registry. Signed-off-by: Or Mergi --- contrib/kind.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index 8c3f6eca6d..145abc3c72 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -822,6 +822,12 @@ set_ovn_image() { } build_ovn_image() { + local push_args="" + if [ "$OCI_BIN" == "podman" ]; then + # docker doesn't perform tls check by default only podman does, hence we need to disable it for podman. + push_args="--tls-verify=false" + fi + if [ "$OVN_IMAGE" == local ]; then set_ovn_image @@ -834,14 +840,14 @@ build_ovn_image() { # store in local registry if [ "$KIND_LOCAL_REGISTRY" == true ];then echo "Pushing built image to local $OCI_BIN registry" - $OCI_BIN push "${OVN_IMAGE}" + $OCI_BIN push "$push_args" "$OVN_IMAGE" fi # We should push to local registry if image is not remote elif [ "${OVN_IMAGE}" != "" -a "${KIND_LOCAL_REGISTRY}" == true ] && (echo "$OVN_IMAGE" | grep / -vq); then local local_registry_ovn_image="localhost:5000/${OVN_IMAGE}" $OCI_BIN tag "$OVN_IMAGE" $local_registry_ovn_image OVN_IMAGE=$local_registry_ovn_image - $OCI_BIN push $OVN_IMAGE + $OCI_BIN push "$push_args" "$OVN_IMAGE" fi } From a1d47314593388f86851b32eacbde99f8cc069a0 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Thu, 15 May 2025 18:01:39 +0300 Subject: [PATCH 058/278] contrib,kind: Use skopeo to get the actual ovnkube-image digest When working with local registry, the automation inspect the built ovnkube-image digest (SHA) and pass it to the daemonset manifest, in order to ensure the latest built image is deployed. Some container runtime may not retain the same digest, result in having one image digest in the local runtime image and different one on the local registry. To avoid that and get the actual image digest that exist in the local registry, use skopeo to inspect the image and get the actual digest. This change introduce new dependency for the project. Signed-off-by: Or Mergi --- contrib/kind.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index 145abc3c72..fda6036d43 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -504,6 +504,11 @@ check_dependencies() { echo "Dependency not met: Neither docker nor podman found" exit 1 fi + + if command_exists podman && ! command_exists skopeo; then + echo "Dependency not met: skopeo not installed. Run the following command to install it: 'sudo dnf install skopeo'" + exit 1 + fi } OPENSSL="" @@ -854,8 +859,14 @@ build_ovn_image() { create_ovn_kube_manifests() { local ovnkube_image=${OVN_IMAGE} if [ "$KIND_LOCAL_REGISTRY" == true ];then - # When updating with local registry we have to reference the sha - ovnkube_image=$($OCI_BIN inspect --format='{{index .RepoDigests 0}}' $OVN_IMAGE) + # When updating with local registry we have to reference the image digest (SHA) + # Check the image digest in the local registry because it might be different then the digest in the local container runtime + if [ "$OCI_BIN" == "podman" ]; then + # due to differences how podman and docker persist images, for podman use skopeo to get the image and digest. + ovnkube_image=$(skopeo inspect --format "{{.Name}}@{{.Digest}}" --tls-verify=false "docker://$OVN_IMAGE") + else + ovnkube_image=$($OCI_BIN inspect --format='{{index .RepoDigests 0}}' $OVN_IMAGE) + fi fi pushd ${DIR}/../dist/images if [ "$OVN_ENABLE_INTERCONNECT" == true ]; then From 8a70c81d7bf7f1a91a8c0c2081f50a0f0b218272 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Mon, 16 Jun 2025 10:49:32 +0100 Subject: [PATCH 059/278] EIP OVN controller: stop pod config flap func was refactored erroneously when network comparisson was refactored. The if comparisson went from: !cachedNetwork.Equals(ni) to: util.AreNetworksCompatible(cachedNetwork, ni) Disruption can be seen for brief periods of time. Signed-off-by: Martin Kennelly --- go-controller/pkg/ovn/egressip.go | 2 +- go-controller/pkg/ovn/egressip_test.go | 178 +++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index e79b9b29c5..08b52dd281 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -1083,7 +1083,7 @@ func (e *EgressIPController) deletePodEgressIPAssignments(ni util.NetInfo, name func (e *EgressIPController) deletePreviousNetworkPodEgressIPAssignments(ni util.NetInfo, name string, statusesToRemove []egressipv1.EgressIPStatusItem, pod *corev1.Pod) { cachedNetwork := e.getNetworkFromPodAssignment(getPodKey(pod)) if cachedNetwork != nil { - if util.AreNetworksCompatible(cachedNetwork, ni) { + if !util.AreNetworksCompatible(cachedNetwork, ni) { if err := e.deletePodEgressIPAssignments(cachedNetwork, name, statusesToRemove, pod); err != nil { // no error is returned because high probability network is deleted klog.Errorf("Failed to delete EgressIP %s assignment for pod %s/%s attached to network %s: %v", diff --git a/go-controller/pkg/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go index b0e5ad142a..43ec170acb 100644 --- a/go-controller/pkg/ovn/egressip_test.go +++ b/go-controller/pkg/ovn/egressip_test.go @@ -3410,6 +3410,184 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" ) }) + ginkgo.Context("IPv4 on pod UPDATE", func() { + ginkgo.It("does not reconfigure or remove existing pod config if no change", func() { + config.OVNKubernetesFeature.EnableInterconnect = true + app.Action = func(*cli.Context) error { + egressPod := *newPodWithLabels(eipNamespace, podName, node1Name, podV4IP, egressPodLabel) + egressNamespace := newNamespace(eipNamespace) + nodeIPv4 := "192.168.126.210/24" + egressIP := net.ParseIP("192.168.126.211") + _, nodeSubnetV4, _ := net.ParseCIDR(v4Node1Subnet) + _, nodeSubnetV6, _ := net.ParseCIDR(v6Node1Subnet) + + annotations := map[string]string{ + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", nodeIPv4, ""), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\"}", v4Node1Subnet, v6Node1Subnet), + "k8s.ovn.org/node-transit-switch-port-ifaddr": "{\"ipv4\":\"100.88.0.2/16\", \"ipv6\": \"fd97::2/64\"}", + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIPv4), + "k8s.ovn.org/zone-name": node1Name, + } + node := getNodeObj(node1Name, annotations, map[string]string{}) // add node to avoid errori-ing out on transit switch IP fetch + fakeOvn.startWithDBSetup( + libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node.Name + "-UUID", + Name: "k8s-" + node.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV4).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV6).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node.Name + "-UUID", + Name: node.Name, + Ports: []string{"k8s-" + node.Name + "-UUID"}, + }, + }, + }, + &corev1.NamespaceList{ + Items: []corev1.Namespace{*egressNamespace}, + }, + &corev1.PodList{ + Items: []corev1.Pod{egressPod}, + }, + &corev1.NodeList{ + Items: []corev1.Node{node}, + }, + ) + + eIP := egressipv1.EgressIP{ + ObjectMeta: newEgressIPMeta(egressIPName), + Spec: egressipv1.EgressIPSpec{ + EgressIPs: []string{ + egressIP.String(), + }, + NamespaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": egressNamespace.Name, + }, + }, + PodSelector: metav1.LabelSelector{ + MatchLabels: egressPodLabel, + }, + }, + } + i, n, _ := net.ParseCIDR(podV4IP + "/23") + n.IP = i + fakeOvn.controller.logicalPortCache.add(&egressPod, "", types.DefaultNetworkName, "", nil, []*net.IPNet{n}) + err := fakeOvn.controller.WatchEgressIPNamespaces() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIPPods() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIP() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + fakeOvn.controller.eIPC.nodeZoneState.Store(nodeName, true) + _, err = fakeOvn.fakeClient.EgressIPClient.K8sV1().EgressIPs().Create(context.TODO(), &eIP, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + fakeOvn.patchEgressIPObj(node1Name, egressIPName, egressIP.String()) + gomega.Eventually(getEgressIPStatusLen(eIP.Name)).Should(gomega.Equal(1)) + + expectedDatabaseState := []libovsdbtest.TestData{ + getReRoutePolicy(egressPod.Status.PodIP, "4", "reroute-UUID", nodeLogicalRouterIPv4, + getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, IPFamilyValueV4, + types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName).GetExternalIDs()), + getEIPSNAT(podV4IP, egressPod.Namespace, egressPod.Name, egressIP.String(), "k8s-node1", DefaultNetworkControllerName), + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + Policies: []string{"reroute-UUID"}, + }, + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Nat: []string{"egressip-nat-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node.Name + "-UUID", + Name: "k8s-" + node.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV4).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(nodeSubnetV6).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node.Name + "-UUID", + Name: node.Name, + Ports: []string{"k8s-" + node.Name + "-UUID"}, + }, + } + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + // async, create no-op updates that trigger reconcile for the selected pod async but update should continue to select the pod and not alter pod config + // meanwhile we watch the ovn dbs and ensure they do not alter for the given pods eip config + // therefore spawn a go routine to update the k8 constructs that will trigger reconcile of the pods, and, we want to ensure nothing is reconfigured. + errCh := make(chan error, 2) + go func() { + ns, err := fakeOvn.fakeClient.KubeClient.CoreV1().Namespaces().Get(context.TODO(), egressNamespace.Name, metav1.GetOptions{}) + if err != nil { + errCh <- err + return + } + // add new namespace label. Does not affect pod selection for EIP + ns = ns.DeepCopy() + ns.Labels["newlabel"] = "noop" + _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Namespaces().Update(context.TODO(), ns, metav1.UpdateOptions{}) + if err != nil { + errCh <- err + return + } + // add new pod label. Does not affect pod selection for EIP + pod, err := fakeOvn.fakeClient.KubeClient.CoreV1().Pods(egressNamespace.Name).Get(context.TODO(), egressPod.Name, metav1.GetOptions{}) + if err != nil { + errCh <- err + return + } + pod = pod.DeepCopy() + pod.Labels["newlabel"] = "noop" + _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Pods(egressNamespace.Name).Update(context.TODO(), pod, metav1.UpdateOptions{}) + if err != nil { + errCh <- err + } + close(errCh) + }() + ginkgo.By("ensure OVN DB config for EIP remains consistent") + // ensure the DBs are unaltered + gomega.Consistently(fakeOvn.nbClient, 500*time.Millisecond, 1*time.Millisecond).WithTimeout(5 * time.Second).Should(libovsdbtest.HaveData(expectedDatabaseState)) + ginkgo.By("check for errors from goroutine updating namespace and pods") + select { + case err := <-errCh: + if err != nil { + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "must successfully update namespace and pods") + } + case <-time.After(100 * time.Millisecond): + // Updates completed successfully + } + return nil + } + err := app.Run([]string{app.Name}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) + ginkgo.Context("IPv6 on pod UPDATE", func() { ginkgo.DescribeTable("should remove OVN pod egress setup when EgressIP stops matching pod label", From db87df1d763800ede8a1cbf3f19fc068a4c0d1d4 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Thu, 26 Jun 2025 09:14:47 +0100 Subject: [PATCH 060/278] Layer 2 EIP: remove stale LRP if pod is remote For layer 2 support for EIP we always add a LRP to the GW router to provide load balancing (EIP HA) and pkt marking to support SNAT. For layer 2 connected pods selected by an EIP, and on the egress node, the controller may not delete GW LRP if the pod is remote. Signed-off-by: Martin Kennelly --- go-controller/pkg/ovn/egressip.go | 9 +- go-controller/pkg/ovn/egressip_udn_l2_test.go | 486 ++++++++++++++++++ 2 files changed, 494 insertions(+), 1 deletion(-) diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index 08b52dd281..d53ba5e633 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -2451,11 +2451,18 @@ func (e *EgressIPController) deletePodEgressIPAssignment(ni util.NetInfo, egress return err } var ops []ovsdb.Operation - if !loadedPodNode || isLocalZonePod { // node is deleted (we can't determine zone so we always try and nuke OR pod is local to zone) + // For CDN only, add SNATs to support external GW feature + if ni.IsDefault() && (!loadedPodNode || isLocalZonePod) { ops, err = e.addExternalGWPodSNATOps(ni, nil, pod.Namespace, pod.Name, status) if err != nil { return err } + } + // Following cases will ensure removal of a pod LRP + // Case 1 - node where pod is hosted is not known + // Case 2 - pod is within the local zone + // case 3 - a local zone node is egress node and pod is attached to layer 2. For layer2, there is always an LRP attached to the egress Node GW router + if !loadedPodNode || isLocalZonePod || (isLocalZoneEgressNode && ni.IsSecondary() && ni.TopologyType() == types.Layer2Topology) { ops, err = e.deleteReroutePolicyOps(ni, ops, status, egressIPName, nextHopIP, routerName, pod.Namespace, pod.Name) if errors.Is(err, libovsdbclient.ErrNotFound) { // if the gateway router join IP setup is already gone, then don't count it as error. diff --git a/go-controller/pkg/ovn/egressip_udn_l2_test.go b/go-controller/pkg/ovn/egressip_udn_l2_test.go index 23a930b2ef..c9080d6b71 100644 --- a/go-controller/pkg/ovn/egressip_udn_l2_test.go +++ b/go-controller/pkg/ovn/egressip_udn_l2_test.go @@ -2558,4 +2558,490 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol gomega.Expect(err).NotTo(gomega.HaveOccurred()) }) }) + + ginkgo.Context("Pod delete", func() { + ginkgo.It("should delete UDN and CDN config", func() { + // create a single EIP IP selecting multiple pods both local and remote. + // Delete pods and ensure OVN DB is as expected + app.Action = func(ctx *cli.Context) error { + // Node 1 is local, Node 2 is remote + egressIP := "192.168.126.101" + node1IPv4 := "192.168.126.202" + node1IPv4CIDR := node1IPv4 + "/24" + node2IPv4 := "192.168.126.51" + node2IPv4CIDR := node2IPv4 + "/24" + _, node1CDNSubnet, _ := net.ParseCIDR(v4Node1Subnet) + _, node1UDNSubnet, _ := net.ParseCIDR(v4Node1Net1) + nadName := util.GetNADName(eipNamespace2, nadName1) + egressCDNNamespace := newNamespaceWithLabels(eipNamespace, egressPodLabel) + egressUDNNamespace := newUDNNamespaceWithLabels(eipNamespace2, egressPodLabel) + egressPodCDNLocal := *newPodWithLabels(eipNamespace, podName, node1Name, podV4IP, egressPodLabel) + egressPodUDNLocal := *newPodWithLabels(eipNamespace2, podName2, node1Name, v4Pod1IPNode1Net1, egressPodLabel) + egressPodCDNRemote := *newPodWithLabels(eipNamespace, podName3, node2Name, podV4IP2, egressPodLabel) + setPrimaryNetworkAnnot(&egressPodCDNRemote, ovntypes.DefaultNetworkName, fmt.Sprintf("%s%s", podV4IP2, util.GetIPFullMaskString(podV4IP2))) + egressPodUDNRemote := *newPodWithLabels(eipNamespace2, podName4, node2Name, v4Pod2IPNode2Net1, egressPodLabel) + setPrimaryNetworkAnnot(&egressPodUDNRemote, nadName, fmt.Sprintf("%s%s", v4Pod2IPNode2Net1, util.GetIPFullMaskString(v4Pod2IPNode2Net1))) + netconf := ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{ + Name: networkName1, + Type: "ovn-k8s-cni-overlay", + }, + Role: ovntypes.NetworkRolePrimary, + Topology: ovntypes.Layer2Topology, + NADName: nadName, + Subnets: v4Net1, + } + nad, err := newNetworkAttachmentDefinition( + eipNamespace2, + nadName1, + netconf, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + nad.Annotations = map[string]string{ovntypes.OvnNetworkIDAnnotation: secondaryNetworkID} + netInfo, err := util.NewNetInfo(&netconf) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + node1Annotations := map[string]string{ + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", node1IPv4CIDR, ""), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\":\"%s\"}", v4Node1Subnet, networkName1, v4Node1Net1), + "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node1Tsp), + "k8s.ovn.org/zone-name": node1Name, + "k8s.ovn.org/node-chassis-id": "473ca66d-d800-472f-b289-1ab81ae7f21c", + "k8s.ovn.org/remote-zone-migrated": node1Name, + "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\"},\"%s\":{\"ipv4\":\"%s\"}}", node1DefaultRtoJIPCIDR, networkName1, node1Network1RtoSIPCIDR), + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node1IPv4CIDR), + util.OvnNodeL3GatewayConfig: fmt.Sprintf(`{"%s":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"%s", "next-hop":"%s", "next-hops": ["%s"]}, +"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop": "192.168.126.1", "next-hops": ["192.168.126.1"]}}`, networkName1, v4Net1, gwIP, gwIP), + } + labels := map[string]string{ + "k8s.ovn.org/egress-assignable": "", + } + node1 := getNodeObj(node1Name, node1Annotations, labels) + node2Annotations := map[string]string{ + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", node2IPv4CIDR, ""), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":\"%s\",\"%s\":\"%s\"}", v4Node2Subnet, networkName1, v4Node2Net1), + "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s/16\"}", v4Node2Tsp), + "k8s.ovn.org/zone-name": node2Name, + "k8s.ovn.org/node-chassis-id": "473ca66d-d800-472f-b289-1ab81ae7f21c", + "k8s.ovn.org/remote-zone-migrated": node2Name, + "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\"},\"%s\":{\"ipv4\":\"%s\"}}", node2DefaultRtoJIPCIDR, networkName1, node2Network1RtoSIPCIDR), + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", node2IPv4CIDR), + util.OvnNodeL3GatewayConfig: fmt.Sprintf(`{"%s":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"%s", "next-hop":"%s", "next-hops": ["%s"]}, +"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop": "192.168.126.1", "next-hops": ["192.168.126.1"]}}`, networkName1, v4Net1, gwIP, gwIP), + } + node2 := getNodeObj(node2Name, node2Annotations, nil) + oneNodeStatus := []egressipv1.EgressIPStatusItem{ + { + Node: node1Name, + EgressIP: egressIP, + }, + } + eIP := egressipv1.EgressIP{ + ObjectMeta: newEgressIPMetaWithMark(egressIPName, eIP1Mark), + Spec: egressipv1.EgressIPSpec{ + EgressIPs: []string{egressIP}, + PodSelector: metav1.LabelSelector{ + MatchLabels: egressPodLabel, + }, + NamespaceSelector: metav1.LabelSelector{ + MatchLabels: egressPodLabel, + }, + }, + Status: egressipv1.EgressIPStatus{ + Items: oneNodeStatus, + }, + } + + initialDB := []libovsdbtest.TestData{ + //CDN start + &nbdb.LogicalRouterPort{ + UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name, + Networks: []string{node1DefaultRtoJIPCIDR}, + }, + &nbdb.LogicalRouter{ + Name: ovntypes.OVNClusterRouter, + UUID: ovntypes.OVNClusterRouter + "-UUID", + }, + &nbdb.LogicalRouter{ + Name: ovntypes.GWRouterPrefix + node1.Name, + UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1Name + "-UUID", + Name: "k8s-" + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1Name + "-UUID", + Name: node1Name, + Ports: []string{"k8s-" + node1Name + "-UUID"}, + }, + // UDN start + &nbdb.LogicalRouterPort{ + UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID", + Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName, + Networks: []string{node1Network1RtoSIPCIDR}, + }, + &nbdb.LogicalRouter{ + UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", + Name: netInfo.GetNetworkScopedGWRouterName(node1.Name), + Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: networkName1, ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + networkName1_ + node1Name + "-UUID", + Name: "k8s-" + networkName1_ + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: netInfo.GetNetworkScopedSwitchName(node1.Name) + "-UUID", + Name: netInfo.GetNetworkScopedSwitchName(node1.Name), + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: networkName1, ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + }, + } + fakeOvn.startWithDBSetup( + libovsdbtest.TestSetup{ + NBData: initialDB, + }, + &corev1.NodeList{ + Items: []corev1.Node{node1, node2}, + }, + &corev1.NamespaceList{ + Items: []corev1.Namespace{*egressCDNNamespace, *egressUDNNamespace}, + }, + &corev1.PodList{ + Items: []corev1.Pod{egressPodCDNLocal, egressPodUDNLocal, egressPodCDNRemote, egressPodUDNRemote}, + }, + &nadv1.NetworkAttachmentDefinitionList{ + Items: []nadv1.NetworkAttachmentDefinition{*nad}, + }, + &egressipv1.EgressIPList{ + Items: []egressipv1.EgressIP{}, + }, + ) + asf := addressset.NewOvnAddressSetFactory(fakeOvn.nbClient, true, false) + // watch EgressIP depends on UDN enabled svcs address set being available + c := udnenabledsvc.NewController(fakeOvn.nbClient, asf, fakeOvn.controller.watchFactory.ServiceCoreInformer(), []string{}) + go func() { + gomega.Expect(c.Run(ctx.Done())).Should(gomega.Succeed()) + }() + // Add CDN pod IPs to CDN cache + iCDN, nCDN, _ := net.ParseCIDR(podV4IP + "/23") + nCDN.IP = iCDN + fakeOvn.controller.logicalPortCache.add(&egressPodCDNLocal, "", ovntypes.DefaultNetworkName, "", nil, []*net.IPNet{nCDN}) + fakeOvn.controller.zone = node1Name + fakeOvn.eIPController.zone = node1Name + secConInfo, ok := fakeOvn.secondaryControllers[networkName1] + gomega.Expect(ok).To(gomega.BeTrue()) + err = fakeOvn.eIPController.SyncLocalNodeZonesCache() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.networkManager.Start() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + defer fakeOvn.networkManager.Stop() + err = fakeOvn.controller.WatchEgressNodes() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIPNamespaces() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIPPods() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIP() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // Add pod IPs to UDN cache + iUDN, nUDN, _ := net.ParseCIDR(v4Pod1IPNode1Net1 + "/23") + nUDN.IP = iUDN + secConInfo.bnc.logicalPortCache.add(&egressPodUDNLocal, "", util.GetNADName(nad.Namespace, nad.Name), "", nil, []*net.IPNet{nUDN}) + _, err = fakeOvn.fakeClient.EgressIPClient.K8sV1().EgressIPs().Create(context.TODO(), &eIP, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + egressSVCServedPodsASv4, _ := buildEgressServiceAddressSets(nil) + egressIPServedPodsASCDNv4, _ := buildEgressIPServedPodsAddressSets([]string{podV4IP}, ovntypes.DefaultNetworkName, DefaultNetworkControllerName) + egressNodeIPsASv4, _ := buildEgressIPNodeAddressSets([]string{node1IPv4, node2IPv4}) + egressIPServedPodsASUDNv4, _ := buildEgressIPServedPodsAddressSetsForController([]string{v4Pod1IPNode1Net1}, netInfo.GetNetworkName(), DefaultNetworkControllerName) + gomega.Eventually(c.IsAddressSetAvailable).Should(gomega.BeTrue()) + dbIDs := udnenabledsvc.GetAddressSetDBIDs() + udnEnabledSvcV4, _ := addressset.GetTestDbAddrSets(dbIDs, []string{}) + node1LRP := "k8s-node1" + expectedDatabaseState := []libovsdbtest.TestData{ + // CDN + getReRouteStaticRoute(v4ClusterSubnet, node1DefaultRtoJIP), + getReRoutePolicy(podV4IP, "4", "reroute-UUID", []string{node1DefaultRtoJIP}, + getEgressIPLRPReRouteDbIDs(eIP.Name, egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, v4ClusterSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "default-no-reroute-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, config.Gateway.V4JoinSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "no-reroute-service-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouter{ + Name: ovntypes.GWRouterPrefix + node1.Name, + UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"}, + Nat: []string{"egressip-nat-UUID", "egressip-nat2-UUID"}, + }, + &nbdb.LogicalRouter{ + Name: ovntypes.OVNClusterRouter, + UUID: ovntypes.OVNClusterRouter + "-UUID", + Policies: []string{"default-no-reroute-UUID", "no-reroute-service-UUID", + "default-no-reroute-node-UUID", "default-no-reroute-reply-traffic", "reroute-UUID"}, + StaticRoutes: []string{"reroute-static-route-UUID"}, + }, + &nbdb.LogicalRouterPort{ + UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name, + Networks: []string{node1DefaultRtoJIPCIDR}, + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s", + egressIPServedPodsASCDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "default-no-reroute-node-UUID", + Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark}, + ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1Name + "-UUID", + Name: "k8s-" + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1Name + "-UUID", + Name: node1Name, + Ports: []string{"k8s-" + node1Name + "-UUID"}, + QOSRules: []string{"default-QoS-UUID"}, + }, + &nbdb.NAT{ + UUID: "egressip-nat-UUID", + LogicalIP: podV4IP2, + ExternalIP: egressIP, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPodCDNRemote.Namespace, egressPodCDNRemote.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1LRP, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.NAT{ + UUID: "egressip-nat2-UUID", + LogicalIP: podV4IP, + ExternalIP: egressIP, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1LRP, + Options: map[string]string{ + "stateless": "false", + }, + }, + getNoReRouteReplyTrafficPolicy(ovntypes.DefaultNetworkName, DefaultNetworkControllerName), + getDefaultQoSRule(false, ovntypes.DefaultNetworkName, DefaultNetworkControllerName), + egressSVCServedPodsASv4, + egressIPServedPodsASCDNv4, + egressNodeIPsASv4, + + // UDN + getReRoutePolicyForController(egressIPName, eipNamespace2, podName2, v4Pod1IPNode1Net1, eIP1Mark, IPFamilyValueV4, []string{gwIP}, netInfo.GetNetworkName(), DefaultNetworkControllerName), + getReRoutePolicyForController(egressIPName, eipNamespace2, podName4, v4Pod2IPNode2Net1, eIP1Mark, IPFamilyValueV4, []string{gwIP}, netInfo.GetNetworkName(), DefaultNetworkControllerName), + getNoReRoutePolicyForUDNEnabledSvc(false, netInfo.GetNetworkName(), DefaultNetworkControllerName, egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, udnEnabledSvcV4.Name), + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, v4Net1), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-default-no-reroute-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, config.Gateway.V4JoinSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-no-reroute-service-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s", + egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-default-no-reroute-node-UUID", + Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark}, + ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPort{ + UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID", + Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName, + Networks: []string{node1Network1RtoSIPCIDR}, + }, + &nbdb.LogicalRouter{ + UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", + Name: netInfo.GetNetworkScopedGWRouterName(node1.Name), + Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + Policies: []string{"udn-default-no-reroute-node-UUID", "udn-default-no-reroute-UUID", + "udn-no-reroute-service-UUID", "udn-enabled-svc-no-reroute-UUID", + fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()), + getReRoutePolicyUUID(eipNamespace2, podName2, IPFamilyValueV4, netInfo.GetNetworkName()), + getReRoutePolicyUUID(eipNamespace2, podName4, IPFamilyValueV4, netInfo.GetNetworkName()), + }, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + networkName1_ + node1Name + "-UUID", + Name: "k8s-" + networkName1_ + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: networkName1_ + layer2SwitchName + "-UUID", + Name: networkName1_ + layer2SwitchName, + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())}, + }, + getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), + getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName), + egressIPServedPodsASUDNv4, + udnEnabledSvcV4, + } + ginkgo.By("ensure expected equals actual") + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + ginkgo.By("deleting all EgressIP seelected pods") + deletePod(egressPodCDNLocal.Namespace, egressPodCDNLocal.Name, fakeOvn.fakeClient.KubeClient) + deletePod(egressPodCDNRemote.Namespace, egressPodCDNRemote.Name, fakeOvn.fakeClient.KubeClient) + deletePod(egressPodUDNLocal.Namespace, egressPodUDNLocal.Name, fakeOvn.fakeClient.KubeClient) + deletePod(egressPodUDNRemote.Namespace, egressPodUDNRemote.Name, fakeOvn.fakeClient.KubeClient) + + ginkgo.By("ensure OVN config is removed for the deleted pods") + egressIPServedPodsASCDNv4, _ = buildEgressIPServedPodsAddressSets([]string{}, ovntypes.DefaultNetworkName, DefaultNetworkControllerName) + egressIPServedPodsASUDNv4, _ = buildEgressIPServedPodsAddressSetsForController([]string{}, netInfo.GetNetworkName(), DefaultNetworkControllerName) + expectedDatabaseState = []libovsdbtest.TestData{ + // CDN + getReRouteStaticRoute(v4ClusterSubnet, node1DefaultRtoJIP), + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, v4ClusterSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "default-no-reroute-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4ClusterSubnet, config.Gateway.V4JoinSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "no-reroute-service-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouter{ + Name: ovntypes.GWRouterPrefix + node1.Name, + UUID: ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Ports: []string{ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID"}, + }, + &nbdb.LogicalRouter{ + Name: ovntypes.OVNClusterRouter, + UUID: ovntypes.OVNClusterRouter + "-UUID", + Policies: []string{"default-no-reroute-UUID", "no-reroute-service-UUID", + "default-no-reroute-node-UUID", "default-no-reroute-reply-traffic"}, + StaticRoutes: []string{"reroute-static-route-UUID"}, + }, + &nbdb.LogicalRouterPort{ + UUID: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name + "-UUID", + Name: ovntypes.GWRouterToJoinSwitchPrefix + ovntypes.GWRouterPrefix + node1.Name, + Networks: []string{node1DefaultRtoJIPCIDR}, + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s", + egressIPServedPodsASCDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "default-no-reroute-node-UUID", + Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark}, + ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, ovntypes.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1Name + "-UUID", + Name: "k8s-" + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1CDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1Name + "-UUID", + Name: node1Name, + Ports: []string{"k8s-" + node1Name + "-UUID"}, + QOSRules: []string{"default-QoS-UUID"}, + }, + getNoReRouteReplyTrafficPolicy(ovntypes.DefaultNetworkName, DefaultNetworkControllerName), + getDefaultQoSRule(false, ovntypes.DefaultNetworkName, DefaultNetworkControllerName), + egressSVCServedPodsASv4, + egressIPServedPodsASCDNv4, + egressNodeIPsASv4, + + // UDN + getNoReRoutePolicyForUDNEnabledSvc(false, netInfo.GetNetworkName(), DefaultNetworkControllerName, egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, udnEnabledSvcV4.Name), + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, v4Net1), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-default-no-reroute-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToPodDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("ip4.src == %s && ip4.dst == %s", v4Net1, config.Gateway.V4JoinSubnet), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-no-reroute-service-UUID", + ExternalIDs: getEgressIPLRPNoReRoutePodToJoinDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPolicy{ + Priority: ovntypes.DefaultNoRereoutePriority, + Match: fmt.Sprintf("(ip4.src == $%s || ip4.src == $%s) && ip4.dst == $%s", + egressIPServedPodsASUDNv4.Name, egressSVCServedPodsASv4.Name, egressNodeIPsASv4.Name), + Action: nbdb.LogicalRouterPolicyActionAllow, + UUID: "udn-default-no-reroute-node-UUID", + Options: map[string]string{"pkt_mark": ovntypes.EgressIPNodeConnectionMark}, + ExternalIDs: getEgressIPLRPNoReRoutePodToNodeDbIDs(IPFamilyValueV4, netInfo.GetNetworkName(), DefaultNetworkControllerName).GetExternalIDs(), + }, + &nbdb.LogicalRouterPort{ + UUID: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID", + Name: ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName, + Networks: []string{node1Network1RtoSIPCIDR}, + }, + &nbdb.LogicalRouter{ + UUID: netInfo.GetNetworkScopedGWRouterName(node1.Name) + "-UUID", + Name: netInfo.GetNetworkScopedGWRouterName(node1.Name), + Ports: []string{ovntypes.RouterToSwitchPrefix + networkName1_ + layer2SwitchName + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + Policies: []string{"udn-default-no-reroute-node-UUID", "udn-default-no-reroute-UUID", + "udn-no-reroute-service-UUID", "udn-enabled-svc-no-reroute-UUID", + fmt.Sprintf("%s-no-reroute-reply-traffic", netInfo.GetNetworkName()), + }, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + networkName1_ + node1Name + "-UUID", + Name: "k8s-" + networkName1_ + node1Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1UDNSubnet).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: networkName1_ + layer2SwitchName + "-UUID", + Name: networkName1_ + layer2SwitchName, + Ports: []string{"k8s-" + networkName1_ + node1Name + "-UUID"}, + ExternalIDs: map[string]string{ovntypes.NetworkExternalID: netInfo.GetNetworkName(), ovntypes.TopologyExternalID: ovntypes.Layer2Topology}, + QOSRules: []string{fmt.Sprintf("%s-QoS-UUID", netInfo.GetNetworkName())}, + }, + getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), + getDefaultQoSRule(false, netInfo.GetNetworkName(), DefaultNetworkControllerName), + egressIPServedPodsASUDNv4, + udnEnabledSvcV4, + } + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + return nil + } + err := app.Run([]string{app.Name}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) }) From c58c193ed3f6c8e951361e8c8e1e0b63e8d26ab4 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 1 Jul 2025 16:13:56 +0200 Subject: [PATCH 061/278] Disable Layer2 IGMP test as it is broken now. Signed-off-by: Nadia Pinaeva --- test/e2e/network_segmentation.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index a3105f2ab0..cc8216379d 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -875,12 +875,13 @@ var _ = Describe("Network Segmentation", func() { cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }), - ginkgo.Entry("with primary layer2 UDN", networkAttachmentConfigParams{ - name: nadName, - topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), - role: "primary", - }), + // TODO: this test is broken, see https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5309 + //ginkgo.Entry("with primary layer2 UDN", networkAttachmentConfigParams{ + // name: nadName, + // topology: "layer2", + // cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + // role: "primary", + //}), ) }) }) From 1ea27391de74c09fff98f2515b46ec381fbcf955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 18 Jun 2025 11:32:01 +0000 Subject: [PATCH 062/278] Revert "Add the IP rule for a UDN only when it is advertised to the default VRF" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit deff5e64ccc6069437bf7abf08f61522f73501a1. Breaks traffic flows to KAPI, DNS on VRF-Lite scenarios. Requires and SNAT that is being worked on [1]. 1. https://issues.redhat.com/browse/OCPBUGS-56506?focusedId=27440592&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-27440592 Signed-off-by: Jaime CaamaƱo Ruiz --- go-controller/pkg/node/gateway_udn.go | 194 ++++++++------------ go-controller/pkg/node/gateway_udn_test.go | 200 +-------------------- 2 files changed, 79 insertions(+), 315 deletions(-) diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 7ab5b50cc9..3e2ff143c9 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -90,10 +90,6 @@ type UserDefinedNetworkGateway struct { // gwInterfaceIndex holds the link index of gateway interface gwInterfaceIndex int - - // save BGP state at the start of reconciliation loop run to handle it consistently throughout the run - isNetworkAdvertisedToDefaultVRF bool - isNetworkAdvertised bool } // UTILS Needed for UDN (also leveraged for default netInfo) in bridgeConfiguration @@ -371,18 +367,18 @@ func (udng *UserDefinedNetworkGateway) AddNetwork() error { return fmt.Errorf("could not add VRF %s routes for network %s, err: %v", vrfDeviceName, udng.GetNetworkName(), err) } - udng.updateAdvertisementStatus() + isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) // create the iprules for this network - if err = udng.updateUDNVRFIPRules(); err != nil { + if err = udng.updateUDNVRFIPRules(isNetworkAdvertised); err != nil { return fmt.Errorf("failed to update IP rules for network %s: %w", udng.GetNetworkName(), err) } - if err = udng.updateAdvertisedUDNIsolationRules(); err != nil { + if err = udng.updateAdvertisedUDNIsolationRules(isNetworkAdvertised); err != nil { return fmt.Errorf("failed to update isolation rules for network %s: %w", udng.GetNetworkName(), err) } - if err := udng.updateUDNVRFIPRoute(); err != nil { + if err := udng.updateUDNVRFIPRoute(isNetworkAdvertised); err != nil { return fmt.Errorf("failed to update ip routes for network %s: %w", udng.GetNetworkName(), err) } @@ -460,16 +456,18 @@ func (udng *UserDefinedNetworkGateway) DelNetwork() error { } } - err := udng.deleteAdvertisedUDNIsolationRules() - if err != nil { - return fmt.Errorf("failed to remove advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) + if util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) { + err := udng.updateAdvertisedUDNIsolationRules(false) + if err != nil { + return fmt.Errorf("failed to remove advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) + } } if err := udng.delMarkChain(); err != nil { return err } // delete the management port interface for this network - err = udng.deleteUDNManagementPort() + err := udng.deleteUDNManagementPort() if err != nil { return err } @@ -627,7 +625,8 @@ func (udng *UserDefinedNetworkGateway) computeRoutesForUDN(mpLink netlink.Link) // Route2: Add default route: default via 172.18.0.1 dev breth0 mtu 1400 // necessary for UDN CNI and host-networked pods default traffic to go to node's gatewayIP - defaultRoute, err := udng.getDefaultRouteWithAdvertisedCheck() + isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) + defaultRoute, err := udng.getDefaultRoute(isNetworkAdvertised) if err != nil { return nil, fmt.Errorf("unable to add default route for network %s, err: %v", udng.GetNetworkName(), err) } @@ -728,7 +727,15 @@ func (udng *UserDefinedNetworkGateway) computeRoutesForUDN(mpLink netlink.Link) return retVal, nil } -func (udng *UserDefinedNetworkGateway) getDefaultRoute() ([]netlink.Route, error) { +func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) ([]netlink.Route, error) { + vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) + // If the network is advertised on a non default VRF then we should only consider routes received from external BGP + // device and not send any traffic based on default route similar to one present in default VRF. This is more important + // for VRF-Lite usecase where we need traffic to leave from vlan device instead of default gateway interface. + if isNetworkAdvertised && !slices.Contains(vrfs, types.DefaultNetworkName) { + return nil, nil + } + networkMTU := udng.NetInfo.MTU() if networkMTU == 0 { networkMTU = config.Default.MTU @@ -753,16 +760,6 @@ func (udng *UserDefinedNetworkGateway) getDefaultRoute() ([]netlink.Route, error return retVal, nil } -func (udng *UserDefinedNetworkGateway) getDefaultRouteWithAdvertisedCheck() ([]netlink.Route, error) { - // If the network is advertised on a non default VRF then we should only consider routes received from external BGP - // device and not send any traffic based on default route similar to one present in default VRF. This is more important - // for VRF-Lite usecase where we need traffic to leave from vlan device instead of default gateway interface. - if udng.isNetworkAdvertised && !udng.isNetworkAdvertisedToDefaultVRF { - return nil, nil - } - return udng.getDefaultRoute() -} - // getV4MasqueradeIP returns the V4 management port masqueradeIP for this network func (udng *UserDefinedNetworkGateway) getV4MasqueradeIP() (*net.IPNet, error) { if !config.IPv4Mode { @@ -795,15 +792,12 @@ func (udng *UserDefinedNetworkGateway) getV6MasqueradeIP() (*net.IPNet, error) { // 2000: from all to 169.254.0.12 lookup 1007 // 2000: from all fwmark 0x1002 lookup 1009 // 2000: from all to 169.254.0.14 lookup 1009 -// If the network is advertised to the default VRF, an example of the rules we set for a network is: +// If the network is advertised, an example of the rules we set for a network is: // 2000: from all fwmark 0x1001 lookup 1007 // 2000: from all to 10.132.0.0/14 lookup 1007 // 2000: from all fwmark 0x1001 lookup 1009 // 2000: from all to 10.134.0.0/14 lookup 1009 -// If the network is advertised ot a non-default VRF, an example of the rules we set for a network is: -// 2000: from all fwmark 0x1001 lookup 1007 -// 2000: from all fwmark 0x1001 lookup 1009 -func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules() ([]netlink.Rule, []netlink.Rule, error) { +func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules(isNetworkAdvertised bool) ([]netlink.Rule, []netlink.Rule, error) { var addIPRules []netlink.Rule var delIPRules []netlink.Rule var masqIPRules []netlink.Rule @@ -836,18 +830,12 @@ func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules() ([]netlink.Rule, } } switch { - case udng.isNetworkAdvertisedToDefaultVRF: - // the network is advertised to the default VRF - delIPRules = append(delIPRules, masqIPRules...) - addIPRules = append(addIPRules, subnetIPRules...) - case udng.isNetworkAdvertised: - // the network is advertised to a non-default VRF - delIPRules = append(delIPRules, masqIPRules...) + case !isNetworkAdvertised: + addIPRules = append(addIPRules, masqIPRules...) delIPRules = append(delIPRules, subnetIPRules...) default: - // the network is not advertised - delIPRules = append(delIPRules, subnetIPRules...) - addIPRules = append(addIPRules, masqIPRules...) + addIPRules = append(addIPRules, subnetIPRules...) + delIPRules = append(delIPRules, masqIPRules...) } return addIPRules, delIPRules, nil } @@ -945,20 +933,19 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { return fmt.Errorf("openflow manager with default bridge configuration has not been provided for network %s", udng.GetNetworkName()) } - udng.updateAdvertisementStatus() - // update bridge configuration + isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) netConfig := udng.openflowManager.defaultBridge.getNetworkBridgeConfig(udng.GetNetworkName()) if netConfig == nil { return fmt.Errorf("missing bridge configuration for network %s", udng.GetNetworkName()) } - netConfig.advertised.Store(udng.isNetworkAdvertised) + netConfig.advertised.Store(isNetworkAdvertised) - if err := udng.updateUDNVRFIPRules(); err != nil { + if err := udng.updateUDNVRFIPRules(isNetworkAdvertised); err != nil { return fmt.Errorf("error while updating ip rule for UDN %s: %s", udng.GetNetworkName(), err) } - if err := udng.updateUDNVRFIPRoute(); err != nil { + if err := udng.updateUDNVRFIPRoute(isNetworkAdvertised); err != nil { return fmt.Errorf("error while updating ip route for UDN %s: %s", udng.GetNetworkName(), err) } @@ -972,16 +959,16 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { // let's sync these flows immediately udng.openflowManager.requestFlowSync() - if err := udng.updateAdvertisedUDNIsolationRules(); err != nil { + if err := udng.updateAdvertisedUDNIsolationRules(isNetworkAdvertised); err != nil { return fmt.Errorf("error while updating advertised UDN isolation rules for network %s: %w", udng.GetNetworkName(), err) } return nil } // updateUDNVRFIPRules updates IP rules for a network depending on whether the -// network is advertised to the default VRF or not -func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules() error { - addIPRules, deleteIPRules, err := udng.constructUDNVRFIPRules() +// network is advertised or not +func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules(isNetworkAdvertised bool) error { + addIPRules, deleteIPRules, err := udng.constructUDNVRFIPRules(isNetworkAdvertised) if err != nil { return fmt.Errorf("unable to get iprules for network %s, err: %v", udng.GetNetworkName(), err) } @@ -1000,40 +987,30 @@ func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRules() error { } // Add or remove default route from a vrf device based on the network is -// advertised on its own network or the default network -func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRoute() error { - vrfName := util.GetNetworkVRFName(udng.NetInfo) - - switch { - case udng.isNetworkAdvertised && !udng.isNetworkAdvertisedToDefaultVRF: - // Remove default route for networks advertised to non-default VRF +// advertised on its own network or default network +func (udng *UserDefinedNetworkGateway) updateUDNVRFIPRoute(isNetworkAdvertised bool) error { + vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) + if isNetworkAdvertised && !slices.Contains(vrfs, types.DefaultNetworkName) { if err := udng.removeDefaultRouteFromVRF(); err != nil { - return fmt.Errorf("failed to remove default route from VRF %s for network %s: %v", - vrfName, udng.GetNetworkName(), err) + return fmt.Errorf("error while removing default route from VRF %s corresponding to network %s: %s", + util.GetNetworkVRFName(udng.NetInfo), udng.GetNetworkName(), err) } - - default: - // Add default route for networks that are either: - // - not advertised - // - advertised to default VRF - defaultRoute, err := udng.getDefaultRouteWithAdvertisedCheck() + } else if !isNetworkAdvertised || slices.Contains(vrfs, types.DefaultNetworkName) { + defaultRoute, err := udng.getDefaultRoute(isNetworkAdvertised) if err != nil { - return fmt.Errorf("failed to get default route for network %s: %v", - udng.GetNetworkName(), err) + return fmt.Errorf("unable to get default route for network %s, err: %v", udng.GetNetworkName(), err) } - - if err = udng.vrfManager.AddVRFRoutes(vrfName, defaultRoute); err != nil { - return fmt.Errorf("failed to add default route to VRF %s for network %s: %v", - vrfName, udng.GetNetworkName(), err) + if err = udng.vrfManager.AddVRFRoutes(util.GetNetworkVRFName(udng.NetInfo), defaultRoute); err != nil { + return fmt.Errorf("error while adding default route to VRF %s corresponding to network %s, err: %v", + util.GetNetworkVRFName(udng.NetInfo), udng.GetNetworkName(), err) } } - return nil } func (udng *UserDefinedNetworkGateway) removeDefaultRouteFromVRF() error { vrfDeviceName := util.GetNetworkVRFName(udng.NetInfo) - defaultRoute, err := udng.getDefaultRoute() + defaultRoute, err := udng.getDefaultRoute(false) if err != nil { return fmt.Errorf("unable to get default route for network %s, err: %v", udng.GetNetworkName(), err) } @@ -1062,22 +1039,39 @@ func (udng *UserDefinedNetworkGateway) removeDefaultRouteFromVRF() error { // comment "advertised UDNs V4 subnets" // elements = { 10.10.0.0/16 comment "cluster_udn_l3network" } // } -func (udng *UserDefinedNetworkGateway) updateAdvertisedUDNIsolationRules() error { - switch { - case udng.isNetworkAdvertised: - return udng.addAdvertisedUDNIsolationRules() - default: - return udng.deleteAdvertisedUDNIsolationRules() - } -} - -func (udng *UserDefinedNetworkGateway) addAdvertisedUDNIsolationRules() error { +func (udng *UserDefinedNetworkGateway) updateAdvertisedUDNIsolationRules(isNetworkAdvertised bool) error { nft, err := nodenft.GetNFTablesHelper() if err != nil { return fmt.Errorf("failed to get nftables helper: %v", err) } tx := nft.NewTransaction() + if !isNetworkAdvertised { + existingV4, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) + if err != nil { + if !knftables.IsNotFound(err) { + return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV4, err) + } + } + existingV6, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV6) + if err != nil { + if !knftables.IsNotFound(err) { + return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV6, err) + } + } + + for _, elem := range append(existingV4, existingV6...) { + if elem.Comment != nil && *elem.Comment == udng.GetNetworkName() { + tx.Delete(elem) + } + } + + if tx.NumOperations() == 0 { + return nil + } + return nft.Run(context.TODO(), tx) + } + for _, udnNet := range udng.Subnets() { set := nftablesAdvertisedUDNsSetV4 if utilnet.IsIPv6CIDR(udnNet.CIDR) { @@ -1096,41 +1090,3 @@ func (udng *UserDefinedNetworkGateway) addAdvertisedUDNIsolationRules() error { } return nft.Run(context.TODO(), tx) } - -func (udng *UserDefinedNetworkGateway) deleteAdvertisedUDNIsolationRules() error { - nft, err := nodenft.GetNFTablesHelper() - if err != nil { - return fmt.Errorf("failed to get nftables helper: %v", err) - } - tx := nft.NewTransaction() - - existingV4, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) - if err != nil { - if !knftables.IsNotFound(err) { - return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV4, err) - } - } - existingV6, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV6) - if err != nil { - if !knftables.IsNotFound(err) { - return fmt.Errorf("could not list existing items in %s set: %w", nftablesAdvertisedUDNsSetV6, err) - } - } - - for _, elem := range append(existingV4, existingV6...) { - if elem.Comment != nil && *elem.Comment == udng.GetNetworkName() { - tx.Delete(elem) - } - } - - if tx.NumOperations() == 0 { - return nil - } - return nft.Run(context.TODO(), tx) -} - -func (udng *UserDefinedNetworkGateway) updateAdvertisementStatus() { - vrfs := udng.GetPodNetworkAdvertisedOnNodeVRFs(udng.node.Name) - udng.isNetworkAdvertised = len(vrfs) > 0 - udng.isNetworkAdvertisedToDefaultVRF = slices.Contains(vrfs, types.DefaultNetworkName) -} diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 8c38c7ec5b..9f66247599 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -1754,7 +1754,7 @@ func TestConstructUDNVRFIPRules(t *testing.T) { }) g.Expect(err).NotTo(HaveOccurred()) udnGateway.vrfTableId = test.vrftableID - rules, delRules, err := udnGateway.constructUDNVRFIPRules() + rules, delRules, err := udnGateway.constructUDNVRFIPRules(false) g.Expect(err).ToNot(HaveOccurred()) for i, rule := range rules { g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) @@ -1776,7 +1776,7 @@ func TestConstructUDNVRFIPRules(t *testing.T) { } } -func TestConstructUDNVRFIPRulesPodNetworkAdvertisedToTheDefaultNetwork(t *testing.T) { +func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { type testRule struct { priority int family int @@ -1941,198 +1941,7 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertisedToTheDefaultNetwork(t *testin }) g.Expect(err).NotTo(HaveOccurred()) udnGateway.vrfTableId = test.vrftableID - udnGateway.isNetworkAdvertised = true - udnGateway.isNetworkAdvertisedToDefaultVRF = true - rules, delRules, err := udnGateway.constructUDNVRFIPRules() - g.Expect(err).ToNot(HaveOccurred()) - for i, rule := range rules { - g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) - g.Expect(rule.Table).To(Equal(test.expectedRules[i].table)) - g.Expect(rule.Family).To(Equal(test.expectedRules[i].family)) - if rule.Dst != nil { - g.Expect(*rule.Dst).To(Equal(test.expectedRules[i].dst)) - } else { - g.Expect(rule.Mark).To(Equal(test.expectedRules[i].mark)) - } - } - for i, rule := range delRules { - g.Expect(rule.Priority).To(Equal(test.deleteRules[i].priority)) - g.Expect(rule.Table).To(Equal(test.deleteRules[i].table)) - g.Expect(rule.Family).To(Equal(test.deleteRules[i].family)) - g.Expect(*rule.Dst).To(Equal(test.deleteRules[i].dst)) - } - }) - } -} - -func TestConstructUDNVRFIPRulesPodNetworkAdvertisedToNoneDefaultNetwork(t *testing.T) { - type testRule struct { - priority int - family int - table int - mark uint32 - dst net.IPNet - } - type testConfig struct { - desc string - vrftableID int - v4mode bool - v6mode bool - expectedRules []testRule - deleteRules []testRule - } - - tests := []testConfig{ - { - desc: "v4 rule test", - vrftableID: 1007, - expectedRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1007, - mark: 0x1003, - }, - }, - deleteRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1007, - dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("169.254.0.16")), - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1007, - dst: *ovntest.MustParseIPNet("100.128.0.0/16"), - }, - }, - v4mode: true, - }, - { - desc: "v6 rule test", - vrftableID: 1009, - expectedRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1009, - mark: 0x1003, - }, - }, - deleteRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1009, - dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("fd69::10")), - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1009, - dst: *ovntest.MustParseIPNet("ae70::/60"), - }, - }, - v6mode: true, - }, - { - desc: "dualstack rule test", - vrftableID: 1010, - expectedRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1010, - mark: 0x1003, - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1010, - mark: 0x1003, - }, - }, - deleteRules: []testRule{ - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1010, - dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("169.254.0.16")), - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1010, - dst: *util.GetIPNetFullMaskFromIP(ovntest.MustParseIP("fd69::10")), - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V4, - table: 1010, - dst: *ovntest.MustParseIPNet("100.128.0.0/16"), - }, - { - priority: UDNMasqueradeIPRulePriority, - family: netlink.FAMILY_V6, - table: 1010, - dst: *ovntest.MustParseIPNet("ae70::/60"), - }, - }, - v4mode: true, - v6mode: true, - }, - } - config.Gateway.V6MasqueradeSubnet = "fd69::/112" - config.Gateway.V4MasqueradeSubnet = "169.254.0.0/16" - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - g := NewWithT(t) - node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: nodeName, - }, - } - config.IPv4Mode = test.v4mode - config.IPv6Mode = test.v6mode - cidr := "" - if config.IPv4Mode { - cidr = "100.128.0.0/16/24" - } - if config.IPv4Mode && config.IPv6Mode { - cidr += ",ae70::/60" - } else if config.IPv6Mode { - cidr = "ae70::/60" - } - nad := ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", - types.Layer3Topology, cidr, types.NetworkRolePrimary) - ovntest.AnnotateNADWithNetworkID("3", nad) - netInfo, err := util.ParseNADInfo(nad) - g.Expect(err).ToNot(HaveOccurred()) - mutableNetInfo := util.NewMutableNetInfo(netInfo) - mutableNetInfo.SetPodNetworkAdvertisedVRFs(map[string][]string{node.Name: {"bluenet"}}) - ofm := getDummyOpenflowManager() - // create dummy gateway interface(Need to run this test as root) - err = netlink.LinkAdd(&netlink.Dummy{ - LinkAttrs: netlink.LinkAttrs{ - Name: "breth0", - }, - }) - g.Expect(err).NotTo(HaveOccurred()) - udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, nil, nil, &gateway{openflowManager: ofm}) - g.Expect(err).NotTo(HaveOccurred()) - // delete dummy gateway interface after creating UDN gateway(Need to run this test as root) - err = netlink.LinkDel(&netlink.Dummy{ - LinkAttrs: netlink.LinkAttrs{ - Name: "breth0", - }, - }) - g.Expect(err).NotTo(HaveOccurred()) - udnGateway.vrfTableId = test.vrftableID - udnGateway.isNetworkAdvertised = true - udnGateway.isNetworkAdvertisedToDefaultVRF = false - rules, delRules, err := udnGateway.constructUDNVRFIPRules() + rules, delRules, err := udnGateway.constructUDNVRFIPRules(true) g.Expect(err).ToNot(HaveOccurred()) for i, rule := range rules { g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) @@ -2263,8 +2072,7 @@ func TestUserDefinedNetworkGateway_updateAdvertisedUDNIsolationRules(t *testing. udng := &UserDefinedNetworkGateway{ NetInfo: netInfo, } - udng.isNetworkAdvertised = tt.isNetworkAdvertised - err = udng.updateAdvertisedUDNIsolationRules() + err = udng.updateAdvertisedUDNIsolationRules(tt.isNetworkAdvertised) g.Expect(err).NotTo(HaveOccurred()) v4Elems, err := nft.ListElements(context.TODO(), "set", nftablesAdvertisedUDNsSetV4) From b0b32b37f701f0ec6d6746b67d8b8b4f797b4a86 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Thu, 3 Jul 2025 16:11:36 +0300 Subject: [PATCH 063/278] kind: Rm push_args variable quotes When using Docker, push image command fails because the push_args var is interpreted as empty string, Docker reject it as invalid variable and fails with the following error: $ docker push '' localhost:5000/ovn-daemonset-fedora:latest docker: 'docker push' requires 1 argument Remove the push_args wrapping quotes. Signed-off-by: Or Mergi --- contrib/kind.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index 5ec980bd95..3d8bd0f30e 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -845,14 +845,14 @@ build_ovn_image() { # store in local registry if [ "$KIND_LOCAL_REGISTRY" == true ];then echo "Pushing built image to local $OCI_BIN registry" - $OCI_BIN push "$push_args" "$OVN_IMAGE" + $OCI_BIN push $push_args "$OVN_IMAGE" fi # We should push to local registry if image is not remote elif [ "${OVN_IMAGE}" != "" -a "${KIND_LOCAL_REGISTRY}" == true ] && (echo "$OVN_IMAGE" | grep / -vq); then local local_registry_ovn_image="localhost:5000/${OVN_IMAGE}" $OCI_BIN tag "$OVN_IMAGE" $local_registry_ovn_image OVN_IMAGE=$local_registry_ovn_image - $OCI_BIN push "$push_args" "$OVN_IMAGE" + $OCI_BIN push $push_args "$OVN_IMAGE" fi } From 2d2e4454d7debe348161063d1e9e48dddaa8a621 Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Fri, 13 Jun 2025 09:51:14 +0200 Subject: [PATCH 064/278] Reconcile namespace for network change Since CanServeNamespace filters out namespace events for namespaces unknown to be served by this primary network, we need to reconcile namespaces once the network is reconfigured to serve a namespace. Hence this commit reconciles those namespaces and also reconciles each network policy if it contains only peer namespace selector. Signed-off-by: Periyasamy Palanisamy --- .../pkg/ovn/base_network_controller.go | 72 ++++++++++---- .../pkg/ovn/base_network_controller_policy.go | 96 +++++++++++++++++-- go-controller/pkg/ovn/gress_policy.go | 7 ++ test/e2e/network_segmentation_policy.go | 86 +++++++++++++++-- 4 files changed, 228 insertions(+), 33 deletions(-) diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index db56f42cb9..bdb026752a 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -191,8 +191,7 @@ type BaseNetworkController struct { func (oc *BaseNetworkController) reconcile(netInfo util.NetInfo, setNodeFailed func(string)) error { // gather some information first - var err error - var retryNodes []*corev1.Node + var reconcileNodes []string oc.localZoneNodes.Range(func(key, _ any) bool { nodeName := key.(string) wasAdvertised := util.IsPodNetworkAdvertisedAtNode(oc, nodeName) @@ -201,41 +200,57 @@ func (oc *BaseNetworkController) reconcile(netInfo util.NetInfo, setNodeFailed f // noop return true } - var node *corev1.Node - node, err = oc.watchFactory.GetNode(nodeName) - if err != nil { - return false - } - retryNodes = append(retryNodes, node) + reconcileNodes = append(reconcileNodes, nodeName) return true }) - if err != nil { - return fmt.Errorf("failed to reconcile network %s: %w", oc.GetNetworkName(), err) - } reconcileRoutes := oc.routeImportManager != nil && oc.routeImportManager.NeedsReconciliation(netInfo) reconcilePendingPods := !oc.IsDefault() && !oc.ReconcilableNetInfo.EqualNADs(netInfo.GetNADs()...) + reconcileNamespaces := sets.NewString() + if oc.IsPrimaryNetwork() { + // since CanServeNamespace filters out namespace events for namespaces unknown + // to be served by this primary network, we need to reconcile namespaces once + // the network is reconfigured to serve a namespace. + reconcileNamespaces = sets.NewString(netInfo.GetNADNamespaces()...).Difference( + sets.NewString(oc.GetNADNamespaces()...)) + } // set the new NetInfo, point of no return - err = util.ReconcileNetInfo(oc.ReconcilableNetInfo, netInfo) + err := util.ReconcileNetInfo(oc.ReconcilableNetInfo, netInfo) if err != nil { return fmt.Errorf("failed to reconcile network information for network %s: %v", oc.GetNetworkName(), err) } + oc.doReconcile(reconcileRoutes, reconcilePendingPods, reconcileNodes, setNodeFailed, reconcileNamespaces.List()) + + return nil +} + +// doReconcile performs the reconciliation after the controller NetInfo has already being +// updated with the changes. What needs to be reconciled should already be known and +// provided on the arguments of the method. This method returns no error and logs them +// instead since once the controller NetInfo has been updated there is no point in retrying. +func (oc *BaseNetworkController) doReconcile(reconcileRoutes, reconcilePendingPods bool, + reconcileNodes []string, setNodeFailed func(string), reconcileNamespaces []string) { if reconcileRoutes { - err = oc.routeImportManager.ReconcileNetwork(oc.GetNetworkName()) + err := oc.routeImportManager.ReconcileNetwork(oc.GetNetworkName()) if err != nil { klog.Errorf("Failed to reconcile network %s on route import controller: %v", oc.GetNetworkName(), err) } } - for _, node := range retryNodes { - setNodeFailed(node.Name) + for _, nodeName := range reconcileNodes { + setNodeFailed(nodeName) + node, err := oc.watchFactory.GetNode(nodeName) + if err != nil { + klog.Infof("Failed to get node %s for reconciling network %s: %v", nodeName, oc.GetNetworkName(), err) + continue + } err = oc.retryNodes.AddRetryObjWithAddNoBackoff(node) if err != nil { - klog.Errorf("Failed to retry node %s for network %s: %v", node.Name, oc.GetNetworkName(), err) + klog.Errorf("Failed to retry node %s for network %s: %v", nodeName, oc.GetNetworkName(), err) } } - if len(retryNodes) > 0 { + if len(reconcileNodes) > 0 { oc.retryNodes.RequestRetryObjs() } @@ -245,7 +260,28 @@ func (oc *BaseNetworkController) reconcile(netInfo util.NetInfo, setNodeFailed f } } - return nil + namespaceAdded := false + for _, ns := range reconcileNamespaces { + namespace, err := oc.watchFactory.GetNamespace(ns) + if err != nil { + klog.Infof("Failed to get namespace %s for reconciling network %s: %v", ns, oc.GetNetworkName(), err) + continue + } + err = oc.retryNamespaces.AddRetryObjWithAddNoBackoff(namespace) + if err != nil { + klog.Infof("Failed to retry namespace %s for network %s: %v", ns, oc.GetNetworkName(), err) + continue + } + namespaceAdded = true + } + if namespaceAdded { + oc.retryNamespaces.RequestRetryObjs() + } + + err := oc.requeuePeerNamespaces(reconcileNamespaces) + if err != nil { + klog.Infof("Failed to retry network policy peer namespaces for network %s: %v", oc.GetNetworkName(), err) + } } // BaseSecondaryNetworkController structure holds per-network fields and network specific diff --git a/go-controller/pkg/ovn/base_network_controller_policy.go b/go-controller/pkg/ovn/base_network_controller_policy.go index f4c10bfacf..4d9fd61781 100644 --- a/go-controller/pkg/ovn/base_network_controller_policy.go +++ b/go-controller/pkg/ovn/base_network_controller_policy.go @@ -10,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -23,6 +24,7 @@ import ( libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" @@ -162,6 +164,8 @@ type networkPolicy struct { localPodHandler *factory.Handler // peer namespace handlers nsHandlerList []*factory.Handler + // peer namespace reconcilers + reconcilePeerNamespaces []*reconcilePeerNamespaces // peerAddressSets stores PodSelectorAddressSet keys for peers that this network policy was successfully added to. // Required for cleanup. peerAddressSets []string @@ -186,17 +190,23 @@ type networkPolicy struct { cancelableContext *util.CancelableContext } +type reconcilePeerNamespaces struct { + retryNamespaces *retry.RetryFramework + namespaceSelector *metav1.LabelSelector +} + func NewNetworkPolicy(policy *knet.NetworkPolicy) *networkPolicy { policyTypeIngress, policyTypeEgress := getPolicyType(policy) np := &networkPolicy{ - name: policy.Name, - namespace: policy.Namespace, - ingressPolicies: make([]*gressPolicy, 0), - egressPolicies: make([]*gressPolicy, 0), - isIngress: policyTypeIngress, - isEgress: policyTypeEgress, - nsHandlerList: make([]*factory.Handler, 0), - localPods: sync.Map{}, + name: policy.Name, + namespace: policy.Namespace, + ingressPolicies: make([]*gressPolicy, 0), + egressPolicies: make([]*gressPolicy, 0), + isIngress: policyTypeIngress, + isEgress: policyTypeEgress, + nsHandlerList: make([]*factory.Handler, 0), + reconcilePeerNamespaces: make([]*reconcilePeerNamespaces, 0), + localPods: sync.Map{}, } return np } @@ -1490,6 +1500,63 @@ func (bnc *BaseNetworkController) peerNamespaceUpdate(np *networkPolicy, gp *gre return err } +// requeuePeerNamespaces enqueues the namespace into network policy peer namespace +// retry framework object(s) which need to be retried immediately with add event. +func (bnc *BaseNetworkController) requeuePeerNamespaces(namespaces []string) error { + npKeys := bnc.networkPolicies.GetKeys() + var errors []error + for _, npKey := range npKeys { + err := bnc.networkPolicies.DoWithLock(npKey, func(npKey string) error { + np, ok := bnc.networkPolicies.Load(npKey) + if !ok { + return nil + } + np.RLock() + defer np.RUnlock() + var errors []error + for _, reconcilePeerNamespace := range np.reconcilePeerNamespaces { + namespaceAdded := false + for _, ns := range namespaces { + namespace, err := bnc.watchFactory.GetNamespace(ns) + if err != nil { + errors = append(errors, fmt.Errorf("failed to retrieve peer namespace %s for network policy %s on network %s: %w", + ns, npKey, bnc.GetNetworkName(), err)) + continue + } + namespaceLabels := labels.Set(namespace.Labels) + peerNamespaceSelector, err := metav1.LabelSelectorAsSelector(reconcilePeerNamespace.namespaceSelector) + if err != nil { + errors = append(errors, fmt.Errorf("failed to parse peer namespace %s selector for network policy %s on network %s: %w", + ns, npKey, bnc.GetNetworkName(), err)) + continue + } + // Filter out namespace when it's labels not matching with network policy peer namespace + // selector. + if !peerNamespaceSelector.Matches(namespaceLabels) { + continue + } + err = reconcilePeerNamespace.retryNamespaces.AddRetryObjWithAddNoBackoff(namespace) + if err != nil { + errors = append(errors, fmt.Errorf("failed to retry peer namespace %s for network policy %s on network %s: %w", + ns, npKey, bnc.GetNetworkName(), err)) + continue + } + namespaceAdded = true + } + if namespaceAdded { + reconcilePeerNamespace.retryNamespaces.RequestRetryObjs() + } + } + return utilerrors.Join(errors...) + }) + if err != nil { + errors = append(errors, fmt.Errorf("failed to retry peer namespaces for network policy %s on network %s: %w", + npKey, bnc.GetNetworkName(), err)) + } + } + return utilerrors.Join(errors...) +} + // addPeerNamespaceHandler starts a watcher for PeerNamespaceSelectorType. // Sync function and Add event for every existing namespace will be executed sequentially first, and an error will be // returned if something fails. @@ -1522,7 +1589,17 @@ func (bnc *BaseNetworkController) addPeerNamespaceHandler( klog.Errorf("WatchResource failed for addPeerNamespaceHandler: %v", err) return err } - + // Add peer namespace retry framework object into np.retryPeerNamespaces list so that + // when a new peer namespace is newly created later under UDN network, it gets reconciled + // and address set is created for the namespace. so we must reconcile it for network policy + // as well to update gress policy ACL with matching peer namespace address set. + if util.IsNetworkSegmentationSupportEnabled() && bnc.IsPrimaryNetwork() { + np.Lock() + np.reconcilePeerNamespaces = append(np.reconcilePeerNamespaces, + &reconcilePeerNamespaces{retryNamespaces: retryPeerNamespaces, + namespaceSelector: namespaceSelector}) + np.Unlock() + } np.nsHandlerList = append(np.nsHandlerList, namespaceHandler) return nil } @@ -1540,6 +1617,7 @@ func (bnc *BaseNetworkController) shutdownHandlers(np *networkPolicy) { for _, handler := range np.nsHandlerList { bnc.watchFactory.RemoveNamespaceHandler(handler) } + np.reconcilePeerNamespaces = make([]*reconcilePeerNamespaces, 0) np.nsHandlerList = make([]*factory.Handler, 0) } diff --git a/go-controller/pkg/ovn/gress_policy.go b/go-controller/pkg/ovn/gress_policy.go index c8445e6ed5..bc55cfb689 100644 --- a/go-controller/pkg/ovn/gress_policy.go +++ b/go-controller/pkg/ovn/gress_policy.go @@ -209,6 +209,10 @@ func (gp *gressPolicy) addNamespaceAddressSet(name string, asf addressset.Addres return false, fmt.Errorf("cannot add peer namespace %s: failed to get address set: %v", name, err) } v4HashName, v6HashName := as.GetASHashNames() + if v4HashName == "" && v6HashName == "" { + // This would happen when a namespace is not yet reconciled with UDN network. + return false, fmt.Errorf("cannot add peer namespace %s: address set has empty hashed name", name) + } v4HashName = "$" + v4HashName v6HashName = "$" + v6HashName @@ -234,6 +238,9 @@ func (gp *gressPolicy) addNamespaceAddressSet(name string, asf addressset.Addres func (gp *gressPolicy) delNamespaceAddressSet(name string) bool { dbIDs := getNamespaceAddrSetDbIDs(name, gp.controllerName) v4HashName, v6HashName := addressset.GetHashNamesForAS(dbIDs) + if v4HashName == "" && v6HashName == "" { + return false + } v4HashName = "$" + v4HashName v6HashName = "$" + v6HashName diff --git a/test/e2e/network_segmentation_policy.go b/test/e2e/network_segmentation_policy.go index 30bc1dc0a5..8abc3d6791 100644 --- a/test/e2e/network_segmentation_policy.go +++ b/test/e2e/network_segmentation_policy.go @@ -35,6 +35,8 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ randomStringLength = 5 nameSpaceYellowSuffix = "yellow" namespaceBlueSuffix = "blue" + namespaceRedSuffix = "red" + namespaceOrangeSuffix = "orange" ) var ( @@ -57,7 +59,10 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ namespaceYellow := getNamespaceName(f, nameSpaceYellowSuffix) namespaceBlue := getNamespaceName(f, namespaceBlueSuffix) - for _, namespace := range []string{namespaceYellow, namespaceBlue} { + namespaceRed := getNamespaceName(f, namespaceRedSuffix) + namespaceOrange := getNamespaceName(f, namespaceOrangeSuffix) + for _, namespace := range []string{namespaceYellow, namespaceBlue, + namespaceRed, namespaceOrange} { ginkgo.By("Creating namespace " + namespace) ns, err := cs.CoreV1().Namespaces().Create(context.Background(), &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -180,11 +185,13 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ namespaceYellow := getNamespaceName(f, nameSpaceYellowSuffix) namespaceBlue := getNamespaceName(f, namespaceBlueSuffix) + namespaceRed := getNamespaceName(f, namespaceRedSuffix) + namespaceOrange := getNamespaceName(f, namespaceOrangeSuffix) nad := networkAttachmentConfigParams{ topology: topology, cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), - // Both yellow and blue namespaces are going to served by green network. + // The yellow, blue and red namespaces are going to served by green network. // Use random suffix for the network name to avoid race between tests. networkName: fmt.Sprintf("%s-%s", "green", rand.String(randomStringLength)), role: "primary", @@ -258,8 +265,8 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ return reachServerPodFromClient(cs, denyServerPodConfig, clientPodConfig, denyServerPodIP, port) }, 1*time.Minute, 6*time.Second).ShouldNot(gomega.Succeed()) - ginkgo.By("creating a \"allow-traffic-to-pod\" network policy") - _, err = allowTrafficToPodFromNamespacePolicy(f, namespaceYellow, namespaceBlue, "allow-traffic-to-pod", allowServerPodLabel) + ginkgo.By("creating a \"allow-traffic-to-pod\" network policy for blue and red namespace") + _, err = allowTrafficToPodFromNamespacePolicy(f, namespaceYellow, namespaceBlue, namespaceRed, "allow-traffic-to-pod", allowServerPodLabel) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By("asserting the *client* pod can contact the allow server pod exposed endpoint") @@ -272,6 +279,72 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ return reachServerPodFromClient(cs, denyServerPodConfig, clientPodConfig, denyServerPodIP, port) }, 1*time.Minute, 6*time.Second).ShouldNot(gomega.Succeed()) + // Create client pod in red namespace and check network policy is working. + ginkgo.By("creating client pod in red namespace and check if it is in pending state until NAD is created") + clientPodConfig.namespace = namespaceRed + podSpec := generatePodSpec(clientPodConfig) + _, err = cs.CoreV1().Pods(clientPodConfig.namespace).Create( + context.Background(), + podSpec, + metav1.CreateOptions{}, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Consistently(func() v1.PodPhase { + updatedPod, err := cs.CoreV1().Pods(clientPodConfig.namespace).Get(context.Background(), + clientPodConfig.name, metav1.GetOptions{}) + if err != nil { + return v1.PodFailed + } + return updatedPod.Status.Phase + }, 1*time.Minute, 6*time.Second).Should(gomega.Equal(v1.PodPending)) + + ginkgo.By("creating NAD for red and orange namespaces and check pod moves into running state") + for _, namespace := range []string{namespaceRed, namespaceOrange} { + ginkgo.By("creating the attachment configuration for " + netConfName + " in namespace " + namespace) + netConfig := newNetworkAttachmentConfig(nad) + netConfig.namespace = namespace + netConfig.name = netConfName + + _, err := nadClient.NetworkAttachmentDefinitions(namespace).Create( + context.Background(), + generateNAD(netConfig), + metav1.CreateOptions{}, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + gomega.Eventually(func() v1.PodPhase { + updatedPod, err := cs.CoreV1().Pods(clientPodConfig.namespace).Get(context.Background(), + clientPodConfig.name, metav1.GetOptions{}) + if err != nil { + return v1.PodFailed + } + return updatedPod.Status.Phase + }, 1*time.Minute, 6*time.Second).Should(gomega.Equal(v1.PodRunning)) + + ginkgo.By("asserting the *red client* pod can contact the allow server pod exposed endpoint") + gomega.Eventually(func() error { + return reachServerPodFromClient(cs, allowServerPodConfig, clientPodConfig, allowServerPodIP, port) + }, 1*time.Minute, 6*time.Second).Should(gomega.Succeed()) + + ginkgo.By("asserting the *red client* pod can not contact deny server pod exposed endpoint") + gomega.Eventually(func() error { + return reachServerPodFromClient(cs, denyServerPodConfig, clientPodConfig, denyServerPodIP, port) + }, 1*time.Minute, 6*time.Second).ShouldNot(gomega.Succeed()) + + // Create client pod in orange namespace now and check network policy is working. + ginkgo.By("creating client pod in orange namespace") + clientPodConfig.namespace = namespaceOrange + runUDNPod(cs, namespaceOrange, clientPodConfig, nil) + + ginkgo.By("asserting the *orange client* pod can not contact the allow server pod exposed endpoint") + gomega.Eventually(func() error { + return reachServerPodFromClient(cs, allowServerPodConfig, clientPodConfig, allowServerPodIP, port) + }, 1*time.Minute, 6*time.Second).ShouldNot(gomega.Succeed()) + + ginkgo.By("asserting the *orange client* pod can not contact deny server pod exposed endpoint") + gomega.Eventually(func() error { + return reachServerPodFromClient(cs, denyServerPodConfig, clientPodConfig, denyServerPodIP, port) + }, 1*time.Minute, 6*time.Second).ShouldNot(gomega.Succeed()) }, ginkgo.Entry( "in L2 primary UDN", @@ -328,7 +401,7 @@ func getNamespaceName(f *framework.Framework, nsSuffix string) string { return fmt.Sprintf("%s-%s", f.Namespace.Name, nsSuffix) } -func allowTrafficToPodFromNamespacePolicy(f *framework.Framework, namespace, fromNamespace, policyName string, podLabel map[string]string) (*knet.NetworkPolicy, error) { +func allowTrafficToPodFromNamespacePolicy(f *framework.Framework, namespace, fromNamespace1, fromNamespace2, policyName string, podLabel map[string]string) (*knet.NetworkPolicy, error) { policy := &knet.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: policyName, @@ -337,7 +410,8 @@ func allowTrafficToPodFromNamespacePolicy(f *framework.Framework, namespace, fro PodSelector: metav1.LabelSelector{MatchLabels: podLabel}, PolicyTypes: []knet.PolicyType{knet.PolicyTypeIngress}, Ingress: []knet.NetworkPolicyIngressRule{{From: []knet.NetworkPolicyPeer{ - {NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": fromNamespace}}}}}}, + {NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": fromNamespace1}}}, + {NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"kubernetes.io/metadata.name": fromNamespace2}}}}}}, }, } return f.ClientSet.NetworkingV1().NetworkPolicies(namespace).Create(context.TODO(), policy, metav1.CreateOptions{}) From 96db6fd1f211477012f989d4e87f9eb3d3b6e1f4 Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Wed, 18 Jun 2025 14:49:55 +0200 Subject: [PATCH 065/278] Use Handler FilterFunc to filter out np peer namespace This commits exports FilterFunc from handler and uses it while reconciling network policy for UDN peer namespaces. Signed-off-by: Periyasamy Palanisamy --- go-controller/pkg/factory/handler.go | 4 + .../pkg/ovn/base_network_controller_policy.go | 77 ++++++++----------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/go-controller/pkg/factory/handler.go b/go-controller/pkg/factory/handler.go index 1e87f7309b..50563b3278 100644 --- a/go-controller/pkg/factory/handler.go +++ b/go-controller/pkg/factory/handler.go @@ -76,6 +76,10 @@ func (h *Handler) OnDelete(obj interface{}) { } } +func (h *Handler) FilterFunc(obj interface{}) bool { + return h.base.FilterFunc(obj) +} + func (h *Handler) kill() bool { return atomic.CompareAndSwapUint32(&h.tombstone, handlerAlive, handlerDead) } diff --git a/go-controller/pkg/ovn/base_network_controller_policy.go b/go-controller/pkg/ovn/base_network_controller_policy.go index 4d9fd61781..1bb39137cf 100644 --- a/go-controller/pkg/ovn/base_network_controller_policy.go +++ b/go-controller/pkg/ovn/base_network_controller_policy.go @@ -10,7 +10,6 @@ import ( corev1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -162,10 +161,8 @@ type networkPolicy struct { // network policy owns only 1 local pod handler localPodHandler *factory.Handler - // peer namespace handlers - nsHandlerList []*factory.Handler // peer namespace reconcilers - reconcilePeerNamespaces []*reconcilePeerNamespaces + reconcilePeerNamespaces []*peerNamespacesRetry // peerAddressSets stores PodSelectorAddressSet keys for peers that this network policy was successfully added to. // Required for cleanup. peerAddressSets []string @@ -190,9 +187,9 @@ type networkPolicy struct { cancelableContext *util.CancelableContext } -type reconcilePeerNamespaces struct { - retryNamespaces *retry.RetryFramework - namespaceSelector *metav1.LabelSelector +type peerNamespacesRetry struct { + retryFramework *retry.RetryFramework + handler *factory.Handler } func NewNetworkPolicy(policy *knet.NetworkPolicy) *networkPolicy { @@ -204,8 +201,7 @@ func NewNetworkPolicy(policy *knet.NetworkPolicy) *networkPolicy { egressPolicies: make([]*gressPolicy, 0), isIngress: policyTypeIngress, isEgress: policyTypeEgress, - nsHandlerList: make([]*factory.Handler, 0), - reconcilePeerNamespaces: make([]*reconcilePeerNamespaces, 0), + reconcilePeerNamespaces: make([]*peerNamespacesRetry, 0), localPods: sync.Map{}, } return np @@ -1503,8 +1499,18 @@ func (bnc *BaseNetworkController) peerNamespaceUpdate(np *networkPolicy, gp *gre // requeuePeerNamespaces enqueues the namespace into network policy peer namespace // retry framework object(s) which need to be retried immediately with add event. func (bnc *BaseNetworkController) requeuePeerNamespaces(namespaces []string) error { - npKeys := bnc.networkPolicies.GetKeys() var errors []error + var peerNamespaces []*corev1.Namespace + for _, ns := range namespaces { + namespace, err := bnc.watchFactory.GetNamespace(ns) + if err != nil { + errors = append(errors, fmt.Errorf("failed to retrieve namespace %s for reconciling network %s: %w", + ns, bnc.GetNetworkName(), err)) + continue + } + peerNamespaces = append(peerNamespaces, namespace) + } + npKeys := bnc.networkPolicies.GetKeys() for _, npKey := range npKeys { err := bnc.networkPolicies.DoWithLock(npKey, func(npKey string) error { np, ok := bnc.networkPolicies.Load(npKey) @@ -1516,35 +1522,22 @@ func (bnc *BaseNetworkController) requeuePeerNamespaces(namespaces []string) err var errors []error for _, reconcilePeerNamespace := range np.reconcilePeerNamespaces { namespaceAdded := false - for _, ns := range namespaces { - namespace, err := bnc.watchFactory.GetNamespace(ns) - if err != nil { - errors = append(errors, fmt.Errorf("failed to retrieve peer namespace %s for network policy %s on network %s: %w", - ns, npKey, bnc.GetNetworkName(), err)) - continue - } - namespaceLabels := labels.Set(namespace.Labels) - peerNamespaceSelector, err := metav1.LabelSelectorAsSelector(reconcilePeerNamespace.namespaceSelector) - if err != nil { - errors = append(errors, fmt.Errorf("failed to parse peer namespace %s selector for network policy %s on network %s: %w", - ns, npKey, bnc.GetNetworkName(), err)) - continue - } + for _, namespace := range peerNamespaces { // Filter out namespace when it's labels not matching with network policy peer namespace // selector. - if !peerNamespaceSelector.Matches(namespaceLabels) { + if !reconcilePeerNamespace.handler.FilterFunc(namespace) { continue } - err = reconcilePeerNamespace.retryNamespaces.AddRetryObjWithAddNoBackoff(namespace) + err := reconcilePeerNamespace.retryFramework.AddRetryObjWithAddNoBackoff(namespace) if err != nil { errors = append(errors, fmt.Errorf("failed to retry peer namespace %s for network policy %s on network %s: %w", - ns, npKey, bnc.GetNetworkName(), err)) + namespace.Name, npKey, bnc.GetNetworkName(), err)) continue } namespaceAdded = true } if namespaceAdded { - reconcilePeerNamespace.retryNamespaces.RequestRetryObjs() + reconcilePeerNamespace.retryFramework.RequestRetryObjs() } } return utilerrors.Join(errors...) @@ -1589,18 +1582,17 @@ func (bnc *BaseNetworkController) addPeerNamespaceHandler( klog.Errorf("WatchResource failed for addPeerNamespaceHandler: %v", err) return err } - // Add peer namespace retry framework object into np.retryPeerNamespaces list so that - // when a new peer namespace is newly created later under UDN network, it gets reconciled - // and address set is created for the namespace. so we must reconcile it for network policy + + // Add peer namespace retry framework object into np.reconcilePeerNamespaces so that when + // a new peer namespace is newly created later under UDN network, it gets reconciled and + // address set is created for the namespace. so we must reconcile it for network policy // as well to update gress policy ACL with matching peer namespace address set. - if util.IsNetworkSegmentationSupportEnabled() && bnc.IsPrimaryNetwork() { - np.Lock() - np.reconcilePeerNamespaces = append(np.reconcilePeerNamespaces, - &reconcilePeerNamespaces{retryNamespaces: retryPeerNamespaces, - namespaceSelector: namespaceSelector}) - np.Unlock() - } - np.nsHandlerList = append(np.nsHandlerList, namespaceHandler) + np.Lock() + np.reconcilePeerNamespaces = append(np.reconcilePeerNamespaces, + &peerNamespacesRetry{retryFramework: retryPeerNamespaces, + handler: namespaceHandler}) + np.Unlock() + return nil } @@ -1614,11 +1606,10 @@ func (bnc *BaseNetworkController) shutdownHandlers(np *networkPolicy) { bnc.watchFactory.RemovePodHandler(np.localPodHandler) np.localPodHandler = nil } - for _, handler := range np.nsHandlerList { - bnc.watchFactory.RemoveNamespaceHandler(handler) + for _, retry := range np.reconcilePeerNamespaces { + bnc.watchFactory.RemoveNamespaceHandler(retry.handler) } - np.reconcilePeerNamespaces = make([]*reconcilePeerNamespaces, 0) - np.nsHandlerList = make([]*factory.Handler, 0) + np.reconcilePeerNamespaces = make([]*peerNamespacesRetry, 0) } // The following 2 functions should return the same key for network policy based on k8s on internal networkPolicy object From f792af555c4b7b7b161a753083ed48fb1a3bff7c Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Tue, 24 Jun 2025 13:27:36 +0200 Subject: [PATCH 066/278] Use namespace reconcilation loop for syncing network policies This commit makes network reconcilation loop to sync only namespace object and network policies sync to happen from namespace reconcilation loop. Signed-off-by: Periyasamy Palanisamy --- .../pkg/ovn/base_network_controller.go | 9 ++- .../pkg/ovn/base_network_controller_policy.go | 55 ++++++++----------- .../ovn/base_network_controller_secondary.go | 10 +++- test/e2e/network_segmentation_policy.go | 2 + 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index bdb026752a..51c5c62dec 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -260,6 +260,10 @@ func (oc *BaseNetworkController) doReconcile(reconcileRoutes, reconcilePendingPo } } + // reconciles namespaces that were added to the network, this will trigger namespace add event and + // network controller creates the address set for the namespace. + // To update gress policy ACLs with peer namespace address set, invoke requeuePeerNamespace method after + // address set is created for the namespace. namespaceAdded := false for _, ns := range reconcileNamespaces { namespace, err := oc.watchFactory.GetNamespace(ns) @@ -277,11 +281,6 @@ func (oc *BaseNetworkController) doReconcile(reconcileRoutes, reconcilePendingPo if namespaceAdded { oc.retryNamespaces.RequestRetryObjs() } - - err := oc.requeuePeerNamespaces(reconcileNamespaces) - if err != nil { - klog.Infof("Failed to retry network policy peer namespaces for network %s: %v", oc.GetNetworkName(), err) - } } // BaseSecondaryNetworkController structure holds per-network fields and network specific diff --git a/go-controller/pkg/ovn/base_network_controller_policy.go b/go-controller/pkg/ovn/base_network_controller_policy.go index 1bb39137cf..e0acdafbdc 100644 --- a/go-controller/pkg/ovn/base_network_controller_policy.go +++ b/go-controller/pkg/ovn/base_network_controller_policy.go @@ -1496,20 +1496,10 @@ func (bnc *BaseNetworkController) peerNamespaceUpdate(np *networkPolicy, gp *gre return err } -// requeuePeerNamespaces enqueues the namespace into network policy peer namespace +// requeuePeerNamespace enqueues the namespace into network policy peer namespace // retry framework object(s) which need to be retried immediately with add event. -func (bnc *BaseNetworkController) requeuePeerNamespaces(namespaces []string) error { +func (bnc *BaseNetworkController) requeuePeerNamespace(namespace *corev1.Namespace) error { var errors []error - var peerNamespaces []*corev1.Namespace - for _, ns := range namespaces { - namespace, err := bnc.watchFactory.GetNamespace(ns) - if err != nil { - errors = append(errors, fmt.Errorf("failed to retrieve namespace %s for reconciling network %s: %w", - ns, bnc.GetNetworkName(), err)) - continue - } - peerNamespaces = append(peerNamespaces, namespace) - } npKeys := bnc.networkPolicies.GetKeys() for _, npKey := range npKeys { err := bnc.networkPolicies.DoWithLock(npKey, func(npKey string) error { @@ -1519,26 +1509,23 @@ func (bnc *BaseNetworkController) requeuePeerNamespaces(namespaces []string) err } np.RLock() defer np.RUnlock() + if np.deleted { + return nil + } var errors []error for _, reconcilePeerNamespace := range np.reconcilePeerNamespaces { - namespaceAdded := false - for _, namespace := range peerNamespaces { - // Filter out namespace when it's labels not matching with network policy peer namespace - // selector. - if !reconcilePeerNamespace.handler.FilterFunc(namespace) { - continue - } - err := reconcilePeerNamespace.retryFramework.AddRetryObjWithAddNoBackoff(namespace) - if err != nil { - errors = append(errors, fmt.Errorf("failed to retry peer namespace %s for network policy %s on network %s: %w", - namespace.Name, npKey, bnc.GetNetworkName(), err)) - continue - } - namespaceAdded = true + // Filter out namespace when it's labels not matching with network policy peer namespace + // selector. + if !reconcilePeerNamespace.handler.FilterFunc(namespace) { + continue } - if namespaceAdded { - reconcilePeerNamespace.retryFramework.RequestRetryObjs() + err := reconcilePeerNamespace.retryFramework.AddRetryObjWithAddNoBackoff(namespace) + if err != nil { + errors = append(errors, fmt.Errorf("failed to retry peer namespace %s for network policy %s on network %s: %w", + namespace.Name, npKey, bnc.GetNetworkName(), err)) + continue } + reconcilePeerNamespace.retryFramework.RequestRetryObjs() } return utilerrors.Join(errors...) }) @@ -1587,11 +1574,13 @@ func (bnc *BaseNetworkController) addPeerNamespaceHandler( // a new peer namespace is newly created later under UDN network, it gets reconciled and // address set is created for the namespace. so we must reconcile it for network policy // as well to update gress policy ACL with matching peer namespace address set. - np.Lock() - np.reconcilePeerNamespaces = append(np.reconcilePeerNamespaces, - &peerNamespacesRetry{retryFramework: retryPeerNamespaces, - handler: namespaceHandler}) - np.Unlock() + if bnc.IsPrimaryNetwork() { + np.Lock() + np.reconcilePeerNamespaces = append(np.reconcilePeerNamespaces, + &peerNamespacesRetry{retryFramework: retryPeerNamespaces, + handler: namespaceHandler}) + np.Unlock() + } return nil } diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index cef46aaa6e..1b5cfdd5ac 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -679,7 +679,15 @@ func (bsnc *BaseSecondaryNetworkController) AddNamespaceForSecondaryNetwork(ns * if err != nil { return fmt.Errorf("failed to ensure namespace locked: %v", err) } - defer nsUnlock() + nsUnlock() + // Enqueue the UDN namespace into network policy controller if it needs to be + // processed by network policy peer namespace handlers. + if bsnc.IsPrimaryNetwork() { + err = bsnc.requeuePeerNamespace(ns) + if err != nil { + return fmt.Errorf("failed to requeue peer namespace %s: %v", ns.Name, err) + } + } return nil } diff --git a/test/e2e/network_segmentation_policy.go b/test/e2e/network_segmentation_policy.go index 8abc3d6791..f00dd63bec 100644 --- a/test/e2e/network_segmentation_policy.go +++ b/test/e2e/network_segmentation_policy.go @@ -298,6 +298,8 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ return updatedPod.Status.Phase }, 1*time.Minute, 6*time.Second).Should(gomega.Equal(v1.PodPending)) + // The pod won't run and the namespace address set won't be created until the NAD for the network is added + // to the namespace and we test here that once that happens the policy is reconciled to account for it. ginkgo.By("creating NAD for red and orange namespaces and check pod moves into running state") for _, namespace := range []string{namespaceRed, namespaceOrange} { ginkgo.By("creating the attachment configuration for " + netConfName + " in namespace " + namespace) From 0b513c6319e3258309ffbba0e67ce7a5d427b17e Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Fri, 4 Jul 2025 15:47:11 +0100 Subject: [PATCH 067/278] chore: bump libovsdb to v0.8.0 The diff between v0.7.0 and v0.8.0 is simply a rename from ovn-org/libovsdb to ovn-kubernetes/libovsdb. Signed-off-by: Dave Tucker --- docs/developer-guide/developer.md | 3 ++- go-controller/.golangci.yml | 7 +++--- go-controller/cmd/ovnkube/ovnkube.go | 2 +- go-controller/go.mod | 2 +- go-controller/go.sum | 4 ++-- go-controller/hack/update-modelgen.sh | 4 ++-- .../observability-lib/ovsdb/bridge.go | 2 +- .../ovsdb/flow_sample_collector_set.go | 2 +- .../observability-lib/ovsdb/interface.go | 2 +- .../observability-lib/ovsdb/observ_model.go | 2 +- .../sampledecoder/db_client.go | 4 ++-- .../sampledecoder/sample_decoder.go | 2 +- go-controller/pkg/cni/cni.go | 2 +- go-controller/pkg/cni/cni_test.go | 2 +- go-controller/pkg/cni/cniserver.go | 2 +- go-controller/pkg/cni/cniserver_test.go | 2 +- go-controller/pkg/cni/types.go | 2 +- .../controllermanager/controller_manager.go | 2 +- .../node_controller_manager.go | 2 +- go-controller/pkg/kubevirt/dhcp.go | 2 +- go-controller/pkg/kubevirt/pod.go | 2 +- go-controller/pkg/kubevirt/router.go | 2 +- go-controller/pkg/libovsdb/libovsdb.go | 4 ++-- go-controller/pkg/libovsdb/ops/acl.go | 4 ++-- go-controller/pkg/libovsdb/ops/address_set.go | 6 ++--- go-controller/pkg/libovsdb/ops/chassis.go | 2 +- go-controller/pkg/libovsdb/ops/copp.go | 4 ++-- go-controller/pkg/libovsdb/ops/dhcp.go | 4 ++-- go-controller/pkg/libovsdb/ops/lbgroup.go | 4 ++-- .../pkg/libovsdb/ops/loadbalancer.go | 6 ++--- go-controller/pkg/libovsdb/ops/mac_binding.go | 2 +- go-controller/pkg/libovsdb/ops/meter.go | 4 ++-- go-controller/pkg/libovsdb/ops/model.go | 6 ++--- .../pkg/libovsdb/ops/model_client.go | 6 ++--- .../pkg/libovsdb/ops/model_client_test.go | 6 ++--- go-controller/pkg/libovsdb/ops/nb_global.go | 2 +- go-controller/pkg/libovsdb/ops/ovs/bridge.go | 2 +- .../pkg/libovsdb/ops/ovs/interface.go | 2 +- .../pkg/libovsdb/ops/ovs/openvswitch.go | 2 +- go-controller/pkg/libovsdb/ops/portbinding.go | 2 +- go-controller/pkg/libovsdb/ops/portgroup.go | 4 ++-- go-controller/pkg/libovsdb/ops/qos.go | 4 ++-- go-controller/pkg/libovsdb/ops/router.go | 4 ++-- go-controller/pkg/libovsdb/ops/sample.go | 6 ++--- go-controller/pkg/libovsdb/ops/sb_global.go | 2 +- go-controller/pkg/libovsdb/ops/switch.go | 4 ++-- .../pkg/libovsdb/ops/template_var.go | 4 ++-- go-controller/pkg/libovsdb/ops/transact.go | 6 ++--- go-controller/pkg/libovsdb/util/acl.go | 2 +- .../pkg/libovsdb/util/address_set.go | 4 ++-- .../pkg/libovsdb/util/mac_binding.go | 6 ++--- go-controller/pkg/libovsdb/util/metric.go | 2 +- go-controller/pkg/libovsdb/util/nb_global.go | 2 +- go-controller/pkg/libovsdb/util/port.go | 2 +- go-controller/pkg/libovsdb/util/router.go | 2 +- go-controller/pkg/libovsdb/util/switch.go | 2 +- go-controller/pkg/metrics/metrics.go | 2 +- go-controller/pkg/metrics/ovn.go | 2 +- .../pkg/metrics/ovnkube_controller.go | 6 ++--- go-controller/pkg/metrics/ovs.go | 2 +- .../pkg/metrics/recorders/duration.go | 8 +++---- .../pkg/metrics/recorders/duration_test.go | 2 +- go-controller/pkg/nbdb/acl.go | 2 +- go-controller/pkg/nbdb/address_set.go | 2 +- go-controller/pkg/nbdb/bfd.go | 2 +- .../pkg/nbdb/chassis_template_var.go | 2 +- go-controller/pkg/nbdb/connection.go | 2 +- go-controller/pkg/nbdb/copp.go | 2 +- go-controller/pkg/nbdb/dhcp_options.go | 2 +- go-controller/pkg/nbdb/dhcp_relay.go | 2 +- go-controller/pkg/nbdb/dns.go | 2 +- go-controller/pkg/nbdb/forwarding_group.go | 2 +- go-controller/pkg/nbdb/gateway_chassis.go | 2 +- go-controller/pkg/nbdb/ha_chassis.go | 2 +- go-controller/pkg/nbdb/ha_chassis_group.go | 2 +- go-controller/pkg/nbdb/load_balancer.go | 2 +- go-controller/pkg/nbdb/load_balancer_group.go | 2 +- .../pkg/nbdb/load_balancer_health_check.go | 2 +- go-controller/pkg/nbdb/logical_router.go | 2 +- .../pkg/nbdb/logical_router_policy.go | 2 +- go-controller/pkg/nbdb/logical_router_port.go | 2 +- .../pkg/nbdb/logical_router_static_route.go | 2 +- go-controller/pkg/nbdb/logical_switch.go | 2 +- go-controller/pkg/nbdb/logical_switch_port.go | 2 +- go-controller/pkg/nbdb/meter.go | 2 +- go-controller/pkg/nbdb/meter_band.go | 2 +- go-controller/pkg/nbdb/mirror.go | 2 +- go-controller/pkg/nbdb/model.go | 4 ++-- go-controller/pkg/nbdb/nat.go | 2 +- go-controller/pkg/nbdb/nb_global.go | 2 +- go-controller/pkg/nbdb/port_group.go | 2 +- go-controller/pkg/nbdb/qos.go | 2 +- go-controller/pkg/nbdb/sample.go | 2 +- go-controller/pkg/nbdb/sample_collector.go | 2 +- go-controller/pkg/nbdb/sampling_app.go | 2 +- go-controller/pkg/nbdb/ssl.go | 2 +- go-controller/pkg/nbdb/static_mac_binding.go | 2 +- .../node/default_node_network_controller.go | 2 +- .../pkg/observability/observability.go | 4 ++-- .../pkg/observability/observability_test.go | 2 +- .../pkg/ovn/address_set/address_set.go | 4 ++-- .../pkg/ovn/address_set/address_set_test.go | 4 ++-- .../pkg/ovn/address_set/fake_address_set.go | 2 +- .../pkg/ovn/address_set/mocks/AddressSet.go | 2 +- .../address_set/mocks/AddressSetFactory.go | 2 +- .../pkg/ovn/base_network_controller.go | 4 ++-- .../pkg/ovn/base_network_controller_pods.go | 4 ++-- .../pkg/ovn/base_network_controller_policy.go | 4 ++-- .../ovn/base_network_controller_secondary.go | 4 ++-- .../admin_network_policy.go | 4 ++-- .../admin_network_policy_controller.go | 2 +- .../controller/admin_network_policy/repair.go | 2 +- .../admin_network_policy/status_test.go | 2 +- .../external_controller_policy_test.go | 2 +- .../controller/apbroute/master_controller.go | 2 +- .../ovn/controller/apbroute/network_client.go | 4 ++-- .../egressservice/egressservice_zone.go | 4 ++-- .../egressservice_zone_service.go | 2 +- .../network_qos/network_qos_controller.go | 2 +- .../network_qos/network_qos_ovnnb.go | 4 ++-- .../network_qos/network_qos_test.go | 2 +- .../ovn/controller/services/loadbalancer.go | 2 +- .../pkg/ovn/controller/services/repair.go | 4 ++-- .../services/services_controller.go | 4 ++-- .../services/services_controller_test.go | 2 +- .../controller/services/svc_template_var.go | 4 ++-- .../udnenabledsvc/udn_enabled_svc.go | 2 +- .../udnenabledsvc/udn_enabled_svc_test.go | 2 +- .../pkg/ovn/controller/unidling/unidle.go | 8 +++---- .../ovn/controller/unidling/unidle_test.go | 2 +- go-controller/pkg/ovn/copp.go | 2 +- .../pkg/ovn/default_network_controller.go | 2 +- .../pkg/ovn/dns_name_resolver/dns.go | 2 +- .../dns_name_resolver/dns_name_resolver.go | 2 +- .../pkg/ovn/dns_name_resolver/external_dns.go | 2 +- .../dns_name_resolver/external_dns_test.go | 2 +- .../dns_name_resolver/external_dns_tracker.go | 2 +- go-controller/pkg/ovn/egressfirewall.go | 2 +- go-controller/pkg/ovn/egressgw.go | 4 ++-- go-controller/pkg/ovn/egressip.go | 4 ++-- go-controller/pkg/ovn/egressqos.go | 2 +- .../logical_router_policy_sync.go | 4 ++-- .../ovn/external_ids_syncer/nat/nat_sync.go | 4 ++-- go-controller/pkg/ovn/gateway.go | 2 +- go-controller/pkg/ovn/gateway/gateway.go | 2 +- .../ovn/gatewayrouter/policybasedroutes.go | 2 +- go-controller/pkg/ovn/hybrid.go | 2 +- go-controller/pkg/ovn/hybrid_test.go | 2 +- go-controller/pkg/ovn/master.go | 2 +- go-controller/pkg/ovn/master_test.go | 2 +- go-controller/pkg/ovn/multihoming_test.go | 2 +- go-controller/pkg/ovn/namespace.go | 2 +- go-controller/pkg/ovn/ovn.go | 2 +- go-controller/pkg/ovn/ovn_test.go | 2 +- go-controller/pkg/ovn/pods.go | 2 +- go-controller/pkg/ovn/policy_test.go | 2 +- .../pkg/ovn/routeimport/route_import.go | 4 ++-- .../secondary_layer3_network_controller.go | 4 ++-- .../pkg/ovn/topology/topologyfactory.go | 2 +- .../pkg/ovn/topology/topologyfactory_test.go | 2 +- go-controller/pkg/ovn/udn_isolation.go | 4 ++-- .../ovn/zone_interconnect/chassis_handler.go | 2 +- .../zone_interconnect/chassis_handler_test.go | 2 +- .../ovn/zone_interconnect/zone_ic_handler.go | 4 ++-- .../zone_interconnect/zone_ic_handler_test.go | 2 +- go-controller/pkg/sbdb/address_set.go | 2 +- go-controller/pkg/sbdb/bfd.go | 2 +- go-controller/pkg/sbdb/chassis.go | 2 +- go-controller/pkg/sbdb/chassis_private.go | 2 +- .../pkg/sbdb/chassis_template_var.go | 2 +- go-controller/pkg/sbdb/connection.go | 2 +- go-controller/pkg/sbdb/controller_event.go | 2 +- go-controller/pkg/sbdb/datapath_binding.go | 2 +- go-controller/pkg/sbdb/dhcp_options.go | 2 +- go-controller/pkg/sbdb/dhcpv6_options.go | 2 +- go-controller/pkg/sbdb/dns.go | 2 +- go-controller/pkg/sbdb/encap.go | 2 +- go-controller/pkg/sbdb/fdb.go | 2 +- go-controller/pkg/sbdb/gateway_chassis.go | 2 +- go-controller/pkg/sbdb/ha_chassis.go | 2 +- go-controller/pkg/sbdb/ha_chassis_group.go | 2 +- go-controller/pkg/sbdb/igmp_group.go | 2 +- go-controller/pkg/sbdb/ip_multicast.go | 2 +- go-controller/pkg/sbdb/load_balancer.go | 2 +- go-controller/pkg/sbdb/logical_dp_group.go | 2 +- go-controller/pkg/sbdb/logical_flow.go | 2 +- go-controller/pkg/sbdb/mac_binding.go | 2 +- go-controller/pkg/sbdb/meter.go | 2 +- go-controller/pkg/sbdb/meter_band.go | 2 +- go-controller/pkg/sbdb/mirror.go | 2 +- go-controller/pkg/sbdb/model.go | 4 ++-- go-controller/pkg/sbdb/multicast_group.go | 2 +- go-controller/pkg/sbdb/port_binding.go | 2 +- go-controller/pkg/sbdb/port_group.go | 2 +- go-controller/pkg/sbdb/rbac_permission.go | 2 +- go-controller/pkg/sbdb/rbac_role.go | 2 +- go-controller/pkg/sbdb/sb_global.go | 2 +- go-controller/pkg/sbdb/service_monitor.go | 2 +- go-controller/pkg/sbdb/ssl.go | 2 +- go-controller/pkg/sbdb/static_mac_binding.go | 2 +- .../pkg/testing/libovsdb/libovsdb.go | 16 ++++++------- .../pkg/testing/libovsdb/matchers.go | 2 +- go-controller/pkg/testing/libovsdb/ops.go | 2 +- go-controller/pkg/util/ovs.go | 12 +++++----- go-controller/pkg/vswitchd/autoattach.go | 2 +- go-controller/pkg/vswitchd/bridge.go | 2 +- go-controller/pkg/vswitchd/controller.go | 2 +- .../pkg/vswitchd/ct_timeout_policy.go | 2 +- go-controller/pkg/vswitchd/ct_zone.go | 2 +- go-controller/pkg/vswitchd/datapath.go | 2 +- .../pkg/vswitchd/flow_sample_collector_set.go | 2 +- go-controller/pkg/vswitchd/flow_table.go | 2 +- go-controller/pkg/vswitchd/interface.go | 2 +- go-controller/pkg/vswitchd/ipfix.go | 2 +- go-controller/pkg/vswitchd/manager.go | 2 +- go-controller/pkg/vswitchd/mirror.go | 2 +- go-controller/pkg/vswitchd/model.go | 4 ++-- go-controller/pkg/vswitchd/netflow.go | 2 +- go-controller/pkg/vswitchd/open_vswitch.go | 2 +- go-controller/pkg/vswitchd/port.go | 2 +- go-controller/pkg/vswitchd/qos.go | 2 +- go-controller/pkg/vswitchd/queue.go | 2 +- go-controller/pkg/vswitchd/sflow.go | 2 +- go-controller/pkg/vswitchd/ssl.go | 2 +- .../libovsdb/LICENSE | 0 .../libovsdb/NOTICE | 0 .../libovsdb/cache/cache.go | 8 +++---- .../libovsdb/cache/doc.go | 0 .../libovsdb/cache/uuidset.go | 0 .../libovsdb/client/api.go | 6 ++--- .../libovsdb/client/api_test_model.go | 6 ++--- .../libovsdb/client/client.go | 10 ++++---- .../libovsdb/client/condition.go | 8 +++---- .../libovsdb/client/config.go | 0 .../libovsdb/client/doc.go | 0 .../libovsdb/client/metrics.go | 0 .../libovsdb/client/monitor.go | 4 ++-- .../libovsdb/client/options.go | 0 .../libovsdb/database/database.go | 4 ++-- .../libovsdb/database/doc.go | 0 .../libovsdb/database/inmemory/doc.go | 0 .../libovsdb/database/inmemory/inmemory.go | 10 ++++---- .../libovsdb/database/references.go | 0 .../libovsdb/database/transaction/doc.go | 0 .../libovsdb/database/transaction/errors.go | 2 +- .../database/transaction/transaction.go | 10 ++++---- .../libovsdb/mapper/info.go | 2 +- .../libovsdb/mapper/mapper.go | 2 +- .../libovsdb/model/client.go | 4 ++-- .../libovsdb/model/database.go | 4 ++-- .../libovsdb/model/model.go | 2 +- .../libovsdb/ovsdb/bindings.go | 0 .../libovsdb/ovsdb/condition.go | 0 .../libovsdb/ovsdb/error.go | 0 .../libovsdb/ovsdb/map.go | 0 .../libovsdb/ovsdb/monitor_select.go | 0 .../libovsdb/ovsdb/mutation.go | 0 .../libovsdb/ovsdb/named_uuid.go | 0 .../libovsdb/ovsdb/notation.go | 0 .../libovsdb/ovsdb/row.go | 0 .../libovsdb/ovsdb/rpc.go | 0 .../libovsdb/ovsdb/schema.go | 0 .../libovsdb/ovsdb/serverdb/.gitignore | 0 .../libovsdb/ovsdb/serverdb/database.go | 2 +- .../libovsdb/ovsdb/serverdb/gen.go | 0 .../libovsdb/ovsdb/serverdb/model.go | 4 ++-- .../libovsdb/ovsdb/set.go | 0 .../libovsdb/ovsdb/update3.go | 0 .../libovsdb/ovsdb/updates.go | 0 .../libovsdb/ovsdb/updates2.go | 0 .../libovsdb/ovsdb/uuid.go | 0 .../libovsdb/server/doc.go | 0 .../libovsdb/server/monitor.go | 4 ++-- .../libovsdb/server/server.go | 6 ++--- .../libovsdb/updates/difference.go | 0 .../libovsdb/updates/doc.go | 0 .../libovsdb/updates/merge.go | 2 +- .../libovsdb/updates/mutate.go | 2 +- .../libovsdb/updates/references.go | 6 ++--- .../libovsdb/updates/updates.go | 6 ++--- go-controller/vendor/modules.txt | 24 +++++++++---------- 281 files changed, 373 insertions(+), 371 deletions(-) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/LICENSE (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/NOTICE (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/cache/cache.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/cache/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/cache/uuidset.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/api.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/api_test_model.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/client.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/condition.go (97%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/config.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/metrics.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/monitor.go (97%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/client/options.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/database.go (93%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/inmemory/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/inmemory/inmemory.go (93%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/references.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/transaction/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/transaction/errors.go (91%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/database/transaction/transaction.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/mapper/info.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/mapper/mapper.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/model/client.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/model/database.go (97%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/model/model.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/bindings.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/condition.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/error.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/map.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/monitor_select.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/mutation.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/named_uuid.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/notation.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/row.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/rpc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/schema.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/serverdb/.gitignore (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/serverdb/database.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/serverdb/gen.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/serverdb/model.go (95%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/set.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/update3.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/updates.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/updates2.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/ovsdb/uuid.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/server/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/server/monitor.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/server/server.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/difference.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/doc.go (100%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/merge.go (98%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/mutate.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/references.go (99%) rename go-controller/vendor/github.com/{ovn-org => ovn-kubernetes}/libovsdb/updates/updates.go (99%) diff --git a/docs/developer-guide/developer.md b/docs/developer-guide/developer.md index d67bd62a9d..3dc7d0dfb8 100644 --- a/docs/developer-guide/developer.md +++ b/docs/developer-guide/developer.md @@ -5,11 +5,12 @@ This file aims to have information that is useful to the people contributing to ## Generating ovsdb bindings using modelgen In order to generate the latest NBDB and SBDB bindings, we have a tool called `modelgen` -which lives in the libovsdb repo: https://github.com/ovn-org/libovsdb#modelgen. It is a +which lives in the libovsdb repo: https://github.com/ovn-kubernetes/libovsdb#modelgen. It is a [code generator](https://go.dev/blog/generate) that uses `pkg/nbdb/gen.go` and `pkg/sbdb/gen.go` files to auto-generate the models and additional code like deep-copy methods. In order to use this tool do the following: + ``` $ cd go-controller/ $ make modelgen diff --git a/go-controller/.golangci.yml b/go-controller/.golangci.yml index 8f60edab95..d381676a37 100644 --- a/go-controller/.golangci.yml +++ b/go-controller/.golangci.yml @@ -33,6 +33,7 @@ linters-settings: - default - prefix(k8s.io,sigs.k8s.io) - prefix(github.com/ovn-org) + - prefix(github.com/ovn-kubernetes) - localmodule - dot @@ -41,7 +42,7 @@ linters-settings: disable: - fieldalignment - shadow - + importas: no-unaliased: true alias: @@ -57,9 +58,9 @@ linters-settings: - pkg: sigs.k8s.io/controller-runtime alias: ctrl # Other frequently used deps - - pkg: github.com/ovn-org/libovsdb/ovsdb + - pkg: github.com/ovn-kubernetes/libovsdb/ovsdb alias: "" - + revive: rules: # TODO: enable recommended (default) revive rules diff --git a/go-controller/cmd/ovnkube/ovnkube.go b/go-controller/cmd/ovnkube/ovnkube.go index 39548a5c21..8021297d14 100644 --- a/go-controller/cmd/ovnkube/ovnkube.go +++ b/go-controller/cmd/ovnkube/ovnkube.go @@ -22,7 +22,7 @@ import ( "k8s.io/klog/v2" kexec "k8s.io/utils/exec" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/clustermanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/go.mod b/go-controller/go.mod index 7868b6ca26..f40f5001e2 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -38,7 +38,7 @@ require ( github.com/onsi/gomega v1.36.1 github.com/openshift/api v0.0.0-20231120222239-b86761094ee3 github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a - github.com/ovn-org/libovsdb v0.7.1-0.20240820095311-ce1951614a20 + github.com/ovn-kubernetes/libovsdb v0.8.0 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.6.1 github.com/safchain/ethtool v0.3.1-0.20231027162144-83e5e0097c91 diff --git a/go-controller/go.sum b/go-controller/go.sum index 3dcc3208b3..50d5e1270d 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -639,8 +639,8 @@ github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a h1:4FVrw8hz0Wb github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a/go.mod h1:arApQobmOjZqtxw44TwnQdUCH+t9DgZ8geYPFqksHws= github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= -github.com/ovn-org/libovsdb v0.7.1-0.20240820095311-ce1951614a20 h1:OoDvzyaK7F/ZANIIFOgb4Haj7mye3Hle0fYZZNdidSs= -github.com/ovn-org/libovsdb v0.7.1-0.20240820095311-ce1951614a20/go.mod h1:dJbxEaalQl83nn904K32FaMjlH/qOObZ0bj4ejQ78AI= +github.com/ovn-kubernetes/libovsdb v0.8.0 h1:cWhqWb5rCiS3yTJ6VJ7s85cElE1NWWJ2XksPGLd5WII= +github.com/ovn-kubernetes/libovsdb v0.8.0/go.mod h1:8nqWvM5pjHRbI5K6Uy/yuA5MdhCnGhNFH5fsSjZD8Rc= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/go-controller/hack/update-modelgen.sh b/go-controller/hack/update-modelgen.sh index 18c6f1a3cd..c17239f669 100755 --- a/go-controller/hack/update-modelgen.sh +++ b/go-controller/hack/update-modelgen.sh @@ -4,12 +4,12 @@ set -o pipefail # generate ovsdb bindings if ! ( command -v modelgen > /dev/null ); then - echo "modelgen not found, installing github.com/ovn-org/libovsdb/cmd/modelgen" + echo "modelgen not found, installing github.com/ovn-kubernetes/libovsdb/cmd/modelgen" olddir="${PWD}" builddir="$(mktemp -d)" cd "${builddir}" # ensure the hash value is not outdated, if wrong bindings are being generated re-install modelgen - GO111MODULE=on go install github.com/ovn-org/libovsdb/cmd/modelgen@v0.7.0 + GO111MODULE=on go install github.com/ovn-kubernetes/libovsdb/cmd/modelgen@v0.8.0 cd "${olddir}" if [[ "${builddir}" == /tmp/* ]]; then #paranoia rm -rf "${builddir}" diff --git a/go-controller/observability-lib/ovsdb/bridge.go b/go-controller/observability-lib/ovsdb/bridge.go index d0135c4886..d918918bb0 100644 --- a/go-controller/observability-lib/ovsdb/bridge.go +++ b/go-controller/observability-lib/ovsdb/bridge.go @@ -3,7 +3,7 @@ package ovsdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const BridgeTable = "Bridge" diff --git a/go-controller/observability-lib/ovsdb/flow_sample_collector_set.go b/go-controller/observability-lib/ovsdb/flow_sample_collector_set.go index 57a26e805d..b4b67f6055 100644 --- a/go-controller/observability-lib/ovsdb/flow_sample_collector_set.go +++ b/go-controller/observability-lib/ovsdb/flow_sample_collector_set.go @@ -3,7 +3,7 @@ package ovsdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const FlowSampleCollectorSetTable = "Flow_Sample_Collector_Set" diff --git a/go-controller/observability-lib/ovsdb/interface.go b/go-controller/observability-lib/ovsdb/interface.go index e9f350995c..9e59b20738 100644 --- a/go-controller/observability-lib/ovsdb/interface.go +++ b/go-controller/observability-lib/ovsdb/interface.go @@ -3,7 +3,7 @@ package ovsdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const InterfaceTable = "Interface" diff --git a/go-controller/observability-lib/ovsdb/observ_model.go b/go-controller/observability-lib/ovsdb/observ_model.go index 22547a3f8c..4667acf5d5 100644 --- a/go-controller/observability-lib/ovsdb/observ_model.go +++ b/go-controller/observability-lib/ovsdb/observ_model.go @@ -1,6 +1,6 @@ package ovsdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" // ObservDatabaseModel returns the DatabaseModel object to be used by observability library. func ObservDatabaseModel() (model.ClientDBModel, error) { diff --git a/go-controller/observability-lib/sampledecoder/db_client.go b/go-controller/observability-lib/sampledecoder/db_client.go index 5587646356..9d65645601 100644 --- a/go-controller/observability-lib/sampledecoder/db_client.go +++ b/go-controller/observability-lib/sampledecoder/db_client.go @@ -10,8 +10,8 @@ import ( "k8s.io/klog/v2/textlogger" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" "github.com/ovn-org/ovn-kubernetes/go-controller/observability-lib/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/observability-lib/sampledecoder/sample_decoder.go b/go-controller/observability-lib/sampledecoder/sample_decoder.go index 341a0d1c18..d92c03b3e8 100644 --- a/go-controller/observability-lib/sampledecoder/sample_decoder.go +++ b/go-controller/observability-lib/sampledecoder/sample_decoder.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/observability-lib/model" "github.com/ovn-org/ovn-kubernetes/go-controller/observability-lib/ovsdb" diff --git a/go-controller/pkg/cni/cni.go b/go-controller/pkg/cni/cni.go index e2cc865265..faf800d52e 100644 --- a/go-controller/pkg/cni/cni.go +++ b/go-controller/pkg/cni/cni.go @@ -15,7 +15,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/udn" diff --git a/go-controller/pkg/cni/cni_test.go b/go-controller/pkg/cni/cni_test.go index ed3f5be1f0..778c83c03c 100644 --- a/go-controller/pkg/cni/cni_test.go +++ b/go-controller/pkg/cni/cni_test.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes/fake" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/cni/cniserver.go b/go-controller/pkg/cni/cniserver.go index 4ec851bbb7..17b888dd63 100644 --- a/go-controller/pkg/cni/cniserver.go +++ b/go-controller/pkg/cni/cniserver.go @@ -16,7 +16,7 @@ import ( corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/cni/cniserver_test.go b/go-controller/pkg/cni/cniserver_test.go index 65c9962e2a..6edfbf49e2 100644 --- a/go-controller/pkg/cni/cniserver_test.go +++ b/go-controller/pkg/cni/cniserver_test.go @@ -23,7 +23,7 @@ import ( "k8s.io/client-go/kubernetes/fake" utiltesting "k8s.io/client-go/util/testing" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" diff --git a/go-controller/pkg/cni/types.go b/go-controller/pkg/cni/types.go index f6c5e10727..7a20787d73 100644 --- a/go-controller/pkg/cni/types.go +++ b/go-controller/pkg/cni/types.go @@ -14,7 +14,7 @@ import ( corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" diff --git a/go-controller/pkg/controllermanager/controller_manager.go b/go-controller/pkg/controllermanager/controller_manager.go index 6b5f1e9f89..27db274d05 100644 --- a/go-controller/pkg/controllermanager/controller_manager.go +++ b/go-controller/pkg/controllermanager/controller_manager.go @@ -14,7 +14,7 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/controllermanager/node_controller_manager.go b/go-controller/pkg/controllermanager/node_controller_manager.go index d3af2b3b40..aca81d30f7 100644 --- a/go-controller/pkg/controllermanager/node_controller_manager.go +++ b/go-controller/pkg/controllermanager/node_controller_manager.go @@ -13,7 +13,7 @@ import ( "k8s.io/klog/v2" kexec "k8s.io/utils/exec" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/kubevirt/dhcp.go b/go-controller/pkg/kubevirt/dhcp.go index 5e8534bd71..51cb600ebd 100644 --- a/go-controller/pkg/kubevirt/dhcp.go +++ b/go-controller/pkg/kubevirt/dhcp.go @@ -9,7 +9,7 @@ import ( ktypes "k8s.io/apimachinery/pkg/types" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/kubevirt/pod.go b/go-controller/pkg/kubevirt/pod.go index 48cd5ed1c2..b0f43ffcbf 100644 --- a/go-controller/pkg/kubevirt/pod.go +++ b/go-controller/pkg/kubevirt/pod.go @@ -13,7 +13,7 @@ import ( ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" diff --git a/go-controller/pkg/kubevirt/router.go b/go-controller/pkg/kubevirt/router.go index 06a6499e1f..ed4a5dfab2 100644 --- a/go-controller/pkg/kubevirt/router.go +++ b/go-controller/pkg/kubevirt/router.go @@ -8,7 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/libovsdb/libovsdb.go b/go-controller/pkg/libovsdb/libovsdb.go index 40bd1298fe..860ec26698 100644 --- a/go-controller/pkg/libovsdb/libovsdb.go +++ b/go-controller/pkg/libovsdb/libovsdb.go @@ -23,8 +23,8 @@ import ( "k8s.io/klog/v2" "k8s.io/klog/v2/textlogger" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/acl.go b/go-controller/pkg/libovsdb/ops/acl.go index cd671595b3..f9987fbeb7 100644 --- a/go-controller/pkg/libovsdb/ops/acl.go +++ b/go-controller/pkg/libovsdb/ops/acl.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/address_set.go b/go-controller/pkg/libovsdb/ops/address_set.go index c6a8ce16e7..90d251bbb5 100644 --- a/go-controller/pkg/libovsdb/ops/address_set.go +++ b/go-controller/pkg/libovsdb/ops/address_set.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" @@ -18,7 +18,7 @@ type addressSetPredicate func(*nbdb.AddressSet) bool // The purpose is to prevent libovsdb interpreting non-nil empty maps/slices // as default and thus being filtered out of the update. The intention is to // use non-nil empty maps/slices to clear them out in the update. -// See: https://github.com/ovn-org/libovsdb/issues/226 +// See: https://github.com/ovn-kubernetes/libovsdb/issues/226 func getNonZeroAddressSetMutableFields(as *nbdb.AddressSet) []interface{} { fields := []interface{}{} if as.Addresses != nil { diff --git a/go-controller/pkg/libovsdb/ops/chassis.go b/go-controller/pkg/libovsdb/ops/chassis.go index 0196da3463..83a2d6a3c2 100644 --- a/go-controller/pkg/libovsdb/ops/chassis.go +++ b/go-controller/pkg/libovsdb/ops/chassis.go @@ -5,7 +5,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" diff --git a/go-controller/pkg/libovsdb/ops/copp.go b/go-controller/pkg/libovsdb/ops/copp.go index dac95c5c0e..a0f8697b1b 100644 --- a/go-controller/pkg/libovsdb/ops/copp.go +++ b/go-controller/pkg/libovsdb/ops/copp.go @@ -1,8 +1,8 @@ package ops import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/dhcp.go b/go-controller/pkg/libovsdb/ops/dhcp.go index 94ab12800a..cb03fde11c 100644 --- a/go-controller/pkg/libovsdb/ops/dhcp.go +++ b/go-controller/pkg/libovsdb/ops/dhcp.go @@ -1,8 +1,8 @@ package ops import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/lbgroup.go b/go-controller/pkg/libovsdb/ops/lbgroup.go index 8ab75c4d15..829cce5003 100644 --- a/go-controller/pkg/libovsdb/ops/lbgroup.go +++ b/go-controller/pkg/libovsdb/ops/lbgroup.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/loadbalancer.go b/go-controller/pkg/libovsdb/ops/loadbalancer.go index 221e980b0b..b984c99fce 100644 --- a/go-controller/pkg/libovsdb/ops/loadbalancer.go +++ b/go-controller/pkg/libovsdb/ops/loadbalancer.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" @@ -16,7 +16,7 @@ import ( // The purpose is to prevent libovsdb interpreting non-nil empty maps/slices // as default and thus being filtered out of the update. The intention is to // use non-nil empty maps/slices to clear them out in the update. -// See: https://github.com/ovn-org/libovsdb/issues/226 +// See: https://github.com/ovn-kubernetes/libovsdb/issues/226 func getNonZeroLoadBalancerMutableFields(lb *nbdb.LoadBalancer) []interface{} { fields := []interface{}{} if lb.Name != "" { diff --git a/go-controller/pkg/libovsdb/ops/mac_binding.go b/go-controller/pkg/libovsdb/ops/mac_binding.go index 1f7a76ba8b..0e1fe6718d 100644 --- a/go-controller/pkg/libovsdb/ops/mac_binding.go +++ b/go-controller/pkg/libovsdb/ops/mac_binding.go @@ -1,7 +1,7 @@ package ops import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/meter.go b/go-controller/pkg/libovsdb/ops/meter.go index d08b27ecc1..666da32fa8 100644 --- a/go-controller/pkg/libovsdb/ops/meter.go +++ b/go-controller/pkg/libovsdb/ops/meter.go @@ -5,8 +5,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/model.go b/go-controller/pkg/libovsdb/ops/model.go index 76b525fa48..f266a16a64 100644 --- a/go-controller/pkg/libovsdb/ops/model.go +++ b/go-controller/pkg/libovsdb/ops/model.go @@ -4,9 +4,9 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" diff --git a/go-controller/pkg/libovsdb/ops/model_client.go b/go-controller/pkg/libovsdb/ops/model_client.go index 0668c20399..bf72f086d9 100644 --- a/go-controller/pkg/libovsdb/ops/model_client.go +++ b/go-controller/pkg/libovsdb/ops/model_client.go @@ -8,9 +8,9 @@ import ( "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" diff --git a/go-controller/pkg/libovsdb/ops/model_client_test.go b/go-controller/pkg/libovsdb/ops/model_client_test.go index 0ab218c631..52471d6408 100644 --- a/go-controller/pkg/libovsdb/ops/model_client_test.go +++ b/go-controller/pkg/libovsdb/ops/model_client_test.go @@ -8,9 +8,9 @@ import ( "github.com/onsi/gomega/types" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" diff --git a/go-controller/pkg/libovsdb/ops/nb_global.go b/go-controller/pkg/libovsdb/ops/nb_global.go index 88d962af0d..dc03be511e 100644 --- a/go-controller/pkg/libovsdb/ops/nb_global.go +++ b/go-controller/pkg/libovsdb/ops/nb_global.go @@ -1,7 +1,7 @@ package ops import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/ovs/bridge.go b/go-controller/pkg/libovsdb/ops/ovs/bridge.go index d109f2b1b6..aa2deeb673 100644 --- a/go-controller/pkg/libovsdb/ops/ovs/bridge.go +++ b/go-controller/pkg/libovsdb/ops/ovs/bridge.go @@ -3,7 +3,7 @@ package ovs import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/vswitchd" diff --git a/go-controller/pkg/libovsdb/ops/ovs/interface.go b/go-controller/pkg/libovsdb/ops/ovs/interface.go index 797e7421ca..41259ef5a4 100644 --- a/go-controller/pkg/libovsdb/ops/ovs/interface.go +++ b/go-controller/pkg/libovsdb/ops/ovs/interface.go @@ -3,7 +3,7 @@ package ovs import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/vswitchd" diff --git a/go-controller/pkg/libovsdb/ops/ovs/openvswitch.go b/go-controller/pkg/libovsdb/ops/ovs/openvswitch.go index c6dd53a89f..a19df4e3fa 100644 --- a/go-controller/pkg/libovsdb/ops/ovs/openvswitch.go +++ b/go-controller/pkg/libovsdb/ops/ovs/openvswitch.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/vswitchd" diff --git a/go-controller/pkg/libovsdb/ops/portbinding.go b/go-controller/pkg/libovsdb/ops/portbinding.go index 861a63cb95..0267a794c0 100644 --- a/go-controller/pkg/libovsdb/ops/portbinding.go +++ b/go-controller/pkg/libovsdb/ops/portbinding.go @@ -3,7 +3,7 @@ package ops import ( "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/portgroup.go b/go-controller/pkg/libovsdb/ops/portgroup.go index 37a6a782af..8a7cfb27f1 100644 --- a/go-controller/pkg/libovsdb/ops/portgroup.go +++ b/go-controller/pkg/libovsdb/ops/portgroup.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/qos.go b/go-controller/pkg/libovsdb/ops/qos.go index 21d6a2f7f8..cfc1d0900a 100644 --- a/go-controller/pkg/libovsdb/ops/qos.go +++ b/go-controller/pkg/libovsdb/ops/qos.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go index df87307918..18b3931a1f 100644 --- a/go-controller/pkg/libovsdb/ops/router.go +++ b/go-controller/pkg/libovsdb/ops/router.go @@ -8,8 +8,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/sample.go b/go-controller/pkg/libovsdb/ops/sample.go index cb0ddbc6bf..8e7799fce3 100644 --- a/go-controller/pkg/libovsdb/ops/sample.go +++ b/go-controller/pkg/libovsdb/ops/sample.go @@ -5,9 +5,9 @@ import ( "golang.org/x/net/context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/sb_global.go b/go-controller/pkg/libovsdb/ops/sb_global.go index 3fe14bf42d..28ee3ecad2 100644 --- a/go-controller/pkg/libovsdb/ops/sb_global.go +++ b/go-controller/pkg/libovsdb/ops/sb_global.go @@ -1,7 +1,7 @@ package ops import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" ) diff --git a/go-controller/pkg/libovsdb/ops/switch.go b/go-controller/pkg/libovsdb/ops/switch.go index 01f724b45d..4136f96bba 100644 --- a/go-controller/pkg/libovsdb/ops/switch.go +++ b/go-controller/pkg/libovsdb/ops/switch.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/template_var.go b/go-controller/pkg/libovsdb/ops/template_var.go index 9c449d5f6b..ff4edc2f92 100644 --- a/go-controller/pkg/libovsdb/ops/template_var.go +++ b/go-controller/pkg/libovsdb/ops/template_var.go @@ -3,8 +3,8 @@ package ops import ( "context" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/ops/transact.go b/go-controller/pkg/libovsdb/ops/transact.go index 312cfdaffa..37aaf6808d 100644 --- a/go-controller/pkg/libovsdb/ops/transact.go +++ b/go-controller/pkg/libovsdb/ops/transact.go @@ -9,9 +9,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" ) diff --git a/go-controller/pkg/libovsdb/util/acl.go b/go-controller/pkg/libovsdb/util/acl.go index dbb6c2b3e5..7169b57798 100644 --- a/go-controller/pkg/libovsdb/util/acl.go +++ b/go-controller/pkg/libovsdb/util/acl.go @@ -7,7 +7,7 @@ import ( corev1 "k8s.io/api/core/v1" knet "k8s.io/api/networking/v1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/util/address_set.go b/go-controller/pkg/libovsdb/util/address_set.go index e4328d43a2..085abf2da6 100644 --- a/go-controller/pkg/libovsdb/util/address_set.go +++ b/go-controller/pkg/libovsdb/util/address_set.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/util/mac_binding.go b/go-controller/pkg/libovsdb/util/mac_binding.go index e6f11d8347..d6410a9b65 100644 --- a/go-controller/pkg/libovsdb/util/mac_binding.go +++ b/go-controller/pkg/libovsdb/util/mac_binding.go @@ -3,9 +3,9 @@ package util import ( "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" diff --git a/go-controller/pkg/libovsdb/util/metric.go b/go-controller/pkg/libovsdb/util/metric.go index 06c787f5fe..89015f343e 100644 --- a/go-controller/pkg/libovsdb/util/metric.go +++ b/go-controller/pkg/libovsdb/util/metric.go @@ -3,7 +3,7 @@ package util import ( "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/util/nb_global.go b/go-controller/pkg/libovsdb/util/nb_global.go index 57d9c69b97..d1bc11f0a2 100644 --- a/go-controller/pkg/libovsdb/util/nb_global.go +++ b/go-controller/pkg/libovsdb/util/nb_global.go @@ -3,7 +3,7 @@ package util import ( "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/util/port.go b/go-controller/pkg/libovsdb/util/port.go index 8e5cbe616f..bc82042419 100644 --- a/go-controller/pkg/libovsdb/util/port.go +++ b/go-controller/pkg/libovsdb/util/port.go @@ -5,7 +5,7 @@ import ( "net" "strings" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/libovsdb/util/router.go b/go-controller/pkg/libovsdb/util/router.go index 12ee755d28..b316fea0e3 100644 --- a/go-controller/pkg/libovsdb/util/router.go +++ b/go-controller/pkg/libovsdb/util/router.go @@ -8,7 +8,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/libovsdb/util/switch.go b/go-controller/pkg/libovsdb/util/switch.go index c0a3b0ca52..c3b1eb9e02 100644 --- a/go-controller/pkg/libovsdb/util/switch.go +++ b/go-controller/pkg/libovsdb/util/switch.go @@ -9,7 +9,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/metrics/metrics.go b/go-controller/pkg/metrics/metrics.go index ea86a65c0d..b8e9d736c7 100644 --- a/go-controller/pkg/metrics/metrics.go +++ b/go-controller/pkg/metrics/metrics.go @@ -24,7 +24,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" diff --git a/go-controller/pkg/metrics/ovn.go b/go-controller/pkg/metrics/ovn.go index 63f057e38f..51510fd7f9 100644 --- a/go-controller/pkg/metrics/ovn.go +++ b/go-controller/pkg/metrics/ovn.go @@ -9,7 +9,7 @@ import ( "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovsops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops/ovs" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" diff --git a/go-controller/pkg/metrics/ovnkube_controller.go b/go-controller/pkg/metrics/ovnkube_controller.go index a4cb9fd693..30c846d07c 100644 --- a/go-controller/pkg/metrics/ovnkube_controller.go +++ b/go-controller/pkg/metrics/ovnkube_controller.go @@ -16,9 +16,9 @@ import ( "k8s.io/client-go/util/workqueue" klog "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/cache" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/cache" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/metrics/ovs.go b/go-controller/pkg/metrics/ovs.go index 455142ae6b..718fa031e7 100644 --- a/go-controller/pkg/metrics/ovs.go +++ b/go-controller/pkg/metrics/ovs.go @@ -14,7 +14,7 @@ import ( "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" ovsops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops/ovs" diff --git a/go-controller/pkg/metrics/recorders/duration.go b/go-controller/pkg/metrics/recorders/duration.go index c0ae704e8f..4376283c20 100644 --- a/go-controller/pkg/metrics/recorders/duration.go +++ b/go-controller/pkg/metrics/recorders/duration.go @@ -11,10 +11,10 @@ import ( "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/cache" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/cache" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/metrics/recorders/duration_test.go b/go-controller/pkg/metrics/recorders/duration_test.go index ee436d8d1b..2e725f99c3 100644 --- a/go-controller/pkg/metrics/recorders/duration_test.go +++ b/go-controller/pkg/metrics/recorders/duration_test.go @@ -12,7 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" fakeclientgo "k8s.io/client-go/kubernetes/fake" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" egressfirewallfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/clientset/versioned/fake" egressipfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1/apis/clientset/versioned/fake" diff --git a/go-controller/pkg/nbdb/acl.go b/go-controller/pkg/nbdb/acl.go index 0c2840c178..5415af620b 100644 --- a/go-controller/pkg/nbdb/acl.go +++ b/go-controller/pkg/nbdb/acl.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ACLTable = "ACL" diff --git a/go-controller/pkg/nbdb/address_set.go b/go-controller/pkg/nbdb/address_set.go index e8a836e2d1..be37eaf40d 100644 --- a/go-controller/pkg/nbdb/address_set.go +++ b/go-controller/pkg/nbdb/address_set.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const AddressSetTable = "Address_Set" diff --git a/go-controller/pkg/nbdb/bfd.go b/go-controller/pkg/nbdb/bfd.go index 46646e81a7..4211ceae80 100644 --- a/go-controller/pkg/nbdb/bfd.go +++ b/go-controller/pkg/nbdb/bfd.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const BFDTable = "BFD" diff --git a/go-controller/pkg/nbdb/chassis_template_var.go b/go-controller/pkg/nbdb/chassis_template_var.go index 602c3f5223..59c61d07de 100644 --- a/go-controller/pkg/nbdb/chassis_template_var.go +++ b/go-controller/pkg/nbdb/chassis_template_var.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ChassisTemplateVarTable = "Chassis_Template_Var" diff --git a/go-controller/pkg/nbdb/connection.go b/go-controller/pkg/nbdb/connection.go index baf6da344b..da2aa4bca3 100644 --- a/go-controller/pkg/nbdb/connection.go +++ b/go-controller/pkg/nbdb/connection.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ConnectionTable = "Connection" diff --git a/go-controller/pkg/nbdb/copp.go b/go-controller/pkg/nbdb/copp.go index 1e146b657e..54bbc841f6 100644 --- a/go-controller/pkg/nbdb/copp.go +++ b/go-controller/pkg/nbdb/copp.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const CoppTable = "Copp" diff --git a/go-controller/pkg/nbdb/dhcp_options.go b/go-controller/pkg/nbdb/dhcp_options.go index fd68ebee2d..7b58c1fe35 100644 --- a/go-controller/pkg/nbdb/dhcp_options.go +++ b/go-controller/pkg/nbdb/dhcp_options.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DHCPOptionsTable = "DHCP_Options" diff --git a/go-controller/pkg/nbdb/dhcp_relay.go b/go-controller/pkg/nbdb/dhcp_relay.go index f0e973ab78..5e10f2aff4 100644 --- a/go-controller/pkg/nbdb/dhcp_relay.go +++ b/go-controller/pkg/nbdb/dhcp_relay.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DHCPRelayTable = "DHCP_Relay" diff --git a/go-controller/pkg/nbdb/dns.go b/go-controller/pkg/nbdb/dns.go index 285d5df280..a15b166a80 100644 --- a/go-controller/pkg/nbdb/dns.go +++ b/go-controller/pkg/nbdb/dns.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DNSTable = "DNS" diff --git a/go-controller/pkg/nbdb/forwarding_group.go b/go-controller/pkg/nbdb/forwarding_group.go index 1a0657559d..82078551d3 100644 --- a/go-controller/pkg/nbdb/forwarding_group.go +++ b/go-controller/pkg/nbdb/forwarding_group.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ForwardingGroupTable = "Forwarding_Group" diff --git a/go-controller/pkg/nbdb/gateway_chassis.go b/go-controller/pkg/nbdb/gateway_chassis.go index 15935847b8..de6925f4c3 100644 --- a/go-controller/pkg/nbdb/gateway_chassis.go +++ b/go-controller/pkg/nbdb/gateway_chassis.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const GatewayChassisTable = "Gateway_Chassis" diff --git a/go-controller/pkg/nbdb/ha_chassis.go b/go-controller/pkg/nbdb/ha_chassis.go index dc09d1ec9d..8c171ddd09 100644 --- a/go-controller/pkg/nbdb/ha_chassis.go +++ b/go-controller/pkg/nbdb/ha_chassis.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const HAChassisTable = "HA_Chassis" diff --git a/go-controller/pkg/nbdb/ha_chassis_group.go b/go-controller/pkg/nbdb/ha_chassis_group.go index bdda95aaf7..6d304fd2e9 100644 --- a/go-controller/pkg/nbdb/ha_chassis_group.go +++ b/go-controller/pkg/nbdb/ha_chassis_group.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const HAChassisGroupTable = "HA_Chassis_Group" diff --git a/go-controller/pkg/nbdb/load_balancer.go b/go-controller/pkg/nbdb/load_balancer.go index 03bcd76011..8bddd25f4a 100644 --- a/go-controller/pkg/nbdb/load_balancer.go +++ b/go-controller/pkg/nbdb/load_balancer.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LoadBalancerTable = "Load_Balancer" diff --git a/go-controller/pkg/nbdb/load_balancer_group.go b/go-controller/pkg/nbdb/load_balancer_group.go index 7759249674..8d39f095ab 100644 --- a/go-controller/pkg/nbdb/load_balancer_group.go +++ b/go-controller/pkg/nbdb/load_balancer_group.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LoadBalancerGroupTable = "Load_Balancer_Group" diff --git a/go-controller/pkg/nbdb/load_balancer_health_check.go b/go-controller/pkg/nbdb/load_balancer_health_check.go index c8163fa007..8fc7020364 100644 --- a/go-controller/pkg/nbdb/load_balancer_health_check.go +++ b/go-controller/pkg/nbdb/load_balancer_health_check.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LoadBalancerHealthCheckTable = "Load_Balancer_Health_Check" diff --git a/go-controller/pkg/nbdb/logical_router.go b/go-controller/pkg/nbdb/logical_router.go index 81c5efaf9d..f303af80fa 100644 --- a/go-controller/pkg/nbdb/logical_router.go +++ b/go-controller/pkg/nbdb/logical_router.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalRouterTable = "Logical_Router" diff --git a/go-controller/pkg/nbdb/logical_router_policy.go b/go-controller/pkg/nbdb/logical_router_policy.go index 7272dbb8ad..51b29ea706 100644 --- a/go-controller/pkg/nbdb/logical_router_policy.go +++ b/go-controller/pkg/nbdb/logical_router_policy.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalRouterPolicyTable = "Logical_Router_Policy" diff --git a/go-controller/pkg/nbdb/logical_router_port.go b/go-controller/pkg/nbdb/logical_router_port.go index d39fe0db42..1d220b82d1 100644 --- a/go-controller/pkg/nbdb/logical_router_port.go +++ b/go-controller/pkg/nbdb/logical_router_port.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalRouterPortTable = "Logical_Router_Port" diff --git a/go-controller/pkg/nbdb/logical_router_static_route.go b/go-controller/pkg/nbdb/logical_router_static_route.go index ce966e5707..205741626c 100644 --- a/go-controller/pkg/nbdb/logical_router_static_route.go +++ b/go-controller/pkg/nbdb/logical_router_static_route.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalRouterStaticRouteTable = "Logical_Router_Static_Route" diff --git a/go-controller/pkg/nbdb/logical_switch.go b/go-controller/pkg/nbdb/logical_switch.go index 50b8214ad3..8a342dd315 100644 --- a/go-controller/pkg/nbdb/logical_switch.go +++ b/go-controller/pkg/nbdb/logical_switch.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalSwitchTable = "Logical_Switch" diff --git a/go-controller/pkg/nbdb/logical_switch_port.go b/go-controller/pkg/nbdb/logical_switch_port.go index c048f76541..b211672bff 100644 --- a/go-controller/pkg/nbdb/logical_switch_port.go +++ b/go-controller/pkg/nbdb/logical_switch_port.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalSwitchPortTable = "Logical_Switch_Port" diff --git a/go-controller/pkg/nbdb/meter.go b/go-controller/pkg/nbdb/meter.go index 09b7e9e6a4..e3a4a713da 100644 --- a/go-controller/pkg/nbdb/meter.go +++ b/go-controller/pkg/nbdb/meter.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MeterTable = "Meter" diff --git a/go-controller/pkg/nbdb/meter_band.go b/go-controller/pkg/nbdb/meter_band.go index 4ef0d901ac..1e1e7ad421 100644 --- a/go-controller/pkg/nbdb/meter_band.go +++ b/go-controller/pkg/nbdb/meter_band.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MeterBandTable = "Meter_Band" diff --git a/go-controller/pkg/nbdb/mirror.go b/go-controller/pkg/nbdb/mirror.go index 57e3b01f6d..352cc238af 100644 --- a/go-controller/pkg/nbdb/mirror.go +++ b/go-controller/pkg/nbdb/mirror.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MirrorTable = "Mirror" diff --git a/go-controller/pkg/nbdb/model.go b/go-controller/pkg/nbdb/model.go index daabac4530..9fbe25db4f 100644 --- a/go-controller/pkg/nbdb/model.go +++ b/go-controller/pkg/nbdb/model.go @@ -6,8 +6,8 @@ package nbdb import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/go-controller/pkg/nbdb/nat.go b/go-controller/pkg/nbdb/nat.go index 4bd1b7ed49..b10bbd25b3 100644 --- a/go-controller/pkg/nbdb/nat.go +++ b/go-controller/pkg/nbdb/nat.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const NATTable = "NAT" diff --git a/go-controller/pkg/nbdb/nb_global.go b/go-controller/pkg/nbdb/nb_global.go index bae9e20f20..3779d259fe 100644 --- a/go-controller/pkg/nbdb/nb_global.go +++ b/go-controller/pkg/nbdb/nb_global.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const NBGlobalTable = "NB_Global" diff --git a/go-controller/pkg/nbdb/port_group.go b/go-controller/pkg/nbdb/port_group.go index bf4fa809bc..525f84d90e 100644 --- a/go-controller/pkg/nbdb/port_group.go +++ b/go-controller/pkg/nbdb/port_group.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const PortGroupTable = "Port_Group" diff --git a/go-controller/pkg/nbdb/qos.go b/go-controller/pkg/nbdb/qos.go index d25322b4b2..3303f61c4d 100644 --- a/go-controller/pkg/nbdb/qos.go +++ b/go-controller/pkg/nbdb/qos.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const QoSTable = "QoS" diff --git a/go-controller/pkg/nbdb/sample.go b/go-controller/pkg/nbdb/sample.go index 639393a1e6..d53ef23825 100644 --- a/go-controller/pkg/nbdb/sample.go +++ b/go-controller/pkg/nbdb/sample.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SampleTable = "Sample" diff --git a/go-controller/pkg/nbdb/sample_collector.go b/go-controller/pkg/nbdb/sample_collector.go index 50f0659040..487465ee0f 100644 --- a/go-controller/pkg/nbdb/sample_collector.go +++ b/go-controller/pkg/nbdb/sample_collector.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SampleCollectorTable = "Sample_Collector" diff --git a/go-controller/pkg/nbdb/sampling_app.go b/go-controller/pkg/nbdb/sampling_app.go index a152b4237d..cd7458da83 100644 --- a/go-controller/pkg/nbdb/sampling_app.go +++ b/go-controller/pkg/nbdb/sampling_app.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SamplingAppTable = "Sampling_App" diff --git a/go-controller/pkg/nbdb/ssl.go b/go-controller/pkg/nbdb/ssl.go index ddaba5d322..847ea8c362 100644 --- a/go-controller/pkg/nbdb/ssl.go +++ b/go-controller/pkg/nbdb/ssl.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SSLTable = "SSL" diff --git a/go-controller/pkg/nbdb/static_mac_binding.go b/go-controller/pkg/nbdb/static_mac_binding.go index 15207e6484..c3397e3e70 100644 --- a/go-controller/pkg/nbdb/static_mac_binding.go +++ b/go-controller/pkg/nbdb/static_mac_binding.go @@ -3,7 +3,7 @@ package nbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const StaticMACBindingTable = "Static_MAC_Binding" diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index f9f3b36ec5..7a75c36984 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -28,7 +28,7 @@ import ( utilnet "k8s.io/utils/net" "sigs.k8s.io/knftables" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" honode "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/controller" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni" diff --git a/go-controller/pkg/observability/observability.go b/go-controller/pkg/observability/observability.go index a3ffbb54f3..9348966f13 100644 --- a/go-controller/pkg/observability/observability.go +++ b/go-controller/pkg/observability/observability.go @@ -10,8 +10,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/observability/observability_test.go b/go-controller/pkg/observability/observability_test.go index cfda506362..a247150093 100644 --- a/go-controller/pkg/observability/observability_test.go +++ b/go-controller/pkg/observability/observability_test.go @@ -4,7 +4,7 @@ import ( "strings" "time" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/address_set/address_set.go b/go-controller/pkg/ovn/address_set/address_set.go index a0b709eafc..ea5e035e22 100644 --- a/go-controller/pkg/ovn/address_set/address_set.go +++ b/go-controller/pkg/ovn/address_set/address_set.go @@ -7,8 +7,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/address_set/address_set_test.go b/go-controller/pkg/ovn/address_set/address_set_test.go index 40dec33d24..4c1c0af814 100644 --- a/go-controller/pkg/ovn/address_set/address_set_test.go +++ b/go-controller/pkg/ovn/address_set/address_set_test.go @@ -5,8 +5,8 @@ import ( "github.com/onsi/gomega" "github.com/urfave/cli/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/address_set/fake_address_set.go b/go-controller/pkg/ovn/address_set/fake_address_set.go index 48f56bb616..2f783b3486 100644 --- a/go-controller/pkg/ovn/address_set/fake_address_set.go +++ b/go-controller/pkg/ovn/address_set/fake_address_set.go @@ -11,7 +11,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/address_set/mocks/AddressSet.go b/go-controller/pkg/ovn/address_set/mocks/AddressSet.go index f8b9761b26..f5dd89448f 100644 --- a/go-controller/pkg/ovn/address_set/mocks/AddressSet.go +++ b/go-controller/pkg/ovn/address_set/mocks/AddressSet.go @@ -3,7 +3,7 @@ package mocks import ( - ovsdb "github.com/ovn-org/libovsdb/ovsdb" + ovsdb "github.com/ovn-kubernetes/libovsdb/ovsdb" mock "github.com/stretchr/testify/mock" ) diff --git a/go-controller/pkg/ovn/address_set/mocks/AddressSetFactory.go b/go-controller/pkg/ovn/address_set/mocks/AddressSetFactory.go index f76d6f132a..0d18215185 100644 --- a/go-controller/pkg/ovn/address_set/mocks/AddressSetFactory.go +++ b/go-controller/pkg/ovn/address_set/mocks/AddressSetFactory.go @@ -8,7 +8,7 @@ import ( ops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" - ovsdb "github.com/ovn-org/libovsdb/ovsdb" + ovsdb "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // AddressSetFactory is an autogenerated mock type for the AddressSetFactory type diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index 51c5c62dec..02c82b172f 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -22,8 +22,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/pod" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/base_network_controller_pods.go b/go-controller/pkg/ovn/base_network_controller_pods.go index c6a105aa2a..1147983e79 100644 --- a/go-controller/pkg/ovn/base_network_controller_pods.go +++ b/go-controller/pkg/ovn/base_network_controller_pods.go @@ -18,8 +18,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ipallocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip" subnetipallocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip/subnet" diff --git a/go-controller/pkg/ovn/base_network_controller_policy.go b/go-controller/pkg/ovn/base_network_controller_policy.go index e0acdafbdc..bf1a253518 100644 --- a/go-controller/pkg/ovn/base_network_controller_policy.go +++ b/go-controller/pkg/ovn/base_network_controller_policy.go @@ -14,8 +14,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index 1b5cfdd5ac..f9c6d0b18f 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -18,8 +18,8 @@ import ( utilnet "k8s.io/utils/net" "k8s.io/utils/ptr" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy.go b/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy.go index 1433544a0b..b1d345bcf4 100644 --- a/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy.go +++ b/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy.go @@ -16,8 +16,8 @@ import ( "k8s.io/klog/v2" anpapi "sigs.k8s.io/network-policy-api/apis/v1alpha1" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" diff --git a/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy_controller.go b/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy_controller.go index 1d230a6a45..080dd22d19 100644 --- a/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy_controller.go +++ b/go-controller/pkg/ovn/controller/admin_network_policy/admin_network_policy_controller.go @@ -21,7 +21,7 @@ import ( anpinformer "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1" anplister "sigs.k8s.io/network-policy-api/pkg/client/listers/apis/v1alpha1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/controller/admin_network_policy/repair.go b/go-controller/pkg/ovn/controller/admin_network_policy/repair.go index 55bf85e71f..84c0cf2a50 100644 --- a/go-controller/pkg/ovn/controller/admin_network_policy/repair.go +++ b/go-controller/pkg/ovn/controller/admin_network_policy/repair.go @@ -9,7 +9,7 @@ import ( "k8s.io/klog/v2" anpapi "sigs.k8s.io/network-policy-api/apis/v1alpha1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/controller/admin_network_policy/status_test.go b/go-controller/pkg/ovn/controller/admin_network_policy/status_test.go index ac63e873e6..6a28fa60d3 100644 --- a/go-controller/pkg/ovn/controller/admin_network_policy/status_test.go +++ b/go-controller/pkg/ovn/controller/admin_network_policy/status_test.go @@ -13,7 +13,7 @@ import ( anpapi "sigs.k8s.io/network-policy-api/apis/v1alpha1" anpfake "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/fake" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go b/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go index 802f31e7bb..2605fad7bc 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go @@ -15,7 +15,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1" diff --git a/go-controller/pkg/ovn/controller/apbroute/master_controller.go b/go-controller/pkg/ovn/controller/apbroute/master_controller.go index 324da524bd..82549c35dd 100644 --- a/go-controller/pkg/ovn/controller/apbroute/master_controller.go +++ b/go-controller/pkg/ovn/controller/apbroute/master_controller.go @@ -14,7 +14,7 @@ import ( corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1" adminpolicybasedrouteapply "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/applyconfiguration/adminpolicybasedroute/v1" diff --git a/go-controller/pkg/ovn/controller/apbroute/network_client.go b/go-controller/pkg/ovn/controller/apbroute/network_client.go index 2f3de1da3a..5faca37f55 100644 --- a/go-controller/pkg/ovn/controller/apbroute/network_client.go +++ b/go-controller/pkg/ovn/controller/apbroute/network_client.go @@ -14,8 +14,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" adminpolicybasedroutelisters "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/listers/adminpolicybasedroute/v1" diff --git a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go index 23f9f8f665..b9c68eb594 100644 --- a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go +++ b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone.go @@ -23,8 +23,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" egressserviceapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressservice/v1" diff --git a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_service.go b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_service.go index e19a3cd77f..192204171b 100644 --- a/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_service.go +++ b/go-controller/pkg/ovn/controller/egressservice/egressservice_zone_service.go @@ -13,7 +13,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/controller/network_qos/network_qos_controller.go b/go-controller/pkg/ovn/controller/network_qos/network_qos_controller.go index 15511e35d8..3a75fc0f30 100644 --- a/go-controller/pkg/ovn/controller/network_qos/network_qos_controller.go +++ b/go-controller/pkg/ovn/controller/network_qos/network_qos_controller.go @@ -21,7 +21,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" networkqosapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/networkqos/v1alpha1" networkqosclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/networkqos/v1alpha1/apis/clientset/versioned" diff --git a/go-controller/pkg/ovn/controller/network_qos/network_qos_ovnnb.go b/go-controller/pkg/ovn/controller/network_qos/network_qos_ovnnb.go index 82eed9b07e..febc4d1953 100644 --- a/go-controller/pkg/ovn/controller/network_qos/network_qos_ovnnb.go +++ b/go-controller/pkg/ovn/controller/network_qos/network_qos_ovnnb.go @@ -6,8 +6,8 @@ import ( "slices" "strconv" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/controller/network_qos/network_qos_test.go b/go-controller/pkg/ovn/controller/network_qos/network_qos_test.go index fd92922479..4d771825ed 100644 --- a/go-controller/pkg/ovn/controller/network_qos/network_qos_test.go +++ b/go-controller/pkg/ovn/controller/network_qos/network_qos_test.go @@ -15,7 +15,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" nqostype "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/networkqos/v1alpha1" diff --git a/go-controller/pkg/ovn/controller/services/loadbalancer.go b/go-controller/pkg/ovn/controller/services/loadbalancer.go index 8c3d1c9114..025bb80d95 100644 --- a/go-controller/pkg/ovn/controller/services/loadbalancer.go +++ b/go-controller/pkg/ovn/controller/services/loadbalancer.go @@ -10,7 +10,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/apis/core" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/controller/services/repair.go b/go-controller/pkg/ovn/controller/services/repair.go index a9d37389fa..169bc64069 100644 --- a/go-controller/pkg/ovn/controller/services/repair.go +++ b/go-controller/pkg/ovn/controller/services/repair.go @@ -11,8 +11,8 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/controller/services/services_controller.go b/go-controller/pkg/ovn/controller/services/services_controller.go index e03ad40b5c..664af03536 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller.go +++ b/go-controller/pkg/ovn/controller/services/services_controller.go @@ -28,8 +28,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" globalconfig "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/controller/services/services_controller_test.go b/go-controller/pkg/ovn/controller/services/services_controller_test.go index 6937f6beca..777d175628 100644 --- a/go-controller/pkg/ovn/controller/services/services_controller_test.go +++ b/go-controller/pkg/ovn/controller/services/services_controller_test.go @@ -21,7 +21,7 @@ import ( utilnet "k8s.io/utils/net" "k8s.io/utils/ptr" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/controller/services/svc_template_var.go b/go-controller/pkg/ovn/controller/services/svc_template_var.go index 2ffcd03cc7..8cf4ee640b 100644 --- a/go-controller/pkg/ovn/controller/services/svc_template_var.go +++ b/go-controller/pkg/ovn/controller/services/svc_template_var.go @@ -8,8 +8,8 @@ import ( corev1 "k8s.io/api/core/v1" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc.go b/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc.go index 59ac681e07..c96bc3a36d 100644 --- a/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc.go +++ b/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc.go @@ -17,7 +17,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc_test.go b/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc_test.go index fd0e09545e..7d3cd9e72f 100644 --- a/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc_test.go +++ b/go-controller/pkg/ovn/controller/udnenabledsvc/udn_enabled_svc_test.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/controller/unidling/unidle.go b/go-controller/pkg/ovn/controller/unidling/unidle.go index bcad0edf4c..d3c65e10fa 100644 --- a/go-controller/pkg/ovn/controller/unidling/unidle.go +++ b/go-controller/pkg/ovn/controller/unidling/unidle.go @@ -12,10 +12,10 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" - libovsdbcache "github.com/ovn-org/libovsdb/cache" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbcache "github.com/ovn-kubernetes/libovsdb/cache" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" diff --git a/go-controller/pkg/ovn/controller/unidling/unidle_test.go b/go-controller/pkg/ovn/controller/unidling/unidle_test.go index 039968d696..3317b65c00 100644 --- a/go-controller/pkg/ovn/controller/unidling/unidle_test.go +++ b/go-controller/pkg/ovn/controller/unidling/unidle_test.go @@ -13,7 +13,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" diff --git a/go-controller/pkg/ovn/copp.go b/go-controller/pkg/ovn/copp.go index 4afc0bba76..39f2f092d4 100644 --- a/go-controller/pkg/ovn/copp.go +++ b/go-controller/pkg/ovn/copp.go @@ -3,7 +3,7 @@ package ovn import ( "fmt" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index d6a0231ea6..ed79067e8e 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -366,7 +366,7 @@ func (oc *DefaultNetworkController) init() error { } klog.V(5).Infof("Existing number of nodes: %d", len(existingNodes)) - // FIXME: When https://github.com/ovn-org/libovsdb/issues/235 is fixed, + // FIXME: When https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed, // use IsTableSupported(nbdb.LoadBalancerGroup). if _, _, err := util.RunOVNNbctl("--columns=_uuid", "list", "Load_Balancer_Group"); err != nil { klog.Warningf("Load Balancer Group support enabled, however version of OVN in use does not support Load Balancer Groups.") diff --git a/go-controller/pkg/ovn/dns_name_resolver/dns.go b/go-controller/pkg/ovn/dns_name_resolver/dns.go index f2ae3ddc2e..17b6b76471 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/dns.go +++ b/go-controller/pkg/ovn/dns_name_resolver/dns.go @@ -9,7 +9,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/dns_name_resolver/dns_name_resolver.go b/go-controller/pkg/ovn/dns_name_resolver/dns_name_resolver.go index 0b8ad32f11..b0d29f0d3d 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/dns_name_resolver.go +++ b/go-controller/pkg/ovn/dns_name_resolver/dns_name_resolver.go @@ -1,7 +1,7 @@ package dnsnameresolver import ( - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" ) diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns.go index 57bff7dec2..cd542c48e1 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns.go @@ -14,7 +14,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/controller" egressfirewalllister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/listers/egressfirewall/v1" diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go index 26e4107244..72661bd4ed 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go @@ -13,7 +13,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go index 8ace7203e2..730bd026af 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go @@ -8,7 +8,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/egressfirewall.go b/go-controller/pkg/ovn/egressfirewall.go index 4e49505d04..20e444b3a7 100644 --- a/go-controller/pkg/ovn/egressfirewall.go +++ b/go-controller/pkg/ovn/egressfirewall.go @@ -20,7 +20,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/controller" diff --git a/go-controller/pkg/ovn/egressgw.go b/go-controller/pkg/ovn/egressgw.go index 27f9e2b970..1f28955295 100644 --- a/go-controller/pkg/ovn/egressgw.go +++ b/go-controller/pkg/ovn/egressgw.go @@ -17,8 +17,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index d53ba5e633..37f87b695e 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -26,8 +26,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/egressqos.go b/go-controller/pkg/ovn/egressqos.go index fc6258408e..605b127d03 100644 --- a/go-controller/pkg/ovn/egressqos.go +++ b/go-controller/pkg/ovn/egressqos.go @@ -24,7 +24,7 @@ import ( utilnet "k8s.io/utils/net" "k8s.io/utils/ptr" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" egressqosapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressqos/v1" diff --git a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go index 01cbd40512..cbe1835322 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go +++ b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go @@ -9,8 +9,8 @@ import ( "k8s.io/klog/v2" utilsnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go index 617bddd411..712f6fe541 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go +++ b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go @@ -7,8 +7,8 @@ import ( "k8s.io/klog/v2" utilsnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 7c38289737..54005e4301 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -15,7 +15,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/gateway/gateway.go b/go-controller/pkg/ovn/gateway/gateway.go index c6e10ab4a9..f716528810 100644 --- a/go-controller/pkg/ovn/gateway/gateway.go +++ b/go-controller/pkg/ovn/gateway/gateway.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/gatewayrouter/policybasedroutes.go b/go-controller/pkg/ovn/gatewayrouter/policybasedroutes.go index e2866ba946..4f61101282 100644 --- a/go-controller/pkg/ovn/gatewayrouter/policybasedroutes.go +++ b/go-controller/pkg/ovn/gatewayrouter/policybasedroutes.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" utilnet "k8s.io/utils/net" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/hybrid.go b/go-controller/pkg/ovn/hybrid.go index 7c84dea2aa..41f98075f1 100644 --- a/go-controller/pkg/ovn/hybrid.go +++ b/go-controller/pkg/ovn/hybrid.go @@ -12,7 +12,7 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" hotypes "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/types" houtil "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/util" diff --git a/go-controller/pkg/ovn/hybrid_test.go b/go-controller/pkg/ovn/hybrid_test.go index 1663e5a8f8..fab60e2c3b 100644 --- a/go-controller/pkg/ovn/hybrid_test.go +++ b/go-controller/pkg/ovn/hybrid_test.go @@ -20,7 +20,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" hotypes "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/types" cm "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/clustermanager" diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index c2ca98a59e..f85cdb75c3 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" hotypes "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/types" houtil "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/util" diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index 0c3ba9e7a8..5c1c8b2c4b 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -23,7 +23,7 @@ import ( clienttesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/record" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" egressfirewallfake "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/clientset/versioned/fake" diff --git a/go-controller/pkg/ovn/multihoming_test.go b/go-controller/pkg/ovn/multihoming_test.go index ab3d12425a..a7b69c3fb9 100644 --- a/go-controller/pkg/ovn/multihoming_test.go +++ b/go-controller/pkg/ovn/multihoming_test.go @@ -11,7 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" diff --git a/go-controller/pkg/ovn/namespace.go b/go-controller/pkg/ovn/namespace.go index 127c034735..01f189228b 100644 --- a/go-controller/pkg/ovn/namespace.go +++ b/go-controller/pkg/ovn/namespace.go @@ -9,7 +9,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 692c768d95..c6a53ee34e 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -17,7 +17,7 @@ import ( ref "k8s.io/client-go/tools/reference" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kubevirt" diff --git a/go-controller/pkg/ovn/ovn_test.go b/go-controller/pkg/ovn/ovn_test.go index 801777854a..0a1b9e3c8f 100644 --- a/go-controller/pkg/ovn/ovn_test.go +++ b/go-controller/pkg/ovn/ovn_test.go @@ -25,7 +25,7 @@ import ( anpapi "sigs.k8s.io/network-policy-api/apis/v1alpha1" anpfake "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/fake" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/pods.go b/go-controller/pkg/ovn/pods.go index 949d48da55..9f39376d9e 100644 --- a/go-controller/pkg/ovn/pods.go +++ b/go-controller/pkg/ovn/pods.go @@ -13,7 +13,7 @@ import ( ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" hotypes "github.com/ovn-org/ovn-kubernetes/go-controller/hybrid-overlay/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/policy_test.go b/go-controller/pkg/ovn/policy_test.go index 657f3d074a..bcfb4898a3 100644 --- a/go-controller/pkg/ovn/policy_test.go +++ b/go-controller/pkg/ovn/policy_test.go @@ -20,7 +20,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/routeimport/route_import.go b/go-controller/pkg/ovn/routeimport/route_import.go index 94da3d34fe..18c372c276 100644 --- a/go-controller/pkg/ovn/routeimport/route_import.go +++ b/go-controller/pkg/ovn/routeimport/route_import.go @@ -15,8 +15,8 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" controllerutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/controller" nbdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index a6c2d500bd..15fdb98aa7 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -12,7 +12,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/pod" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" @@ -684,7 +684,7 @@ func (oc *SecondaryLayer3NetworkController) init() error { } } - // FIXME: When https://github.com/ovn-org/libovsdb/issues/235 is fixed, + // FIXME: When https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed, // use IsTableSupported(nbdb.LoadBalancerGroup). if _, _, err := util.RunOVNNbctl("--columns=_uuid", "list", "Load_Balancer_Group"); err != nil { klog.Warningf("Load Balancer Group support enabled, however version of OVN in use does not support Load Balancer Groups.") diff --git a/go-controller/pkg/ovn/topology/topologyfactory.go b/go-controller/pkg/ovn/topology/topologyfactory.go index 8781612242..d9a1980cbc 100644 --- a/go-controller/pkg/ovn/topology/topologyfactory.go +++ b/go-controller/pkg/ovn/topology/topologyfactory.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" diff --git a/go-controller/pkg/ovn/topology/topologyfactory_test.go b/go-controller/pkg/ovn/topology/topologyfactory_test.go index 01b113c97e..af8a036d6f 100644 --- a/go-controller/pkg/ovn/topology/topologyfactory_test.go +++ b/go-controller/pkg/ovn/topology/topologyfactory_test.go @@ -5,7 +5,7 @@ import ( cnitypes "github.com/containernetworking/cni/pkg/types" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/ovn/udn_isolation.go b/go-controller/pkg/ovn/udn_isolation.go index 6c44489f9c..d9f6ddfde1 100644 --- a/go-controller/pkg/ovn/udn_isolation.go +++ b/go-controller/pkg/ovn/udn_isolation.go @@ -10,8 +10,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" diff --git a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go index 172cac5e33..b838221892 100644 --- a/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/chassis_handler.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/zone_interconnect/chassis_handler_test.go b/go-controller/pkg/ovn/zone_interconnect/chassis_handler_test.go index 05b9fb6b9c..df74e807d1 100644 --- a/go-controller/pkg/ovn/zone_interconnect/chassis_handler_test.go +++ b/go-controller/pkg/ovn/zone_interconnect/chassis_handler_test.go @@ -10,7 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index f484bc1528..9d088e6659 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -12,8 +12,8 @@ import ( "k8s.io/klog/v2" utilnet "k8s.io/utils/net" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go index 8af1215714..c0a54a1d61 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler_test.go @@ -13,7 +13,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" diff --git a/go-controller/pkg/sbdb/address_set.go b/go-controller/pkg/sbdb/address_set.go index b3b1c3c2d8..88b221dedf 100644 --- a/go-controller/pkg/sbdb/address_set.go +++ b/go-controller/pkg/sbdb/address_set.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const AddressSetTable = "Address_Set" diff --git a/go-controller/pkg/sbdb/bfd.go b/go-controller/pkg/sbdb/bfd.go index cf27814b51..eb3822e902 100644 --- a/go-controller/pkg/sbdb/bfd.go +++ b/go-controller/pkg/sbdb/bfd.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const BFDTable = "BFD" diff --git a/go-controller/pkg/sbdb/chassis.go b/go-controller/pkg/sbdb/chassis.go index 3526f096f2..3cbffee206 100644 --- a/go-controller/pkg/sbdb/chassis.go +++ b/go-controller/pkg/sbdb/chassis.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ChassisTable = "Chassis" diff --git a/go-controller/pkg/sbdb/chassis_private.go b/go-controller/pkg/sbdb/chassis_private.go index 1e8c3764bd..dc848a1569 100644 --- a/go-controller/pkg/sbdb/chassis_private.go +++ b/go-controller/pkg/sbdb/chassis_private.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ChassisPrivateTable = "Chassis_Private" diff --git a/go-controller/pkg/sbdb/chassis_template_var.go b/go-controller/pkg/sbdb/chassis_template_var.go index 212e772be6..2e8213ade8 100644 --- a/go-controller/pkg/sbdb/chassis_template_var.go +++ b/go-controller/pkg/sbdb/chassis_template_var.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ChassisTemplateVarTable = "Chassis_Template_Var" diff --git a/go-controller/pkg/sbdb/connection.go b/go-controller/pkg/sbdb/connection.go index 8f96f54226..2deb8bd30a 100644 --- a/go-controller/pkg/sbdb/connection.go +++ b/go-controller/pkg/sbdb/connection.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ConnectionTable = "Connection" diff --git a/go-controller/pkg/sbdb/controller_event.go b/go-controller/pkg/sbdb/controller_event.go index 741ffd028a..0233181ca6 100644 --- a/go-controller/pkg/sbdb/controller_event.go +++ b/go-controller/pkg/sbdb/controller_event.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ControllerEventTable = "Controller_Event" diff --git a/go-controller/pkg/sbdb/datapath_binding.go b/go-controller/pkg/sbdb/datapath_binding.go index 10247286f7..295660e9c3 100644 --- a/go-controller/pkg/sbdb/datapath_binding.go +++ b/go-controller/pkg/sbdb/datapath_binding.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DatapathBindingTable = "Datapath_Binding" diff --git a/go-controller/pkg/sbdb/dhcp_options.go b/go-controller/pkg/sbdb/dhcp_options.go index e9ec44ce29..e0bb7627f1 100644 --- a/go-controller/pkg/sbdb/dhcp_options.go +++ b/go-controller/pkg/sbdb/dhcp_options.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DHCPOptionsTable = "DHCP_Options" diff --git a/go-controller/pkg/sbdb/dhcpv6_options.go b/go-controller/pkg/sbdb/dhcpv6_options.go index 908d1e0ad0..95a2a8d8f4 100644 --- a/go-controller/pkg/sbdb/dhcpv6_options.go +++ b/go-controller/pkg/sbdb/dhcpv6_options.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DHCPv6OptionsTable = "DHCPv6_Options" diff --git a/go-controller/pkg/sbdb/dns.go b/go-controller/pkg/sbdb/dns.go index 95c0a52d1e..c044f990b0 100644 --- a/go-controller/pkg/sbdb/dns.go +++ b/go-controller/pkg/sbdb/dns.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DNSTable = "DNS" diff --git a/go-controller/pkg/sbdb/encap.go b/go-controller/pkg/sbdb/encap.go index 9a2f17fba2..4c524a52ca 100644 --- a/go-controller/pkg/sbdb/encap.go +++ b/go-controller/pkg/sbdb/encap.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const EncapTable = "Encap" diff --git a/go-controller/pkg/sbdb/fdb.go b/go-controller/pkg/sbdb/fdb.go index 8253e7059b..346593ac6f 100644 --- a/go-controller/pkg/sbdb/fdb.go +++ b/go-controller/pkg/sbdb/fdb.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const FDBTable = "FDB" diff --git a/go-controller/pkg/sbdb/gateway_chassis.go b/go-controller/pkg/sbdb/gateway_chassis.go index a84ad7fc47..f08883222d 100644 --- a/go-controller/pkg/sbdb/gateway_chassis.go +++ b/go-controller/pkg/sbdb/gateway_chassis.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const GatewayChassisTable = "Gateway_Chassis" diff --git a/go-controller/pkg/sbdb/ha_chassis.go b/go-controller/pkg/sbdb/ha_chassis.go index b0b3cebbba..b40d7999e3 100644 --- a/go-controller/pkg/sbdb/ha_chassis.go +++ b/go-controller/pkg/sbdb/ha_chassis.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const HAChassisTable = "HA_Chassis" diff --git a/go-controller/pkg/sbdb/ha_chassis_group.go b/go-controller/pkg/sbdb/ha_chassis_group.go index 1cc013c705..72a5622f5b 100644 --- a/go-controller/pkg/sbdb/ha_chassis_group.go +++ b/go-controller/pkg/sbdb/ha_chassis_group.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const HAChassisGroupTable = "HA_Chassis_Group" diff --git a/go-controller/pkg/sbdb/igmp_group.go b/go-controller/pkg/sbdb/igmp_group.go index 73a0bb9437..19381eb855 100644 --- a/go-controller/pkg/sbdb/igmp_group.go +++ b/go-controller/pkg/sbdb/igmp_group.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const IGMPGroupTable = "IGMP_Group" diff --git a/go-controller/pkg/sbdb/ip_multicast.go b/go-controller/pkg/sbdb/ip_multicast.go index 493cd342d2..902b7204f1 100644 --- a/go-controller/pkg/sbdb/ip_multicast.go +++ b/go-controller/pkg/sbdb/ip_multicast.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const IPMulticastTable = "IP_Multicast" diff --git a/go-controller/pkg/sbdb/load_balancer.go b/go-controller/pkg/sbdb/load_balancer.go index bc341807e7..7bf4da265a 100644 --- a/go-controller/pkg/sbdb/load_balancer.go +++ b/go-controller/pkg/sbdb/load_balancer.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LoadBalancerTable = "Load_Balancer" diff --git a/go-controller/pkg/sbdb/logical_dp_group.go b/go-controller/pkg/sbdb/logical_dp_group.go index 911de2eed0..86727f4486 100644 --- a/go-controller/pkg/sbdb/logical_dp_group.go +++ b/go-controller/pkg/sbdb/logical_dp_group.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalDPGroupTable = "Logical_DP_Group" diff --git a/go-controller/pkg/sbdb/logical_flow.go b/go-controller/pkg/sbdb/logical_flow.go index 42af1cdf54..da2341990d 100644 --- a/go-controller/pkg/sbdb/logical_flow.go +++ b/go-controller/pkg/sbdb/logical_flow.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const LogicalFlowTable = "Logical_Flow" diff --git a/go-controller/pkg/sbdb/mac_binding.go b/go-controller/pkg/sbdb/mac_binding.go index 705431f1d0..9764c6dc35 100644 --- a/go-controller/pkg/sbdb/mac_binding.go +++ b/go-controller/pkg/sbdb/mac_binding.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MACBindingTable = "MAC_Binding" diff --git a/go-controller/pkg/sbdb/meter.go b/go-controller/pkg/sbdb/meter.go index 95c4daec2f..9d86874c0b 100644 --- a/go-controller/pkg/sbdb/meter.go +++ b/go-controller/pkg/sbdb/meter.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MeterTable = "Meter" diff --git a/go-controller/pkg/sbdb/meter_band.go b/go-controller/pkg/sbdb/meter_band.go index addb01b645..10d3d740f8 100644 --- a/go-controller/pkg/sbdb/meter_band.go +++ b/go-controller/pkg/sbdb/meter_band.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MeterBandTable = "Meter_Band" diff --git a/go-controller/pkg/sbdb/mirror.go b/go-controller/pkg/sbdb/mirror.go index 69444ea735..b9139214ca 100644 --- a/go-controller/pkg/sbdb/mirror.go +++ b/go-controller/pkg/sbdb/mirror.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MirrorTable = "Mirror" diff --git a/go-controller/pkg/sbdb/model.go b/go-controller/pkg/sbdb/model.go index bc838fe497..c5420638e5 100644 --- a/go-controller/pkg/sbdb/model.go +++ b/go-controller/pkg/sbdb/model.go @@ -6,8 +6,8 @@ package sbdb import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/go-controller/pkg/sbdb/multicast_group.go b/go-controller/pkg/sbdb/multicast_group.go index 1af933ea6c..b8e2a828d9 100644 --- a/go-controller/pkg/sbdb/multicast_group.go +++ b/go-controller/pkg/sbdb/multicast_group.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MulticastGroupTable = "Multicast_Group" diff --git a/go-controller/pkg/sbdb/port_binding.go b/go-controller/pkg/sbdb/port_binding.go index b3d30f843a..48668023fc 100644 --- a/go-controller/pkg/sbdb/port_binding.go +++ b/go-controller/pkg/sbdb/port_binding.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const PortBindingTable = "Port_Binding" diff --git a/go-controller/pkg/sbdb/port_group.go b/go-controller/pkg/sbdb/port_group.go index 358e26b33d..e197ae6e4d 100644 --- a/go-controller/pkg/sbdb/port_group.go +++ b/go-controller/pkg/sbdb/port_group.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const PortGroupTable = "Port_Group" diff --git a/go-controller/pkg/sbdb/rbac_permission.go b/go-controller/pkg/sbdb/rbac_permission.go index 9d760527e9..228c56bfe8 100644 --- a/go-controller/pkg/sbdb/rbac_permission.go +++ b/go-controller/pkg/sbdb/rbac_permission.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const RBACPermissionTable = "RBAC_Permission" diff --git a/go-controller/pkg/sbdb/rbac_role.go b/go-controller/pkg/sbdb/rbac_role.go index ce8798645c..427582d3b8 100644 --- a/go-controller/pkg/sbdb/rbac_role.go +++ b/go-controller/pkg/sbdb/rbac_role.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const RBACRoleTable = "RBAC_Role" diff --git a/go-controller/pkg/sbdb/sb_global.go b/go-controller/pkg/sbdb/sb_global.go index 2374478db7..667fdae3e0 100644 --- a/go-controller/pkg/sbdb/sb_global.go +++ b/go-controller/pkg/sbdb/sb_global.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SBGlobalTable = "SB_Global" diff --git a/go-controller/pkg/sbdb/service_monitor.go b/go-controller/pkg/sbdb/service_monitor.go index d3e1188680..189f09f659 100644 --- a/go-controller/pkg/sbdb/service_monitor.go +++ b/go-controller/pkg/sbdb/service_monitor.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ServiceMonitorTable = "Service_Monitor" diff --git a/go-controller/pkg/sbdb/ssl.go b/go-controller/pkg/sbdb/ssl.go index 3fab5fd1e9..08c8e641cf 100644 --- a/go-controller/pkg/sbdb/ssl.go +++ b/go-controller/pkg/sbdb/ssl.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SSLTable = "SSL" diff --git a/go-controller/pkg/sbdb/static_mac_binding.go b/go-controller/pkg/sbdb/static_mac_binding.go index 370968f604..8a3c590e31 100644 --- a/go-controller/pkg/sbdb/static_mac_binding.go +++ b/go-controller/pkg/sbdb/static_mac_binding.go @@ -3,7 +3,7 @@ package sbdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const StaticMACBindingTable = "Static_MAC_Binding" diff --git a/go-controller/pkg/testing/libovsdb/libovsdb.go b/go-controller/pkg/testing/libovsdb/libovsdb.go index 8f10bab356..a6836811d1 100644 --- a/go-controller/pkg/testing/libovsdb/libovsdb.go +++ b/go-controller/pkg/testing/libovsdb/libovsdb.go @@ -21,14 +21,14 @@ import ( "k8s.io/apimachinery/pkg/util/wait" - libovsdbclient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/database/inmemory" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" - "github.com/ovn-org/libovsdb/ovsdb/serverdb" - "github.com/ovn-org/libovsdb/server" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/database/inmemory" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb" + "github.com/ovn-kubernetes/libovsdb/server" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cryptorand" diff --git a/go-controller/pkg/testing/libovsdb/matchers.go b/go-controller/pkg/testing/libovsdb/matchers.go index 102d8fbc63..1ff3977065 100644 --- a/go-controller/pkg/testing/libovsdb/matchers.go +++ b/go-controller/pkg/testing/libovsdb/matchers.go @@ -9,7 +9,7 @@ import ( gomegaformat "github.com/onsi/gomega/format" gomegatypes "github.com/onsi/gomega/types" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" ) // isSetEqual compares a slice as an unordered set diff --git a/go-controller/pkg/testing/libovsdb/ops.go b/go-controller/pkg/testing/libovsdb/ops.go index 1926bbc3f5..de73c1d154 100644 --- a/go-controller/pkg/testing/libovsdb/ops.go +++ b/go-controller/pkg/testing/libovsdb/ops.go @@ -6,7 +6,7 @@ import ( "fmt" "hash/fnv" - libovsdbclient "github.com/ovn-org/libovsdb/client" + libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" ) diff --git a/go-controller/pkg/util/ovs.go b/go-controller/pkg/util/ovs.go index ff21e828db..7c1028995a 100644 --- a/go-controller/pkg/util/ovs.go +++ b/go-controller/pkg/util/ovs.go @@ -377,7 +377,7 @@ func RunOVNAppctlWithTimeout(timeout int, args ...string) (string, string, error // Run the ovn-ctl command and retry if "Connection refused" // poll waitng for service to become available -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func runOVNretry(cmdPath string, envVars []string, args ...string) (*bytes.Buffer, *bytes.Buffer, error) { retriesLeft := ovnCmdRetryCount @@ -434,14 +434,14 @@ func getNbOVSDBArgs(command string, args ...string) []string { } // RunOVNNbctlWithTimeout runs command via ovn-nbctl with a specific timeout -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func RunOVNNbctlWithTimeout(timeout int, args ...string) (string, string, error) { stdout, stderr, err := RunOVNNbctlRawOutput(timeout, args...) return strings.Trim(strings.TrimSpace(stdout), "\""), stderr, err } // RunOVNNbctlRawOutput returns the output with no trimming or other string manipulation -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func RunOVNNbctlRawOutput(timeout int, args ...string) (string, string, error) { cmdArgs, envVars := getNbctlArgsAndEnv(timeout, args...) stdout, stderr, err := runOVNretry(runner.nbctlPath, envVars, cmdArgs...) @@ -449,13 +449,13 @@ func RunOVNNbctlRawOutput(timeout int, args ...string) (string, string, error) { } // RunOVNNbctl runs a command via ovn-nbctl. -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func RunOVNNbctl(args ...string) (string, string, error) { return RunOVNNbctlWithTimeout(ovsCommandTimeout, args...) } // RunOVNSbctlWithTimeout runs command via ovn-sbctl with a specific timeout -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func RunOVNSbctlWithTimeout(timeout int, args ...string) (string, string, error) { var cmdArgs []string @@ -499,7 +499,7 @@ func RunOVSDBClientOVNNB(command string, args ...string) (string, string, error) } // RunOVNSbctl runs a command via ovn-sbctl. -// FIXME: Remove when https://github.com/ovn-org/libovsdb/issues/235 is fixed +// FIXME: Remove when https://github.com/ovn-kubernetes/libovsdb/issues/235 is fixed func RunOVNSbctl(args ...string) (string, string, error) { return RunOVNSbctlWithTimeout(ovsCommandTimeout, args...) } diff --git a/go-controller/pkg/vswitchd/autoattach.go b/go-controller/pkg/vswitchd/autoattach.go index b9655736aa..e54dbba3ae 100644 --- a/go-controller/pkg/vswitchd/autoattach.go +++ b/go-controller/pkg/vswitchd/autoattach.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const AutoAttachTable = "AutoAttach" diff --git a/go-controller/pkg/vswitchd/bridge.go b/go-controller/pkg/vswitchd/bridge.go index 8953faa3f2..14997f995b 100644 --- a/go-controller/pkg/vswitchd/bridge.go +++ b/go-controller/pkg/vswitchd/bridge.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const BridgeTable = "Bridge" diff --git a/go-controller/pkg/vswitchd/controller.go b/go-controller/pkg/vswitchd/controller.go index 1b38c989bf..ff02062eaa 100644 --- a/go-controller/pkg/vswitchd/controller.go +++ b/go-controller/pkg/vswitchd/controller.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ControllerTable = "Controller" diff --git a/go-controller/pkg/vswitchd/ct_timeout_policy.go b/go-controller/pkg/vswitchd/ct_timeout_policy.go index 98bf690498..150db9b2f7 100644 --- a/go-controller/pkg/vswitchd/ct_timeout_policy.go +++ b/go-controller/pkg/vswitchd/ct_timeout_policy.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const CTTimeoutPolicyTable = "CT_Timeout_Policy" diff --git a/go-controller/pkg/vswitchd/ct_zone.go b/go-controller/pkg/vswitchd/ct_zone.go index 4eaba845c4..6868191974 100644 --- a/go-controller/pkg/vswitchd/ct_zone.go +++ b/go-controller/pkg/vswitchd/ct_zone.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const CTZoneTable = "CT_Zone" diff --git a/go-controller/pkg/vswitchd/datapath.go b/go-controller/pkg/vswitchd/datapath.go index 71a995f93e..899f5d3531 100644 --- a/go-controller/pkg/vswitchd/datapath.go +++ b/go-controller/pkg/vswitchd/datapath.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DatapathTable = "Datapath" diff --git a/go-controller/pkg/vswitchd/flow_sample_collector_set.go b/go-controller/pkg/vswitchd/flow_sample_collector_set.go index 2c90f5d438..8c975711a5 100644 --- a/go-controller/pkg/vswitchd/flow_sample_collector_set.go +++ b/go-controller/pkg/vswitchd/flow_sample_collector_set.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const FlowSampleCollectorSetTable = "Flow_Sample_Collector_Set" diff --git a/go-controller/pkg/vswitchd/flow_table.go b/go-controller/pkg/vswitchd/flow_table.go index 42d49d2f58..911b6fbb1d 100644 --- a/go-controller/pkg/vswitchd/flow_table.go +++ b/go-controller/pkg/vswitchd/flow_table.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const FlowTableTable = "Flow_Table" diff --git a/go-controller/pkg/vswitchd/interface.go b/go-controller/pkg/vswitchd/interface.go index e6f67ba9c7..6f89cc5d1a 100644 --- a/go-controller/pkg/vswitchd/interface.go +++ b/go-controller/pkg/vswitchd/interface.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const InterfaceTable = "Interface" diff --git a/go-controller/pkg/vswitchd/ipfix.go b/go-controller/pkg/vswitchd/ipfix.go index 72b5d3915c..8ea91c8fd1 100644 --- a/go-controller/pkg/vswitchd/ipfix.go +++ b/go-controller/pkg/vswitchd/ipfix.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const IPFIXTable = "IPFIX" diff --git a/go-controller/pkg/vswitchd/manager.go b/go-controller/pkg/vswitchd/manager.go index ff1df96caa..45a9dcb609 100644 --- a/go-controller/pkg/vswitchd/manager.go +++ b/go-controller/pkg/vswitchd/manager.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const ManagerTable = "Manager" diff --git a/go-controller/pkg/vswitchd/mirror.go b/go-controller/pkg/vswitchd/mirror.go index 044455d253..2bab171097 100644 --- a/go-controller/pkg/vswitchd/mirror.go +++ b/go-controller/pkg/vswitchd/mirror.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const MirrorTable = "Mirror" diff --git a/go-controller/pkg/vswitchd/model.go b/go-controller/pkg/vswitchd/model.go index c862f04277..20b8d0cc94 100644 --- a/go-controller/pkg/vswitchd/model.go +++ b/go-controller/pkg/vswitchd/model.go @@ -6,8 +6,8 @@ package vswitchd import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/go-controller/pkg/vswitchd/netflow.go b/go-controller/pkg/vswitchd/netflow.go index f958587044..d1f05029fd 100644 --- a/go-controller/pkg/vswitchd/netflow.go +++ b/go-controller/pkg/vswitchd/netflow.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const NetFlowTable = "NetFlow" diff --git a/go-controller/pkg/vswitchd/open_vswitch.go b/go-controller/pkg/vswitchd/open_vswitch.go index e8ea481d5b..e8a1456fe9 100644 --- a/go-controller/pkg/vswitchd/open_vswitch.go +++ b/go-controller/pkg/vswitchd/open_vswitch.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const OpenvSwitchTable = "Open_vSwitch" diff --git a/go-controller/pkg/vswitchd/port.go b/go-controller/pkg/vswitchd/port.go index cf0ba96153..6aa3350c93 100644 --- a/go-controller/pkg/vswitchd/port.go +++ b/go-controller/pkg/vswitchd/port.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const PortTable = "Port" diff --git a/go-controller/pkg/vswitchd/qos.go b/go-controller/pkg/vswitchd/qos.go index aa1c9dd004..0ac14541d9 100644 --- a/go-controller/pkg/vswitchd/qos.go +++ b/go-controller/pkg/vswitchd/qos.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const QoSTable = "QoS" diff --git a/go-controller/pkg/vswitchd/queue.go b/go-controller/pkg/vswitchd/queue.go index e8615e9cf7..60094eb8c2 100644 --- a/go-controller/pkg/vswitchd/queue.go +++ b/go-controller/pkg/vswitchd/queue.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const QueueTable = "Queue" diff --git a/go-controller/pkg/vswitchd/sflow.go b/go-controller/pkg/vswitchd/sflow.go index fcbcc8569e..58841d7877 100644 --- a/go-controller/pkg/vswitchd/sflow.go +++ b/go-controller/pkg/vswitchd/sflow.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SFlowTable = "sFlow" diff --git a/go-controller/pkg/vswitchd/ssl.go b/go-controller/pkg/vswitchd/ssl.go index 79c4b1bad4..84dfbd1f33 100644 --- a/go-controller/pkg/vswitchd/ssl.go +++ b/go-controller/pkg/vswitchd/ssl.go @@ -3,7 +3,7 @@ package vswitchd -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const SSLTable = "SSL" diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/LICENSE b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/LICENSE similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/LICENSE rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/LICENSE diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/NOTICE b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/NOTICE similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/NOTICE rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/NOTICE diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/cache/cache.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/cache.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/cache/cache.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/cache.go index 0b1e09e721..ffe871fd3e 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/cache/cache.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/cache.go @@ -15,10 +15,10 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/stdr" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" - "github.com/ovn-org/libovsdb/updates" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/updates" ) const ( diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/cache/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/cache/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/cache/uuidset.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/uuidset.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/cache/uuidset.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/cache/uuidset.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/api.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/api.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api.go index 4977589442..f6a8d6fb34 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/client/api.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api.go @@ -7,9 +7,9 @@ import ( "reflect" "github.com/go-logr/logr" - "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // API defines basic operations to interact with the database diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/api_test_model.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api_test_model.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/api_test_model.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api_test_model.go index 36ea476e08..7a97b6d08c 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/client/api_test_model.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/api_test_model.go @@ -4,9 +4,9 @@ import ( "encoding/json" "testing" - "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/stretchr/testify/assert" ) diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/client.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/client.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/client.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/client.go index 10ea757ec7..3926ad6ddf 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/client/client.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/client.go @@ -20,11 +20,11 @@ import ( "github.com/cenkalti/rpc2/jsonrpc" "github.com/go-logr/logr" "github.com/go-logr/stdr" - "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" - "github.com/ovn-org/libovsdb/ovsdb/serverdb" + "github.com/ovn-kubernetes/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb" ) // Constants defined for libovsdb diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/condition.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/condition.go similarity index 97% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/condition.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/condition.go index 1dfabda02e..1269339cea 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/client/condition.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/condition.go @@ -4,10 +4,10 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // Conditional is the interface used by the ConditionalAPI to match on cache objects diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/config.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/config.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/config.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/config.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/metrics.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/metrics.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/metrics.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/metrics.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/monitor.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/monitor.go similarity index 97% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/monitor.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/monitor.go index 4a0270a87a..767a4cf3d6 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/client/monitor.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/monitor.go @@ -5,8 +5,8 @@ import ( "reflect" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) const emptyUUID = "00000000-0000-0000-0000-000000000000" diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/client/options.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/options.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/client/options.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/client/options.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/database.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/database.go similarity index 93% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/database.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/database.go index 12f1222f19..9bdb69568b 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/database/database.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/database.go @@ -2,8 +2,8 @@ package database import ( "github.com/google/uuid" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // Database abstracts a database that a server can use to store and transact data diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/inmemory/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/inmemory/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/inmemory/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/inmemory/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/inmemory/inmemory.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/inmemory/inmemory.go similarity index 93% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/inmemory/inmemory.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/inmemory/inmemory.go index 6c1dce9e79..763dcd7fd0 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/database/inmemory/inmemory.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/inmemory/inmemory.go @@ -9,11 +9,11 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/cache" - dbase "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/database/transaction" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/cache" + dbase "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/database/transaction" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) type inMemoryDatabase struct { diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/references.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/references.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/references.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/references.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/errors.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/errors.go similarity index 91% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/errors.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/errors.go index 35e47c7294..204a7f544a 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/errors.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/errors.go @@ -3,7 +3,7 @@ package transaction import ( "fmt" - "github.com/ovn-org/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/cache" ) func newIndexExistsDetails(err cache.ErrIndexExists) string { diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/transaction.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/transaction.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/transaction.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/transaction.go index 69736d0048..77b8e920c0 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/database/transaction/transaction.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/database/transaction/transaction.go @@ -7,11 +7,11 @@ import ( "github.com/go-logr/logr" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/cache" - "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" - "github.com/ovn-org/libovsdb/updates" + "github.com/ovn-kubernetes/libovsdb/cache" + "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/updates" ) type Transaction struct { diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/mapper/info.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/info.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/mapper/info.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/info.go index 8ac436c790..0e24ef25ec 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/mapper/info.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/info.go @@ -4,7 +4,7 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // ErrColumnNotFound is an error that can occur when the column does not exist for a table diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/mapper/mapper.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/mapper.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/mapper/mapper.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/mapper.go index 5ca7a412bb..24ce7b3b8c 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/mapper/mapper.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/mapper/mapper.go @@ -4,7 +4,7 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // Mapper offers functions to interact with libovsdb through user-provided native structs. diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/model/client.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/client.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/model/client.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/client.go index 5eb686244a..e8a39260e9 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/model/client.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/client.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // ColumnKey addresses a column and optionally a key within a column diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/model/database.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/database.go similarity index 97% rename from go-controller/vendor/github.com/ovn-org/libovsdb/model/database.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/database.go index 0857d903f3..30ccff67b1 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/model/database.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/database.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // A DatabaseModel represents libovsdb's metadata about the database. diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/model/model.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/model.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/model/model.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/model.go index c8575f5bf3..249db69921 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/model/model.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/model/model.go @@ -5,7 +5,7 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // A Model is the base interface used to build Database Models. It is used diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/bindings.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/bindings.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/bindings.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/bindings.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/condition.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/condition.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/condition.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/condition.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/error.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/error.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/error.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/error.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/map.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/map.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/map.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/map.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/monitor_select.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/monitor_select.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/monitor_select.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/monitor_select.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/mutation.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/mutation.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/mutation.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/mutation.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/named_uuid.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/named_uuid.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/named_uuid.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/named_uuid.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/notation.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/notation.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/notation.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/notation.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/row.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/row.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/row.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/row.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/rpc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/rpc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/rpc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/rpc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/schema.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/schema.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/schema.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/schema.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/.gitignore b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/.gitignore similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/.gitignore rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/.gitignore diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/database.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/database.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/database.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/database.go index 274a7164fe..a93ca0d86f 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/database.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/database.go @@ -3,7 +3,7 @@ package serverdb -import "github.com/ovn-org/libovsdb/model" +import "github.com/ovn-kubernetes/libovsdb/model" const DatabaseTable = "Database" diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/gen.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/gen.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/gen.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/gen.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/model.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/model.go similarity index 95% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/model.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/model.go index 3c117faa26..c0aeeb74c3 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/serverdb/model.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb/model.go @@ -6,8 +6,8 @@ package serverdb import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/set.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/set.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/set.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/set.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/update3.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/update3.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/update3.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/update3.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/updates.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/updates.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/updates.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/updates.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/updates2.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/updates2.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/updates2.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/updates2.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/uuid.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/uuid.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/ovsdb/uuid.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/ovsdb/uuid.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/server/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/server/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/server/monitor.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/monitor.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/server/monitor.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/monitor.go index 2dedf992b0..305769a212 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/server/monitor.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/monitor.go @@ -7,8 +7,8 @@ import ( "github.com/cenkalti/rpc2" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // connectionMonitors maps a connection to a map or monitors diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/server/server.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/server.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/server/server.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/server.go index ec60ea5d20..830560fc36 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/server/server.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/server/server.go @@ -14,9 +14,9 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/google/uuid" - "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // OvsdbServer is an ovsdb server diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/difference.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/difference.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/difference.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/difference.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/doc.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/doc.go similarity index 100% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/doc.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/doc.go diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/merge.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/merge.go similarity index 98% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/merge.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/merge.go index 562f226232..82d78239f6 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/merge.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/merge.go @@ -4,7 +4,7 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) func merge(ts *ovsdb.TableSchema, a, b modelUpdate) (modelUpdate, error) { diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/mutate.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/mutate.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/mutate.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/mutate.go index 1d87737fcd..b91ef85341 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/mutate.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/mutate.go @@ -3,7 +3,7 @@ package updates import ( "reflect" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) func removeFromSlice(a, b reflect.Value) (reflect.Value, bool) { diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/references.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/references.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/references.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/references.go index 938d02aae9..4d998e0511 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/references.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/references.go @@ -3,9 +3,9 @@ package updates import ( "fmt" - "github.com/ovn-org/libovsdb/database" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/database" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // ReferenceProvider should be implemented by a database that tracks references diff --git a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/updates.go b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/updates.go similarity index 99% rename from go-controller/vendor/github.com/ovn-org/libovsdb/updates/updates.go rename to go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/updates.go index 4ff2363a05..00fbcccffa 100644 --- a/go-controller/vendor/github.com/ovn-org/libovsdb/updates/updates.go +++ b/go-controller/vendor/github.com/ovn-kubernetes/libovsdb/updates/updates.go @@ -4,9 +4,9 @@ import ( "fmt" "reflect" - "github.com/ovn-org/libovsdb/mapper" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/mapper" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) type rowUpdate2 = ovsdb.RowUpdate2 diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index a0ecf2cb4a..5732a53975 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -385,19 +385,19 @@ github.com/openshift/client-go/network/listers/network/v1alpha1 # github.com/openshift/custom-resource-status v1.1.2 ## explicit; go 1.12 github.com/openshift/custom-resource-status/conditions/v1 -# github.com/ovn-org/libovsdb v0.7.1-0.20240820095311-ce1951614a20 +# github.com/ovn-kubernetes/libovsdb v0.8.0 ## explicit; go 1.18 -github.com/ovn-org/libovsdb/cache -github.com/ovn-org/libovsdb/client -github.com/ovn-org/libovsdb/database -github.com/ovn-org/libovsdb/database/inmemory -github.com/ovn-org/libovsdb/database/transaction -github.com/ovn-org/libovsdb/mapper -github.com/ovn-org/libovsdb/model -github.com/ovn-org/libovsdb/ovsdb -github.com/ovn-org/libovsdb/ovsdb/serverdb -github.com/ovn-org/libovsdb/server -github.com/ovn-org/libovsdb/updates +github.com/ovn-kubernetes/libovsdb/cache +github.com/ovn-kubernetes/libovsdb/client +github.com/ovn-kubernetes/libovsdb/database +github.com/ovn-kubernetes/libovsdb/database/inmemory +github.com/ovn-kubernetes/libovsdb/database/transaction +github.com/ovn-kubernetes/libovsdb/mapper +github.com/ovn-kubernetes/libovsdb/model +github.com/ovn-kubernetes/libovsdb/ovsdb +github.com/ovn-kubernetes/libovsdb/ovsdb/serverdb +github.com/ovn-kubernetes/libovsdb/server +github.com/ovn-kubernetes/libovsdb/updates # github.com/pborman/uuid v1.2.0 ## explicit github.com/pborman/uuid From 0e00ae6d43bd8d976385a47dafb2be364a7ad41d Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Tue, 1 Jul 2025 10:45:02 +0300 Subject: [PATCH 068/278] contrib,kind: Init container runtime binary on cluster deletion On cluster delete operations the container runtime binary (represented by OCI_BIN) is hardcoded. Set OCI_BIN according to env. Signed-off-by: Or Mergi --- contrib/kind.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/kind.sh b/contrib/kind.sh index 3d8bd0f30e..958c907e68 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -42,6 +42,8 @@ function setup_kubectl_bin() { # The root cause is unknown, this also can not be reproduced in Ubuntu 20.04 or # with Fedora32 Cloud, but it does not happen if we clean first the ovn-kubernetes resources. delete() { + OCI_BIN=${KIND_EXPERIMENTAL_PROVIDER:-docker} + if [ "$KIND_INSTALL_METALLB" == true ]; then destroy_metallb fi From 6ec1a4489b479a20306ec6d81f5a4882ab8d52c5 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Tue, 1 Jul 2025 10:50:56 +0300 Subject: [PATCH 069/278] kind-common, metallb: Avoid hard coding container runtime binary Set OCI_BIN according to env. Some inspect operation that use formatting did not work in podman due to formatting differences comparing to docker. The format string is changes to a form that fits both docker and podman With the new format string, the index keyword is redundant hence removed. Signed-off-by: Or Mergi --- contrib/kind-common | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/contrib/kind-common b/contrib/kind-common index e8bfb7be01..2c4b7d445f 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -174,16 +174,16 @@ EOF # Override GOBIN until https://github.com/metallb/metallb/issues/2218 is fixed. GOBIN="" inv dev-env -n ovn -b frr -p bgp -i "${ip_family}" - docker network rm -f clientnet - docker network create --subnet="${METALLB_CLIENT_NET_SUBNET_IPV4}" ${ipv6_network} --driver bridge clientnet - docker network connect clientnet frr + $OCI_BIN network rm -f clientnet + $OCI_BIN network create --subnet="${METALLB_CLIENT_NET_SUBNET_IPV4}" ${ipv6_network} --driver bridge clientnet + $OCI_BIN network connect clientnet frr if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then # Enable IPv6 forwarding in FRR - docker exec frr sysctl -w net.ipv6.conf.all.forwarding=1 + $OCI_BIN exec frr sysctl -w net.ipv6.conf.all.forwarding=1 fi # Note: this image let's us use it also for creating load balancer backends that can send big packets - docker rm -f lbclient - docker run --cap-add NET_ADMIN --user 0 -d --network clientnet --rm --name lbclient quay.io/itssurya/dev-images:metallb-lbservice + $OCI_BIN rm -f lbclient + $OCI_BIN run --cap-add NET_ADMIN --user 0 -d --network clientnet --rm --name lbclient quay.io/itssurya/dev-images:metallb-lbservice popd delete_metallb_dir @@ -197,18 +197,18 @@ EOF kubectl label node "$n" node.kubernetes.io/exclude-from-external-load-balancers- done - kind_network_v4=$(docker inspect -f '{{index .NetworkSettings.Networks "kind" "IPAddress"}}' frr) + kind_network_v4=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.kind.IPAddress}}' frr) echo "FRR kind network IPv4: ${kind_network_v4}" - kind_network_v6=$(docker inspect -f '{{index .NetworkSettings.Networks "kind" "GlobalIPv6Address"}}' frr) + kind_network_v6=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.kind.GlobalIPv6Address}}' frr) echo "FRR kind network IPv6: ${kind_network_v6}" local client_network_v4 client_network_v6 - client_network_v4=$(docker inspect -f '{{index .NetworkSettings.Networks "clientnet" "IPAddress"}}' frr) + client_network_v4=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.clientnet.IPAddress}}' frr) echo "FRR client network IPv4: ${client_network_v4}" - client_network_v6=$(docker inspect -f '{{index .NetworkSettings.Networks "clientnet" "GlobalIPv6Address"}}' frr) + client_network_v6=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.clientnet.GlobalIPv6Address}}' frr) echo "FRR client network IPv6: ${client_network_v6}" local client_subnets - client_subnets=$(docker network inspect clientnet -f '{{range .IPAM.Config}}{{.Subnet}}#{{end}}') + client_subnets=$($OCI_BIN network inspect clientnet -f '{{range .IPAM.Config}}{{.Subnet}}#{{end}}') echo "${client_subnets}" local client_subnets_v4 client_subnets_v6 client_subnets_v4=$(echo "${client_subnets}" | cut -d '#' -f 1) @@ -219,10 +219,10 @@ EOF KIND_NODES=$(kind_get_nodes) for n in ${KIND_NODES}; do if [ "$PLATFORM_IPV4_SUPPORT" == true ]; then - docker exec "${n}" ip route add "${client_subnets_v4}" via "${kind_network_v4}" + $OCI_BIN exec "${n}" ip route add "${client_subnets_v4}" via "${kind_network_v4}" fi if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then - docker exec "${n}" ip -6 route add "${client_subnets_v6}" via "${kind_network_v6}" + $OCI_BIN exec "${n}" ip -6 route add "${client_subnets_v6}" via "${kind_network_v6}" fi done @@ -230,10 +230,10 @@ EOF # one svcVIP (192.168.10.0/fc00:f853:ccd:e799::) is more than enough since at a time we will only # have one load balancer service if [ "$PLATFORM_IPV4_SUPPORT" == true ]; then - docker exec lbclient ip route add 192.168.10.0 via "${client_network_v4}" dev eth0 + $OCI_BIN exec lbclient ip route add 192.168.10.0 via "${client_network_v4}" dev eth0 fi if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then - docker exec lbclient ip -6 route add fc00:f853:ccd:e799:: via "${client_network_v6}" dev eth0 + $OCI_BIN exec lbclient ip -6 route add fc00:f853:ccd:e799:: via "${client_network_v6}" dev eth0 fi sleep 30 } @@ -254,14 +254,14 @@ install_plugins() { } destroy_metallb() { - if docker ps --format '{{.Names}}' | grep -Eq '^lbclient$'; then - docker stop lbclient + if $OCI_BIN ps --format '{{.Names}}' | grep -Eq '^lbclient$'; then + $OCI_BIN stop lbclient fi - if docker ps --format '{{.Names}}' | grep -Eq '^frr$'; then - docker stop frr + if $OCI_BIN ps --format '{{.Names}}' | grep -Eq '^frr$'; then + $OCI_BIN stop frr fi - if docker network ls --format '{{.Name}}' | grep -q '^clientnet$'; then - docker network rm clientnet + if $OCI_BIN network ls --format '{{.Name}}' | grep -q '^clientnet$'; then + $OCI_BIN network rm clientnet fi delete_metallb_dir } From 7d1999188349af5648438fb9518d0b6bb3676c75 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Tue, 1 Jul 2025 10:54:57 +0300 Subject: [PATCH 070/278] kind-common, k8s-frr, bgp: Avoid hard coding container runtime binary Set OCI_BIN according to env. Some inspect operation that use formatting did not work in podman due to formatting differences comparing to docker. The format string is changes to a form that fits both docker and podman. With the new format string, the index keyword is redundant hence removed. Signed-off-by: Or Mergi --- contrib/kind-common | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/contrib/kind-common b/contrib/kind-common index 2c4b7d445f..bbb7cda7e1 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -708,7 +708,7 @@ deploy_frr_external_container() { popd || exit 1 if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then # Enable IPv6 forwarding in FRR - docker exec frr sysctl -w net.ipv6.conf.all.forwarding=1 + $OCI_BIN exec frr sysctl -w net.ipv6.conf.all.forwarding=1 fi } @@ -735,40 +735,40 @@ deploy_bgp_external_server() { ip_family="ipv4" ipv6_network="" fi - docker rm -f bgpserver - docker network rm -f bgpnet - docker network create --subnet="${BGP_SERVER_NET_SUBNET_IPV4}" ${ipv6_network} --driver bridge bgpnet - docker network connect bgpnet frr - docker run --cap-add NET_ADMIN --user 0 -d --network bgpnet --rm --name bgpserver -p 8080:8080 registry.k8s.io/e2e-test-images/agnhost:2.45 netexec + $OCI_BIN rm -f bgpserver + $OCI_BIN network rm -f bgpnet + $OCI_BIN network create --subnet="${BGP_SERVER_NET_SUBNET_IPV4}" ${ipv6_network} --driver bridge bgpnet + $OCI_BIN network connect bgpnet frr + $OCI_BIN run --cap-add NET_ADMIN --user 0 -d --network bgpnet --rm --name bgpserver -p 8080:8080 registry.k8s.io/e2e-test-images/agnhost:2.45 netexec # let's make the bgp external server have its default route towards FRR router so that we don't need to add routes during tests back to the pods in the # cluster for return traffic local bgp_network_frr_v4 bgp_network_frr_v6 - bgp_network_frr_v4=$($OCI_BIN inspect -f '{{index .NetworkSettings.Networks "bgpnet" "IPAddress"}}' frr) + bgp_network_frr_v4=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.bgpnet.IPAddress}}' frr) echo "FRR kind network IPv4: ${bgp_network_frr_v4}" $OCI_BIN exec bgpserver ip route replace default via "$bgp_network_frr_v4" if [ "$PLATFORM_IPV6_SUPPORT" == true ] ; then - bgp_network_frr_v6=$($OCI_BIN inspect -f '{{index .NetworkSettings.Networks "bgpnet" "GlobalIPv6Address"}}' frr) + bgp_network_frr_v6=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.bgpnet.GlobalIPv6Address}}' frr) echo "FRR kind network IPv6: ${bgp_network_frr_v6}" $OCI_BIN exec bgpserver ip -6 route replace default via "$bgp_network_frr_v6" fi # disable the default route to make sure the container only routes accross # directly connected or learnt networks (doing this at the very end since # docker changes the routing table when a new network is connected) - docker exec frr ip route delete default - docker exec frr ip route - docker exec frr ip -6 route delete default - docker exec frr ip -6 route + $OCI_BIN exec frr ip route delete default + $OCI_BIN exec frr ip route + $OCI_BIN exec frr ip -6 route delete default + $OCI_BIN exec frr ip -6 route } destroy_bgp() { - if docker ps --format '{{.Names}}' | grep -Eq '^bgpserver$'; then - docker stop bgpserver + if $OCI_BIN ps --format '{{.Names}}' | grep -Eq '^bgpserver$'; then + $OCI_BIN stop bgpserver fi - if docker ps --format '{{.Names}}' | grep -Eq '^frr$'; then - docker stop frr + if $OCI_BIN ps --format '{{.Names}}' | grep -Eq '^frr$'; then + $OCI_BIN stop frr fi - if docker network ls --format '{{.Name}}' | grep -q '^bgpnet$'; then - docker network rm bgpnet + if $OCI_BIN network ls --format '{{.Name}}' | grep -q '^bgpnet$'; then + $OCI_BIN network rm bgpnet fi } @@ -807,7 +807,7 @@ install_ffr_k8s() { echo "Attempting to reach frr-k8s webhook" kind export kubeconfig --name ovn while true; do -docker exec ovn-control-plane curl -ksS --connect-timeout 0.1 https://$(kubectl get svc -n frr-k8s-system frr-k8s-webhook-service -o jsonpath='{.spec.clusterIP}') +$OCI_BIN exec ovn-control-plane curl -ksS --connect-timeout 0.1 https://$(kubectl get svc -n frr-k8s-system frr-k8s-webhook-service -o jsonpath='{.spec.clusterIP}') [ \$? -eq 0 ] && exit 0 echo "Couldn't reach frr-k8s webhook, trying in 1s..." sleep 1s From 44b7719615a65f8cfc474d682be1804dde141509 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Tue, 1 Jul 2025 23:39:39 +0300 Subject: [PATCH 071/278] e2e: Enable testing BGP using podman When using podman, BGP test suite fails due to checks against the env container runtime which are not compatible with podman: - Inspecting network objects is not compatible due to diffrences in how podman and docker persist network objects - List containers using JSON format To overcome the above, change network inspect operation and container list using format to a form that compatible with bot docker and podman. Signed-off-by: Or Mergi --- test/e2e/containerengine/container_engine.go | 10 ++++++++++ test/e2e/infraprovider/providers/kind/kind.go | 10 ++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/test/e2e/containerengine/container_engine.go b/test/e2e/containerengine/container_engine.go index 12d96829b2..a9281fbb48 100644 --- a/test/e2e/containerengine/container_engine.go +++ b/test/e2e/containerengine/container_engine.go @@ -12,6 +12,16 @@ func (ce ContainerEngine) String() string { return string(ce) } +func (ce ContainerEngine) NetworkCIDRsFmt() string { + if ce == Podman { + return "{{json .Subnets }}" + } + if ce == Docker { + return "{{json .IPAM.Config }}" + } + return "" +} + const ( Docker ContainerEngine = "docker" Podman ContainerEngine = "podman" diff --git a/test/e2e/infraprovider/providers/kind/kind.go b/test/e2e/infraprovider/providers/kind/kind.go index 9e1fe63e47..f58a5bc746 100644 --- a/test/e2e/infraprovider/providers/kind/kind.go +++ b/test/e2e/infraprovider/providers/kind/kind.go @@ -414,7 +414,6 @@ func (c *contextKind) cleanUp() error { const ( nameFormat = "{{.Name}}" - inspectNetworkIPAMJSON = "{{json .IPAM.Config }}" inspectNetworkIPv4GWKeyStr = "{{ .NetworkSettings.Networks.%s.Gateway }}" inspectNetworkIPv4AddrKeyStr = "{{ .NetworkSettings.Networks.%s.IPAddress }}" inspectNetworkIPv4PrefixKeyStr = "{{ .NetworkSettings.Networks.%s.IPPrefixLen }}" @@ -437,7 +436,7 @@ func isNetworkAttachedToContainer(networkName, containerName string) bool { func doesContainerNameExist(name string) bool { // check if it is present before retrieving logs - stdOut, err := exec.Command(containerengine.Get().String(), "ps", "-f", fmt.Sprintf("Name=^%s$", name), "-q").CombinedOutput() + stdOut, err := exec.Command(containerengine.Get().String(), "ps", "-f", fmt.Sprintf("name=^%s$", name), "-q").CombinedOutput() if err != nil { panic(fmt.Sprintf("failed to check if external container (%s) exists: %v (%s)", name, err, stdOut)) } @@ -466,13 +465,16 @@ func getNetwork(networkName string) (containerEngineNetwork, error) { return n, api.NotFound } configs := make([]containerEngineNetworkConfig, 0, 1) - dataBytes, err := exec.Command(containerengine.Get().String(), "network", "inspect", "-f", inspectNetworkIPAMJSON, networkName).CombinedOutput() + + ce := containerengine.Get() + netConfFmt := ce.NetworkCIDRsFmt() + dataBytes, err := exec.Command(ce.String(), "network", "inspect", "-f", netConfFmt, networkName).CombinedOutput() if err != nil { return n, fmt.Errorf("failed to extract network %q data: %v", networkName, err) } dataBytes = []byte(strings.Trim(string(dataBytes), "\n")) if err = json.Unmarshal(dataBytes, &configs); err != nil { - return n, fmt.Errorf("failed to unmarshall network %q configuration using network inspect -f %q: %v", networkName, inspectNetworkIPAMJSON, err) + return n, fmt.Errorf("failed to unmarshall network %q configuration using network inspect -f %q: %v", networkName, netConfFmt, err) } if len(configs) == 0 { return n, fmt.Errorf("failed to find any IPAM configuration for network %s", networkName) From 7588fd3a66c65fc05fdc870b9dd1c7c8de9ba0d3 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Wed, 11 Jun 2025 10:12:19 +0100 Subject: [PATCH 072/278] EgressIP: fix startup sync to add metadata Initial implementations erroneously assumed a CIDR for NATs logicalIP. Also, eip controller expects all OVN constructs that support EIP to have this metadata so if we cannot build this metadata then add dummy data so its cleaned up later by EIP controller. This was not caught by unit tests because the unit test also contained the assumption of only logical IP with no mask. It was not caught by upstream CI because we have no reboot tests. Signed-off-by: Martin Kennelly --- .../logical_router_policy_sync.go | 3 +- .../logical_router_policy_sync_test.go | 2 +- .../ovn/external_ids_syncer/nat/nat_sync.go | 22 ++++++--- .../external_ids_syncer/nat/nat_sync_test.go | 48 +++++++++---------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go index 01cbd40512..8933e78521 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go +++ b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync.go @@ -101,7 +101,8 @@ func (syncer *LRPSyncer) syncEgressIPReRoutes() error { podInfo, err := cache.getPod(podIP) if err != nil { klog.Infof("Failed to find Logical Switch Port cache entry for pod IP %s: %v", podIP.String(), err) - continue + // pod not found, add dummy metadata that will be cleaned up by EIP controller sync. + podInfo = podNetInfo{namespace: "UNKNOWN", name: "UNKNOWN"} } ipFamily := getIPFamily(isIPv6) lrp.ExternalIDs = getEgressIPLRPReRouteDbIDs(eipName, podInfo.namespace, podInfo.name, ipFamily, defaultNetworkName, syncer.controllerName).GetExternalIDs() diff --git a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync_test.go b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync_test.go index efebfb9c31..da0b0d2ff9 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync_test.go +++ b/go-controller/pkg/ovn/external_ids_syncer/logical_router_policy/logical_router_policy_sync_test.go @@ -122,7 +122,7 @@ var _ = ginkgo.Describe("OVN Logical Router Syncer", func() { map[string]string{"name": egressIPName}, defaultNetworkControllerName)}, finalLRPs: []*nbdb.LogicalRouterPolicy{getReRouteLRP(podNamespace, podName, v4PodIPStr, 0, v4IPFamilyValue, v4PodNextHops, - map[string]string{"name": egressIPName}, + getEgressIPLRPReRouteDbIDs(egressIPName, "UNKNOWN", "UNKNOWN", v4IPFamilyValue, defaultNetworkName, defaultNetworkControllerName).GetExternalIDs(), defaultNetworkControllerName)}, v4ClusterSubnets: []*net.IPNet{v4PodClusterSubnet}, v4JoinSubnet: v4JoinSubnet, diff --git a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go index 617bddd411..cf9d433cc8 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go +++ b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync.go @@ -10,6 +10,7 @@ import ( libovsdbclient "github.com/ovn-org/libovsdb/client" "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ovntypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -86,10 +87,10 @@ func (n *NATSyncer) syncEgressIPNATs() error { klog.Errorf("Expected NAT %s to contain 'name' as a key within its external IDs", nat.UUID) continue } - podIP, _, err := net.ParseCIDR(nat.LogicalIP) - if err != nil { - klog.Errorf("Failed to process logical IP %q of NAT %s", nat.LogicalIP, nat.UUID) - continue + // for egress IP, the logicalIP does not contain a mask. + podIP := net.ParseIP(nat.LogicalIP) + if podIP == nil { + return fmt.Errorf("failed to process logical IP %q of NAT %s", nat.LogicalIP, nat.UUID) } isV6 := utilsnet.IsIPv6(podIP) var ipFamily egressIPFamilyValue @@ -103,15 +104,15 @@ func (n *NATSyncer) syncEgressIPNATs() error { pod, found = v4PodCache.getPodByIP(podIP) } if !found { - klog.Errorf("Failed to find logical switch port that contains IP address %s", podIP.String()) - continue + // set it to unknown and the egress IP controller syncer will take care of removing it. + pod = podNetInfo{namespace: "UNKNOWN", name: "UNKNOWN"} + ipFamily = getFirstSupportIPFamily() } nat.ExternalIDs = getEgressIPNATDbIDs(eIPName, pod.namespace, pod.name, ipFamily, n.controllerName).GetExternalIDs() ops, err = libovsdbops.UpdateNATOps(n.nbClient, ops, nat) if err != nil { klog.Errorf("Failed to generate NAT ops for NAT %s: %v", nat.UUID, err) } - klog.Infof("## martin found %d nats", len(ops)) } _, err = libovsdbops.TransactAndCheck(n.nbClient, ops) @@ -176,3 +177,10 @@ func getEgressIPNATDbIDs(eIPName, podNamespace, podName string, ipFamily egressI libovsdbops.IPFamilyKey: string(ipFamily), }) } + +func getFirstSupportIPFamily() egressIPFamilyValue { + if config.IPv4Mode { + return ipFamilyValueV4 + } + return ipFamilyValueV6 +} diff --git a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync_test.go b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync_test.go index 9c0c9fa18d..58d8b54045 100644 --- a/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync_test.go +++ b/go-controller/pkg/ovn/external_ids_syncer/nat/nat_sync_test.go @@ -26,22 +26,22 @@ const ( egressIP = "10.10.10.10" nat1UUID = "nat-1-UUID" nat2UUID = "nat-2-UUID" - pod1V4CIDRStr = "10.128.0.5/32" - pod1V6CIDRStr = "2001:0000:130F:0000:0000:09C0:876A:130B/128" + pod1V4Str = "10.128.0.5" + pod1V6Str = "2001:0000:130F:0000:0000:09C0:876A:130B" pod1Namespace = "ns1" pod1Name = "pod1" - pod2V4CIDRStr = "10.128.0.6/32" - pod2V6CIDRStr = "2001:0000:130F:0000:0000:09C0:876A:130A/128" + pod2V4Str = "10.128.0.6" + pod2V6Str = "2001:0000:130F:0000:0000:09C0:876A:130A" pod2Namespace = "ns1" pod2Name = "pod2" defaultNetworkControllerName = "default-network-controller" ) var ( - pod1V4IPNet = testing.MustParseIPNet(pod1V4CIDRStr) - pod1V6IPNet = testing.MustParseIPNet(pod1V6CIDRStr) - pod2V4IPNet = testing.MustParseIPNet(pod2V4CIDRStr) - pod2V6IPNet = testing.MustParseIPNet(pod2V6CIDRStr) + pod1V4IP = testing.MustParseIP(pod1V4Str) + pod1V6IP = testing.MustParseIP(pod1V6Str) + pod2V4IP = testing.MustParseIP(pod2V4Str) + pod2V6IP = testing.MustParseIP(pod2V6Str) legacyExtIDs = map[string]string{legacyEIPNameExtIDKey: egressIPName} pod1V4ExtIDs = getEgressIPNATDbIDs(egressIPName, pod1Namespace, pod1Name, ipFamilyValueV4, defaultNetworkControllerName).GetExternalIDs() pod1V6ExtIDs = getEgressIPNATDbIDs(egressIPName, pod1Namespace, pod1Name, ipFamilyValueV6, defaultNetworkControllerName).GetExternalIDs() @@ -54,64 +54,64 @@ var _ = ginkgo.Describe("NAT Syncer", func() { ginkgo.DescribeTable("egress NATs", func(sync natSync) { performTest(defaultNetworkControllerName, sync.initialNATs, sync.finalNATs, sync.pods) }, ginkgo.Entry("converts legacy IPv4 NATs", natSync{ - initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4CIDRStr, egressIP, legacyExtIDs)}, - finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4CIDRStr, egressIP, pod1V4ExtIDs)}, + initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4Str, egressIP, legacyExtIDs)}, + finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4Str, egressIP, pod1V4ExtIDs)}, pods: podsNetInfo{ { - []net.IP{pod1V4IPNet.IP}, + []net.IP{pod1V4IP}, pod1Namespace, pod1Name, }, { - []net.IP{pod2V4IPNet.IP}, + []net.IP{pod2V4IP}, pod2Namespace, pod2Name, }, }, }), ginkgo.Entry("converts legacy IPv6 NATs", natSync{ - initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6CIDRStr, egressIP, legacyExtIDs)}, - finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6CIDRStr, egressIP, pod1V6ExtIDs)}, + initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6Str, egressIP, legacyExtIDs)}, + finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6Str, egressIP, pod1V6ExtIDs)}, pods: podsNetInfo{ { - []net.IP{pod1V6IPNet.IP}, + []net.IP{pod1V6IP}, pod1Namespace, pod1Name, }, { - []net.IP{pod2V6IPNet.IP}, + []net.IP{pod2V6IP}, pod2Namespace, pod2Name, }, }, }), ginkgo.Entry("converts legacy dual stack NATs", natSync{ - initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4CIDRStr, egressIP, legacyExtIDs), getSNAT(nat2UUID, pod1V6CIDRStr, egressIP, legacyExtIDs)}, - finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4CIDRStr, egressIP, pod1V4ExtIDs), getSNAT(nat2UUID, pod1V6CIDRStr, egressIP, pod1V6ExtIDs)}, + initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4Str, egressIP, legacyExtIDs), getSNAT(nat2UUID, pod1V6Str, egressIP, legacyExtIDs)}, + finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V4Str, egressIP, pod1V4ExtIDs), getSNAT(nat2UUID, pod1V6Str, egressIP, pod1V6ExtIDs)}, pods: podsNetInfo{ { - []net.IP{pod1V4IPNet.IP, pod1V6IPNet.IP}, + []net.IP{pod1V4IP, pod1V6IP}, pod1Namespace, pod1Name, }, { - []net.IP{pod2V4IPNet.IP, pod2V6IPNet.IP}, + []net.IP{pod2V4IP, pod2V6IP}, pod2Namespace, pod2Name, }, }, }), ginkgo.Entry("doesn't alter NAT with correct external IDs", natSync{ - initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6CIDRStr, egressIP, pod1V6ExtIDs)}, - finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6CIDRStr, egressIP, pod1V6ExtIDs)}, + initialNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6Str, egressIP, pod1V6ExtIDs)}, + finalNATs: []*nbdb.NAT{getSNAT(nat1UUID, pod1V6Str, egressIP, pod1V6ExtIDs)}, pods: podsNetInfo{ { - []net.IP{pod1V4IPNet.IP, pod1V6IPNet.IP}, + []net.IP{pod1V4IP, pod1V6IP}, pod1Namespace, pod1Name, }, { - []net.IP{pod2V4IPNet.IP, pod2V6IPNet.IP}, + []net.IP{pod2V4IP, pod2V6IP}, pod2Namespace, pod2Name, }, From 68db55ebec7162b54e100d4ca0ad2b84fd22fe86 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Mon, 7 Jul 2025 11:26:49 +0100 Subject: [PATCH 073/278] EIP OVN startup syncer: fix processing of ovn constructs The startup syncer was removing OVN constructs due to logic bugs introduced when EIP code was refactored for UDN. The are added again when eip controller syncs but this causes interruption. 1. Due to poor naming, enforcement of types and programmer error we were mixing up variables between a pod IP and an EIP IP. See: nodeName, ok := cache.egressIPIPToNodeCache[parsedLogicalIP.String()] parsedLogicalIP is a pod IP and not an EIP IP. 2. When iterating over the existing config for an EIP, we should delete config for LRPs where an EIP doesn't exist. 3. Remove LRPs when a network isnt found Signed-off-by: Martin Kennelly --- go-controller/pkg/ovn/egressip.go | 78 +++++++++++++++++++------------ 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index d53ba5e633..2bcedbdbec 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -1138,6 +1138,8 @@ func (e *EgressIPController) isLocalZoneNode(node *corev1.Node) bool { type egressIPCache struct { // egressIP name -> network name -> cache egressIPNameToPods map[string]map[string]selectedPods + // egressIP name -> to assigned Node names + egressIPNameToAssignedNodes map[string][]string // egressLocalNodes will contain all nodes that are local // to this zone which are serving this egressIP object.. // This will help sync SNATs @@ -1154,7 +1156,7 @@ type egressIPCache struct { } type nodeNetworkRedirects struct { - // node name -> network name -> redirect IPs + // network name -> node name -> redirect IPs cache map[string]map[string]redirectIPs } @@ -1600,21 +1602,36 @@ func (e *EgressIPController) syncPodAssignmentCache(egressIPCache egressIPCache) // It also removes stale nexthops from router policies used by EgressIPs. // Upon failure, it may be invoked multiple times in order to avoid a pod restart. func (e *EgressIPController) syncStaleEgressReroutePolicy(cache egressIPCache) error { - for _, networkCache := range cache.egressIPNameToPods { + for eipName, networkCache := range cache.egressIPNameToPods { for networkName, data := range networkCache { logicalRouterPolicyStaleNexthops := []*nbdb.LogicalRouterPolicy{} + // select LRPs scoped to the correct LRP priority, network and EIP name p := func(item *nbdb.LogicalRouterPolicy) bool { if item.Priority != types.EgressIPReroutePriority || item.ExternalIDs[libovsdbops.NetworkKey.String()] != networkName { return false } - egressIPName, _ := getEIPLRPObjK8MetaData(item.ExternalIDs) - if egressIPName == "" { + networkNodeRedirectCache, ok := cache.egressNodeRedirectsCache.cache[networkName] + if !ok || len(networkNodeRedirectCache) == 0 { + klog.Infof("syncStaleEgressReroutePolicy found invalid logical router policy (UUID: %s) because no assigned Nodes for EgressIP %s", item.UUID, eipName) + return true + } + extractedEgressIPName, _ := getEIPLRPObjK8MetaData(item.ExternalIDs) + if extractedEgressIPName == "" { klog.Errorf("syncStaleEgressReroutePolicy found logical router policy (UUID: %s) with invalid meta data associated with network %s", item.UUID, networkName) - return false + return true + } + if extractedEgressIPName != eipName { + // remove if there's no reference to this EIP name + _, ok := cache.egressIPNameToPods[extractedEgressIPName] + return !ok } splitMatch := strings.Split(item.Match, " ") - logicalIP := splitMatch[len(splitMatch)-1] - parsedLogicalIP := net.ParseIP(logicalIP) + podIPStr := splitMatch[len(splitMatch)-1] + podIP := net.ParseIP(podIPStr) + if podIP == nil { + klog.Infof("syncStaleEgressReroutePolicy found invalid LRP with broken match with UID %q", item.UUID) + return true + } egressPodIPs := sets.NewString() // Since LRPs are created only for pods local to this zone // we need to care about only those pods. Nexthop for them will @@ -1624,31 +1641,24 @@ func (e *EgressIPController) syncStaleEgressReroutePolicy(cache egressIPCache) e for _, podIPs := range data.egressLocalPods { egressPodIPs.Insert(podIPs.UnsortedList()...) } - if !egressPodIPs.Has(parsedLogicalIP.String()) { - klog.Infof("syncStaleEgressReroutePolicy will delete %s due to no nexthop or stale logical ip: %v", egressIPName, item) + if !egressPodIPs.Has(podIP.String()) { + klog.Infof("syncStaleEgressReroutePolicy will delete %s due to no nexthop or stale logical ip: %v", extractedEgressIPName, item) return true } // Check for stale nexthops that may exist in the logical router policy and store that in logicalRouterPolicyStaleNexthops. // Note: adding missing nexthop(s) to the logical router policy is done outside the scope of this function. staleNextHops := []string{} for _, nexthop := range item.Nexthops { - nodeName, ok := cache.egressIPIPToNodeCache[parsedLogicalIP.String()] - if ok { - klog.Infof("syncStaleEgressReroutePolicy will delete %s due to no node assigned to logical ip: %v", egressIPName, item) - return true - } - networksRedirects, ok := cache.egressNodeRedirectsCache.cache[nodeName] - if ok { - klog.Infof("syncStaleEgressReroutePolicy will delete %s due to no network in cache: %v", egressIPName, item) - return true - } - redirects, ok := networksRedirects[networkName] - if !ok { - klog.Infof("syncStaleEgressReroutePolicy will delete %s due to no redirects for network in cache: %v", egressIPName, item) - return true + // ensure valid next hop by iterating through the node config + var isFound bool // isFound is true, if the next hop IP is found within the set of assigned nodes + for _, nodeRedirect := range networkNodeRedirectCache { + if nodeRedirect.containsIP(nexthop) { + isFound = true + break + } } - //FIXME: be more specific about which is the valid next hop instead of relying on verifying if the IP is within a valid set of IPs. - if !redirects.containsIP(nexthop) { + if !isFound { + //FIXME: be more specific about which is the valid next hop instead of relying on verifying if the IP is within a valid set of IPs. staleNextHops = append(staleNextHops, nexthop) } } @@ -1907,9 +1917,12 @@ func (e *EgressIPController) generateCacheForEgressIP() (egressIPCache, error) { // This will help sync SNATs egressLocalNodesCache := sets.New[string]() cache.egressLocalNodesCache = egressLocalNodesCache - // egressIP name -> node name - egressNodesCache := make(map[string]string, 0) - cache.egressIPIPToNodeCache = egressNodesCache + // egressIP name -> nodes where the IPs are assigned + egressIPNameNodesCache := make(map[string][]string, 0) + cache.egressIPNameToAssignedNodes = egressIPNameNodesCache + // egressIP IP -> node name. Assigned node for EIP. + egressIPIPNodeCache := make(map[string]string, 0) + cache.egressIPIPToNodeCache = egressIPIPNodeCache cache.markCache = make(map[string]string) egressIPs, err := e.watchFactory.GetEgressIPs() if err != nil { @@ -1922,11 +1935,18 @@ func (e *EgressIPController) generateCacheForEgressIP() (egressIPCache, error) { } cache.markCache[egressIP.Name] = mark.String() egressIPsCache[egressIP.Name] = make(map[string]selectedPods, 0) + egressIPNameNodesCache[egressIP.Name] = make([]string, 0, len(egressIP.Status.Items)) for _, status := range egressIP.Status.Items { + eipIP := net.ParseIP(status.EgressIP) + if eipIP == nil { + klog.Errorf("Failed to parse EgressIP %s IP %q from status", egressIP.Name, status.EgressIP) + continue + } + egressIPIPNodeCache[eipIP.String()] = status.Node if localZoneNodes.Has(status.Node) { egressLocalNodesCache.Insert(status.Node) } - egressNodesCache[status.EgressIP] = status.Node + egressIPNameNodesCache[egressIP.Name] = append(egressIPNameNodesCache[egressIP.Name], status.Node) } namespaces, err = e.watchFactory.GetNamespacesBySelector(egressIP.Spec.NamespaceSelector) if err != nil { From 41a91515866aa607e3159857c64d8e7b37d54e02 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Mon, 7 Jul 2025 11:41:09 +0100 Subject: [PATCH 074/278] EIP OVN controller: remove possibility of crash, improve logging and readability No func changes. Check if obj is nil post parsing IP. Improve logging of stale OVN config. Signed-off-by: Martin Kennelly --- go-controller/pkg/ovn/egressip.go | 35 ++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index 2bcedbdbec..f90365aa0a 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -1446,7 +1446,7 @@ func (e *EgressIPController) syncStaleGWMarkRules(egressIPCache egressIPCache) e continue } for networkName, podCache := range networkPodCache { - for eIP, nodeName := range egressIPCache.egressIPIPToNodeCache { + for eIPIP, nodeName := range egressIPCache.egressIPIPToNodeCache { if !egressIPCache.egressLocalNodesCache.Has(nodeName) { continue } @@ -1460,7 +1460,7 @@ func (e *EgressIPController) syncStaleGWMarkRules(egressIPCache egressIPCache) e return fmt.Errorf("failed to create new network %s: %v", networkName, err) } routerName := ni.GetNetworkScopedGWRouterName(nodeName) - isEIPIPv6 := utilnet.IsIPv6String(eIP) + isEIPIPv6 := utilnet.IsIPv6String(eIPIP) for podKey, podIPs := range podCache.egressLocalPods { ops, err = processPodFn(ops, eIPName, podKey, egressIPCache.markCache[eIPName], routerName, networkName, podIPs, isEIPIPv6) if err != nil { @@ -1679,7 +1679,14 @@ func (e *EgressIPController) syncStaleEgressReroutePolicy(cache egressIPCache) e // Update Logical Router Policies that have stale nexthops. Notice that we must do this separately // because logicalRouterPolicyStaleNexthops must be populated first - klog.Infof("syncStaleEgressReroutePolicy will remove stale nexthops for network %s: %+v", networkName, logicalRouterPolicyStaleNexthops) + for _, staleNextHopLogicalRouterPolicy := range logicalRouterPolicyStaleNexthops { + if staleNextHopLogicalRouterPolicy.Nexthop == nil { + continue + } + klog.Infof("syncStaleEgressReroutePolicy will remove stale nexthops for LRP %q for network %s: %s", + staleNextHopLogicalRouterPolicy.UUID, networkName, *staleNextHopLogicalRouterPolicy.Nexthop) + } + err = libovsdbops.DeleteNextHopsFromLogicalRouterPolicies(e.nbClient, cache.networkToRouter[networkName], logicalRouterPolicyStaleNexthops...) if err != nil { return fmt.Errorf("unable to remove stale next hops from logical router policies for network %s: %v", networkName, err) @@ -1709,7 +1716,13 @@ func (e *EgressIPController) syncStaleSNATRules(egressIPCache egressIPCache) err return false } egressIPName := egressIPMeta[0] - parsedLogicalIP := net.ParseIP(item.LogicalIP).String() + // check logical IP maps to a valid pod + parsedPodIP := net.ParseIP(item.LogicalIP) + if parsedPodIP == nil { + klog.Errorf("syncStaleSNATRules found invalid logical IP for NAT with UID %q", item.UUID) + return true + } + parsedPodIPStr := parsedPodIP.String() cacheEntry, exists := egressIPCache.egressIPNameToPods[egressIPName][types.DefaultNetworkName] egressPodIPs := sets.NewString() if exists { @@ -1722,7 +1735,7 @@ func (e *EgressIPController) syncStaleSNATRules(egressIPCache egressIPCache) err egressPodIPs.Insert(podIPs.UnsortedList()...) } } - if !exists || !egressPodIPs.Has(parsedLogicalIP) { + if !exists || !egressPodIPs.Has(parsedPodIPStr) { klog.Infof("syncStaleSNATRules will delete %s due to logical ip: %v", egressIPName, item) return true } @@ -1731,9 +1744,15 @@ func (e *EgressIPController) syncStaleSNATRules(egressIPCache egressIPCache) err klog.Errorf("syncStaleSNATRules failed to find default network in networks cache") return false } - if node, ok := egressIPCache.egressIPIPToNodeCache[item.ExternalIP]; !ok || !cacheEntry.egressLocalPods[types.DefaultNetworkName].Has(node) || - item.LogicalPort == nil || *item.LogicalPort != ni.GetNetworkScopedK8sMgmtIntfName(node) { - klog.Infof("syncStaleSNATRules will delete %s due to external ip or stale logical port: %v", egressIPName, item) + // check external IP maps to a valid EgressIP IP and its assigned to a Node + node, ok := egressIPCache.egressIPIPToNodeCache[item.ExternalIP] + if !ok { + klog.Infof("syncStaleSNATRules found NAT %q without EIP assigned to a Node", item.UUID) + return true + } + // check logical port is set and correspondes to the correct egress node + if item.LogicalPort == nil || *item.LogicalPort != ni.GetNetworkScopedK8sMgmtIntfName(node) { + klog.Infof("syncStaleSNATRules found NAT %q with invalid logical port", item.UUID) return true } return false From 053585e9a7743c1fea5417449e987fe9c8f7c52e Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Mon, 7 Jul 2025 11:43:49 +0100 Subject: [PATCH 075/278] OVN EIP startup syncer: add UTs for pod / node deleted Removes config for deleted nodes/pods while controller was down and ensures ovn config is removed while preserving valid config. Signed-off-by: Martin Kennelly --- go-controller/pkg/ovn/egressip_test.go | 549 +++++++++++++++++++++++++ 1 file changed, 549 insertions(+) diff --git a/go-controller/pkg/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go index 43ec170acb..b05422bf65 100644 --- a/go-controller/pkg/ovn/egressip_test.go +++ b/go-controller/pkg/ovn/egressip_test.go @@ -52,6 +52,8 @@ const ( podV4IP3 = "10.128.1.3" podV4IP4 = "10.128.1.4" podV6IP = "ae70::66" + podV6IP2 = "be70::66" + podV6IP3 = "be70::67" v6GatewayIP = "ae70::1" v6Node1Subnet = "ae70::66/64" v6Node2Subnet = "be70::66/64" @@ -12901,6 +12903,553 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" }) }) + + ginkgo.Context("Sync", func() { + ginkgo.It("removes config for previously selected pods on a deleted Node", func() { + // node 1 is local zone and egress Node. + // pod was on node 2 but it is deleted. Node 2 previously was also an egress Node. + app.Action = func(*cli.Context) error { + config.OVNKubernetesFeature.EnableInterconnect = true + // dual stack cluster + config.IPv4Mode = true + config.IPv6Mode = true + egressNamespace := newNamespace(eipNamespace) + egressPod := corev1.Pod{ + ObjectMeta: newPodMeta(eipNamespace, podName, egressPodLabel), + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "containerName", + Image: "containerImage", + }, + }, + NodeName: node1Name, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + PodIP: podV4IP, + PodIPs: []corev1.PodIP{{IP: podV4IP}, {IP: podV6IP}}, + }, + } + // node 1 (local zone) + node1IPv4 := "192.168.126.210" + Node1IPv4CIDR := node1IPv4 + "/24" + node1IPv6 := "fc00:f853:ccd:e793::30" + node1IPv6CIDR := node1IPv6 + "/64" + node1TranSwitchIPv4CIDR := "100.88.0.2/16" + node1TranSwitchIPv6CIDR := "fd97::2/64" + _, node1IPV4Net, _ := net.ParseCIDR(v4Node1Subnet) + _, node1IPV6Net, _ := net.ParseCIDR(v6Node1Subnet) + nodeAnnotations := map[string]string{ + "k8s.ovn.org/l3-gateway-config": `{"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop":"192.168.126.1"}}`, + "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\",\"ipv6\":\"%s\"}}", nodeLogicalRouterIfAddrV4, nodeLogicalRouterIfAddrV6), + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", Node1IPv4CIDR, node1IPv6CIDR), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":[\"%s\", \"%s\"]}", v4Node1Subnet, v6Node1Subnet), + "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s\", \"ipv6\": \"%s\"}", node1TranSwitchIPv4CIDR, node1TranSwitchIPv6CIDR), + "k8s.ovn.org/zone-name": node1Name, + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\",\"%s\"]", Node1IPv4CIDR, node1IPv6CIDR), + } + node1 := getNodeObj(node1Name, nodeAnnotations, map[string]string{}) // add node to avoid error-ing out on transit switch IP fetch + // node 2 - deleted (remote zone) + node2TranSwitchIPv6 := "fd97::3" + eipIPv4 := "192.168.126.200" + eipIPv6 := "0:0:0:0:0:feff:c0a8:8e0d" + deletedPodIPv4 := podV4IP2 + // dual IP family EIP selecting one pod in local zone + eIP := egressipv1.EgressIP{ + ObjectMeta: newEgressIPMeta(egressIPName), + Spec: egressipv1.EgressIPSpec{ + EgressIPs: []string{ + eipIPv4, + eipIPv6, + }, + PodSelector: metav1.LabelSelector{ + MatchLabels: egressPodLabel, + }, + NamespaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": egressNamespace.Name, + }, + }, + }, + Status: egressipv1.EgressIPStatus{ + Items: []egressipv1.EgressIPStatusItem{ + { + Node: node1Name, + EgressIP: eipIPv4, + }, + // Previous was set to node 2 and Node was deleted while local zone EIP controller was down. + //{ + // Node: node2Name, + // EgressIP: eipIPv6, + //}, + }, + }, + } + ginkgo.By("start OVN DBs with valid and invalid (pod doesn't exist..) OVN config") + node1NatLogicalPortName := "k8s-" + node1Name + fakeOvn.startWithDBSetup( + libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + // LRPs to support EIP assigned to a remote node node thats deleted while the controller was down + // Valid LRP for IPv4 egress node. IPv4 egress Node is local. IPv6 egress node is remote and deleted but ovn config remains + getReRoutePolicy(podV4IP, "4", "valid-reroute-ipv4-UUID", + nodeLogicalRouterIPv4, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV4, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + // invalid LRP for IPv6 because remove node is deleted + getReRoutePolicy(podV6IP, "6", "invalid-reroute-ipv6-UUID", + []string{node2TranSwitchIPv6}, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV6, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + // NATs to support EIP assigned to the local node + // valid NAT + &nbdb.NAT{ + UUID: "valid-nat-ipv4-UUID", + LogicalIP: podV4IP, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPod.Namespace, egressPod.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + // invalid NAT for a deleted pod on remote node + &nbdb.NAT{ + UUID: "invalid-nat-ipv4-UUID", + LogicalIP: deletedPodIPv4, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressNamespace.Namespace, "deletedpod", IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + Policies: []string{"valid-reroute-ipv4-UUID", "invalid-reroute-ipv6-UUID"}, + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + Nat: []string{"valid-nat-ipv4-UUID", "invalid-nat-ipv4-UUID"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1.Name + "-UUID", + Name: "k8s-" + node1.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1.Name + "-UUID", + Name: node1.Name, + Ports: []string{"k8s-" + node1.Name + "-UUID"}, + }, + }, + }, + &corev1.NamespaceList{ + Items: []corev1.Namespace{*egressNamespace}, + }, + &corev1.PodList{ + Items: []corev1.Pod{egressPod}, + }, + &corev1.NodeList{ + Items: []corev1.Node{node1}, + }, + &egressipv1.EgressIPList{ + Items: []egressipv1.EgressIP{eIP}, + }, + ) + i, podIPv4Net, _ := net.ParseCIDR(podV4IP + "/23") + podIPv4Net.IP = i + i, podIPv6Net, _ := net.ParseCIDR(podV6IP + "/23") + podIPv6Net.IP = i + fakeOvn.controller.logicalPortCache.add(&egressPod, "", types.DefaultNetworkName, "", nil, []*net.IPNet{podIPv4Net, podIPv6Net}) + + // hack pod to be in the provided zone + fakeOvn.controller.eIPC.nodeZoneState.Store(node1Name, true) + fakeOvn.controller.eIPC.nodeZoneState.Store(node2Name, false) + fakeOvn.controller.localZoneNodes.Store(node1Name, true) + fakeOvn.controller.localZoneNodes.Store(node2Name, false) + + err := fakeOvn.controller.WatchEgressIPNamespaces() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIPPods() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIP() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + ginkgo.By("ensuring cleanup of invalid LRP and NAT") + egressIPServedPodsASv4, egressIPServedPodsASv6 := buildEgressIPServedPodsAddressSets([]string{podV4IP, podV6IP}, types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName) + expectedDatabaseState := []libovsdbtest.TestData{ + getReRoutePolicy(podV4IP, "4", "valid-reroute-ipv4-UUID", + nodeLogicalRouterIPv4, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV4, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + &nbdb.NAT{ + UUID: "valid-egressip-nat-UUID", + LogicalIP: podV4IP, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPod.Namespace, egressPod.Name, IPFamilyValueV4, fakeOvn.controller.controllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + Policies: []string{"valid-reroute-ipv4-UUID"}, + }, + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Nat: []string{"valid-egressip-nat-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1.Name + "-UUID", + Name: "k8s-" + node1.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1.Name + "-UUID", + Name: node1.Name, + Ports: []string{"k8s-" + node1.Name + "-UUID"}, + }, + egressIPServedPodsASv4, + egressIPServedPodsASv6, + } + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + ginkgo.By("ensure config is consistent") + gomega.Consistently(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + return nil + } + + err := app.Run([]string{app.Name}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.It("remove invalid OVN config for deleted pod", func() { + // removes invalid SNAT/NAT for a pod that was selected by an EIP but was removed while controller was not running and therefore OVN config should be removed + // does not modify valid SNAT/NAT + // further references to "local" or "remote" imply local or remote OVN zone for IC. + // one EIP object with two assigned IPs of different IP families (v4 and v6) which select one pod that's local + app.Action = func(*cli.Context) error { + config.OVNKubernetesFeature.EnableInterconnect = true + // dual stack cluster + config.IPv4Mode = true + config.IPv6Mode = true + egressPod := corev1.Pod{ + ObjectMeta: newPodMeta(eipNamespace, podName, egressPodLabel), + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "containerName", + Image: "containerImage", + }, + }, + NodeName: node1Name, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + PodIP: podV4IP, + PodIPs: []corev1.PodIP{{IP: podV4IP}, {IP: podV6IP}}, + }, + } + // deletedPodIP is a pod IP of a Pod that was deleted while eip controller was not running therefore config will exist in OVN DBs to support EIP + deletedPodIPv4, deletedPod2IPv4, deletedPodIPv6, deletedPod2IPv6 := podV4IP2, "10.128.0.20", podV6IP2, podV6IP3 + egressNamespace := newNamespace(eipNamespace) + // node 1 (local zone) + node1IPv4 := "192.168.126.210" + Node1IPv4CIDR := node1IPv4 + "/24" + node1IPv6 := "fc00:f853:ccd:e793::30" + node1IPv6CIDR := node1IPv6 + "/64" + node1TranSwitchIPv4CIDR := "100.88.0.2/16" + node1TranSwitchIPv6CIDR := "fd97::2/64" + _, node1IPV4Net, _ := net.ParseCIDR(v4Node1Subnet) + _, node1IPV6Net, _ := net.ParseCIDR(v6Node1Subnet) + nodeAnnotations := map[string]string{ + "k8s.ovn.org/l3-gateway-config": `{"default":{"mode":"local","mac-address":"7e:57:f8:f0:3c:49", "ip-address":"192.168.126.12/24", "next-hop":"192.168.126.1"}}`, + "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\",\"ipv6\":\"%s\"}}", nodeLogicalRouterIfAddrV4, nodeLogicalRouterIfAddrV6), + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", Node1IPv4CIDR, node1IPv6CIDR), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":[\"%s\", \"%s\"]}", v4Node1Subnet, v6Node1Subnet), + "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s\", \"ipv6\": \"%s\"}", node1TranSwitchIPv4CIDR, node1TranSwitchIPv6CIDR), + "k8s.ovn.org/zone-name": node1Name, + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\",\"%s\"]", Node1IPv4CIDR, node1IPv6CIDR), + } + node1 := getNodeObj(node1Name, nodeAnnotations, map[string]string{}) // add node to avoid error-ing out on transit switch IP fetch + // node 2 (remote zone) + node2IPv4 := "192.168.126.202" + node2IPv4CIDR := node2IPv4 + "/24" + node2IPv6 := "fc00:f853:cce:e793::20" + node2IPv6CIDR := node2IPv6 + "/64" + node2TranSwitchIPv4 := "100.88.0.3" + node2TranSwitchIPv4CIDR := node2TranSwitchIPv4 + "/16" + node2TranSwitchIPv6 := "fd97::3" + node2TranSwitchIPv6CIDR := node2TranSwitchIPv6 + "/64" + _, node2IPV4Net, _ := net.ParseCIDR(v4Node2Subnet) + _, node2IPV6Net, _ := net.ParseCIDR(v6Node2Subnet) + nodeAnnotations = map[string]string{ + "k8s.ovn.org/node-gateway-router-lrp-ifaddrs": fmt.Sprintf("{\"default\":{\"ipv4\":\"%s\",\"ipv6\":\"%s\"}}", node2LogicalRouterIfAddrV4, node2LogicalRouterIfAddrV6), + "k8s.ovn.org/node-primary-ifaddr": fmt.Sprintf("{\"ipv4\": \"%s\", \"ipv6\": \"%s\"}", node2IPv4CIDR, node2IPv6CIDR), + "k8s.ovn.org/node-subnets": fmt.Sprintf("{\"default\":[\"%s\", \"%s\"]}", v4Node2Subnet, v6Node2Subnet), + "k8s.ovn.org/node-transit-switch-port-ifaddr": fmt.Sprintf("{\"ipv4\":\"%s\", \"ipv6\": \"%s\"}", node2TranSwitchIPv4CIDR, node2TranSwitchIPv6CIDR), + "k8s.ovn.org/zone-name": node2Name, + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\",\"%s\"]", node2IPv4CIDR, node2IPv6CIDR), + } + node2 := getNodeObj(node2Name, nodeAnnotations, map[string]string{}) + eipIPv4 := "192.168.126.200" + eipIPv6 := "0:0:0:0:0:feff:c0a8:8e0d" + // dual IP family EIP selecting one pod in local zone + eIP := egressipv1.EgressIP{ + ObjectMeta: newEgressIPMeta(egressIPName), + Spec: egressipv1.EgressIPSpec{ + EgressIPs: []string{ + eipIPv4, + eipIPv6, + }, + PodSelector: metav1.LabelSelector{ + MatchLabels: egressPodLabel, + }, + NamespaceSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": egressNamespace.Name, + }, + }, + }, + Status: egressipv1.EgressIPStatus{ + Items: []egressipv1.EgressIPStatusItem{ + { + Node: node1Name, + EgressIP: eipIPv4, + }, + { + Node: node2Name, + EgressIP: eipIPv6, + }, + }, + }, + } + ginkgo.By("start OVN DBs with valid and invalid ( 2 pods don't exist..) OVN config") + node1NatLogicalPortName := "k8s-" + node1Name + fakeOvn.startWithDBSetup( + libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + // LRPs to support EIP assigned to a remote node + // valid LRP for IPv4/IPv6. IPv4 Egress Node is local, IPv6 is remote + getReRoutePolicy(podV4IP, "4", "valid-reroute-ipv4-UUID", + nodeLogicalRouterIPv4, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV4, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + getReRoutePolicy(podV6IP, "6", "valid-reroute-ipv6-UUID", + []string{node2TranSwitchIPv6}, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV6, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + // invalid lrps to redirect to the remote egress node for deleted pods + getReRoutePolicy(deletedPodIPv6, "6", "invalid-reroute-ipv6-UUID", + []string{node2TranSwitchIPv6}, getEgressIPLRPReRouteDbIDs(eIP.Name, "UNKNOWN", "UNKNOWN", + IPFamilyValueV6, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + getReRoutePolicy(deletedPod2IPv6, "6", "invalid-reroute2-ipv6-UUID", + []string{node2TranSwitchIPv6}, getEgressIPLRPReRouteDbIDs(eIP.Name, "UNKNOWN", "UNKNOWN", + IPFamilyValueV6, types.DefaultNetworkName, DefaultNetworkControllerName).GetExternalIDs()), + // NATs to support EIP assigned to the local node + // valid NAT + &nbdb.NAT{ + UUID: "valid-nat-ipv4-UUID", + LogicalIP: podV4IP, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPod.Namespace, egressPod.Name, IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + // invalid NATs + &nbdb.NAT{ + UUID: "invalid-nat-ipv4-UUID", + LogicalIP: deletedPodIPv4, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, "UNKNOWN", "UNKNOWN", IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.NAT{ + UUID: "invalid-nat2-ipv4-UUID", + LogicalIP: deletedPod2IPv4, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, "UNKNOWN", "UNKNOWN", IPFamilyValueV4, DefaultNetworkControllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + Policies: []string{"valid-reroute-ipv4-UUID", "valid-reroute-ipv6-UUID", "invalid-reroute-ipv6-UUID", "invalid-reroute2-ipv6-UUID"}, + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + Nat: []string{"valid-nat-ipv4-UUID", "invalid-nat-ipv4-UUID"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1.Name + "-UUID", + Name: "k8s-" + node1.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node2.Name + "-UUID", + Name: "k8s-" + node2.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fd " + util.GetNodeManagementIfAddr(node2IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fd " + util.GetNodeManagementIfAddr(node2IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1.Name + "-UUID", + Name: node1.Name, + Ports: []string{"k8s-" + node1.Name + "-UUID"}, + }, + &nbdb.LogicalSwitch{ + UUID: node2.Name + "-UUID", + Name: node2.Name, + Ports: []string{"k8s-" + node2.Name + "-UUID"}, + }, + }, + }, + &corev1.NamespaceList{ + Items: []corev1.Namespace{*egressNamespace}, + }, + &corev1.PodList{ + Items: []corev1.Pod{egressPod}, + }, + &corev1.NodeList{ + Items: []corev1.Node{node1, node2}, + }, + &egressipv1.EgressIPList{ + Items: []egressipv1.EgressIP{eIP}, + }, + ) + i, podIPv4Net, _ := net.ParseCIDR(podV4IP + "/23") + podIPv4Net.IP = i + i, podIPv6Net, _ := net.ParseCIDR(podV6IP + "/23") + podIPv6Net.IP = i + fakeOvn.controller.logicalPortCache.add(&egressPod, "", types.DefaultNetworkName, "", nil, []*net.IPNet{podIPv4Net, podIPv6Net}) + + // hack pod to be in the provided zone + fakeOvn.controller.eIPC.nodeZoneState.Store(node1Name, true) + fakeOvn.controller.eIPC.nodeZoneState.Store(node2Name, false) + fakeOvn.controller.localZoneNodes.Store(node1Name, true) + fakeOvn.controller.localZoneNodes.Store(node2Name, false) + + err := fakeOvn.controller.WatchEgressIPNamespaces() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIPPods() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchEgressIP() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + ginkgo.By("ensuring cleanup of invalid LRP and NAT") + egressIPServedPodsASv4, egressIPServedPodsASv6 := buildEgressIPServedPodsAddressSets([]string{podV4IP, podV6IP}, types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName) + expectedDatabaseState := []libovsdbtest.TestData{ + // LRPs to support EIP assigned to a remote node + // valid LRP for IPv4/IPv6. IPv4 Egress Node is local, IPv6 is remote + getReRoutePolicy(podV4IP, "4", "valid-reroute-ipv4-UUID", + nodeLogicalRouterIPv4, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV4, types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName).GetExternalIDs()), + getReRoutePolicy(podV6IP, "6", "valid-reroute-ipv6-UUID", + []string{node2TranSwitchIPv6}, getEgressIPLRPReRouteDbIDs(eIP.Name, egressPod.Namespace, egressPod.Name, + IPFamilyValueV6, types.DefaultNetworkName, fakeOvn.controller.eIPC.controllerName).GetExternalIDs()), + // valid NAT + &nbdb.NAT{ + UUID: "valid-egressip-nat-UUID", + LogicalIP: podV4IP, + ExternalIP: eipIPv4, + ExternalIDs: getEgressIPNATDbIDs(egressIPName, egressPod.Namespace, egressPod.Name, IPFamilyValueV4, fakeOvn.controller.controllerName).GetExternalIDs(), + Type: nbdb.NATTypeSNAT, + LogicalPort: &node1NatLogicalPortName, + Options: map[string]string{ + "stateless": "false", + }, + }, + &nbdb.LogicalRouter{ + Name: types.OVNClusterRouter, + UUID: types.OVNClusterRouter + "-UUID", + Policies: []string{"valid-reroute-ipv4-UUID", "valid-reroute-ipv6-UUID"}, + }, + &nbdb.LogicalRouterPort{ + UUID: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID", + Name: types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name, + Networks: []string{nodeLogicalRouterIfAddrV6, nodeLogicalRouterIfAddrV4}, + }, + &nbdb.LogicalRouter{ + Name: types.GWRouterPrefix + node1Name, + UUID: types.GWRouterPrefix + node1Name + "-UUID", + Ports: []string{types.GWRouterToJoinSwitchPrefix + types.GWRouterPrefix + node1Name + "-UUID"}, + Nat: []string{"valid-egressip-nat-UUID"}, + Options: map[string]string{"dynamic_neigh_routers": "false"}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node1.Name + "-UUID", + Name: "k8s-" + node1.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fb " + util.GetNodeManagementIfAddr(node1IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitchPort{ + UUID: "k8s-" + node2.Name + "-UUID", + Name: "k8s-" + node2.Name, + Addresses: []string{"fe:1a:b2:3f:0e:fd " + util.GetNodeManagementIfAddr(node2IPV4Net).IP.String(), + "fe:1a:b2:3f:0e:fd " + util.GetNodeManagementIfAddr(node2IPV6Net).IP.String()}, + }, + &nbdb.LogicalSwitch{ + UUID: node1.Name + "-UUID", + Name: node1.Name, + Ports: []string{"k8s-" + node1.Name + "-UUID"}, + }, + &nbdb.LogicalSwitch{ + UUID: node2.Name + "-UUID", + Name: node2.Name, + Ports: []string{"k8s-" + node2.Name + "-UUID"}, + }, + egressIPServedPodsASv4, + egressIPServedPodsASv6, + } + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + ginkgo.By("ensure config is consistent") + gomega.Consistently(fakeOvn.nbClient).Should(libovsdbtest.HaveData(expectedDatabaseState)) + return nil + } + + err := app.Run([]string{app.Name}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + }) + }) // TEST UTILITY FUNCTIONS; From 1448d5ab14337b647f3e3034ea4ffc077431979a Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Wed, 7 May 2025 15:37:29 -0400 Subject: [PATCH 076/278] Drop in_port from ip dispatch OF rule The in_port was to match on IP traffic coming from the physical link to be dispatched to conntrack and table 1 to find out whether the packet was a reply to the host or to OVN. We are now conntracking these packets also as they go to localnet ports attached to the bridge. Therefore we need to also match on packets from those ports. We do not want traffic from OVN or from LOCAL to hit this flow, but that should be avoided by higher priority flows. Signed-off-by: Tim Rozet (cherry picked from commit 8c1594ee55408ae4748d7322bd093e2acbc0ce98) --- go-controller/pkg/node/gateway_shared_intf.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 00c96cef1a..8d771d5054 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -2003,11 +2003,12 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin } if ofPortPhys != "" { - // table 0, packets coming from external. Send it through conntrack and + // table 0, packets coming from external or other localnet ports. Send it through conntrack and // resubmit to table 1 to know the state and mark of the connection. + // Note, there are higher priority rules that take care of traffic coming from LOCAL and OVN ports. dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=50, in_port=%s, ip, "+ - "actions=ct(zone=%d, nat, table=1)", defaultOpenFlowCookie, ofPortPhys, config.Default.ConntrackZone)) + fmt.Sprintf("cookie=%s, priority=50, ip, actions=ct(zone=%d, nat, table=1)", + defaultOpenFlowCookie, config.Default.ConntrackZone)) } } From 3de7eade84e858cac080bb9386e4301d901b726d Mon Sep 17 00:00:00 2001 From: Riccardo Ravaioli Date: Wed, 7 May 2025 10:20:51 -0400 Subject: [PATCH 077/278] Reapply "Add flow for host -> localnet on same node" This reverts commit ebb73398310c882902f0f8b297bb8386d039ecfc. Signed-off-by: Riccardo Ravaioli --- go-controller/pkg/node/gateway_shared_intf.go | 107 +++++++++++------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 8d771d5054..7556aa54f7 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1956,9 +1956,10 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.masqCTMark, ofPortPhys)) - // Allow OVN->Host traffic on the same node + // Allow (a) OVN->host traffic on the same node + // (b) host->host traffic on the same node if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { - dftFlows = append(dftFlows, ovnToHostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...) + dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...) } } else { // for UDN we additionally SNAT the packet from masquerade IP -> node IP @@ -2053,9 +2054,10 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.masqCTMark, ofPortPhys)) - // Allow OVN->Host traffic on the same node + // Allow (a) OVN->host traffic on the same node + // (b) host->host traffic on the same node if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { - dftFlows = append(dftFlows, ovnToHostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...) + dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...) } } else { // for UDN we additionally SNAT the packet from masquerade IP -> node IP @@ -2250,23 +2252,15 @@ func pmtudDropFlows(bridge *bridgeConfiguration, ipAddrs []string) []string { return flows } -// ovnToHostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic from the OVN network to the host network -// when the destination is on the same node as the sender. This is necessary for pods in the default network to reach -// localnet pods on the same node, when the localnet is mapped to breth0. The expected srcMAC is the MAC address of breth0 -// and the expected hostSubnets is the host subnets found on the node primary interface. -func ovnToHostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { - var inPort, ctMark, ipFamily, ipFamilyDest string +// hostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic: +// a. from pods in the OVN network to pods in a localnet network, on the same node +// b. from pods on the host to pods in a localnet network, on the same node +// when the localnet is mapped to breth0. +// The expected srcMAC is the MAC address of breth0 and the expected hostSubnets is the host subnets found on the node +// primary interface. +func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { var flows []string - - if config.Gateway.Mode == config.GatewayModeShared { - inPort = netConfig.ofPortPatch - ctMark = netConfig.masqCTMark - } else if config.Gateway.Mode == config.GatewayModeLocal { - inPort = "LOCAL" - ctMark = ctMarkHost - } else { - return nil - } + var ipFamily, ipFamilyDest string if isV6 { ipFamily = "ipv6" @@ -2276,38 +2270,69 @@ func ovnToHostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC ipFamilyDest = "nw_dst" } + formatFlow := func(inPort, destIP, ctMark string) string { + // Matching IP traffic will be handled by the bridge instead of being output directly + // to the NIC by the existing flow at prio=100. + flowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, " + + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" + return fmt.Sprintf(flowTemplate, + defaultOpenFlowCookie, + inPort, + srcMAC, + ipFamily, + ipFamilyDest, + destIP, + config.Default.ConntrackZone, + ctMark) + } + + // Traffic path (a): OVN->localnet for shared gw mode + if config.Gateway.Mode == config.GatewayModeShared { + for _, hostSubnet := range hostSubnets { + if utilnet.IsIPv6(hostSubnet.IP) != isV6 { + continue + } + flows = append(flows, formatFlow(netConfig.ofPortPatch, hostSubnet.String(), netConfig.masqCTMark)) + } + } + + // Traffic path (a): OVN->localnet for local gw mode + // Traffic path (b): host->localnet for both gw modes for _, hostSubnet := range hostSubnets { - if (hostSubnet.IP.To4() == nil) != isV6 { + if utilnet.IsIPv6(hostSubnet.IP) != isV6 { continue } - // IP traffic from the OVN network to the host network should be handled normally by the bridge instead of - // being output directly to the NIC by the existing flow at prio=100. - flows = append(flows, - fmt.Sprintf("cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL", + flows = append(flows, formatFlow(ovsLocalPort, hostSubnet.String(), ctMarkHost)) + } + + if isV6 { + // IPv6 neighbor discovery uses ICMPv6 messages sent to a special destination (ff02::1:ff00:0/104) + // that is unrelated to the host subnets matched in the prio=102 flow above. + // Allow neighbor discovery by matching against ICMP type and ingress port. + formatICMPFlow := func(inPort, ctMark string, icmpType int) string { + icmpFlowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, " + + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" + return fmt.Sprintf(icmpFlowTemplate, defaultOpenFlowCookie, inPort, srcMAC, - ipFamily, - ipFamilyDest, - hostSubnet.String(), + icmpType, config.Default.ConntrackZone, - ctMark)) - } + ctMark) + } - if isV6 { - // Neighbor discovery in IPv6 happens through ICMPv6 messages to a special destination (ff02::1:ff00:0/104), - // which has nothing to do with the host subnets we're matching against in the flow above at prio=102. - // Let's allow neighbor discovery by matching against icmp type and in_port. for _, icmpType := range []int{types.NeighborSolicitationICMPType, types.NeighborAdvertisementICMPType} { - flows = append(flows, - fmt.Sprintf("cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL", - defaultOpenFlowCookie, inPort, srcMAC, icmpType, - config.Default.ConntrackZone, ctMark)) + // Traffic path (a) for ICMP: OVN-> localnet for shared gw mode + if config.Gateway.Mode == config.GatewayModeShared { + flows = append(flows, + formatICMPFlow(netConfig.ofPortPatch, netConfig.masqCTMark, icmpType)) + } + + // Traffic path (a) for ICMP: OVN->localnet for local gw mode + // Traffic path (b) for ICMP: host->localnet for both gw modes + flows = append(flows, formatICMPFlow(ovsLocalPort, ctMarkHost, icmpType)) } } - return flows } From 2626e8d5bd4e0d43e69da98ea518e8c76ca62881 Mon Sep 17 00:00:00 2001 From: Riccardo Ravaioli Date: Wed, 7 May 2025 10:21:02 -0400 Subject: [PATCH 078/278] Reapply "e2e: connect to host-networked pod from localnet" This reverts commit 936e6214a82062eb51f0649db2f74dcb9d205e12. Signed-off-by: Riccardo Ravaioli --- test/e2e/multihoming.go | 66 +++++++++++++++++++++++++++++++++-- test/e2e/multihoming_utils.go | 45 +++++++++++++++++------- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index a2f611676b..e16fa151a0 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -333,17 +333,31 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { kickstartPod(cs, clientPodConfig) // Check that the client pod can reach the server pod on the server localnet interface - serverIPs, err := podIPsForAttachment(cs, f.Namespace.Name, serverPod.GetName(), netConfig.name) + var serverIPs []string + if serverPodConfig.hostNetwork { + serverIPs, err = podIPsFromStatus(cs, serverPodConfig.namespace, serverPodConfig.name) + } else { + serverIPs, err = podIPsForAttachment(cs, serverPod.Namespace, serverPod.Name, netConfig.name) + + } Expect(err).NotTo(HaveOccurred()) + for _, serverIP := range serverIPs { By(fmt.Sprintf("asserting the *client* can contact the server pod exposed endpoint: %q on port %q", serverIP, port)) + curlArgs := []string{} + pingArgs := []string{} + if clientPodConfig.attachments != nil { + // When the client is attached to a localnet, send probes from the localnet interface + curlArgs = []string{"--interface", "net1"} + pingArgs = []string{"-I", "net1"} + } Eventually(func() error { - return reachServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, port) + return reachServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, port, curlArgs...) }, 2*time.Minute, 6*time.Second).Should(Succeed()) By(fmt.Sprintf("asserting the *client* can ping the server pod exposed endpoint: %q", serverIP)) Eventually(func() error { - return pingServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP) + return pingServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, pingArgs...) }, 2*time.Minute, 6*time.Second).Should(Succeed()) } }, @@ -391,6 +405,52 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }, Label("BUG", "OCPBUGS-43004"), ), + ginkgo.Entry( + "can reach a host-networked pod on a different node", + networkAttachmentConfigParams{ + name: secondaryNetworkName, + topology: "localnet", + }, + podConfiguration{ // client on localnet + attachments: []nadapi.NetworkSelectionElement{{ + Name: secondaryNetworkName, + }}, + name: clientPodName, + nodeSelector: map[string]string{nodeHostnameKey: workerOneNodeName}, + isPrivileged: true, + needsIPRequestFromHostSubnet: true, + }, + podConfiguration{ // server on default network, pod is host-networked + name: podName, + containerCmd: httpServerContainerCmd(port), + nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, + hostNetwork: true, + }, + Label("STORY", "SDN-5345"), + ), + ginkgo.Entry( + "can reach a host-networked pod on the same node", + networkAttachmentConfigParams{ + name: secondaryNetworkName, + topology: "localnet", + }, + podConfiguration{ // client on localnet + attachments: []nadapi.NetworkSelectionElement{{ + Name: secondaryNetworkName, + }}, + name: clientPodName, + nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, + isPrivileged: true, + needsIPRequestFromHostSubnet: true, + }, + podConfiguration{ // server on default network, pod is host-networked + name: podName, + containerCmd: httpServerContainerCmd(port), + nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, + hostNetwork: true, + }, + Label("STORY", "SDN-5345"), + ), ) }) diff --git a/test/e2e/multihoming_utils.go b/test/e2e/multihoming_utils.go index db55689986..636ea78eba 100644 --- a/test/e2e/multihoming_utils.go +++ b/test/e2e/multihoming_utils.go @@ -161,6 +161,7 @@ type podConfiguration struct { isPrivileged bool labels map[string]string requiresExtraNamespace bool + hostNetwork bool needsIPRequestFromHostSubnet bool } @@ -171,6 +172,7 @@ func generatePodSpec(config podConfiguration) *v1.Pod { } podSpec.Spec.NodeSelector = config.nodeSelector podSpec.Labels = config.labels + podSpec.Spec.HostNetwork = config.hostNetwork if config.isPrivileged { podSpec.Spec.Containers[0].SecurityContext.Privileged = ptr.To(true) } else { @@ -253,17 +255,19 @@ func inRange(cidr string, ip string) error { return fmt.Errorf("ip [%s] is NOT in range %s", ip, cidr) } -func connectToServer(clientPodConfig podConfiguration, serverIP string, port uint16) error { - _, err := e2ekubectl.RunKubectl( - clientPodConfig.namespace, +func connectToServer(clientPodConfig podConfiguration, serverIP string, port uint16, args ...string) error { + target := net.JoinHostPort(serverIP, fmt.Sprintf("%d", port)) + baseArgs := []string{ "exec", clientPodConfig.name, "--", "curl", "--connect-timeout", "2", - net.JoinHostPort(serverIP, fmt.Sprintf("%d", port)), - ) + } + baseArgs = append(baseArgs, args...) + + _, err := e2ekubectl.RunKubectl(clientPodConfig.namespace, append(baseArgs, target)...) return err } @@ -308,16 +312,19 @@ func getSecondaryInterfaceMTU(clientPodConfig podConfiguration) (int, error) { return mtu, nil } -func pingServer(clientPodConfig podConfiguration, serverIP string) error { - _, err := e2ekubectl.RunKubectl( - clientPodConfig.namespace, +func pingServer(clientPodConfig podConfiguration, serverIP string, args ...string) error { + baseArgs := []string{ "exec", clientPodConfig.name, "--", "ping", "-c", "1", // send one ICMP echo request "-W", "2", // timeout after 2 seconds if no response - serverIP) + } + baseArgs = append(baseArgs, args...) + + _, err := e2ekubectl.RunKubectl(clientPodConfig.namespace, append(baseArgs, serverIP)...) + return err } @@ -381,6 +388,18 @@ func podIPForAttachment(k8sClient clientset.Interface, podNamespace string, podN return ips[ipIndex], nil } +func podIPsFromStatus(k8sClient clientset.Interface, podNamespace string, podName string) ([]string, error) { + pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + podIPs := make([]string, 0, len(pod.Status.PodIPs)) + for _, podIP := range pod.Status.PodIPs { + podIPs = append(podIPs, podIP.IP) + } + return podIPs, nil +} + func allowedClient(podName string) string { return "allowed-" + podName } @@ -610,27 +629,27 @@ func allowedTCPPortsForPolicy(allowPorts ...int) []mnpapi.MultiNetworkPolicyPort return portAllowlist } -func reachServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, serverPort uint16) error { +func reachServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, serverPort uint16, args ...string) error { updatedPod, err := cs.CoreV1().Pods(serverConfig.namespace).Get(context.Background(), serverConfig.name, metav1.GetOptions{}) if err != nil { return err } if updatedPod.Status.Phase == v1.PodRunning { - return connectToServer(clientConfig, serverIP, serverPort) + return connectToServer(clientConfig, serverIP, serverPort, args...) } return fmt.Errorf("pod not running. /me is sad") } -func pingServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string) error { +func pingServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, args ...string) error { updatedPod, err := cs.CoreV1().Pods(serverConfig.namespace).Get(context.Background(), serverConfig.name, metav1.GetOptions{}) if err != nil { return err } if updatedPod.Status.Phase == v1.PodRunning { - return pingServer(clientConfig, serverIP) + return pingServer(clientConfig, serverIP, args...) } return fmt.Errorf("pod not running. /me is sad") From 84ed994926697b1b0e7b014fa3495bc7da156bf9 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Mon, 30 Jun 2025 17:58:02 +0200 Subject: [PATCH 079/278] [openflow manager] allow patch port ofport change for UDNs. With the high load, UDN can be re-created and node-nad-controller needs more time to update bridge config than zone-nad-controller, which re-creates the external switch and causes ofport change. Under high load node-nad-controller may miss delete+update NAD event, so it will lawfully think that the network hasn't changed, while zone-nad-controller can re-create the external switch, and that would require a network re-create on the node side. Consider assigned network ID to re-create network if the ID has changed. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/node/openflow_manager.go | 4 ++-- go-controller/pkg/util/multi_network.go | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 5fa7d77865..96b55a52e1 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -275,12 +275,12 @@ func checkPorts(netConfigs []*bridgeUDNConfiguration, physIntf, ofPortPhys strin } if netConfig.ofPortPatch != curOfportPatch { - if netConfig.isDefaultNetwork() || curOfportPatch != "" { + if netConfig.isDefaultNetwork() { klog.Errorf("Fatal error: patch port %s ofport changed from %s to %s", netConfig.patchPort, netConfig.ofPortPatch, curOfportPatch) os.Exit(1) } else { - klog.Warningf("Patch port %s removed for existing network", netConfig.patchPort) + klog.Warningf("UDN patch port %s changed for existing network from %v to %v. Expecting bridge config update.", netConfig.patchPort, netConfig.ofPortPatch, curOfportPatch) } } } diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index 2cf3d906f6..b4a5bd4b98 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -822,6 +822,11 @@ func (nInfo *secondaryNetInfo) canReconcile(other NetInfo) bool { if nInfo == nil && other == nil { return true } + // if network ID has changed, it means the network was re-created, and all controllers + // should execute delete+create instead of update + if nInfo.GetNetworkID() != types.InvalidID && other.GetNetworkID() != types.InvalidID && nInfo.GetNetworkID() != other.GetNetworkID() { + return false + } if nInfo.netName != other.GetNetworkName() { return false } From f07c055194d744593c83ae4ead16fd0720de9b15 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 9 Jul 2025 12:26:34 +0100 Subject: [PATCH 080/278] chore: Update artwork Updates artwork based on cncf/artwork#574. Signed-off-by: Dave Tucker --- docs/images/ovn-inside-k8s-stacked.png | Bin 41022 -> 41507 bytes docs/images/ovn-inside-k8s.png | Bin 33763 -> 39743 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/ovn-inside-k8s-stacked.png b/docs/images/ovn-inside-k8s-stacked.png index ea2c8937cb05525a7bdf03b750aa74b2be818970..2fc5b0770f1d66099bbf88b6163de2e3d37408ac 100644 GIT binary patch literal 41507 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 diff --git a/docs/images/ovn-inside-k8s.png b/docs/images/ovn-inside-k8s.png index 91d9981986ff8bc77c1b7ea96c5c24f084cd9ce0..3393d0af2792fa719e77e955f085209211837a63 100644 GIT binary patch literal 39743 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

jb=ZmR=} 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 HvkdqjPm<7< literal 33763 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 From 2e888476b29aa30393d0c108f626153908d21836 Mon Sep 17 00:00:00 2001 From: Or Mergi Date: Thu, 26 Jun 2025 15:06:45 +0300 Subject: [PATCH 081/278] Add flag for preconfigured UDN addresses feature The flag serves as a feature gate for the feature allowing connecting workloads with preconfigured network to user-defined networks [1]. OVN-Kuberentes flag name is "enable-preconfigured-udn-addresses". The feature doesn't support non-interconnected mode, hence no change for ovn-master manifests. The feature doesn't support DPU mode, hence no change for ovn-node-dpu and ovn-node-dpu-host manifests. The feature gate take place on ovn-control-plane (ovn-cluster-manager) and ovn-node (ovnkube-controller) containers, both single and multi zone modes. In order to create development cluster with the FG enabled use the flag "pre-conf-udn-addr-enable" or the shorter form "uae", for example: $ (./contrib/kind.sh -ep podman -lr -i6 -ds -mne -nse -ikv -uae) [1] https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5238 Signed-off-by: Or Mergi --- contrib/kind-helm.sh | 51 +++--- contrib/kind.sh | 155 ++++++++++-------- dist/images/daemonset.sh | 11 ++ dist/images/ovnkube.sh | 35 +++- dist/templates/ovnkube-control-plane.yaml.j2 | 2 + dist/templates/ovnkube-node.yaml.j2 | 4 + .../ovnkube-single-node-zone.yaml.j2 | 2 + .../templates/ovnkube-zone-controller.yaml.j2 | 2 + go-controller/pkg/config/config.go | 7 + go-controller/pkg/config/config_test.go | 6 + .../templates/ovnkube-control-plane.yaml | 2 + .../ovnkube-node/templates/ovnkube-node.yaml | 2 + .../templates/ovnkube-single-node-zone.yaml | 2 + .../templates/ovnkube-zone-controller.yaml | 2 + .../values-multi-node-zone.yaml | 2 + .../values-single-node-zone.yaml | 2 + 16 files changed, 195 insertions(+), 92 deletions(-) diff --git a/contrib/kind-helm.sh b/contrib/kind-helm.sh index c682c94ac7..21d7ffef88 100755 --- a/contrib/kind-helm.sh +++ b/contrib/kind-helm.sh @@ -27,6 +27,7 @@ set_default_params() { export KIND_REMOVE_TAINT=${KIND_REMOVE_TAINT:-true} export ENABLE_MULTI_NET=${ENABLE_MULTI_NET:-false} export ENABLE_NETWORK_SEGMENTATION=${ENABLE_NETWORK_SEGMENTATION:-false} + export ENABLE_PRE_CONF_UDN_ADDR=${ENABLE_PRE_CONF_UDN_ADDR:-false} export OVN_NETWORK_QOS_ENABLE=${OVN_NETWORK_QOS_ENABLE:-false} export KIND_NUM_WORKER=${KIND_NUM_WORKER:-2} export KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-ovn} @@ -99,6 +100,7 @@ usage() { echo " [ -ikv | --install-kubevirt ]" echo " [ -mne | --multi-network-enable ]" echo " [ -nse | --network-segmentation-enable ]" + echo " [ -uae | --preconfigured-udn-addresses-enable ]" echo " [ -nqe | --network-qos-enable ]" echo " [ -wk | --num-workers ]" echo " [ -ic | --enable-interconnect]" @@ -106,28 +108,29 @@ usage() { echo " [ -cn | --cluster-name ]" echo " [ -h ]" echo "" - echo "--delete Delete current cluster" - echo "-cf | --config-file Name of the KIND configuration file" - echo "-kt | --keep-taint Do not remove taint components" - echo " DEFAULT: Remove taint components" - echo "-me | --multicast-enabled Enable multicast. DEFAULT: Disabled" - echo "-ho | --hybrid-enabled Enable hybrid overlay. DEFAULT: Disabled" - echo "-obs | --observability Enable observability. DEFAULT: Disabled" - echo "-el | --ovn-empty-lb-events Enable empty-lb-events generation for LB without backends. DEFAULT: Disabled" - echo "-ii | --install-ingress Flag to install Ingress Components." - echo " DEFAULT: Don't install ingress components." - echo "-mlb | --install-metallb Install metallb to test service type LoadBalancer deployments" - echo "-pl | --install-cni-plugins Install CNI plugins" - echo "-ikv | --install-kubevirt Install kubevirt" - echo "-mne | --multi-network-enable Enable multi networks. DEFAULT: Disabled" - echo "-nse | --network-segmentation-enable Enable network segmentation. DEFAULT: Disabled" - echo "-nqe | --network-qos-enable Enable network QoS. DEFAULT: Disabled" - echo "-ha | --ha-enabled Enable high availability. DEFAULT: HA Disabled" - echo "-wk | --num-workers Number of worker nodes. DEFAULT: 2 workers" - echo "-cn | --cluster-name Configure the kind cluster's name" - echo "-dns | --enable-dnsnameresolver Enable DNSNameResolver for resolving the DNS names used in the DNS rules of EgressFirewall." - echo "-ic | --enable-interconnect Enable interconnect with each node as a zone (only valid if OVN_HA is false)" - echo "-npz | --nodes-per-zone Specify number of nodes per zone (Default 0, which means global zone; >0 means interconnect zone, where 1 for single-node zone, >1 for multi-node zone). If this value > 1, then (total k8s nodes (workers + 1) / num of nodes per zone) should be zero." + echo "--delete Delete current cluster" + echo "-cf | --config-file Name of the KIND configuration file" + echo "-kt | --keep-taint Do not remove taint components" + echo " DEFAULT: Remove taint components" + echo "-me | --multicast-enabled Enable multicast. DEFAULT: Disabled" + echo "-ho | --hybrid-enabled Enable hybrid overlay. DEFAULT: Disabled" + echo "-obs | --observability Enable observability. DEFAULT: Disabled" + echo "-el | --ovn-empty-lb-events Enable empty-lb-events generation for LB without backends. DEFAULT: Disabled" + echo "-ii | --install-ingress Flag to install Ingress Components." + echo " DEFAULT: Don't install ingress components." + echo "-mlb | --install-metallb Install metallb to test service type LoadBalancer deployments" + echo "-pl | --install-cni-plugins Install CNI plugins" + echo "-ikv | --install-kubevirt Install kubevirt" + echo "-mne | --multi-network-enable Enable multi networks. DEFAULT: Disabled" + echo "-nse | --network-segmentation-enable Enable network segmentation. DEFAULT: Disabled" + echo "-uae | --preconfigured-udn-addresses-enable Enable connecting workloads with preconfigured network to user-defined networks. DEFAULT: Disabled" + echo "-nqe | --network-qos-enable Enable network QoS. DEFAULT: Disabled" + echo "-ha | --ha-enabled Enable high availability. DEFAULT: HA Disabled" + echo "-wk | --num-workers Number of worker nodes. DEFAULT: 2 workers" + echo "-cn | --cluster-name Configure the kind cluster's name" + echo "-dns | --enable-dnsnameresolver Enable DNSNameResolver for resolving the DNS names used in the DNS rules of EgressFirewall." + echo "-ic | --enable-interconnect Enable interconnect with each node as a zone (only valid if OVN_HA is false)" + echo "-npz | --nodes-per-zone Specify number of nodes per zone (Default 0, which means global zone; >0 means interconnect zone, where 1 for single-node zone, >1 for multi-node zone). If this value > 1, then (total k8s nodes (workers + 1) / num of nodes per zone) should be zero." echo "" } @@ -168,6 +171,8 @@ parse_args() { ;; -nse | --network-segmentation-enable) ENABLE_NETWORK_SEGMENTATION=true ;; + -uae | --preconfigured-udn-addresses-enable) ENABLE_PRE_CONF_UDN_ADDR=true + ;; -nqe | --network-qos-enable ) OVN_NETWORK_QOS_ENABLE=true ;; -ha | --ha-enabled ) OVN_HA=true @@ -223,6 +228,7 @@ print_params() { echo "KIND_REMOVE_TAINT = $KIND_REMOVE_TAINT" echo "ENABLE_MULTI_NET = $ENABLE_MULTI_NET" echo "ENABLE_NETWORK_SEGMENTATION = $ENABLE_NETWORK_SEGMENTATION" + echo "ENABLE_PRE_CONF_UDN_ADDR = $ENABLE_PRE_CONF_UDN_ADDR" echo "OVN_NETWORK_QOS_ENABLE = $OVN_NETWORK_QOS_ENABLE" echo "OVN_IMAGE = $OVN_IMAGE" echo "KIND_NUM_MASTER = $KIND_NUM_MASTER" @@ -416,6 +422,7 @@ helm install ovn-kubernetes . -f "${value_file}" \ --set global.enableMulticast=$(if [ "${OVN_MULTICAST_ENABLE}" == "true" ]; then echo "true"; else echo "false"; fi) \ --set global.enableMultiNetwork=$(if [ "${ENABLE_MULTI_NET}" == "true" ]; then echo "true"; else echo "false"; fi) \ --set global.enableNetworkSegmentation=$(if [ "${ENABLE_NETWORK_SEGMENTATION}" == "true" ]; then echo "true"; else echo "false"; fi) \ + --set global.enablePreconfiguredUDNAddresses=$(if [ "${ENABLE_PRE_CONF_UDN_ADDR}" == "true" ]; then echo "true"; else echo "false"; fi) \ --set global.enableHybridOverlay=$(if [ "${OVN_HYBRID_OVERLAY_ENABLE}" == "true" ]; then echo "true"; else echo "false"; fi) \ --set global.enableObservability=$(if [ "${OVN_OBSERV_ENABLE}" == "true" ]; then echo "true"; else echo "false"; fi) \ --set global.emptyLbEvents=$(if [ "${OVN_EMPTY_LB_EVENTS}" == "true" ]; then echo "true"; else echo "false"; fi) \ diff --git a/contrib/kind.sh b/contrib/kind.sh index 5ec980bd95..72c48a9af2 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -80,6 +80,7 @@ usage() { echo " [-is | --ipsec]" echo " [-cm | --compact-mode]" echo " [-ic | --enable-interconnect]" + echo " [-uae | --preconfigured-udn-addresses-enable]" echo " [-rae | --enable-route-advertisements]" echo " [-adv | --advertise-default-network]" echo " [-nqe | --network-qos-enable]" @@ -88,73 +89,74 @@ usage() { echo " [-obs | --observability]" echo " [-h]]" echo "" - echo "-cf | --config-file Name of the KIND J2 configuration file." - echo " DEFAULT: ./kind.yaml.j2" - echo "-kt | --keep-taint Do not remove taint components." - echo " DEFAULT: Remove taint components." - echo "-ha | --ha-enabled Enable high availability. DEFAULT: HA Disabled." - echo "-scm | --separate-cluster-manager Separate cluster manager from ovnkube-master and run as a separate container within ovnkube-master deployment." - echo "-me | --multicast-enabled Enable multicast. DEFAULT: Disabled." - echo "-ho | --hybrid-enabled Enable hybrid overlay. DEFAULT: Disabled." - echo "-ds | --disable-snat-multiple-gws Disable SNAT for multiple gws. DEFAULT: Disabled." - echo "-dp | --disable-pkt-mtu-check Disable checking packet size greater than MTU. Default: Disabled" - echo "-df | --disable-forwarding Disable forwarding on OVNK managed interfaces. Default: Disabled" - echo "-ecp | --encap-port UDP port used for geneve overlay. DEFAULT: 6081" - echo "-pl | --install-cni-plugins ] Installs additional CNI network plugins. DEFAULT: Disabled" - echo "-nf | --netflow-targets Comma delimited list of ip:port or :port (using node IP) netflow collectors. DEFAULT: Disabled." - echo "-sf | --sflow-targets Comma delimited list of ip:port or :port (using node IP) sflow collectors. DEFAULT: Disabled." - echo "-if | --ipfix-targets Comma delimited list of ip:port or :port (using node IP) ipfix collectors. DEFAULT: Disabled." - echo "-ifs | --ipfix-sampling Fraction of packets that are sampled and sent to each target collector: 1 packet out of every . DEFAULT: 400 (1 out of 400 packets)." - echo "-ifm | --ipfix-cache-max-flows Maximum number of IPFIX flow records that can be cached at a time. If 0, caching is disabled. DEFAULT: Disabled." - echo "-ifa | --ipfix-cache-active-timeout Maximum period in seconds for which an IPFIX flow record is cached and aggregated before being sent. If 0, caching is disabled. DEFAULT: 60." - echo "-el | --ovn-empty-lb-events Enable empty-lb-events generation for LB without backends. DEFAULT: Disabled" - echo "-ii | --install-ingress Flag to install Ingress Components." - echo " DEFAULT: Don't install ingress components." - echo "-mlb | --install-metallb Install metallb to test service type LoadBalancer deployments" - echo "-n4 | --no-ipv4 Disable IPv4. DEFAULT: IPv4 Enabled." - echo "-i6 | --ipv6 Enable IPv6. DEFAULT: IPv6 Disabled." - echo "-wk | --num-workers Number of worker nodes. DEFAULT: HA - 2 worker" - echo " nodes and no HA - 0 worker nodes." - echo "-sw | --allow-system-writes Allow script to update system. Intended to allow" - echo " github CI to be updated with IPv6 settings." - echo " DEFAULT: Don't allow." - echo "-gm | --gateway-mode Enable 'shared' or 'local' gateway mode." - echo " DEFAULT: shared." - echo "-ov | --ovn-image Use the specified docker image instead of building locally. DEFAULT: local build." - echo "-ovr | --ovn-repo Specify the repository to build OVN from" - echo "-ovg | --ovn-gitref Specify the branch, tag or commit id to build OVN from, it can be a pattern like 'branch-*' it will order results and use the first one" - echo "-ml | --master-loglevel Log level for ovnkube (master), DEFAULT: 5." - echo "-nl | --node-loglevel Log level for ovnkube (node), DEFAULT: 5" - echo "-dbl | --dbchecker-loglevel Log level for ovn-dbchecker (ovnkube-db), DEFAULT: 5." - echo "-ndl | --ovn-loglevel-northd Log config for ovn northd, DEFAULT: '-vconsole:info -vfile:info'." - echo "-nbl | --ovn-loglevel-nb Log config for northbound DB DEFAULT: '-vconsole:info -vfile:info'." - echo "-sbl | --ovn-loglevel-sb Log config for southboudn DB DEFAULT: '-vconsole:info -vfile:info'." - echo "-cl | --ovn-loglevel-controller Log config for ovn-controller DEFAULT: '-vconsole:info'." - echo "-lcl | --libovsdb-client-logfile Separate logs for libovsdb client into provided file. DEFAULT: do not separate." - echo "-ep | --experimental-provider Use an experimental OCI provider such as podman, instead of docker. DEFAULT: Disabled." - echo "-eb | --egress-gw-separate-bridge The external gateway traffic uses a separate bridge." - echo "-lr | --local-kind-registry Configure kind to use a local docker registry rather than manually loading images" - echo "-dd | --dns-domain Configure a custom dnsDomain for k8s services, Defaults to 'cluster.local'" - echo "-cn | --cluster-name Configure the kind cluster's name" - echo "-ric | --run-in-container Configure the script to be run from a docker container, allowing it to still communicate with the kind controlplane" - echo "-ehp | --egress-ip-healthcheck-port TCP port used for gRPC session by egress IP node check. DEFAULT: 9107 (Use "0" for legacy dial to port 9)." - echo "-is | --ipsec Enable IPsec encryption (spawns ovn-ipsec pods)" - echo "-sm | --scale-metrics Enable scale metrics" - echo "-cm | --compact-mode Enable compact mode, ovnkube master and node run in the same process." - echo "-ic | --enable-interconnect Enable interconnect with each node as a zone (only valid if OVN_HA is false)" - echo "-nqe | --network-qos-enable Enable network QoS. DEFAULT: Disabled." - echo "--disable-ovnkube-identity Disable per-node cert and ovnkube-identity webhook" - echo "-npz | --nodes-per-zone If interconnect is enabled, number of nodes per zone (Default 1). If this value > 1, then (total k8s nodes (workers + 1) / num of nodes per zone) should be zero." - echo "-mtu Define the overlay mtu" - echo "--isolated Deploy with an isolated environment (no default gateway)" - echo "--delete Delete current cluster" - echo "--deploy Deploy ovn kubernetes without restarting kind" - echo "--add-nodes Adds nodes to an existing cluster. The number of nodes to be added is specified by --num-workers. Also use -ic if the cluster is using interconnect." - echo "-dns | --enable-dnsnameresolver Enable DNSNameResolver for resolving the DNS names used in the DNS rules of EgressFirewall." - echo "-obs | --observability Enable OVN Observability feature." - echo "-rae | --enable-route-advertisements Enable route advertisements" - echo "-adv | --advertise-default-network Applies a RouteAdvertisements configuration to advertise the default network on all nodes" - echo "" +echo "-cf | --config-file Name of the KIND J2 configuration file." +echo " DEFAULT: ./kind.yaml.j2" +echo "-kt | --keep-taint Do not remove taint components." +echo " DEFAULT: Remove taint components." +echo "-ha | --ha-enabled Enable high availability. DEFAULT: HA Disabled." +echo "-scm | --separate-cluster-manager Separate cluster manager from ovnkube-master and run as a separate container within ovnkube-master deployment." +echo "-me | --multicast-enabled Enable multicast. DEFAULT: Disabled." +echo "-ho | --hybrid-enabled Enable hybrid overlay. DEFAULT: Disabled." +echo "-ds | --disable-snat-multiple-gws Disable SNAT for multiple gws. DEFAULT: Disabled." +echo "-dp | --disable-pkt-mtu-check Disable checking packet size greater than MTU. Default: Disabled" +echo "-df | --disable-forwarding Disable forwarding on OVNK managed interfaces. Default: Disabled" +echo "-ecp | --encap-port UDP port used for geneve overlay. DEFAULT: 6081" +echo "-pl | --install-cni-plugins ] Installs additional CNI network plugins. DEFAULT: Disabled" +echo "-nf | --netflow-targets Comma delimited list of ip:port or :port (using node IP) netflow collectors. DEFAULT: Disabled." +echo "-sf | --sflow-targets Comma delimited list of ip:port or :port (using node IP) sflow collectors. DEFAULT: Disabled." +echo "-if | --ipfix-targets Comma delimited list of ip:port or :port (using node IP) ipfix collectors. DEFAULT: Disabled." +echo "-ifs | --ipfix-sampling Fraction of packets that are sampled and sent to each target collector: 1 packet out of every . DEFAULT: 400 (1 out of 400 packets)." +echo "-ifm | --ipfix-cache-max-flows Maximum number of IPFIX flow records that can be cached at a time. If 0, caching is disabled. DEFAULT: Disabled." +echo "-ifa | --ipfix-cache-active-timeout Maximum period in seconds for which an IPFIX flow record is cached and aggregated before being sent. If 0, caching is disabled. DEFAULT: 60." +echo "-el | --ovn-empty-lb-events Enable empty-lb-events generation for LB without backends. DEFAULT: Disabled" +echo "-ii | --install-ingress Flag to install Ingress Components." +echo " DEFAULT: Don't install ingress components." +echo "-mlb | --install-metallb Install metallb to test service type LoadBalancer deployments" +echo "-n4 | --no-ipv4 Disable IPv4. DEFAULT: IPv4 Enabled." +echo "-i6 | --ipv6 Enable IPv6. DEFAULT: IPv6 Disabled." +echo "-wk | --num-workers Number of worker nodes. DEFAULT: HA - 2 worker" +echo " nodes and no HA - 0 worker nodes." +echo "-sw | --allow-system-writes Allow script to update system. Intended to allow" +echo " github CI to be updated with IPv6 settings." +echo " DEFAULT: Don't allow." +echo "-gm | --gateway-mode Enable 'shared' or 'local' gateway mode." +echo " DEFAULT: shared." +echo "-ov | --ovn-image Use the specified docker image instead of building locally. DEFAULT: local build." +echo "-ovr | --ovn-repo Specify the repository to build OVN from" +echo "-ovg | --ovn-gitref Specify the branch, tag or commit id to build OVN from, it can be a pattern like 'branch-*' it will order results and use the first one" +echo "-ml | --master-loglevel Log level for ovnkube (master), DEFAULT: 5." +echo "-nl | --node-loglevel Log level for ovnkube (node), DEFAULT: 5" +echo "-dbl | --dbchecker-loglevel Log level for ovn-dbchecker (ovnkube-db), DEFAULT: 5." +echo "-ndl | --ovn-loglevel-northd Log config for ovn northd, DEFAULT: '-vconsole:info -vfile:info'." +echo "-nbl | --ovn-loglevel-nb Log config for northbound DB DEFAULT: '-vconsole:info -vfile:info'." +echo "-sbl | --ovn-loglevel-sb Log config for southboudn DB DEFAULT: '-vconsole:info -vfile:info'." +echo "-cl | --ovn-loglevel-controller Log config for ovn-controller DEFAULT: '-vconsole:info'." +echo "-lcl | --libovsdb-client-logfile Separate logs for libovsdb client into provided file. DEFAULT: do not separate." +echo "-ep | --experimental-provider Use an experimental OCI provider such as podman, instead of docker. DEFAULT: Disabled." +echo "-eb | --egress-gw-separate-bridge The external gateway traffic uses a separate bridge." +echo "-lr | --local-kind-registry Configure kind to use a local docker registry rather than manually loading images" +echo "-dd | --dns-domain Configure a custom dnsDomain for k8s services, Defaults to 'cluster.local'" +echo "-cn | --cluster-name Configure the kind cluster's name" +echo "-ric | --run-in-container Configure the script to be run from a docker container, allowing it to still communicate with the kind controlplane" +echo "-ehp | --egress-ip-healthcheck-port TCP port used for gRPC session by egress IP node check. DEFAULT: 9107 (Use "0" for legacy dial to port 9)." +echo "-is | --ipsec Enable IPsec encryption (spawns ovn-ipsec pods)" +echo "-sm | --scale-metrics Enable scale metrics" +echo "-cm | --compact-mode Enable compact mode, ovnkube master and node run in the same process." +echo "-ic | --enable-interconnect Enable interconnect with each node as a zone (only valid if OVN_HA is false)" +echo "-nqe | --network-qos-enable Enable network QoS. DEFAULT: Disabled." +echo "--disable-ovnkube-identity Disable per-node cert and ovnkube-identity webhook" +echo "-npz | --nodes-per-zone If interconnect is enabled, number of nodes per zone (Default 1). If this value > 1, then (total k8s nodes (workers + 1) / num of nodes per zone) should be zero." +echo "-mtu Define the overlay mtu" +echo "--isolated Deploy with an isolated environment (no default gateway)" +echo "--delete Delete current cluster" +echo "--deploy Deploy ovn kubernetes without restarting kind" +echo "--add-nodes Adds nodes to an existing cluster. The number of nodes to be added is specified by --num-workers. Also use -ic if the cluster is using interconnect." +echo "-dns | --enable-dnsnameresolver Enable DNSNameResolver for resolving the DNS names used in the DNS rules of EgressFirewall." +echo "-obs | --observability Enable OVN Observability feature." +echo "-uae | --preconfigured-udn-addresses-enable Enable connecting workloads with preconfigured network to user-defined networks" +echo "-rae | --enable-route-advertisements Enable route advertisements" +echo "-adv | --advertise-default-network Applies a RouteAdvertisements configuration to advertise the default network on all nodes" +echo "" } parse_args() { @@ -337,6 +339,8 @@ parse_args() { ;; -nse | --network-segmentation-enable) ENABLE_NETWORK_SEGMENTATION=true ;; + -uae | --preconfigured-udn-addresses-enable) ENABLE_PRE_CONF_UDN_ADDR=true + ;; -rae | --route-advertisements-enable) ENABLE_ROUTE_ADVERTISEMENTS=true ;; -adv | --advertise-default-network) ADVERTISE_DEFAULT_NETWORK=true @@ -434,6 +438,7 @@ print_params() { echo "ENABLE_NETWORK_SEGMENTATION= $ENABLE_NETWORK_SEGMENTATION" echo "ENABLE_ROUTE_ADVERTISEMENTS= $ENABLE_ROUTE_ADVERTISEMENTS" echo "ADVERTISE_DEFAULT_NETWORK = $ADVERTISE_DEFAULT_NETWORK" + echo "ENABLE_PRE_CONF_UDN_ADDR = $ENABLE_PRE_CONF_UDN_ADDR" echo "OVN_ENABLE_INTERCONNECT = $OVN_ENABLE_INTERCONNECT" if [ "$OVN_ENABLE_INTERCONNECT" == true ]; then echo "KIND_NUM_NODES_PER_ZONE = $KIND_NUM_NODES_PER_ZONE" @@ -654,6 +659,11 @@ set_default_params() { fi ENABLE_MULTI_NET=${ENABLE_MULTI_NET:-false} ENABLE_NETWORK_SEGMENTATION=${ENABLE_NETWORK_SEGMENTATION:-false} + if [ "$ENABLE_NETWORK_SEGMENTATION" == true ] && [ "$ENABLE_MULTI_NET" != true ]; then + echo "Network segmentation (UDN) requires multi-network to be enabled (-mne)" + exit 1 + fi + ENABLE_ROUTE_ADVERTISEMENTS=${ENABLE_ROUTE_ADVERTISEMENTS:-false} if [ "$ENABLE_ROUTE_ADVERTISEMENTS" == true ] && [ "$ENABLE_MULTI_NET" != true ]; then echo "Route advertisements requires multi-network to be enabled (-mne)" @@ -663,6 +673,16 @@ set_default_params() { echo "Route advertisements requires interconnect to be enabled (-ic)" exit 1 fi + + ENABLE_PRE_CONF_UDN_ADDR=${ENABLE_PRE_CONF_UDN_ADDR:-false} + if [[ $ENABLE_PRE_CONF_UDN_ADDR == true && $ENABLE_NETWORK_SEGMENTATION != true ]]; then + echo "Preconfigured UDN addresses requires network-segmentation to be enabled (-nse)" + exit 1 + fi + if [[ $ENABLE_PRE_CONF_UDN_ADDR == true && $OVN_ENABLE_INTERCONNECT != true ]]; then + echo "Preconfigured UDN addresses requires interconnect to be enabled (-ic)" + exit 1 + fi ADVERTISE_DEFAULT_NETWORK=${ADVERTISE_DEFAULT_NETWORK:-false} OVN_COMPACT_MODE=${OVN_COMPACT_MODE:-false} if [ "$OVN_COMPACT_MODE" == true ]; then @@ -916,6 +936,7 @@ create_ovn_kube_manifests() { --ex-gw-network-interface="${OVN_EX_GW_NETWORK_INTERFACE}" \ --multi-network-enable="${ENABLE_MULTI_NET}" \ --network-segmentation-enable="${ENABLE_NETWORK_SEGMENTATION}" \ + --preconfigured-udn-addresses-enable="${ENABLE_PRE_CONF_UDN_ADDR}" \ --route-advertisements-enable="${ENABLE_ROUTE_ADVERTISEMENTS}" \ --advertise-default-network="${ADVERTISE_DEFAULT_NETWORK}" \ --ovnkube-metrics-scale-enable="${OVN_METRICS_SCALE_ENABLE}" \ diff --git a/dist/images/daemonset.sh b/dist/images/daemonset.sh index 95e4a503e8..7c3daedee9 100755 --- a/dist/images/daemonset.sh +++ b/dist/images/daemonset.sh @@ -71,6 +71,7 @@ OVN_EGRESSSERVICE_ENABLE= OVN_DISABLE_OVN_IFACE_ID_VER="false" OVN_MULTI_NETWORK_ENABLE= OVN_NETWORK_SEGMENTATION_ENABLE= +OVN_PRE_CONF_UDN_ADDR_ENABLE= OVN_ROUTE_ADVERTISEMENTS_ENABLE= OVN_ADVERTISE_DEFAULT_NETWORK= OVN_V4_JOIN_SUBNET="" @@ -273,6 +274,9 @@ while [ "$1" != "" ]; do --network-segmentation-enable) OVN_NETWORK_SEGMENTATION_ENABLE=$VALUE ;; + --preconfigured-udn-addresses-enable) + OVN_PRE_CONF_UDN_ADDR_ENABLE=$VALUE + ;; --route-advertisements-enable) OVN_ROUTE_ADVERTISEMENTS_ENABLE=$VALUE ;; @@ -468,6 +472,8 @@ ovn_multi_network_enable=${OVN_MULTI_NETWORK_ENABLE} echo "ovn_multi_network_enable: ${ovn_multi_network_enable}" ovn_network_segmentation_enable=${OVN_NETWORK_SEGMENTATION_ENABLE} echo "ovn_network_segmentation_enable: ${ovn_network_segmentation_enable}" +ovn_pre_conf_udn_addr_enable=${OVN_PRE_CONF_UDN_ADDR_ENABLE} +echo "ovn_pre_conf_udn_addr_enable: ${ovn_pre_conf_udn_addr_enable}" ovn_route_advertisements_enable=${OVN_ROUTE_ADVERTISEMENTS_ENABLE} echo "ovn_route_advertisements_enable: ${ovn_route_advertisements_enable}" ovn_advertise_default_network=${OVN_ADVERTISE_DEFAULT_NETWORK} @@ -612,6 +618,7 @@ ovn_image=${ovnkube_image} \ ovn_egress_ip_healthcheck_port=${ovn_egress_ip_healthcheck_port} \ ovn_multi_network_enable=${ovn_multi_network_enable} \ ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \ + ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \ ovn_egress_service_enable=${ovn_egress_service_enable} \ ovn_ssl_en=${ovn_ssl_en} \ @@ -814,6 +821,7 @@ ovn_image=${ovnkube_image} \ ovn_egress_qos_enable=${ovn_egress_qos_enable} \ ovn_multi_network_enable=${ovn_multi_network_enable} \ ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \ + ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \ ovn_egress_service_enable=${ovn_egress_service_enable} \ ovn_ssl_en=${ovn_ssl_en} \ @@ -894,6 +902,7 @@ ovn_image=${ovnkube_image} \ ovn_egress_qos_enable=${ovn_egress_qos_enable} \ ovn_multi_network_enable=${ovn_multi_network_enable} \ ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \ + ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \ ovn_egress_service_enable=${ovn_egress_service_enable} \ ovn_ssl_en=${ovn_ssl_en} \ @@ -961,6 +970,7 @@ ovn_image=${ovnkube_image} \ ovn_egress_qos_enable=${ovn_egress_qos_enable} \ ovn_multi_network_enable=${ovn_multi_network_enable} \ ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \ + ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \ ovn_ssl_en=${ovn_ssl_en} \ ovn_remote_probe_interval=${ovn_remote_probe_interval} \ @@ -1057,6 +1067,7 @@ ovn_enable_dnsnameresolver=${ovn_enable_dnsnameresolver} \ jinjanate ../templates/rbac-ovnkube-node.yaml.j2 -o ${output_dir}/rbac-ovnkube-node.yaml ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \ +ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \ ovn_enable_dnsnameresolver=${ovn_enable_dnsnameresolver} \ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \ jinjanate ../templates/rbac-ovnkube-cluster-manager.yaml.j2 -o ${output_dir}/rbac-ovnkube-cluster-manager.yaml diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 85b8eeab14..32d3347cd3 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -269,6 +269,8 @@ ovn_disable_ovn_iface_id_ver=${OVN_DISABLE_OVN_IFACE_ID_VER:-false} ovn_multi_network_enable=${OVN_MULTI_NETWORK_ENABLE:-false} #OVN_NETWORK_SEGMENTATION_ENABLE - enable user defined primary networks for ovn-kubernetes ovn_network_segmentation_enable=${OVN_NETWORK_SEGMENTATION_ENABLE:=false} +#OVN_PRE_CONF_UDN_ADDR_ENABLE - enable connecting workloads with custom network configuration to UDNs +ovn_pre_conf_udn_addr_enable=${OVN_PRE_CONF_UDN_ADDR_ENABLE:=false} #OVN_NROUTE_ADVERTISEMENTS_ENABLE - enable route advertisements for ovn-kubernetes ovn_route_advertisements_enable=${OVN_ROUTE_ADVERTISEMENTS_ENABLE:=false} ovn_acl_logging_rate_limit=${OVN_ACL_LOGGING_RATE_LIMIT:-"20"} @@ -1269,7 +1271,7 @@ ovn-master() { ovnkube_metrics_scale_enable_flag="--metrics-enable-scale --metrics-enable-pprof" fi echo "ovnkube_metrics_scale_enable_flag: ${ovnkube_metrics_scale_enable_flag}" - + ovn_stateless_netpol_enable_flag= if [[ ${ovn_stateless_netpol_enable} == "true" ]]; then ovn_stateless_netpol_enable_flag="--enable-stateless-netpol" @@ -1293,7 +1295,7 @@ ovn-master() { ovn_observ_enable_flag="--enable-observability" fi echo "ovn_observ_enable_flag=${ovn_observ_enable_flag}" - + nohostsubnet_label_option= if [[ ${ovn_nohostsubnet_label} != "" ]]; then nohostsubnet_label_option="--no-hostsubnet-nodes=${ovn_nohostsubnet_label}" @@ -1539,6 +1541,12 @@ ovnkube-controller() { fi echo "network_segmentation_enabled_flag=${network_segmentation_enabled_flag}" + pre_conf_udn_addr_enable_flag= + if [[ ${ovn_pre_conf_udn_addr_enable} == "true" ]]; then + pre_conf_udn_addr_enable_flag="--enable-preconfigured-udn-addresses" + fi + echo "pre_conf_udn_addr_enable_flag=${pre_conf_udn_addr_enable_flag}" + route_advertisements_enabled_flag= if [[ ${ovn_route_advertisements_enable} == "true" ]]; then route_advertisements_enabled_flag="--enable-route-advertisements" @@ -1659,6 +1667,7 @@ ovnkube-controller() { ${multicast_enabled_flag} \ ${multi_network_enabled_flag} \ ${network_segmentation_enabled_flag} \ + ${pre_conf_udn_addr_enable_flag} \ ${route_advertisements_enabled_flag} \ ${ovn_acl_logging_rate_limit_flag} \ ${ovn_dbs} \ @@ -1843,6 +1852,12 @@ ovnkube-controller-with-node() { fi echo "network_segmentation_enabled_flag=${network_segmentation_enabled_flag}" + pre_conf_udn_addr_enable_flag= + if [[ ${ovn_pre_conf_udn_addr_enable} == "true" ]]; then + pre_conf_udn_addr_enable_flag="--enable-preconfigured-udn-addresses" + fi + echo "pre_conf_udn_addr_enable_flag=${pre_conf_udn_addr_enable_flag}" + route_advertisements_enabled_flag= if [[ ${ovn_route_advertisements_enable} == "true" ]]; then route_advertisements_enabled_flag="--enable-route-advertisements" @@ -1961,7 +1976,7 @@ ovnkube-controller-with-node() { if test -z "${OVN_UNPRIVILEGED_MODE+x}" -o "x${OVN_UNPRIVILEGED_MODE}" = xno; then ovn_unprivileged_flag="" fi - + ovn_metrics_bind_address="${metrics_endpoint_ip}:${metrics_bind_port}" metrics_bind_address="${metrics_endpoint_ip}:${metrics_worker_port}" echo "ovn_metrics_bind_address=${ovn_metrics_bind_address}" @@ -2102,6 +2117,7 @@ ovnkube-controller-with-node() { ${multicast_enabled_flag} \ ${multi_network_enabled_flag} \ ${network_segmentation_enabled_flag} \ + ${pre_conf_udn_addr_enable_flag} \ ${route_advertisements_enabled_flag} \ ${netflow_targets} \ ${ofctrl_wait_before_clear} \ @@ -2269,6 +2285,12 @@ ovn-cluster-manager() { fi echo "network_segmentation_enabled_flag=${network_segmentation_enabled_flag}" + pre_conf_udn_addr_enable_flag= + if [[ ${ovn_pre_conf_udn_addr_enable} == "true" ]]; then + pre_conf_udn_addr_enable_flag="--enable-preconfigured-udn-addresses" + fi + echo "pre_conf_udn_addr_enable_flag=${pre_conf_udn_addr_enable_flag}" + route_advertisements_enabled_flag= if [[ ${ovn_route_advertisements_enable} == "true" ]]; then route_advertisements_enabled_flag="--enable-route-advertisements" @@ -2336,6 +2358,7 @@ ovn-cluster-manager() { ${multicast_enabled_flag} \ ${multi_network_enabled_flag} \ ${network_segmentation_enabled_flag} \ + ${pre_conf_udn_addr_enable_flag} \ ${route_advertisements_enabled_flag} \ ${persistent_ips_enabled_flag} \ ${ovnkube_enable_interconnect_flag} \ @@ -2513,6 +2536,11 @@ ovn-node() { network_segmentation_enabled_flag="--enable-multi-network --enable-network-segmentation" fi + pre_conf_udn_addr_enable_flag= + if [[ ${ovn_pre_conf_udn_addr_enable} == "true" ]]; then + pre_conf_udn_addr_enable_flag="--enable-preconfigured-udn-addresses" + fi + route_advertisements_enabled_flag= if [[ ${ovn_route_advertisements_enable} == "true" ]]; then route_advertisements_enabled_flag="--enable-route-advertisements" @@ -2748,6 +2776,7 @@ ovn-node() { ${multicast_enabled_flag} \ ${multi_network_enabled_flag} \ ${network_segmentation_enabled_flag} \ + ${pre_conf_udn_addr_enable_flag} \ ${route_advertisements_enabled_flag} \ ${netflow_targets} \ ${ofctrl_wait_before_clear} \ diff --git a/dist/templates/ovnkube-control-plane.yaml.j2 b/dist/templates/ovnkube-control-plane.yaml.j2 index 51e3f9319b..dccc617ebc 100644 --- a/dist/templates/ovnkube-control-plane.yaml.j2 +++ b/dist/templates/ovnkube-control-plane.yaml.j2 @@ -139,6 +139,8 @@ spec: value: "{{ ovn_multi_network_enable }}" - name: OVN_NETWORK_SEGMENTATION_ENABLE value: "{{ ovn_network_segmentation_enable }}" + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: "{{ ovn_pre_conf_udn_addr_enable }}" - name: OVN_ROUTE_ADVERTISEMENTS_ENABLE value: "{{ ovn_route_advertisements_enable }}" - name: OVN_HYBRID_OVERLAY_NET_CIDR diff --git a/dist/templates/ovnkube-node.yaml.j2 b/dist/templates/ovnkube-node.yaml.j2 index 98591a5ac1..2bf8e5ed2a 100644 --- a/dist/templates/ovnkube-node.yaml.j2 +++ b/dist/templates/ovnkube-node.yaml.j2 @@ -255,6 +255,10 @@ spec: - name: OVNKUBE_NODE_MODE value: "dpu" {% endif -%} + {% if ovnkube_app_name!="ovnkube-node-dpu" and ovnkube_app_name!="ovnkube-node-dpu-host" -%} + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: "{{ ovn_pre_conf_udn_addr_enable }}" + {% endif -%} - name: OVNKUBE_NODE_MGMT_PORT_NETDEV value: "{{ ovnkube_node_mgmt_port_netdev }}" - name: OVN_HOST_NETWORK_NAMESPACE diff --git a/dist/templates/ovnkube-single-node-zone.yaml.j2 b/dist/templates/ovnkube-single-node-zone.yaml.j2 index d2d485cca7..df5533a668 100644 --- a/dist/templates/ovnkube-single-node-zone.yaml.j2 +++ b/dist/templates/ovnkube-single-node-zone.yaml.j2 @@ -433,6 +433,8 @@ spec: value: "{{ ovn_multi_network_enable }}" - name: OVN_NETWORK_SEGMENTATION_ENABLE value: "{{ ovn_network_segmentation_enable }}" + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: "{{ ovn_pre_conf_udn_addr_enable }}" - name: OVN_ROUTE_ADVERTISEMENTS_ENABLE value: "{{ ovn_route_advertisements_enable }}" - name: OVNKUBE_NODE_MGMT_PORT_NETDEV diff --git a/dist/templates/ovnkube-zone-controller.yaml.j2 b/dist/templates/ovnkube-zone-controller.yaml.j2 index 363ade3014..cc87fe1a53 100644 --- a/dist/templates/ovnkube-zone-controller.yaml.j2 +++ b/dist/templates/ovnkube-zone-controller.yaml.j2 @@ -345,6 +345,8 @@ spec: value: "{{ ovn_multi_network_enable }}" - name: OVN_NETWORK_SEGMENTATION_ENABLE value: "{{ ovn_network_segmentation_enable }}" + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: "{{ ovn_pre_conf_udn_addr_enable }}" - name: OVN_ROUTE_ADVERTISEMENTS_ENABLE value: "{{ ovn_route_advertisements_enable }}" - name: OVN_HYBRID_OVERLAY_NET_CIDR diff --git a/go-controller/pkg/config/config.go b/go-controller/pkg/config/config.go index 7cd97479c4..297f18b55f 100644 --- a/go-controller/pkg/config/config.go +++ b/go-controller/pkg/config/config.go @@ -423,6 +423,7 @@ type OVNKubernetesFeatureConfig struct { EgressIPNodeHealthCheckPort int `gcfg:"egressip-node-healthcheck-port"` EnableMultiNetwork bool `gcfg:"enable-multi-network"` EnableNetworkSegmentation bool `gcfg:"enable-network-segmentation"` + EnablePreconfiguredUDNAddresses bool `gcfg:"enable-preconfigured-udn-addresses"` EnableRouteAdvertisements bool `gcfg:"enable-route-advertisements"` // This feature requires a kernel fix https://github.com/torvalds/linux/commit/7f3287db654395f9c5ddd246325ff7889f550286 // to work on a kind cluster. Flag allows to disable it for current CI, will be turned on when github runners have this fix. @@ -1098,6 +1099,12 @@ var OVNK8sFeatureFlags = []cli.Flag{ Destination: &cliConfig.OVNKubernetesFeature.EnableNetworkSegmentation, Value: OVNKubernetesFeature.EnableNetworkSegmentation, }, + &cli.BoolFlag{ + Name: "enable-preconfigured-udn-addresses", + Usage: "Enable workloads connect to user-defined network with preconfigured addresses.", + Destination: &cliConfig.OVNKubernetesFeature.EnablePreconfiguredUDNAddresses, + Value: OVNKubernetesFeature.EnablePreconfiguredUDNAddresses, + }, &cli.BoolFlag{ Name: "enable-route-advertisements", Usage: "Configure to use route advertisements feature with ovn-kubernetes.", diff --git a/go-controller/pkg/config/config_test.go b/go-controller/pkg/config/config_test.go index ddfaf84e65..39e7fa41bc 100644 --- a/go-controller/pkg/config/config_test.go +++ b/go-controller/pkg/config/config_test.go @@ -227,6 +227,7 @@ egressip-node-healthcheck-port=1234 enable-multi-network=false enable-multi-networkpolicy=false enable-network-segmentation=false +enable-preconfigured-udn-addresses=false enable-route-advertisements=false enable-interconnect=false enable-multi-external-gateway=false @@ -338,6 +339,7 @@ var _ = Describe("Config Operations", func() { gomega.Expect(OVNKubernetesFeature.EgressIPNodeHealthCheckPort).To(gomega.Equal(0)) gomega.Expect(OVNKubernetesFeature.EnableMultiNetwork).To(gomega.BeFalse()) gomega.Expect(OVNKubernetesFeature.EnableNetworkSegmentation).To(gomega.BeFalse()) + gomega.Expect(OVNKubernetesFeature.EnablePreconfiguredUDNAddresses).To(gomega.BeFalse()) gomega.Expect(OVNKubernetesFeature.EnableRouteAdvertisements).To(gomega.BeFalse()) gomega.Expect(OVNKubernetesFeature.EnableMultiNetworkPolicy).To(gomega.BeFalse()) gomega.Expect(OVNKubernetesFeature.EnableInterconnect).To(gomega.BeFalse()) @@ -597,6 +599,7 @@ var _ = Describe("Config Operations", func() { "enable-multi-network=true", "enable-multi-networkpolicy=true", "enable-network-segmentation=true", + "enable-preconfigured-udn-addresses=true", "enable-route-advertisements=true", "enable-interconnect=true", "enable-multi-external-gateway=true", @@ -687,6 +690,7 @@ var _ = Describe("Config Operations", func() { gomega.Expect(OVNKubernetesFeature.EgressIPNodeHealthCheckPort).To(gomega.Equal(1234)) gomega.Expect(OVNKubernetesFeature.EnableMultiNetwork).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableNetworkSegmentation).To(gomega.BeTrue()) + gomega.Expect(OVNKubernetesFeature.EnablePreconfiguredUDNAddresses).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableRouteAdvertisements).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableInterconnect).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableMultiExternalGateway).To(gomega.BeTrue()) @@ -794,6 +798,7 @@ var _ = Describe("Config Operations", func() { gomega.Expect(OVNKubernetesFeature.EgressIPNodeHealthCheckPort).To(gomega.Equal(4321)) gomega.Expect(OVNKubernetesFeature.EnableMultiNetwork).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableNetworkSegmentation).To(gomega.BeTrue()) + gomega.Expect(OVNKubernetesFeature.EnablePreconfiguredUDNAddresses).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableRouteAdvertisements).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableMultiNetworkPolicy).To(gomega.BeTrue()) gomega.Expect(OVNKubernetesFeature.EnableInterconnect).To(gomega.BeTrue()) @@ -869,6 +874,7 @@ var _ = Describe("Config Operations", func() { "-enable-multi-network=true", "-enable-multi-networkpolicy=true", "-enable-network-segmentation=true", + "-enable-preconfigured-udn-addresses=true", "-enable-route-advertisements=true", "-enable-interconnect=true", "-enable-multi-external-gateway=true", diff --git a/helm/ovn-kubernetes/charts/ovnkube-control-plane/templates/ovnkube-control-plane.yaml b/helm/ovn-kubernetes/charts/ovnkube-control-plane/templates/ovnkube-control-plane.yaml index 2b6edcaa8e..465a0aa665 100644 --- a/helm/ovn-kubernetes/charts/ovnkube-control-plane/templates/ovnkube-control-plane.yaml +++ b/helm/ovn-kubernetes/charts/ovnkube-control-plane/templates/ovnkube-control-plane.yaml @@ -126,6 +126,8 @@ spec: value: {{ hasKey .Values.global "enableMultiNetwork" | ternary .Values.global.enableMultiNetwork false | quote }} - name: OVN_NETWORK_SEGMENTATION_ENABLE value: {{ default "" .Values.global.enableNetworkSegmentation | quote }} + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: {{ default "" .Values.global.enablePreconfiguredUDNAddresses | quote }} - name: OVN_HYBRID_OVERLAY_NET_CIDR value: {{ default "" .Values.global.hybridOverlayNetCidr | quote }} - name: OVN_DISABLE_SNAT_MULTIPLE_GWS diff --git a/helm/ovn-kubernetes/charts/ovnkube-node/templates/ovnkube-node.yaml b/helm/ovn-kubernetes/charts/ovnkube-node/templates/ovnkube-node.yaml index e4b0a0621a..dbf6268f6a 100644 --- a/helm/ovn-kubernetes/charts/ovnkube-node/templates/ovnkube-node.yaml +++ b/helm/ovn-kubernetes/charts/ovnkube-node/templates/ovnkube-node.yaml @@ -229,6 +229,8 @@ spec: value: {{ hasKey .Values.global "enableMultiNetwork" | ternary .Values.global.enableMultiNetwork false | quote }} - name: OVN_NETWORK_SEGMENTATION_ENABLE value: {{ default "" .Values.global.enableNetworkSegmentation | quote }} + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: {{ default "" .Values.global.enablePreconfiguredUDNAddresses | quote }} - name: OVN_ENABLE_INTERCONNECT value: {{ hasKey .Values.global "enableInterconnect" | ternary .Values.global.enableInterconnect false | quote }} - name: OVN_ENABLE_MULTI_EXTERNAL_GATEWAY diff --git a/helm/ovn-kubernetes/charts/ovnkube-single-node-zone/templates/ovnkube-single-node-zone.yaml b/helm/ovn-kubernetes/charts/ovnkube-single-node-zone/templates/ovnkube-single-node-zone.yaml index d60276308b..2cd3913633 100644 --- a/helm/ovn-kubernetes/charts/ovnkube-single-node-zone/templates/ovnkube-single-node-zone.yaml +++ b/helm/ovn-kubernetes/charts/ovnkube-single-node-zone/templates/ovnkube-single-node-zone.yaml @@ -414,6 +414,8 @@ spec: value: {{ hasKey .Values.global "enableMultiNetwork" | ternary .Values.global.enableMultiNetwork false | quote }} - name: OVN_NETWORK_SEGMENTATION_ENABLE value: {{ default "" .Values.global.enableNetworkSegmentation | quote }} + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: {{ default "" .Values.global.enablePreconfiguredUDNAddresses | quote }} - name: OVNKUBE_NODE_MGMT_PORT_NETDEV value: {{ default "" .Values.global.nodeMgmtPortNetdev | quote }} - name: OVN_EMPTY_LB_EVENTS diff --git a/helm/ovn-kubernetes/charts/ovnkube-zone-controller/templates/ovnkube-zone-controller.yaml b/helm/ovn-kubernetes/charts/ovnkube-zone-controller/templates/ovnkube-zone-controller.yaml index f692ed0524..3a437db089 100644 --- a/helm/ovn-kubernetes/charts/ovnkube-zone-controller/templates/ovnkube-zone-controller.yaml +++ b/helm/ovn-kubernetes/charts/ovnkube-zone-controller/templates/ovnkube-zone-controller.yaml @@ -313,6 +313,8 @@ spec: value: {{ hasKey .Values.global "enableMultiNetwork" | ternary .Values.global.enableMultiNetwork false | quote }} - name: OVN_NETWORK_SEGMENTATION_ENABLE value: {{ default "" .Values.global.enableNetworkSegmentation | quote }} + - name: OVN_PRE_CONF_UDN_ADDR_ENABLE + value: {{ default "" .Values.global.enablePreconfiguredUDNAddresses | quote }} - name: OVN_HYBRID_OVERLAY_NET_CIDR value: {{ default "" .Values.global.hybridOverlayNetCidr | quote }} - name: OVN_DISABLE_SNAT_MULTIPLE_GWS diff --git a/helm/ovn-kubernetes/values-multi-node-zone.yaml b/helm/ovn-kubernetes/values-multi-node-zone.yaml index 2eef44ecae..8056461256 100644 --- a/helm/ovn-kubernetes/values-multi-node-zone.yaml +++ b/helm/ovn-kubernetes/values-multi-node-zone.yaml @@ -76,6 +76,8 @@ global: enableMultiNetwork: false # -- Configure to use user defined networks (UDN) feature with ovn-kubernetes enableNetworkSegmentation: false + # -- Configure to enable workloads with preconfigured network connect to user defined networks (UDN) with ovn-kubernetes + enablePreconfiguredUDNAddresses: false # -- Configure to enable IPsec enableIpsec: false # -- Use SSL transport to NB/SB db and northd diff --git a/helm/ovn-kubernetes/values-single-node-zone.yaml b/helm/ovn-kubernetes/values-single-node-zone.yaml index 9747d45440..516b77220b 100644 --- a/helm/ovn-kubernetes/values-single-node-zone.yaml +++ b/helm/ovn-kubernetes/values-single-node-zone.yaml @@ -76,6 +76,8 @@ global: enableMultiNetwork: false # -- Configure to use user defined networks (UDN) feature with ovn-kubernetes enableNetworkSegmentation: false + # -- Configure to enable workloads with preconfigured network connect to user defined networks (UDN) with ovn-kubernetes + enablePreconfiguredUDNAddresses: false # -- Configure to enable IPsec enableIpsec: false # -- Use SSL transport to NB/SB db and northd From 6902456aec609963b12d8533f72448cbf45beb6a Mon Sep 17 00:00:00 2001 From: Xiaobin Qu Date: Thu, 10 Jul 2025 16:28:18 -0700 Subject: [PATCH 082/278] fix broken TOC links --- docs/features/network-qos-guide.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/features/network-qos-guide.md b/docs/features/network-qos-guide.md index 586368fb32..2e07a7bc31 100644 --- a/docs/features/network-qos-guide.md +++ b/docs/features/network-qos-guide.md @@ -8,7 +8,7 @@ 4. [Create Sample Pods and Verify the Configuration](#4-create-sample-pods-and-verify-the-configuration) 5. [Explain the NetworkQoS Object](#5-explain-the-networkqos-object) -## **1 Overview** +## **1 Overview** Differentiated Services Code Point (DSCP) marking and egress bandwidth metering let you prioritize or police specific traffic flows. The new **NetworkQoS** Custom Resource Definition (CRD) in [ovn-kubernetes](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/dist/templates/k8s.ovn.org_networkqoses.yaml.j2) makes both features available to Kubernetes users on **all** pod interfaces—primary or secondary—without touching pod manifests. @@ -19,7 +19,7 @@ cd contrib ./kind-helm.sh -nqe -mne ; # --enable-network-qos --enable-multi-network ``` -## **2 Create a Secondary Network** +## **2 Create a Secondary Network** File: nad.yaml @@ -46,7 +46,7 @@ spec: ``` *Why the label?* `NetworkQoS` uses a label selector to find matching NADs. Without at least one label, the selector cannot match. -## **3 Define a NetworkQoS Policy** +## **3 Define a NetworkQoS Policy** File: nqos.yaml @@ -108,7 +108,7 @@ NAME STATUS qos-external NetworkQoS Destinations applied ``` -## **4 Create Sample Pods and Verify the Configuration** +## **4 Create Sample Pods and Verify the Configuration** ### **4.1 Launch Test Pods** @@ -284,7 +284,7 @@ tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 byt 10.245.2.3 > 10.245.4.3: ICMP echo reply, id 14, seq 56, length 64 ``` -## **5 Explain the NetworkQoS Object** +## **5 Explain the NetworkQoS Object** Below is an *abbreviated* map of the CRD schema returned by `kubectl explain networkqos --recursive` (v1alpha1). Use this as a quick reference. For the definitive specification, always consult the `kubectl explain` output or the CRD YAML in the ovn-kubernetes repository. From 2edfdaf96632716022cd13db5986ee53fe777b03 Mon Sep 17 00:00:00 2001 From: Jamo Luhrsen Date: Fri, 11 Jul 2025 15:27:59 -0700 Subject: [PATCH 083/278] always() run the diags when a step fails (e.g., e2e testing) the rest of the workflow will not run unless it's tagged with always(). and when something fails is exactly when we want to get some diags. move all references to "Runner Diagnostics" to use always() Signed-off-by: Jamo Luhrsen --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2172e8d40d..1782c16b65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -387,6 +387,7 @@ jobs: uses: actions/checkout@v4 - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: ovn upgrade @@ -395,6 +396,7 @@ jobs: make -C test upgrade-ovn - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Run E2E shard-conformance @@ -402,6 +404,7 @@ jobs: make -C test shard-conformance - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Export kind logs @@ -634,6 +637,7 @@ jobs: run: make -C test traffic-flow-tests WHAT="setup" - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Run Tests @@ -687,6 +691,7 @@ jobs: fi - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Export kind logs @@ -799,6 +804,7 @@ jobs: ./contrib/kind-dual-stack-conversion.sh - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Run Dual-Stack Tests @@ -806,6 +812,7 @@ jobs: make -C test shard-test WHAT="Networking Granular Checks\|DualStack" - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Run Dual-Stack Control-Plane Tests @@ -813,6 +820,7 @@ jobs: make -C test control-plane WHAT="DualStack" - name: Runner Diagnostics + if: always() uses: ./.github/actions/diagnostics - name: Export kind logs From 7b1b7dd7073753a5ef6cc0797c25e150f7fd1a3d Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Mon, 14 Jul 2025 13:33:20 +0200 Subject: [PATCH 084/278] [OVN build] Fetch ovs version from the ovn submodule by default. You need to clone OVN first for that Signed-off-by: Nadia Pinaeva --- dist/images/Dockerfile.fedora | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dist/images/Dockerfile.fedora b/dist/images/Dockerfile.fedora index e1789bd1e5..fb3bce7abb 100644 --- a/dist/images/Dockerfile.fedora +++ b/dist/images/Dockerfile.fedora @@ -24,14 +24,28 @@ ENV PYTHONDONTWRITEBYTECODE yes RUN INSTALL_PKGS="git rpm-build dnf-plugins-core" && \ dnf install --best --refresh -y --setopt=tsflags=nodocs $INSTALL_PKGS +# Clone OVN Source Code. +ARG OVN_REPO=https://github.com/ovn-org/ovn.git +ARG OVN_GITREF=main +WORKDIR /root +RUN mkdir ovn && pushd ovn && \ + git init && \ + git remote add origin $OVN_REPO && \ + git fetch origin ${OVN_GITREF} --depth 1 && \ + git reset --hard FETCH_HEAD && \ + popd + # Clone OVS Source Code. ARG OVS_REPO=https://github.com/openvswitch/ovs.git -ARG OVS_GITREF=branch-* +# OVS_GITREF can be set to a specific commit or branch, otherwise the version pinned by OVN will be used. +ARG OVS_GITREF="" WORKDIR /root -RUN mkdir ovs && pushd ovs && \ +RUN OVS_OVN_GITREF=$(cd ovn && git submodule status ovs|cut -c 2-|cut -d ' ' -f 1) && \ + mkdir ovs && pushd ovs && \ git init && \ git remote add origin $OVS_REPO && \ - git fetch $OVS_REPO $(git ls-remote origin "${OVS_GITREF}" | sort -V -k2 | tail -1 | awk '{print $1}') --depth 1 && \ + OVS_GITREF="${OVS_GITREF:-$OVS_OVN_GITREF}" && \ + git fetch $OVS_REPO ${OVS_GITREF} --depth 1 && \ git reset --hard FETCH_HEAD && \ echo "1" && \ find rhel && \ @@ -48,16 +62,6 @@ RUN rm rpm/rpmbuild/RPMS/x86_64/*debug* RUN rm rpm/rpmbuild/RPMS/x86_64/*devel* RUN git log -n 1 -# Clone OVN Source Code. -ARG OVN_REPO=https://github.com/ovn-org/ovn.git -ARG OVN_GITREF=main -WORKDIR /root -RUN mkdir ovn && pushd ovn && \ - git init && \ - git remote add origin $OVN_REPO && \ - git fetch origin ${OVN_GITREF} --depth 1 && \ - git reset --hard FETCH_HEAD && \ - popd # Build OVN rpms. WORKDIR /root/ovn/ From 1272c122997432095dd87f821d60c75fcad65425 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 10 Jul 2025 17:13:00 -0700 Subject: [PATCH 085/278] cluster-manager clbs if udn requests PersistentIPs but it is not enabled The log shows 'failed to run ovnkube: failed to start cluster manager: initial sync failed: failed to sync network cluster_udn_test-net: [clustermanager-nad-controller network controller]: failed to ensure network cluster_udn_test-net: failed to start network cluster) cluster_udn_test-net: failed to initialize pod ip allocator: network "cluster_udn_test-net" allows persistent IPs but missing the claims reconciler' Signed-off-by: Yun Zhou --- .../userdefinednetwork/template/net-attach-def-template.go | 3 +++ .../template/net-attach-def-template_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go index a06e7085ed..0b3aa61194 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go @@ -166,6 +166,9 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s netConfSpec.VLANID = int(cfg.VLAN.Access.ID) } } + if netConfSpec.AllowPersistentIPs && !config.OVNKubernetesFeature.EnablePersistentIPs { + return nil, fmt.Errorf("allowPersistentIPs is set but persistentIPs is Disabled") + } if err := util.ValidateNetConf(nadName, netConfSpec); err != nil { return nil, err diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go index 68f2e4022a..ab0593e210 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go @@ -301,6 +301,7 @@ var _ = Describe("NetAttachDefTemplate", func() { // must be defined so the primary user defined network can match the ip families of the underlying cluster config.IPv4Mode = true config.IPv6Mode = true + config.OVNKubernetesFeature.EnablePersistentIPs = true nad, err := RenderNetAttachDefManifest(testUdn, testNs) Expect(err).NotTo(HaveOccurred()) Expect(nad.TypeMeta).To(Equal(expectedNAD.TypeMeta)) @@ -436,6 +437,7 @@ var _ = Describe("NetAttachDefTemplate", func() { // must be defined so the primary user defined network can match the ip families of the underlying cluster config.IPv4Mode = true config.IPv6Mode = true + config.OVNKubernetesFeature.EnablePersistentIPs = true nad, err := RenderNetAttachDefManifest(cudn, testNs) Expect(err).NotTo(HaveOccurred()) Expect(nad.TypeMeta).To(Equal(expectedNAD.TypeMeta)) From 93a8828f9cb0e899ca68f05df7719cb303c24d53 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Tue, 15 Jul 2025 11:39:19 +0200 Subject: [PATCH 086/278] Update OWNERS file: Add Patryk/Martin as approvers Signed-off-by: Surya Seetharaman --- OWNERS | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/OWNERS b/OWNERS index 6a8622bcf2..8e82d18d80 100644 --- a/OWNERS +++ b/OWNERS @@ -1,16 +1,14 @@ reviewers: - - abhat - - dcbw - - JacobTanenbaum - jcaamano + - kyrtapz + - martinkennelly - trozet - tssurya approvers: - abhat - - danwinship - - dcbw - - JacobTanenbaum - jcaamano + - kyrtapz + - martinkennelly - knobunc - trozet - tssurya From 465e00a0758512de22f1a68f84d42862b3f60f5e Mon Sep 17 00:00:00 2001 From: Or Shoval Date: Wed, 9 Jul 2025 09:37:03 +0300 Subject: [PATCH 087/278] images: Use Quay instead docker.io Docker.io has rate limits, hence use Quay instead. Signed-off-by: Or Shoval --- dist/images/Dockerfile.fedora | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/images/Dockerfile.fedora b/dist/images/Dockerfile.fedora index fb3bce7abb..49b8da6872 100644 --- a/dist/images/Dockerfile.fedora +++ b/dist/images/Dockerfile.fedora @@ -14,7 +14,7 @@ ARG OVN_FROM=koji ############################################# # Stage to get OVN and OVS RPMs from source # ############################################# -FROM fedora:41 AS ovnbuilder +FROM quay.io/fedora/fedora:41 AS ovnbuilder USER root @@ -78,7 +78,7 @@ RUN git log -n 1 ######################################## # Stage to download OVN RPMs from koji # ######################################## -FROM fedora:41 AS kojidownloader +FROM quay.io/fedora/fedora:41 AS kojidownloader ARG ovnver=ovn-24.09.2-71.fc41 USER root @@ -99,14 +99,14 @@ RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] || [ -z "$TARGETPLATFORM"] ; then k ###################################### # Stage to copy OVN RPMs from source # ###################################### -FROM fedora:41 AS source +FROM quay.io/fedora/fedora:41 AS source COPY --from=ovnbuilder /root/ovn/rpm/rpmbuild/RPMS/x86_64/*.rpm / COPY --from=ovnbuilder /root/ovs/rpm/rpmbuild/RPMS/x86_64/*.rpm / #################################### # Stage to copy OVN RPMs from koji # #################################### -FROM fedora:41 AS koji +FROM quay.io/fedora/fedora:41 AS koji COPY --from=kojidownloader /*.rpm / From 10f14edb9952e11cecd97d6e57632f0341f8d3a7 Mon Sep 17 00:00:00 2001 From: Geo Turcsanyi Date: Mon, 14 Jul 2025 18:53:30 +0200 Subject: [PATCH 088/278] update jinjanate install to use pipx and mention pipx and skopeo as dependencies Signed-off-by: Geo Turcsanyi --- contrib/kind.sh | 10 +++++----- .../launching-ovn-kubernetes-on-kind.md | 19 ++++++------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index 7fa446b97c..af1c0f537c 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -459,8 +459,8 @@ print_params() { install_jinjanator_renderer() { # ensure jinjanator renderer installed - pip install wheel --user - pip freeze | grep jinjanator || pip install jinjanator[yaml] --user + pipx install jinjanator[yaml] + pipx ensurepath --force >/dev/null export PATH=~/.local/bin:$PATH } @@ -499,11 +499,11 @@ check_dependencies() { fi if ! command_exists jinjanate ; then - if ! command_exists pip ; then - echo "Dependency not met: 'jinjanator' not installed and cannot install with 'pip'" + if ! command_exists pipx ; then + echo "Dependency not met: 'jinjanator' not installed and cannot install with 'pipx'" exit 1 fi - echo "'jinjanate' not found, installing with 'pip'" + echo "'jinjanate' not found, installing with 'pipx'" install_jinjanator_renderer fi diff --git a/docs/installation/launching-ovn-kubernetes-on-kind.md b/docs/installation/launching-ovn-kubernetes-on-kind.md index 5c61f3a9cd..1a6ae11a15 100644 --- a/docs/installation/launching-ovn-kubernetes-on-kind.md +++ b/docs/installation/launching-ovn-kubernetes-on-kind.md @@ -14,20 +14,19 @@ KIND (Kubernetes in Docker) deployment of OVN kubernetes is a fast and easy mean sudo firewall-cmd --permanent --add-port=11337/tcp; sudo firewall-cmd --reload ``` - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) -- Python and pip +- Python 3 and [pipx](https://pipx.pypa.io/stable/installation/) - jq - openssl - openvswitch - Go 1.23.0 or above - -**NOTE :** In certain operating systems such as CentOS 8.x, pip2 and pip3 binaries are installed instead of pip. In such situations create a softlink for "pip" that points to "pip2". +- For podman users: skopeo For OVN kubernetes KIND deployment, use the `kind.sh` script. First Download and build the OVN-Kubernetes repo: -``` -git clone https://github.com/ovn-kubernetes/ovn-kubernetes.git; +```shell +git clone https://github.com/ovn-kubernetes/ovn-kubernetes.git cd ovn-kubernetes ``` The `kind.sh` script builds OVN-Kubernetes into a container image. To verify @@ -54,13 +53,6 @@ $ ./kind.sh $ popd ``` -**NOTE:** If you run into issues with installing jinjanate on Ubuntu due to [PEP-0668](https://peps.python.org/pep-0668/) you can work around via: -``` -sudo apt-get install pipx -pipx install jinjanator[yaml] -pipx ensurepath -``` - ### Run the KIND deployment with podman To verify local changes, the steps are mostly the same as with docker, except the `fedora` make target: @@ -87,8 +79,9 @@ To deploy KIND however, you need to start it as root and then copy root's kube c ``` $ pushd contrib $ sudo ./kind.sh -ep podman +$ mkdir -p ~/.kube $ sudo cp /root/ovn.conf ~/.kube/kind-config -$ sudo chown $(id -u):$(id -g) -R ~/.kube +$ sudo chown $(id -u):$(id -g) ~/.kube/kind-config $ export KUBECONFIG=~/.kube/kind-config $ popd ``` From ab24f2552fb0ef75872049e5ee412ddafd297598 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 10 Jul 2025 10:35:53 +0200 Subject: [PATCH 089/278] [gateway] cleanup: do some equivalent changes to simplify the code. Move stuff evaluation closer to where it is used. Replace gatewayRouter with gw.gwRouterName (why would you use this indirection?) Rename logicalRouter to gwRouter Join 2 very similar loops for gwLRPIPs and gwLRPJoinIPs (they even had exactly the same comment) Remove deleting GatewayRouter LRP as the GatewayRouter itself is deleted a coupled lines below. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 151 +++++++++++++------------------ 1 file changed, 65 insertions(+), 86 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 54005e4301..cefd275782 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -254,27 +254,7 @@ func (gw *GatewayManager) GatewayInit( enableGatewayMTU bool, ) error { - gwLRPIPs := make([]net.IP, 0) - for _, gwLRPJoinIP := range gwLRPJoinIPs { - gwLRPIPs = append(gwLRPIPs, gwLRPJoinIP.IP) - } - if gw.netInfo.TopologyType() == types.Layer2Topology { - // At layer2 GR LRP acts as the layer3 ovn_cluster_router so we need - // to configure here the .1 address, this will work only for IC with - // one node per zone, since ARPs for .1 will not go beyond local switch. - // This is being done to add the ICMP SNATs for .1 podSubnet that OVN GR generates - for _, subnet := range hostSubnets { - gwLRPIPs = append(gwLRPIPs, util.GetNodeGatewayIfAddr(subnet).IP) - } - } - // Create a gateway router. - gatewayRouter := gw.gwRouterName - physicalIPs := make([]string, len(l3GatewayConfig.IPAddresses)) - for i, ip := range l3GatewayConfig.IPAddresses { - physicalIPs[i] = ip.IP.String() - } - dynamicNeighRouters := "true" if config.OVNKubernetesFeature.EnableInterconnect { dynamicNeighRouters = "false" @@ -305,6 +285,10 @@ func (gw *GatewayManager) GatewayInit( } logicalRouterOptions["lb_force_snat_ip"] = strings.Join(joinIPDualStack, " ") } + physicalIPs := make([]string, len(l3GatewayConfig.IPAddresses)) + for i, ip := range l3GatewayConfig.IPAddresses { + physicalIPs[i] = ip.IP.String() + } logicalRouterExternalIDs := map[string]string{ "physical_ip": physicalIPs[0], "physical_ips": strings.Join(physicalIPs, ","), @@ -314,27 +298,27 @@ func (gw *GatewayManager) GatewayInit( maps.Copy(logicalRouterExternalIDs, util.GenerateExternalIDsForSwitchOrRouter(gw.netInfo)) } - logicalRouter := nbdb.LogicalRouter{ - Name: gatewayRouter, + gwRouter := nbdb.LogicalRouter{ + Name: gw.gwRouterName, Options: logicalRouterOptions, ExternalIDs: logicalRouterExternalIDs, Copp: &gw.coppUUID, } if gw.clusterLoadBalancerGroupUUID != "" { - logicalRouter.LoadBalancerGroup = []string{gw.clusterLoadBalancerGroupUUID} + gwRouter.LoadBalancerGroup = []string{gw.clusterLoadBalancerGroupUUID} if l3GatewayConfig.NodePortEnable && gw.routerLoadBalancerGroupUUID != "" { // add routerLoadBalancerGroupUUID to the gateway router only if nodePort is enabled - logicalRouter.LoadBalancerGroup = append(logicalRouter.LoadBalancerGroup, gw.routerLoadBalancerGroupUUID) + gwRouter.LoadBalancerGroup = append(gwRouter.LoadBalancerGroup, gw.routerLoadBalancerGroupUUID) } } // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, // so let's save the old value before we update the router for later use var oldExtIPs []net.IP - oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &logicalRouter) + oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &gwRouter) if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { - return fmt.Errorf("failed in retrieving %s, error: %v", gatewayRouter, err) + return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) } if oldLogicalRouter != nil && oldLogicalRouter.ExternalIDs != nil { @@ -352,14 +336,14 @@ func (gw *GatewayManager) GatewayInit( } } - err = libovsdbops.CreateOrUpdateLogicalRouter(gw.nbClient, &logicalRouter, &logicalRouter.Options, - &logicalRouter.ExternalIDs, &logicalRouter.LoadBalancerGroup, &logicalRouter.Copp) + err = libovsdbops.CreateOrUpdateLogicalRouter(gw.nbClient, &gwRouter, &gwRouter.Options, + &gwRouter.ExternalIDs, &gwRouter.LoadBalancerGroup, &gwRouter.Copp) if err != nil { - return fmt.Errorf("failed to create logical router %+v: %v", logicalRouter, err) + return fmt.Errorf("failed to create logical router %+v: %v", gwRouter, err) } - gwSwitchPort := types.JoinSwitchToGWRouterPrefix + gatewayRouter - gwRouterPort := types.GWRouterToJoinSwitchPrefix + gatewayRouter + gwSwitchPort := types.JoinSwitchToGWRouterPrefix + gw.gwRouterName + gwRouterPort := types.GWRouterToJoinSwitchPrefix + gw.gwRouterName // In Layer2 networks there is no join switch and the gw.joinSwitchName points to the cluster switch. // Ensure that the ports are named appropriately, this is important for the logical router policies @@ -383,23 +367,23 @@ func (gw *GatewayManager) GatewayInit( types.NetworkExternalID: gw.netInfo.GetNetworkName(), types.TopologyExternalID: gw.netInfo.TopologyType(), } - if gw.netInfo.TopologyType() == types.Layer2Topology { - node, err := gw.watchFactory.GetNode(nodeName) - if err != nil { - return fmt.Errorf("failed to fetch node %s from watch factory %w", node, err) - } - tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, gw.netInfo.GetNetworkName()) - if err != nil { - if util.IsAnnotationNotSetError(err) { - // remote node may not have the annotation yet, suppress it - return types.NewSuppressedError(err) - } - // Don't consider this node as cluster-manager has not allocated node id yet. - return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", - nodeName, gw.netInfo.GetNetworkName(), err) + } + if gw.netInfo.TopologyType() == types.Layer2Topology { + node, err := gw.watchFactory.GetNode(nodeName) + if err != nil { + return fmt.Errorf("failed to fetch node %s from watch factory %w", node, err) + } + tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, gw.netInfo.GetNetworkName()) + if err != nil { + if util.IsAnnotationNotSetError(err) { + // remote node may not have the annotation yet, suppress it + return types.NewSuppressedError(err) } - logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) + // Don't consider this node as cluster-manager has not allocated node id yet. + return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", + nodeName, gw.netInfo.GetNetworkName(), err) } + logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) } sw := nbdb.LogicalSwitch{Name: gw.joinSwitchName} err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(gw.nbClient, &sw, &logicalSwitchPort) @@ -407,19 +391,23 @@ func (gw *GatewayManager) GatewayInit( return fmt.Errorf("failed to create port %v on logical switch %q: %v", gwSwitchPort, sw.Name, err) } - gwLRPMAC := util.IPAddrToHWAddr(gwLRPIPs[0]) + gwLRPIPs := make([]net.IP, 0) gwLRPNetworks := []string{} for _, gwLRPJoinIP := range gwLRPJoinIPs { + gwLRPIPs = append(gwLRPIPs, gwLRPJoinIP.IP) gwLRPNetworks = append(gwLRPNetworks, gwLRPJoinIP.String()) } if gw.netInfo.TopologyType() == types.Layer2Topology { // At layer2 GR LRP acts as the layer3 ovn_cluster_router so we need // to configure here the .1 address, this will work only for IC with // one node per zone, since ARPs for .1 will not go beyond local switch. + // This is being done to add the ICMP SNATs for .1 podSubnet that OVN GR generates for _, subnet := range hostSubnets { + gwLRPIPs = append(gwLRPIPs, util.GetNodeGatewayIfAddr(subnet).IP) gwLRPNetworks = append(gwLRPNetworks, util.GetNodeGatewayIfAddr(subnet).String()) } } + gwLRPMAC := util.IPAddrToHWAddr(gwLRPIPs[0]) var options map[string]string if enableGatewayMTU { @@ -453,11 +441,11 @@ func (gw *GatewayManager) GatewayInit( } } - err = libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, &logicalRouter, + err = libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, &gwRouter, &logicalRouterPort, nil, &logicalRouterPort.MAC, &logicalRouterPort.Networks, &logicalRouterPort.Options) if err != nil { - return fmt.Errorf("failed to create port %+v on router %+v: %v", logicalRouterPort, logicalRouter, err) + return fmt.Errorf("failed to create port %+v on router %+v: %v", logicalRouterPort, gwRouter, err) } if len(drLRPIfAddrs) > 0 { for _, entry := range clusterIPSubnet { @@ -465,7 +453,7 @@ func (gw *GatewayManager) GatewayInit( if err != nil { return fmt.Errorf("failed to add a static route in GR %s with distributed "+ "router as the nexthop: %v", - gatewayRouter, err) + gw.gwRouterName, err) } // TODO There has to be a better way to do this. It seems like the @@ -476,9 +464,9 @@ func (gw *GatewayManager) GatewayInit( // a better way to do it. Adding support for indirection in ModelClients // opModel (being able to operate on thins pointed to from another model) // would be a great way to simplify this. - updatedLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &logicalRouter) + updatedGWRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &gwRouter) if err != nil { - return fmt.Errorf("unable to retrieve logical router %+v: %v", logicalRouter, err) + return fmt.Errorf("unable to retrieve logical router %+v: %v", gwRouter, err) } lrsr := nbdb.LogicalRouterStaticRoute{ @@ -493,19 +481,19 @@ func (gw *GatewayManager) GatewayInit( } p := func(item *nbdb.LogicalRouterStaticRoute) bool { return item.IPPrefix == lrsr.IPPrefix && libovsdbops.PolicyEqualPredicate(item.Policy, lrsr.Policy) && - util.SliceHasStringItem(updatedLogicalRouter.StaticRoutes, item.UUID) + util.SliceHasStringItem(updatedGWRouter.StaticRoutes, item.UUID) } - err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gatewayRouter, &lrsr, p, + err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gw.gwRouterName, &lrsr, p, &lrsr.Nexthop) if err != nil { - return fmt.Errorf("failed to add a static route %+v in GR %s with distributed router as the nexthop, err: %v", lrsr, gatewayRouter, err) + return fmt.Errorf("failed to add a static route %+v in GR %s with distributed router as the nexthop, err: %v", lrsr, gw.gwRouterName, err) } } } if err := gw.addExternalSwitch("", l3GatewayConfig.InterfaceID, - gatewayRouter, + gw.gwRouterName, l3GatewayConfig.MACAddress.String(), physNetName(gw.netInfo), l3GatewayConfig.IPAddresses, @@ -516,7 +504,7 @@ func (gw *GatewayManager) GatewayInit( if l3GatewayConfig.EgressGWInterfaceID != "" { if err := gw.addExternalSwitch(types.EgressGWSwitchPrefix, l3GatewayConfig.EgressGWInterfaceID, - gatewayRouter, + gw.gwRouterName, l3GatewayConfig.EgressGWMACAddress.String(), types.PhysicalNetworkExGwName, l3GatewayConfig.EgressGWIPAddresses, @@ -525,16 +513,16 @@ func (gw *GatewayManager) GatewayInit( } } - externalRouterPort := types.GWRouterToExtSwitchPrefix + gatewayRouter + externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName nextHops := l3GatewayConfig.NextHops // Remove stale OVN resources with any old masquerade IP - if err := deleteStaleMasqueradeResources(gw.nbClient, gatewayRouter, nodeName, gw.watchFactory); err != nil { + if err := deleteStaleMasqueradeResources(gw.nbClient, gw.gwRouterName, nodeName, gw.watchFactory); err != nil { return fmt.Errorf("failed to remove stale masquerade resources from northbound database: %w", err) } - if err := gateway.CreateDummyGWMacBindings(gw.nbClient, gatewayRouter, gw.netInfo); err != nil { + if err := gateway.CreateDummyGWMacBindings(gw.nbClient, gw.gwRouterName, gw.netInfo); err != nil { return err } @@ -560,10 +548,10 @@ func (gw *GatewayManager) GatewayInit( return item.OutputPort != nil && *item.OutputPort == *lrsr.OutputPort && item.IPPrefix == lrsr.IPPrefix && libovsdbops.PolicyEqualPredicate(item.Policy, lrsr.Policy) } - err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gatewayRouter, &lrsr, p, + err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gw.gwRouterName, &lrsr, p, &lrsr.Nexthop) if err != nil { - return fmt.Errorf("error creating service static route %+v in GR %s: %v", lrsr, gatewayRouter, err) + return fmt.Errorf("error creating service static route %+v in GR %s: %v", lrsr, gw.gwRouterName, err) } } // Add default gateway routes in GR @@ -590,10 +578,10 @@ func (gw *GatewayManager) GatewayInit( return item.OutputPort != nil && *item.OutputPort == *lrsr.OutputPort && item.IPPrefix == lrsr.IPPrefix && libovsdbops.PolicyEqualPredicate(lrsr.Policy, item.Policy) } - err := libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gatewayRouter, &lrsr, + err := libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gw.gwRouterName, &lrsr, p, &lrsr.Nexthop) if err != nil { - return fmt.Errorf("error creating static route %+v in GR %s: %v", lrsr, gatewayRouter, err) + return fmt.Errorf("error creating static route %+v in GR %s: %v", lrsr, gw.gwRouterName, err) } } @@ -718,7 +706,7 @@ func (gw *GatewayManager) GatewayInit( for _, externalIP := range externalIPs { oldExternalIP, err := util.MatchFirstIPFamily(utilnet.IsIPv6(externalIP), oldExtIPs) if err != nil { - return fmt.Errorf("failed to update GW SNAT rule for pods on router %s error: %v", gatewayRouter, err) + return fmt.Errorf("failed to update GW SNAT rule for pods on router %s error: %v", gw.gwRouterName, err) } if externalIP.String() == oldExternalIP.String() { // no external ip change, skip @@ -740,7 +728,7 @@ func (gw *GatewayManager) GatewayInit( joinIP, err := util.MatchFirstIPFamily(utilnet.IsIPv6(parsedLogicalIP), gwLRPIPs) if err != nil { return fmt.Errorf("failed to find valid IP family match for join subnet IP: %s on "+ - "gateway router: %s, provided IPs: %#v", parsedLogicalIP, gatewayRouter, gwLRPIPs) + "gateway router: %s, provided IPs: %#v", parsedLogicalIP, gw.gwRouterName, gwLRPIPs) } if nat.LogicalIP != joinIP.String() { // needs to be updated @@ -754,9 +742,9 @@ func (gw *GatewayManager) GatewayInit( } if len(natsToUpdate) > 0 { - err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &logicalRouter, natsToUpdate...) + err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, natsToUpdate...) if err != nil { - return fmt.Errorf("failed to update GW SNAT rule for pod on router %s error: %v", gatewayRouter, err) + return fmt.Errorf("failed to update GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } } @@ -773,7 +761,7 @@ func (gw *GatewayManager) GatewayInit( externalIP, err := util.MatchIPFamily(utilnet.IsIPv6(gwLRPIP), externalIPs) if err != nil { return fmt.Errorf("failed to find valid external IP family match for join subnet IP: %s on "+ - "gateway router: %s", gwLRPIP, gatewayRouter) + "gateway router: %s", gwLRPIP, gw.gwRouterName) } joinIPNet, err := util.GetIPNetFullMask(gwLRPIP.String()) if err != nil { @@ -782,9 +770,9 @@ func (gw *GatewayManager) GatewayInit( nat := libovsdbops.BuildSNAT(&externalIP[0], joinIPNet, "", extIDs) joinNATs = append(joinNATs, nat) } - err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &logicalRouter, joinNATs...) + err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, joinNATs...) if err != nil { - return fmt.Errorf("failed to create SNAT rule for join subnet on router %s error: %v", gatewayRouter, err) + return fmt.Errorf("failed to create SNAT rule for join subnet on router %s error: %v", gw.gwRouterName, err) } nats := make([]*nbdb.NAT, 0, len(clusterIPSubnet)) @@ -796,15 +784,15 @@ func (gw *GatewayManager) GatewayInit( externalIP, err := util.MatchIPFamily(utilnet.IsIPv6CIDR(entry), externalIPs) if err != nil { return fmt.Errorf("failed to create default SNAT rules for gateway router %s: %v", - gatewayRouter, err) + gw.gwRouterName, err) } nat = libovsdbops.BuildSNATWithMatch(&externalIP[0], entry, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, &logicalRouter, nats...) + err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, nats...) if err != nil { - return fmt.Errorf("failed to update SNAT rule for pod on router %s error: %v", gatewayRouter, err) + return fmt.Errorf("failed to update SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } } else { // ensure we do not have any leftover SNAT entries after an upgrade @@ -812,9 +800,9 @@ func (gw *GatewayManager) GatewayInit( nat = libovsdbops.BuildSNATWithMatch(nil, logicalSubnet, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.DeleteNATs(gw.nbClient, &logicalRouter, nats...) + err := libovsdbops.DeleteNATs(gw.nbClient, &gwRouter, nats...) if err != nil { - return fmt.Errorf("failed to delete GW SNAT rule for pod on router %s error: %v", gatewayRouter, err) + return fmt.Errorf("failed to delete GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } } @@ -1115,16 +1103,6 @@ func (gw *GatewayManager) Cleanup() error { return fmt.Errorf("failed to delete logical switch port %s from switch %s: %w", portName, sw.Name, err) } - // Remove the logical router port on the gateway router that connects to the join switch - logicalRouter := nbdb.LogicalRouter{Name: gw.gwRouterName} - logicalRouterPort := nbdb.LogicalRouterPort{ - Name: gwRouterToJoinSwitchPortName, - } - err = libovsdbops.DeleteLogicalRouterPorts(gw.nbClient, &logicalRouter, &logicalRouterPort) - if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { - return fmt.Errorf("failed to delete port %s on router %s: %w", logicalRouterPort.Name, gw.gwRouterName, err) - } - // Remove the static mac bindings of the gateway router err = gateway.DeleteDummyGWMacBindings(gw.nbClient, gw.gwRouterName, gw.netInfo) if err != nil { @@ -1132,6 +1110,7 @@ func (gw *GatewayManager) Cleanup() error { } // Remove the gateway router associated with nodeName + logicalRouter := nbdb.LogicalRouter{Name: gw.gwRouterName} err = libovsdbops.DeleteLogicalRouter(gw.nbClient, &logicalRouter) if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { return fmt.Errorf("failed to delete gateway router %s: %w", gw.gwRouterName, err) From 6e38032cce5c876dc3a0269ffab9b1d6386fb54b Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 10 Jul 2025 10:41:49 +0200 Subject: [PATCH 090/278] [gateway] create createGWRouter function from GatewayInit Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 55 +++++++++++++++++++------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index cefd275782..266492809c 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -242,18 +242,7 @@ func (gw *GatewayManager) cleanupStalePodSNATs(nodeName string, nodeIPs []*net.I return nil } -// GatewayInit creates a gateway router for the local chassis. -// enableGatewayMTU enables options:gateway_mtu for gateway routers. -func (gw *GatewayManager) GatewayInit( - nodeName string, - clusterIPSubnet []*net.IPNet, - hostSubnets []*net.IPNet, - l3GatewayConfig *util.L3GatewayConfig, - gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, - externalIPs []net.IP, - enableGatewayMTU bool, -) error { - +func (gw *GatewayManager) createGWRouter(l3GatewayConfig *util.L3GatewayConfig, gwLRPJoinIPs []*net.IPNet) (*nbdb.LogicalRouter, error) { // Create a gateway router. dynamicNeighRouters := "true" if config.OVNKubernetesFeature.EnableInterconnect { @@ -313,10 +302,33 @@ func (gw *GatewayManager) GatewayInit( } } + err := libovsdbops.CreateOrUpdateLogicalRouter(gw.nbClient, &gwRouter, &gwRouter.Options, + &gwRouter.ExternalIDs, &gwRouter.LoadBalancerGroup, &gwRouter.Copp) + if err != nil { + return nil, fmt.Errorf("failed to create logical router %+v: %v", gwRouter, err) + } + return &gwRouter, nil +} + +// GatewayInit creates a gateway router for the local chassis. +// enableGatewayMTU enables options:gateway_mtu for gateway routers. +func (gw *GatewayManager) GatewayInit( + nodeName string, + clusterIPSubnet []*net.IPNet, + hostSubnets []*net.IPNet, + l3GatewayConfig *util.L3GatewayConfig, + gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, + externalIPs []net.IP, + enableGatewayMTU bool, +) error { + // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, // so let's save the old value before we update the router for later use var oldExtIPs []net.IP - oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &gwRouter) + oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, + &nbdb.LogicalRouter{ + Name: gw.gwRouterName, + }) if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) } @@ -336,10 +348,9 @@ func (gw *GatewayManager) GatewayInit( } } - err = libovsdbops.CreateOrUpdateLogicalRouter(gw.nbClient, &gwRouter, &gwRouter.Options, - &gwRouter.ExternalIDs, &gwRouter.LoadBalancerGroup, &gwRouter.Copp) + gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) if err != nil { - return fmt.Errorf("failed to create logical router %+v: %v", gwRouter, err) + return err } gwSwitchPort := types.JoinSwitchToGWRouterPrefix + gw.gwRouterName @@ -441,7 +452,7 @@ func (gw *GatewayManager) GatewayInit( } } - err = libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, &gwRouter, + err = libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, gwRouter, &logicalRouterPort, nil, &logicalRouterPort.MAC, &logicalRouterPort.Networks, &logicalRouterPort.Options) if err != nil { @@ -464,7 +475,7 @@ func (gw *GatewayManager) GatewayInit( // a better way to do it. Adding support for indirection in ModelClients // opModel (being able to operate on thins pointed to from another model) // would be a great way to simplify this. - updatedGWRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, &gwRouter) + updatedGWRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, gwRouter) if err != nil { return fmt.Errorf("unable to retrieve logical router %+v: %v", gwRouter, err) } @@ -742,7 +753,7 @@ func (gw *GatewayManager) GatewayInit( } if len(natsToUpdate) > 0 { - err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, natsToUpdate...) + err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, natsToUpdate...) if err != nil { return fmt.Errorf("failed to update GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } @@ -770,7 +781,7 @@ func (gw *GatewayManager) GatewayInit( nat := libovsdbops.BuildSNAT(&externalIP[0], joinIPNet, "", extIDs) joinNATs = append(joinNATs, nat) } - err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, joinNATs...) + err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, joinNATs...) if err != nil { return fmt.Errorf("failed to create SNAT rule for join subnet on router %s error: %v", gw.gwRouterName, err) } @@ -790,7 +801,7 @@ func (gw *GatewayManager) GatewayInit( nat = libovsdbops.BuildSNATWithMatch(&externalIP[0], entry, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, &gwRouter, nats...) + err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, nats...) if err != nil { return fmt.Errorf("failed to update SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } @@ -800,7 +811,7 @@ func (gw *GatewayManager) GatewayInit( nat = libovsdbops.BuildSNATWithMatch(nil, logicalSubnet, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.DeleteNATs(gw.nbClient, &gwRouter, nats...) + err := libovsdbops.DeleteNATs(gw.nbClient, gwRouter, nats...) if err != nil { return fmt.Errorf("failed to delete GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } From f54436cdc67cb46b8542dba81270a1ac68d59f7f Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 10 Jul 2025 11:02:19 +0200 Subject: [PATCH 091/278] [gateway] move gw router and its port creation to functions. Localize joinSwitch-related name fetching in methods Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 162 ++++++++++++++++++------------- 1 file changed, 92 insertions(+), 70 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 266492809c..ae8af7aa9f 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -310,67 +310,39 @@ func (gw *GatewayManager) createGWRouter(l3GatewayConfig *util.L3GatewayConfig, return &gwRouter, nil } -// GatewayInit creates a gateway router for the local chassis. -// enableGatewayMTU enables options:gateway_mtu for gateway routers. -func (gw *GatewayManager) GatewayInit( - nodeName string, - clusterIPSubnet []*net.IPNet, - hostSubnets []*net.IPNet, - l3GatewayConfig *util.L3GatewayConfig, - gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, - externalIPs []net.IP, - enableGatewayMTU bool, -) error { - - // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, - // so let's save the old value before we update the router for later use - var oldExtIPs []net.IP - oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, - &nbdb.LogicalRouter{ - Name: gw.gwRouterName, - }) - if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { - return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) - } - - if oldLogicalRouter != nil && oldLogicalRouter.ExternalIDs != nil { - if physicalIPs, ok := oldLogicalRouter.ExternalIDs["physical_ips"]; ok { - oldExternalIPs := strings.Split(physicalIPs, ",") - oldExtIPs = make([]net.IP, len(oldExternalIPs)) - for i, oldExternalIP := range oldExternalIPs { - cidr := oldExternalIP + util.GetIPFullMaskString(oldExternalIP) - ip, _, err := net.ParseCIDR(cidr) - if err != nil { - return fmt.Errorf("invalid cidr:%s error: %v", cidr, err) - } - oldExtIPs[i] = ip - } - } - } - - gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) - if err != nil { - return err +func (gw *GatewayManager) getGWRouterPeerPortName() string { + // In Layer2 networks there is no join switch and the gw.joinSwitchName points to the cluster switch. + // Ensure that the ports are named appropriately, this is important for the logical router policies + // created for local node access. + // TODO(kyrtapz): Clean this up for clarity as part of https://github.com/ovn-org/ovn-kubernetes/issues/4689 + if gw.netInfo.TopologyType() == types.Layer2Topology { + return types.SwitchToRouterPrefix + gw.joinSwitchName } - gwSwitchPort := types.JoinSwitchToGWRouterPrefix + gw.gwRouterName - gwRouterPort := types.GWRouterToJoinSwitchPrefix + gw.gwRouterName + return types.JoinSwitchToGWRouterPrefix + gw.gwRouterName +} +func (gw *GatewayManager) getGWRouterPortName() string { // In Layer2 networks there is no join switch and the gw.joinSwitchName points to the cluster switch. // Ensure that the ports are named appropriately, this is important for the logical router policies // created for local node access. // TODO(kyrtapz): Clean this up for clarity as part of https://github.com/ovn-org/ovn-kubernetes/issues/4689 if gw.netInfo.TopologyType() == types.Layer2Topology { - gwSwitchPort = types.SwitchToRouterPrefix + gw.joinSwitchName - gwRouterPort = types.RouterToSwitchPrefix + gw.joinSwitchName + return types.RouterToSwitchPrefix + gw.joinSwitchName } + return types.GWRouterToJoinSwitchPrefix + gw.gwRouterName +} + +func (gw *GatewayManager) createGWRouterPeerPort(nodeName string) error { + gwSwitchPort := gw.getGWRouterPeerPortName() + gwRouterPortName := gw.getGWRouterPortName() logicalSwitchPort := nbdb.LogicalSwitchPort{ Name: gwSwitchPort, Type: "router", Addresses: []string{"router"}, Options: map[string]string{ - "router-port": gwRouterPort, + "router-port": gwRouterPortName, }, } if gw.netInfo.IsSecondary() { @@ -397,11 +369,15 @@ func (gw *GatewayManager) GatewayInit( logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) } sw := nbdb.LogicalSwitch{Name: gw.joinSwitchName} - err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(gw.nbClient, &sw, &logicalSwitchPort) + err := libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(gw.nbClient, &sw, &logicalSwitchPort) if err != nil { return fmt.Errorf("failed to create port %v on logical switch %q: %v", gwSwitchPort, sw.Name, err) } + return err +} +func (gw *GatewayManager) createGWRouterPort(hostSubnets []*net.IPNet, gwLRPJoinIPs []*net.IPNet, + enableGatewayMTU bool, gwRouter *nbdb.LogicalRouter) ([]net.IP, error) { gwLRPIPs := make([]net.IP, 0) gwLRPNetworks := []string{} for _, gwLRPJoinIP := range gwLRPJoinIPs { @@ -426,20 +402,21 @@ func (gw *GatewayManager) GatewayInit( "gateway_mtu": strconv.Itoa(config.Default.MTU), } } - logicalRouterPort := nbdb.LogicalRouterPort{ - Name: gwRouterPort, + + gwRouterPort := nbdb.LogicalRouterPort{ + Name: gw.getGWRouterPortName(), MAC: gwLRPMAC.String(), Networks: gwLRPNetworks, Options: options, } if gw.netInfo.IsSecondary() { - logicalRouterPort.ExternalIDs = map[string]string{ + gwRouterPort.ExternalIDs = map[string]string{ types.NetworkExternalID: gw.netInfo.GetNetworkName(), types.TopologyExternalID: gw.netInfo.TopologyType(), } _, isNetIPv6 := gw.netInfo.IPMode() if gw.netInfo.TopologyType() == types.Layer2Topology && isNetIPv6 && config.IPv6Mode { - logicalRouterPort.Ipv6RaConfigs = map[string]string{ + gwRouterPort.Ipv6RaConfigs = map[string]string{ "address_mode": "dhcpv6_stateful", "send_periodic": "true", "max_interval": "900", // 15 minutes @@ -447,17 +424,72 @@ func (gw *GatewayManager) GatewayInit( "router_preference": "LOW", // The static gateway configured by CNI is MEDIUM, so make this SLOW so it has less effect for pods } if gw.netInfo.MTU() > 0 { - logicalRouterPort.Ipv6RaConfigs["mtu"] = fmt.Sprintf("%d", gw.netInfo.MTU()) + gwRouterPort.Ipv6RaConfigs["mtu"] = fmt.Sprintf("%d", gw.netInfo.MTU()) } } } - err = libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, gwRouter, - &logicalRouterPort, nil, &logicalRouterPort.MAC, &logicalRouterPort.Networks, - &logicalRouterPort.Options) + err := libovsdbops.CreateOrUpdateLogicalRouterPort(gw.nbClient, gwRouter, + &gwRouterPort, nil, &gwRouterPort.MAC, &gwRouterPort.Networks, + &gwRouterPort.Options) if err != nil { - return fmt.Errorf("failed to create port %+v on router %+v: %v", logicalRouterPort, gwRouter, err) + return nil, fmt.Errorf("failed to create port %+v on router %+v: %v", gwRouterPort, gwRouter, err) } + return gwLRPIPs, nil +} + +// GatewayInit creates a gateway router for the local chassis. +// enableGatewayMTU enables options:gateway_mtu for gateway routers. +func (gw *GatewayManager) GatewayInit( + nodeName string, + clusterIPSubnet []*net.IPNet, + hostSubnets []*net.IPNet, + l3GatewayConfig *util.L3GatewayConfig, + gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, + externalIPs []net.IP, + enableGatewayMTU bool, +) error { + + // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, + // so let's save the old value before we update the router for later use + var oldExtIPs []net.IP + oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, + &nbdb.LogicalRouter{ + Name: gw.gwRouterName, + }) + if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { + return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) + } + + if oldLogicalRouter != nil && oldLogicalRouter.ExternalIDs != nil { + if physicalIPs, ok := oldLogicalRouter.ExternalIDs["physical_ips"]; ok { + oldExternalIPs := strings.Split(physicalIPs, ",") + oldExtIPs = make([]net.IP, len(oldExternalIPs)) + for i, oldExternalIP := range oldExternalIPs { + cidr := oldExternalIP + util.GetIPFullMaskString(oldExternalIP) + ip, _, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf("invalid cidr:%s error: %v", cidr, err) + } + oldExtIPs[i] = ip + } + } + } + + gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) + if err != nil { + return err + } + + if err = gw.createGWRouterPeerPort(nodeName); err != nil { + return err + } + + gwLRPIPs, err := gw.createGWRouterPort(hostSubnets, gwLRPJoinIPs, enableGatewayMTU, gwRouter) + if err != nil { + return err + } + if len(drLRPIfAddrs) > 0 { for _, entry := range clusterIPSubnet { drLRPIfAddr, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(entry), drLRPIfAddrs) @@ -525,9 +557,6 @@ func (gw *GatewayManager) GatewayInit( } externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName - - nextHops := l3GatewayConfig.NextHops - // Remove stale OVN resources with any old masquerade IP if err := deleteStaleMasqueradeResources(gw.nbClient, gw.gwRouterName, nodeName, gw.watchFactory); err != nil { return fmt.Errorf("failed to remove stale masquerade resources from northbound database: %w", err) @@ -565,6 +594,8 @@ func (gw *GatewayManager) GatewayInit( return fmt.Errorf("error creating service static route %+v in GR %s: %v", lrsr, gw.gwRouterName, err) } } + + nextHops := l3GatewayConfig.NextHops // Add default gateway routes in GR for _, nextHop := range nextHops { var allIPs string @@ -1078,17 +1109,8 @@ func (gw *GatewayManager) Cleanup() error { // Get the gateway router port's IP address (connected to join switch) var nextHops []net.IP - gwRouterToJoinSwitchPortName := types.GWRouterToJoinSwitchPrefix + gw.gwRouterName - portName := types.JoinSwitchToGWRouterPrefix + gw.gwRouterName - - // In Layer2 networks there is no join switch and the gw.joinSwitchName points to the cluster switch. - // Ensure that the ports are named appropriately, this is important for the logical router policies - // created for local node access. - // TODO(kyrtapz): Clean this up for clarity as part of https://github.com/ovn-org/ovn-kubernetes/issues/4689 - if gw.netInfo.TopologyType() == types.Layer2Topology { - gwRouterToJoinSwitchPortName = types.RouterToSwitchPrefix + gw.joinSwitchName - portName = types.SwitchToRouterPrefix + gw.joinSwitchName - } + gwRouterToJoinSwitchPortName := gw.getGWRouterPortName() + portName := gw.getGWRouterPeerPortName() gwIPAddrs, err := libovsdbutil.GetLRPAddrs(gw.nbClient, gwRouterToJoinSwitchPortName) if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { From f392ef0e6f3e2cc6a556aad690c4b3a657106f14 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 10 Jul 2025 11:48:57 +0200 Subject: [PATCH 092/278] [gateway] split GatewayInit into more methods. I have moved staticRoutes update for drLRPIfAddrs to after external switch creation, shouldn't break anything. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 232 +++++++++++++++++++------------ 1 file changed, 140 insertions(+), 92 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index ae8af7aa9f..28c685398a 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -438,58 +438,8 @@ func (gw *GatewayManager) createGWRouterPort(hostSubnets []*net.IPNet, gwLRPJoin return gwLRPIPs, nil } -// GatewayInit creates a gateway router for the local chassis. -// enableGatewayMTU enables options:gateway_mtu for gateway routers. -func (gw *GatewayManager) GatewayInit( - nodeName string, - clusterIPSubnet []*net.IPNet, - hostSubnets []*net.IPNet, - l3GatewayConfig *util.L3GatewayConfig, - gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, - externalIPs []net.IP, - enableGatewayMTU bool, -) error { - - // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, - // so let's save the old value before we update the router for later use - var oldExtIPs []net.IP - oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, - &nbdb.LogicalRouter{ - Name: gw.gwRouterName, - }) - if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { - return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) - } - - if oldLogicalRouter != nil && oldLogicalRouter.ExternalIDs != nil { - if physicalIPs, ok := oldLogicalRouter.ExternalIDs["physical_ips"]; ok { - oldExternalIPs := strings.Split(physicalIPs, ",") - oldExtIPs = make([]net.IP, len(oldExternalIPs)) - for i, oldExternalIP := range oldExternalIPs { - cidr := oldExternalIP + util.GetIPFullMaskString(oldExternalIP) - ip, _, err := net.ParseCIDR(cidr) - if err != nil { - return fmt.Errorf("invalid cidr:%s error: %v", cidr, err) - } - oldExtIPs[i] = ip - } - } - } - - gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) - if err != nil { - return err - } - - if err = gw.createGWRouterPeerPort(nodeName); err != nil { - return err - } - - gwLRPIPs, err := gw.createGWRouterPort(hostSubnets, gwLRPJoinIPs, enableGatewayMTU, gwRouter) - if err != nil { - return err - } - +func (gw *GatewayManager) updateGWRouterStaticRoutes(clusterIPSubnet, drLRPIfAddrs []*net.IPNet, + l3GatewayConfig *util.L3GatewayConfig, externalRouterPort string, gwRouter *nbdb.LogicalRouter) error { if len(drLRPIfAddrs) > 0 { for _, entry := range clusterIPSubnet { drLRPIfAddr, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(entry), drLRPIfAddrs) @@ -534,38 +484,6 @@ func (gw *GatewayManager) GatewayInit( } } - if err := gw.addExternalSwitch("", - l3GatewayConfig.InterfaceID, - gw.gwRouterName, - l3GatewayConfig.MACAddress.String(), - physNetName(gw.netInfo), - l3GatewayConfig.IPAddresses, - l3GatewayConfig.VLANID); err != nil { - return err - } - - if l3GatewayConfig.EgressGWInterfaceID != "" { - if err := gw.addExternalSwitch(types.EgressGWSwitchPrefix, - l3GatewayConfig.EgressGWInterfaceID, - gw.gwRouterName, - l3GatewayConfig.EgressGWMACAddress.String(), - types.PhysicalNetworkExGwName, - l3GatewayConfig.EgressGWIPAddresses, - nil); err != nil { - return err - } - } - - externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName - // Remove stale OVN resources with any old masquerade IP - if err := deleteStaleMasqueradeResources(gw.nbClient, gw.gwRouterName, nodeName, gw.watchFactory); err != nil { - return fmt.Errorf("failed to remove stale masquerade resources from northbound database: %w", err) - } - - if err := gateway.CreateDummyGWMacBindings(gw.nbClient, gw.gwRouterName, gw.netInfo); err != nil { - return err - } - for _, nextHop := range node.DummyNextHopIPs() { // Add return service route for OVN back to host prefix := config.Gateway.V4MasqueradeSubnet @@ -588,7 +506,7 @@ func (gw *GatewayManager) GatewayInit( return item.OutputPort != nil && *item.OutputPort == *lrsr.OutputPort && item.IPPrefix == lrsr.IPPrefix && libovsdbops.PolicyEqualPredicate(item.Policy, lrsr.Policy) } - err = libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gw.gwRouterName, &lrsr, p, + err := libovsdbops.CreateOrReplaceLogicalRouterStaticRouteWithPredicate(gw.nbClient, gw.gwRouterName, &lrsr, p, &lrsr.Nexthop) if err != nil { return fmt.Errorf("error creating service static route %+v in GR %s: %v", lrsr, gw.gwRouterName, err) @@ -627,6 +545,10 @@ func (gw *GatewayManager) GatewayInit( } } + return nil +} + +func (gw *GatewayManager) updateClusterRouterStaticRoutes(hostSubnets []*net.IPNet, gwLRPIPs []net.IP) error { // We need to add a route to the Gateway router's IP, on the // cluster router, to ensure that the return traffic goes back // to the same gateway router @@ -721,17 +643,36 @@ func (gw *GatewayManager) GatewayInit( } } } + return nil +} +// syncNATsForGRIPChange updates the SNAT rules on the gateway router that are created outside the GatewayManager. +// Multiple handlers, like +// - DefaultNetworkController.addLogicalPort +// - DefaultNetworkController.updateNamespace +// - EgressIPController.addExternalGWPodSNATOps +// - EgressIPController.addPodEgressIPAssignment +// - SecondaryLayer2NetworkController.buildUDNEgressSNAT +// - SecondaryLayer3NetworkController.addUDNNodeSubnetEgressSNAT +// use gateway config parameters to create SNAT rules on the gateway router, but some of them (not all) don't watch +// gateway config changes and rely on the GatewayManager to update their SNAT rules. +// Is it racy? Yes! +// This function also updates SNAT created by `updateGWRouterNAT`, because NATs don't use ExternalIDs, +// and their fields are used to find equivalent NATs. That means on gateway IPs change, instead of updating +// the old NAT, we would create a new one. FIXME: add externalIDs to NATs +func (gw *GatewayManager) syncNATsForGRIPChange(externalIPs, oldExtIPs, gwLRPIPs []net.IP, + gwRouter, oldGWRouter *nbdb.LogicalRouter) error { // if config.Gateway.DisabledSNATMultipleGWs is not set (by default it is not), // the NAT rules for pods not having annotations to route through either external // gws or pod CNFs will be added within pods.go addLogicalPort var natsToUpdate []*nbdb.NAT // If l3gatewayAnnotation.IPAddresses changed, we need to update the SNATs on the GR oldNATs := []*nbdb.NAT{} - if oldLogicalRouter != nil { - oldNATs, err = libovsdbops.GetRouterNATs(gw.nbClient, oldLogicalRouter) + var err error + if oldGWRouter != nil { + oldNATs, err = libovsdbops.GetRouterNATs(gw.nbClient, oldGWRouter) if err != nil && errors.Is(err, libovsdbclient.ErrNotFound) { - return fmt.Errorf("unable to get NAT entries for router on node %s: %w", nodeName, err) + return fmt.Errorf("unable to get NAT entries for router %s: %w", oldGWRouter.Name, err) } } @@ -789,7 +730,11 @@ func (gw *GatewayManager) GatewayInit( return fmt.Errorf("failed to update GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } } + return nil +} +func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []*net.IPNet, l3GatewayConfig *util.L3GatewayConfig, + externalIPs, gwLRPIPs []net.IP, gwRouter *nbdb.LogicalRouter) error { // REMOVEME(trozet) workaround - create join subnet SNAT to handle ICMP needs frag return var extIDs map[string]string if gw.netInfo.IsSecondary() { @@ -812,7 +757,7 @@ func (gw *GatewayManager) GatewayInit( nat := libovsdbops.BuildSNAT(&externalIP[0], joinIPNet, "", extIDs) joinNATs = append(joinNATs, nat) } - err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, joinNATs...) + err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, joinNATs...) if err != nil { return fmt.Errorf("failed to create SNAT rule for join subnet on router %s error: %v", gw.gwRouterName, err) } @@ -832,7 +777,7 @@ func (gw *GatewayManager) GatewayInit( nat = libovsdbops.BuildSNATWithMatch(&externalIP[0], entry, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, nats...) + err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, nats...) if err != nil { return fmt.Errorf("failed to update SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } @@ -842,15 +787,118 @@ func (gw *GatewayManager) GatewayInit( nat = libovsdbops.BuildSNATWithMatch(nil, logicalSubnet, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) nats = append(nats, nat) } - err := libovsdbops.DeleteNATs(gw.nbClient, gwRouter, nats...) + err = libovsdbops.DeleteNATs(gw.nbClient, gwRouter, nats...) if err != nil { return fmt.Errorf("failed to delete GW SNAT rule for pod on router %s error: %v", gw.gwRouterName, err) } } - if err := gw.cleanupStalePodSNATs(nodeName, l3GatewayConfig.IPAddresses, gwLRPIPs); err != nil { + if err = gw.cleanupStalePodSNATs(nodeName, l3GatewayConfig.IPAddresses, gwLRPIPs); err != nil { return fmt.Errorf("failed to sync stale SNATs on node %s: %v", nodeName, err) } + return nil +} + +// GatewayInit creates a gateway router for the local chassis. +// enableGatewayMTU enables options:gateway_mtu for gateway routers. +func (gw *GatewayManager) GatewayInit( + nodeName string, + clusterIPSubnet []*net.IPNet, + hostSubnets []*net.IPNet, + l3GatewayConfig *util.L3GatewayConfig, + gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, + externalIPs []net.IP, + enableGatewayMTU bool, +) error { + + // If l3gatewayAnnotation.IPAddresses changed, we need to update the perPodSNATs, + // so let's save the old value before we update the router for later use + var oldExtIPs []net.IP + oldLogicalRouter, err := libovsdbops.GetLogicalRouter(gw.nbClient, + &nbdb.LogicalRouter{ + Name: gw.gwRouterName, + }) + if err != nil && !errors.Is(err, libovsdbclient.ErrNotFound) { + return fmt.Errorf("failed in retrieving %s, error: %v", gw.gwRouterName, err) + } + + if oldLogicalRouter != nil && oldLogicalRouter.ExternalIDs != nil { + if physicalIPs, ok := oldLogicalRouter.ExternalIDs["physical_ips"]; ok { + oldExternalIPs := strings.Split(physicalIPs, ",") + oldExtIPs = make([]net.IP, len(oldExternalIPs)) + for i, oldExternalIP := range oldExternalIPs { + cidr := oldExternalIP + util.GetIPFullMaskString(oldExternalIP) + ip, _, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf("invalid cidr:%s error: %v", cidr, err) + } + oldExtIPs[i] = ip + } + } + } + + gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) + if err != nil { + return err + } + + if err = gw.createGWRouterPeerPort(nodeName); err != nil { + return err + } + + gwLRPIPs, err := gw.createGWRouterPort(hostSubnets, gwLRPJoinIPs, enableGatewayMTU, gwRouter) + if err != nil { + return err + } + + if err := gw.addExternalSwitch("", + l3GatewayConfig.InterfaceID, + gw.gwRouterName, + l3GatewayConfig.MACAddress.String(), + physNetName(gw.netInfo), + l3GatewayConfig.IPAddresses, + l3GatewayConfig.VLANID); err != nil { + return err + } + + if l3GatewayConfig.EgressGWInterfaceID != "" { + if err := gw.addExternalSwitch(types.EgressGWSwitchPrefix, + l3GatewayConfig.EgressGWInterfaceID, + gw.gwRouterName, + l3GatewayConfig.EgressGWMACAddress.String(), + types.PhysicalNetworkExGwName, + l3GatewayConfig.EgressGWIPAddresses, + nil); err != nil { + return err + } + } + + // Remove stale OVN resources with any old masquerade IP + if err := deleteStaleMasqueradeResources(gw.nbClient, gw.gwRouterName, nodeName, gw.watchFactory); err != nil { + return fmt.Errorf("failed to remove stale masquerade resources from northbound database: %w", err) + } + + if err := gateway.CreateDummyGWMacBindings(gw.nbClient, gw.gwRouterName, gw.netInfo); err != nil { + return err + } + + externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName + if err = gw.updateGWRouterStaticRoutes(clusterIPSubnet, drLRPIfAddrs, l3GatewayConfig, externalRouterPort, + gwRouter); err != nil { + return err + } + + if err = gw.updateClusterRouterStaticRoutes(hostSubnets, gwLRPIPs); err != nil { + return err + } + + if err = gw.syncNATsForGRIPChange(externalIPs, oldExtIPs, gwLRPIPs, gwRouter, oldLogicalRouter); err != nil { + return err + } + + if err = gw.updateGWRouterNAT(nodeName, clusterIPSubnet, l3GatewayConfig, externalIPs, gwLRPIPs, gwRouter); err != nil { + return err + } // recording gateway mode metrics here after gateway setup is done metrics.RecordEgressRoutingViaHost() From 414e29623852c97be3743d29a5022db59947ff7c Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 12:14:30 +0200 Subject: [PATCH 093/278] [lint] add make lint-fix command to fix lint issues Add exit 1 to fail make command when container_runtime is not present. Signed-off-by: Nadia Pinaeva --- go-controller/Makefile | 12 ++++++++++-- go-controller/hack/lint.sh | 10 +++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/go-controller/Makefile b/go-controller/Makefile index 9b2a59e595..4c86486ce2 100644 --- a/go-controller/Makefile +++ b/go-controller/Makefile @@ -93,9 +93,17 @@ clean: lint: ifeq ($(CONTAINER_RUNNABLE), 0) - @GOPATH=${GOPATH} ./hack/lint.sh $(CONTAINER_RUNTIME) + @GOPATH=${GOPATH} ./hack/lint.sh $(CONTAINER_RUNTIME) || { echo "lint failed! Try running 'make lint-fix'"; exit 1; } else - echo "linter can only be run within a container since it needs a specific golangci-lint version" + echo "linter can only be run within a container since it needs a specific golangci-lint version"; exit 1 +endif + +lint-fix: +ifeq ($(CONTAINER_RUNNABLE), 0) + @GOPATH=${GOPATH} ./hack/lint.sh $(CONTAINER_RUNTIME) fix || { echo "ERROR: lint fix failed! There is a bug that changes file ownership to root \ + when this happens. To fix it, simply run 'chown -R : *' from the repo root."; exit 1; } +else + echo "linter can only be run within a container since it needs a specific golangci-lint version"; exit 1 endif gofmt: diff --git a/go-controller/hack/lint.sh b/go-controller/hack/lint.sh index 5ac32e96dd..57f4695827 100755 --- a/go-controller/hack/lint.sh +++ b/go-controller/hack/lint.sh @@ -1,14 +1,18 @@ #!/usr/bin/env bash - VERSION=v1.60.3 +extra_flags="" if [ "$#" -ne 1 ]; then + if [ "$#" -eq 2 ] && [ "$2" == "fix" ]; then + extra_flags="--fix" + else echo "Expected command line argument - container runtime (docker/podman) got $# arguments: $@" exit 1 + fi fi $1 run --security-opt label=disable --rm \ -v ${HOME}/.cache/golangci-lint:/cache -e GOLANGCI_LINT_CACHE=/cache \ -v $(pwd):/app -w /app -e GO111MODULE=on docker.io/golangci/golangci-lint:${VERSION} \ golangci-lint run --verbose --print-resources-usage \ - --modules-download-mode=vendor --timeout=15m0s && \ - echo "lint OK!" + --modules-download-mode=vendor --timeout=15m0s ${extra_flags} && \ + echo "lint OK!" \ No newline at end of file From 77124b84a8b0507b1a11511a048ba143995bc8d8 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 11:08:55 +0200 Subject: [PATCH 094/278] [libovsdb/ops] Start adding option constants for ovn. Only added 4 constants for now that are related to gateway config. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/libovsdb/ops/options.go | 18 ++ .../pkg/ovn/base_network_controller.go | 4 +- .../pkg/ovn/base_network_controller_pods.go | 2 +- go-controller/pkg/ovn/egressgw_test.go | 101 +++---- go-controller/pkg/ovn/egressip_test.go | 254 +++++++++--------- go-controller/pkg/ovn/egressip_udn_l3_test.go | 39 +-- .../pkg/ovn/external_gateway_apb_test.go | 117 ++++---- go-controller/pkg/ovn/gateway.go | 8 +- go-controller/pkg/ovn/gateway_test.go | 9 +- go-controller/pkg/ovn/kubevirt_test.go | 8 +- go-controller/pkg/ovn/master_test.go | 6 +- go-controller/pkg/ovn/multihoming_test.go | 23 +- go-controller/pkg/ovn/multipolicy_test.go | 5 +- .../pkg/ovn/network_segmentation_test.go | 3 +- go-controller/pkg/ovn/pods_test.go | 12 +- .../secondary_layer2_network_controller.go | 4 +- ...econdary_layer2_network_controller_test.go | 3 +- ...econdary_layer3_network_controller_test.go | 8 +- .../pkg/ovn/topology/topologyfactory.go | 2 +- .../pkg/ovn/topology/topologyfactory_test.go | 3 +- .../ovn/zone_interconnect/zone_ic_handler.go | 12 +- 21 files changed, 335 insertions(+), 306 deletions(-) create mode 100644 go-controller/pkg/libovsdb/ops/options.go diff --git a/go-controller/pkg/libovsdb/ops/options.go b/go-controller/pkg/libovsdb/ops/options.go new file mode 100644 index 0000000000..d960062e92 --- /dev/null +++ b/go-controller/pkg/libovsdb/ops/options.go @@ -0,0 +1,18 @@ +package ops + +// This is a list of options used for OVN operations. +// Started with adding only some of them, feel free to continue extending this list. +// Eventually we expect to have no string options in the code. +const ( + // RequestedTnlKey can be used by LogicalSwitch, LogicalSwitchPort, LogicalRouter and LogicalRouterPort + // for distributed switches/routers + RequestedTnlKey = "requested-tnl-key" + // RequestedChassis can be used by LogicalSwitchPort and LogicalRouterPort. + // It specifies the chassis (by name or hostname) that is allowed to bind this port. + RequestedChassis = "requested-chassis" + // RouterPort can be used by LogicalSwitchPort to specify a connection to a logical router. + RouterPort = "router-port" + // GatewayMTU can be used by LogicalRouterPort to specify the MTU for the gateway port. + // If set, logical flows will be added to router pipeline to check packet length. + GatewayMTU = "gateway_mtu" +) diff --git a/go-controller/pkg/ovn/base_network_controller.go b/go-controller/pkg/ovn/base_network_controller.go index 02c82b172f..aba4b9ab04 100644 --- a/go-controller/pkg/ovn/base_network_controller.go +++ b/go-controller/pkg/ovn/base_network_controller.go @@ -425,7 +425,7 @@ func (bnc *BaseNetworkController) syncNodeClusterRouterPort(node *corev1.Node, h enableGatewayMTU := util.ParseNodeGatewayMTUSupport(node) if enableGatewayMTU { lrpOptions = map[string]string{ - "gateway_mtu": strconv.Itoa(config.Default.MTU), + libovsdbops.GatewayMTU: strconv.Itoa(config.Default.MTU), } } logicalRouterPort := nbdb.LogicalRouterPort{ @@ -560,7 +560,7 @@ func (bnc *BaseNetworkController) createNodeLogicalSwitch(nodeName string, hostS Type: "router", Addresses: []string{"router"}, Options: map[string]string{ - "router-port": types.RouterToSwitchPrefix + switchName, + libovsdbops.RouterPort: types.RouterToSwitchPrefix + switchName, }, } if bnc.IsDefault() { diff --git a/go-controller/pkg/ovn/base_network_controller_pods.go b/go-controller/pkg/ovn/base_network_controller_pods.go index 1147983e79..4d334cf6a3 100644 --- a/go-controller/pkg/ovn/base_network_controller_pods.go +++ b/go-controller/pkg/ovn/base_network_controller_pods.go @@ -535,7 +535,7 @@ func (bnc *BaseNetworkController) addLogicalPortToNetwork(pod *corev1.Pod, nadNa // rescheduled. if !config.Kubernetes.DisableRequestedChassis { - lsp.Options["requested-chassis"] = pod.Spec.NodeName + lsp.Options[libovsdbops.RequestedChassis] = pod.Spec.NodeName } // let's calculate if this network controller's role for this pod diff --git a/go-controller/pkg/ovn/egressgw_test.go b/go-controller/pkg/ovn/egressgw_test.go index fff6c32fa3..9696d4192b 100644 --- a/go-controller/pkg/ovn/egressgw_test.go +++ b/go-controller/pkg/ovn/egressgw_test.go @@ -19,6 +19,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/controller/apbroute" @@ -133,8 +134,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -169,8 +170,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -273,8 +274,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -309,8 +310,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -417,8 +418,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -463,8 +464,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -895,8 +896,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -966,8 +967,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1076,8 +1077,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1116,8 +1117,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1237,8 +1238,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1277,8 +1278,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1408,8 +1409,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1448,8 +1449,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1589,8 +1590,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1629,8 +1630,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1662,8 +1663,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1708,8 +1709,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1742,8 +1743,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1782,8 +1783,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1896,8 +1897,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2030,8 +2031,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2171,8 +2172,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2364,8 +2365,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "requested-chassis": "node1", - "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", + "iface-id-ver": "myPod", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2546,8 +2547,8 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "requested-chassis": "node1", - "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", + "iface-id-ver": "myPod", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, diff --git a/go-controller/pkg/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go index b05422bf65..8cbac3665c 100644 --- a/go-controller/pkg/ovn/egressip_test.go +++ b/go-controller/pkg/ovn/egressip_test.go @@ -314,7 +314,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -428,7 +428,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -524,7 +524,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -632,7 +632,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -756,7 +756,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -766,7 +766,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -919,7 +919,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -929,7 +929,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1076,7 +1076,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1086,7 +1086,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1096,7 +1096,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1302,7 +1302,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1312,7 +1312,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1322,7 +1322,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1482,7 +1482,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1492,7 +1492,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1502,7 +1502,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1712,7 +1712,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1722,7 +1722,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1732,7 +1732,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1894,7 +1894,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1904,7 +1904,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2055,7 +2055,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2065,7 +2065,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2269,7 +2269,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2279,7 +2279,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2500,7 +2500,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2510,7 +2510,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2721,7 +2721,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2731,7 +2731,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2867,7 +2867,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -2877,7 +2877,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -3008,7 +3008,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -3167,7 +3167,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -3177,7 +3177,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -3332,7 +3332,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -3342,7 +3342,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -5690,7 +5690,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -5700,7 +5700,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -5862,7 +5862,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -5872,7 +5872,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6027,7 +6027,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6037,7 +6037,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6502,7 +6502,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6512,7 +6512,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6623,7 +6623,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6633,7 +6633,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6710,7 +6710,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + nodeName, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + nodeName, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + nodeName, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6804,7 +6804,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + nodeName, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + nodeName, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + nodeName, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -6915,7 +6915,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7038,7 +7038,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7074,8 +7074,8 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" "namespace": egressPod1.Namespace, }, Options: map[string]string{ - "requested-chassis": egressPod1.Spec.NodeName, - "iface-id-ver": egressPod1.Name, + libovsdbops.RequestedChassis: egressPod1.Spec.NodeName, + "iface-id-ver": egressPod1.Name, }, PortSecurity: []string{podAddr}, } @@ -7136,7 +7136,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7271,7 +7271,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7391,7 +7391,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7427,8 +7427,8 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" "namespace": egressPod1.Namespace, }, Options: map[string]string{ - "requested-chassis": egressPod1.Spec.NodeName, - "iface-id-ver": egressPod1.Name, + libovsdbops.RequestedChassis: egressPod1.Spec.NodeName, + "iface-id-ver": egressPod1.Name, }, PortSecurity: []string{podAddr}, } @@ -7611,7 +7611,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7621,7 +7621,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -7854,8 +7854,8 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" "namespace": egressPod1.Namespace, }, Options: map[string]string{ - "requested-chassis": egressPod1.Spec.NodeName, - "iface-id-ver": egressPod1.Name, + libovsdbops.RequestedChassis: egressPod1.Spec.NodeName, + "iface-id-ver": egressPod1.Name, }, PortSecurity: []string{podAddr}, } @@ -8233,7 +8233,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node.Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8317,7 +8317,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node.Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8402,7 +8402,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node.Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node.Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8489,7 +8489,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8499,7 +8499,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8584,7 +8584,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8594,7 +8594,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8688,7 +8688,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8698,7 +8698,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8783,7 +8783,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8793,7 +8793,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8868,7 +8868,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8878,7 +8878,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -8998,7 +8998,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9008,7 +9008,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9143,7 +9143,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9153,7 +9153,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9271,7 +9271,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9355,7 +9355,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9437,7 +9437,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9649,7 +9649,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9659,7 +9659,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9743,7 +9743,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9753,7 +9753,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9827,7 +9827,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9837,7 +9837,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9921,7 +9921,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -9931,7 +9931,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10023,7 +10023,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10033,7 +10033,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10118,7 +10118,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10128,7 +10128,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10203,7 +10203,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10213,7 +10213,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10314,7 +10314,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10324,7 +10324,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10421,7 +10421,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10617,7 +10617,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -10910,7 +10910,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11241,7 +11241,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11251,7 +11251,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11361,7 +11361,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11371,7 +11371,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11493,7 +11493,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11503,7 +11503,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11683,7 +11683,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11693,7 +11693,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11831,7 +11831,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11841,7 +11841,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11956,7 +11956,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -11966,7 +11966,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12064,7 +12064,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Options: map[string]string{ "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, }, }, &nbdb.LogicalSwitch{ @@ -12195,7 +12195,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12329,7 +12329,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Options: map[string]string{ "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, }, }, &nbdb.LogicalSwitchPort{ @@ -12337,7 +12337,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12347,7 +12347,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12479,7 +12479,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12489,7 +12489,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12499,7 +12499,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12661,7 +12661,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12671,7 +12671,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12681,7 +12681,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12847,7 +12847,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node1Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node1Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12857,7 +12857,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node2Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node2Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -12867,7 +12867,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations cluster default network" Name: types.EXTSwitchToGWRouterPrefix + types.GWRouterPrefix + node3Name, Type: "router", Options: map[string]string{ - "router-port": types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + "GR_" + node3Name, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, diff --git a/go-controller/pkg/ovn/egressip_udn_l3_test.go b/go-controller/pkg/ovn/egressip_udn_l3_test.go index dc01cc84c4..0d311a96d5 100644 --- a/go-controller/pkg/ovn/egressip_udn_l3_test.go +++ b/go-controller/pkg/ovn/egressip_udn_l3_test.go @@ -22,6 +22,7 @@ import ( ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" egressipv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressip/v1" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/controller/udnenabledsvc" @@ -56,6 +57,8 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol eIP1Mark = 50000 eIP2Mark = 50001 secondaryNetworkID = "2" + //tnlKey = zoneinterconnect.BaseTransitSwitchTunnelKey + secondaryNetworkID + tnlKey = "16711685" ) getEgressIPStatusLen := func(egressIPName string) func() int { @@ -1375,7 +1378,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol UUID: "stor-" + networkName1_ + node1Name + "-UUID", Name: "stor-" + networkName1_ + node1Name, Addresses: []string{"router"}, - Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Options: map[string]string{libovsdbops.RouterPort: "rtos-" + networkName1_ + node1Name}, Type: "router", }, &nbdb.ACL{ @@ -1414,11 +1417,11 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol ovntypes.TopologyExternalID: ovntypes.Layer3Topology, ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary}, OtherConfig: map[string]string{ - "mcast_snoop": "true", - "mcast_querier": "false", - "mcast_flood_unregistered": "true", - "interconn-ts": networkName1_ + ovntypes.TransitSwitch, - "requested-tnl-key": "16711685", + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + libovsdbops.RequestedTnlKey: tnlKey, }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), @@ -1562,7 +1565,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol UUID: "stor-" + networkName1_ + node1Name + "-UUID", Name: "stor-" + networkName1_ + node1Name, Addresses: []string{"router"}, - Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Options: map[string]string{libovsdbops.RouterPort: "rtos-" + networkName1_ + node1Name}, Type: "router", }, &nbdb.ACL{ @@ -1601,11 +1604,11 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol ovntypes.TopologyExternalID: ovntypes.Layer3Topology, ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary}, OtherConfig: map[string]string{ - "mcast_snoop": "true", - "mcast_querier": "false", - "mcast_flood_unregistered": "true", - "interconn-ts": networkName1_ + ovntypes.TransitSwitch, - "requested-tnl-key": "16711685", + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + libovsdbops.RequestedTnlKey: tnlKey, }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), @@ -2725,7 +2728,7 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol UUID: "stor-" + networkName1_ + node1Name + "-UUID", Name: "stor-" + networkName1_ + node1Name, Addresses: []string{"router"}, - Options: map[string]string{"router-port": "rtos-" + networkName1_ + node1Name}, + Options: map[string]string{libovsdbops.RouterPort: "rtos-" + networkName1_ + node1Name}, Type: "router", }, &nbdb.ACL{ @@ -2765,11 +2768,11 @@ var _ = ginkgo.Describe("EgressIP Operations for user defined network with topol ovntypes.NetworkRoleExternalID: ovntypes.NetworkRolePrimary, }, OtherConfig: map[string]string{ - "mcast_snoop": "true", - "mcast_querier": "false", - "mcast_flood_unregistered": "true", - "interconn-ts": networkName1_ + ovntypes.TransitSwitch, - "requested-tnl-key": "16711685", + "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": networkName1_ + ovntypes.TransitSwitch, + libovsdbops.RequestedTnlKey: tnlKey, }, }, getNoReRouteReplyTrafficPolicyForController(netInfo.GetNetworkName(), DefaultNetworkControllerName), diff --git a/go-controller/pkg/ovn/external_gateway_apb_test.go b/go-controller/pkg/ovn/external_gateway_apb_test.go index 605066d7f6..b237174ae0 100644 --- a/go-controller/pkg/ovn/external_gateway_apb_test.go +++ b/go-controller/pkg/ovn/external_gateway_apb_test.go @@ -22,6 +22,7 @@ import ( adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1" adminpolicybasedrouteclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/clientset/versioned" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/controller/apbroute" @@ -176,8 +177,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -212,8 +213,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -322,8 +323,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -358,8 +359,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -461,8 +462,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -497,8 +498,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -604,8 +605,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -650,8 +651,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -812,8 +813,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -894,8 +895,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1037,8 +1038,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:49:a1:93:cb fd00:10:244:2::3"}, }, @@ -1165,8 +1166,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1236,8 +1237,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1337,8 +1338,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1373,8 +1374,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1479,8 +1480,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1515,8 +1516,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1641,8 +1642,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1677,8 +1678,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1795,8 +1796,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1831,8 +1832,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1858,8 +1859,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1900,8 +1901,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -1990,8 +1991,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2110,8 +2111,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2241,8 +2242,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2283,8 +2284,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2339,8 +2340,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "iface-id-ver": "myPod", - "requested-chassis": "node1", + "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, @@ -2538,8 +2539,8 @@ var _ = ginkgo.Describe("OVN for APB External Route Operations", func() { }, Name: "namespace1_myPod", Options: map[string]string{ - "requested-chassis": "node1", - "iface-id-ver": "myPod", + libovsdbops.RequestedChassis: "node1", + "iface-id-ver": "myPod", }, PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, }, diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 28c685398a..7c9a5834d9 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -342,7 +342,7 @@ func (gw *GatewayManager) createGWRouterPeerPort(nodeName string) error { Type: "router", Addresses: []string{"router"}, Options: map[string]string{ - "router-port": gwRouterPortName, + libovsdbops.RouterPort: gwRouterPortName, }, } if gw.netInfo.IsSecondary() { @@ -366,7 +366,7 @@ func (gw *GatewayManager) createGWRouterPeerPort(nodeName string) error { return fmt.Errorf("failed to fetch tunnelID annotation from the node %s for network %s, err: %w", nodeName, gw.netInfo.GetNetworkName(), err) } - logicalSwitchPort.Options["requested-tnl-key"] = strconv.Itoa(tunnelID) + logicalSwitchPort.Options[libovsdbops.RequestedTnlKey] = strconv.Itoa(tunnelID) } sw := nbdb.LogicalSwitch{Name: gw.joinSwitchName} err := libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(gw.nbClient, &sw, &logicalSwitchPort) @@ -399,7 +399,7 @@ func (gw *GatewayManager) createGWRouterPort(hostSubnets []*net.IPNet, gwLRPJoin var options map[string]string if enableGatewayMTU { options = map[string]string{ - "gateway_mtu": strconv.Itoa(config.Default.MTU), + libovsdbops.GatewayMTU: strconv.Itoa(config.Default.MTU), } } @@ -974,7 +974,7 @@ func (gw *GatewayManager) addExternalSwitch(prefix, interfaceID, gatewayRouter, Name: externalSwitchPortToRouter, Type: "router", Options: map[string]string{ - "router-port": externalRouterPort, + libovsdbops.RouterPort: externalRouterPort, // This option will program OVN to start sending GARPs for all external IPS // that the logical switch port has been configured to use. This is diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index 57f5fb4be2..a8e611119d 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -14,6 +14,7 @@ import ( utilnet "k8s.io/utils/net" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" @@ -87,7 +88,7 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN var options map[string]string if gatewayMTU != "" { options = map[string]string{ - "gateway_mtu": gatewayMTU, + libovsdbops.GatewayMTU: gatewayMTU, } } testData = append(testData, &nbdb.LogicalRouterPort{ @@ -344,7 +345,7 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN Type: "router", Addresses: []string{"router"}, Options: map[string]string{ - "router-port": gwRouterPort, + libovsdbops.RouterPort: gwRouterPort, }, }, &nbdb.LogicalSwitchPort{ @@ -352,7 +353,7 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN Name: externalSwitchPortToRouter, Type: "router", Options: map[string]string{ - "router-port": externalRouterPort, + libovsdbops.RouterPort: externalRouterPort, "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", }, @@ -1692,7 +1693,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { &nbdb.LogicalRouterPort{ UUID: types.GWRouterToExtSwitchPrefix + types.GWRouterPrefix + nodeName + "-UUID", Name: types.GWRouterToExtSwitchPrefix + types.GWRouterPrefix + nodeName, - Options: map[string]string{"gateway_mtu": "1400"}, + Options: map[string]string{libovsdbops.GatewayMTU: "1400"}, }, expectedGR, expectedOVNClusterRouter, diff --git a/go-controller/pkg/ovn/kubevirt_test.go b/go-controller/pkg/ovn/kubevirt_test.go index b7c80c6399..71293a0763 100644 --- a/go-controller/pkg/ovn/kubevirt_test.go +++ b/go-controller/pkg/ovn/kubevirt_test.go @@ -540,8 +540,8 @@ var _ = Describe("OVN Kubevirt Operations", func() { UUID: ovntypes.SwitchToRouterPrefix + t.nodeName + "-UUID", Type: "router", Options: map[string]string{ - "router-port": logicalRouterPort.Name, - "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), + libovsdbops.RouterPort: logicalRouterPort.Name, + "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), }, } logicalSwitch = &nbdb.LogicalSwitch{ @@ -600,8 +600,8 @@ var _ = Describe("OVN Kubevirt Operations", func() { UUID: ovntypes.SwitchToRouterPrefix + t.migrationTarget.nodeName + "-UUID", Type: "router", Options: map[string]string{ - "router-port": migrationTargetLRP.Name, - "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), + libovsdbops.RouterPort: migrationTargetLRP.Name, + "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), }, } migrationTargetLS = &nbdb.LogicalSwitch{ diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index 5c1c8b2c4b..aef5eb82b6 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -336,7 +336,7 @@ func addNodeLogicalFlowsHelper(testData []libovsdbtest.TestData, expectedOVNClus Networks: []string{node.NodeGWIP}, GatewayChassis: []string{chassisName + "-UUID"}, Options: map[string]string{ - "gateway_mtu": "1400", + libovsdbops.GatewayMTU: "1400", }, }) if serviceControllerEnabled { @@ -356,8 +356,8 @@ func addNodeLogicalFlowsHelper(testData []libovsdbtest.TestData, expectedOVNClus UUID: types.SwitchToRouterPrefix + node.Name + "-UUID", Type: "router", Options: map[string]string{ - "router-port": types.RouterToSwitchPrefix + node.Name, - "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), + libovsdbops.RouterPort: types.RouterToSwitchPrefix + node.Name, + "arp_proxy": kubevirt.ComposeARPProxyLSPOption(), }, Addresses: []string{"router"}, }) diff --git a/go-controller/pkg/ovn/multihoming_test.go b/go-controller/pkg/ovn/multihoming_test.go index a7b69c3fb9..bfcdcd1a75 100644 --- a/go-controller/pkg/ovn/multihoming_test.go +++ b/go-controller/pkg/ovn/multihoming_test.go @@ -16,6 +16,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" @@ -165,7 +166,7 @@ func (em *secondaryNetworkExpectationMachine) expectedLogicalSwitchesAndPortsWit delete(lsp.Options, "iface-id-ver") } if ocInfo.bnc.isLayer2Interconnect() { - lsp.Options["requested-tnl-key"] = "1" // hardcode this for now. + lsp.Options[libovsdbops.RequestedTnlKey] = "1" // hardcode this for now. } data = append(data, lsp) @@ -216,12 +217,12 @@ func (em *secondaryNetworkExpectationMachine) expectedLogicalSwitchesAndPortsWit "k8s.ovn.org/topology": ocInfo.bnc.TopologyType(), "k8s.ovn.org/network": ocInfo.bnc.GetNetworkName(), }, - Options: map[string]string{"router-port": ovntypes.RouterToSwitchPrefix + switchName}, + Options: map[string]string{libovsdbops.RouterPort: ovntypes.RouterToSwitchPrefix + switchName}, Type: "router", } data = append(data, lsp) if util.IsNetworkSegmentationSupportEnabled() && ocInfo.bnc.IsPrimaryNetwork() { - lsp.Options["requested-tnl-key"] = "25" + lsp.Options[libovsdbops.RequestedTnlKey] = "25" } nodeslsps[switchName] = append(nodeslsps[switchName], networkSwitchToGWRouterLSPUUID) @@ -291,11 +292,11 @@ func (em *secondaryNetworkExpectationMachine) expectedLogicalSwitchesAndPortsWit UUID: transitSwitchName + "-UUID", Name: transitSwitchName, OtherConfig: map[string]string{ - "mcast_querier": "false", - "mcast_flood_unregistered": "true", - "interconn-ts": transitSwitchName, - "requested-tnl-key": "16711685", - "mcast_snoop": "true", + "mcast_querier": "false", + "mcast_flood_unregistered": "true", + "interconn-ts": transitSwitchName, + libovsdbops.RequestedTnlKey: "16711685", + "mcast_snoop": "true", }, ExternalIDs: extIDs, }) @@ -332,8 +333,8 @@ func newExpectedSwitchPort(lspUUID string, portName string, podAddr string, pod ovntypes.TopologyExternalID: netInfo.TopologyType(), }, Options: map[string]string{ - "requested-chassis": pod.nodeName, - "iface-id-ver": pod.podName, + libovsdbops.RequestedChassis: pod.nodeName, + "iface-id-ver": pod.podName, }, PortSecurity: []string{podAddr}, } @@ -343,7 +344,7 @@ func newExpectedSwitchToRouterPort(lspUUID string, portName string, pod testPod, lrp := newExpectedSwitchPort(lspUUID, portName, "router", pod, netInfo, nad) lrp.ExternalIDs = nil lrp.Options = map[string]string{ - "router-port": "rtos-isolatednet_test-node", + libovsdbops.RouterPort: "rtos-isolatednet_test-node", } lrp.PortSecurity = nil lrp.Type = "router" diff --git a/go-controller/pkg/ovn/multipolicy_test.go b/go-controller/pkg/ovn/multipolicy_test.go index bb132d215a..095b35772f 100644 --- a/go-controller/pkg/ovn/multipolicy_test.go +++ b/go-controller/pkg/ovn/multipolicy_test.go @@ -20,6 +20,7 @@ import ( ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" @@ -150,8 +151,8 @@ func getExpectedDataPodsAndSwitchesForSecondaryNetwork(fakeOvn *FakeOVN, pods [] ovntypes.TopologyExternalID: ocInfo.bnc.TopologyType(), }, Options: map[string]string{ - "requested-chassis": pod.nodeName, - "iface-id-ver": pod.podName, + libovsdbops.RequestedChassis: pod.nodeName, + "iface-id-ver": pod.podName, }, PortSecurity: []string{podAddr}, diff --git a/go-controller/pkg/ovn/network_segmentation_test.go b/go-controller/pkg/ovn/network_segmentation_test.go index f52ad64c2f..cfcc0f7e83 100644 --- a/go-controller/pkg/ovn/network_segmentation_test.go +++ b/go-controller/pkg/ovn/network_segmentation_test.go @@ -10,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" ovntypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -84,7 +85,7 @@ var _ = ginkgo.Describe("OVN Pod Operations with network segmentation", func() { }, Options: map[string]string{ // check requested-chassis will be updated to correct t1.nodeName value - "requested-chassis": t1.nodeName, + libovsdbops.RequestedChassis: t1.nodeName, // check old value for iface-id-ver will be updated to pod.UID "iface-id-ver": "wrong_value", }, diff --git a/go-controller/pkg/ovn/pods_test.go b/go-controller/pkg/ovn/pods_test.go index 6045183157..cf1caae6e7 100644 --- a/go-controller/pkg/ovn/pods_test.go +++ b/go-controller/pkg/ovn/pods_test.go @@ -470,8 +470,8 @@ func getExpectedDataPodsSwitchesPortGroup(netInfo util.NetInfo, pods []testPod, "namespace": pod.namespace, }, Options: map[string]string{ - "requested-chassis": pod.nodeName, - "iface-id-ver": pod.podName, + libovsdbops.RequestedChassis: pod.nodeName, + "iface-id-ver": pod.podName, }, PortSecurity: []string{podAddr}, } @@ -2018,7 +2018,7 @@ var _ = ginkgo.Describe("OVN Pod Operations", func() { }, Options: map[string]string{ // check requested-chassis will be updated to correct t1.nodeName value - "requested-chassis": t2.nodeName, + libovsdbops.RequestedChassis: t2.nodeName, // check old value for iface-id-ver will be updated to pod.UID "iface-id-ver": "wrong_value", }, @@ -2033,7 +2033,7 @@ var _ = ginkgo.Describe("OVN Pod Operations", func() { "namespace": t2.namespace, }, Options: map[string]string{ - "requested-chassis": t2.nodeName, + libovsdbops.RequestedChassis: t2.nodeName, //"iface-id-ver": is empty to check that it won't be set on update }, PortSecurity: []string{fmt.Sprintf("%s %s", t2.podMAC, t2.podIP)}, @@ -2048,7 +2048,7 @@ var _ = ginkgo.Describe("OVN Pod Operations", func() { }, Options: map[string]string{ // check requested-chassis will be updated to correct t1.nodeName value - "requested-chassis": t3.nodeName, + libovsdbops.RequestedChassis: t3.nodeName, // check old value for iface-id-ver will be updated to pod.UID "iface-id-ver": "wrong_value", }, @@ -2218,7 +2218,7 @@ var _ = ginkgo.Describe("OVN Pod Operations", func() { }, Options: map[string]string{ // check requested-chassis will be updated to correct t1.nodeName value - "requested-chassis": t1.nodeName, + libovsdbops.RequestedChassis: t1.nodeName, // check old value for iface-id-ver will be updated to pod.UID "iface-id-ver": "wrong_value", }, diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index fd15ab6684..d5fd185c9a 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -725,8 +725,8 @@ func (oc *SecondaryLayer2NetworkController) addPortForRemoteNodeGR(node *corev1. node.Name, oc.GetNetworkName(), err) } logicalSwitchPort.Options = map[string]string{ - "requested-tnl-key": strconv.Itoa(tunnelID), - "requested-chassis": node.Name, + libovsdbops.RequestedTnlKey: strconv.Itoa(tunnelID), + libovsdbops.RequestedChassis: node.Name, } sw := nbdb.LogicalSwitch{Name: oc.GetNetworkScopedSwitchName(types.OVNLayer2Switch)} err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitch(oc.nbClient, &sw, &logicalSwitchPort) diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller_test.go b/go-controller/pkg/ovn/secondary_layer2_network_controller_test.go index 91fc80bc6e..1079a14198 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller_test.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/utils/ptr" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" @@ -605,7 +606,7 @@ func expectedLayer2EgressEntities(netInfo util.NetInfo, gwConfig util.L3GatewayC } func expectedGWToNetworkSwitchRouterPort(name string, netInfo util.NetInfo, networks ...*net.IPNet) *nbdb.LogicalRouterPort { - options := map[string]string{"gateway_mtu": fmt.Sprintf("%d", 1400)} + options := map[string]string{libovsdbops.GatewayMTU: fmt.Sprintf("%d", 1400)} lrp := expectedLogicalRouterPort(name, netInfo, options, networks...) if config.IPv6Mode { diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller_test.go b/go-controller/pkg/ovn/secondary_layer3_network_controller_test.go index 077a6fd822..163d06dfd9 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller_test.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller_test.go @@ -762,7 +762,7 @@ func expectedGatewayChassis(nodeName string, netInfo util.NetInfo, gwConfig util func expectedGRToJoinSwitchLRP(gatewayRouterName string, gwRouterLRPIP *net.IPNet, netInfo util.NetInfo) *nbdb.LogicalRouterPort { lrpName := fmt.Sprintf("%s%s", types.GWRouterToJoinSwitchPrefix, gatewayRouterName) - options := map[string]string{"gateway_mtu": fmt.Sprintf("%d", 1400)} + options := map[string]string{libovsdbops.GatewayMTU: fmt.Sprintf("%d", 1400)} return expectedLogicalRouterPort(lrpName, netInfo, options, gwRouterLRPIP) } @@ -836,7 +836,7 @@ func expectedLayer3EgressEntities(netInfo util.NetInfo, gwConfig util.L3GatewayC Networks: []string{"192.168.1.1/24"}, MAC: "0a:58:c0:a8:01:01", GatewayChassis: []string{gatewayChassisUUID}, - Options: map[string]string{"gateway_mtu": "1400"}, + Options: map[string]string{libovsdbops.GatewayMTU: "1400"}, }, expectedGRStaticRoute(staticRouteUUID1, nodeSubnet.String(), lrsrNextHop, &nbdb.LogicalRouterStaticRoutePolicySrcIP, nil, netInfo), expectedGRStaticRoute(staticRouteUUID2, gwRouterJoinIPAddress().IP.String(), gwRouterJoinIPAddress().IP.String(), nil, nil, netInfo), @@ -973,7 +973,7 @@ func externalSwitchRouterPortOptions(gatewayRouterName string) map[string]string return map[string]string{ "nat-addresses": "router", "exclude-lb-vips-from-garp": "true", - "router-port": types.GWRouterToExtSwitchPrefix + gatewayRouterName, + libovsdbops.RouterPort: types.GWRouterToExtSwitchPrefix + gatewayRouterName, } } @@ -992,7 +992,7 @@ func expectedJoinSwitchAndLSPs(netInfo util.NetInfo, nodeName string) []libovsdb Name: types.JoinSwitchToGWRouterPrefix + gwRouterName, Addresses: []string{"router"}, ExternalIDs: standardNonDefaultNetworkExtIDs(netInfo), - Options: map[string]string{"router-port": types.GWRouterToJoinSwitchPrefix + gwRouterName}, + Options: map[string]string{libovsdbops.RouterPort: types.GWRouterToJoinSwitchPrefix + gwRouterName}, Type: "router", }, } diff --git a/go-controller/pkg/ovn/topology/topologyfactory.go b/go-controller/pkg/ovn/topology/topologyfactory.go index d9a1980cbc..b20743a242 100644 --- a/go-controller/pkg/ovn/topology/topologyfactory.go +++ b/go-controller/pkg/ovn/topology/topologyfactory.go @@ -130,7 +130,7 @@ func (gtf *GatewayTopologyFactory) NewJoinSwitch( Name: drSwitchPort, Type: "router", Options: map[string]string{ - "router-port": drRouterPort, + libovsdbops.RouterPort: drRouterPort, }, Addresses: []string{"router"}, } diff --git a/go-controller/pkg/ovn/topology/topologyfactory_test.go b/go-controller/pkg/ovn/topology/topologyfactory_test.go index af8a036d6f..4d189e030a 100644 --- a/go-controller/pkg/ovn/topology/topologyfactory_test.go +++ b/go-controller/pkg/ovn/topology/topologyfactory_test.go @@ -9,6 +9,7 @@ import ( ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" libovsdbtest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/libovsdb" ovntypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -237,7 +238,7 @@ func expectedLogicalSwitchPort(portName string) *nbdb.LogicalSwitchPort { Addresses: []string{"router"}, Name: portName, Options: map[string]string{ - "router-port": "rtoj-mydearrouter", + libovsdbops.RouterPort: "rtoj-mydearrouter", }, ParentName: nil, PortSecurity: nil, diff --git a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go index 9d088e6659..ab366c7931 100644 --- a/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go +++ b/go-controller/pkg/ovn/zone_interconnect/zone_ic_handler.go @@ -360,7 +360,7 @@ func (zic *ZoneInterconnectHandler) AddTransitPortConfig(remote bool, podAnnotat if port.Options == nil { port.Options = map[string]string{} } - port.Options["requested-tnl-key"] = strconv.Itoa(podAnnotation.TunnelID) + port.Options[libovsdbops.RequestedTnlKey] = strconv.Itoa(podAnnotation.TunnelID) if remote { port.Type = lportTypeRemote @@ -375,7 +375,7 @@ func (zic *ZoneInterconnectHandler) addTransitSwitchConfig(sw *nbdb.LogicalSwitc } sw.OtherConfig["interconn-ts"] = sw.Name - sw.OtherConfig["requested-tnl-key"] = strconv.Itoa(BaseTransitSwitchTunnelKey + networkID) + sw.OtherConfig[libovsdbops.RequestedTnlKey] = strconv.Itoa(BaseTransitSwitchTunnelKey + networkID) sw.OtherConfig["mcast_snoop"] = "true" sw.OtherConfig["mcast_querier"] = "false" sw.OtherConfig["mcast_flood_unregistered"] = "true" @@ -420,8 +420,8 @@ func (zic *ZoneInterconnectHandler) createLocalZoneNodeResources(node *corev1.No } lspOptions := map[string]string{ - "router-port": logicalRouterPortName, - "requested-tnl-key": strconv.Itoa(nodeID), + libovsdbops.RouterPort: logicalRouterPortName, + libovsdbops.RequestedTnlKey: strconv.Itoa(nodeID), } // Store the node name in the external_ids column for book keeping @@ -459,8 +459,8 @@ func (zic *ZoneInterconnectHandler) createRemoteZoneNodeResources(node *corev1.N } lspOptions := map[string]string{ - "requested-tnl-key": strconv.Itoa(nodeID), - "requested-chassis": node.Name, + libovsdbops.RequestedTnlKey: strconv.Itoa(nodeID), + libovsdbops.RequestedChassis: node.Name, } // Store the node name in the external_ids column for book keeping externalIDs := map[string]string{ From 4834e3deebff6b8a32fb94ca2cd95448c48dd4fd Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Mon, 14 Jul 2025 19:10:40 +0200 Subject: [PATCH 095/278] Fix log, reuse existing function in GetNetworkScopedSwitchName. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 2 +- go-controller/pkg/util/multi_network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 7c9a5834d9..6b69507c3d 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -354,7 +354,7 @@ func (gw *GatewayManager) createGWRouterPeerPort(nodeName string) error { if gw.netInfo.TopologyType() == types.Layer2Topology { node, err := gw.watchFactory.GetNode(nodeName) if err != nil { - return fmt.Errorf("failed to fetch node %s from watch factory %w", node, err) + return fmt.Errorf("failed to fetch node %s from watch factory %w", node.Name, err) } tunnelID, err := util.ParseUDNLayer2NodeGRLRPTunnelIDs(node, gw.netInfo.GetNetworkName()) if err != nil { diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index b4a5bd4b98..fd91edd3be 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -709,7 +709,7 @@ func (nInfo *secondaryNetInfo) GetNetworkScopedGWRouterName(nodeName string) str func (nInfo *secondaryNetInfo) GetNetworkScopedSwitchName(nodeName string) string { // In Layer2Topology there is just one global switch if nInfo.TopologyType() == types.Layer2Topology { - return fmt.Sprintf("%s%s", nInfo.getPrefix(), types.OVNLayer2Switch) + return nInfo.GetNetworkScopedName(types.OVNLayer2Switch) } return nInfo.GetNetworkScopedName(nodeName) } From 1d9b4cf0911a5ffbc8563d60ec2fcba696f66d13 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 09:32:31 +0200 Subject: [PATCH 096/278] [pod SNAT] reuse code around pod SNAT creation. Remove unused "match" parameter from addOrUpdatePodSNATOps. Rename logicalRouter/router to gwRouter. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/egressgw.go | 19 ++++++++----------- go-controller/pkg/ovn/egressip.go | 8 ++++---- go-controller/pkg/ovn/pods.go | 2 +- .../secondary_layer2_network_controller.go | 12 ++++++------ 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/go-controller/pkg/ovn/egressgw.go b/go-controller/pkg/ovn/egressgw.go index 1f28955295..2b8e939585 100644 --- a/go-controller/pkg/ovn/egressgw.go +++ b/go-controller/pkg/ovn/egressgw.go @@ -649,15 +649,12 @@ func deletePodSNATOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, gwR // addOrUpdatePodSNAT adds or updates per pod SNAT rules towards the nodeIP that are applied to the GR where the pod resides // used when disableSNATMultipleGWs=true func addOrUpdatePodSNAT(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet) error { - nats, err := buildPodSNAT(extIPs, podIfAddrs, "") + ops, err := addOrUpdatePodSNATOps(nbClient, gwRouterName, extIPs, podIfAddrs, nil) if err != nil { return err } - logicalRouter := nbdb.LogicalRouter{ - Name: gwRouterName, - } - if err := libovsdbops.CreateOrUpdateNATs(nbClient, &logicalRouter, nats...); err != nil { - return fmt.Errorf("failed to update SNAT for pods of router %s: %v", logicalRouter.Name, err) + if _, err = libovsdbops.TransactAndCheck(nbClient, ops); err != nil { + return fmt.Errorf("failed to update SNAT for pods of router %s: %v", gwRouterName, err) } return nil } @@ -665,14 +662,14 @@ func addOrUpdatePodSNAT(nbClient libovsdbclient.Client, gwRouterName string, ext // addOrUpdatePodSNATOps returns the operation that adds or updates per pod SNAT rules towards the nodeIP that are // applied to the GR where the pod resides // used when disableSNATMultipleGWs=true -func addOrUpdatePodSNATOps(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet, match string, ops []ovsdb.Operation) ([]ovsdb.Operation, error) { - router := &nbdb.LogicalRouter{Name: gwRouterName} - nats, err := buildPodSNAT(extIPs, podIfAddrs, match) +func addOrUpdatePodSNATOps(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet, ops []ovsdb.Operation) ([]ovsdb.Operation, error) { + gwRouter := &nbdb.LogicalRouter{Name: gwRouterName} + nats, err := buildPodSNAT(extIPs, podIfAddrs, "") if err != nil { return nil, err } - if ops, err = libovsdbops.CreateOrUpdateNATsOps(nbClient, ops, router, nats...); err != nil { - return nil, fmt.Errorf("failed to update SNAT for pods of router: %s, error: %v", gwRouterName, err) + if ops, err = libovsdbops.CreateOrUpdateNATsOps(nbClient, ops, gwRouter, nats...); err != nil { + return nil, fmt.Errorf("failed to create ops to update SNAT for pods of router: %s, error: %v", gwRouterName, err) } return ops, nil } diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index fff34928c6..5f50cefb95 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -2594,7 +2594,7 @@ func (e *EgressIPController) addExternalGWPodSNATOps(ni util.NetInfo, ops []ovsd if err != nil { return nil, err } - ops, err = addOrUpdatePodSNATOps(e.nbClient, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podIPs, "", ops) + ops, err = addOrUpdatePodSNATOps(e.nbClient, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podIPs, ops) if err != nil { return nil, err } @@ -3639,12 +3639,12 @@ func (e *EgressIPController) createNATRuleOps(ni util.NetInfo, ops []ovsdb.Opera nats = append(nats, nat) } } - router := &nbdb.LogicalRouter{ + gwRouter := &nbdb.LogicalRouter{ Name: ni.GetNetworkScopedGWRouterName(status.Node), } - ops, err = libovsdbops.CreateOrUpdateNATsOps(e.nbClient, ops, router, nats...) + ops, err = libovsdbops.CreateOrUpdateNATsOps(e.nbClient, ops, gwRouter, nats...) if err != nil { - return nil, fmt.Errorf("unable to create snat rules, for router: %s, error: %v", router.Name, err) + return nil, fmt.Errorf("unable to create snat rules, for router: %s, error: %v", gwRouter.Name, err) } return ops, nil } diff --git a/go-controller/pkg/ovn/pods.go b/go-controller/pkg/ovn/pods.go index 9f39376d9e..5c3478f3cb 100644 --- a/go-controller/pkg/ovn/pods.go +++ b/go-controller/pkg/ovn/pods.go @@ -315,7 +315,7 @@ func (oc *DefaultNetworkController) addLogicalPort(pod *corev1.Pod) (err error) // namespace annotations to go through external egress router if extIPs, err := getExternalIPsGR(oc.watchFactory, pod.Spec.NodeName); err != nil { return err - } else if ops, err = addOrUpdatePodSNATOps(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podAnnotation.IPs, "", ops); err != nil { + } else if ops, err = addOrUpdatePodSNATOps(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podAnnotation.IPs, ops); err != nil { return err } } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index d5fd185c9a..d43b853008 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -757,8 +757,8 @@ func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Node) e // externalIP = "169.254.0.12"; which is the masqueradeIP for this L2 UDN // so all in all we want to condionally SNAT all packets that are coming from pods hosted on this node, // which are leaving via UDN's mpX interface to the UDN's masqueradeIP. -func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, routerName string) error { - outputPort := types.GWRouterToJoinSwitchPrefix + routerName +func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, gwRouterName string) error { + outputPort := types.GWRouterToJoinSwitchPrefix + gwRouterName nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort) if err != nil { return err @@ -766,12 +766,12 @@ func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localP if len(nats) == 0 { return nil // nothing to do } - router := &nbdb.LogicalRouter{ - Name: routerName, + gwRouter := &nbdb.LogicalRouter{ + Name: gwRouterName, } - if err := libovsdbops.CreateOrUpdateNATs(oc.nbClient, router, nats...); err != nil { + if err := libovsdbops.CreateOrUpdateNATs(oc.nbClient, gwRouter, nats...); err != nil { return fmt.Errorf("failed to update SNAT for cluster on router: %q for network %q, error: %w", - routerName, oc.GetNetworkName(), err) + gwRouterName, oc.GetNetworkName(), err) } return nil } From cc8e9c83c0efa05db7f72e57760b06ce2af5bb76 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 10:50:12 +0200 Subject: [PATCH 097/278] [master] reuse SecondaryL3GatewayConfig for master gateway configuration Move gateway-related info gathering to nodeGatewayConfig function similar to secondary controllers. syncNodeGateway was always called with hostSubnets=nil, remove. Update UT: master_test was always passing hostAddrs to syncDefaultGatewayLogicalNetwork, but the real code only sets them for shared GW mode. Update addNodeLogicalFlowsHelper to account for that, move its call from BeforeEach to the test, as GW mode is not set in BeforeEach. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/hybrid_test.go | 32 ++------ go-controller/pkg/ovn/master.go | 66 ++++++++++------ go-controller/pkg/ovn/master_test.go | 75 ++++++++++++------- go-controller/pkg/ovn/ovn.go | 35 ++++----- .../secondary_layer3_network_controller.go | 13 +--- 5 files changed, 111 insertions(+), 110 deletions(-) diff --git a/go-controller/pkg/ovn/hybrid_test.go b/go-controller/pkg/ovn/hybrid_test.go index fab60e2c3b..4b01354429 100644 --- a/go-controller/pkg/ovn/hybrid_test.go +++ b/go-controller/pkg/ovn/hybrid_test.go @@ -335,10 +335,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) f, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -429,7 +425,7 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { }, 2).Should(gomega.HaveKeyWithValue(hotypes.HybridOverlayDRIP, nodeHOIP)) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) + err = clusterController.syncNodeGateway(updatedNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) var clusterSubnets []*net.IPNet @@ -617,10 +613,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) f, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -705,7 +697,7 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { setupCOPP := true setupClusterController(clusterController, setupCOPP) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) + err = clusterController.syncNodeGateway(updatedNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) //assuming all the pods have finished processing @@ -826,10 +818,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) f, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -912,7 +900,7 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { }, 2).Should(gomega.HaveKeyWithValue(hotypes.HybridOverlayDRMAC, nodeHOMAC)) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) + err = clusterController.syncNodeGateway(updatedNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) var clusterSubnets []*net.IPNet @@ -1124,10 +1112,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode1.Name, metav1.GetOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) f, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1214,8 +1198,7 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { }, 2).Should(gomega.HaveKeyWithValue(hotypes.HybridOverlayDRMAC, nodeHOMAC)) //ensure hybrid overlay elements have been added - subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) + err = clusterController.syncNodeGateway(updatedNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Eventually(func() ([]*nbdb.LogicalRouterStaticRoute, error) { @@ -1337,10 +1320,6 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { updatedNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - hostAddrs, err := util.ParseNodeHostCIDRsDropNetMask(updatedNode) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) f, err = factory.NewMasterWatchFactory(fakeClient.GetMasterClientset()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1419,8 +1398,7 @@ var _ = ginkgo.Describe("Hybrid SDN Master Operations", func() { return updatedNode.Annotations, nil }, 2).Should(gomega.HaveKeyWithValue(hotypes.HybridOverlayDRMAC, nodeHOMAC)) - subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = clusterController.syncDefaultGatewayLogicalNetwork(updatedNode, l3GatewayConfig, []*net.IPNet{subnet}, hostAddrs.UnsortedList()) + err = clusterController.syncNodeGateway(updatedNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // switch the node to a ovn node diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index f85cdb75c3..13d5283012 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -33,6 +33,15 @@ const ( OvnNodeAnnotationRetryTimeout = 1 * time.Second ) +type L3GatewayConfig struct { + config *util.L3GatewayConfig + hostSubnets []*net.IPNet + clusterSubnets []*net.IPNet + gwLRPJoinIPs []*net.IPNet + hostAddrs []string + externalIPs []net.IP +} + // SetupMaster creates the central router and load-balancers for the network func (oc *DefaultNetworkController) SetupMaster() error { // Create default Control Plane Protection (COPP) entry for routers @@ -82,17 +91,35 @@ func (oc *DefaultNetworkController) syncNodeManagementPortDefault(node *corev1.N return err } -func (oc *DefaultNetworkController) syncDefaultGatewayLogicalNetwork( - node *corev1.Node, - l3GatewayConfig *util.L3GatewayConfig, - hostSubnets []*net.IPNet, - hostAddrs []string, -) error { +func (oc *DefaultNetworkController) nodeGatewayConfig(node *corev1.Node) (*L3GatewayConfig, error) { + l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) + if err != nil { + return nil, err + } + + externalIPs := make([]net.IP, len(l3GatewayConfig.IPAddresses)) + for i, ip := range l3GatewayConfig.IPAddresses { + externalIPs[i] = ip.IP + } + + var hostAddrs []string + if config.Gateway.Mode == config.GatewayModeShared { + hostAddrs, err = util.GetNodeHostAddrs(node) + if err != nil && !util.IsAnnotationNotSetError(err) { + return nil, fmt.Errorf("failed to get host CIDRs for node: %s: %v", node.Name, err) + } + } + var clusterSubnets []*net.IPNet for _, clusterSubnet := range config.Default.ClusterSubnets { clusterSubnets = append(clusterSubnets, clusterSubnet.CIDR) } + hostSubnets, err := util.ParseNodeHostSubnetAnnotation(node, oc.GetNetworkName()) + if err != nil { + return nil, err + } + gwLRPIPs, err := util.ParseNodeGatewayRouterJoinAddrs(node, oc.GetNetworkName()) if err != nil { if util.IsAnnotationNotSetError(err) { @@ -101,26 +128,19 @@ func (oc *DefaultNetworkController) syncDefaultGatewayLogicalNetwork( var err1 error gwLRPIPs, err1 = util.ParseNodeGatewayRouterLRPAddrs(node) if err1 != nil { - return fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) + return nil, fmt.Errorf("failed to get join switch port IP address for node %s: %v/%v", node.Name, err, err1) } } } - externalIPs := make([]net.IP, len(l3GatewayConfig.IPAddresses)) - for i, ip := range l3GatewayConfig.IPAddresses { - externalIPs[i] = ip.IP - } - - return oc.newGatewayManager(node.Name).syncGatewayLogicalNetwork( - node, - l3GatewayConfig, - hostSubnets, - hostAddrs, - clusterSubnets, - gwLRPIPs, - oc.ovnClusterLRPToJoinIfAddrs, - externalIPs, - ) + return &L3GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterSubnets, + gwLRPJoinIPs: gwLRPIPs, + hostAddrs: hostAddrs, + externalIPs: externalIPs, + }, nil } func (oc *DefaultNetworkController) addNode(node *corev1.Node) ([]*net.IPNet, error) { @@ -596,7 +616,7 @@ func (oc *DefaultNetworkController) addUpdateLocalNodeEvent(node *corev1.Node, n } if nSyncs.syncGw { - err := oc.syncNodeGateway(node, nil) + err := oc.syncNodeGateway(node) if err != nil { errs = append(errs, err) oc.gatewaysFailed.Store(node.Name, true) diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index aef5eb82b6..b7b902f740 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -383,14 +383,19 @@ func addNodeLogicalFlowsHelper(testData []libovsdbtest.TestData, expectedOVNClus Nexthops: []string{node.NodeMgmtPortIP}, Priority: intPriority, }) - testData = append(testData, &nbdb.LogicalRouterPolicy{ - UUID: "policy-based-route-2-UUID", - Action: nbdb.LogicalRouterPolicyActionReroute, - Match: matchStr2, - Nexthops: []string{node.NodeMgmtPortIP}, - Priority: intPriority, - }) - expectedOVNClusterRouter.Policies = append(expectedOVNClusterRouter.Policies, []string{"policy-based-route-1-UUID", "policy-based-route-2-UUID"}...) + expectedOVNClusterRouter.Policies = append(expectedOVNClusterRouter.Policies, "policy-based-route-1-UUID") + + if config.Gateway.Mode == config.GatewayModeShared { + testData = append(testData, &nbdb.LogicalRouterPolicy{ + UUID: "policy-based-route-2-UUID", + Action: nbdb.LogicalRouterPolicyActionReroute, + Match: matchStr2, + Nexthops: []string{node.NodeMgmtPortIP}, + Priority: intPriority, + }) + expectedOVNClusterRouter.Policies = append(expectedOVNClusterRouter.Policies, "policy-based-route-2-UUID") + + } testData = append(testData, expectedClusterPortGroup) testData = append(testData, expectedClusterRouterPortGroup) return testData @@ -1093,9 +1098,6 @@ var _ = ginkgo.Describe("Default network controller operations", func() { }() oc.SCTPSupport = true - - expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, - expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) }) ginkgo.AfterEach(func() { @@ -1113,7 +1115,11 @@ var _ = ginkgo.Describe("Default network controller operations", func() { clusterSubnets := startFakeController(oc, wg) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = oc.syncDefaultGatewayLogicalNetwork(&testNode, l3GatewayConfig, []*net.IPNet{subnet}, []string{node1.NodeIP}) + + // Get node with the latest annotations set + testNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) retry.InitRetryObjWithAdd(testNode, testNode.Name, oc.retryNodes) gomega.Expect(retry.RetryObjsLen(oc.retryNodes)).To(gomega.Equal(1)) @@ -1129,6 +1135,8 @@ var _ = ginkgo.Describe("Default network controller operations", func() { gomega.Expect(retry.CheckRetryObj(testNode.Name, oc.retryNodes)).To(gomega.BeFalse()) skipSnat := false + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1171,11 +1179,13 @@ var _ = ginkgo.Describe("Default network controller operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) err = oc.syncNodeManagementPortDefault(node, node.Name, []*net.IPNet{subnet}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = oc.syncDefaultGatewayLogicalNetwork(node, l3GatewayConfig, []*net.IPNet{subnet}, []string{node1.NodeIP}) + err = oc.syncNodeGateway(node) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By("Stale route should have been removed") skipSnat := false + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1202,10 +1212,15 @@ var _ = ginkgo.Describe("Default network controller operations", func() { clusterSubnets := startFakeController(oc, wg) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = oc.syncDefaultGatewayLogicalNetwork(&testNode, l3GatewayConfig, []*net.IPNet{subnet}, []string{node1.NodeIP}) + // Get node with the latest annotations set + testNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) skipSnat := false + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1260,7 +1275,10 @@ var _ = ginkgo.Describe("Default network controller operations", func() { // ensure the stale SNAT's are cleaned up gomega.Expect(oc.StartServiceController(wg, false)).To(gomega.Succeed()) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - err = oc.syncDefaultGatewayLogicalNetwork(&testNode, l3GatewayConfig, []*net.IPNet{subnet}, []string{node1.NodeIP}) + // Get node with the latest annotations set + testNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) skipSnat := config.Gateway.DisableSNATMultipleGWs || oc.isPodNetworkAdvertisedAtNode(node1.Name) @@ -1268,6 +1286,8 @@ var _ = ginkgo.Describe("Default network controller operations", func() { for _, clusterSubnet := range config.Default.ClusterSubnets { clusterSubnets = append(clusterSubnets, clusterSubnet.CIDR) } + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1349,6 +1369,8 @@ var _ = ginkgo.Describe("Default network controller operations", func() { skipSnat := false subnet := ovntest.MustParseIPNet(node1.NodeSubnet) + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1394,6 +1416,8 @@ var _ = ginkgo.Describe("Default network controller operations", func() { skipSnat := false subnet := ovntest.MustParseIPNet(node1.NodeSubnet) + expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, + expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, @@ -1459,13 +1483,10 @@ var _ = ginkgo.Describe("Default network controller operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) startFakeController(oc, wg) - subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - nodeHostAddrs := []string{} - for _, nodeHostCIDR := range nodeHostCIDRs.UnsortedList() { - ip, _, _ := net.ParseCIDR(nodeHostCIDR) - nodeHostAddrs = append(nodeHostAddrs, ip.String()) - } - err = oc.syncDefaultGatewayLogicalNetwork(&testNode, l3GatewayConfig, []*net.IPNet{subnet}, nodeHostAddrs) + // Get node with the latest annotations set + testNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // inject transient problem, nbdb is down @@ -1558,13 +1579,9 @@ var _ = ginkgo.Describe("Default network controller operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) startFakeController(oc, wg) - subnet := ovntest.MustParseIPNet(node1.NodeSubnet) - nodeHostAddrs := []string{} - for _, nodeHostCIDR := range nodeHostCIDRs.UnsortedList() { - ip, _, _ := net.ParseCIDR(nodeHostCIDR) - nodeHostAddrs = append(nodeHostAddrs, ip.String()) - } - err = oc.syncDefaultGatewayLogicalNetwork(&testNode, l3GatewayConfig, []*net.IPNet{subnet}, nodeHostAddrs) + testNode, err := fakeClient.KubeClient.CoreV1().Nodes().Get(context.TODO(), testNode.Name, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // Delete the node's gateway Logical Router Port to force node delete to handle a diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index c6a53ee34e..d035fdd299 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -373,32 +373,27 @@ func (oc *DefaultNetworkController) WatchEgressIPPods() error { } // syncNodeGateway ensures a node's gateway router is configured -func (oc *DefaultNetworkController) syncNodeGateway(node *corev1.Node, hostSubnets []*net.IPNet) error { - l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) +func (oc *DefaultNetworkController) syncNodeGateway(node *corev1.Node) error { + gwConfig, err := oc.nodeGatewayConfig(node) if err != nil { - return err + return fmt.Errorf("error getting gateway config for node %s: %v", node.Name, err) } - if hostSubnets == nil { - hostSubnets, err = util.ParseNodeHostSubnetAnnotation(node, ovntypes.DefaultNetworkName) - if err != nil { - return err - } - } - - if l3GatewayConfig.Mode == config.GatewayModeDisabled { + if gwConfig.config.Mode == config.GatewayModeDisabled { if err := oc.newGatewayManager(node.Name).Cleanup(); err != nil { return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) } - } else if hostSubnets != nil { - var hostAddrs []string - if config.Gateway.Mode == config.GatewayModeShared { - hostAddrs, err = util.GetNodeHostAddrs(node) - if err != nil && !util.IsAnnotationNotSetError(err) { - return fmt.Errorf("failed to get host CIDRs for node: %s: %v", node.Name, err) - } - } - if err := oc.syncDefaultGatewayLogicalNetwork(node, l3GatewayConfig, hostSubnets, hostAddrs); err != nil { + } else { + if err := oc.newGatewayManager(node.Name).syncGatewayLogicalNetwork( + node, + gwConfig.config, + gwConfig.hostSubnets, + gwConfig.hostAddrs, + gwConfig.clusterSubnets, + gwConfig.gwLRPJoinIPs, + oc.ovnClusterLRPToJoinIfAddrs, + gwConfig.externalIPs, + ); err != nil { return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) } } diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 15fdb98aa7..ac945b2739 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -1045,16 +1045,7 @@ func (oc *SecondaryLayer3NetworkController) gatherJoinSwitchIPs() error { return nil } -type SecondaryL3GatewayConfig struct { - config *util.L3GatewayConfig - hostSubnets []*net.IPNet - clusterSubnets []*net.IPNet - gwLRPJoinIPs []*net.IPNet - hostAddrs []string - externalIPs []net.IP -} - -func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) (*SecondaryL3GatewayConfig, error) { +func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) (*L3GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, fmt.Errorf("failed to get node %s network %s L3 gateway config: %v", node.Name, oc.GetNetworkName(), err) @@ -1104,7 +1095,7 @@ func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) // Overwrite the primary interface ID with the correct, per-network one. l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) - return &SecondaryL3GatewayConfig{ + return &L3GatewayConfig{ config: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterSubnets, From 02c2c185086795dee7ab3be7edfec3dd801960a2 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 10:59:58 +0200 Subject: [PATCH 098/278] [gateway] update syncNodeGateway to reduce nesting Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 6b69507c3d..39f6e36116 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -1402,21 +1402,21 @@ func (gw *GatewayManager) syncNodeGateway( if err := gw.Cleanup(); err != nil { return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) } - } else if hostSubnets != nil { - if err := gw.syncGatewayLogicalNetwork( - node, - l3GatewayConfig, - hostSubnets, - hostAddrs, - clusterSubnets, - grLRPJoinIPs, // the joinIP allocated to this node for this controller's network - joinSwitchIPs, // the .1 of this controller's global joinSubnet - externalIPs, - ); err != nil { - return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) - } + return nil } - return nil + if hostSubnets == nil { + return nil + } + return gw.syncGatewayLogicalNetwork( + node, + l3GatewayConfig, + hostSubnets, + hostAddrs, + clusterSubnets, + grLRPJoinIPs, // the joinIP allocated to this node for this controller's network + joinSwitchIPs, // the .1 of this controller's global joinSubnet + externalIPs, + ) } func physNetName(netInfo util.NetInfo) string { From e427103f5f97a8eb03db8e2675411863f8edb6bf Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 11:04:59 +0200 Subject: [PATCH 099/278] [gateway] rename public and private interfaces. Squash syncGatewayLogicalNetwork and syncNodeGateway into 1 function SyncGateway. Call SyncGateway from ovn.go since it does config.GatewayModeDisabled check inside. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 53 ++++++------------- go-controller/pkg/ovn/gateway_test.go | 32 +++++------ go-controller/pkg/ovn/ovn.go | 28 ++++------ .../secondary_layer2_network_controller.go | 2 +- .../secondary_layer3_network_controller.go | 2 +- 5 files changed, 45 insertions(+), 72 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 39f6e36116..8d084476a1 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -799,9 +799,9 @@ func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []* return nil } -// GatewayInit creates a gateway router for the local chassis. +// gatewayInit creates a gateway router for the local chassis. // enableGatewayMTU enables options:gateway_mtu for gateway routers. -func (gw *GatewayManager) GatewayInit( +func (gw *GatewayManager) gatewayInit( nodeName string, clusterIPSubnet []*net.IPNet, hostSubnets []*net.IPNet, @@ -1330,19 +1330,29 @@ func (gw *GatewayManager) isRoutingAdvertised(node string) bool { return util.IsPodNetworkAdvertisedAtNode(gw.netInfo, node) } -func (gw *GatewayManager) syncGatewayLogicalNetwork( +// SyncGateway ensures a node's gateway router is configured according to the L3 config and host subnets +func (gw *GatewayManager) SyncGateway( node *corev1.Node, l3GatewayConfig *util.L3GatewayConfig, hostSubnets []*net.IPNet, hostAddrs []string, - clusterSubnets []*net.IPNet, - grLRPJoinIPs []*net.IPNet, + clusterSubnets, grLRPJoinIPs []*net.IPNet, ovnClusterLRPToJoinIfAddrs []*net.IPNet, externalIPs []net.IP, ) error { + if l3GatewayConfig.Mode == config.GatewayModeDisabled { + if err := gw.Cleanup(); err != nil { + return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) + } + return nil + } + if hostSubnets == nil { + return nil + } + enableGatewayMTU := util.ParseNodeGatewayMTUSupport(node) - err := gw.GatewayInit( + err := gw.gatewayInit( node.Name, clusterSubnets, hostSubnets, @@ -1388,37 +1398,6 @@ func (gw *GatewayManager) syncGatewayLogicalNetwork( return nil } -// syncNodeGateway ensures a node's gateway router is configured according to the L3 config and host subnets -func (gw *GatewayManager) syncNodeGateway( - node *corev1.Node, - l3GatewayConfig *util.L3GatewayConfig, - hostSubnets []*net.IPNet, - hostAddrs []string, - clusterSubnets, grLRPJoinIPs []*net.IPNet, - joinSwitchIPs []*net.IPNet, - externalIPs []net.IP, -) error { - if l3GatewayConfig.Mode == config.GatewayModeDisabled { - if err := gw.Cleanup(); err != nil { - return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) - } - return nil - } - if hostSubnets == nil { - return nil - } - return gw.syncGatewayLogicalNetwork( - node, - l3GatewayConfig, - hostSubnets, - hostAddrs, - clusterSubnets, - grLRPJoinIPs, // the joinIP allocated to this node for this controller's network - joinSwitchIPs, // the .1 of this controller's global joinSubnet - externalIPs, - ) -} - func physNetName(netInfo util.NetInfo) string { if netInfo.IsDefault() || netInfo.IsPrimaryNetwork() { return types.PhysicalNetworkName diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index a8e611119d..82118aeb94 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -469,7 +469,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -582,7 +582,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -701,7 +701,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -786,7 +786,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -871,7 +871,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { mgmtPortIP := "" // Disable option:gateway_mtu. - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -890,7 +890,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { // Enable option:gateway_mtu. expectedOVNClusterRouter.StaticRoutes = []string{} - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -968,7 +968,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { // We don't set up the Allow from mgmt port ACL here mgmtPortIP := "" - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -989,7 +989,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { oldJoinLRPIPs := joinLRPIPs joinLRPIPs = ovntest.MustParseIPNets("100.64.0.99/16") expectedOVNClusterRouter.StaticRoutes = []string{} - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1072,7 +1072,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1151,7 +1151,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { config.IPv4Mode = false config.IPv6Mode = true - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1231,7 +1231,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1309,7 +1309,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1419,7 +1419,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1531,7 +1531,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1618,7 +1618,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, @@ -1734,7 +1734,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) expectedOVNClusterRouter.StaticRoutes = []string{} - err = newGatewayManager(fakeOvn, nodeName).GatewayInit( + err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, clusterIPSubnets, hostSubnets, diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index d035fdd299..64fb5c54d8 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -379,23 +379,17 @@ func (oc *DefaultNetworkController) syncNodeGateway(node *corev1.Node) error { return fmt.Errorf("error getting gateway config for node %s: %v", node.Name, err) } - if gwConfig.config.Mode == config.GatewayModeDisabled { - if err := oc.newGatewayManager(node.Name).Cleanup(); err != nil { - return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) - } - } else { - if err := oc.newGatewayManager(node.Name).syncGatewayLogicalNetwork( - node, - gwConfig.config, - gwConfig.hostSubnets, - gwConfig.hostAddrs, - gwConfig.clusterSubnets, - gwConfig.gwLRPJoinIPs, - oc.ovnClusterLRPToJoinIfAddrs, - gwConfig.externalIPs, - ); err != nil { - return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) - } + if err := oc.newGatewayManager(node.Name).SyncGateway( + node, + gwConfig.config, + gwConfig.hostSubnets, + gwConfig.hostAddrs, + gwConfig.clusterSubnets, + gwConfig.gwLRPJoinIPs, + oc.ovnClusterLRPToJoinIfAddrs, + gwConfig.externalIPs, + ); err != nil { + return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) } if util.IsPodNetworkAdvertisedAtNode(oc, node.Name) { diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index d43b853008..a8c3637b53 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -580,7 +580,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 errs = append(errs, err) oc.gatewaysFailed.Store(node.Name, true) } else { - if err := gwManager.syncNodeGateway( + if err := gwManager.SyncGateway( node, gwConfig.config, gwConfig.hostSubnets, diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index ac945b2739..1585dd55f4 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -777,7 +777,7 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *corev1 errs = append(errs, fmt.Errorf("failed to generate node GW configuration: %v", err)) oc.gatewaysFailed.Store(node.Name, true) } else { - if err := gwManager.syncNodeGateway( + if err := gwManager.SyncGateway( node, gwConfig.config, gwConfig.hostSubnets, From 0473203be6277731f8e93973129b92e6e240128d Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 11:27:14 +0200 Subject: [PATCH 100/278] [gateway] pass gateway args for all controllers using GatewayConfig. It is much easier to track how each parameter is used and set, removes 3 different names for the same thing problem. Add ovnClusterLRPToJoinIfAddrs to the GatewayConfig. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 58 ++--- go-controller/pkg/ovn/gateway_test.go | 240 +++++++++++------- go-controller/pkg/ovn/master.go | 32 +-- go-controller/pkg/ovn/ovn.go | 8 +- .../secondary_layer2_network_controller.go | 30 +-- .../secondary_layer3_network_controller.go | 25 +- 6 files changed, 203 insertions(+), 190 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 8d084476a1..8177f1c0d3 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -803,11 +803,7 @@ func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []* // enableGatewayMTU enables options:gateway_mtu for gateway routers. func (gw *GatewayManager) gatewayInit( nodeName string, - clusterIPSubnet []*net.IPNet, - hostSubnets []*net.IPNet, - l3GatewayConfig *util.L3GatewayConfig, - gwLRPJoinIPs, drLRPIfAddrs []*net.IPNet, - externalIPs []net.IP, + gwConfig *GatewayConfig, enableGatewayMTU bool, ) error { @@ -837,7 +833,7 @@ func (gw *GatewayManager) gatewayInit( } } - gwRouter, err := gw.createGWRouter(l3GatewayConfig, gwLRPJoinIPs) + gwRouter, err := gw.createGWRouter(gwConfig.config, gwConfig.gwLRPJoinIPs) if err != nil { return err } @@ -846,28 +842,28 @@ func (gw *GatewayManager) gatewayInit( return err } - gwLRPIPs, err := gw.createGWRouterPort(hostSubnets, gwLRPJoinIPs, enableGatewayMTU, gwRouter) + gwLRPIPs, err := gw.createGWRouterPort(gwConfig.hostSubnets, gwConfig.gwLRPJoinIPs, enableGatewayMTU, gwRouter) if err != nil { return err } if err := gw.addExternalSwitch("", - l3GatewayConfig.InterfaceID, + gwConfig.config.InterfaceID, gw.gwRouterName, - l3GatewayConfig.MACAddress.String(), + gwConfig.config.MACAddress.String(), physNetName(gw.netInfo), - l3GatewayConfig.IPAddresses, - l3GatewayConfig.VLANID); err != nil { + gwConfig.config.IPAddresses, + gwConfig.config.VLANID); err != nil { return err } - if l3GatewayConfig.EgressGWInterfaceID != "" { + if gwConfig.config.EgressGWInterfaceID != "" { if err := gw.addExternalSwitch(types.EgressGWSwitchPrefix, - l3GatewayConfig.EgressGWInterfaceID, + gwConfig.config.EgressGWInterfaceID, gw.gwRouterName, - l3GatewayConfig.EgressGWMACAddress.String(), + gwConfig.config.EgressGWMACAddress.String(), types.PhysicalNetworkExGwName, - l3GatewayConfig.EgressGWIPAddresses, + gwConfig.config.EgressGWIPAddresses, nil); err != nil { return err } @@ -883,20 +879,20 @@ func (gw *GatewayManager) gatewayInit( } externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName - if err = gw.updateGWRouterStaticRoutes(clusterIPSubnet, drLRPIfAddrs, l3GatewayConfig, externalRouterPort, + if err = gw.updateGWRouterStaticRoutes(gwConfig.clusterSubnets, gwConfig.ovnClusterLRPToJoinIfAddrs, gwConfig.config, externalRouterPort, gwRouter); err != nil { return err } - if err = gw.updateClusterRouterStaticRoutes(hostSubnets, gwLRPIPs); err != nil { + if err = gw.updateClusterRouterStaticRoutes(gwConfig.hostSubnets, gwLRPIPs); err != nil { return err } - if err = gw.syncNATsForGRIPChange(externalIPs, oldExtIPs, gwLRPIPs, gwRouter, oldLogicalRouter); err != nil { + if err = gw.syncNATsForGRIPChange(gwConfig.externalIPs, oldExtIPs, gwLRPIPs, gwRouter, oldLogicalRouter); err != nil { return err } - if err = gw.updateGWRouterNAT(nodeName, clusterIPSubnet, l3GatewayConfig, externalIPs, gwLRPIPs, gwRouter); err != nil { + if err = gw.updateGWRouterNAT(nodeName, gwConfig.clusterSubnets, gwConfig.config, gwConfig.externalIPs, gwLRPIPs, gwRouter); err != nil { return err } @@ -1333,20 +1329,15 @@ func (gw *GatewayManager) isRoutingAdvertised(node string) bool { // SyncGateway ensures a node's gateway router is configured according to the L3 config and host subnets func (gw *GatewayManager) SyncGateway( node *corev1.Node, - l3GatewayConfig *util.L3GatewayConfig, - hostSubnets []*net.IPNet, - hostAddrs []string, - clusterSubnets, grLRPJoinIPs []*net.IPNet, - ovnClusterLRPToJoinIfAddrs []*net.IPNet, - externalIPs []net.IP, + gwConfig *GatewayConfig, ) error { - if l3GatewayConfig.Mode == config.GatewayModeDisabled { + if gwConfig.config.Mode == config.GatewayModeDisabled { if err := gw.Cleanup(); err != nil { return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) } return nil } - if hostSubnets == nil { + if gwConfig.hostSubnets == nil { return nil } @@ -1354,12 +1345,7 @@ func (gw *GatewayManager) SyncGateway( err := gw.gatewayInit( node.Name, - clusterSubnets, - hostSubnets, - l3GatewayConfig, - grLRPJoinIPs, // the joinIP allocated to this node's GR for this controller's network - ovnClusterLRPToJoinIfAddrs, - externalIPs, + gwConfig, enableGatewayMTU, ) if err != nil { @@ -1370,16 +1356,16 @@ func (gw *GatewayManager) SyncGateway( if gw.clusterRouterName == "" { routerName = gw.gwRouterName } - for _, subnet := range hostSubnets { + for _, subnet := range gwConfig.hostSubnets { mgmtIfAddr := util.GetNodeManagementIfAddr(subnet) if mgmtIfAddr == nil { return fmt.Errorf("management interface address not found for subnet %q on network %q", subnet, gw.netInfo.GetNetworkName()) } - l3GatewayConfigIP, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6(mgmtIfAddr.IP), l3GatewayConfig.IPAddresses) + l3GatewayConfigIP, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6(mgmtIfAddr.IP), gwConfig.config.IPAddresses) if err != nil { return fmt.Errorf("failed to extract the gateway IP addr for network %q: %v", gw.netInfo.GetNetworkName(), err) } - relevantHostIPs, err := util.MatchAllIPStringFamily(utilnet.IsIPv6(mgmtIfAddr.IP), hostAddrs) + relevantHostIPs, err := util.MatchAllIPStringFamily(utilnet.IsIPv6(mgmtIfAddr.IP), gwConfig.hostAddrs) if err != nil && err != util.ErrorNoIP { return fmt.Errorf("failed to extract the host IP addrs for network %q: %v", gw.netInfo.GetNetworkName(), err) } diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index 82118aeb94..3b15905e13 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -464,6 +464,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -471,12 +480,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -577,6 +581,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -584,12 +597,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -696,6 +704,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -703,12 +720,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -781,6 +793,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { IPAddresses: ovntest.MustParseIPNets("169.255.33.2/24"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -788,12 +809,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -859,7 +875,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } - + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -873,12 +897,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { // Disable option:gateway_mtu. err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, false, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -892,12 +911,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { expectedOVNClusterRouter.StaticRoutes = []string{} err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -957,6 +971,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -970,12 +993,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, false, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -988,15 +1006,11 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { ginkgo.By("modifying the node join IP") oldJoinLRPIPs := joinLRPIPs joinLRPIPs = ovntest.MustParseIPNets("100.64.0.99/16") + gwConfig.gwLRPJoinIPs = joinLRPIPs expectedOVNClusterRouter.StaticRoutes = []string{} err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1067,6 +1081,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("fd99::1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -1074,12 +1097,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1143,6 +1161,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { IPAddresses: ovntest.MustParseIPNets("fd99::2/64"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -1153,12 +1180,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1226,6 +1248,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1", "fd99::1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -1233,12 +1264,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1303,6 +1329,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } config.Gateway.DisableSNATMultipleGWs = true var err error @@ -1311,12 +1346,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1413,6 +1443,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } config.Gateway.DisableSNATMultipleGWs = true var err error @@ -1421,12 +1460,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1526,6 +1560,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1", "fd99::1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } var err error fakeOvn.controller.defaultCOPPUUID, err = EnsureDefaultCOPP(fakeOvn.nbClient) @@ -1533,12 +1576,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1612,6 +1650,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } config.Gateway.DisableSNATMultipleGWs = true var err error @@ -1620,12 +1667,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1727,6 +1769,15 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NextHops: ovntest.MustParseIPs("169.255.33.1"), NodePortEnable: true, } + gwConfig := &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterIPSubnets, + gwLRPJoinIPs: joinLRPIPs, + hostAddrs: nil, + externalIPs: extractExternalIPs(l3GatewayConfig), + ovnClusterLRPToJoinIfAddrs: defLRPIPs, + } config.Gateway.DisableSNATMultipleGWs = true var err error @@ -1736,12 +1787,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { expectedOVNClusterRouter.StaticRoutes = []string{} err = newGatewayManager(fakeOvn, nodeName).gatewayInit( nodeName, - clusterIPSubnets, - hostSubnets, - l3GatewayConfig, - joinLRPIPs, - defLRPIPs, - extractExternalIPs(l3GatewayConfig), + gwConfig, true, ) gomega.Expect(err).NotTo(gomega.HaveOccurred()) diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 13d5283012..2b02dbf91a 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -33,13 +33,14 @@ const ( OvnNodeAnnotationRetryTimeout = 1 * time.Second ) -type L3GatewayConfig struct { - config *util.L3GatewayConfig - hostSubnets []*net.IPNet - clusterSubnets []*net.IPNet - gwLRPJoinIPs []*net.IPNet - hostAddrs []string - externalIPs []net.IP +type GatewayConfig struct { + config *util.L3GatewayConfig + hostSubnets []*net.IPNet + clusterSubnets []*net.IPNet + gwLRPJoinIPs []*net.IPNet + hostAddrs []string + externalIPs []net.IP + ovnClusterLRPToJoinIfAddrs []*net.IPNet } // SetupMaster creates the central router and load-balancers for the network @@ -91,7 +92,7 @@ func (oc *DefaultNetworkController) syncNodeManagementPortDefault(node *corev1.N return err } -func (oc *DefaultNetworkController) nodeGatewayConfig(node *corev1.Node) (*L3GatewayConfig, error) { +func (oc *DefaultNetworkController) nodeGatewayConfig(node *corev1.Node) (*GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, err @@ -133,13 +134,14 @@ func (oc *DefaultNetworkController) nodeGatewayConfig(node *corev1.Node) (*L3Gat } } - return &L3GatewayConfig{ - config: l3GatewayConfig, - hostSubnets: hostSubnets, - clusterSubnets: clusterSubnets, - gwLRPJoinIPs: gwLRPIPs, - hostAddrs: hostAddrs, - externalIPs: externalIPs, + return &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterSubnets, + gwLRPJoinIPs: gwLRPIPs, + hostAddrs: hostAddrs, + externalIPs: externalIPs, + ovnClusterLRPToJoinIfAddrs: oc.ovnClusterLRPToJoinIfAddrs, }, nil } diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 64fb5c54d8..293e23f4aa 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -381,13 +381,7 @@ func (oc *DefaultNetworkController) syncNodeGateway(node *corev1.Node) error { if err := oc.newGatewayManager(node.Name).SyncGateway( node, - gwConfig.config, - gwConfig.hostSubnets, - gwConfig.hostAddrs, - gwConfig.clusterSubnets, - gwConfig.gwLRPJoinIPs, - oc.ovnClusterLRPToJoinIfAddrs, - gwConfig.externalIPs, + gwConfig, ); err != nil { return fmt.Errorf("error creating gateway for node %s: %v", node.Name, err) } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index a8c3637b53..74ad6eab88 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -582,13 +582,7 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 } else { if err := gwManager.SyncGateway( node, - gwConfig.config, - gwConfig.hostSubnets, - nil, - gwConfig.hostSubnets, - gwConfig.gwLRPJoinIPs, // the joinIP allocated to this node for this controller's network - nil, // no need for ovnClusterLRPToJoinIfAddrs - gwConfig.externalIPs, + gwConfig, ); err != nil { errs = append(errs, err) oc.gatewaysFailed.Store(node.Name, true) @@ -795,14 +789,7 @@ func (oc *SecondaryLayer2NetworkController) deleteUDNClusterSubnetEgressSNAT(loc return nil } -type SecondaryL2GatewayConfig struct { - config *util.L3GatewayConfig - hostSubnets []*net.IPNet - gwLRPJoinIPs []*net.IPNet - externalIPs []net.IP -} - -func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) (*SecondaryL2GatewayConfig, error) { +func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) (*GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, fmt.Errorf("failed to get node %s network %s L3 gateway config: %v", node.Name, oc.GetNetworkName(), err) @@ -842,11 +829,14 @@ func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) // Overwrite the primary interface ID with the correct, per-network one. l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) - return &SecondaryL2GatewayConfig{ - config: l3GatewayConfig, - hostSubnets: hostSubnets, - gwLRPJoinIPs: gwLRPJoinIPs, - externalIPs: externalIPs, + return &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: hostSubnets, + gwLRPJoinIPs: gwLRPJoinIPs, + hostAddrs: nil, + externalIPs: externalIPs, + ovnClusterLRPToJoinIfAddrs: nil, }, nil } diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index 1585dd55f4..e550000318 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -779,13 +779,7 @@ func (oc *SecondaryLayer3NetworkController) addUpdateLocalNodeEvent(node *corev1 } else { if err := gwManager.SyncGateway( node, - gwConfig.config, - gwConfig.hostSubnets, - gwConfig.hostAddrs, - gwConfig.clusterSubnets, - gwConfig.gwLRPJoinIPs, // the joinIP allocated to this node for this controller's network - oc.ovnClusterLRPToJoinIfAddrs, // the .1 of this controller's global joinSubnet - gwConfig.externalIPs, + gwConfig, ); err != nil { errs = append(errs, fmt.Errorf( "failed to sync node GW for network %q: %v", @@ -1045,7 +1039,7 @@ func (oc *SecondaryLayer3NetworkController) gatherJoinSwitchIPs() error { return nil } -func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) (*L3GatewayConfig, error) { +func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) (*GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { return nil, fmt.Errorf("failed to get node %s network %s L3 gateway config: %v", node.Name, oc.GetNetworkName(), err) @@ -1095,13 +1089,14 @@ func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) // Overwrite the primary interface ID with the correct, per-network one. l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) - return &L3GatewayConfig{ - config: l3GatewayConfig, - hostSubnets: hostSubnets, - clusterSubnets: clusterSubnets, - gwLRPJoinIPs: gwLRPJoinIPs, - hostAddrs: hostAddrs, - externalIPs: externalIPs, + return &GatewayConfig{ + config: l3GatewayConfig, + hostSubnets: hostSubnets, + clusterSubnets: clusterSubnets, + gwLRPJoinIPs: gwLRPJoinIPs, + hostAddrs: hostAddrs, + externalIPs: externalIPs, + ovnClusterLRPToJoinIfAddrs: oc.ovnClusterLRPToJoinIfAddrs, }, nil } From 6395072de4c04a07e6a16f4d44d5025041321af5 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 17:53:22 +0200 Subject: [PATCH 101/278] [gateway] rename GatewayConfig config to annoConfig Signed-off-by: Nadia Pinaeva --- go-controller/pkg/ovn/gateway.go | 26 ++++++++--------- go-controller/pkg/ovn/gateway_test.go | 28 +++++++++---------- go-controller/pkg/ovn/master.go | 4 +-- .../secondary_layer2_network_controller.go | 2 +- .../secondary_layer3_network_controller.go | 2 +- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 8177f1c0d3..a43adf5368 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -833,7 +833,7 @@ func (gw *GatewayManager) gatewayInit( } } - gwRouter, err := gw.createGWRouter(gwConfig.config, gwConfig.gwLRPJoinIPs) + gwRouter, err := gw.createGWRouter(gwConfig.annoConfig, gwConfig.gwLRPJoinIPs) if err != nil { return err } @@ -848,22 +848,22 @@ func (gw *GatewayManager) gatewayInit( } if err := gw.addExternalSwitch("", - gwConfig.config.InterfaceID, + gwConfig.annoConfig.InterfaceID, gw.gwRouterName, - gwConfig.config.MACAddress.String(), + gwConfig.annoConfig.MACAddress.String(), physNetName(gw.netInfo), - gwConfig.config.IPAddresses, - gwConfig.config.VLANID); err != nil { + gwConfig.annoConfig.IPAddresses, + gwConfig.annoConfig.VLANID); err != nil { return err } - if gwConfig.config.EgressGWInterfaceID != "" { + if gwConfig.annoConfig.EgressGWInterfaceID != "" { if err := gw.addExternalSwitch(types.EgressGWSwitchPrefix, - gwConfig.config.EgressGWInterfaceID, + gwConfig.annoConfig.EgressGWInterfaceID, gw.gwRouterName, - gwConfig.config.EgressGWMACAddress.String(), + gwConfig.annoConfig.EgressGWMACAddress.String(), types.PhysicalNetworkExGwName, - gwConfig.config.EgressGWIPAddresses, + gwConfig.annoConfig.EgressGWIPAddresses, nil); err != nil { return err } @@ -879,7 +879,7 @@ func (gw *GatewayManager) gatewayInit( } externalRouterPort := types.GWRouterToExtSwitchPrefix + gw.gwRouterName - if err = gw.updateGWRouterStaticRoutes(gwConfig.clusterSubnets, gwConfig.ovnClusterLRPToJoinIfAddrs, gwConfig.config, externalRouterPort, + if err = gw.updateGWRouterStaticRoutes(gwConfig.clusterSubnets, gwConfig.ovnClusterLRPToJoinIfAddrs, gwConfig.annoConfig, externalRouterPort, gwRouter); err != nil { return err } @@ -892,7 +892,7 @@ func (gw *GatewayManager) gatewayInit( return err } - if err = gw.updateGWRouterNAT(nodeName, gwConfig.clusterSubnets, gwConfig.config, gwConfig.externalIPs, gwLRPIPs, gwRouter); err != nil { + if err = gw.updateGWRouterNAT(nodeName, gwConfig.clusterSubnets, gwConfig.annoConfig, gwConfig.externalIPs, gwLRPIPs, gwRouter); err != nil { return err } @@ -1331,7 +1331,7 @@ func (gw *GatewayManager) SyncGateway( node *corev1.Node, gwConfig *GatewayConfig, ) error { - if gwConfig.config.Mode == config.GatewayModeDisabled { + if gwConfig.annoConfig.Mode == config.GatewayModeDisabled { if err := gw.Cleanup(); err != nil { return fmt.Errorf("error cleaning up gateway for node %s: %v", node.Name, err) } @@ -1361,7 +1361,7 @@ func (gw *GatewayManager) SyncGateway( if mgmtIfAddr == nil { return fmt.Errorf("management interface address not found for subnet %q on network %q", subnet, gw.netInfo.GetNetworkName()) } - l3GatewayConfigIP, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6(mgmtIfAddr.IP), gwConfig.config.IPAddresses) + l3GatewayConfigIP, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6(mgmtIfAddr.IP), gwConfig.annoConfig.IPAddresses) if err != nil { return fmt.Errorf("failed to extract the gateway IP addr for network %q: %v", gw.netInfo.GetNetworkName(), err) } diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index 3b15905e13..8e87ba0afd 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -465,7 +465,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -582,7 +582,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -705,7 +705,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -794,7 +794,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -876,7 +876,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -972,7 +972,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1082,7 +1082,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1162,7 +1162,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1249,7 +1249,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1330,7 +1330,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1444,7 +1444,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1561,7 +1561,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1651,7 +1651,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, @@ -1770,7 +1770,7 @@ var _ = ginkgo.Describe("Gateway Init Operations", func() { NodePortEnable: true, } gwConfig := &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterIPSubnets, gwLRPJoinIPs: joinLRPIPs, diff --git a/go-controller/pkg/ovn/master.go b/go-controller/pkg/ovn/master.go index 2b02dbf91a..b5394c7ffe 100644 --- a/go-controller/pkg/ovn/master.go +++ b/go-controller/pkg/ovn/master.go @@ -34,7 +34,7 @@ const ( ) type GatewayConfig struct { - config *util.L3GatewayConfig + annoConfig *util.L3GatewayConfig hostSubnets []*net.IPNet clusterSubnets []*net.IPNet gwLRPJoinIPs []*net.IPNet @@ -135,7 +135,7 @@ func (oc *DefaultNetworkController) nodeGatewayConfig(node *corev1.Node) (*Gatew } return &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterSubnets, gwLRPJoinIPs: gwLRPIPs, diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 74ad6eab88..7ce63fc278 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -830,7 +830,7 @@ func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) // Overwrite the primary interface ID with the correct, per-network one. l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) return &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: hostSubnets, gwLRPJoinIPs: gwLRPJoinIPs, diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index e550000318..b2355b9100 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -1090,7 +1090,7 @@ func (oc *SecondaryLayer3NetworkController) nodeGatewayConfig(node *corev1.Node) l3GatewayConfig.InterfaceID = oc.GetNetworkScopedExtPortName(l3GatewayConfig.BridgeID, node.Name) return &GatewayConfig{ - config: l3GatewayConfig, + annoConfig: l3GatewayConfig, hostSubnets: hostSubnets, clusterSubnets: clusterSubnets, gwLRPJoinIPs: gwLRPJoinIPs, From 005427354a0533550efb1236175f1baadb6cf797 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Wed, 2 Jul 2025 18:16:53 -0700 Subject: [PATCH 102/278] VF gateway trigger errors message in updateServiceFlowCache When gateway accelerated interface is used, we noticed the error messages 'gateway_shared_intf.go:392] Unable to get port list from bridge. ... failed to get list of ports on bridge "enp1s0f0v0":, stderr: "ovs-ofctl: enp1s0f0v0 is not a bridge or a socket\n" ...'. Also, bridgeConfiguration.getGatewayIface() function is confusing, as b.gwIface is always non-empty, so one can just directly use b.gwIface. Signed-off-by: Yun Zhou --- .../pkg/node/default_node_network_controller.go | 10 ++-------- go-controller/pkg/node/gateway.go | 12 ++---------- go-controller/pkg/node/gateway_shared_intf.go | 16 ++++++++-------- go-controller/pkg/node/gateway_udn.go | 2 +- go-controller/pkg/node/gateway_udn_test.go | 2 +- .../pkg/node/node_ip_handler_linux_test.go | 2 +- 6 files changed, 15 insertions(+), 29 deletions(-) diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index 7a75c36984..3b120dc579 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -1186,10 +1186,8 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { // is not needed. Future upgrade flows will need to take DPUs into account. if config.OvnKubeNode.Mode != types.NodeModeDPUHost { if config.OvnKubeNode.Mode == types.NodeModeFull { - bridgeName := nc.Gateway.GetGatewayIface() - // Configure route for svc towards shared gw bridge - // Have to have the route to bridge for multi-NIC mode, where the default gateway may go to a non-OVS interface - if err := configureSvcRouteViaBridge(nc.routeManager, bridgeName); err != nil { + // Configure route for svc towards shared gateway interface + if err := configureSvcRouteViaInterface(nc.routeManager, nc.Gateway.GetGatewayIface(), DummyNextHopIPs()); err != nil { return err } } @@ -1655,10 +1653,6 @@ func getPMTUDKey(nodeName string) string { return fmt.Sprintf("%s_pmtud", nodeName) } -func configureSvcRouteViaBridge(routeManager *routemanager.Controller, bridge string) error { - return configureSvcRouteViaInterface(routeManager, bridge, DummyNextHopIPs()) -} - // DummyNextHopIPs returns the fake next hops used for service traffic routing. // It is used in: // - br-ex, where we don't really care about the next hop GW in use as traffic is always routed to OVN diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 38a7ad2910..db1bcae279 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -465,7 +465,7 @@ func (g *gateway) GetGatewayBridgeIface() string { } func (g *gateway) GetGatewayIface() string { - return g.openflowManager.defaultBridge.getGatewayIface() + return g.openflowManager.defaultBridge.gwIface } // getMaxFrameLength returns the maximum frame size (ignoring VLAN header) that a gateway can handle @@ -556,19 +556,11 @@ type bridgeConfiguration struct { nextHops []net.IP } -func (b *bridgeConfiguration) getGatewayIface() string { - // If gwIface is set, then accelerated GW interface is present and we use it. If else use external bridge instead. - if b.gwIface != "" { - return b.gwIface - } - return b.bridgeName -} - // updateInterfaceIPAddresses sets and returns the bridge's current ips func (b *bridgeConfiguration) updateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { b.Lock() defer b.Unlock() - ifAddrs, err := getNetworkInterfaceIPAddresses(b.getGatewayIface()) + ifAddrs, err := getNetworkInterfaceIPAddresses(b.gwIface) if err != nil { return nil, err } diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 7556aa54f7..a8d3b81aa7 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -2476,15 +2476,15 @@ func newGateway( // Delete stale masquerade resources if there are any. This is to make sure that there // are no Linux resources with IP from old masquerade subnet when masquerade subnet // gets changed as part of day2 operation. - if err := deleteStaleMasqueradeResources(gwBridge.getGatewayIface(), nodeName, watchFactory); err != nil { + if err := deleteStaleMasqueradeResources(gwBridge.gwIface, nodeName, watchFactory); err != nil { return fmt.Errorf("failed to remove stale masquerade resources: %w", err) } - if err := setNodeMasqueradeIPOnExtBridge(gwBridge.getGatewayIface()); err != nil { - return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", gwBridge.getGatewayIface(), err) + if err := setNodeMasqueradeIPOnExtBridge(gwBridge.gwIface); err != nil { + return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", gwBridge.gwIface, err) } - if err := addMasqueradeRoute(routeManager, gwBridge.getGatewayIface(), nodeName, gwIPs, watchFactory); err != nil { + if err := addMasqueradeRoute(routeManager, gwBridge.gwIface, nodeName, gwIPs, watchFactory); err != nil { return fmt.Errorf("failed to set the node masquerade route to OVN: %v", err) } @@ -2531,7 +2531,7 @@ func newGateway( gw.openflowManager.requestFlowSync() } - if err := addHostMACBindings(gwBridge.getGatewayIface()); err != nil { + if err := addHostMACBindings(gwBridge.gwIface); err != nil { return fmt.Errorf("failed to add MAC bindings for service routing: %w", err) } @@ -2593,11 +2593,11 @@ func newNodePortWatcher( subnets = append(subnets, config.Kubernetes.ServiceCIDRs...) if config.Gateway.DisableForwarding { if err := initExternalBridgeServiceForwardingRules(subnets); err != nil { - return nil, fmt.Errorf("failed to add accept rules in forwarding table for bridge %s: err %v", gwBridge.getGatewayIface(), err) + return nil, fmt.Errorf("failed to add accept rules in forwarding table for bridge %s: err %v", gwBridge.gwIface, err) } } else { if err := delExternalBridgeServiceForwardingRules(subnets); err != nil { - return nil, fmt.Errorf("failed to delete accept rules in forwarding table for bridge %s: err %v", gwBridge.getGatewayIface(), err) + return nil, fmt.Errorf("failed to delete accept rules in forwarding table for bridge %s: err %v", gwBridge.gwIface, err) } } @@ -2615,7 +2615,7 @@ func newNodePortWatcher( gatewayIPv4: gatewayIPv4, gatewayIPv6: gatewayIPv6, ofportPhys: ofportPhys, - gwBridge: gwBridge.getGatewayIface(), + gwBridge: gwBridge.bridgeName, serviceInfo: make(map[ktypes.NamespacedName]*serviceConfig), nodeIPManager: nodeIPManager, ofm: ofm, diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 3e2ff143c9..d991fc74eb 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -268,7 +268,7 @@ func NewUserDefinedNetworkGateway(netInfo util.NetInfo, node *corev1.Node, nodeL if gw.openflowManager == nil { return nil, fmt.Errorf("openflow manager has not been provided for network: %s", netInfo.GetNetworkName()) } - intfName := gw.openflowManager.defaultBridge.getGatewayIface() + intfName := gw.openflowManager.defaultBridge.gwIface link, err := util.GetNetLinkOps().LinkByName(intfName) if err != nil { return nil, fmt.Errorf("unable to get link for %s, error: %v", intfName, err) diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 1c02ffbbdb..4d73529c86 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -343,7 +343,7 @@ func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfigurat func getDummyOpenflowManager() *openflowManager { gwBridge := &bridgeConfiguration{ - gwIface: "", + gwIface: "breth0", bridgeName: "breth0", } ofm := &openflowManager{ diff --git a/go-controller/pkg/node/node_ip_handler_linux_test.go b/go-controller/pkg/node/node_ip_handler_linux_test.go index d8ff6710d9..ee10bbfc41 100644 --- a/go-controller/pkg/node/node_ip_handler_linux_test.go +++ b/go-controller/pkg/node/node_ip_handler_linux_test.go @@ -401,7 +401,7 @@ func configureKubeOVNContext(nodeName string, useNetlink bool) *testCtx { mpmock := &nodemocks.ManagementPort{} mpmock.On("GetAddresses").Return([]*net.IPNet{tc.mgmtPortIP4, tc.mgmtPortIP6}) - fakeBridgeConfiguration := &bridgeConfiguration{bridgeName: "breth0"} + fakeBridgeConfiguration := &bridgeConfiguration{bridgeName: "breth0", gwIface: "breth0"} k := &kube.Kube{KClient: tc.fakeClient} tc.ipManager = newAddressManagerInternal(nodeName, k, mpmock, tc.watchFactory, fakeBridgeConfiguration, useNetlink) From c55d657709e84dd96830108d9ed22fce4539f547 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Thu, 8 May 2025 10:12:41 +0200 Subject: [PATCH 103/278] OKEP: Pre-assigned network configuration for primary user defined networks workloads Co-authored-by: Miguel Duarte Barroso Signed-off-by: Patryk Diak --- .../okep-5233-preconfigured-udn-addresses.md | 514 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 515 insertions(+) create mode 100644 docs/okeps/okep-5233-preconfigured-udn-addresses.md diff --git a/docs/okeps/okep-5233-preconfigured-udn-addresses.md b/docs/okeps/okep-5233-preconfigured-udn-addresses.md new file mode 100644 index 0000000000..332fec24fb --- /dev/null +++ b/docs/okeps/okep-5233-preconfigured-udn-addresses.md @@ -0,0 +1,514 @@ +# OKEP-5233: Predefined addresses for primary user defined networks workloads + +* Issue: [#5233](https://github.com/ovn-org/ovn-kubernetes/issues/5233) + +## Problem Statement + +Migrating legacy workloads with predefined network configurations (IP, MAC, default gateway) +to OVN-Kubernetes is currently not possible. There is a need to import these workloads, preserving +their network configuration, while also enabling non-NATed traffic to better integrate with +existing infrastructures. + +## Goals + +* Enable pods on primary Layer2 User Defined Network (UDN) and Cluster UDN to use a predefined static network + configuration including IP address, MAC address, and default gateway. +* Ensure it is possible to enable non-NATed traffic for pods with predefined static network configuration + by exposing the Layer2 Cluster UDN through BGP (see [Risks, Known Limitations and Mitigations](#risks-known-limitations-and-mitigations) for current BGP support limitations). + +## Non-Goals + +* Modifying the default gateway and management IPs of a primary UDN after it was created. +* Modifying a pod's network configuration after the pod was created. +* Non-NATed traffic support in secondary networks. +* Predefined IP/MAC addresses support for pods in Layer3 UDNs. +* Configurable default gateway and infrastructure addresses in Layer3 UDNs. +* Predefined IP/MAC addresses support for pods in Localnet UDNs. +* Configuring default gateway and infrastructure addresses in Layer2 (Cluster) UDNs that do not belong to the networks subnets. +* No-downtime workload migration. + +## Introduction + +Legacy workloads, particularly virtual machines, are often set up with static +network configurations. When migrating to OVN-Kubernetes UDNs, +it should be possible to integrate these gradually to prevent disruptions. + +Currently, OVN-Kubernetes allocates IP addresses dynamically and it generates the MAC +addresses from it. It sets the pod's default gateway to the first usable IP address of its subnet. +For primary UDNs, it additionally reserves the second usable IP address for the internal management port which +excludes it from being available for workloads. + +## User-Stories/Use-Cases + +* As a user, I want to define a custom default gateway IP for a new primary Layer2 UDN +so that my migrated workloads can maintain their existing network configuration without disruption. + +* As a user, I want the ability to configure a new primary Layer2 UDN with a custom management IP +address to prevent IP conflicts with the workloads I am importing. + +* As a user, I want to assign a predefined IP address and MAC address to a pod to ensure the +network identity of my imported workload is maintained. + +* As a user, I want to prevent OVN-Kubernetes from automatically assigning IP addresses that are +already in use by my existing infrastructure, so that I can migrate my services gradually without network conflicts. + +## Proposed Solution + +### Primary UDN configuration + +To support the migration of pre-configured workloads, the UDN and cluster UDN API has to +be enhanced. The aim is to provide control over the IP addresses that OVN-Kubernetes +consumes in the overlay network, this includes the default gateway and management IPs. +The proposed changes are specified in the [Layer2 User Defined Network API changes](#layer2-user-defined-network-api-changes) section. + +### Pod network identity + +OVN-Kubernetes currently supports configuring pods' secondary network interfaces through +the `k8s.v1.cni.cncf.io/networks` annotation, which contains a JSON array of +[NetworkSelectionElement](https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/blob/e12bd55d48a1f798a1720218819063f5903b72e3/pkg/apis/k8s.cni.cncf.io/v1/types.go#L136-L171) +objects. Additionally, it is possible to modify the cluster's default network attachment by +setting the `v1.multus-cni.io/default-network` annotation to a singular NetworkSelectionElement +object. + +To enable using predefined MAC and IP addresses on pods attached to a primary UDN, +the `v1.multus-cni.io/default-network` will be reused, as it is a well-known annotation for +configuring the pod's default network. The `k8s.v1.cni.cncf.io/networks` annotation is specific to +secondary networks and expects a list of networks, which does not fit well with primary UDNs. +With the proposed approach, the `k8s.ovn.org/primary-udn-ipamclaim` annotation, used to link a +pod with a matching claim, will be deprecated in favor of the `IPAMClaimReference` field in the +NetworkSelectionElement. When `IPAMClaimReference` is specified we will update its status to reflect +the result of the IP allocation, see [IPAMClaim API changes](#ipamclaim-api-changes). +OVN-Kubernetes will keep track of all allocated MAC and IP addresses to detect conflicts. +When a conflict is detected, OVN-Kubernetes will emit a Kubernetes event to the pod indicating +the specific conflict (IP or MAC address already in use) and prevent the pod from starting. + +```mermaid +%%{init: { 'sequence': {'messageAlign': 'left'} }}%% +sequenceDiagram +actor User +participant K8s_API_Server as "K8s API Server" +participant OVN_K_Controller as "OVN-Kubernetes" + +note over User, K8s_API_Server: Pre-Step: User defines UDN
(Optional) with custom Default Gateway / Management IP + +User->>K8s_API_Server: Create Pod with annotation:
'v1.multus-cni.io/default-network':
[{
name: 'default',
namespace: 'ovn-kubernetes',
ips: ['10.0.0.10'],
mac: '00:1A:2B:3C:4D:5E',
ipam-claim-reference: 'my-claim'
}] + + +K8s_API_Server->>OVN_K_Controller: Notify: New Pod Spec + +OVN_K_Controller->>OVN_K_Controller: Parse 'v1.multus-cni.io/default-network' annotation
Perform IP/MAC conflict check within UDN
(Verify requested IP/MAC are not in use) + + +alt "No IP/MAC Conflict" +opt "IPAMClaimReference is specified in annotation" +OVN_K_Controller->>K8s_API_Server: Update Status conditions and addresses of referenced IPAMClaim +end + +OVN_K_Controller->>OVN_K_Controller: Configure Pod's Primary Network Interface +note right of OVN_K_Controller: Pod provisioning succeeds +else "IP/MAC Conflict Detected in UDN" +opt "IPAMClaimReference is specified in annotation" +OVN_K_Controller->>K8s_API_Server: Update Status conditions of referenced IPAMClaim +end +OVN_K_Controller->>K8s_API_Server: Emit IP/MAC Conflict error event to the Pod +note right of OVN_K_Controller: Pod provisioning fails +end +``` + +### API Details + +#### Layer2 User Defined Network API changes + +Proposed API change adds `infrastructureSubnets` `reservedSubnets` and `defaultGatewayIPs` fields to the `Layer2Config` which is a part of both +the [UDN](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/a3d0a2b238bef9b1399b3342228d75504afed18b/go-controller/pkg/crd/userdefinednetwork/v1/udn.go#L47) +and [cluster UDN](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/a3d0a2b238bef9b1399b3342228d75504afed18b/go-controller/pkg/crd/userdefinednetwork/v1/cudn.go#L63) specs: + +```diff +// +kubebuilder:validation:XValidation:rule="has(self.ipam) && has(self.ipam.mode) && self.ipam.mode != 'Enabled' || has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset" +// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || !has(self.subnets)", message="Subnets must be unset when ipam.mode is Disabled" +// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || self.role == 'Secondary'", message="Disabled ipam.mode is only supported for Secondary network" +// +kubebuilder:validation:XValidation:rule="!has(self.joinSubnets) || has(self.role) && self.role == 'Primary'", message="JoinSubnets is only supported for Primary network" +// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subnet is used" ++ // +kubebuilder:validation:XValidation:rule="!has(self.defaultGatewayIPs) || has(self.role) && self.role == 'Primary'", message="defaultGatewayIPs is only supported for Primary network" ++ // +kubebuilder:validation:XValidation:rule="!has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip, self.subnets.exists(subnet, cidr(subnet).containsIP(ip)))", message="defaultGatewayIPs must belong to one of the subnets specified in the subnets field" ++ // +kubebuilder:validation:XValidation:rule="!has(self.reservedSubnets) || has(self.reservedSubnets) && has(self.subnets)", message="reservedSubnets must be unset when subnets is unset" ++ // +kubebuilder:validation:XValidation:rule="!has(self.reservedSubnets) || self.reservedSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="reservedSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".reservedSubnets" ++ // +kubebuilder:validation:XValidation:rule="!has(self.infrastructureSubnets) || has(self.infrastructureSubnets) && has(self.subnets)", message="infrastructureSubnets must be unset when subnets is unset" ++ // +kubebuilder:validation:XValidation:rule="!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="infrastructureSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".infrastructureSubnets" ++ // +kubebuilder:validation:XValidation:rule="!has(self.infrastructureSubnets) || !has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip, self.infrastructureSubnets.exists(subnet, cidr(subnet).containsIP(ip)))", message="defaultGatewayIPs have to belong to infrastructureSubnets" ++ // +kubebuilder:validation:XValidation:rule="!has(self.infrastructureSubnets) || !has(self.reservedSubnets) || self.infrastructureSubnets.all(infra, !self.reservedSubnets.exists(reserved, cidr(infra).containsCIDR(reserved) || cidr(reserved).containsCIDR(infra)))", message="infrastructureSubnets and reservedSubnets must not overlap" +type Layer2Config struct { + +// Role describes the network role in the pod. +// +// Allowed value is "Secondary". +// Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. +// +// +kubebuilder:validation:Enum=Primary;Secondary +// +kubebuilder:validation:Required +// +required +Role NetworkRole `json:"role"` + +// MTU is the maximum transmission unit for a network. +// MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. +// +// +kubebuilder:validation:Minimum=576 +// +kubebuilder:validation:Maximum=65536 +// +optional +MTU int32 `json:"mtu,omitempty"` + +// Subnets are used for the pod network across the cluster. +// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. +// +// The format should match standard CIDR notation (for example, "10.128.0.0/16"). +// This field must be omitted if `ipam.mode` is `Disabled`. +// +// +optional +Subnets DualStackCIDRs `json:"subnets,omitempty"` + ++ // reservedSubnets specifies a list of CIDRs reserved for static IP assignment, excluded from automatic allocation. ++ // reservedSubnets is optional. When omitted, all IP addresses in `subnets` are available for automatic assignment. ++ // IPs from these ranges can still be requested through static IP assignment in pod annotations. ++ // Each item should be in range of the specified CIDR(s) in `subnets`. ++ // The maximum number of entries allowed is 25. ++ // The format should match standard CIDR notation (for example, "10.128.0.0/16"). ++ // This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. ++ // +optional ++ // +kubebuilder:validation:MinItems=1 ++ // +kubebuilder:validation:MaxItems=25 ++ ReservedSubnets []CIDR `json:"reservedSubnets,omitempty"` + +// JoinSubnets are used inside the OVN network topology. +// +// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed. +// This field is only allowed for "Primary" network. +// It is not recommended to set this field without explicit need and understanding of the OVN network topology. +// When omitted, the platform will choose a reasonable default which is subject to change over time. +// +// +optional +JoinSubnets DualStackCIDRs `json:"joinSubnets,omitempty"` + ++ // infrastructureSubnets specifies a list of internal CIDR ranges that OVN-Kubernetes will reserve for internal network infrastructure. ++ // Any IP addresses within these ranges cannot be assigned to workloads. ++ // When omitted, OVN-Kubernetes will automatically allocate IP addresses from `subnets` for its infrastructure needs. ++ // When `reservedSubnets` is also specified the CIDRs cannot overlap. ++ // When `defaultGatewayIPs` is also specified the default gateway IPs must belong to one of the CIDRs. ++ // Each item should be in range of the specified CIDR(s) in `subnets`. ++ // The maximum number of entries allowed is 10. ++ // The format should match standard CIDR notation (for example, "10.128.0.0/16"). ++ // This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`. ++ // +optional ++ // +kubebuilder:validation:MinItems=1 ++ // +kubebuilder:validation:MaxItems=10 ++ InfrastructureSubnets []CIDR `json:"infrastructureSubnets,omitempty"` + ++ // defaultGatewayIPs specifies the default gateway IP used in the internal OVN topology. ++ // ++ // Dual-stack clusters may set 2 IPs (one for each IP family), otherwise only 1 IP is allowed. ++ // This field is only allowed for "Primary" network. ++ // It is not recommended to set this field without explicit need and understanding of the OVN network topology. ++ // When omitted, an IP from network subnet is used. ++ // ++ // +optional ++ DefaultGatewayIPs DualStackIPs `json:"defaultGatewayIPs,omitempty"` + +// IPAM section contains IPAM-related configuration for the network. +// +optional +IPAM *IPAMConfig `json:"ipam,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="isIP(self)", message="IP is invalid" +type IP string + +// +kubebuilder:validation:MinItems=1 +// +kubebuilder:validation:MaxItems=2 +// +kubebuilder:validation:XValidation:rule="size(self) != 2 || !isIP(self[0]) || !isIP(self[1]) || ip(self[0]).family() != ip(self[1]).family()", message="When 2 IPs are set, they must be from different IP families" +type DualStackIPs []IP + +``` + +The API changes mentioned above will be carried to the `NetworkAttachmentDefinition` JSON spec. + +#### IPAMClaim API changes + +The following pull request is tracking the IPAMClaim API change that introduces the status conditions: + + +[IPAMClaim CRD doc](https://docs.google.com/document/d/1OQIJIrCtsYpR5O44w0hpoJ2TyKBz1Du-KhRT4RtrAjk) - `IPAM allocation on behalf of other entities` section + +### Usage Example + +A user migrating services wants to import a workload pod preserving it's original IP address. +Workload data: + +```yaml +IP: 192.168.100.205 +MAC: 00:1A:2B:3C:4D:5E +Default Gateway: 192.168.100.2 +``` + +```yaml +apiVersion: k8s.ovn.org/v1 +kind: ClusterUserDefinedNetwork +metadata: + name: network-l2 +spec: + topology: "Layer2" + layer2: + role: Primary + subnets: ["192.168.100.0/24"] + infrastructureSubnets: ["192.168.100.0/30"] # used for OVN-Kubernetes infrastructure + reservedSubnets: ["192.168.100.200/29"] # reserved for workloads that will require predefined addresses + defaultGatewayIPs: ["192.168.100.2"] +``` + +With this configuration, OVN-Kubernetes automatically assigns IPs from `.4-.199` and `.208-.254` for new workloads, while pods can request specific IPs from the reserved range: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: migrated-app + annotations: + v1.multus-cni.io/default-network: | + {"name": "default", "namespace": "ovn-kubernetes", "ips": ["192.168.100.205"], "mac": "00:1A:2B:3C:4D:5E", "ipam-claim-reference": "my-claim"} +spec: +``` + +### Implementation Details + +#### Configurability + +The changes outlined in this enhancement should be configurable. This means a configuration knob +is required to instruct OVN-Kubernetes on whether to process the annotation described in the +[Pod network identity](#pod-network-identity) section. The feature knob will be called `preconfigured-udn-addresses-enable`. + +#### NetworkSelectionElement annotation + +Currently, the `v1.multus-cni.io/default-network` annotation is only processed for the cluster default network. +This enhancement will extend this behavior, allowing it to be applied to pods created in the primary Layer2 UDN as well. +The annotation should only be processed for new pods, modifying it after the addresses were allocated won't +be reflected in the pods network configuration and this should be blocked through a +[Validating Admission Policy](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/): + +```yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: predefined-network-addresses +spec: + matchConstraints: + resourceRules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["UPDATE"] + resources: ["pods"] + failurePolicy: Fail + validations: + - expression: "('v1.multus-cni.io/default-network' in oldObject.metadata.annotations) == ('v1.multus-cni.io/default-network' in object.metadata.annotations)" + message: "The 'v1.multus-cni.io/default-network' annotation cannot be changed after the pod was created" +``` + +The `NetworkSelectionElement` structure has an extensive list of fields, this enhancement +focuses only on the following: + +```cgo +type NetworkSelectionElement struct { + // Name contains the name of the Network object this element selects + Name string `json:"name"` + // Namespace contains the optional namespace that the network referenced + // by Name exists in + Namespace string `json:"namespace,omitempty"` + // IPRequest contains an optional requested IP addresses for this network + // attachment + IPRequest []string `json:"ips,omitempty"` + // MacRequest contains an optional requested MAC address for this + // network attachment + MacRequest string `json:"mac,omitempty"` + // IPAMClaimReference container the IPAMClaim name where the IPs for this + // attachment will be located. + IPAMClaimReference string `json:"ipam-claim-reference,omitempty"` +} +``` + +Any other field set in the struct will be ignored by OVN-Kubernetes. + +When using the `v1.multus-cni.io/default-network` annotation, Multus strictly requires its value to reference an +existing NAD. Multus then builds the CNI requests based on it. +This proposal introduces a static default NAD object applied to the cluster. This object will serve as a +stub to generate the CNI calls, preserving the current behavior: + +```yaml +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: default + namespace: ovn-kubernetes +spec: + config: '{"cniVersion": "0.4.0", "name": "ovn-kubernetes", "type": "ovn-k8s-cni-overlay"}' +``` + +With this approach, users must configure the `Name` to `default` and the `Namespace` to `ovn-kubernetes`. +This configuration ensures Multus still references the default network while OVN-Kubernetes will internally use the +primary UDN to handle MAC/IP requests from the NSE. + +> The default NAD object specified above is already used when the default network is exposed through BGP as +part of the route advertisement feature. The proposal is to have it available all the time. + +With `k8s.ovn.org/primary-udn-ipamclaim` being deprecated in favor of the `IPAMClaimReference` field +in the `NetworkSelectionElement` we have to define the expected behavior. To avoid conflicting +settings when `v1.multus-cni.io/default-network` is set the `k8s.ovn.org/primary-udn-ipamclaim` is +going to be ignored, it will be reflected in the opposite scenario for backwards compatibility +with a plan to remove it in a future release. +Deprecation plan for the `k8s.ovn.org/primary-udn-ipamclaim` annotation: + +* release-N - emit a warning event stating that the annotation is deprecated and will be removed in a future release. +* release-N+1 - fail to configure pods with the annotation set. +* release-N+2 - remove any code handling the annotation, effectively ignoring it. + +Note that `GatewayRequest` is not listed, the default gateway is an attribute of the network is not going to be +configurable per pod. + +### Address allocation + +OVN-Kubernetes currently [generates](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/3ef29b9a32b04b7917a0afd6b0e9651d17242ed7/go-controller/pkg/util/net.go#L100-L113) +the overlay MAC addresses from the IPs: + +* IPv4: It takes the four octets of the address (e.g `AA.BB.CC.DD`) and uses them to +create the MAC address with a constant prefix (e.g. `0A:58:AA:BB:CC:DD`). +* IPv6: Computes a SHA256 checksum from the IPv6 string and uses the first four bytes for the MAC +address with the `0A:58` constant prefix(e.g. `0A:58:SHA[0]:SHA[1]:SHA[2]:SHA[3]`). + +Although unlikely, we need to implement logic that ensures that the MAC address requested through +the `NetworkSelectionElement` does not conflict with any other configured address on the UDN +(including addresses consumed by OVN-Kubernetes). + +OVN-Kubernetes already persists the IP and MAC addresses in the `k8s.ovn.org/pod-networks` annotation for each pod: + +```cgo +// PodAnnotation describes the assigned network details for a single pod network. (The +// actual annotation may include the equivalent of multiple PodAnnotations.) +type PodAnnotation struct { +// IPs are the pod's assigned IP addresses/prefixes +IPs []*net.IPNet +// MAC is the pod's assigned MAC address +MAC net.HardwareAddr +// Gateways are the pod's gateway IP addresses; note that there may be +// fewer Gateways than IPs. +Gateways []net.IP + +// GatewayIPv6LLA is the IPv6 Link Local Address for the pod's gateway, that is the address +// that will be set as gateway with router advertisements +// generated from the gateway router from the node where the pod is running. +GatewayIPv6LLA net.IP + +// Routes are additional routes to add to the pod's network namespace +Routes []PodRoute + +// TunnelID assigned to each pod for layer2 secondary networks +TunnelID int + +// Role defines what role this network plays for the given pod. +// Expected values are: +// (1) "primary" if this network is the primary network of the pod. +// The "default" network is the primary network of any pod usually +// unless user-defined-network-segmentation feature has been activated. +// If network segmentation feature is enabled then any user defined +// network can be the primary network of the pod. +// (2) "secondary" if this network is the secondary network of the pod. +// Only user defined networks can be secondary networks for a pod. +// (3) "infrastructure-locked" is applicable only to "default" network if +// a user defined network is the "primary" network for this pod. This +// signifies the "default" network is only used for probing and +// is otherwise locked for all intents and purposes. +// At a given time a pod can have only 1 network with role:"primary" +Role string +} +``` + +This annotation will be used to build an initial cache of allocated addresses at startup, which will then be updated +dynamically at runtime and used for conflict detection. +A similar approach is required for IP address conflict detection. +When a conflict is detected the pod should not start and an appropriate event should be emitted. + +When the `NetworkSelectionElement` contains an `IPAMClaimReference` the referenced IPAMClaim should +reflect the IP allocation status including error reporting through the newly introduced +`Conditions` status field. +In the opposite scenario where the `NetworkSelectionElement` does not specify the `IPAMClaimReference` +the IP allocation is not persisted when the pod is removed. + +### Testing Details + +The following scenarios should be covered in testing: + +* VM workloads import into OVN-Kubernetes with no changes to the instances network configuration. +* Imported VM workloads can live-migrate to another node without any additional traffic disruption. +* 'v1.multus-cni.io/default-network' cannot be changed after the pod was created. +* It should be possible to configure the pods MAC or the IP address without configuring the other. +* When `reservedSubnets` is configured automatic IP allocation should not use addresses specified in it. +* It should be possible to configure the pods IP address using the 'v1.multus-cni.io/default-network' +even if the address is a part of the `reservedSubnets`. +* Requesting an IP address and default gateway IP that is not a part of the networks subnet should fail. +* Detect MAC and IP address conflicts between the requested addresses for a newly created pods and the addresses that +are already allocated in the network. +* After configuring custom default gateway and management addresses on a Layer2 UDN the previous default +IPs can be consumed by workloads(e.g. for 10.0.0.0/16 network create pods with 10.0.0.1 and 10.0.0.2 addresses). +* Modifying the default gateway and management addresses on a Layer2 UDN should not be possible after the network +was created. + +The scenarios mentioned above have to cover both IPv4 and IPv6 IP families. + +### Documentation Details + +## Risks, Known Limitations and Mitigations + +* Modifying the 'v1.multus-cni.io/default-network' value after the pod was created could have unpredictable +consequences. +To mitigate this introduce a Validating Admission Policy described in [Implementation Details](#implementation-details). + +* By allowing users to specify the IP and MAC addresses for the pods there is a risk of conflicts. +To mitigate this OVN-Kubernetes will check that the requested addresses are not currently used in the UDN. +There is still a risk that the user picks an address that's consumed by something outside of the UDN but that's beyond +what OVN-Kubernetes controls and can check. + +* The dynamic, per-node subnet allocation in Layer3 UDNs, where each node has a unique default gateway and +management IP, makes user-specified UDN gateway/management IPs and static pod IP/MAC assignments very complex. This +enhancement will not support Layer3 UDNs. + +* BGP support today is limited to cluster UDNs, to ensure a non-NATed traffic for pods with predefined addresses +the user has to use a cluster UDN to configure the network. This is a limitation unrelated to this enhancement +and it is possible it will be solved in the future. + +* By consuming the 'v1.multus-cni.io/default-network' annotation for altering the primary UDNs pod configuration the +user won't be able to use it for configuring the cluster default network attachment. This is acceptable as there is +currently no support for modifying the cluster default network through this annotation while using primary UDNs. +If there is a requirement in the future another mechanism can be considered. + +* OVN-Kubernetes computes MAC addresses from pod IPs rather than allocating them, which creates potential +MAC address conflicts in a potential scenario where a MAC address previously used by a stopped VM gets consumed by +OVN-Kubernetes for a dynamically allocated IP. To mitigate these conflicts, users will have to use a different +MAC address and recreate the workload. For importing workloads that already use this prefix, a future enhancement +could add a field to the Layer2 spec allowing users to specify a custom MAC prefix for the UDN. + +## OVN Kubernetes Version Skew + +## Alternatives + +* Instead of the [Pod network identity](#pod-network-identity) approach, we could expand the +IPAMClaim API. It currently lacks IP request capabilities, and using IPAMClaim for MAC addresses +is confusing. Introducing a new API would mean deprecating the IPAMClaim, while managing +upgrades and supporting both solutions for a period of time. This requires significant effort, which +is not feasible at this time. + +* As described in the [NetworkSelectionElement annotation](#networkselectionelement-annotation) section, using the +`v1.multus-cni.io/default-network` annotation means Multus strictly requires this annotation's value to reference an +existing NAD. An alternative to the proposed approach would be to reference the NAD that defines the primary network. +It was discarded as it would require OVN-Kubernetes to modify the CNI handling logic because multus +would target the CNI requests towards the custom network. Additionally it would require users to determine the exact +NAD name and namespace for every primary UDN pod needing custom MAC, IP, or IPAMClaim. + +## References + +* [IPAMClaim CRD doc](https://docs.google.com/document/d/1OQIJIrCtsYpR5O44w0hpoJ2TyKBz1Du-KhRT4RtrAjk) - `IPAM allocation on behalf of other entities` section + +* IPAMClaim status conditions pull request: diff --git a/mkdocs.yml b/mkdocs.yml index 9fd08b2c08..e5cd4a3dd6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -148,5 +148,6 @@ nav: - Localnet API: okeps/okep-5085-localnet-api.md - Network QoS: okeps/okep-4380-network-qos.md - User Defined Networks: okeps/okep-5193-user-defined-networks.md + - Preconfigured UDN Addresses: okeps/okep-5233-preconfigured-udn-addresses.md - Blog: - blog/index.md From 261078099929bc7878d691adf62af9ce9af04f63 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Tue, 15 Jul 2025 18:29:08 -0400 Subject: [PATCH 104/278] *.sh: use /usr/bin/env to locate bash On some unconventional systems like NixOS, bash is not installed in /bin/bash and should instead be located via `env`. (Only `/bin/sh` is guaranteed to be present on a POSIX system under `/bin`.) Signed-off-by: Ihar Hrachyshka --- dist/images/daemonset.sh | 2 +- dist/images/ovn-config.sh | 2 +- dist/images/ovn-run.sh | 2 +- dist/images/ovndb-raft-functions.sh | 2 +- dist/images/ovnkube.sh | 2 +- dist/images/push_manifest.sh | 2 +- dist/install-ovn-k8s.sh | 2 +- go-controller/hack/build-go.sh | 2 +- go-controller/hack/init.sh | 2 +- go-controller/hack/regenerate_vendor_mocks.sh | 2 +- go-controller/hack/test-go.sh | 2 +- go-controller/hack/verify-go-mod-vendor.sh | 2 +- go-controller/hack/verify-gofmt.sh | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dist/images/daemonset.sh b/dist/images/daemonset.sh index 7c3daedee9..0613a37238 100755 --- a/dist/images/daemonset.sh +++ b/dist/images/daemonset.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #set -x #Always exit on errors diff --git a/dist/images/ovn-config.sh b/dist/images/ovn-config.sh index 69e2c6471c..42e0e1253a 100755 --- a/dist/images/ovn-config.sh +++ b/dist/images/ovn-config.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run on master to configure ovn-kubernetes # The /etc/openvswitch/ovn_k8s.conf and /etc/sysconfig/ovn-kubernetes diff --git a/dist/images/ovn-run.sh b/dist/images/ovn-run.sh index 9f5acfcdca..d684547434 100755 --- a/dist/images/ovn-run.sh +++ b/dist/images/ovn-run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run the ovs-vswitchd daemon from a container diff --git a/dist/images/ovndb-raft-functions.sh b/dist/images/ovndb-raft-functions.sh index 4d6e124f2d..8737ca3b50 100644 --- a/dist/images/ovndb-raft-functions.sh +++ b/dist/images/ovndb-raft-functions.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #set -euo pipefail verify-ovsdb-raft() { diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 32d3347cd3..d8b8869108 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #set -euo pipefail # Enable verbose shell output if OVNKUBE_SH_VERBOSE is set to 'true' diff --git a/dist/images/push_manifest.sh b/dist/images/push_manifest.sh index f82531df8d..f42c8c30f9 100755 --- a/dist/images/push_manifest.sh +++ b/dist/images/push_manifest.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Currently supported platforms of multi-arch images are: amd64 arm64 LINUX_ARCH=(amd64 arm64) diff --git a/dist/install-ovn-k8s.sh b/dist/install-ovn-k8s.sh index 7ffdc0ac0e..d4b72e8e39 100755 --- a/dist/install-ovn-k8s.sh +++ b/dist/install-ovn-k8s.sh @@ -1,4 +1,4 @@ -#!/bin/bash -ex +#!/usr/bin/env bash -ex # shellcheck disable=SC2016 SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") diff --git a/go-controller/hack/build-go.sh b/go-controller/hack/build-go.sh index 17b963adea..13a12e0bf9 100755 --- a/go-controller/hack/build-go.sh +++ b/go-controller/hack/build-go.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e GO=${GO:-go} diff --git a/go-controller/hack/init.sh b/go-controller/hack/init.sh index 69dcb8f73e..98a210ea71 100755 --- a/go-controller/hack/init.sh +++ b/go-controller/hack/init.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OUT_DIR=${OUT_DIR:-_output} diff --git a/go-controller/hack/regenerate_vendor_mocks.sh b/go-controller/hack/regenerate_vendor_mocks.sh index a94d4481cd..c1e94c89e7 100755 --- a/go-controller/hack/regenerate_vendor_mocks.sh +++ b/go-controller/hack/regenerate_vendor_mocks.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash workdir=$(cd ../ && pwd) substitute_string='pkg/testing/mocks' diff --git a/go-controller/hack/test-go.sh b/go-controller/hack/test-go.sh index b41bdc8817..f2df39f672 100755 --- a/go-controller/hack/test-go.sh +++ b/go-controller/hack/test-go.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e source "$(dirname "${BASH_SOURCE}")/init.sh" diff --git a/go-controller/hack/verify-go-mod-vendor.sh b/go-controller/hack/verify-go-mod-vendor.sh index 39ce9104bc..fd865e965c 100755 --- a/go-controller/hack/verify-go-mod-vendor.sh +++ b/go-controller/hack/verify-go-mod-vendor.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit # Nozero exit code of any of the commands below will fail the test. set -o nounset set -o pipefail diff --git a/go-controller/hack/verify-gofmt.sh b/go-controller/hack/verify-gofmt.sh index de5f65452b..d2c73e47d3 100755 --- a/go-controller/hack/verify-gofmt.sh +++ b/go-controller/hack/verify-gofmt.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit set -o nounset From 1c559bb923d4d85eb1c8ee5ef44ae0c106107465 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Tue, 15 Jul 2025 13:32:53 -0700 Subject: [PATCH 105/278] fix ovspinning test error in our cicd, sometimes we noticed ovspinning unit testing error: === RUN TestAlignCPUAffinity I0715 18:20:46.441374 13095 ovspinning_linux_test.go:65] Test CPU Affinity [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] I0715 18:20:46.441595 13095 ovspinning_linux.go:46] Starting OVS daemon CPU pinning I0715 18:20:46.464072 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13108) (ntasks=15) to 0, was 0-7 I0715 18:20:46.465325 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13107) (ntasks=1) to 0, was 0-7 I0715 18:20:46.484089 13095 ovspinning_linux_test.go:65] Test CPU Affinity [2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] I0715 18:20:46.502727 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13108) (ntasks=15) to 1, was 0 I0715 18:20:46.502910 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13107) (ntasks=1) to 1, was 0 ... I0715 18:20:46.602398 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13108) (ntasks=15) to 5, was 4 I0715 18:20:46.602929 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13107) (ntasks=1) to 5, was 4 I0715 18:20:46.626233 13095 ovspinning_linux_test.go:65] Test CPU Affinity [40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] I0715 18:20:46.642720 13095 ovspinning_linux.go:196] Setting CPU affinity of PID(13108) (ntasks=15) to 6, was 5 ovspinning_linux_test.go:67: Error Trace: /builds/sdn/ovn-kubernetes/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go:251 /builds/sdn/ovn-kubernetes/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go:67 Error: Not equal: expected: unix.CPUSet{0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} actual : unix.CPUSet{0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} Diff: --- Expected +++ Actual @@ -1,3 +1,3 @@ (unix.CPUSet) (len=16) { - (unix.cpuMask) 64, + (unix.cpuMask) 32, (unix.cpuMask) 0, Test: TestAlignCPUAffinity Messages: task[13219] of process[13108] Expected CPUSet [40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] != Actual CPUSet [20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Signed-off-by: Yun Zhou --- .../pkg/node/ovspinning/ovspinning_linux_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go b/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go index 634878d55c..3d9606079f 100644 --- a/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go +++ b/go-controller/pkg/node/ovspinning/ovspinning_linux_test.go @@ -246,10 +246,12 @@ func assertPIDHasSchedAffinity(t *testing.T, pid int, expectedCPUSet unix.CPUSet require.NoError(t, err) for _, task := range tasks { - err := unix.SchedGetaffinity(task, &actual) - require.NoError(t, err) - assert.Equal(t, expectedCPUSet, actual, - "task[%d] of process[%d] Expected CPUSet %0x != Actual CPUSet %0x", task, pid, expectedCPUSet, actual) + assert.Eventually(t, func() bool { + err := unix.SchedGetaffinity(task, &actual) + assert.NoError(t, err) + + return actual == expectedCPUSet + }, time.Second, 10*time.Millisecond, "task[%d] of process[%d] Expected CPUSet %0x != Actual CPUSet %0x", task, pid, expectedCPUSet, actual) } } From f09eca1cfaa23a691141b626fea8f1df498769a6 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 16 Jan 2025 11:38:00 +0100 Subject: [PATCH 106/278] Revert "Add option to disable udn-host-isolation." This reverts commit d87f4800b5593f3de5dce7467e68bea109b1ad85. Signed-off-by: Nadia Pinaeva --- .github/workflows/test.yml | 1 - dist/images/ovnkube.sh | 2 - go-controller/pkg/config/config.go | 27 ++--- .../node/default_node_network_controller.go | 2 +- test/e2e/network_segmentation.go | 98 +++++++++---------- test/e2e/util.go | 5 - 6 files changed, 56 insertions(+), 79 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1782c16b65..480a6345b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -495,7 +495,6 @@ jobs: OVN_SECOND_BRIDGE: "${{ matrix.second-bridge == '2br' }}" ENABLE_MULTI_NET: "${{ matrix.target == 'multi-homing' || matrix.target == 'kv-live-migration' || matrix.target == 'network-segmentation' || matrix.target == 'tools' || matrix.target == 'multi-homing-helm' || matrix.target == 'traffic-flow-test-only' || matrix.routeadvertisements != '' }}" ENABLE_NETWORK_SEGMENTATION: "${{ matrix.target == 'network-segmentation' || matrix.network-segmentation == 'enable-network-segmentation' }}" - DISABLE_UDN_HOST_ISOLATION: "true" PLATFORM_IPV4_SUPPORT: "${{ matrix.ipfamily == 'IPv4' || matrix.ipfamily == 'dualstack' }}" PLATFORM_IPV6_SUPPORT: "${{ matrix.ipfamily == 'IPv6' || matrix.ipfamily == 'dualstack' }}" KIND_INSTALL_KUBEVIRT: "${{ matrix.target == 'kv-live-migration' }}" diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 32d3347cd3..fc5ab6a9b6 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -2162,7 +2162,6 @@ ovnkube-controller-with-node() { --nodeport \ --ovn-metrics-bind-address ${ovn_metrics_bind_address} \ --pidfile ${OVN_RUNDIR}/ovnkube-controller-with-node.pid \ - --disable-udn-host-isolation \ --zone ${ovn_zone} & wait_for_event attempts=3 process_ready ovnkube-controller-with-node @@ -2814,7 +2813,6 @@ ovn-node() { --nodeport \ --ovn-metrics-bind-address ${ovn_metrics_bind_address} \ --pidfile ${OVN_RUNDIR}/ovnkube.pid \ - --disable-udn-host-isolation \ --zone ${ovn_zone} & wait_for_event attempts=3 process_ready ovnkube diff --git a/go-controller/pkg/config/config.go b/go-controller/pkg/config/config.go index 297f18b55f..89757b864b 100644 --- a/go-controller/pkg/config/config.go +++ b/go-controller/pkg/config/config.go @@ -425,18 +425,15 @@ type OVNKubernetesFeatureConfig struct { EnableNetworkSegmentation bool `gcfg:"enable-network-segmentation"` EnablePreconfiguredUDNAddresses bool `gcfg:"enable-preconfigured-udn-addresses"` EnableRouteAdvertisements bool `gcfg:"enable-route-advertisements"` - // This feature requires a kernel fix https://github.com/torvalds/linux/commit/7f3287db654395f9c5ddd246325ff7889f550286 - // to work on a kind cluster. Flag allows to disable it for current CI, will be turned on when github runners have this fix. - DisableUDNHostIsolation bool `gcfg:"disable-udn-host-isolation"` - EnableMultiNetworkPolicy bool `gcfg:"enable-multi-networkpolicy"` - EnableStatelessNetPol bool `gcfg:"enable-stateless-netpol"` - EnableInterconnect bool `gcfg:"enable-interconnect"` - EnableMultiExternalGateway bool `gcfg:"enable-multi-external-gateway"` - EnablePersistentIPs bool `gcfg:"enable-persistent-ips"` - EnableDNSNameResolver bool `gcfg:"enable-dns-name-resolver"` - EnableServiceTemplateSupport bool `gcfg:"enable-svc-template-support"` - EnableObservability bool `gcfg:"enable-observability"` - EnableNetworkQoS bool `gcfg:"enable-network-qos"` + EnableMultiNetworkPolicy bool `gcfg:"enable-multi-networkpolicy"` + EnableStatelessNetPol bool `gcfg:"enable-stateless-netpol"` + EnableInterconnect bool `gcfg:"enable-interconnect"` + EnableMultiExternalGateway bool `gcfg:"enable-multi-external-gateway"` + EnablePersistentIPs bool `gcfg:"enable-persistent-ips"` + EnableDNSNameResolver bool `gcfg:"enable-dns-name-resolver"` + EnableServiceTemplateSupport bool `gcfg:"enable-svc-template-support"` + EnableObservability bool `gcfg:"enable-observability"` + EnableNetworkQoS bool `gcfg:"enable-network-qos"` } // GatewayMode holds the node gateway mode @@ -1087,12 +1084,6 @@ var OVNK8sFeatureFlags = []cli.Flag{ Destination: &cliConfig.OVNKubernetesFeature.EnableMultiNetworkPolicy, Value: OVNKubernetesFeature.EnableMultiNetworkPolicy, }, - &cli.BoolFlag{ - Name: "disable-udn-host-isolation", - Usage: "Configure to disable UDN host isolation with ovn-kubernetes.", - Destination: &cliConfig.OVNKubernetesFeature.DisableUDNHostIsolation, - Value: OVNKubernetesFeature.DisableUDNHostIsolation, - }, &cli.BoolFlag{ Name: "enable-network-segmentation", Usage: "Configure to use network segmentation feature with ovn-kubernetes.", diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index 7a75c36984..a27a611688 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -150,7 +150,7 @@ func newDefaultNodeNetworkController(cnnci *CommonNodeNetworkControllerInfo, sto routeManager: routeManager, ovsClient: ovsClient, } - if util.IsNetworkSegmentationSupportEnabled() && !config.OVNKubernetesFeature.DisableUDNHostIsolation { + if util.IsNetworkSegmentationSupportEnabled() { c.udnHostIsolationManager = NewUDNHostIsolationManager(config.IPv4Mode, config.IPv6Mode, cnnci.watchFactory.PodCoreInformer(), cnnci.name, cnnci.recorder) } diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index 83fc059678..43d7d9fb5a 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -385,52 +385,50 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { }, 10*time.Second, 1*time.Second).Should(BeTrue()) Expect(udnPod.Status.ContainerStatuses[0].RestartCount).To(Equal(int32(0))) - if !isUDNHostIsolationDisabled() { - By("checking default network hostNetwork pod and non-kubelet host process can't reach the UDN pod") - hostNetPod, err := createPod(f, "host-net-pod", nodeName, - defaultNetNamespace, []string{}, nil, func(pod *v1.Pod) { - pod.Spec.HostNetwork = true - }) - Expect(err).NotTo(HaveOccurred()) + By("checking default network hostNetwork pod and non-kubelet host process can't reach the UDN pod") + hostNetPod, err := createPod(f, "host-net-pod", nodeName, + defaultNetNamespace, []string{}, nil, func(pod *v1.Pod) { + pod.Spec.HostNetwork = true + }) + Expect(err).NotTo(HaveOccurred()) - // positive check for reachable default network pod - for _, destIP := range []string{defaultIPv4, defaultIPv6} { - if destIP == "" { - continue - } - By("checking the default network hostNetwork can reach default pod on IP " + destIP) - Eventually(func() bool { - return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetDefaultPort) == nil - }).Should(BeTrue()) - By("checking the non-kubelet host process can reach default pod on IP " + destIP) - Eventually(func() bool { - _, err := infraprovider.Get().ExecK8NodeCommand(nodeName, []string{ - "curl", "--connect-timeout", "2", - net.JoinHostPort(destIP, fmt.Sprintf("%d", podClusterNetDefaultPort)), + // positive check for reachable default network pod + for _, destIP := range []string{defaultIPv4, defaultIPv6} { + if destIP == "" { + continue + } + By("checking the default network hostNetwork can reach default pod on IP " + destIP) + Eventually(func() bool { + return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetDefaultPort) == nil + }).Should(BeTrue()) + By("checking the non-kubelet host process can reach default pod on IP " + destIP) + Eventually(func() bool { + _, err := infraprovider.Get().ExecK8NodeCommand(nodeName, []string{ + "curl", "--connect-timeout", "2", + net.JoinHostPort(destIP, fmt.Sprintf("%d", podClusterNetDefaultPort)), }) - return err == nil - }).Should(BeTrue()) + return err == nil + }).Should(BeTrue()) + } + // negative check for UDN pod + for _, destIP := range []string{udnIPv4, udnIPv6} { + if destIP == "" { + continue } - // negative check for UDN pod - for _, destIP := range []string{udnIPv4, udnIPv6} { - if destIP == "" { - continue - } - By("checking the default network hostNetwork pod can't reach UDN pod on IP " + destIP) - Consistently(func() bool { - return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil - }, 5*time.Second).Should(BeTrue()) + By("checking the default network hostNetwork pod can't reach UDN pod on IP " + destIP) + Consistently(func() bool { + return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil + }, 5*time.Second).Should(BeTrue()) - By("checking the non-kubelet host process can't reach UDN pod on IP " + destIP) - Consistently(func() bool { - _, err := infraprovider.Get().ExecK8NodeCommand(nodeName, []string{ - "curl", "--connect-timeout", "2", - net.JoinHostPort(destIP, fmt.Sprintf("%d", podClusterNetPort)), + By("checking the non-kubelet host process can't reach UDN pod on IP " + destIP) + Consistently(func() bool { + _, err := infraprovider.Get().ExecK8NodeCommand(nodeName, []string{ + "curl", "--connect-timeout", "2", + net.JoinHostPort(destIP, fmt.Sprintf("%d", podClusterNetPort)), }) - return err != nil - }, 5*time.Second).Should(BeTrue()) - } + return err != nil + }, 5*time.Second).Should(BeTrue()) } By("asserting UDN pod can reach the kapi service in the default network") @@ -1645,12 +1643,10 @@ spec: return connectToServer(podConfiguration{namespace: defaultClientPod.Namespace, name: defaultClientPod.Name}, destIP, podClusterNetPort) != nil }, 5*time.Second).Should(BeTrue()) - if !isUDNHostIsolationDisabled() { - By("checking the default hostNetwork pod can't reach UDN pod on IP " + destIP) - Consistently(func() bool { - return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil - }, 5*time.Second).Should(BeTrue()) - } + By("checking the default hostNetwork pod can't reach UDN pod on IP " + destIP) + Consistently(func() bool { + return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil + }, 5*time.Second).Should(BeTrue()) } By("Open UDN pod port") @@ -1695,12 +1691,10 @@ spec: return connectToServer(podConfiguration{namespace: defaultClientPod.Namespace, name: defaultClientPod.Name}, destIP, podClusterNetPort) != nil }, 5*time.Second).Should(BeTrue()) - if !isUDNHostIsolationDisabled() { - By("checking the default hostNetwork pod can't reach UDN pod on IP " + destIP) - Eventually(func() bool { - return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil - }, 5*time.Second).Should(BeTrue()) - } + By("checking the default hostNetwork pod can't reach UDN pod on IP " + destIP) + Eventually(func() bool { + return connectToServer(podConfiguration{namespace: hostNetPod.Namespace, name: hostNetPod.Name}, destIP, podClusterNetPort) != nil + }, 5*time.Second).Should(BeTrue()) } By("Verify syntax error is reported via event") events, err := cs.CoreV1().Events(udnPod.Namespace).List(context.Background(), metav1.ListOptions{}) diff --git a/test/e2e/util.go b/test/e2e/util.go index aba6dcbc44..47d0ba4f91 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1144,11 +1144,6 @@ func isInterconnectEnabled() bool { return present && val == "true" } -func isUDNHostIsolationDisabled() bool { - val, present := os.LookupEnv("DISABLE_UDN_HOST_ISOLATION") - return present && val == "true" -} - func isNetworkSegmentationEnabled() bool { val, present := os.LookupEnv("ENABLE_NETWORK_SEGMENTATION") return present && val == "true" From 99a7b11115e51b2aedd7aecf0e444883524e7c54 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Thu, 16 Jan 2025 16:14:09 +0100 Subject: [PATCH 107/278] Update CI runners to use ubuntu 24.04 Remove nonexistent packages Signed-off-by: Nadia Pinaeva --- .github/workflows/test.yml | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 480a6345b8..e2a0067ee6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: # separate job for parallelism lint: name: Lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@v4 @@ -63,7 +63,7 @@ jobs: build-master: name: Build-master - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: # Create a cache for the built master image - name: Restore master image cache @@ -156,7 +156,7 @@ jobs: build-pr: name: Build-PR - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: # Create a cache for the build PR image - name: Restore PR image cache @@ -271,7 +271,7 @@ jobs: ovn-upgrade-e2e: name: Upgrade OVN from Master to PR branch based image if: github.event_name != 'schedule' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 120 needs: - build-master @@ -319,10 +319,9 @@ jobs: sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ - azure-cli aspnetcore-* dotnet-* ghc-* firefox \ + azure-cli firefox \ google-chrome-stable \ - llvm-* microsoft-edge-stable mono-* \ - msbuild mysql-server-core-* php-* php7* \ + llvm-* microsoft-edge-stable \ powershell temurin-* zulu-* # clean unused packages sudo apt-get autoclean @@ -422,7 +421,7 @@ jobs: e2e: name: e2e - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 # 30 mins for kind, 180 mins for control-plane tests, 10 minutes for all other steps timeout-minutes: 220 strategy: @@ -525,10 +524,9 @@ jobs: sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ - azure-cli aspnetcore-* dotnet-* ghc-* firefox \ + azure-cli firefox \ google-chrome-stable \ - llvm-* microsoft-edge-stable mono-* \ - msbuild mysql-server-core-* php-* php7* \ + llvm-* microsoft-edge-stable \ powershell temurin-* zulu-* # clean unused packages sudo apt-get autoclean @@ -712,7 +710,7 @@ jobs: e2e-dual-conversion: name: e2e-dual-conversion if: github.event_name != 'schedule' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 60 strategy: fail-fast: false @@ -761,10 +759,9 @@ jobs: sudo rm -rf /usr/local/lib/android/sdk sudo apt-get update sudo eatmydata apt-get purge --auto-remove -y \ - azure-cli aspnetcore-* dotnet-* ghc-* firefox \ + azure-cli firefox \ google-chrome-stable \ - llvm-* microsoft-edge-stable mono-* \ - msbuild mysql-server-core-* php-* php7* \ + llvm-* microsoft-edge-stable \ powershell temurin-* zulu-* # clean unused packages sudo apt-get autoclean From 6b01b29b9feb90df4a1f695b455dd274cd9cf127 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 15 Jul 2025 17:33:10 +0200 Subject: [PATCH 108/278] [e2e] Change node ip replacement commands to work on ubuntu 24. If you add a second ip from the same subnet to an interface, it will be considered a secondary IP address and will be deleted together with the primary (aka old) IP. Therefore, remove the primary IP first, then add new one. Routes should be picked up just fine. Signed-off-by: Nadia Pinaeva --- test/e2e/node_ip_mac_migration.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test/e2e/node_ip_mac_migration.go b/test/e2e/node_ip_mac_migration.go index a74d161c0d..0326c2c7b7 100644 --- a/test/e2e/node_ip_mac_migration.go +++ b/test/e2e/node_ip_mac_migration.go @@ -953,26 +953,31 @@ func migrateWorkerNodeIP(nodeName, fromIP, targetIP string, invertOrder bool) (e // Define a function to change the IP address for later use. changeIPAddress := func() error { - // Add new IP first - this will preserve the default route. newIPMask := targetIP + "/" + mask - framework.Logf("Adding new IP address %s to node %s", newIPMask, nodeName) - // Add cleanup command. - cleanupCmd := []string{"ip", "address", "del", newIPMask, "dev", iface} + + // Delete current IP address. If you add a second ip from the same subnet to an interface, it will + // be considered a secondary IP address and will be deleted together with the primary (aka old) IP. + framework.Logf("Deleting current IP address %s from node %s", parsedNetIPMask.String(), nodeName) + // Add cleanup command to add original IP back to the end of the cleanupCommands list. + // This way, we preserve first delete then add new IP sequence. + cleanupCmd := []string{"ip", "address", "add", parsedNetIPMask.String(), "dev", iface} cleanupCommands = append(cleanupCommands, cleanupCmd) // Run command. - _, err = infraprovider.Get().ExecK8NodeCommand(nodeName, []string{"ip", "address", "add", newIPMask, "dev", iface}) + _, err = infraprovider.Get().ExecK8NodeCommand(nodeName, []string{"ip", "address", "del", parsedNetIPMask.String(), "dev", iface}) if err != nil { - return fmt.Errorf("failed to add new IP %s to interface %s on node %s: %v", newIPMask, iface, nodeName, err) + return err } - // Delete current IP address. On rollback, first add the old IP and then delete the new one. - framework.Logf("Deleting current IP address %s from node %s", parsedNetIPMask.String(), nodeName) - // Add cleanup command. - cleanupCmd = []string{"ip", "address", "add", parsedNetIPMask.String(), "dev", iface} + + // Now add new IP. + framework.Logf("Adding new IP address %s to node %s", newIPMask, nodeName) + // Add cleanup command to remove the new IP address to the beginning of the cleanupCommands list. + cleanupCmd = []string{"ip", "address", "del", newIPMask, "dev", iface} cleanupCommands = append([][]string{cleanupCmd}, cleanupCommands...) + // Run command. - _, err = infraprovider.Get().ExecK8NodeCommand(nodeName, []string{"ip", "address", "del", parsedNetIPMask.String(), "dev", iface}) + _, err = infraprovider.Get().ExecK8NodeCommand(nodeName, []string{"ip", "address", "add", newIPMask, "dev", iface}) if err != nil { - return err + return fmt.Errorf("failed to add new IP %s to interface %s on node %s: %v", newIPMask, iface, nodeName, err) } return nil } From ab8d473387ea2eced0bdbc432bbaf46fa988328e Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Mon, 14 Jul 2025 09:54:20 +0200 Subject: [PATCH 109/278] virt: Delete LSP at external process killing VM When something like OOM killer kill the running virt-launcher pod code enter on [1] this prevent ovnk from deleting the LSP so when VM is restarted the traffic is blocked. This change just return nil virtual machine status, since there is no live migration going on. [1] https://github.com/openshift/ovn-kubernetes/blob/release-4.18/go-controller/pkg/kubevirt/pod.go#L475 Signed-off-by: Enrique Llorente --- go-controller/pkg/kubevirt/pod.go | 12 +++-- go-controller/pkg/kubevirt/pod_test.go | 11 ++-- go-controller/pkg/ovn/kubevirt_test.go | 10 ++-- test/e2e/kubevirt.go | 69 +++++++++++++++++++++----- test/e2e/kubevirt/pod.go | 17 +++++++ 5 files changed, 92 insertions(+), 27 deletions(-) diff --git a/go-controller/pkg/kubevirt/pod.go b/go-controller/pkg/kubevirt/pod.go index b0f43ffcbf..901d28ca74 100644 --- a/go-controller/pkg/kubevirt/pod.go +++ b/go-controller/pkg/kubevirt/pod.go @@ -470,11 +470,15 @@ func DiscoverLiveMigrationStatus(client *factory.WatchFactory, pod *corev1.Pod) targetPod := vmPods[len(vmPods)-1] livingPods := filterNotComplete(vmPods) + + // If there is no living pod we should state no live migration status + if len(livingPods) == 0 { + return nil, nil + } + + // There is a living pod but is not the target one so the migration + // has failed. if util.PodCompleted(targetPod) { - // if target pod failed, then there should be only one living source pod. - if len(livingPods) != 1 { - return nil, fmt.Errorf("unexpected live migration state: should have a single living pod") - } return &LiveMigrationStatus{ SourcePod: livingPods[0], TargetPod: targetPod, diff --git a/go-controller/pkg/kubevirt/pod_test.go b/go-controller/pkg/kubevirt/pod_test.go index 8db076019b..2bab9282f8 100644 --- a/go-controller/pkg/kubevirt/pod_test.go +++ b/go-controller/pkg/kubevirt/pod_test.go @@ -98,6 +98,11 @@ var _ = Describe("Kubevirt Pod", func() { pods: []corev1.Pod{successfullyMigratedKvSourcePod, failedMigrationKvTargetPod, successfulMigrationKvTargetPod}, }, ), + Entry("returns nil when there is all the pods are completed (not running vm after migration)", + testParams{ + pods: []corev1.Pod{completedKubevirtPod(t0), completedKubevirtPod(t1), completedKubevirtPod(t3)}, + }, + ), Entry("returns Migration in progress status when 2 pods are running, target pod is not yet ready", testParams{ pods: []corev1.Pod{runningKvSourcePod, duringMigrationKvTargetPod}, @@ -148,12 +153,6 @@ var _ = Describe("Kubevirt Pod", func() { }, }, ), - Entry("returns err when kubevirt VM has several living pods and target pod failed", - testParams{ - pods: []corev1.Pod{runningKvSourcePod, successfulMigrationKvTargetPod, anotherFailedMigrationKvTargetPod}, - expectedError: fmt.Errorf("unexpected live migration state: should have a single living pod"), - }, - ), Entry("returns err when kubevirt VM has several living pods", testParams{ pods: []corev1.Pod{runningKvSourcePod, duringMigrationKvTargetPod, yetAnotherDuringMigrationKvTargetPod}, diff --git a/go-controller/pkg/ovn/kubevirt_test.go b/go-controller/pkg/ovn/kubevirt_test.go index 71293a0763..342aad35d4 100644 --- a/go-controller/pkg/ovn/kubevirt_test.go +++ b/go-controller/pkg/ovn/kubevirt_test.go @@ -146,10 +146,6 @@ var _ = Describe("OVN Kubevirt Operations", func() { addressIPv6: "fd11::3", }, } - logicalSwitch *nbdb.LogicalSwitch - ovnClusterRouter *nbdb.LogicalRouter - logicalRouterPort *nbdb.LogicalRouterPort - migrationSourceLSRP, migrationTargetLSRP *nbdb.LogicalSwitchPort lrpIP = func(network string) string { return strings.Split(network, "/")[0] @@ -497,6 +493,12 @@ var _ = Describe("OVN Kubevirt Operations", func() { Context("during execution", func() { DescribeTable("reconcile migratable vm pods", func(t testData) { + var ( + logicalSwitch *nbdb.LogicalSwitch + ovnClusterRouter *nbdb.LogicalRouter + logicalRouterPort *nbdb.LogicalRouterPort + migrationSourceLSRP, migrationTargetLSRP *nbdb.LogicalSwitchPort + ) _, parsedClusterCIDRIPv4, err := net.ParseCIDR(clusterCIDRIPv4) Expect(err).ToNot(HaveOccurred()) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index d6a774ec4d..4406a4d964 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -50,7 +50,7 @@ import ( e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" testutils "k8s.io/kubernetes/test/utils" utilnet "k8s.io/utils/net" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" crclient "sigs.k8s.io/controller-runtime/pkg/client" butaneconfig "github.com/coreos/butane/config" @@ -794,9 +794,9 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun }).WithPolling(time.Second).WithTimeout(time.Minute).Should(Succeed()) } - waitVirtualMachineInstanceReadiness = func(vmi *kubevirtv1.VirtualMachineInstance) { + waitVirtualMachineInstanceReadinessWith = func(vmi *kubevirtv1.VirtualMachineInstance, conditionStatus corev1.ConditionStatus) { GinkgoHelper() - By(fmt.Sprintf("Waiting for readiness at virtual machine %s", vmi.Name)) + By(fmt.Sprintf("Waiting for readiness=%q at virtual machine %s", conditionStatus, vmi.Name)) Eventually(func() []kubevirtv1.VirtualMachineInstanceCondition { err := crClient.Get(context.Background(), crclient.ObjectKeyFromObject(vmi), vmi) Expect(err).To(SatisfyAny( @@ -807,10 +807,20 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun }).WithPolling(time.Second).WithTimeout(5 * time.Minute).Should( ContainElement(SatisfyAll( HaveField("Type", kubevirtv1.VirtualMachineInstanceReady), - HaveField("Status", corev1.ConditionTrue), + HaveField("Status", conditionStatus), ))) } + waitVirtualMachineInstanceReadiness = func(vmi *kubevirtv1.VirtualMachineInstance) { + GinkgoHelper() + waitVirtualMachineInstanceReadinessWith(vmi, corev1.ConditionTrue) + } + + waitVirtualMachineInstanceFailed = func(vmi *kubevirtv1.VirtualMachineInstance) { + GinkgoHelper() + waitVirtualMachineInstanceReadinessWith(vmi, corev1.ConditionFalse) + } + waitVirtualMachineAddresses = func(vmi *kubevirtv1.VirtualMachineInstance) []kubevirt.Address { GinkgoHelper() step := by(vmi.Name, "Wait for virtual machine to receive IPv4 address from DHCP") @@ -903,7 +913,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun NetworkSource: networkSource, }, }, - TerminationGracePeriodSeconds: pointer.Int64(5), + TerminationGracePeriodSeconds: ptr.To(int64(5)), Volumes: []kubevirtv1.Volume{ { Name: "containerdisk", @@ -929,7 +939,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun GenerateName: vmi.GenerateName, }, Spec: kubevirtv1.VirtualMachineSpec{ - Running: pointer.Bool(true), + RunStrategy: ptr.To(kubevirtv1.RunStrategyAlways), Template: &kubevirtv1.VirtualMachineInstanceTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Annotations: vmi.Annotations, @@ -1414,8 +1424,8 @@ fi Name: "force-post-copy", }, Spec: kvmigrationsv1alpha1.MigrationPolicySpec{ - AllowPostCopy: pointer.Bool(true), - CompletionTimeoutPerGiB: pointer.Int64(1), + AllowPostCopy: ptr.To(true), + CompletionTimeoutPerGiB: ptr.To(int64(1)), BandwidthPerMigration: &bandwidthPerMigration, Selectors: &kvmigrationsv1alpha1.Selectors{ VirtualMachineInstanceSelector: kvmigrationsv1alpha1.LabelSelector{ @@ -2219,15 +2229,20 @@ chpasswd: { expire: False } networkData, err := staticIPsNetworkData(selectCIDRs(vmiIPv4, vmiIPv6)) Expect(err).NotTo(HaveOccurred()) - vmi := fedoraWithTestToolingVMI(nil /*labels*/, nil /*annotations*/, nil /*nodeSelector*/, kubevirtv1.NetworkSource{ + vm := fedoraWithTestToolingVM(nil /*labels*/, nil /*annotations*/, nil /*nodeSelector*/, kubevirtv1.NetworkSource{ Multus: &kubevirtv1.MultusNetwork{ NetworkName: cudn.Name, }, }, userData, networkData) // Harcode mac address so it's the same after live migration - vmi.Spec.Domain.Devices.Interfaces[0].MacAddress = vmiMAC - createVirtualMachineInstance(vmi) - + vm.Spec.Template.Spec.Domain.Devices.Interfaces[0].MacAddress = vmiMAC + createVirtualMachine(vm) + vmi := &kubevirtv1.VirtualMachineInstance{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: vm.Name, + }, + } waitVirtualMachineInstanceReadiness(vmi) Expect(crClient.Get(context.TODO(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) @@ -2253,11 +2268,39 @@ chpasswd: { expire: False } by(vmi.Name, "Running live migration for virtual machine instance") td(vmi) - step = by(vmi.Name, fmt.Sprintf("Login to virtual machine after virtual machine instance live migration")) + // Update vmi status after live migration + Expect(crClient.Get(context.Background(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) + + step = by(vmi.Name, "Login to virtual machine after virtual machine instance live migration") Expect(kubevirt.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) step = by(vmi.Name, "Check east/west traffic after virtual machine instance live migration") checkEastWestIperfTraffic(vmi, testPodsIPs, step) + + By("Stop iperf3 traffic before force killing vm, so iperf3 server do not get stuck") + output, err = kubevirt.RunCommand(vmi, "killall iperf3", 5*time.Second) + Expect(err).ToNot(HaveOccurred(), output) + + step = by(vmi.Name, fmt.Sprintf("Force kill qemu at node %q where VM is running on", vmi.Status.NodeName)) + Expect(kubevirt.ForceKillVirtLauncherAtNode(infraprovider.Get(), vmi.Status.NodeName, vmi.Namespace, vmi.Name)).To(Succeed()) + + step = by(vmi.Name, "Waiting for failed restarted VMI to reach ready state") + waitVirtualMachineInstanceFailed(vmi) + waitVirtualMachineInstanceReadiness(vmi) + Expect(crClient.Get(context.TODO(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) + + step = by(vmi.Name, "Login to virtual machine after virtual machine instance force killed") + Expect(kubevirt.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) + + step = by(vmi.Name, "Restart iperf traffic after forcing a vm failure") + Expect(startEastWestIperfTraffic(vmi, testPodsIPs, step)).To(Succeed(), step) + checkEastWestIperfTraffic(vmi, testPodsIPs, step) + + by(vmi.Name, "Running live migration after forcing vm failure") + td(vmi) + + step = by(vmi.Name, "Check east/west traffic for failed virtual machine after live migration") + checkEastWestIperfTraffic(vmi, testPodsIPs, step) }, Entry("after succeeded live migration", liveMigrateSucceed), Entry("after failed live migration", liveMigrateFailed), diff --git a/test/e2e/kubevirt/pod.go b/test/e2e/kubevirt/pod.go index 5ad2011a1f..1293e1acc5 100644 --- a/test/e2e/kubevirt/pod.go +++ b/test/e2e/kubevirt/pod.go @@ -1,6 +1,9 @@ package kubevirt import ( + "fmt" + + infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -31,3 +34,17 @@ func GenerateFakeVirtLauncherPod(namespace, vmName string) *corev1.Pod { }, } } + +func ForceKillVirtLauncherAtNode(p infraapi.Provider, nodeName, vmNamespace, vmName string) error { + // /usr/bin/virt-launcher --qemu-timeout 312s --name worker-dcf9j --uid bcf975f4-7bdd-4264-948b-b6080320e38a --namespace kv-live-migration-2575 --kubevirt-share-dir /var/run/kubevirt --ephemeral-disk-dir /var/run/kubevirt-ephemeral-disks --container-disk-dir /var/run/kubevirt/container-disks --grace-period-seconds 20 --hook-sidecars 0 --ovmf-path /usr/share/OVMF --run-as-nonroot + killScript := fmt.Sprintf(` +pid=$(pgrep -f 'virt-launcher .*--name %s.*--namespace %s'|grep -v $$) +ps aux |grep virt-launcher +kill -9 $pid +`, vmName, vmNamespace) + output, err := p.ExecK8NodeCommand(nodeName, []string{"bash", "-xe", "-c", killScript}) + if err != nil { + return fmt.Errorf("%s:%w", output, err) + } + return nil +} From 6018f47f7c229274a38d5697dc83e3b56e194ac2 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Fri, 2 May 2025 09:24:33 +0100 Subject: [PATCH 110/278] E2E: lookup support for IP families instead of using env var Using env vars isnt user friendly and it complicates testing downstream. Signed-off-by: Martin Kennelly --- test/e2e/egressip.go | 5 +- test/e2e/kubevirt.go | 26 ++-- test/e2e/multihoming_utils.go | 35 ++++-- test/e2e/network_segmentation.go | 115 +++++++++++------- ...work_segmentation_endpointslices_mirror.go | 14 ++- test/e2e/network_segmentation_localnet.go | 5 +- test/e2e/network_segmentation_policy.go | 8 +- test/e2e/network_segmentation_services.go | 5 +- test/e2e/pod.go | 6 +- test/e2e/route_advertisements.go | 79 ++++++------ test/e2e/util.go | 27 +++- 11 files changed, 193 insertions(+), 132 deletions(-) diff --git a/test/e2e/egressip.go b/test/e2e/egressip.go index 7faad7185e..d9d281aa7b 100644 --- a/test/e2e/egressip.go +++ b/test/e2e/egressip.go @@ -702,6 +702,7 @@ var _ = ginkgo.DescribeTableSubtree("e2e egress IP validation", feature.EgressIP if len(nodes.Items) < 3 { framework.Failf("Test requires >= 3 Ready nodes, but there are only %v nodes", len(nodes.Items)) } + netConfigParams.cidr = filterCIDRsAndJoin(f.ClientSet, netConfigParams.cidr) if isSupported, reason := isNetworkSupported(nodes, netConfigParams); !isSupported { ginkgo.Skip(reason) } @@ -3193,13 +3194,13 @@ spec: ginkgo.Entry("L3 Primary UDN", networkAttachmentConfigParams{ name: "l3primary", topology: types.Layer3Topology, - cidr: correctCIDRFamily("30.10.0.0/16", "2014:100:200::0/60"), + cidr: joinCIDRs("30.10.0.0/16", "2014:100:200::0/60"), role: "primary", }), ginkgo.Entry("L2 Primary UDN", networkAttachmentConfigParams{ name: "l2primary", topology: types.Layer2Topology, - cidr: correctCIDRFamily("10.10.0.0/16", "2014:100:200::0/60"), + cidr: joinCIDRs("10.10.0.0/16", "2014:100:200::0/60"), role: "primary", }), ) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index d6a774ec4d..30c5fd9546 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1699,8 +1699,8 @@ write_files: namespace = fr.Namespace.Name networkName := "" - cidrs := generateL2Subnets(cidrIPv4, cidrIPv6) - cudn, networkName = kubevirt.GenerateCUDN(namespace, "net1", td.topology, td.role, cidrs) + dualCIDRs := filterDualStackCIDRs(fr.ClientSet, []udnv1.CIDR{udnv1.CIDR(cidrIPv4), udnv1.CIDR(cidrIPv6)}) + cudn, networkName = kubevirt.GenerateCUDN(namespace, "net1", td.topology, td.role, dualCIDRs) if td.topology == udnv1.NetworkTopologyLocalnet { By("setting up the localnet underlay") @@ -1809,7 +1809,7 @@ ip route add %[3]s via %[4]s // expect 2 addresses on dual-stack deployments; 1 on single-stack step = by(vmi.Name, "Wait for addresses at the virtual machine") - expectedNumberOfAddresses := len(cidrs) + expectedNumberOfAddresses := len(dualCIDRs) expectedAddreses := virtualMachineAddressesFromStatus(vmi, expectedNumberOfAddresses) expectedAddresesAtGuest := expectedAddreses testPodsIPs := podsMultusNetworkIPs(iperfServerTestPods, podNetworkStatusByNetConfigPredicate(namespace, cudn.Name, strings.ToLower(string(td.role)))) @@ -1836,7 +1836,7 @@ ip route add %[3]s via %[4]s checkEastWestIperfTraffic(vmi, testPodsIPs, step) if td.role == udnv1.NetworkRolePrimary { - if isIPv6Supported() && isInterconnectEnabled() { + if isIPv6Supported(fr.ClientSet) && isInterconnectEnabled() { step = by(vmi.Name, fmt.Sprintf("Checking IPv6 gateway before %s %s", td.resource.description, td.test.description)) nodeRunningVMI, err := fr.ClientSet.CoreV1().Nodes().Get(context.Background(), vmi.Status.NodeName, metav1.GetOptions{}) @@ -1906,7 +1906,7 @@ ip route add %[3]s via %[4]s } if td.role == udnv1.NetworkRolePrimary && td.test.description == liveMigrate.description && isInterconnectEnabled() { - if isIPv4Supported() { + if isIPv4Supported(fr.ClientSet) { step = by(vmi.Name, fmt.Sprintf("Checking IPv4 gateway cached mac after %s %s", td.resource.description, td.test.description)) Expect(crClient.Get(context.TODO(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) @@ -1923,7 +1923,7 @@ ip route add %[3]s via %[4]s WithPolling(time.Second). Should(Equal(expectedGatewayMAC), step) } - if isIPv6Supported() { + if isIPv6Supported(fr.ClientSet) { step = by(vmi.Name, fmt.Sprintf("Checking IPv6 gateway after %s %s", td.resource.description, td.test.description)) targetNode, err := fr.ClientSet.CoreV1().Nodes().Get(context.Background(), vmi.Status.MigrationState.TargetNode, metav1.GetOptions{}) @@ -2055,8 +2055,8 @@ ip route add %[3]s via %[4]s }) fr.Namespace = ns namespace = fr.Namespace.Name - cidrs := generateL2Subnets(cidrIPv4, cidrIPv6) - cudn, _ := kubevirt.GenerateCUDN(namespace, "net1", udnv1.NetworkTopologyLayer2, udnv1.NetworkRolePrimary, cidrs) + dualCIDRs := filterDualStackCIDRs(fr.ClientSet, []udnv1.CIDR{udnv1.CIDR(cidrIPv4), udnv1.CIDR(cidrIPv6)}) + cudn, _ := kubevirt.GenerateCUDN(namespace, "net1", udnv1.NetworkTopologyLayer2, udnv1.NetworkRolePrimary, dualCIDRs) cudn.Spec.Network.Layer2.MTU = 1300 createCUDN(cudn) @@ -2097,7 +2097,7 @@ ip route add %[3]s via %[4]s Get(context.Background(), config.Kubernetes.DNSServiceName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - if isIPv4Supported() { + if isIPv4Supported(fr.ClientSet) { expectedIP, err := matchIPv4StringFamily(primaryUDNNetworkStatus.IPs) Expect(err).NotTo(HaveOccurred()) @@ -2125,7 +2125,7 @@ ip route add %[3]s via %[4]s Expect(primaryUDNValueForDevice("GENERAL.MTU")).To(ConsistOf("1300")) } - if isIPv6Supported() { + if isIPv6Supported(fr.ClientSet) { expectedIP, err := matchIPv6StringFamily(primaryUDNNetworkStatus.IPs) Expect(err).NotTo(HaveOccurred()) Eventually(primaryUDNValueFor). @@ -2164,7 +2164,7 @@ ip route add %[3]s via %[4]s vmiIPv4 = "10.128.0.100/24" vmiIPv6 = "2010:100:200::100/60" vmiMAC = "0A:58:0A:80:00:64" - cidr = selectCIDRs(ipv4CIDR, ipv6CIDR) + cidrs = []string{ipv4CIDR, ipv6CIDR} staticIPsNetworkData = func(ips []string) (string, error) { type Ethernet struct { Addresses []string `json:"addresses,omitempty"` @@ -2213,10 +2213,10 @@ chpasswd: { expire: False } selectedNodes = workerNodeList.Items Expect(selectedNodes).NotTo(BeEmpty()) - iperfServerTestPods, err = createIperfServerPods(selectedNodes, cudn.Name, cudn.Spec.Network.Localnet.Role, cidr) + iperfServerTestPods, err = createIperfServerPods(selectedNodes, cudn.Name, cudn.Spec.Network.Localnet.Role, filterCIDRs(fr.ClientSet, cidrs...)) Expect(err).NotTo(HaveOccurred()) - networkData, err := staticIPsNetworkData(selectCIDRs(vmiIPv4, vmiIPv6)) + networkData, err := staticIPsNetworkData(filterCIDRs(fr.ClientSet, vmiIPv4, vmiIPv6)) Expect(err).NotTo(HaveOccurred()) vmi := fedoraWithTestToolingVMI(nil /*labels*/, nil /*annotations*/, nil /*nodeSelector*/, kubevirtv1.NetworkSource{ diff --git a/test/e2e/multihoming_utils.go b/test/e2e/multihoming_utils.go index 636ea78eba..2fb10354d4 100644 --- a/test/e2e/multihoming_utils.go +++ b/test/e2e/multihoming_utils.go @@ -29,24 +29,33 @@ func netCIDR(netCIDR string, netPrefixLengthPerNode int) string { return fmt.Sprintf("%s/%d", netCIDR, netPrefixLengthPerNode) } -// takes ipv4 and ipv6 cidrs and returns the correct type for the cluster under test -func correctCIDRFamily(ipv4CIDR, ipv6CIDR string) string { - return strings.Join(selectCIDRs(ipv4CIDR, ipv6CIDR), ",") +func joinCIDRs(cidrs ...string) string { + return strings.Join(cidrs, ",") } -// takes ipv4 and ipv6 cidrs and returns the correct type for the cluster under test -func selectCIDRs(ipv4CIDR, ipv6CIDR string) []string { - // dual stack cluster - if isIPv6Supported() && isIPv4Supported() { - return []string{ipv4CIDR, ipv6CIDR} +func splitCIDRs(cidrs string) []string { + if cidrs == "" { + return []string{} } - // is an ipv6 only cluster - if isIPv6Supported() { - return []string{ipv6CIDR} + return strings.Split(cidrs, ",") +} + +func filterCIDRsAndJoin(cs clientset.Interface, cidrs string) string { + if cidrs == "" { + return "" // we may not always set CIDR - i.e. CDN } + return joinCIDRs(filterCIDRs(cs, splitCIDRs(cidrs)...)...) +} - //ipv4 only cluster - return []string{ipv4CIDR} +func filterCIDRs(cs clientset.Interface, cidrs ...string) []string { + var supportedCIDRs []string + for _, cidr := range cidrs { + if !isCIDRIPFamilySupported(cs, cidr) { + continue + } + supportedCIDRs = append(supportedCIDRs, cidr) + } + return supportedCIDRs } func getNetCIDRSubnet(netCIDR string) (string, error) { diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index 12c6da9ee9..4a9db3330d 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -10,6 +10,7 @@ import ( "strings" "time" + udnv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" @@ -90,6 +91,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { func(netConfig *networkAttachmentConfigParams) { By("creating the network") netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) Expect(createNetworkFn(netConfig)).To(Succeed()) By("creating a pod on the udn namespace") @@ -126,7 +128,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, ), @@ -134,7 +136,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, ), @@ -157,6 +159,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { By("creating the network") netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) Expect(createNetworkFn(netConfig)).To(Succeed()) By("creating client/server pods") @@ -198,7 +201,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -216,7 +219,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -268,6 +271,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { By("creating the network") netConfigParams.namespace = f.Namespace.Name + netConfigParams.cidr = filterCIDRsAndJoin(f.ClientSet, netConfigParams.cidr) Expect(createNetworkFn(netConfigParams)).To(Succeed()) udnPodConfig.namespace = f.Namespace.Name @@ -496,7 +500,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -511,7 +515,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { &networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -565,11 +569,12 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { netConfig := &networkAttachmentConfigParams{ topology: topology, - cidr: correctCIDRFamily(userDefinedv4Subnet, userDefinedv6Subnet), + cidr: joinCIDRs(userDefinedv4Subnet, userDefinedv6Subnet), role: "primary", namespace: namespace, name: network, } + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) Expect(createNetworkFn(netConfig)).To(Succeed()) // update the name because createNetworkFn may mutate the netConfig.name @@ -714,16 +719,18 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { name: "tenant-blue", namespace: f.Namespace.Name, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", } + netConfig1.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig1.cidr) netConfig2 := networkAttachmentConfigParams{ name: "blue", namespace: f.Namespace.Name + "-tenant", topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", } + netConfig2.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig2.cidr) nodes, err := e2enode.GetBoundedReadySchedulableNodes(context.TODO(), cs, 2) framework.ExpectNoError(err) if len(nodes.Items) < 2 { @@ -836,6 +843,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { DescribeTable("should be able to send multicast UDP traffic between nodes", func(netConfigParams networkAttachmentConfigParams) { ginkgo.By("creating the attachment configuration") netConfigParams.namespace = f.Namespace.Name + netConfigParams.cidr = filterCIDRsAndJoin(cs, netConfigParams.cidr) netConfig := newNetworkAttachmentConfig(netConfigParams) _, err := nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( context.Background(), @@ -848,19 +856,20 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { ginkgo.Entry("with primary layer3 UDN", networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }), ginkgo.Entry("with primary layer2 UDN", networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }), ) DescribeTable("should be able to receive multicast IGMP query", func(netConfigParams networkAttachmentConfigParams) { ginkgo.By("creating the attachment configuration") netConfigParams.namespace = f.Namespace.Name + netConfigParams.cidr = filterCIDRsAndJoin(cs, netConfigParams.cidr) netConfig := newNetworkAttachmentConfig(netConfigParams) _, err := nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( context.Background(), @@ -873,14 +882,14 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { ginkgo.Entry("with primary layer3 UDN", networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }), // TODO: this test is broken, see https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5309 //ginkgo.Entry("with primary layer2 UDN", networkAttachmentConfigParams{ // name: nadName, // topology: "layer2", - // cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + // cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), // role: "primary", //}), ) @@ -910,7 +919,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { Expect(err).NotTo(HaveOccurred()) By("create tests UserDefinedNetwork") - cleanup, err := createManifest(defaultNetNamespace.Name, newPrimaryUserDefinedNetworkManifest(testUdnName)) + cleanup, err := createManifest(defaultNetNamespace.Name, newPrimaryUserDefinedNetworkManifest(cs, testUdnName)) DeferCleanup(cleanup) Expect(err).NotTo(HaveOccurred()) Eventually(userDefinedNetworkReadyFunc(f.DynamicClient, defaultNetNamespace.Name, testUdnName), 5*time.Second).Should(Not(Succeed())) @@ -1127,13 +1136,13 @@ spec: topology: "layer3", name: primaryNadName, networkName: primaryNadName, - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), })) _, err := nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create(context.Background(), primaryNetNad, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("create primary network UserDefinedNetwork") - cleanup, err := createManifest(f.Namespace.Name, newPrimaryUserDefinedNetworkManifest(primaryUdnName)) + cleanup, err := createManifest(f.Namespace.Name, newPrimaryUserDefinedNetworkManifest(cs, primaryUdnName)) DeferCleanup(cleanup) Expect(err).NotTo(HaveOccurred()) @@ -1425,14 +1434,14 @@ spec: topology: "layer3", name: primaryNadName, networkName: primaryNadName, - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), })) _, err := nadClient.NetworkAttachmentDefinitions(primaryNetTenantNs).Create(context.Background(), primaryNetNad, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("create primary Cluster UDN CR") cudnName := randomNetworkMetaName() - cleanup, err := createManifest(f.Namespace.Name, newPrimaryClusterUDNManifest(cudnName, testTenantNamespaces...)) + cleanup, err := createManifest(f.Namespace.Name, newPrimaryClusterUDNManifest(cs, cudnName, testTenantNamespaces...)) DeferCleanup(func() { cleanup() _, err := e2ekubectl.RunKubectl("", "delete", "clusteruserdefinednetwork", cudnName, "--wait", fmt.Sprintf("--timeout=%ds", 60)) @@ -1494,6 +1503,7 @@ spec: By("creating the network") netConfigParams.namespace = f.Namespace.Name + netConfigParams.cidr = filterCIDRsAndJoin(f.ClientSet, netConfigParams.cidr) Expect(createNetworkFn(netConfigParams)).To(Succeed()) By("instantiating the client pod") @@ -1521,15 +1531,15 @@ spec: Expect(err).NotTo(HaveOccurred()) framework.Logf("Client pod's annotation for network %s is %v", netConfigParams.name, podAnno) - Expect(podAnno.Routes).To(HaveLen(expectedNumberOfRoutes(*netConfigParams))) + Expect(podAnno.Routes).To(HaveLen(expectedNumberOfRoutes(cs, *netConfigParams))) - assertClientExternalConnectivity(clientPodConfig, externalContainer.GetIPv4(), externalContainer.GetIPv6(), externalContainer.GetPort()) + assertClientExternalConnectivity(cs, clientPodConfig, externalContainer.GetIPv4(), externalContainer.GetIPv6(), externalContainer.GetPort()) }, Entry("by one pod over a layer2 network", &networkAttachmentConfigParams{ name: userDefinedNetworkName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig("client-pod"), @@ -1538,7 +1548,7 @@ spec: &networkAttachmentConfigParams{ name: userDefinedNetworkName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig("client-pod"), @@ -1585,7 +1595,7 @@ spec: BeforeEach(func() { By("create tests UserDefinedNetwork") - cleanup, err := createManifest(f.Namespace.Name, newPrimaryUserDefinedNetworkManifest(testUdnName)) + cleanup, err := createManifest(f.Namespace.Name, newPrimaryUserDefinedNetworkManifest(cs, testUdnName)) DeferCleanup(cleanup) Expect(err).NotTo(HaveOccurred()) Eventually(userDefinedNetworkReadyFunc(f.DynamicClient, f.Namespace.Name, testUdnName), 5*time.Second, time.Second).Should(Succeed()) @@ -1732,6 +1742,7 @@ spec: } By("creating the network") netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) udnManifest := generateUserDefinedNetworkManifest(&netConfig) cleanup, err := createManifest(netConfig.namespace, udnManifest) Expect(err).ShouldNot(HaveOccurred(), "creating manifest must succeed") @@ -1770,7 +1781,7 @@ spec: networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -1788,7 +1799,7 @@ spec: networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -2152,7 +2163,7 @@ spec: ` } -func newPrimaryClusterUDNManifest(name string, targetNamespaces ...string) string { +func newPrimaryClusterUDNManifest(cs clientset.Interface, name string, targetNamespaces ...string) string { targetNs := strings.Join(targetNamespaces, ",") return ` apiVersion: k8s.ovn.org/v1 @@ -2169,7 +2180,7 @@ spec: topology: Layer3 layer3: role: Primary - subnets: ` + generateCIDRforClusterUDN("10.20.100.0/16", "2014:100:200::0/60") + subnets: ` + generateCIDRforClusterUDN(cs, "10.20.100.0/16", "2014:100:200::0/60") } func newL2SecondaryUDNManifest(name string) string { @@ -2186,7 +2197,7 @@ spec: ` } -func newPrimaryUserDefinedNetworkManifest(name string) string { +func newPrimaryUserDefinedNetworkManifest(cs clientset.Interface, name string) string { return ` apiVersion: k8s.ovn.org/v1 kind: UserDefinedNetwork @@ -2196,19 +2207,19 @@ spec: topology: Layer3 layer3: role: Primary - subnets: ` + generateCIDRforUDN("10.20.100.0/16", "2014:100:200::0/60") + subnets: ` + generateCIDRforUDN(cs, "10.20.100.0/16", "2014:100:200::0/60") } -func generateCIDRforUDN(v4, v6 string) string { +func generateCIDRforUDN(cs clientset.Interface, v4, v6 string) string { cidr := ` - cidr: ` + v4 + ` ` - if isIPv6Supported() && isIPv4Supported() { + if isIPv6Supported(cs) && isIPv4Supported(cs) { cidr = ` - cidr: ` + v4 + ` - cidr: ` + v6 + ` ` - } else if isIPv6Supported() { + } else if isIPv6Supported(cs) { cidr = ` - cidr: ` + v6 + ` ` @@ -2216,11 +2227,33 @@ func generateCIDRforUDN(v4, v6 string) string { return cidr } -func generateCIDRforClusterUDN(v4, v6 string) string { +func filterDualStackCIDRs(cs clientset.Interface, cidrs udnv1.DualStackCIDRs) udnv1.DualStackCIDRs { + filteredCIDRs := make(udnv1.DualStackCIDRs, 0, len(cidrs)) + for _, cidr := range cidrs { + if !isCIDRIPFamilySupported(cs, string(cidr)) { + continue + } + filteredCIDRs = append(filteredCIDRs, cidr) + } + return filteredCIDRs +} + +func filterL3Subnets(cs clientset.Interface, l3Subnets []udnv1.Layer3Subnet) []udnv1.Layer3Subnet { + filteredL3Subnets := make([]udnv1.Layer3Subnet, 0, len(l3Subnets)) + for _, l3Subnet := range l3Subnets { + if !isCIDRIPFamilySupported(cs, string(l3Subnet.CIDR)) { + continue + } + filteredL3Subnets = append(filteredL3Subnets, l3Subnet) + } + return filteredL3Subnets +} + +func generateCIDRforClusterUDN(cs clientset.Interface, v4, v6 string) string { cidr := `[{cidr: ` + v4 + `}]` - if isIPv6Supported() && isIPv4Supported() { + if isIPv6Supported(cs) && isIPv4Supported(cs) { cidr = `[{cidr: ` + v4 + `},{cidr: ` + v6 + `}]` - } else if isIPv6Supported() { + } else if isIPv6Supported(cs) { cidr = `[{cidr: ` + v6 + `}]` } return cidr @@ -2344,15 +2377,15 @@ func connectToServerViaDefaultNetwork(clientPodConfig podConfiguration, serverIP } // assertClientExternalConnectivity checks if the client can connect to an externally created IP outside the cluster -func assertClientExternalConnectivity(clientPodConfig podConfiguration, externalIpv4 string, externalIpv6 string, port uint16) { - if isIPv4Supported() { +func assertClientExternalConnectivity(cs clientset.Interface, clientPodConfig podConfiguration, externalIpv4 string, externalIpv6 string, port uint16) { + if isIPv4Supported(cs) { By("asserting the *client* pod can contact the server's v4 IP located outside the cluster") Eventually(func() error { return connectToServer(clientPodConfig, externalIpv4, port) }, 2*time.Minute, 6*time.Second).Should(Succeed()) } - if isIPv6Supported() { + if isIPv6Supported(cs) { By("asserting the *client* pod can contact the server's v6 IP located outside the cluster") Eventually(func() error { return connectToServer(clientPodConfig, externalIpv6, port) @@ -2360,15 +2393,15 @@ func assertClientExternalConnectivity(clientPodConfig podConfiguration, external } } -func expectedNumberOfRoutes(netConfig networkAttachmentConfigParams) int { +func expectedNumberOfRoutes(cs clientset.Interface, netConfig networkAttachmentConfigParams) int { if netConfig.topology == "layer2" { - if isIPv6Supported() && isIPv4Supported() { + if isIPv6Supported(cs) && isIPv4Supported(cs) { return 4 // 2 routes per family } else { return 2 //one family supported } } - if isIPv6Supported() && isIPv4Supported() { + if isIPv6Supported(cs) && isIPv4Supported(cs) { return 6 // 3 v4 routes + 3 v6 routes for UDN } return 3 //only one family, each has 3 routes diff --git a/test/e2e/network_segmentation_endpointslices_mirror.go b/test/e2e/network_segmentation_endpointslices_mirror.go index 3790b2d568..fddadb1cea 100644 --- a/test/e2e/network_segmentation_endpointslices_mirror.go +++ b/test/e2e/network_segmentation_endpointslices_mirror.go @@ -60,6 +60,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ ) { By("creating the network") netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) Expect(createNetworkFn(netConfig)).To(Succeed()) replicas := int32(3) @@ -125,7 +126,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, false, @@ -135,7 +136,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, false, @@ -145,7 +146,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, true, @@ -155,7 +156,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, true, @@ -195,6 +196,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ Expect(err).NotTo(HaveOccurred()) By("creating the network") netConfig.namespace = defaultNetNamespace.Name + netConfig.cidr = filterCIDRsAndJoin(f.ClientSet, netConfig.cidr) Expect(createNetworkFn(netConfig)).To(Succeed()) replicas := int32(3) @@ -233,7 +235,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "secondary", }, ), @@ -242,7 +244,7 @@ var _ = Describe("Network Segmentation EndpointSlices mirroring", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "secondary", }, ), diff --git a/test/e2e/network_segmentation_localnet.go b/test/e2e/network_segmentation_localnet.go index a6b68db97c..1647baa9fa 100644 --- a/test/e2e/network_segmentation_localnet.go +++ b/test/e2e/network_segmentation_localnet.go @@ -67,9 +67,10 @@ var _ = Describe("Network Segmentation: Localnet", func() { name: cudnName, physicalNetworkName: physicalNetworkName, vlanID: vlan, - cidr: correctCIDRFamily(subnetIPv4, subnetIPv6), - excludeCIDRs: selectCIDRs(excludeSubnetIPv4, excludeSubnetIPv6), + cidr: filterCIDRsAndJoin(f.ClientSet, joinCIDRs(subnetIPv4, subnetIPv6)), + excludeCIDRs: filterCIDRs(f.ClientSet, excludeSubnetIPv4, excludeSubnetIPv6), } + cudnYAML := newLocalnetCUDNYaml(netConf, nsBlue, nsRed) cleanup, err := createManifest("", cudnYAML) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/network_segmentation_policy.go b/test/e2e/network_segmentation_policy.go index f00dd63bec..ffcf5f728a 100644 --- a/test/e2e/network_segmentation_policy.go +++ b/test/e2e/network_segmentation_policy.go @@ -85,6 +85,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ ginkgo.By("Creating the attachment configuration") netConfig := newNetworkAttachmentConfig(netConfigParams) netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(cs, netConfig.cidr) _, err := nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( context.Background(), generateNAD(netConfig), @@ -137,7 +138,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -157,7 +158,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, *podConfig( @@ -190,7 +191,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ nad := networkAttachmentConfigParams{ topology: topology, - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: filterCIDRsAndJoin(cs, joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet)), // The yellow, blue and red namespaces are going to served by green network. // Use random suffix for the network name to avoid race between tests. networkName: fmt.Sprintf("%s-%s", "green", rand.String(randomStringLength)), @@ -202,6 +203,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ for _, namespace := range []string{namespaceYellow, namespaceBlue} { ginkgo.By("creating the attachment configuration for " + netConfName + " in namespace " + namespace) netConfig := newNetworkAttachmentConfig(nad) + netConfig.cidr = filterCIDRsAndJoin(cs, netConfig.cidr) netConfig.namespace = namespace netConfig.name = netConfName diff --git a/test/e2e/network_segmentation_services.go b/test/e2e/network_segmentation_services.go index 6f0822064f..8d2678c178 100644 --- a/test/e2e/network_segmentation_services.go +++ b/test/e2e/network_segmentation_services.go @@ -113,6 +113,7 @@ var _ = Describe("Network Segmentation: services", feature.NetworkSegmentation, By("Creating the attachment configuration") netConfig := newNetworkAttachmentConfig(netConfigParams) netConfig.namespace = f.Namespace.Name + netConfig.cidr = filterCIDRsAndJoin(cs, netConfig.cidr) _, err = nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( context.Background(), generateNAD(netConfig), @@ -268,7 +269,7 @@ ips=$(ip -o addr show dev $iface| grep global |awk '{print $4}' | cut -d/ -f1 | networkAttachmentConfigParams{ name: nadName, topology: "layer3", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, ), @@ -277,7 +278,7 @@ ips=$(ip -o addr show dev $iface| grep global |awk '{print $4}' | cut -d/ -f1 | networkAttachmentConfigParams{ name: nadName, topology: "layer2", - cidr: correctCIDRFamily(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), + cidr: joinCIDRs(userDefinedNetworkIPv4Subnet, userDefinedNetworkIPv6Subnet), role: "primary", }, ), diff --git a/test/e2e/pod.go b/test/e2e/pod.go index f5b7b12aae..e43ecee03a 100644 --- a/test/e2e/pod.go +++ b/test/e2e/pod.go @@ -109,11 +109,11 @@ var _ = ginkgo.Describe("Pod to external server PMTUD", func() { ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external container (%s)", externalContainer) - if isIPv4Supported() { + if isIPv4Supported(f.ClientSet) { gomega.Expect(externalContainer.GetIPv4()).ToNot(gomega.BeEmpty()) externalContainerIPs = append(externalContainerIPs, externalContainer.GetIPv4()) } - if isIPv6Supported() { + if isIPv6Supported(f.ClientSet) { gomega.Expect(externalContainer.GetIPv6()).ToNot(gomega.BeEmpty()) externalContainerIPs = append(externalContainerIPs, fmt.Sprintf("[%s]", externalContainer.GetIPv6())) } @@ -155,7 +155,7 @@ var _ = ginkgo.Describe("Pod to external server PMTUD", func() { primaryInf, err := infraprovider.Get().GetK8NodeNetworkInterface(clientPodNodeName, providerPrimaryNetwork) framework.ExpectNoError(err, "failed to get provider primary network interface info") clientnodeIP := primaryInf.IPv4 - if IsIPv6Cluster(f.ClientSet) && isIPv6Supported() { + if IsIPv6Cluster(f.ClientSet) && isIPv6Supported(f.ClientSet) { clientnodeIP = fmt.Sprintf("[%s]", primaryInf.IPv6) } gomega.Expect(clientnodeIP).NotTo(gomega.BeEmpty()) diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index f6dcdfc800..56bb25ccc6 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -53,10 +53,10 @@ var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is bgpServer := infraapi.ExternalContainer{Name: serverContainerName} networkInterface, err := infraprovider.Get().GetExternalContainerNetworkInterface(bgpServer, bgpNetwork) framework.ExpectNoError(err, "container %s attached to network %s must contain network info", serverContainerName, bgpExternalNetworkName) - if isIPv4Supported() && len(networkInterface.IPv4) > 0 { + if isIPv4Supported(f.ClientSet) && len(networkInterface.IPv4) > 0 { serverContainerIPs = append(serverContainerIPs, networkInterface.IPv4) } - if isIPv6Supported() && len(networkInterface.IPv6) > 0 { + if isIPv6Supported(f.ClientSet) && len(networkInterface.IPv6) > 0 { serverContainerIPs = append(serverContainerIPs, networkInterface.IPv6) } framework.Logf("The external server IPs are: %+v", serverContainerIPs) @@ -219,7 +219,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is 60*time.Second) framework.ExpectNoError(err, fmt.Sprintf("Testing pod to external traffic failed: %v", err)) expectedPodIP := podv4IP - if isIPv6Supported() && utilnet.IsIPv6String(serverContainerIP) { + if isIPv6Supported(f.ClientSet) && utilnet.IsIPv6String(serverContainerIP) { expectedPodIP = podv6IP // For IPv6 addresses, need to handle the brackets in the output outputIP := strings.TrimPrefix(strings.Split(stdout, "]:")[0], "[") @@ -267,10 +267,10 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert bgpServer := infraapi.ExternalContainer{Name: serverContainerName} networkInterface, err := infraprovider.Get().GetExternalContainerNetworkInterface(bgpServer, bgpNetwork) framework.ExpectNoError(err, "container %s attached to network %s must contain network info", serverContainerName, bgpExternalNetworkName) - if isIPv4Supported() && len(networkInterface.IPv4) > 0 { + if isIPv4Supported(f.ClientSet) && len(networkInterface.IPv4) > 0 { serverContainerIPs = append(serverContainerIPs, networkInterface.IPv4) } - if isIPv6Supported() && len(networkInterface.IPv6) > 0 { + if isIPv6Supported(f.ClientSet) && len(networkInterface.IPv6) > 0 { serverContainerIPs = append(serverContainerIPs, networkInterface.IPv6) } gomega.Expect(len(serverContainerIPs)).Should(gomega.BeNumerically(">", 0), "failed to find external container IPs") @@ -308,6 +308,12 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert ginkgo.By("create ClusterUserDefinedNetwork") udnClient, err := udnclientset.NewForConfig(f.ClientConfig()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + if cudnTemplate.Spec.Network.Layer3 != nil { + cudnTemplate.Spec.Network.Layer3.Subnets = filterL3Subnets(f.ClientSet, cudnTemplate.Spec.Network.Layer3.Subnets) + } + if cudnTemplate.Spec.Network.Layer2 != nil { + cudnTemplate.Spec.Network.Layer2.Subnets = filterDualStackCIDRs(f.ClientSet, cudnTemplate.Spec.Network.Layer2.Subnets) + } cUDN, err := udnClient.K8sV1().ClusterUserDefinedNetworks().Create(context.Background(), cudnTemplate, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.DeferCleanup(func() { @@ -418,7 +424,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert framework.Poll, 60*time.Second) framework.ExpectNoError(err, fmt.Sprintf("Testing pod to external traffic failed: %v", err)) - if isIPv6Supported() && utilnet.IsIPv6String(serverContainerIP) { + if isIPv6Supported(f.ClientSet) && utilnet.IsIPv6String(serverContainerIP) { podIP, err = podIPsForUserDefinedPrimaryNetwork(f.ClientSet, f.Namespace.Name, clientPod.Name, namespacedName(f.Namespace.Name, cUDN.Name), 1) // For IPv6 addresses, need to handle the brackets in the output outputIP := strings.TrimPrefix(strings.Split(stdout, "]:")[0], "[") @@ -442,13 +448,13 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert Topology: udnv1.NetworkTopologyLayer3, Layer3: &udnv1.Layer3Config{ Role: "Primary", - Subnets: generateL3Subnets(udnv1.Layer3Subnet{ + Subnets: []udnv1.Layer3Subnet{{ CIDR: "103.103.0.0/16", HostSubnet: 24, - }, udnv1.Layer3Subnet{ + }, { CIDR: "2014:100:200::0/60", HostSubnet: 64, - }), + }}, }, }, }, @@ -487,7 +493,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: "Primary", - Subnets: generateL2Subnets("103.0.0.0/16", "2014:100::0/60"), + Subnets: udnv1.DualStackCIDRs{"103.0.0.0/16", "2014:100::0/60"}, }, }, }, @@ -572,6 +578,19 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" udnClient, err := udnclientset.NewForConfig(f.ClientConfig()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + if cudnATemplate.Spec.Network.Layer3 != nil { + cudnATemplate.Spec.Network.Layer3.Subnets = filterL3Subnets(f.ClientSet, cudnATemplate.Spec.Network.Layer3.Subnets) + } + if cudnATemplate.Spec.Network.Layer2 != nil { + cudnATemplate.Spec.Network.Layer2.Subnets = filterDualStackCIDRs(f.ClientSet, cudnATemplate.Spec.Network.Layer2.Subnets) + } + if cudnBTemplate.Spec.Network.Layer3 != nil { + cudnBTemplate.Spec.Network.Layer3.Subnets = filterL3Subnets(f.ClientSet, cudnBTemplate.Spec.Network.Layer3.Subnets) + } + if cudnBTemplate.Spec.Network.Layer2 != nil { + cudnBTemplate.Spec.Network.Layer2.Subnets = filterDualStackCIDRs(f.ClientSet, cudnBTemplate.Spec.Network.Layer2.Subnets) + } + cudnA, err = udnClient.K8sV1().ClusterUserDefinedNetworks().Create(context.Background(), cudnATemplate, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -798,7 +817,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" return fmt.Errorf("expected connectivity check to contain %q, got %q", expectedOutput, out) } } - if isIPv6Supported() && isIPv4Supported() { + if isIPv6Supported(f.ClientSet) && isIPv4Supported(f.ClientSet) { // use ipFamilyIndex of 1 to pick the IPv6 addresses clientName, clientNamespace, dst, expectedOutput, expectErr := connInfo(1) out, err := checkConnectivity(clientName, clientNamespace, dst) @@ -988,13 +1007,13 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" Topology: udnv1.NetworkTopologyLayer3, Layer3: &udnv1.Layer3Config{ Role: "Primary", - Subnets: generateL3Subnets(udnv1.Layer3Subnet{ + Subnets: []udnv1.Layer3Subnet{{ CIDR: "102.102.0.0/16", HostSubnet: 24, - }, udnv1.Layer3Subnet{ + }, { CIDR: "2013:100:200::0/60", HostSubnet: 64, - }), + }}, }, }, }, @@ -1008,13 +1027,13 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" Topology: udnv1.NetworkTopologyLayer3, Layer3: &udnv1.Layer3Config{ Role: "Primary", - Subnets: generateL3Subnets(udnv1.Layer3Subnet{ + Subnets: []udnv1.Layer3Subnet{{ CIDR: "103.103.0.0/16", HostSubnet: 24, - }, udnv1.Layer3Subnet{ + }, { CIDR: "2014:100:200::0/60", HostSubnet: 64, - }), + }}, }, }, }, @@ -1031,7 +1050,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: "Primary", - Subnets: generateL2Subnets("102.102.0.0/16", "2013:100:200::0/60"), + Subnets: udnv1.DualStackCIDRs{"102.102.0.0/16", "2013:100:200::0/60"}, }, }, }, @@ -1045,32 +1064,10 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: "Primary", - Subnets: generateL2Subnets("103.103.0.0/16", "2014:100:200::0/60"), + Subnets: udnv1.DualStackCIDRs{"103.103.0.0/16", "2014:100:200::0/60"}, }, }, }, }, ), ) - -func generateL3Subnets(v4, v6 udnv1.Layer3Subnet) []udnv1.Layer3Subnet { - var subnets []udnv1.Layer3Subnet - if isIPv4Supported() { - subnets = append(subnets, v4) - } - if isIPv6Supported() { - subnets = append(subnets, v6) - } - return subnets -} - -func generateL2Subnets(v4, v6 string) udnv1.DualStackCIDRs { - var subnets udnv1.DualStackCIDRs - if isIPv4Supported() { - subnets = append(subnets, udnv1.CIDR(v4)) - } - if isIPv6Supported() { - subnets = append(subnets, udnv1.CIDR(v6)) - } - return subnets -} diff --git a/test/e2e/util.go b/test/e2e/util.go index aba6dcbc44..2818f33016 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1129,14 +1129,29 @@ func randStr(n int) string { return string(b) } -func isIPv4Supported() bool { - val, present := os.LookupEnv("PLATFORM_IPV4_SUPPORT") - return present && val == "true" +func isCIDRIPFamilySupported(cs clientset.Interface, cidr string) bool { + ginkgo.GinkgoHelper() + gomega.Expect(cidr).To(gomega.ContainSubstring("/")) + isIPv6 := utilnet.IsIPv6CIDRString(cidr) + return (isIPv4Supported(cs) && !isIPv6) || (isIPv6Supported(cs) && isIPv6) } -func isIPv6Supported() bool { - val, present := os.LookupEnv("PLATFORM_IPV6_SUPPORT") - return present && val == "true" +func isIPv4Supported(cs clientset.Interface) bool { + v4, _ := getSupportedIPFamilies(cs) + return v4 +} + +func isIPv6Supported(cs clientset.Interface) bool { + _, v6 := getSupportedIPFamilies(cs) + return v6 +} + +func getSupportedIPFamilies(cs clientset.Interface) (bool, bool) { + n, err := e2enode.GetRandomReadySchedulableNode(context.TODO(), cs) + framework.ExpectNoError(err, "must fetch a Ready Node") + v4NodeAddrs := e2enode.GetAddressesByTypeAndFamily(n, v1.NodeInternalIP, v1.IPv4Protocol) + v6NodeAddrs := e2enode.GetAddressesByTypeAndFamily(n, v1.NodeInternalIP, v1.IPv6Protocol) + return len(v4NodeAddrs) > 0, len(v6NodeAddrs) > 0 } func isInterconnectEnabled() bool { From b18165069af68d15dd27f38b3ce19d062a572100 Mon Sep 17 00:00:00 2001 From: Martin Kennelly Date: Fri, 27 Jun 2025 11:03:13 +0100 Subject: [PATCH 111/278] E2Es: remove additional references to KinD setup Signed-off-by: Martin Kennelly --- test/e2e/e2e.go | 19 ++++----- test/e2e/multi_node_zones_interconnect.go | 20 ++++----- test/e2e/multihoming.go | 51 ++++++++++++----------- test/e2e/route_advertisements.go | 2 +- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index ac9bc8fb3b..be1b46bf75 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -17,6 +17,8 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/test/e2e/containerengine" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" @@ -558,16 +560,13 @@ func getApiAddress() string { } // IsGatewayModeLocal returns true if the gateway mode is local -func IsGatewayModeLocal() bool { - anno, err := e2ekubectl.RunKubectl("default", "get", "node", "ovn-control-plane", "-o", "template", "--template={{.metadata.annotations}}") - if err != nil { - framework.Logf("Error getting annotations: %v", err) - return false - } - framework.Logf("Annotations received: %s", anno) - isLocal := strings.Contains(anno, "local") - framework.Logf("IsGatewayModeLocal returning: %v", isLocal) - return isLocal +func IsGatewayModeLocal(cs kubernetes.Interface) bool { + ginkgo.GinkgoHelper() + node, err := e2enode.GetRandomReadySchedulableNode(context.TODO(), cs) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + l3Config, err := util.ParseNodeL3GatewayAnnotation(node) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "must get node l3 gateway annotation") + return l3Config.Mode == config.GatewayModeLocal } // restartOVNKubeNodePod restarts the ovnkube-node pod from namespace, running on nodeName diff --git a/test/e2e/multi_node_zones_interconnect.go b/test/e2e/multi_node_zones_interconnect.go index 0a358cd7ea..d4cc5356b2 100644 --- a/test/e2e/multi_node_zones_interconnect.go +++ b/test/e2e/multi_node_zones_interconnect.go @@ -91,10 +91,8 @@ func checkPodsInterconnectivity(clientPod, serverPod *v1.Pod, namespace string, var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, func() { const ( - serverPodNodeName = "ovn-control-plane" - serverPodName = "server-pod" - clientPodNodeName = "ovn-worker3" - clientPodName = "client-pod" + serverPodName = "server-pod" + clientPodName = "client-pod" ) fr := wrappedTestFramework("multi-node-zones") @@ -120,13 +118,13 @@ var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, f len(nodes.Items)) } - serverPodNode, err = cs.CoreV1().Nodes().Get(context.TODO(), serverPodNodeName, metav1.GetOptions{}) + serverPodNode, err = cs.CoreV1().Nodes().Get(context.TODO(), nodes.Items[0].Name, metav1.GetOptions{}) if err != nil { e2eskipper.Skipf( "Test requires node with the name %s", serverPodName, ) } - clientPodNode, err = cs.CoreV1().Nodes().Get(context.TODO(), clientPodNodeName, metav1.GetOptions{}) + clientPodNode, err = cs.CoreV1().Nodes().Get(context.TODO(), nodes.Items[1].Name, metav1.GetOptions{}) if err != nil { e2eskipper.Skipf( "Test requires node with the name %s", clientPodName, @@ -141,7 +139,7 @@ var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, f if serverPodNodeZone == clientPodNodeZone { e2eskipper.Skipf( - "Test requires nodes %s and %s are in different zones", serverPodNodeName, clientPodNodeName, + "Test requires nodes %s and %s are in different zones", nodes.Items[0].Name, nodes.Items[1].Name, ) } }) @@ -150,13 +148,13 @@ var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, f // Create a server pod on zone - zone-1 cmd := httpServerContainerCmd(8000) serverPod := e2epod.NewAgnhostPod(fr.Namespace.Name, serverPodName, nil, nil, nil, cmd...) - serverPod.Spec.NodeName = serverPodNodeName + serverPod.Spec.NodeName = serverPodNode.Name e2epod.NewPodClient(fr).CreateSync(context.TODO(), serverPod) // Create a client pod on zone - zone-2 cmd = []string{} clientPod := e2epod.NewAgnhostPod(fr.Namespace.Name, clientPodName, nil, nil, nil, cmd...) - clientPod.Spec.NodeName = clientPodNodeName + clientPod.Spec.NodeName = clientPodNode.Name e2epod.NewPodClient(fr).CreateSync(context.TODO(), clientPod) ginkgo.By("asserting the *client* pod can contact the server pod exposed endpoint") @@ -164,7 +162,7 @@ var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, f framework.ExpectNoError(err, "failed to check pods interconnectivity") // Change the zone of client-pod node to that of server-pod node - s := fmt.Sprintf("Changing the client-pod node %s zone from %s to %s", clientPodNodeName, clientPodNodeZone, serverPodNodeZone) + s := fmt.Sprintf("Changing the client-pod node %s zone from %s to %s", clientPodNode.Name, clientPodNodeZone, serverPodNodeZone) ginkgo.By(s) err = changeNodeZone(clientPodNode, serverPodNodeZone, cs) framework.ExpectNoError(err, "failed to change node zone") @@ -174,7 +172,7 @@ var _ = ginkgo.Describe("Multi node zones interconnect", feature.Interconnect, f framework.ExpectNoError(err, "failed to check pods interconnectivity") // Change back the zone of client-pod node - s = fmt.Sprintf("Changing back the client-pod node %s zone from %s to %s", clientPodNodeName, serverPodNodeZone, clientPodNodeZone) + s = fmt.Sprintf("Changing back the client-pod node %s zone from %s to %s", clientPodNode.Name, serverPodNodeZone, clientPodNodeZone) ginkgo.By(s) err = changeNodeZone(clientPodNode, clientPodNodeZone, cs) framework.ExpectNoError(err, "failed to change node zone") diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index e16fa151a0..46ad7eedc5 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -272,18 +272,26 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { ) const ( - clientPodName = "client-pod" - clientIPOffset = 100 - serverIPOffset = 102 - port = 9000 - workerOneNodeName = "ovn-worker" - workerTwoNodeName = "ovn-worker2" + clientPodName = "client-pod" + clientIPOffset = 100 + serverIPOffset = 102 + port = 9000 ) ginkgo.DescribeTable("attached to a localnet network mapped to breth0", - func(netConfigParams networkAttachmentConfigParams, clientPodConfig, serverPodConfig podConfiguration) { - + func(netConfigParams networkAttachmentConfigParams, clientPodConfig, serverPodConfig podConfiguration, isCollocatedPods bool) { + By("Get two scheduable nodes and ensure client and server are located on distinct Nodes") + nodes, err := e2enode.GetBoundedReadySchedulableNodes(context.Background(), f.ClientSet, 2) + framework.ExpectNoError(err, "2 scheduable nodes are required") + Expect(len(nodes.Items)).To(BeNumerically(">=", 1), "cluster should have at least 2 nodes") + if isCollocatedPods { + clientPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[0].GetName()} + serverPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[0].GetName()} + } else { + clientPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[0].GetName()} + serverPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[1].GetName()} + } netConfig := newNetworkAttachmentConfig(networkAttachmentConfigParams{ name: secondaryNetworkName, namespace: f.Namespace.Name, @@ -307,7 +315,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { nad := generateNAD(netConfig) By(fmt.Sprintf("creating the attachment configuration: %v\n", nad)) - _, err := nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( + _, err = nadClient.NetworkAttachmentDefinitions(f.Namespace.Name).Create( context.Background(), nad, metav1.CreateOptions{}, @@ -369,7 +377,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }, podConfiguration{ // client on default network name: clientPodName, - nodeSelector: map[string]string{nodeHostnameKey: workerOneNodeName}, isPrivileged: true, }, podConfiguration{ // server attached to localnet secondary network @@ -378,9 +385,9 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }}, name: podName, containerCmd: httpServerContainerCmd(port), - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, needsIPRequestFromHostSubnet: true, // will override attachments above with an IPRequest }, + false, // scheduled on distinct Nodes Label("BUG", "OCPBUGS-43004"), ), ginkgo.Entry( @@ -391,7 +398,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }, podConfiguration{ // client on default network name: clientPodName + "-same-node", - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, isPrivileged: true, }, podConfiguration{ // server attached to localnet secondary network @@ -400,9 +406,9 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }}, name: podName, containerCmd: httpServerContainerCmd(port), - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, needsIPRequestFromHostSubnet: true, }, + true, // collocated on same Node Label("BUG", "OCPBUGS-43004"), ), ginkgo.Entry( @@ -416,16 +422,15 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Name: secondaryNetworkName, }}, name: clientPodName, - nodeSelector: map[string]string{nodeHostnameKey: workerOneNodeName}, isPrivileged: true, needsIPRequestFromHostSubnet: true, }, podConfiguration{ // server on default network, pod is host-networked name: podName, containerCmd: httpServerContainerCmd(port), - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, hostNetwork: true, }, + false, // not collocated on same node Label("STORY", "SDN-5345"), ), ginkgo.Entry( @@ -439,16 +444,15 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Name: secondaryNetworkName, }}, name: clientPodName, - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, isPrivileged: true, needsIPRequestFromHostSubnet: true, }, podConfiguration{ // server on default network, pod is host-networked name: podName, containerCmd: httpServerContainerCmd(port), - nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName}, hostNetwork: true, }, + true, // collocated on same node Label("STORY", "SDN-5345"), ), ) @@ -456,13 +460,11 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Context("multiple pods connected to the same OVN-K secondary network", func() { const ( - workerOneNodeName = "ovn-worker" - workerTwoNodeName = "ovn-worker2" - clientPodName = "client-pod" - nodeHostnameKey = "kubernetes.io/hostname" - port = 9000 - clientIP = "192.168.200.10/24" - staticServerIP = "192.168.200.20/24" + clientPodName = "client-pod" + nodeHostnameKey = "kubernetes.io/hostname" + port = 9000 + clientIP = "192.168.200.10/24" + staticServerIP = "192.168.200.20/24" ) ginkgo.It("eventually configures pods that were added to an already existing network before the nad", func() { @@ -567,6 +569,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { By("Get two scheduable nodes and schedule client and server to be on distinct Nodes") nodes, err := e2enode.GetBoundedReadySchedulableNodes(context.Background(), f.ClientSet, 2) framework.ExpectNoError(err, "2 scheduable nodes are required") + Expect(len(nodes.Items)).To(BeNumerically(">=", 1), "cluster should have at least 2 nodes") clientPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[0].GetName()} serverPodConfig.nodeSelector = map[string]string{nodeHostnameKey: nodes.Items[1].GetName()} diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index f6dcdfc800..2cc4c2318f 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -299,7 +299,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert Values: []string{f.Namespace.Name}, }}} - if IsGatewayModeLocal() && cudnTemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { + if IsGatewayModeLocal(f.ClientSet) && cudnTemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { e2eskipper.Skipf( "BGP for L2 networks on LGW is currently unsupported", ) From a6cac97aed379e36f8c5d152d96ca788991866c2 Mon Sep 17 00:00:00 2001 From: Alin Gabriel Serdean Date: Tue, 24 Jun 2025 08:20:37 +0000 Subject: [PATCH 112/278] ovnkube in dpu host mode: advanced gateway detection This patch introduces a new way to detect the gateway interface in the case ovnkube is running in DPU-HOST mode. Introduce the argument derive-from-mgmt-port, if this is specified as the gateway interface, we will identify the physical function of the device used as an management port accelerated interface. Signed-off-by: Alin Gabriel Serdean --- .../hardware-offload/derive-from-mgmt-port.md | 163 ++++++++++++++ .../hardware-offload/dpu-gateway-interface.md | 208 ++++++++++++++++++ docs/features/hardware-offload/dpu-support.md | 2 + docs/installation/ovn_k8s.conf.5 | 10 + .../node/default_node_network_controller.go | 30 +++ .../default_node_network_controller_test.go | 199 +++++++++++++++++ go-controller/pkg/types/const.go | 3 + 7 files changed, 615 insertions(+) create mode 100644 docs/features/hardware-offload/derive-from-mgmt-port.md create mode 100644 docs/features/hardware-offload/dpu-gateway-interface.md diff --git a/docs/features/hardware-offload/derive-from-mgmt-port.md b/docs/features/hardware-offload/derive-from-mgmt-port.md new file mode 100644 index 0000000000..e7d7f38194 --- /dev/null +++ b/docs/features/hardware-offload/derive-from-mgmt-port.md @@ -0,0 +1,163 @@ +# From PCI Address Gateway Interface Feature + +## Overview + +The "derive-from-mgmt-port" gateway interface feature is a new capability in OVN-Kubernetes that enables automatic gateway interface resolution in DPU (Data Processing Unit) host mode deployments. This feature automatically discovers and configures the appropriate Physical Function (PF) interface as the gateway interface based on the Virtual Function (VF) used for the management port. + +## Problem Statement + +In DPU deployments, the host typically has access to Virtual Functions (VFs) for management purposes, while the Physical Functions (PFs) are used for external connectivity. Previously, administrators had to manually specify the gateway interface, which required: + +1. Knowledge of the hardware topology +2. Manual mapping of VF to PF relationships +3. Configuration updates when hardware changes +4. Potential for misconfiguration + +## Solution + +The "derive-from-mgmt-port" feature automates the gateway interface discovery process by: + +1. **Automatic Discovery**: Automatically finds the PF interface associated with the management port VF +2. **Hardware Abstraction**: Eliminates the need for manual hardware topology knowledge +3. **Dynamic Configuration**: Adapts to hardware changes automatically +4. **Reduced Configuration**: Simplifies deployment configuration + +## Benefits + +### For Administrators + +- **Simplified Configuration**: No need to manually specify gateway interfaces +- **Reduced Errors**: Eliminates manual mapping errors +- **Hardware Agnostic**: Works with any SR-IOV capable hardware +- **Dynamic Adaptation**: Automatically adapts to hardware changes + +### For Operations + +- **Faster Deployment**: Reduced configuration time +- **Consistent Setup**: Standardized gateway interface selection +- **Reduced Maintenance**: Less manual intervention required +- **Better Reliability**: Fewer configuration-related issues + +### For Development + +- **Cleaner Code**: Centralized gateway interface logic +- **Better Testing**: Comprehensive unit test coverage +- **Extensible Design**: Foundation for future enhancements + +## Technical Implementation + +### Code Changes + +1. **New Constant**: Added `DeriveFromMgmtPort = "derive-from-mgmt-port"` constant in `go-controller/pkg/types/const.go` +2. **Enhanced Logic**: Extended gateway initialization in `go-controller/pkg/node/default_node_network_controller.go` +3. **Comprehensive Testing**: Added unit tests covering success and failure scenarios + +### Key Functions + +- `getManagementPortNetDev()`: Resolves management port device name +- `GetPciFromNetDevice()`: Retrieves PCI address from network device +- `GetPfPciFromVfPci()`: Resolves PF PCI address from VF PCI address +- `GetNetDevicesFromPci()`: Discovers network devices associated with PCI address + +### Error Handling + +The implementation includes robust error handling for: +- Missing network devices +- PCI address resolution failures +- SR-IOV operation failures +- Hardware compatibility issues + +## Configuration Examples + +### Basic Configuration + +```bash +--ovnkube-node-mode=dpu-host +--ovnkube-node-mgmt-port-netdev=pf0vf0 +--gateway-interface=derive-from-mgmt-port +``` + +### Helm Configuration + +```yaml +ovnkube-node: + mode: dpu-host + mgmtPortNetdev: pf0vf0 + +gateway: + interface: derive-from-mgmt-port +``` + +### Configuration File + +```ini +[OvnKubeNode] +mode=dpu-host +mgmt-port-netdev=pf0vf0 + +[Gateway] +interface=derive-from-mgmt-port +``` + +## Migration Guide + +### From Manual Configuration + +**Before:** +```bash +--gateway-interface=eth0 +``` + +**After:** +```bash +--gateway-interface=derive-from-mgmt-port +``` + +### Verification Steps + +1. Verify SR-IOV configuration is correct +2. Ensure management port device is properly configured +3. Check that PF interfaces are available +4. Monitor logs for successful gateway interface resolution + +## Testing + +### Unit Tests + +Comprehensive unit tests cover: +- Successful gateway interface resolution +- Error handling for missing devices +- PCI address resolution failures +- Network device discovery failures + +### Integration Tests + +The feature integrates with existing: +- Gateway initialization +- DPU host mode functionality +- SR-IOV operations +- Network configuration + +## Future Enhancements + +Potential improvements include: +- Support for multiple gateway interfaces +- Enhanced device selection criteria +- Integration with device plugins +- Support for non-SR-IOV hardware +- Advanced error reporting and diagnostics + +## Related Documentation + +- [DPU Gateway Interface Configuration](dpu-gateway-interface.md) +- [DPU Support](dpu-support.md) +- [Gateway Accelerated Interface Configuration](../design/gateway-accelerated-interface-configuration.md) +- [Configuration Guide](../../getting-started/configuration.md) + +## Support + +For issues related to this feature: +1. Check the troubleshooting section in the DPU Gateway Interface Configuration guide +2. Verify SR-IOV hardware and driver support +3. Review error messages and logs +4. Consult the OVN-Kubernetes community for additional support \ No newline at end of file diff --git a/docs/features/hardware-offload/dpu-gateway-interface.md b/docs/features/hardware-offload/dpu-gateway-interface.md new file mode 100644 index 0000000000..49f3e6ccec --- /dev/null +++ b/docs/features/hardware-offload/dpu-gateway-interface.md @@ -0,0 +1,208 @@ +# DPU Gateway Interface Configuration + +## Overview + +In DPU (Data Processing Unit) host mode deployments, OVN-Kubernetes supports automatic gateway interface resolution from PCI address. This feature is particularly useful when the management port is a Virtual Function (VF) and you want to automatically select the corresponding Physical Function (PF) interface as the gateway. + +## Background + +In DPU deployments, the host typically has access to Virtual Functions (VFs) for management purposes, while the Physical Functions (PFs) are used for external connectivity. The "derive-from-mgmt-port" feature allows OVN-Kubernetes to automatically discover and configure the appropriate PF interface as the gateway interface based on the VF used for the management port. + +## How It Works + +When configured with `--gateway-interface=derive-from-mgmt-port`, OVN-Kubernetes performs the following steps: + +1. **Management Port Resolution**: Gets the management port network device name (specified by `--ovnkube-node-mgmt-port-netdev`) +2. **VF PCI Address Retrieval**: Retrieves the PCI address of the management port device (VF) +3. **PF PCI Address Resolution**: Gets the Physical Function (PF) PCI address from the Virtual Function (VF) PCI address +4. **Network Device Discovery**: Retrieves all network devices associated with the PF PCI address +5. **Interface Selection**: Selects the first available network device as the gateway interface + +## Configuration + +### Command Line Options + +```bash +--ovnkube-node-mode=dpu-host +--ovnkube-node-mgmt-port-netdev=pf0vf0 +--gateway-interface=derive-from-mgmt-port +``` + +### Configuration File + +```ini +[OvnKubeNode] +mode=dpu-host +mgmt-port-netdev=pf0vf0 + +[Gateway] +interface=derive-from-mgmt-port +``` + +### Helm Configuration + +```yaml +ovnkube-node: + mode: dpu-host + mgmtPortNetdev: pf0vf0 + +gateway: + interface: derive-from-mgmt-port +``` + +## Example Scenario + +Consider a DPU setup with the following configuration: + +- **Management port device**: `pf0vf0` (Virtual Function) +- **VF PCI address**: `0000:01:02.3` +- **PF PCI address**: `0000:01:00.0` +- **Available PF interfaces**: `eth0`, `eth1` + +With `--gateway-interface=derive-from-mgmt-port`, OVN-Kubernetes will: + +1. Start with the management port device `pf0vf0` +2. Get its PCI address `0000:01:02.3` +3. Resolve the PF PCI address to `0000:01:00.0` +4. Find all network devices associated with PF `0000:01:00.0`: `eth0`, `eth1` +5. Select `eth0` (first device) as the gateway interface + +## Requirements + +### Hardware Requirements + +- SR-IOV capable network interface card +- Virtual Function (VF) and Physical Function (PF) setup +- Management port configured as a VF + +### Software Requirements + +- SR-IOV utilities available on the system +- OVN-Kubernetes running in DPU host mode +- Proper VF/PF driver support + +### Configuration Requirements + +- Must be used in DPU host mode (`--ovnkube-node-mode=dpu-host`) +- Management port netdev must be specified (`--ovnkube-node-mgmt-port-netdev`) +- Gateway interface must be set to `derive-from-mgmt-port` + +## Error Handling + +The system will return an error in the following scenarios: + +### No Network Devices Found + +``` +no netdevs found for pci address 0000:01:00.0 +``` + +**Cause**: The PF PCI address doesn't have any associated network devices. + +**Resolution**: Verify that the PF has network interfaces configured and are visible to the system. + +### PCI Address Resolution Failure + +``` +failed to get PCI address +``` + +**Cause**: Unable to retrieve the PCI address from the management port device. + +**Resolution**: Ensure the management port device exists and is properly configured. + +### PF PCI Address Resolution Failure + +``` +failed to get PF PCI address +``` + +**Cause**: Unable to resolve the PF PCI address from the VF PCI address. + +**Resolution**: Verify SR-IOV configuration and driver support. + +### Network Device Discovery Failure + +``` +failed to get network devices +``` + +**Cause**: Unable to retrieve network devices associated with the PF PCI address. + +**Resolution**: Check SR-IOV utilities and system configuration. + +## Troubleshooting + +### Verify SR-IOV Configuration + +```bash +# Check if SR-IOV is enabled +lspci | grep -i ethernet + +# Check VF configuration +ip link show + +# Check PF/VF relationship +ls /sys/bus/pci/devices/*/virtfn* +``` + +### Verify Management Port Device + +```bash +# Check if management port device exists +ip link show pf0vf0 + +# Check PCI address +ethtool -i pf0vf0 | grep bus-info +``` + +### Debug PCI Address Resolution + +```bash +# Get VF PCI address +cat /sys/class/net/pf0vf0/device/address + +# Get PF PCI address (if available) +cat /sys/class/net/pf0vf0/device/physfn/address +``` + +## Integration with Existing Features + +### Gateway Accelerated Interface + +The "derive-from-mgmt-port" feature is used in conjunction with management interface to select the appropriate gateway accelerated interface. + +The management port can be specified through one of the following options: +``` + --ovnkube-node-mgmt-port-netdev) + OVNKUBE_NODE_MGMT_PORT_NETDEV=$VALUE +``` + +``` + --ovnkube-node-mgmt-port-dp-resource-name) + OVNKUBE_NODE_MGMT_PORT_DP_RESOURCE_NAME=$VALUE +``` + +OVNKUBE_NODE_MGMT_PORT_DP_RESOURCE_NAME has priority over OVNKUBE_NODE_MGMT_PORT_NETDEV and it is easier to use since it points to a SRIOV Device Plugin pool name. + +### Multiple Network Support + +This feature works with multiple network support and can be used in environments where pods have multiple interfaces connected to different networks. + +## Limitations + +- Only available in DPU host mode +- Requires SR-IOV capable hardware +- Limited to the first available network device from the PF +- Depends on proper VF/PF driver support +- May not work with all SR-IOV implementations + +## Future Enhancements + +Potential improvements to this feature could include: + +- Support for selecting specific network devices based on criteria +- Integration with device plugin resources +- Support for multiple gateway interfaces +- Enhanced error reporting and diagnostics +- Support for non-SR-IOV hardware configurations \ No newline at end of file diff --git a/docs/features/hardware-offload/dpu-support.md b/docs/features/hardware-offload/dpu-support.md index bc9d731a39..6ac6a5ca7d 100644 --- a/docs/features/hardware-offload/dpu-support.md +++ b/docs/features/hardware-offload/dpu-support.md @@ -43,6 +43,8 @@ These aforementioned parts are expected to be deployed also on two different Kub #### OVN Kubernetes component on a DPU-Enabled Host - ovn-node +For detailed configuration of gateway interfaces in DPU host mode, see [DPU Gateway Interface Configuration](dpu-gateway-interface.md). + ### DPU Cluster --- diff --git a/docs/installation/ovn_k8s.conf.5 b/docs/installation/ovn_k8s.conf.5 index fc790db071..10f224b831 100644 --- a/docs/installation/ovn_k8s.conf.5 +++ b/docs/installation/ovn_k8s.conf.5 @@ -124,6 +124,16 @@ or set to "shared" (share a network interface) or "local" (use a NAT-ed virtual This interface will be used as the gateway interface in "shared" mode. If not specified the interface with the default route will be used. .TP +\fBinterface\fR=derive-from-mgmt-port +In DPU host mode, automatically resolve the gateway interface from PCI address. +This performs the following steps: +1. Get the management port network device name +2. Retrieve the PCI address of the management port device +3. Get the Physical Function (PF) PCI address from the Virtual Function (VF) PCI address +4. Retrieve all network devices associated with the PF PCI address +5. Select the first available network device as the gateway interface +This option requires SR-IOV capable hardware and must be used with DPU host mode. +.TP \fBnext-hop\fR=1.2.3.4 This is the gateway IP address of \fBinterface\fR to which traffic exiting the OVN logical network should be sent in "shared" mode. If not specified diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index 558f7be8c9..51dc1571e1 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -1028,6 +1028,36 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { // Complete gateway initialization if config.OvnKubeNode.Mode == types.NodeModeDPUHost { + // Resolve gateway interface from PCI address when configured as "derive-from-mgmt-port" + // This performs the following steps: + // Get the management port network device name + // Retrieve the PCI address of the management port device + // Get the Physical Function (PF) PCI address from the Virtual Function (VF) PCI address + // Retrieve all network devices associated with the PF PCI address + // Select the first available network device as the gateway interface + if config.Gateway.Interface == types.DeriveFromMgmtPort { + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + if err != nil { + return err + } + pciAddr, err := util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + if err != nil { + return err + } + pfPciAddr, err := util.GetSriovnetOps().GetPfPciFromVfPci(pciAddr) + if err != nil { + return err + } + netdevs, err := util.GetSriovnetOps().GetNetDevicesFromPci(pfPciAddr) + if err != nil { + return err + } + if len(netdevs) == 0 { + return fmt.Errorf("no netdevs found for pci address %s", pfPciAddr) + } + netdevName = netdevs[0] + config.Gateway.Interface = netdevName + } err = nc.initGatewayDPUHost(nc.nodeAddress) if err != nil { return err diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index cb7087ff1b..875b0da694 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -1560,5 +1560,204 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } }) }) + + Describe("derive-from-mgmt-port gateway interface resolution", func() { + var ( + kubeMock *mocks.Interface + sriovnetMock utilMocks.SriovnetOps + netlinkOpsMock *utilMocks.NetLinkOps + netlinkLinkMock *netlink_mocks.Link + ) + + const ( + nodeName = "test-node" + mgmtPortNetdev = "pf0vf0" + vfPciAddr = "0000:01:02.3" + pfPciAddr = "0000:01:00.0" + expectedGatewayIntf = "eth0" + ) + + BeforeEach(func() { + kubeMock = new(mocks.Interface) + sriovnetMock = utilMocks.SriovnetOps{} + netlinkOpsMock = new(utilMocks.NetLinkOps) + netlinkLinkMock = new(netlink_mocks.Link) + + util.SetSriovnetOpsInst(&sriovnetMock) + util.SetNetLinkOpMockInst(netlinkOpsMock) + + // Setup default node network controller + cnnci := &CommonNodeNetworkControllerInfo{ + name: nodeName, + Kube: kubeMock, + } + nc = &DefaultNodeNetworkController{ + BaseNodeNetworkController: BaseNodeNetworkController{ + CommonNodeNetworkControllerInfo: *cnnci, + ReconcilableNetInfo: &util.DefaultNetInfo{}, + }, + } + + // Set DPU host mode + config.OvnKubeNode.Mode = types.NodeModeDPUHost + config.OvnKubeNode.MgmtPortNetdev = mgmtPortNetdev + config.Gateway.Interface = types.DeriveFromMgmtPort + }) + + AfterEach(func() { + util.ResetNetLinkOpMockInst() + }) + + Context("when gateway interface is set to derive-from-mgmt-port", func() { + It("should resolve gateway interface from PCI address successfully", func() { + // Mock getManagementPortNetDev to return the management port device + netlinkOpsMock.On("LinkByName", mgmtPortNetdev).Return(netlinkLinkMock, nil) + netlinkLinkMock.On("Attrs").Return(&netlink.LinkAttrs{ + Name: mgmtPortNetdev, + }) + + // Mock GetPciFromNetDevice to return VF PCI address + sriovnetMock.On("GetPciFromNetDevice", mgmtPortNetdev).Return(vfPciAddr, nil) + + // Mock GetPfPciFromVfPci to return PF PCI address + sriovnetMock.On("GetPfPciFromVfPci", vfPciAddr).Return(pfPciAddr, nil) + + // Mock GetNetDevicesFromPci to return available network devices + sriovnetMock.On("GetNetDevicesFromPci", pfPciAddr).Return([]string{expectedGatewayIntf, "eth1"}, nil) + + // Execute the gateway interface resolution logic + // This simulates the logic in the Start() method + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + Expect(err).NotTo(HaveOccurred()) + Expect(netdevName).To(Equal(mgmtPortNetdev)) + + pciAddr, err := util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + Expect(err).NotTo(HaveOccurred()) + Expect(pciAddr).To(Equal(vfPciAddr)) + + pfPciAddr, err := util.GetSriovnetOps().GetPfPciFromVfPci(pciAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(pfPciAddr).To(Equal(pfPciAddr)) + + netdevs, err := util.GetSriovnetOps().GetNetDevicesFromPci(pfPciAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(netdevs).To(HaveLen(2)) + Expect(netdevs[0]).To(Equal(expectedGatewayIntf)) + + // Verify that the first device is selected as the gateway interface + selectedNetdev := netdevs[0] + Expect(selectedNetdev).To(Equal(expectedGatewayIntf)) + }) + + It("should return error when no network devices found for PCI address", func() { + // Mock getManagementPortNetDev to return the management port device + netlinkOpsMock.On("LinkByName", mgmtPortNetdev).Return(netlinkLinkMock, nil) + netlinkLinkMock.On("Attrs").Return(&netlink.LinkAttrs{ + Name: mgmtPortNetdev, + }) + + // Mock GetPciFromNetDevice to return VF PCI address + sriovnetMock.On("GetPciFromNetDevice", mgmtPortNetdev).Return(vfPciAddr, nil) + + // Mock GetPfPciFromVfPci to return PF PCI address + sriovnetMock.On("GetPfPciFromVfPci", vfPciAddr).Return(pfPciAddr, nil) + + // Mock GetNetDevicesFromPci to return empty list + sriovnetMock.On("GetNetDevicesFromPci", pfPciAddr).Return([]string{}, nil) + + // Execute the gateway interface resolution logic + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + Expect(err).NotTo(HaveOccurred()) + + pciAddr, err := util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + Expect(err).NotTo(HaveOccurred()) + + pfPciAddr, err := util.GetSriovnetOps().GetPfPciFromVfPci(pciAddr) + Expect(err).NotTo(HaveOccurred()) + + netdevs, err := util.GetSriovnetOps().GetNetDevicesFromPci(pfPciAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(netdevs).To(BeEmpty()) + + // This should result in an error when no devices are found + Expect(netdevs).To(BeEmpty()) + }) + + It("should return error when GetPciFromNetDevice fails", func() { + // Mock getManagementPortNetDev to return the management port device + netlinkOpsMock.On("LinkByName", mgmtPortNetdev).Return(netlinkLinkMock, nil) + netlinkLinkMock.On("Attrs").Return(&netlink.LinkAttrs{ + Name: mgmtPortNetdev, + }) + + // Mock GetPciFromNetDevice to return error + sriovnetMock.On("GetPciFromNetDevice", mgmtPortNetdev).Return("", fmt.Errorf("failed to get PCI address")) + + // Execute the gateway interface resolution logic + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + Expect(err).NotTo(HaveOccurred()) + + _, err = util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get PCI address")) + }) + + It("should return error when GetPfPciFromVfPci fails", func() { + // Mock getManagementPortNetDev to return the management port device + netlinkOpsMock.On("LinkByName", mgmtPortNetdev).Return(netlinkLinkMock, nil) + netlinkLinkMock.On("Attrs").Return(&netlink.LinkAttrs{ + Name: mgmtPortNetdev, + }) + + // Mock GetPciFromNetDevice to return VF PCI address + sriovnetMock.On("GetPciFromNetDevice", mgmtPortNetdev).Return(vfPciAddr, nil) + + // Mock GetPfPciFromVfPci to return error + sriovnetMock.On("GetPfPciFromVfPci", vfPciAddr).Return("", fmt.Errorf("failed to get PF PCI address")) + + // Execute the gateway interface resolution logic + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + Expect(err).NotTo(HaveOccurred()) + + pciAddr, err := util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + Expect(err).NotTo(HaveOccurred()) + + _, err = util.GetSriovnetOps().GetPfPciFromVfPci(pciAddr) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get PF PCI address")) + }) + + It("should return error when GetNetDevicesFromPci fails", func() { + // Mock getManagementPortNetDev to return the management port device + netlinkOpsMock.On("LinkByName", mgmtPortNetdev).Return(netlinkLinkMock, nil) + netlinkLinkMock.On("Attrs").Return(&netlink.LinkAttrs{ + Name: mgmtPortNetdev, + }) + + // Mock GetPciFromNetDevice to return VF PCI address + sriovnetMock.On("GetPciFromNetDevice", mgmtPortNetdev).Return(vfPciAddr, nil) + + // Mock GetPfPciFromVfPci to return PF PCI address + sriovnetMock.On("GetPfPciFromVfPci", vfPciAddr).Return(pfPciAddr, nil) + + // Mock GetNetDevicesFromPci to return error + sriovnetMock.On("GetNetDevicesFromPci", pfPciAddr).Return(nil, fmt.Errorf("failed to get network devices")) + + // Execute the gateway interface resolution logic + netdevName, err := getManagementPortNetDev(config.OvnKubeNode.MgmtPortNetdev) + Expect(err).NotTo(HaveOccurred()) + + pciAddr, err := util.GetSriovnetOps().GetPciFromNetDevice(netdevName) + Expect(err).NotTo(HaveOccurred()) + + pfPciAddr, err := util.GetSriovnetOps().GetPfPciFromVfPci(pciAddr) + Expect(err).NotTo(HaveOccurred()) + + _, err = util.GetSriovnetOps().GetNetDevicesFromPci(pfPciAddr) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get network devices")) + }) + }) + }) }) }) diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index 452421d289..8ba7269cad 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -187,6 +187,9 @@ const ( NodeModeDPU = "dpu" NodeModeDPUHost = "dpu-host" + // Gateway interface configuration + DeriveFromMgmtPort = "derive-from-mgmt-port" + // Geneve header length for IPv4 (https://github.com/openshift/cluster-network-operator/pull/720#issuecomment-664020823) GeneveHeaderLengthIPv4 = 58 // Geneve header length for IPv6 (https://github.com/openshift/cluster-network-operator/pull/720#issuecomment-664020823) From 9e356ba505445561734660f7b22dfb10631a1aa3 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Thu, 17 Jul 2025 09:06:35 +0000 Subject: [PATCH 113/278] doc: Remove OKEP template from the list of rendered pages Signed-off-by: Ihar Hrachyshka --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index e5cd4a3dd6..f82f75c977 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -144,7 +144,6 @@ nav: - OVN observability: observability/ovn-observability.md - Enhancement Proposals: # - FeatureName: okeps/ - - Template: okeps/okep-4368-template.md - Localnet API: okeps/okep-5085-localnet-api.md - Network QoS: okeps/okep-4380-network-qos.md - User Defined Networks: okeps/okep-5193-user-defined-networks.md From 82850ed2b176cbef36419a92f718166a0b9f9d65 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Fri, 4 Jul 2025 14:05:54 +0200 Subject: [PATCH 114/278] Enable Layer2 route advertisements on LGW Configure use_ct_inv_match=false on nbdb when route advertisements are enabled in LGW to support assymetric traffic on Layer2 advertised networks. Signed-off-by: Patryk Diak --- dist/images/ovnkube.sh | 5 ++ .../ovnkube-single-node-zone.yaml.j2 | 4 ++ .../routeadvertisements/controller.go | 4 -- test/e2e/route_advertisements.go | 52 +++++++++++-------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 192f342662..3931d4e180 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -989,6 +989,11 @@ local-nb-ovsdb() { wait_for_event attempts=3 process_ready ovnnb_db echo "=============== nb-ovsdb (unix sockets only) ========== RUNNING" + [[ "local" == "${OVN_GATEWAY_MODE}" && "true" == "${OVN_ROUTE_ADVERTISEMENTS_ENABLE}" ]] && { + ovn-nbctl set NB_Global . options:use_ct_inv_match=false + echo "=============== nb-ovsdb ========== reconfigured for route advertisements" + } + # Let ovn-northd sleep and not use so much CPU ovn-nbctl set NB_Global . options:northd-backoff-interval-ms=${ovn_northd_backoff_interval} echo "=============== nb-ovsdb ========== reconfigured for northd backoff" diff --git a/dist/templates/ovnkube-single-node-zone.yaml.j2 b/dist/templates/ovnkube-single-node-zone.yaml.j2 index df5533a668..ad4e4488f9 100644 --- a/dist/templates/ovnkube-single-node-zone.yaml.j2 +++ b/dist/templates/ovnkube-single-node-zone.yaml.j2 @@ -79,6 +79,10 @@ spec: value: "{{ ovn_loglevel_nb }}" - name: OVN_NORTHD_BACKOFF_INTERVAL value: "{{ ovn_northd_backoff_interval }}" + - name: OVN_GATEWAY_MODE + value: "{{ ovn_gateway_mode }}" + - name: OVN_ROUTE_ADVERTISEMENTS_ENABLE + value: "{{ ovn_route_advertisements_enable }}" - name: K8S_APISERVER valueFrom: configMapKeyRef: diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller.go b/go-controller/pkg/clustermanager/routeadvertisements/controller.go index 04daa6cde1..11f7eb79ab 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller.go @@ -373,10 +373,6 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements) return nil, nil, fmt.Errorf("%w: selected network %q has unsupported topology %q", errConfig, networkName, network.TopologyType()) } - if config.Gateway.Mode == config.GatewayModeLocal && network.TopologyType() == types.Layer2Topology { - return nil, nil, fmt.Errorf("%w: BGP is currently not supported for Layer2 networks in local gateway mode, network: %s", errConfig, network.GetNetworkName()) - } - if advertisements.Has(ratypes.EgressIP) && network.TopologyType() == types.Layer2Topology { return nil, nil, fmt.Errorf("%w: EgressIP advertisement is currently not supported for Layer2 networks, network: %s", errConfig, network.GetNetworkName()) } diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 2cc4c2318f..66c258eeae 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -299,11 +299,6 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert Values: []string{f.Namespace.Name}, }}} - if IsGatewayModeLocal(f.ClientSet) && cudnTemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { - e2eskipper.Skipf( - "BGP for L2 networks on LGW is currently unsupported", - ) - } // Create CUDN ginkgo.By("create ClusterUserDefinedNetwork") udnClient, err := udnclientset.NewForConfig(f.ClientConfig()) @@ -521,6 +516,10 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks", func(cudnATemplate, cudnBTemplate *udnv1.ClusterUserDefinedNetwork) { const curlConnectionTimeoutCode = "28" + const ( + ipFamilyV4 = iota + ipFamilyV6 + ) f := wrappedTestFramework("bpp-network-isolation") f.SkipNamespaceCreation = true @@ -536,9 +535,6 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" var ra *rav1.RouteAdvertisements var hostNetworkPort int ginkgo.BeforeEach(func() { - if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() { - e2eskipper.Skipf("Advertising Layer2 UDNs is not currently supported in LGW") - } ginkgo.By("Configuring primary UDN namespaces") var err error udnNamespaceA, err = f.CreateNamespace(context.TODO(), f.BaseName, map[string]string{ @@ -700,9 +696,6 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }) ginkgo.AfterEach(func() { - if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() { - return - } gomega.Expect(f.ClientSet.CoreV1().Pods(udnNamespaceA.Name).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})).To(gomega.Succeed()) gomega.Expect(f.ClientSet.CoreV1().Pods(udnNamespaceB.Name).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})).To(gomega.Succeed()) @@ -779,7 +772,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" framework.Logf("Connectivity check successful:'%s' -> %s", client, targetAddress) return out, nil } - clientName, clientNamespace, dst, expectedOutput, expectErr := connInfo(0) + clientName, clientNamespace, dst, expectedOutput, expectErr := connInfo(ipFamilyV4) asyncAssertion := gomega.Eventually timeout := time.Second * 30 @@ -800,7 +793,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" } if isIPv6Supported() && isIPv4Supported() { // use ipFamilyIndex of 1 to pick the IPv6 addresses - clientName, clientNamespace, dst, expectedOutput, expectErr := connInfo(1) + clientName, clientNamespace, dst, expectedOutput, expectErr := connInfo(ipFamilyV6) out, err := checkConnectivity(clientName, clientNamespace, dst) if expectErr != (err != nil) { return fmt.Errorf("expected connectivity check to return error(%t), got %v, output %v", expectErr, err, out) @@ -906,7 +899,16 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }), ginkgo.Entry("pod in the UDN should not be able to access a service in a different UDN", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { - return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", curlConnectionTimeoutCode, true + err := true + out := curlConnectionTimeoutCode + if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() { + // FIXME: prevent looping of traffic in L2 UDNs + // bad behaviour: packet is looping from management port -> breth0 -> GR -> management port -> breth0 and so on + // which is a never ending loop + // this causes curl timeout with code 7 host unreachable instead of code 28 + out = "" + } + return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", out, err }), ginkgo.Entry("host to a local UDN pod should not work", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { @@ -963,14 +965,20 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" errBool := false out := "" if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { - // FIXME: fix assymmetry in L2 UDNs - // bad behaviour: packet is coming from other node -> entering eth0 -> bretho and here kernel drops the packet since - // rp_filter is set to 1 in breth0 and there is an iprule that sends the packet to mpX interface so kernel sees the packet - // having return path different from the incoming interface. - // The SNAT to nodeIP should fix this. - // this causes curl timeout with code 28 - errBool = true - out = curlConnectionTimeoutCode + // FIXME: this should be removed once we add the SNAT for pod->node traffic + // We now permit asymmetric traffic on LGW. This prevents the issue from occurring with IPv6. + // However, for IPv4 LGW rp_filter is still blocking the replies. + // The situation is different on SGW as we don't allow asymmetric traffic at all, which is why IPv6 traffic fails there too. + if ipFamilyIndex == ipFamilyV4 || !isLocalGWModeEnabled() { + // FIXME: fix assymmetry in L2 UDNs + // bad behaviour: packet is coming from other node -> entering eth0 -> bretho and here kernel drops the packet since + // rp_filter is set to 1 in breth0 and there is an iprule that sends the packet to mpX interface so kernel sees the packet + // having return path different from the incoming interface. + // The SNAT to nodeIP should fix this. + // this causes curl timeout with code 28 + errBool = true + out = curlConnectionTimeoutCode + } } return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/hostname", out, errBool }), From dcc403c1ddf11e30e6990699616405f6dc47dd71 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Wed, 16 Jul 2025 14:59:56 +0200 Subject: [PATCH 115/278] Fix UDN to alien ClusterIP looping issue The isolation rules originally added here: d63887ed167da260d3f26c71ec06e520d89a4b0f redirect the traffic originated from a UDN back to the same UDNs patchport. This causes a following traffic loop for advertised L2 networks in LGW: 1. A UDN pod sends traffic to a service IP outside the UDN. 2. Traffic exits through the `ovn-mpX` port and is routed to `breth0`. 3. The following OpenFlow rules redirect it back to the UDN patch port: - `table=0,priority=550,ip,in_port=LOCAL,nw_src=,nw_dst=,actions=ct(commit,table=2,zone=64001)` - `table=2,priority=200,ip,nw_src= actions=...,output:""` 4. A route on the L2 gateway router sends the traffic back to `ovn-mpX`, restarting the loop. To fix this, the rule is changed to drop the traffic directly instead of redirecting it. Although currently this change is required only for advertised L2 networks in LGW the rule is changed for all scenarios to avoid introducing use-case specific behavior. Additionally, the priority of the packet marking flow is adjusted to remove any potential ambiguity. While this change could affect future support for host-networked UDN pods accessing ClusterIP services, it should be possible to re-use the existing per-UDN pkt marking approach. Signed-off-by: Patryk Diak --- go-controller/pkg/node/gateway_shared_intf.go | 25 +++++++++++-------- go-controller/pkg/node/gateway_udn_test.go | 24 +++++++++--------- test/e2e/route_advertisements.go | 22 ++-------------- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index a8d3b81aa7..922a68a2bd 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -406,9 +406,9 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // Add flows for default network services that are accessible from UDN networks if util.IsNetworkSegmentationSupportEnabled() { - // The flow added below has a higher priority than the per UDN service flow: - // priority=200, table=2, ip, ip_src=169.254.0., actions=set_field:->eth_dst,output: - // This ordering ensures that traffic to UDN allowed default services goes to the the default patch port. + // The flow added below has a higher priority than the per UDN service isolation flow: + // priority=200, table=2, ip, ip_src=169.254.0., actions=drop + // This ordering ensures that traffic to UDN allowed default services goes to the default patch port. if util.IsUDNEnabledService(ktypes.NamespacedName{Namespace: service.Namespace, Name: service.Name}.String()) { key = strings.Join([]string{"UDNAllowedSVC", service.Namespace, service.Name}, "_") @@ -1788,13 +1788,17 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st // Use the filtered subnets for the flow compute instead of the masqueradeIP srcIPOrSubnet = matchingIPFamilySubnet.String() } + + // Drop traffic coming from the masquerade IP or the UDN subnet(for advertised UDNs) to ensure that + // isolation between networks is enforced. This handles the case where a pod on the UDN subnet is sending traffic to + // a service in another UDN. dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+ - "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, srcIPOrSubnet, - bridgeMacAddress, netConfig.ofPortPatch)) + "actions=drop", + defaultOpenFlowCookie, srcIPOrSubnet)) + dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, table=2, ip, pkt_mark=%s, "+ + fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+ "actions=set_field:%s->eth_dst,output:%s", defaultOpenFlowCookie, netConfig.pktMark, bridgeMacAddress, netConfig.ofPortPatch)) @@ -1825,11 +1829,10 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st } dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+ - "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, srcIPOrSubnet, - bridgeMacAddress, netConfig.ofPortPatch)) + "actions=drop", + defaultOpenFlowCookie, srcIPOrSubnet)) dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, pkt_mark=%s, "+ + fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+ "actions=set_field:%s->eth_dst,output:%s", defaultOpenFlowCookie, netConfig.pktMark, bridgeMacAddress, netConfig.ofPortPatch)) diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 4d73529c86..575d8bc9c8 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -280,7 +280,7 @@ func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeUDNCo Expect(nTable2Flows).To(Equal(1)) } -func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName, bridgeMAC string, svcCIDR *net.IPNet, expectedNFlows int) { +func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { By(fmt.Sprintf("Checking advertsised UDN %s service isolation flows for %s; expected %d flows", netName, svcCIDR.String(), expectedNFlows)) @@ -303,8 +303,8 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN var nFlows int for _, flow := range flows { - if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=set_field:%s->eth_dst,output:%s", - protoPrefix, protoPrefix, matchingIPFamilySubnet, bridgeMAC, netConfig.ofPortPatch)) { + if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", + protoPrefix, protoPrefix, matchingIPFamilySubnet)) { nFlows++ } if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=LOCAL, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=64001,table=2)", @@ -316,7 +316,7 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN Expect(nFlows).To(Equal(expectedNFlows)) } -func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName, bridgeMAC string, svcCIDR *net.IPNet, expectedNFlows int) { +func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows", netName, svcCIDR.String(), expectedNFlows)) @@ -332,8 +332,8 @@ func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfigurat var nFlows int for _, flow := range flows { - if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=set_field:%s->eth_dst,output:%s", - protoPrefix, protoPrefix, mgmtMasqIP, bridgeMAC, netConfig.ofPortPatch)) { + if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", + protoPrefix, protoPrefix, mgmtMasqIP)) { nFlows++ } } @@ -797,7 +797,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per UDN for table 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 1) + checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -827,7 +827,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for table 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0) + checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) @@ -1028,7 +1028,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per UDN for tables 0 and 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 1) + checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -1058,7 +1058,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for tables 0 and 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0) + checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) @@ -1269,7 +1269,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per advertised UDN for table 2 and table 0 for service isolation. - checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 2) + checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 2) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -1299,7 +1299,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for table 2 and table0 for service isolation. - checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0) + checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 66c258eeae..efdbdb9451 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -882,16 +882,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }), ginkgo.Entry("pod in the UDN should not be able to access a default network service", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { - err := true - out := curlConnectionTimeoutCode - if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { - // FIXME: prevent looping of traffic in L2 UDNs - // bad behaviour: packet is looping from management port -> breth0 -> GR -> management port -> breth0 and so on - // which is a never ending loop - // this causes curl timeout with code 7 host unreachable instead of code 28 - out = "" - } - return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetDefault.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", out, err + return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetDefault.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", curlConnectionTimeoutCode, true }), ginkgo.Entry("pod in the UDN should be able to access kapi in default network service", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { @@ -899,16 +890,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }), ginkgo.Entry("pod in the UDN should not be able to access a service in a different UDN", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { - err := true - out := curlConnectionTimeoutCode - if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() { - // FIXME: prevent looping of traffic in L2 UDNs - // bad behaviour: packet is looping from management port -> breth0 -> GR -> management port -> breth0 and so on - // which is a never ending loop - // this causes curl timeout with code 7 host unreachable instead of code 28 - out = "" - } - return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", out, err + return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", curlConnectionTimeoutCode, true }), ginkgo.Entry("host to a local UDN pod should not work", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { From a0101b5635a5b4e626df7928883b8b8c8c35d79a Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Fri, 25 Apr 2025 11:37:29 +0200 Subject: [PATCH 116/278] kv, e2e: Download virtctl at tests Signed-off-by: Enrique Llorente --- contrib/kind-common | 23 ------- test/e2e/kubevirt.go | 59 ++++++++-------- test/e2e/kubevirt/client.go | 128 +++++++++++++++++++++++++++++++++++ test/e2e/kubevirt/console.go | 32 ++++----- test/e2e/kubevirt/ip.go | 4 +- test/e2e/kubevirt/net.go | 8 +-- test/e2e/kubevirt/nmstate.go | 4 +- test/scripts/install-kind.sh | 3 - 8 files changed, 181 insertions(+), 80 deletions(-) create mode 100644 test/e2e/kubevirt/client.go diff --git a/contrib/kind-common b/contrib/kind-common index bbb7cda7e1..2a564dece0 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -388,30 +388,7 @@ install_kubevirt() { local kubevirt_stable_release_url=$(get_kubevirt_release_url "stable") kubectl -n kubevirt patch kubevirt kubevirt --type=json --patch '[{"op":"add","path":"/spec/configuration/network","value":{}},{"op":"add","path":"/spec/configuration/network/binding","value":{"l2bridge":{"domainAttachmentType":"managedTap","migration":{}}}}]' - - if [ ! -d "./bin" ] - then - mkdir -p ./bin - if_error_exit "Failed to create bin dir!" - fi - - if [[ "$OSTYPE" == "linux-gnu" ]]; then - OS_TYPE="linux" - elif [[ "$OSTYPE" == "darwin"* ]]; then - OS_TYPE="darwin" - fi - - pushd ./bin - if [ ! -f ./virtctl ]; then - kubevirt_stable_release_url=$(get_kubevirt_release_url "stable") - cli_name="virtctl-${kubevirt_stable_release_url##*/}-${OS_TYPE}-${ARCH}" - curl -LO "${kubevirt_stable_release_url}/${cli_name}" - mv ${cli_name} virtctl - if_error_exit "Failed to download virtctl!" - fi - popd - chmod +x ./bin/virtctl } install_cert_manager() { diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 4ae12854a2..5c6adca101 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -8,7 +8,6 @@ import ( "net" "net/netip" "os" - "os/exec" "strings" "sync" "time" @@ -103,6 +102,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun fr = wrappedTestFramework("kv-live-migration") d = diagnostics.New(fr) crClient crclient.Client + virtClient *kubevirt.Client namespace string iperf3DefaultPort = int32(5201) tcpServerPort = int32(9900) @@ -300,7 +300,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun output := "" Eventually(func() error { var err error - output, err = kubevirt.RunCommand(vmi, fmt.Sprintf("curl http://%s", net.JoinHostPort(podIP, "8000")), polling) + output, err = virtClient.RunCommand(vmi, fmt.Sprintf("curl http://%s", net.JoinHostPort(podIP, "8000")), polling) return err }). WithPolling(polling). @@ -316,7 +316,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun polling := 15 * time.Second for podName, serverPodIPs := range serverPodIPsByName { for _, serverPodIP := range serverPodIPs { - output, err := kubevirt.RunCommand(vmi, fmt.Sprintf("iperf3 -t 0 -c %[2]s --logfile /tmp/%[1]s_%[2]s_iperf3.log &", podName, serverPodIP), polling) + output, err := virtClient.RunCommand(vmi, fmt.Sprintf("iperf3 -t 0 -c %[2]s --logfile /tmp/%[1]s_%[2]s_iperf3.log &", podName, serverPodIP), polling) if err != nil { return fmt.Errorf("%s: %w", output, err) } @@ -361,7 +361,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun for _, podIP := range podIPs { iperfLogFile := fmt.Sprintf("/tmp/%s_%s_iperf3.log", podName, podIP) execFn := func(cmd string) (string, error) { - return kubevirt.RunCommand(vmi, cmd, 2*time.Second) + return virtClient.RunCommand(vmi, cmd, 2*time.Second) } checkIperfTraffic(iperfLogFile, execFn, stage) } @@ -404,7 +404,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun startNorthSouthEgressIperfTraffic = func(vmi *kubevirtv1.VirtualMachineInstance, addresses []string, port int32, stage string) error { GinkgoHelper() execFn := func(cmd string) (string, error) { - return kubevirt.RunCommand(vmi, cmd, 5*time.Second) + return virtClient.RunCommand(vmi, cmd, 5*time.Second) } return startNorthSouthIperfTraffic(execFn, addresses, port, "egress", stage) } @@ -431,7 +431,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun for _, ip := range addresses { iperfLogFile := fmt.Sprintf("/tmp/egress_test_%s_%d_iperf3.log", ip, port) execFn := func(cmd string) (string, error) { - return kubevirt.RunCommand(vmi, cmd, 5*time.Second) + return virtClient.RunCommand(vmi, cmd, 5*time.Second) } checkIperfTraffic(iperfLogFile, execFn, stage) } @@ -446,7 +446,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun continue } cmd := fmt.Sprintf("ping -c 3 -W 2 %s", ip) - stdout, err := kubevirt.RunCommand(vmi, cmd, 5*time.Second) + stdout, err := virtClient.RunCommand(vmi, cmd, 5*time.Second) Expect(err).NotTo(HaveOccurred()) Expect(stdout).To(ContainSubstring(" 0% packet loss")) } @@ -514,7 +514,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun step = by(vmName, stage+": Check n/s tcp traffic") output := "" Eventually(func() error { - output, err = kubevirt.RunCommand(vmi, "curl -kL https://kubernetes.default.svc.cluster.local", polling) + output, err = virtClient.RunCommand(vmi, "curl -kL https://kubernetes.default.svc.cluster.local", polling) return err }). WithPolling(polling). @@ -730,7 +730,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun addressByFamily = func(familyFn func(iface kubevirt.Interface) []kubevirt.Address, vmi *kubevirtv1.VirtualMachineInstance) func() ([]kubevirt.Address, error) { return func() ([]kubevirt.Address, error) { - networkState, err := kubevirt.RetrieveNetworkState(vmi) + networkState, err := kubevirt.RetrieveNetworkState(virtClient, vmi) if err != nil { return nil, err } @@ -831,14 +831,14 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun addresses, err := addressByFamily(ipv4, vmi)() Expect(err).NotTo(HaveOccurred()) if isDualStack() { - output, err := kubevirt.RunCommand(vmi, `echo '{"interfaces":[{"name":"enp1s0","type":"ethernet","state":"up","ipv4":{"enabled":true,"dhcp":true},"ipv6":{"enabled":true,"dhcp":true,"autoconf":false}}],"routes":{"config":[{"destination":"::/0","next-hop-interface":"enp1s0","next-hop-address":"fe80::1"}]}}' |nmstatectl apply`, 5*time.Second) + output, err := virtClient.RunCommand(vmi, `echo '{"interfaces":[{"name":"enp1s0","type":"ethernet","state":"up","ipv4":{"enabled":true,"dhcp":true},"ipv6":{"enabled":true,"dhcp":true,"autoconf":false}}],"routes":{"config":[{"destination":"::/0","next-hop-interface":"enp1s0","next-hop-address":"fe80::1"}]}}' |nmstatectl apply`, 5*time.Second) Expect(err).NotTo(HaveOccurred(), output) step = by(vmi.Name, "Wait for virtual machine to receive IPv6 address from DHCP") Eventually(addressByFamily(ipv6, vmi)). WithPolling(time.Second). WithTimeout(5*time.Minute). Should(HaveLen(2), func() string { - output, _ := kubevirt.RunCommand(vmi, "journalctl -u nmstate", 2*time.Second) + output, _ := virtClient.RunCommand(vmi, "journalctl -u nmstate", 2*time.Second) return step + " -> journal nmstate: " + output }) ipv6Addresses, err := addressByFamily(ipv6, vmi)() @@ -1076,7 +1076,7 @@ passwd: } err := crClient.Get(context.TODO(), crclient.ObjectKeyFromObject(vmi), vmi) Expect(err).NotTo(HaveOccurred()) - Expect(kubevirt.LoginToFedora(vmi, "core", "fedora")).To(Succeed(), step) + Expect(virtClient.LoginToFedora(vmi, "core", "fedora")).To(Succeed(), step) waitVirtualMachineAddresses(vmi) @@ -1084,7 +1084,7 @@ passwd: svc, err := fr.ClientSet.CoreV1().Services(namespace).Create(context.TODO(), composeService("tcpserver", vm.Name, tcpServerPort), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), step) defer func() { - output, err := kubevirt.RunCommand(vmi, "podman logs tcpserver", 10*time.Second) + output, err := virtClient.RunCommand(vmi, "podman logs tcpserver", 10*time.Second) Expect(err).NotTo(HaveOccurred()) fmt.Printf("%s tcpserver logs: %s", vmi.Name, output) }() @@ -1334,6 +1334,9 @@ fi var err error crClient, err = newControllerRuntimeClient() Expect(err).NotTo(HaveOccurred()) + + virtClient, err = kubevirt.NewClient("/tmp") + Expect(err).NotTo(HaveOccurred()) }) Context("with default pod network", Ordered, func() { @@ -1533,7 +1536,7 @@ fi description: "restart", cmd: func() { By("Restarting vm") - output, err := exec.Command("virtctl", "restart", "-n", namespace, vmi.Name).CombinedOutput() + output, err := virtClient.RestartVirtualMachine(vmi) Expect(err).NotTo(HaveOccurred(), output) By("Wait some time to vmi conditions to catch up after restart") @@ -1811,7 +1814,7 @@ ip route add %[3]s via %[4]s step := by(vmi.Name, "Login to virtual machine for the first time") Eventually(func() error { - return kubevirt.LoginToFedora(vmi, "fedora", "fedora") + return virtClient.LoginToFedora(vmi, "fedora", "fedora") }). WithTimeout(5*time.Second). WithPolling(time.Second). @@ -1836,7 +1839,7 @@ ip route add %[3]s via %[4]s Expect(testPodsIPs).NotTo(BeEmpty()) Eventually(kubevirt.RetrieveAllGlobalAddressesFromGuest). - WithArguments(vmi). + WithArguments(virtClient, vmi). WithTimeout(5*time.Second). WithPolling(time.Second). Should(ConsistOf(expectedAddresesAtGuest), step) @@ -1855,13 +1858,13 @@ ip route add %[3]s via %[4]s expectedIPv6GatewayPath, err := kubevirt.GenerateGatewayIPv6RouterLLA(nodeRunningVMI, networkName) Expect(err).NotTo(HaveOccurred()) Eventually(kubevirt.RetrieveIPv6Gateways). - WithArguments(vmi). + WithArguments(virtClient, vmi). WithTimeout(5*time.Second). WithPolling(time.Second). Should(Equal([]string{expectedIPv6GatewayPath}), "should filter remote ipv6 gateway nexthop") } step = by(vmi.Name, fmt.Sprintf("Check north/south traffic before %s %s", td.resource.description, td.test.description)) - output, err := kubevirt.RunCommand(vmi, "/tmp/iperf-server.sh", time.Minute) + output, err := virtClient.RunCommand(vmi, "/tmp/iperf-server.sh", time.Minute) Expect(err).NotTo(HaveOccurred(), step+": "+output) Expect(startNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step)).To(Succeed()) checkNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step) @@ -1884,13 +1887,13 @@ ip route add %[3]s via %[4]s td.test.cmd() step = by(vmi.Name, fmt.Sprintf("Login to virtual machine after %s %s", td.resource.description, td.test.description)) - Expect(kubevirt.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) + Expect(virtClient.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) obtainedAddresses := virtualMachineAddressesFromStatus(vmi, expectedNumberOfAddresses) Expect(obtainedAddresses).To(Equal(expectedAddreses)) Eventually(kubevirt.RetrieveAllGlobalAddressesFromGuest). - WithArguments(vmi). + WithArguments(virtClient, vmi). WithTimeout(5*time.Second). WithPolling(time.Second). Should(ConsistOf(expectedAddresesAtGuest), step) @@ -1900,7 +1903,7 @@ ip route add %[3]s via %[4]s // At restart we need re-connect Expect(startEastWestIperfTraffic(vmi, testPodsIPs, step)).To(Succeed(), step) if td.role == udnv1.NetworkRolePrimary { - output, err := kubevirt.RunCommand(vmi, "/tmp/iperf-server.sh &", time.Minute) + output, err := virtClient.RunCommand(vmi, "/tmp/iperf-server.sh &", time.Minute) Expect(err).NotTo(HaveOccurred(), step+": "+output) Expect(startNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step)).To(Succeed()) } @@ -1928,7 +1931,7 @@ ip route add %[3]s via %[4]s Expect(err).NotTo(HaveOccurred(), step) Eventually(kubevirt.RetrieveCachedGatewayMAC). - WithArguments(vmi, "enp1s0", cidrIPv4). + WithArguments(virtClient, vmi, "enp1s0", cidrIPv4). WithTimeout(10*time.Second). WithPolling(time.Second). Should(Equal(expectedGatewayMAC), step) @@ -1942,7 +1945,7 @@ ip route add %[3]s via %[4]s targetNodeIPv6GatewayPath, err := kubevirt.GenerateGatewayIPv6RouterLLA(targetNode, networkName) Expect(err).NotTo(HaveOccurred()) Eventually(kubevirt.RetrieveIPv6Gateways). - WithArguments(vmi). + WithArguments(virtClient, vmi). WithTimeout(5*time.Second). WithPolling(time.Second). Should(Equal([]string{targetNodeIPv6GatewayPath}), "should reconcile ipv6 gateway nexthop after live migration") @@ -2248,14 +2251,14 @@ chpasswd: { expire: False } step := by(vmi.Name, "Login to virtual machine for the first time") Eventually(func() error { - return kubevirt.LoginToFedora(vmi, "fedora", "fedora") + return virtClient.LoginToFedora(vmi, "fedora", "fedora") }). WithTimeout(5*time.Second). WithPolling(time.Second). Should(Succeed(), step) step = by(vmi.Name, "Wait for cloud init to finish at first boot") - output, err := kubevirt.RunCommand(vmi, "cloud-init status --wait", time.Minute) + output, err := virtClient.RunCommand(vmi, "cloud-init status --wait", time.Minute) Expect(err).NotTo(HaveOccurred(), step+": "+output) testPodsIPs := podsMultusNetworkIPs(iperfServerTestPods, podNetworkStatusByNetConfigPredicate(namespace, cudn.Name, strings.ToLower(string(cudn.Spec.Network.Localnet.Role)))) @@ -2272,13 +2275,13 @@ chpasswd: { expire: False } Expect(crClient.Get(context.Background(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) step = by(vmi.Name, "Login to virtual machine after virtual machine instance live migration") - Expect(kubevirt.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) + Expect(virtClient.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) step = by(vmi.Name, "Check east/west traffic after virtual machine instance live migration") checkEastWestIperfTraffic(vmi, testPodsIPs, step) By("Stop iperf3 traffic before force killing vm, so iperf3 server do not get stuck") - output, err = kubevirt.RunCommand(vmi, "killall iperf3", 5*time.Second) + output, err = virtClient.RunCommand(vmi, "killall iperf3", 5*time.Second) Expect(err).ToNot(HaveOccurred(), output) step = by(vmi.Name, fmt.Sprintf("Force kill qemu at node %q where VM is running on", vmi.Status.NodeName)) @@ -2290,7 +2293,7 @@ chpasswd: { expire: False } Expect(crClient.Get(context.TODO(), crclient.ObjectKeyFromObject(vmi), vmi)).To(Succeed()) step = by(vmi.Name, "Login to virtual machine after virtual machine instance force killed") - Expect(kubevirt.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) + Expect(virtClient.LoginToFedora(vmi, "fedora", "fedora")).To(Succeed(), step) step = by(vmi.Name, "Restart iperf traffic after forcing a vm failure") Expect(startEastWestIperfTraffic(vmi, testPodsIPs, step)).To(Succeed(), step) diff --git a/test/e2e/kubevirt/client.go b/test/e2e/kubevirt/client.go new file mode 100644 index 0000000000..60c2cbcc2f --- /dev/null +++ b/test/e2e/kubevirt/client.go @@ -0,0 +1,128 @@ +package kubevirt + +import ( + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + + retry "k8s.io/client-go/util/retry" + + kubevirtv1 "kubevirt.io/api/core/v1" +) + +type Client struct { + path string +} + +func NewClient(cliDir string) (*Client, error) { + // Ensure the virtctl directory exists. + if err := os.MkdirAll(cliDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create virtctl directory %q: %w", cliDir, err) + } + + // Ensure the virtctl executable is present. + if err := ensureVirtctl(cliDir); err != nil { + return nil, fmt.Errorf("failed to ensure virtctl: %w", err) + } + + return &Client{path: filepath.Join(cliDir, "virtctl")}, nil +} + +func (virtctl *Client) RestartVirtualMachine(vmi *kubevirtv1.VirtualMachineInstance) (string, error) { + output, err := exec.Command(virtctl.path, "restart", "-n", vmi.Namespace, vmi.Name).CombinedOutput() + if err != nil { + return "", fmt.Errorf("failed to restart VM: %w", err) + } + return string(output), nil +} + +func ensureVirtctl(cliDir string) error { + // Check if the "virtctl" executable exists in the specified path. + // If it does not exist, call the installVirtctl function. + if _, err := os.Stat(filepath.Join(cliDir, "virtctl")); os.IsNotExist(err) { + return installVirtctl(cliDir) + } else if err != nil { + return fmt.Errorf("error checking virtctl executable: %w", err) + } + return nil +} + +func downloadVirtctlBinary() (io.ReadCloser, error) { + // Fetch the latest stable version of KubeVirt from the stable.txt file. + stableResp, err := http.Get("https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt") + if err != nil { + return nil, fmt.Errorf("failed to fetch stable version: %w", err) + } + defer stableResp.Body.Close() + + // Check if the HTTP response status is OK. + if stableResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch stable version: received status code %d", stableResp.StatusCode) + } + + // Read the version from the response body. + versionBytes, err := io.ReadAll(stableResp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read stable version: %w", err) + } + version := strings.TrimSpace(string(versionBytes)) + + // Construct the download URL for the virtctl binary. + virtctlURL := fmt.Sprintf("https://github.com/kubevirt/kubevirt/releases/download/%s/virtctl-%s-linux-amd64", version, version) + + // Download the virtctl binary. + virtctlResp, err := http.Get(virtctlURL) + if err != nil { + return nil, fmt.Errorf("failed to download virtctl: %w", err) + } + + // Check if the HTTP response status is OK. + if virtctlResp.StatusCode != http.StatusOK { + // Close the body on error to prevent resource leaks + virtctlResp.Body.Close() + return nil, fmt.Errorf("failed to download virtctl: received status code %d", virtctlResp.StatusCode) + } + + return virtctlResp.Body, nil +} + +func installVirtctl(cliDir string) error { + var virtctlBody io.ReadCloser + allErrors := func(err error) bool { + return true + } + err := retry.OnError(retry.DefaultRetry, allErrors, func() error { + var downloadErr error + virtctlBody, downloadErr = downloadVirtctlBinary() + return downloadErr // Return the error if download failed, nil otherwise. + }) + if err != nil { + // If err is not nil here, it means all retries failed. + return err + } + defer virtctlBody.Close() // Ensure the body is closed + + // Save the binary to the specified directory. + cliPath := filepath.Join(cliDir, "virtctl") + outFile, err := os.Create(cliPath) + if err != nil { + return fmt.Errorf("failed to create virtctl file at %s: %w", cliPath, err) + } + defer outFile.Close() + + _, err = io.Copy(outFile, virtctlBody) + if err != nil { + return fmt.Errorf("failed to save virtctl binary to %s: %w", cliPath, err) + } + + // Make the binary executable. + if err := os.Chmod(cliPath, 0755); err != nil { + return fmt.Errorf("failed to make virtctl executable at %s: %w", cliPath, err) + } + + return nil +} diff --git a/test/e2e/kubevirt/console.go b/test/e2e/kubevirt/console.go index 822bd04162..4ca7533be8 100644 --- a/test/e2e/kubevirt/console.go +++ b/test/e2e/kubevirt/console.go @@ -54,21 +54,12 @@ var ( shellFailRegexp = regexp.MustCompile(shellFail) ) -// SafeExpectBatch runs the batch from `expected`, connecting to a VMI's console and -// waiting `wait` seconds for the batch to return. -// It validates that the commands arrive to the console. -// NOTE: This functions heritage limitations from `expectBatchWithValidatedSend` refer to it to check them. -func safeExpectBatch(vmi *v1.VirtualMachineInstance, expected []expect.Batcher, timeout time.Duration) error { - _, err := safeExpectBatchWithResponse(vmi, expected, timeout) - return err -} - // safeExpectBatchWithResponse runs the batch from `expected`, connecting to a VMI's console and // waiting `wait` seconds for the batch to return with a response. // It validates that the commands arrive to the console. // NOTE: This functions inherits limitations from `expectBatchWithValidatedSend`, refer to it for more information. -func safeExpectBatchWithResponse(vmi *v1.VirtualMachineInstance, expected []expect.Batcher, timeout time.Duration) ([]expect.BatchRes, error) { - expecter, _, err := newExpecter(vmi, consoleConnectionTimeout, expect.Verbose(true), expect.VerboseWriter(GinkgoWriter)) +func safeExpectBatchWithResponse(virtctlPath string, vmi *v1.VirtualMachineInstance, expected []expect.Batcher, timeout time.Duration) ([]expect.BatchRes, error) { + expecter, _, err := newExpecter(virtctlPath, vmi, consoleConnectionTimeout, expect.Verbose(true), expect.VerboseWriter(GinkgoWriter)) if err != nil { return nil, err } @@ -81,8 +72,12 @@ func safeExpectBatchWithResponse(vmi *v1.VirtualMachineInstance, expected []expe return resp, err } -func RunCommand(vmi *v1.VirtualMachineInstance, command string, timeout time.Duration) (string, error) { - results, err := safeExpectBatchWithResponse(vmi, []expect.Batcher{ +func (virtctl *Client) RunCommand(vmi *v1.VirtualMachineInstance, command string, timeout time.Duration) (string, error) { + return runCommand(virtctl.path, vmi, command, timeout) +} + +func runCommand(virtctlPath string, vmi *v1.VirtualMachineInstance, command string, timeout time.Duration) (string, error) { + results, err := safeExpectBatchWithResponse(virtctlPath, vmi, []expect.Batcher{ &expect.BSnd{S: "\n"}, &expect.BExp{R: PromptExpression}, &expect.BSnd{S: command + "\n"}, @@ -114,10 +109,11 @@ func skipInput(scanner *bufio.Scanner) bool { // newExpecter will connect to an already logged in VMI console and return the generated expecter it will wait `timeout` for the connection. func newExpecter( + virtctlPath string, vmi *v1.VirtualMachineInstance, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error) { - virtctlCmd := []string{"virtctl", "console", "-n", vmi.Namespace, vmi.Name} + virtctlCmd := []string{virtctlPath, "console", "-n", vmi.Namespace, vmi.Name} return expect.SpawnWithArgs(virtctlCmd, timeout, expect.SendTimeout(timeout), expect.Verbose(true), expect.VerboseWriter(GinkgoWriter)) } @@ -182,13 +178,13 @@ func expectBatchWithValidatedSend(expecter expect.Expecter, batch []expect.Batch return res, err } -func LoginToFedora(vmi *kubevirtv1.VirtualMachineInstance, user, password string) error { - return LoginToFedoraWithHostname(vmi, user, password, vmi.Name) +func (virtctl *Client) LoginToFedora(vmi *kubevirtv1.VirtualMachineInstance, user, password string) error { + return loginToFedoraWithHostname(virtctl.path, vmi, user, password, vmi.Name) } // LoginToFedora performs a console login to a Fedora base VM -func LoginToFedoraWithHostname(vmi *kubevirtv1.VirtualMachineInstance, user, password, hostname string) error { - expecter, _, err := newExpecter(vmi, consoleConnectionTimeout, expect.Verbose(true), expect.VerboseWriter(GinkgoWriter)) +func loginToFedoraWithHostname(virtctlPath string, vmi *kubevirtv1.VirtualMachineInstance, user, password, hostname string) error { + expecter, _, err := newExpecter(virtctlPath, vmi, consoleConnectionTimeout, expect.Verbose(true), expect.VerboseWriter(GinkgoWriter)) if err != nil { return err } diff --git a/test/e2e/kubevirt/ip.go b/test/e2e/kubevirt/ip.go index 180c7d252a..3e11bd9b92 100644 --- a/test/e2e/kubevirt/ip.go +++ b/test/e2e/kubevirt/ip.go @@ -8,7 +8,7 @@ import ( v1 "kubevirt.io/api/core/v1" ) -func RetrieveAllGlobalAddressesFromGuest(vmi *v1.VirtualMachineInstance) ([]string, error) { +func RetrieveAllGlobalAddressesFromGuest(cli *Client, vmi *v1.VirtualMachineInstance) ([]string, error) { ifaces := []struct { Name string `json:"ifname"` Addresses []struct { @@ -19,7 +19,7 @@ func RetrieveAllGlobalAddressesFromGuest(vmi *v1.VirtualMachineInstance) ([]stri } `json:"addr_info"` }{} - output, err := RunCommand(vmi, "ip -j a show", 2*time.Second) + output, err := cli.RunCommand(vmi, "ip -j a show", 2*time.Second) if err != nil { return nil, fmt.Errorf("failed retrieving adresses with ip command: %s: %w", output, err) } diff --git a/test/e2e/kubevirt/net.go b/test/e2e/kubevirt/net.go index 8c65118ae1..03b7e819ff 100644 --- a/test/e2e/kubevirt/net.go +++ b/test/e2e/kubevirt/net.go @@ -27,7 +27,7 @@ nmcli c mod %[1]s ipv4.addresses "" ipv6.addresses "" ipv4.gateway "" ipv6.gatew nmcli d reapply %[1]s`, iface) } -func RetrieveCachedGatewayMAC(vmi *kubevirtv1.VirtualMachineInstance, dev, cidr string) (string, error) { +func RetrieveCachedGatewayMAC(cli *Client, vmi *kubevirtv1.VirtualMachineInstance, dev, cidr string) (string, error) { _, ipNet, err := net.ParseCIDR(cidr) if err != nil { return "", err @@ -35,7 +35,7 @@ func RetrieveCachedGatewayMAC(vmi *kubevirtv1.VirtualMachineInstance, dev, cidr gatewayIP := util.GetNodeGatewayIfAddr(ipNet).IP.String() - output, err := RunCommand(vmi, fmt.Sprintf("ip neigh get %s dev %s", gatewayIP, dev), 2*time.Second) + output, err := cli.RunCommand(vmi, fmt.Sprintf("ip neigh get %s dev %s", gatewayIP, dev), 2*time.Second) if err != nil { return "", fmt.Errorf("%s: %v", output, err) } @@ -46,12 +46,12 @@ func RetrieveCachedGatewayMAC(vmi *kubevirtv1.VirtualMachineInstance, dev, cidr return outputSplit[4], nil } -func RetrieveIPv6Gateways(vmi *v1.VirtualMachineInstance) ([]string, error) { +func RetrieveIPv6Gateways(cli *Client, vmi *v1.VirtualMachineInstance) ([]string, error) { routes := []struct { Gateway string `json:"gateway"` }{} - output, err := RunCommand(vmi, "ip -6 -j route list default", 2*time.Second) + output, err := cli.RunCommand(vmi, "ip -6 -j route list default", 2*time.Second) if err != nil { return nil, fmt.Errorf("%s: %v", output, err) } diff --git a/test/e2e/kubevirt/nmstate.go b/test/e2e/kubevirt/nmstate.go index 10e8e34108..bd852ca794 100644 --- a/test/e2e/kubevirt/nmstate.go +++ b/test/e2e/kubevirt/nmstate.go @@ -27,8 +27,8 @@ type NetworkState struct { Interfaces []Interface `json:"interfaces"` } -func RetrieveNetworkState(vmi *v1.VirtualMachineInstance) (*NetworkState, error) { - output, err := RunCommand(vmi, "nmstatectl show --json", 2*time.Second) +func RetrieveNetworkState(cli *Client, vmi *v1.VirtualMachineInstance) (*NetworkState, error) { + output, err := cli.RunCommand(vmi, "nmstatectl show --json", 2*time.Second) if err != nil { return nil, fmt.Errorf("%s: %v", output, err) } diff --git a/test/scripts/install-kind.sh b/test/scripts/install-kind.sh index d7674159e1..1b41646c7e 100755 --- a/test/scripts/install-kind.sh +++ b/test/scripts/install-kind.sh @@ -78,8 +78,5 @@ else ./kind.sh fi -if [ "$KIND_INSTALL_KUBEVIRT" == true ]; then - sudo mv ./bin/virtctl /usr/local/bin/virtctl -fi popd # go our of $SCRIPT_DIR/../../contrib From b60dbcd9dfbd7dfe26fe082b0e62ccea8c0744a9 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Fri, 25 Apr 2025 11:40:48 +0200 Subject: [PATCH 117/278] kv, e2e: ensure there is no dots at podtest name Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 5c6adca101..94e8ad7b31 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1132,11 +1132,15 @@ passwd: g.Expect(pod.Status.PodIP).NotTo(BeEmpty(), "pod %s has no valid IP address yet", pod.Name) } + sanitizeNodeName = func(nodeName string) string { + return strings.ReplaceAll(nodeName, ".", "-") + } + createHTTPServerPods = func(annotations map[string]string) []*corev1.Pod { var pods []*corev1.Pod for _, selectedNode := range selectedNodes { pod := composeAgnhostPod( - "testpod-"+selectedNode.Name, + "testpod-"+sanitizeNodeName(selectedNode.Name), namespace, selectedNode.Name, "netexec", "--http-port", "8000") @@ -1206,7 +1210,7 @@ fi IPRequest: staticIPs, } } - pod, err := createPod(fr, "testpod-"+node.Name, node.Name, namespace, []string{"bash", "-c"}, map[string]string{}, func(pod *corev1.Pod) { + pod, err := createPod(fr, "testpod-"+sanitizeNodeName(node.Name), node.Name, namespace, []string{"bash", "-c"}, map[string]string{}, func(pod *corev1.Pod) { if nse != nil { pod.Annotations = networkSelectionElements(*nse) } From 318782be67d347c18ffebe21b6bf6c1fa42a2770 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 29 Apr 2025 12:26:54 +0200 Subject: [PATCH 118/278] kv, e2e: Use the ExternalContainer struct instead of name Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 47 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 94e8ad7b31..6a05bb688b 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -393,10 +393,10 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun return nil } - startNorthSouthIngressIperfTraffic = func(containerName string, addresses []string, port int32, stage string) error { + startNorthSouthIngressIperfTraffic = func(container infraapi.ExternalContainer, addresses []string, port int32, stage string) error { GinkgoHelper() execFn := func(cmd string) (string, error) { - return infraprovider.Get().ExecExternalContainerCommand(infraapi.ExternalContainer{Name: containerName}, []string{"bash", "-c", cmd}) + return infraprovider.Get().ExecExternalContainerCommand(container, []string{"bash", "-c", cmd}) } return startNorthSouthIperfTraffic(execFn, addresses, port, "ingress", stage) } @@ -409,13 +409,13 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun return startNorthSouthIperfTraffic(execFn, addresses, port, "egress", stage) } - checkNorthSouthIngressIperfTraffic = func(containerName string, addresses []string, port int32, stage string) { + checkNorthSouthIngressIperfTraffic = func(container infraapi.ExternalContainer, addresses []string, port int32, stage string) { GinkgoHelper() Expect(addresses).NotTo(BeEmpty()) for _, ip := range addresses { iperfLogFile := fmt.Sprintf("/tmp/ingress_test_%s_%d_iperf3.log", ip, port) execFn := func(cmd string) (string, error) { - return infraprovider.Get().ExecExternalContainerCommand(infraapi.ExternalContainer{Name: containerName}, []string{"bash", "-c", cmd}) + return infraprovider.Get().ExecExternalContainerCommand(container, []string{"bash", "-c", cmd}) } checkIperfTraffic(iperfLogFile, execFn, stage) } @@ -1762,23 +1762,22 @@ write_files: iperfServerTestPods, err = createIperfServerPods(selectedNodes, cudn.Name, td.role, []string{}) Expect(err).NotTo(HaveOccurred()) - network, err := infraprovider.Get().PrimaryNetwork() - Expect(err).ShouldNot(HaveOccurred(), "primary network must be available to attach containers") - if containerNetwork := containerNetwork(td); containerNetwork != network.Name() { - network, err = infraprovider.Get().GetNetwork(containerNetwork) - Expect(err).ShouldNot(HaveOccurred(), "must to get alternative network") - } - externalContainerPort := infraprovider.Get().GetExternalContainerPort() - externalContainerName := namespace + "-iperf" - externalContainerSpec := infraapi.ExternalContainer{ - Name: externalContainerName, - Image: images.IPerf3(), - Network: network, - Args: []string{"sleep infinity"}, - ExtPort: externalContainerPort, + var externalContainer infraapi.ExternalContainer + if td.role == udnv1.NetworkRolePrimary { + primaryProviderNetwork, err := infraprovider.Get().PrimaryNetwork() + Expect(err).ShouldNot(HaveOccurred(), "primary network must be available to attach containers") + externalContainerPort := infraprovider.Get().GetExternalContainerPort() + externalContainerName := namespace + "-iperf" + externalContainerSpec := infraapi.ExternalContainer{ + Name: externalContainerName, + Image: images.IPerf3(), + Network: primaryProviderNetwork, + Args: []string{"sleep infinity"}, + ExtPort: externalContainerPort, + } + externalContainer, err = providerCtx.CreateExternalContainer(externalContainerSpec) + Expect(err).ShouldNot(HaveOccurred(), "creation of external container is test dependency") } - externalContainer, err := providerCtx.CreateExternalContainer(externalContainerSpec) - Expect(err).ShouldNot(HaveOccurred(), "creation of external container is test dependency") var externalContainerIPs []string if externalContainer.IsIPv4() { @@ -1870,8 +1869,8 @@ ip route add %[3]s via %[4]s step = by(vmi.Name, fmt.Sprintf("Check north/south traffic before %s %s", td.resource.description, td.test.description)) output, err := virtClient.RunCommand(vmi, "/tmp/iperf-server.sh", time.Minute) Expect(err).NotTo(HaveOccurred(), step+": "+output) - Expect(startNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step)).To(Succeed()) - checkNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step) + Expect(startNorthSouthIngressIperfTraffic(externalContainer, serverIPs, serverPort, step)).To(Succeed()) + checkNorthSouthIngressIperfTraffic(externalContainer, serverIPs, serverPort, step) checkNorthSouthEgressICMPTraffic(vmi, externalContainerIPs, step) if td.ingress == "routed" { _, err := infraprovider.Get().ExecExternalContainerCommand(externalContainer, []string{"bash", "-c", iperfServerScript}) @@ -1909,13 +1908,13 @@ ip route add %[3]s via %[4]s if td.role == udnv1.NetworkRolePrimary { output, err := virtClient.RunCommand(vmi, "/tmp/iperf-server.sh &", time.Minute) Expect(err).NotTo(HaveOccurred(), step+": "+output) - Expect(startNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step)).To(Succeed()) + Expect(startNorthSouthIngressIperfTraffic(externalContainer, serverIPs, serverPort, step)).To(Succeed()) } } checkEastWestIperfTraffic(vmi, testPodsIPs, step) if td.role == udnv1.NetworkRolePrimary { step = by(vmi.Name, fmt.Sprintf("Check north/south traffic after %s %s", td.resource.description, td.test.description)) - checkNorthSouthIngressIperfTraffic(externalContainerName, serverIPs, serverPort, step) + checkNorthSouthIngressIperfTraffic(externalContainer, serverIPs, serverPort, step) checkNorthSouthEgressICMPTraffic(vmi, externalContainerIPs, step) if td.ingress == "routed" { checkNorthSouthEgressIperfTraffic(vmi, externalContainerIPs, iperf3DefaultPort, step) From 7c1de13e6210cd2f5769f7d4094f168fc7f1b4ec Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 29 Apr 2025 11:17:21 +0200 Subject: [PATCH 119/278] e2e: Remove harcoded breth0 Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 2 +- test/e2e/localnet-underlay.go | 13 ++++++------- test/e2e/multihoming.go | 7 ++++--- test/e2e/node_ip_mac_migration.go | 6 +++--- test/e2e/util.go | 2 +- test/scripts/e2e-cp.sh | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 6a05bb688b..b687b1cc42 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1422,7 +1422,7 @@ fi Expect(err).NotTo(HaveOccurred()) d.ConntrackDumpingDaemonSet() - d.OVSFlowsDumpingDaemonSet("breth0") + d.OVSFlowsDumpingDaemonSet(deploymentconfig.Get().ExternalBridgeName()) d.IPTablesDumpingDaemonSet() bandwidthPerMigration := resource.MustParse("40Mi") diff --git a/test/e2e/localnet-underlay.go b/test/e2e/localnet-underlay.go index 03649143dd..97c06d0ecc 100644 --- a/test/e2e/localnet-underlay.go +++ b/test/e2e/localnet-underlay.go @@ -17,15 +17,14 @@ import ( ) const ( - defaultOvsBridge = "breth0" - secondaryBridge = "ovsbr1" - add = "add-br" - del = "del-br" + secondaryBridge = "ovsbr1" + add = "add-br" + del = "del-br" ) func setupUnderlay(ovsPods []v1.Pod, bridgeName, portName, networkName string, vlanID int) error { for _, ovsPod := range ovsPods { - if bridgeName != defaultOvsBridge { + if bridgeName != deploymentconfig.Get().ExternalBridgeName() { if err := addOVSBridge(ovsPod.Namespace, ovsPod.Name, bridgeName); err != nil { return err } @@ -68,7 +67,7 @@ func ovsRemoveSwitchPort(ovsPods []v1.Pod, portName string, newVLANID int) error func teardownUnderlay(ovsPods []v1.Pod, bridgeName string) error { for _, ovsPod := range ovsPods { - if bridgeName != defaultOvsBridge { + if bridgeName != deploymentconfig.Get().ExternalBridgeName() { if err := removeOVSBridge(ovsPod.Namespace, ovsPod.Name, bridgeName); err != nil { return err } @@ -180,7 +179,7 @@ func configureBridgeMappings(podNamespace, podName string, mappings ...BridgeMap func defaultNetworkBridgeMapping() BridgeMapping { return BridgeMapping{ physnet: "physnet", - ovsBridge: "breth0", + ovsBridge: deploymentconfig.Get().ExternalBridgeName(), } } diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index 46ad7eedc5..3fb940ba3e 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -31,6 +31,7 @@ import ( ipgenerator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/ip" util "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" ) const ( @@ -278,7 +279,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { port = 9000 ) - ginkgo.DescribeTable("attached to a localnet network mapped to breth0", + ginkgo.DescribeTable("attached to a localnet network mapped to external primary interface bridge", //nolint:lll func(netConfigParams networkAttachmentConfigParams, clientPodConfig, serverPodConfig podConfiguration, isCollocatedPods bool) { By("Get two scheduable nodes and ensure client and server are located on distinct Nodes") @@ -309,9 +310,9 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Expect(pods).NotTo(BeEmpty()) defer func() { By("tearing down the localnet underlay") - Expect(teardownUnderlay(pods, defaultOvsBridge)).To(Succeed()) + Expect(teardownUnderlay(pods, deploymentconfig.Get().ExternalBridgeName())).To(Succeed()) }() - Expect(setupUnderlay(pods, defaultOvsBridge, "", netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(setupUnderlay(pods, deploymentconfig.Get().ExternalBridgeName(), "", netConfig.networkName, netConfig.vlanID)).To(Succeed()) nad := generateNAD(netConfig) By(fmt.Sprintf("creating the attachment configuration: %v\n", nad)) diff --git a/test/e2e/node_ip_mac_migration.go b/test/e2e/node_ip_mac_migration.go index 0326c2c7b7..d7b12f4b24 100644 --- a/test/e2e/node_ip_mac_migration.go +++ b/test/e2e/node_ip_mac_migration.go @@ -454,7 +454,7 @@ spec: Expect(pods.Items).To(HaveLen(1)) ovnkPod = pods.Items[0] - cmd := "ovs-ofctl dump-flows breth0 table=0" + cmd := fmt.Sprintf("ovs-ofctl dump-flows %s table=0", deploymentconfig.Get().ExternalBridgeName()) err = wait.PollImmediate(framework.Poll, 30*time.Second, func() (bool, error) { stdout, err := e2epodoutput.RunHostCmdWithRetries(ovnkPod.Namespace, ovnkPod.Name, cmd, framework.Poll, 30*time.Second) if err != nil { @@ -515,7 +515,7 @@ spec: time.Sleep(time.Duration(settleTimeout) * time.Second) By(fmt.Sprintf("Checking nodeport flows have been updated to use new IP: %s", migrationWorkerNodeIP)) - cmd := "ovs-ofctl dump-flows breth0 table=0" + cmd := fmt.Sprintf("ovs-ofctl dump-flows %s table=0", deploymentconfig.Get().ExternalBridgeName()) err = wait.PollImmediate(framework.Poll, 30*time.Second, func() (bool, error) { stdout, err := e2epodoutput.RunHostCmdWithRetries(ovnkPod.Namespace, ovnkPod.Name, cmd, framework.Poll, 30*time.Second) if err != nil { @@ -628,7 +628,7 @@ func checkFlowsForMACPeriodically(ovnkPod v1.Pod, addr net.HardwareAddr, duratio } func checkFlowsForMAC(ovnkPod v1.Pod, mac net.HardwareAddr) error { - cmd := "ovs-ofctl dump-flows breth0" + cmd := fmt.Sprintf("ovs-ofctl dump-flows %s", deploymentconfig.Get().ExternalBridgeName()) flowOutput := e2epodoutput.RunHostCmdOrDie(ovnkPod.Namespace, ovnkPod.Name, cmd) lines := strings.Split(flowOutput, "\n") for _, line := range lines { diff --git a/test/e2e/util.go b/test/e2e/util.go index 89ab1e12c9..e31bffe724 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1227,7 +1227,7 @@ func routeToNode(nodeName string, ips []string, mtu int, add bool) error { cmd = []string{"ip", "-6"} } var err error - cmd = append(cmd, "route", ipOp, fmt.Sprintf("%s/%d", ip, mask), "dev", "breth0") + cmd = append(cmd, "route", ipOp, fmt.Sprintf("%s/%d", ip, mask), "dev", deploymentconfig.Get().ExternalBridgeName()) if mtu != 0 { cmd = append(cmd, "mtu", strconv.Itoa(mtu)) } diff --git a/test/scripts/e2e-cp.sh b/test/scripts/e2e-cp.sh index 59fc1cd01a..cbccc5ee29 100755 --- a/test/scripts/e2e-cp.sh +++ b/test/scripts/e2e-cp.sh @@ -145,9 +145,9 @@ else # pod reached from default network through secondary interface, asymetric, configuration does not make sense # TODO: perhaps the secondary network attached pods should not be attached to default network - skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to breth0 can be reached by a client pod in the default network on the same node" - skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to breth0 can be reached by a client pod in the default network on a different node" - + skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to external primary interface bridge can be reached by a client pod in the default network on the same node" + skip "Multi Homing A single pod with an OVN-K secondary network attached to a localnet network mapped to external primary interface bridge can be reached by a client pod in the default network on a different node" + # these tests require metallb but the configuration we do for it is not compatible with the configuration we do to advertise the default network # TODO: consolidate configuration skip "Load Balancer Service Tests with MetalLB" From f1c76a6197fc2c09da581a56f53ce2486dded360 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 29 Apr 2025 11:41:13 +0200 Subject: [PATCH 120/278] e2e, kv: Increase network status timeout Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index b687b1cc42..433749f5ba 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -472,7 +472,7 @@ var _ = Describe("Kubevirt Virtual Machines", feature.VirtualMachineSupport, fun networkStatuses, err = podNetworkStatus(pod, networkStatusPredicate) return networkStatuses, err }). - WithTimeout(5 * time.Second). + WithTimeout(15 * time.Second). WithPolling(200 * time.Millisecond). Should(HaveLen(1)) for _, ip := range networkStatuses[0].IPs { From 9fed90c790221bd4b925ba2a4ad57784a1cbea3f Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Wed, 30 Apr 2025 10:43:35 +0200 Subject: [PATCH 121/278] e2e: Use ovnk allocator and reserve IPs This change replace the customa llocator with the ovnk allocator and also reserver the cluster IPs so test can just ask for another IP without the problem of cluster ip collision. Signed-off-by: Enrique Llorente --- test/e2e/ipalloc/ipalloc.go | 47 ------- test/e2e/ipalloc/primaryipalloc.go | 166 +++++------------------- test/e2e/ipalloc/primaryipalloc_test.go | 90 +++++-------- 3 files changed, 65 insertions(+), 238 deletions(-) delete mode 100644 test/e2e/ipalloc/ipalloc.go diff --git a/test/e2e/ipalloc/ipalloc.go b/test/e2e/ipalloc/ipalloc.go deleted file mode 100644 index 7decbaa0a1..0000000000 --- a/test/e2e/ipalloc/ipalloc.go +++ /dev/null @@ -1,47 +0,0 @@ -package ipalloc - -import ( - "fmt" - "math/big" - "net" -) - -type ipAllocator struct { - net *net.IPNet - // base is a cached version of the start IP in the CIDR range as a *big.Int - base *big.Int - // max is the maximum size of the usable addresses in the range - max int - count int -} - -func newIPAllocator(cidr *net.IPNet) *ipAllocator { - return &ipAllocator{net: cidr, base: getBaseInt(cidr.IP), max: limit(cidr)} -} - -func (n *ipAllocator) AllocateNextIP() (net.IP, error) { - if n.count >= n.max { - return net.IP{}, fmt.Errorf("limit of %d reached", n.max) - } - n.base.Add(n.base, big.NewInt(1)) - n.count += 1 - b := n.base.Bytes() - b = append(make([]byte, 16), b...) - return b[len(b)-16:], nil -} - -func getBaseInt(ip net.IP) *big.Int { - return big.NewInt(0).SetBytes(ip.To16()) -} - -func limit(subnet *net.IPNet) int { - ones, bits := subnet.Mask.Size() - if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 127 { - return 0 - } - // limit to 2^8 (256) IPs for e2es - if bits == 128 && (bits-ones) >= 8 { - return int(1) << uint(8) - } - return int(1) << uint(bits-ones) -} diff --git a/test/e2e/ipalloc/primaryipalloc.go b/test/e2e/ipalloc/primaryipalloc.go index 79a0ae5010..1e7c34bb87 100644 --- a/test/e2e/ipalloc/primaryipalloc.go +++ b/test/e2e/ipalloc/primaryipalloc.go @@ -3,19 +3,20 @@ package ipalloc import ( "context" "fmt" + "net" + "sync" + + ipallocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/kubernetes/typed/core/v1" - "net" - "sync" ) // primaryIPAllocator attempts to allocate an IP in the same subnet as a nodes primary network type primaryIPAllocator struct { mu *sync.Mutex - v4 *ipAllocator - v6 *ipAllocator + v4 *ipallocator.Range + v6 *ipallocator.Range nodeClient v1.NodeInterface } @@ -47,91 +48,37 @@ func newPrimaryIPAllocator(nodeClient v1.NodeInterface) (*primaryIPAllocator, er if len(nodes.Items) == 0 { return ipa, fmt.Errorf("expected at least one node but found zero") } - // FIXME: the approach taken here to find the first node IP+mask and then to increment the second last octet wont work in - // all scenarios (node with /24). We should generate an EgressIP compatible with a Node providers primary network and then take care its unique globally. - // The approach here is to grab initial starting IP from first node found, increment the second last octet. - // Approach taken here won't work for Nodes handed /24 subnets. - nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&nodes.Items[0]) - if err != nil { - return ipa, fmt.Errorf("failed to parse node primary interface address from Node object: %v", err) - } - if nodePrimaryIPs.V4.IP != nil { - // should be ok with /16 and /64 node primary provider subnets - // TODO; fixme; what about /24 subnet Nodes like GCP - nodePrimaryIPs.V4.IP[len(nodePrimaryIPs.V4.IP)-2]++ - ipa.v4 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V4.IP, Mask: nodePrimaryIPs.V4.Net.Mask}) - } - if nodePrimaryIPs.V6.IP != nil { - nodePrimaryIPs.V6.IP[len(nodePrimaryIPs.V6.IP)-2]++ - ipa.v6 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V6.IP, Mask: nodePrimaryIPs.V6.Net.Mask}) - } - // verify the new starting base IP is within all Nodes subnets - if nodePrimaryIPs.V4.IP != nil { - ipNets, err := getNodePrimaryProviderIPs(nodes.Items, false) - if err != nil { - return ipa, err - } - nextIP, err := ipa.v4.AllocateNextIP() - if err != nil { - return ipa, err - } - if !isIPWithinAllSubnets(ipNets, nextIP) { - return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP) - } - } - if nodePrimaryIPs.V6.IP != nil { - ipNets, err := getNodePrimaryProviderIPs(nodes.Items, true) - if err != nil { - return ipa, err - } - nextIP, err := ipa.v6.AllocateNextIP() - if err != nil { - return ipa, err - } - if !isIPWithinAllSubnets(ipNets, nextIP) { - return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP) - } - } - - return ipa, nil -} - -func getNodePrimaryProviderIPs(nodes []corev1.Node, isIPv6 bool) ([]*net.IPNet, error) { - ipNets := make([]*net.IPNet, 0, len(nodes)) - for _, node := range nodes { + for _, node := range nodes.Items { nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&node) if err != nil { - return nil, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err) + return ipa, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err) + } + if nodePrimaryIPs.V4.IP != nil { + if ipa.v4 == nil { + ipa.v4, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V4.Net) + if err != nil { + return ipa, fmt.Errorf("failed to create new CIDR range for IPv4: %v", err) + } + } + if err := ipa.v4.Allocate(nodePrimaryIPs.V4.IP); err != nil { + return ipa, fmt.Errorf("failed to allocate IPv4 %s: %v", nodePrimaryIPs.V4.IP, err) + } + } + if nodePrimaryIPs.V6.IP != nil { + if ipa.v6 == nil { + ipa.v6, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V6.Net) + if err != nil { + return ipa, fmt.Errorf("failed to create new CIDR range for IPv6: %v", err) + } + } + if err := ipa.v6.Allocate(nodePrimaryIPs.V6.IP); err != nil { + return ipa, fmt.Errorf("failed to allocate IPv6 %s: %v", nodePrimaryIPs.V6.IP, err) + } } - var mask net.IPMask - var ip net.IP - if isIPv6 { - ip = nodePrimaryIPs.V6.IP - mask = nodePrimaryIPs.V6.Net.Mask - } else { - ip = nodePrimaryIPs.V4.IP - mask = nodePrimaryIPs.V4.Net.Mask - } - if len(ip) == 0 || len(mask) == 0 { - return nil, fmt.Errorf("failed to find Node %s primary Node IP and/or mask", node.Name) - } - ipNets = append(ipNets, &net.IPNet{IP: ip, Mask: mask}) } - return ipNets, nil -} - -func isIPWithinAllSubnets(ipNets []*net.IPNet, ip net.IP) bool { - if len(ipNets) == 0 { - return false - } - for _, ipNet := range ipNets { - if !ipNet.Contains(ip) { - return false - } - } - return true + return ipa, nil } func (pia *primaryIPAllocator) IncrementAndGetNextV4(times int) (net.IP, error) { @@ -148,12 +95,9 @@ func (pia *primaryIPAllocator) AllocateNextV4() (net.IP, error) { if pia.v4 == nil { return nil, fmt.Errorf("IPv4 is not enable ") } - if pia.v4.net == nil { - return nil, fmt.Errorf("IPv4 is not enabled but Allocation request was called") - } pia.mu.Lock() defer pia.mu.Unlock() - return allocateIP(pia.nodeClient, pia.v4.AllocateNextIP) + return pia.v4.AllocateNext() } func (pia *primaryIPAllocator) IncrementAndGetNextV6(times int) (net.IP, error) { @@ -170,51 +114,7 @@ func (pia primaryIPAllocator) AllocateNextV6() (net.IP, error) { if pia.v6 == nil { return nil, fmt.Errorf("IPv6 is not enabled but Allocation request was called") } - if pia.v6.net == nil { - return nil, fmt.Errorf("ipv6 network is not set") - } pia.mu.Lock() defer pia.mu.Unlock() - return allocateIP(pia.nodeClient, pia.v6.AllocateNextIP) -} - -type allocNextFn func() (net.IP, error) - -func allocateIP(nodeClient v1.NodeInterface, allocateFn allocNextFn) (net.IP, error) { - nodeList, err := nodeClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to list nodes: %v", err) - } - for { - nextIP, err := allocateFn() - if err != nil { - return nil, fmt.Errorf("failed to allocated next IP address: %v", err) - } - firstOctet := nextIP[len(nextIP)-1] - // skip 0 and 1 - if firstOctet == 0 || firstOctet == 1 { - continue - } - isConflict, err := isConflictWithExistingHostIPs(nodeList.Items, nextIP) - if err != nil { - return nil, fmt.Errorf("failed to determine if IP conflicts with existing IPs: %v", err) - } - if !isConflict { - return nextIP, nil - } - } -} - -func isConflictWithExistingHostIPs(nodes []corev1.Node, ip net.IP) (bool, error) { - ipStr := ip.String() - for _, node := range nodes { - nodeIPsSet, err := util.ParseNodeHostCIDRsDropNetMask(&node) - if err != nil { - return false, fmt.Errorf("failed to parse node %s primary annotation info: %v", node.Name, err) - } - if nodeIPsSet.Has(ipStr) { - return true, nil - } - } - return false, nil + return pia.v6.AllocateNext() } diff --git a/test/e2e/ipalloc/primaryipalloc_test.go b/test/e2e/ipalloc/primaryipalloc_test.go index 815915b7ea..1702afe545 100644 --- a/test/e2e/ipalloc/primaryipalloc_test.go +++ b/test/e2e/ipalloc/primaryipalloc_test.go @@ -15,7 +15,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" - utilsnet "k8s.io/utils/net" ) func TestUtilSuite(t *testing.T) { @@ -23,40 +22,6 @@ func TestUtilSuite(t *testing.T) { ginkgo.RunSpecs(t, "node ip alloc suite") } -func TestAllocateNext(t *testing.T) { - tests := []struct { - desc string - input *net.IPNet - output []net.IP - }{ - { - desc: "increments IPv4 address", - input: mustParseCIDRIncIP("192.168.1.5/16"), // mask /24 would fail - output: []net.IP{net.ParseIP("192.168.1.6"), net.ParseIP("192.168.1.7"), net.ParseIP("192.168.1.8")}, - }, - { - desc: "increments IPv6 address", - input: mustParseCIDRIncIP("fc00:f853:ccd:e793::6/64"), - output: []net.IP{net.ParseIP("fc00:f853:ccd:e793::7"), net.ParseIP("fc00:f853:ccd:e793::8"), net.ParseIP("fc00:f853:ccd:e793::9")}, - }, - } - - for i, tc := range tests { - t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) { - nodeIPAlloc := newIPAllocator(tc.input) - for _, expectedIP := range tc.output { - allocatedIP, err := nodeIPAlloc.AllocateNextIP() - if err != nil { - t.Errorf("failed to allocated next IP: %v", err) - } - if !allocatedIP.Equal(expectedIP) { - t.Errorf("Expected IP %q, but got %q", expectedIP.String(), allocatedIP.String()) - } - } - }) - } -} - // mustParseCIDRIncIP parses the IP and CIDR. It adds the IP to the returned IPNet. func mustParseCIDRIncIP(cidr string) *net.IPNet { ip, ipNet, err := net.ParseCIDR(cidr) @@ -78,20 +43,19 @@ type node struct { } func TestIPAlloc(t *testing.T) { + g := gomega.NewWithT(t) + tests := []struct { - desc string - existingPrimaryNodeIPs []node - expectedFromAllocateNext []string + desc string + existingPrimaryNodeIPs []node }{ { - desc: "IPv4", - existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}}, - expectedFromAllocateNext: []string{"192.168.2.3", "192.168.2.4"}, + desc: "IPv4", + existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}}, }, { - desc: "IPv6", - existingPrimaryNodeIPs: []node{{v4: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v4: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}}, - expectedFromAllocateNext: []string{"fc00:f853:ccd:e793::8", "fc00:f853:ccd:e793::9"}, + desc: "IPv6", + existingPrimaryNodeIPs: []node{{v6: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v6: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}}, }, } @@ -103,23 +67,33 @@ func TestIPAlloc(t *testing.T) { t.Errorf(err.Error()) return } - for _, expectedIPStr := range tc.expectedFromAllocateNext { - expectedIP := net.ParseIP(expectedIPStr) - var nextIP net.IP - var err error - if utilsnet.IsIPv6(expectedIP) { - nextIP, err = pipa.AllocateNextV6() - } else { - nextIP, err = pipa.AllocateNextV4() - } - if err != nil || nextIP == nil { - t.Errorf("failed to allocated next IPv4 or IPv6 address. err %v", err) - return + existingIPv4IPs := []string{} + existingIPv6IPs := []string{} + allocatedIPv4IPs := []string{} + allocatedIPv6IPs := []string{} + for _, existingPrimaryNodeIP := range tc.existingPrimaryNodeIPs { + if existingPrimaryNodeIP.v4.ip != "" { + existingIPv4IPs = append(existingIPv4IPs, existingPrimaryNodeIP.v4.ip) + nextIPv4, err := pipa.AllocateNextV4() + g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv4 address") + g.Expect(nextIPv4).ToNot(gomega.BeNil(), "should allocate next IPv4 address") + allocatedIPv4IPs = append(allocatedIPv4IPs, nextIPv4.String()) } - if !nextIP.Equal(expectedIP) { - t.Errorf("expected IP %q, but found %q", expectedIP, nextIP) + + if existingPrimaryNodeIP.v6.ip != "" { + existingIPv6IPs = append(existingIPv6IPs, existingPrimaryNodeIP.v6.ip) + nextIPv6, err := pipa.AllocateNextV6() + g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv6 address") + g.Expect(nextIPv6).ToNot(gomega.BeNil(), "should allocate next IPv6 address") + allocatedIPv6IPs = append(allocatedIPv6IPs, nextIPv6.String()) } } + if len(existingIPv4IPs) > 0 { + g.Expect(allocatedIPv4IPs).NotTo(gomega.ContainElements(existingIPv4IPs)) + } + if len(existingIPv6IPs) > 0 { + g.Expect(allocatedIPv6IPs).NotTo(gomega.ContainElements(existingIPv6IPs)) + } }) } From 1870116b0bb2b4810441e33d1b63e1886d320bd7 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Wed, 28 May 2025 11:39:39 +0200 Subject: [PATCH 122/278] e2e, kv: Use bgpnet for external container network Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 433749f5ba..55e7309cf3 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1764,14 +1764,14 @@ write_files: var externalContainer infraapi.ExternalContainer if td.role == udnv1.NetworkRolePrimary { - primaryProviderNetwork, err := infraprovider.Get().PrimaryNetwork() + providerNetwork, err := infraprovider.Get().GetNetwork(containerNetwork(td)) Expect(err).ShouldNot(HaveOccurred(), "primary network must be available to attach containers") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainerName := namespace + "-iperf" externalContainerSpec := infraapi.ExternalContainer{ Name: externalContainerName, Image: images.IPerf3(), - Network: primaryProviderNetwork, + Network: providerNetwork, Args: []string{"sleep infinity"}, ExtPort: externalContainerPort, } From ae5b6387fc94f6f860895ee69c643b16225a9731 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 29 Apr 2025 12:53:13 +0200 Subject: [PATCH 123/278] e2e: Move underlay setup to providers Signed-off-by: Enrique Llorente --- test/e2e/infraprovider/api/api.go | 18 ++ test/e2e/infraprovider/providers/kind/kind.go | 79 ++++++++ test/e2e/infraprovider/providers/kind/ovs.go | 93 +++++++++ test/e2e/kubevirt.go | 24 +-- test/e2e/localnet-underlay.go | 184 ------------------ test/e2e/multihoming.go | 133 +++++-------- test/e2e/network_segmentation_localnet.go | 38 ++-- 7 files changed, 263 insertions(+), 306 deletions(-) create mode 100644 test/e2e/infraprovider/providers/kind/ovs.go diff --git a/test/e2e/infraprovider/api/api.go b/test/e2e/infraprovider/api/api.go index 5ef104b7f3..545313ce8c 100644 --- a/test/e2e/infraprovider/api/api.go +++ b/test/e2e/infraprovider/api/api.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "strings" + + "k8s.io/kubernetes/test/e2e/framework" ) // Provider represents the infrastructure provider @@ -37,6 +39,21 @@ type Provider interface { GetK8HostPort() uint16 // supported K8 host ports } +// Underlay represents the configuration for an underlay network. +// Note: The physical network referenced by PhysicalNetworkName must be pre-created and available. +type Underlay struct { + // PhysicalNetworkName is the name of the pre-created physical network to use. + PhysicalNetworkName string + // LogicalNetworkName is the logical network name to be used. + LogicalNetworkName string + // BridgeName is the name of the bridge associated with the underlay. + BridgeName string + // PortName is the name of the port on the bridge. + PortName string + // VlanID is the VLAN identifier for the underlay network. + VlanID int +} + type Context interface { CreateExternalContainer(container ExternalContainer) (ExternalContainer, error) DeleteExternalContainer(container ExternalContainer) error @@ -46,6 +63,7 @@ type Context interface { AttachNetwork(network Network, instance string) (NetworkInterface, error) DetachNetwork(network Network, instance string) error GetAttachedNetworks() (Networks, error) + SetupUnderlay(f *framework.Framework, underlay Underlay) error AddCleanUpFn(func() error) } diff --git a/test/e2e/infraprovider/providers/kind/kind.go b/test/e2e/infraprovider/providers/kind/kind.go index f58a5bc746..ff5d1bdd45 100644 --- a/test/e2e/infraprovider/providers/kind/kind.go +++ b/test/e2e/infraprovider/providers/kind/kind.go @@ -13,10 +13,12 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/ovn-org/ovn-kubernetes/test/e2e/containerengine" + "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/portalloc" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/test/e2e/framework" utilnet "k8s.io/utils/net" @@ -359,6 +361,83 @@ func (c *contextKind) getAttachedNetworks() (api.Networks, error) { return attachedNetworks, nil } +func (c *contextKind) SetupUnderlay(f *framework.Framework, underlay api.Underlay) error { + if underlay.LogicalNetworkName == "" { + return fmt.Errorf("underlay logical network name must be set") + } + + if underlay.PhysicalNetworkName == "" { + underlay.PhysicalNetworkName = "underlay" + } + + if underlay.BridgeName == "" { + underlay.BridgeName = secondaryBridge + } + + const ( + ovsKubeNodeLabel = "app=ovnkube-node" + ) + + ovsPodList, err := f.ClientSet.CoreV1().Pods(deploymentconfig.Get().OVNKubernetesNamespace()).List( + context.Background(), + metav1.ListOptions{LabelSelector: ovsKubeNodeLabel}, + ) + if err != nil { + return fmt.Errorf("failed to list OVS pods with label %q at namespace %q: %w", ovsKubeNodeLabel, deploymentconfig.Get().OVNKubernetesNamespace(), err) + } + + if len(ovsPodList.Items) == 0 { + return fmt.Errorf("no pods with label %q in namespace %q", ovsKubeNodeLabel, deploymentconfig.Get().OVNKubernetesNamespace()) + } + for _, ovsPod := range ovsPodList.Items { + if underlay.BridgeName != deploymentconfig.Get().ExternalBridgeName() { + underlayInterface, err := getNetworkInterface(ovsPod.Spec.NodeName, underlay.PhysicalNetworkName) + if err != nil { + return fmt.Errorf("failed to get underlay interface for network %s on node %s: %w", underlay.PhysicalNetworkName, ovsPod.Spec.NodeName, err) + } + c.AddCleanUpFn(func() error { + if err := removeOVSBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName); err != nil { + return fmt.Errorf("failed to remove OVS bridge %s for pod %s/%s during cleanup: %w", underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err) + } + return nil + }) + if err := ensureOVSBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName); err != nil { + return fmt.Errorf("failed to add OVS bridge %s for pod %s/%s: %w", underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err) + } + + if err := ovsAttachPortToBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName, underlayInterface.InfName); err != nil { + return fmt.Errorf("failed to attach port %s to bridge %s for pod %s/%s: %w", underlayInterface.InfName, underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err) + } + if underlay.VlanID > 0 { + if err := ovsEnableVLANAccessPort(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName, underlayInterface.InfName, underlay.VlanID); err != nil { + return fmt.Errorf("failed to enable VLAN %d on port %s for bridge %s for pod %s/%s: %w", underlay.VlanID, underlayInterface.InfName, underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err) + } + } + } + c.AddCleanUpFn(func() error { + if err := configureBridgeMappings( + ovsPod.Namespace, + ovsPod.Name, + defaultNetworkBridgeMapping(), + ); err != nil { + return fmt.Errorf("failed to restore default bridge mappings for pod %s/%s during cleanup: %w", ovsPod.Namespace, ovsPod.Name, err) + } + return nil + }) + + if err := configureBridgeMappings( + ovsPod.Namespace, + ovsPod.Name, + defaultNetworkBridgeMapping(), + bridgeMapping(underlay.LogicalNetworkName, underlay.BridgeName), + ); err != nil { + return fmt.Errorf("failed to configure bridge mappings for pod %s/%s for logical network %s to bridge %s: %w", ovsPod.Namespace, ovsPod.Name, underlay.LogicalNetworkName, underlay.BridgeName, err) + } + } + return nil + +} + func (c *contextKind) AddCleanUpFn(cleanUpFn func() error) { c.Lock() defer c.Unlock() diff --git a/test/e2e/infraprovider/providers/kind/ovs.go b/test/e2e/infraprovider/providers/kind/ovs.go new file mode 100644 index 0000000000..337ae4e702 --- /dev/null +++ b/test/e2e/infraprovider/providers/kind/ovs.go @@ -0,0 +1,93 @@ +package kind + +import ( + "fmt" + "strings" + "time" + + "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + + e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" +) + +const ( + secondaryBridge = "ovsbr1" +) + +func ensureOVSBridge(podNamespace, podName string, bridgeName string) error { + cmd := fmt.Sprintf("ovs-vsctl br-exists %[1]s || ovs-vsctl add-br %[1]s", bridgeName) + if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { + return fmt.Errorf("failed to add ovs bridge %q: %v", bridgeName, err) + } + return nil +} + +func removeOVSBridge(podNamespace, podName string, bridgeName string) error { + cmd := fmt.Sprintf("if ovs-vsctl br-exists %[1]s; then ovs-vsctl del-br %[1]s; fi", bridgeName) + if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { + return fmt.Errorf("failed to remove ovs bridge %q: %v", bridgeName, err) + } + return nil +} + +func ovsAttachPortToBridge(podNamespace, podName string, bridgeName string, portName string) error { + cmd := fmt.Sprintf("ovs-vsctl list port %[2]s || ovs-vsctl add-port %[1]s %[2]s", bridgeName, portName) + if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { + return fmt.Errorf("failed to addadd port %s from OVS bridge %s: %v", portName, bridgeName, err) + } + return nil +} + +func ovsEnableVLANAccessPort(podNamespace, podName string, bridgeName string, portName string, vlanID int) error { + cmd := fmt.Sprintf("ovs-vsctl set port %[1]s tag=%[2]d vlan_mode=access", portName, vlanID) + if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { + return fmt.Errorf("failed to enable vlan access port %s from OVS bridge %s: %v", portName, bridgeName, err) + } + return nil +} + +type BridgeMapping struct { + physnet string + ovsBridge string +} + +func (bm BridgeMapping) String() string { + return fmt.Sprintf("%s:%s", bm.physnet, bm.ovsBridge) +} + +type BridgeMappings []BridgeMapping + +func (bms BridgeMappings) String() string { + return strings.Join(Map(bms, func(bm BridgeMapping) string { return bm.String() }), ",") +} + +func Map[T, V any](items []T, fn func(T) V) []V { + result := make([]V, len(items)) + for i, t := range items { + result[i] = fn(t) + } + return result +} + +func configureBridgeMappings(podNamespace, podName string, mappings ...BridgeMapping) error { + mappingsString := fmt.Sprintf("external_ids:ovn-bridge-mappings=%s", BridgeMappings(mappings).String()) + cmd := strings.Join([]string{"ovs-vsctl", "set", "open", ".", mappingsString}, " ") + if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { + return fmt.Errorf("failed to configure bridge mappings %q: %v", mappingsString, err) + } + return nil +} + +func defaultNetworkBridgeMapping() BridgeMapping { + return BridgeMapping{ + physnet: "physnet", + ovsBridge: deploymentconfig.Get().ExternalBridgeName(), + } +} + +func bridgeMapping(physnet, ovsBridge string) BridgeMapping { + return BridgeMapping{ + physnet: physnet, + ovsBridge: ovsBridge, + } +} diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 55e7309cf3..6410f16087 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1721,17 +1721,7 @@ write_files: if td.topology == udnv1.NetworkTopologyLocalnet { By("setting up the localnet underlay") - nodes := ovsPods(clientSet) - Expect(nodes).NotTo(BeEmpty()) - DeferCleanup(func() { - if e2eframework.TestContext.DeleteNamespace && (e2eframework.TestContext.DeleteNamespaceOnFailure || !CurrentSpecReport().Failed()) { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - } - }) - - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, networkName, 0 /*vlanID*/)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(fr, infraapi.Underlay{LogicalNetworkName: networkName})).To(Succeed()) } createCUDN(cudn) @@ -2209,20 +2199,10 @@ chpasswd: { expire: False } ) DescribeTable("should maintain tcp connection with minimal downtime", func(td func(vmi *kubevirtv1.VirtualMachineInstance)) { By("setting up the localnet underlay") - nodes := ovsPods(clientSet) - Expect(nodes).NotTo(BeEmpty()) - DeferCleanup(func() { - if e2eframework.TestContext.DeleteNamespace && (e2eframework.TestContext.DeleteNamespaceOnFailure || !CurrentSpecReport().Failed()) { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - } - }) - cudn, networkName := kubevirt.GenerateCUDN(namespace, "net1", udnv1.NetworkTopologyLocalnet, udnv1.NetworkRoleSecondary, udnv1.DualStackCIDRs{}) createCUDN(cudn) - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, networkName, 0 /*vlanID*/)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(fr, infraapi.Underlay{LogicalNetworkName: networkName})).To(Succeed()) workerNodeList, err := fr.ClientSet.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{LabelSelector: labels.FormatLabels(map[string]string{"node-role.kubernetes.io/worker": ""})}) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/localnet-underlay.go b/test/e2e/localnet-underlay.go index 97c06d0ecc..8beed9c1ba 100644 --- a/test/e2e/localnet-underlay.go +++ b/test/e2e/localnet-underlay.go @@ -1,195 +1,11 @@ package e2e import ( - "context" "fmt" "os" "os/exec" - "strings" - "time" - - "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - clientset "k8s.io/client-go/kubernetes" - e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" ) -const ( - secondaryBridge = "ovsbr1" - add = "add-br" - del = "del-br" -) - -func setupUnderlay(ovsPods []v1.Pod, bridgeName, portName, networkName string, vlanID int) error { - for _, ovsPod := range ovsPods { - if bridgeName != deploymentconfig.Get().ExternalBridgeName() { - if err := addOVSBridge(ovsPod.Namespace, ovsPod.Name, bridgeName); err != nil { - return err - } - - if vlanID > 0 { - if err := ovsEnableVLANAccessPort(ovsPod.Namespace, ovsPod.Name, bridgeName, portName, vlanID); err != nil { - return err - } - } else { - if err := ovsAttachPortToBridge(ovsPod.Namespace, ovsPod.Name, bridgeName, portName); err != nil { - return err - } - } - } - if err := configureBridgeMappings( - ovsPod.Namespace, - ovsPod.Name, - defaultNetworkBridgeMapping(), - bridgeMapping(networkName, bridgeName), - ); err != nil { - return err - } - } - return nil -} - -func ovsRemoveSwitchPort(ovsPods []v1.Pod, portName string, newVLANID int) error { - for _, ovsPod := range ovsPods { - if err := ovsRemoveVLANAccessPort(ovsPod.Namespace, ovsPod.Name, secondaryBridge, portName); err != nil { - return fmt.Errorf("failed to remove old VLAN port: %v", err) - } - - if err := ovsEnableVLANAccessPort(ovsPod.Namespace, ovsPod.Name, secondaryBridge, portName, newVLANID); err != nil { - return fmt.Errorf("failed to add new VLAN port: %v", err) - } - } - - return nil -} - -func teardownUnderlay(ovsPods []v1.Pod, bridgeName string) error { - for _, ovsPod := range ovsPods { - if bridgeName != deploymentconfig.Get().ExternalBridgeName() { - if err := removeOVSBridge(ovsPod.Namespace, ovsPod.Name, bridgeName); err != nil { - return err - } - } - // restore default bridge mapping - if err := configureBridgeMappings( - ovsPod.Namespace, - ovsPod.Name, - defaultNetworkBridgeMapping(), - ); err != nil { - return err - } - } - return nil -} - -func ovsPods(clientSet clientset.Interface) []v1.Pod { - const ( - ovsNodeLabel = "app=ovs-node" - ) - pods, err := clientSet.CoreV1().Pods(deploymentconfig.Get().OVNKubernetesNamespace()).List( - context.Background(), - metav1.ListOptions{LabelSelector: ovsNodeLabel}, - ) - if err != nil { - return nil - } - return pods.Items -} - -func addOVSBridge(podNamespace, podName string, bridgeName string) error { - cmd := strings.Join([]string{"ovs-vsctl", add, bridgeName}, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to add ovs bridge %q: %v", bridgeName, err) - } - return nil -} - -func removeOVSBridge(podNamespace, podName string, bridgeName string) error { - cmd := strings.Join([]string{"ovs-vsctl", del, bridgeName}, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to add ovs bridge %q: %v", bridgeName, err) - } - return nil -} - -func ovsAttachPortToBridge(podNamespace, podName string, bridgeName string, portName string) error { - cmd := strings.Join([]string{ - "ovs-vsctl", "add-port", bridgeName, portName, - }, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to remove port %s from OVS bridge %s: %v", portName, bridgeName, err) - } - return nil -} - -func ovsEnableVLANAccessPort(podNamespace, podName string, bridgeName string, portName string, vlanID int) error { - cmd := strings.Join([]string{ - "ovs-vsctl", "add-port", bridgeName, portName, fmt.Sprintf("tag=%d", vlanID), "vlan_mode=access", - }, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to remove port %s from OVS bridge %s: %v", portName, bridgeName, err) - } - return nil -} - -func ovsRemoveVLANAccessPort(podNamespace, podName string, bridgeName string, portName string) error { - cmd := strings.Join([]string{ - "ovs-vsctl", "del-port", bridgeName, portName, - }, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to remove port %s from OVS bridge %s: %v", portName, bridgeName, err) - } - return nil -} - -type BridgeMapping struct { - physnet string - ovsBridge string -} - -func (bm BridgeMapping) String() string { - return fmt.Sprintf("%s:%s", bm.physnet, bm.ovsBridge) -} - -type BridgeMappings []BridgeMapping - -func (bms BridgeMappings) String() string { - return strings.Join(Map(bms, func(bm BridgeMapping) string { return bm.String() }), ",") -} - -func Map[T, V any](items []T, fn func(T) V) []V { - result := make([]V, len(items)) - for i, t := range items { - result[i] = fn(t) - } - return result -} - -func configureBridgeMappings(podNamespace, podName string, mappings ...BridgeMapping) error { - mappingsString := fmt.Sprintf("external_ids:ovn-bridge-mappings=%s", BridgeMappings(mappings).String()) - cmd := strings.Join([]string{"ovs-vsctl", "set", "open", ".", mappingsString}, " ") - if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil { - return fmt.Errorf("failed to configure bridge mappings %q: %v", mappingsString, err) - } - return nil -} - -func defaultNetworkBridgeMapping() BridgeMapping { - return BridgeMapping{ - physnet: "physnet", - ovsBridge: deploymentconfig.Get().ExternalBridgeName(), - } -} - -func bridgeMapping(physnet, ovsBridge string) BridgeMapping { - return BridgeMapping{ - physnet: physnet, - ovsBridge: ovsBridge, - } -} - // TODO: make this function idempotent; use golang netlink instead func createVLANInterface(deviceName string, vlanID string, ipAddress *string) error { vlan := vlanName(deviceName, vlanID) diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index 3fb940ba3e..be949e799e 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -32,6 +32,8 @@ import ( ipgenerator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/ip" util "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" + infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" ) const ( @@ -56,9 +58,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { f := wrappedTestFramework("multi-homing") var ( - cs clientset.Interface - nadClient nadclient.K8sCniCncfIoV1Interface - mnpClient mnpclient.K8sCniCncfIoV1beta1Interface + cs clientset.Interface + nadClient nadclient.K8sCniCncfIoV1Interface + mnpClient mnpclient.K8sCniCncfIoV1beta1Interface + providerCtx infraapi.Context ) BeforeEach(func() { @@ -69,6 +72,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Expect(err).NotTo(HaveOccurred()) mnpClient, err = mnpclient.NewForConfig(f.ClientConfig()) Expect(err).NotTo(HaveOccurred()) + providerCtx = infraprovider.Get().NewTestContext() }) Context("A single pod with an OVN-K secondary network", func() { @@ -80,8 +84,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { if netConfig.topology == "localnet" { By("applying ovs bridge mapping") - Expect(setBridgeMappings(cs, defaultNetworkBridgeMapping(), bridgeMapping(netConfig.networkName, secondaryBridge))).NotTo(HaveOccurred()) - ginkgo.DeferCleanup(setBridgeMappings, cs, defaultNetworkBridgeMapping()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) } By("creating the attachment configuration") @@ -306,13 +312,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { } By("setting up the localnet underlay") - pods := ovsPods(cs) - Expect(pods).NotTo(BeEmpty()) - defer func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(pods, deploymentconfig.Get().ExternalBridgeName())).To(Succeed()) - }() - Expect(setupUnderlay(pods, deploymentconfig.Get().ExternalBridgeName(), "", netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + BridgeName: deploymentconfig.Get().ExternalBridgeName(), + LogicalNetworkName: netConfig.networkName, + })).To(Succeed()) nad := generateNAD(netConfig) By(fmt.Sprintf("creating the attachment configuration: %v\n", nad)) @@ -547,16 +550,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { serverPodConfig.namespace = f.Namespace.Name if netConfig.topology == "localnet" { - By("setting up the localnet underlay") - nodes := ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - defer func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - }() - - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) } By("creating the attachment configuration") @@ -902,17 +899,15 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Context("localnet OVN-K secondary network", func() { const ( - clientPodName = "client-pod" - nodeHostnameKey = "kubernetes.io/hostname" - servicePort uint16 = 9000 - dockerNetworkName = "underlay" - underlayServiceIP = "60.128.0.1" - secondaryInterfaceName = "eth1" - expectedOriginalMTU = 1200 + clientPodName = "client-pod" + nodeHostnameKey = "kubernetes.io/hostname" + servicePort uint16 = 9000 + dockerNetworkName = "underlay" + underlayServiceIP = "60.128.0.1" + expectedOriginalMTU = 1200 ) var netConfig networkAttachmentConfig - var nodes []v1.Pod var underlayBridgeName string var cmdWebServer *exec.Cmd @@ -931,9 +926,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }) By("setting up the localnet underlay") - nodes = ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) }) BeforeEach(func() { @@ -982,11 +978,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - }) - It("correctly sets the MTU on the pod", func() { Eventually(func() error { clientPodConfig := podConfiguration{ @@ -1114,7 +1105,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Context("and the service connected to the underlay is reconfigured to connect to the new VLAN-ID", func() { BeforeEach(func() { - Expect(ovsRemoveSwitchPort(nodes, secondaryInterfaceName, newLocalnetVLANID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: newLocalnetVLANID, + })).To(Succeed(), "configuring the OVS bridge with new localnet vlan id") }) It("can now communicate over a localnet secondary network from pod to the underlay service", func() { @@ -1304,9 +1298,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Context("with a trunked configuration", func() { const vlanID = 20 BeforeEach(func() { - nodes = ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - // we are setting up the bridge in trunked mode by not // specifying a particular VLAN ID on the network conf netConfig = newNetworkAttachmentConfig( @@ -1319,7 +1310,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { }) By("setting up the localnet underlay with a trunked configuration") - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed(), "configuring the OVS bridge") + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed(), "configuring the OVS bridge") By(fmt.Sprintf("creating a VLAN interface on top of the bridge connecting the cluster nodes with IP: %s", underlayIP)) cli, err := client.NewClientWithOpts(client.FromEnv) @@ -1344,7 +1338,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { AfterEach(func() { Expect(cmdWebServer.Process.Kill()).NotTo(HaveOccurred(), "kill the python webserver") Expect(deleteVLANInterface(underlayBridgeName, strconv.Itoa(vlanID))).NotTo(HaveOccurred(), "remove the underlay physical configuration") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed(), "tear down the localnet underlay") }) It("the same bridge mapping can be shared by a separate VLAN by using the physical network name attribute", func() { @@ -1424,15 +1417,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { if netConfig.topology == "localnet" { By("setting up the localnet underlay") - nodes := ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - defer func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - }() - - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) } Expect(createNads(f, nadClient, extraNamespace, netConfig)).NotTo(HaveOccurred()) @@ -1851,14 +1839,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { netConfig := newNetworkAttachmentConfig(netConfigParams) By("setting up the localnet underlay") - nodes := ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - defer func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - }() - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) Expect(createNads(f, nadClient, extraNamespace, netConfig)).NotTo(HaveOccurred()) @@ -1983,14 +1967,10 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { netConfig := newNetworkAttachmentConfig(netConfigParams) By("setting up the localnet underlay") - nodes := ovsPods(cs) - Expect(nodes).NotTo(BeEmpty()) - defer func() { - By("tearing down the localnet underlay") - Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed()) - }() - const secondaryInterfaceName = "eth1" - Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, netConfig.networkName, netConfig.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + LogicalNetworkName: netConfig.networkName, + VlanID: netConfig.vlanID, + })).To(Succeed()) Expect(createNads(f, nadClient, extraNamespace, netConfig)).NotTo(HaveOccurred()) @@ -2285,18 +2265,3 @@ func addIPRequestToPodConfig(cs clientset.Interface, podConfig *podConfiguration } return nil } - -func setBridgeMappings(cs clientset.Interface, mappings ...BridgeMapping) error { - pods := ovsPods(cs) - if len(pods) == 0 { - return fmt.Errorf("pods list is empty") - } - - for _, pods := range pods { - if err := configureBridgeMappings(pods.Namespace, pods.Name, mappings...); err != nil { - return err - } - } - - return nil -} diff --git a/test/e2e/network_segmentation_localnet.go b/test/e2e/network_segmentation_localnet.go index 1647baa9fa..3acd6b1c20 100644 --- a/test/e2e/network_segmentation_localnet.go +++ b/test/e2e/network_segmentation_localnet.go @@ -9,6 +9,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" + infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,20 +23,26 @@ import ( ) var _ = Describe("Network Segmentation: Localnet", func() { - f := wrappedTestFramework("network-segmentation-localnet") + var ( + f = wrappedTestFramework("network-segmentation-localnet") + providerCtx infraapi.Context + ) f.SkipNamespaceCreation = true + BeforeEach(func() { + providerCtx = infraprovider.Get().NewTestContext() + }) + It("using ClusterUserDefinedNetwork CR, pods in different namespaces, should communicate over localnet topology", func() { const ( - vlan = 200 - testPort = 9000 - subnetIPv4 = "192.168.100.0/24" - subnetIPv6 = "2001:dbb::/64" - excludeSubnetIPv4 = "192.168.100.0/29" - excludeSubnetIPv6 = "2001:dbb::/120" - secondaryIfaceName = "eth1" - ovsBrName = "ovsbr-eth1" + vlan = 200 + testPort = 9000 + subnetIPv4 = "192.168.100.0/24" + subnetIPv6 = "2001:dbb::/64" + excludeSubnetIPv4 = "192.168.100.0/29" + excludeSubnetIPv6 = "2001:dbb::/120" ) + ovsBrName := "ovsbr-udn" // use unique names to avoid conflicts with tests running in parallel nsBlue := uniqueMetaName("blue") nsRed := uniqueMetaName("red") @@ -42,14 +50,12 @@ var _ = Describe("Network Segmentation: Localnet", func() { physicalNetworkName := uniqueMetaName("localnet1") By("setup the localnet underlay") - ovsPods := ovsPods(f.ClientSet) - Expect(ovsPods).NotTo(BeEmpty()) - DeferCleanup(func() { - By("teardown the localnet underlay") - Expect(teardownUnderlay(ovsPods, ovsBrName)).To(Succeed()) - }) c := networkAttachmentConfig{networkAttachmentConfigParams: networkAttachmentConfigParams{networkName: physicalNetworkName, vlanID: vlan}} - Expect(setupUnderlay(ovsPods, ovsBrName, secondaryIfaceName, c.networkName, c.vlanID)).To(Succeed()) + Expect(providerCtx.SetupUnderlay(f, infraapi.Underlay{ + BridgeName: ovsBrName, + LogicalNetworkName: c.networkName, + VlanID: c.vlanID, + })).To(Succeed()) By("create test namespaces") _, err := f.ClientSet.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsRed}}, metav1.CreateOptions{}) From 115b25a3e4ee0cc4e9d352474492552db390ed08 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Mon, 16 Jun 2025 10:36:22 +0200 Subject: [PATCH 124/278] e2e: Move http servers to external container Signed-off-by: Enrique Llorente --- test/e2e/infraprovider/api/api.go | 15 +-- test/e2e/infraprovider/providers/kind/kind.go | 3 + test/e2e/localnet-underlay.go | 51 --------- test/e2e/multihoming.go | 105 ++++++++---------- 4 files changed, 55 insertions(+), 119 deletions(-) diff --git a/test/e2e/infraprovider/api/api.go b/test/e2e/infraprovider/api/api.go index 545313ce8c..c654f798c3 100644 --- a/test/e2e/infraprovider/api/api.go +++ b/test/e2e/infraprovider/api/api.go @@ -182,13 +182,14 @@ func (n NetworkInterface) GetMAC() string { } type ExternalContainer struct { - Name string - Image string - Network Network - Args []string - ExtPort uint16 - IPv4 string - IPv6 string + Name string + Image string + Network Network + Entrypoint string + Args []string + ExtPort uint16 + IPv4 string + IPv6 string } func (ec ExternalContainer) GetName() string { diff --git a/test/e2e/infraprovider/providers/kind/kind.go b/test/e2e/infraprovider/providers/kind/kind.go index ff5d1bdd45..4d0dc6a226 100644 --- a/test/e2e/infraprovider/providers/kind/kind.go +++ b/test/e2e/infraprovider/providers/kind/kind.go @@ -147,6 +147,9 @@ func (c *contextKind) createExternalContainer(container api.ExternalContainer) ( return container, fmt.Errorf("container %s already exists", container.Name) } cmd := []string{"run", "-itd", "--privileged", "--name", container.Name, "--network", container.Network.Name(), "--hostname", container.Name} + if container.Entrypoint != "" { + cmd = append(cmd, "--entrypoint", container.Entrypoint) + } cmd = append(cmd, container.Image) if len(container.Args) > 0 { cmd = append(cmd, container.Args...) diff --git a/test/e2e/localnet-underlay.go b/test/e2e/localnet-underlay.go index 8beed9c1ba..df8caf702f 100644 --- a/test/e2e/localnet-underlay.go +++ b/test/e2e/localnet-underlay.go @@ -1,52 +1 @@ package e2e - -import ( - "fmt" - "os" - "os/exec" -) - -// TODO: make this function idempotent; use golang netlink instead -func createVLANInterface(deviceName string, vlanID string, ipAddress *string) error { - vlan := vlanName(deviceName, vlanID) - cmd := exec.Command("sudo", "ip", "link", "add", "link", deviceName, "name", vlan, "type", "vlan", "id", vlanID) - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to create vlan interface %s: %v", vlan, err) - } - - cmd = exec.Command("sudo", "ip", "link", "set", "dev", vlan, "up") - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to enable vlan interface %s: %v", vlan, err) - } - - if ipAddress != nil { - cmd = exec.Command("sudo", "ip", "addr", "add", *ipAddress, "dev", vlan) - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to define the vlan interface %q IP Address %s: %v", vlan, *ipAddress, err) - } - } - return nil -} - -// TODO: make this function idempotent; use golang netlink instead -func deleteVLANInterface(deviceName string, vlanID string) error { - vlan := vlanName(deviceName, vlanID) - cmd := exec.Command("sudo", "ip", "link", "del", vlan) - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to delete vlan interface %s: %v", vlan, err) - } - return nil -} - -func vlanName(deviceName string, vlanID string) string { - // MAX IFSIZE 16; got to truncate it to add the vlan suffix - if len(deviceName)+len(vlanID)+1 > 16 { - deviceName = deviceName[:len(deviceName)-len(vlanID)-1] - } - return fmt.Sprintf("%s.%s", deviceName, vlanID) -} diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index be949e799e..3ad1dd46e7 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -5,9 +5,6 @@ import ( "errors" "fmt" "net/netip" - "os" - "os/exec" - "strconv" "strings" "time" @@ -16,7 +13,6 @@ import ( . "github.com/onsi/gomega" "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" - "github.com/docker/docker/client" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,6 +28,7 @@ import ( ipgenerator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/ip" util "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" ) @@ -907,9 +904,9 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { expectedOriginalMTU = 1200 ) - var netConfig networkAttachmentConfig - var underlayBridgeName string - var cmdWebServer *exec.Cmd + var ( + netConfig networkAttachmentConfig + ) underlayIP := underlayServiceIP + "/24" Context("with a service running on the underlay", func() { @@ -932,28 +929,23 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { })).To(Succeed()) }) - BeforeEach(func() { - By("adding IP to the underlay docker bridge") - cli, err := client.NewClientWithOpts(client.FromEnv) - Expect(err).NotTo(HaveOccurred()) - - gatewayIP, err := getNetworkGateway(cli, dockerNetworkName) - Expect(err).NotTo(HaveOccurred()) - - underlayBridgeName, err = findInterfaceByIP(gatewayIP) - Expect(err).NotTo(HaveOccurred()) - - cmd := exec.Command("sudo", "ip", "addr", "add", underlayIP, "dev", underlayBridgeName) - cmd.Stderr = os.Stderr - err = cmd.Run() - Expect(err).NotTo(HaveOccurred()) - }) - BeforeEach(func() { By("starting a service, connected to the underlay") - cmdWebServer = exec.Command("python3", "-m", "http.server", "--bind", underlayServiceIP, strconv.Itoa(int(servicePort))) - cmdWebServer.Stderr = os.Stderr - Expect(cmdWebServer.Start()).NotTo(HaveOccurred(), "failed to create web server, port might be busy") + providerCtx = infraprovider.Get().NewTestContext() + + underlayNetwork, err := infraprovider.Get().GetNetwork(dockerNetworkName) + Expect(err).NotTo(HaveOccurred(), "must get underlay network") + externalContainerName := f.Namespace.Name + "-web-server" + serviceContainerSpec := infraapi.ExternalContainer{ + Name: externalContainerName, + Image: images.AgnHost(), + Network: underlayNetwork, + Entrypoint: "bash", + Args: []string{"-c", fmt.Sprintf("ip a add %s/24 dev eth0 && ./agnhost netexec --http-port=%d", underlayServiceIP, servicePort)}, + ExtPort: servicePort, + } + _, err = providerCtx.CreateExternalContainer(serviceContainerSpec) + Expect(err).NotTo(HaveOccurred(), "must create external container 1") }) BeforeEach(func() { @@ -966,18 +958,6 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - err := cmdWebServer.Process.Kill() - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - cmd := exec.Command("sudo", "ip", "addr", "del", underlayIP, "dev", underlayBridgeName) - cmd.Stderr = os.Stderr - err := cmd.Run() - Expect(err).NotTo(HaveOccurred()) - }) - It("correctly sets the MTU on the pod", func() { Eventually(func() error { clientPodConfig := podConfiguration{ @@ -1008,6 +988,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { By("asserting the *client* pod can contact the underlay service") Expect(connectToServer(clientPodConfig, underlayServiceIP, servicePort)).To(Succeed()) + }) Context("and networkAttachmentDefinition is modified", func() { @@ -1315,29 +1296,30 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { VlanID: netConfig.vlanID, })).To(Succeed(), "configuring the OVS bridge") - By(fmt.Sprintf("creating a VLAN interface on top of the bridge connecting the cluster nodes with IP: %s", underlayIP)) - cli, err := client.NewClientWithOpts(client.FromEnv) - Expect(err).NotTo(HaveOccurred()) - - gatewayIP, err := getNetworkGateway(cli, dockerNetworkName) - Expect(err).NotTo(HaveOccurred()) - - underlayBridgeName, err = findInterfaceByIP(gatewayIP) - Expect(err).NotTo(HaveOccurred()) - Expect(createVLANInterface(underlayBridgeName, strconv.Itoa(vlanID), &underlayIP)).To( - Succeed(), - "create a VLAN interface on the bridge interconnecting the cluster nodes", - ) - - By("starting a service, connected to the underlay") - cmdWebServer = exec.Command("python3", "-m", "http.server", "--bind", underlayServiceIP, strconv.Itoa(port)) - cmdWebServer.Stderr = os.Stderr - Expect(cmdWebServer.Start()).NotTo(HaveOccurred(), "failed to create web server, port might be busy") - }) + By("starting a service, connected to the underlay over a VLAN") + providerCtx = infraprovider.Get().NewTestContext() + + ifName := "eth0" + vlanName := fmt.Sprintf("%s.%d", ifName, vlanID) + underlayNetwork, err := infraprovider.Get().GetNetwork(dockerNetworkName) + Expect(err).NotTo(HaveOccurred(), "must get underlay network") + externalContainerName := f.Namespace.Name + "-web-server" + serviceContainerSpec := infraapi.ExternalContainer{ + Name: externalContainerName, + Image: images.AgnHost(), + Network: underlayNetwork, + Entrypoint: "bash", + ExtPort: servicePort, + Args: []string{"-c", fmt.Sprintf(` +ip link add link %[1]s name %[2]s type vlan id %[3]d +ip link set dev %[2]s up +ip a add %[4]s/24 dev %[2]s +./agnhost netexec --http-port=%[5]d +`, ifName, vlanName, vlanID, underlayServiceIP, servicePort)}, + } + _, err = providerCtx.CreateExternalContainer(serviceContainerSpec) + Expect(err).NotTo(HaveOccurred(), "must create external container 1") - AfterEach(func() { - Expect(cmdWebServer.Process.Kill()).NotTo(HaveOccurred(), "kill the python webserver") - Expect(deleteVLANInterface(underlayBridgeName, strconv.Itoa(vlanID))).NotTo(HaveOccurred(), "remove the underlay physical configuration") }) It("the same bridge mapping can be shared by a separate VLAN by using the physical network name attribute", func() { @@ -1370,6 +1352,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { By(fmt.Sprintf("asserting the *client* pod can contact the underlay service with IP %q on the separate vlan", underlayIP)) Expect(connectToServer(clientPodConfig, underlayServiceIP, servicePort)).To(Succeed()) + }) }) }) From 956981a0f102ab6d34b32a9e3b9aed1c5d18f05f Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Wed, 25 Jun 2025 12:12:39 +0200 Subject: [PATCH 125/278] kv, e2e: Use PrimaryNetwork() Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 6410f16087..839301ae11 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1671,11 +1671,11 @@ write_files: ingress string } var ( - containerNetwork = func(td testData) string { + containerNetwork = func(td testData) (infraapi.Network, error) { if td.ingress == "routed" { - return "bgpnet" + return infraprovider.Get().GetNetwork("bgpnet") } - return "kind" + return infraprovider.Get().PrimaryNetwork() } exposeVMIperfServer = func(td testData, vmi *kubevirtv1.VirtualMachineInstance, vmiAddresses []string) ([]string, int32) { GinkgoHelper() @@ -1754,7 +1754,7 @@ write_files: var externalContainer infraapi.ExternalContainer if td.role == udnv1.NetworkRolePrimary { - providerNetwork, err := infraprovider.Get().GetNetwork(containerNetwork(td)) + providerNetwork, err := containerNetwork(td) Expect(err).ShouldNot(HaveOccurred(), "primary network must be available to attach containers") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainerName := namespace + "-iperf" @@ -1780,8 +1780,8 @@ write_files: if td.ingress == "routed" { // pre=created test dependency and therefore we dont delete frrExternalContainer := infraapi.ExternalContainer{Name: "frr"} - frrNetwork, err := infraprovider.Get().GetNetwork(containerNetwork(td)) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to fetch network %q: %v", containerNetwork(td), err)) + frrNetwork, err := containerNetwork(td) + Expect(err).NotTo(HaveOccurred()) frrExternalContainerInterface, err := infraprovider.Get().GetExternalContainerNetworkInterface(frrExternalContainer, frrNetwork) Expect(err).NotTo(HaveOccurred(), "must fetch FRR container network interface attached to secondary network") From f1a4b4b0fd9b4ce643aaf1f8edb4ed960d46499a Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 12:10:39 +0200 Subject: [PATCH 126/278] [node/egressipgw] Move egressIP functionality to its own package. Make required objects public Signed-off-by: Nadia Pinaeva --- .../node/{ => egressip}/gateway_egressip.go | 60 +++++++++---------- .../egressip/gateway_egressip_suite_test.go | 13 ++++ .../{ => egressip}/gateway_egressip_test.go | 36 +++++------ go-controller/pkg/node/gateway.go | 15 ++--- go-controller/pkg/node/gateway_shared_intf.go | 3 +- 5 files changed, 71 insertions(+), 56 deletions(-) rename go-controller/pkg/node/{ => egressip}/gateway_egressip.go (91%) create mode 100644 go-controller/pkg/node/egressip/gateway_egressip_suite_test.go rename go-controller/pkg/node/{ => egressip}/gateway_egressip_test.go (95%) diff --git a/go-controller/pkg/node/gateway_egressip.go b/go-controller/pkg/node/egressip/gateway_egressip.go similarity index 91% rename from go-controller/pkg/node/gateway_egressip.go rename to go-controller/pkg/node/egressip/gateway_egressip.go index 13e41c4542..38bd2b058e 100644 --- a/go-controller/pkg/node/gateway_egressip.go +++ b/go-controller/pkg/node/egressip/gateway_egressip.go @@ -1,4 +1,4 @@ -package node +package egressip import ( "encoding/json" @@ -75,15 +75,15 @@ func (e markIPs) containsIP(ip net.IP) bool { return false } -type markIPsCache struct { +type MarkIPsCache struct { mu sync.Mutex hasSyncOnce bool markToIPs markIPs IPToMark map[string]int } -func newMarkIPsCache() *markIPsCache { - return &markIPsCache{ +func NewMarkIPsCache() *MarkIPsCache { + return &MarkIPsCache{ mu: sync.Mutex{}, markToIPs: markIPs{ v4: make(map[int]string), @@ -93,7 +93,7 @@ func newMarkIPsCache() *markIPsCache { } } -func (mic *markIPsCache) IsIPPresent(ip net.IP) bool { +func (mic *MarkIPsCache) IsIPPresent(ip net.IP) bool { mic.mu.Lock() defer mic.mu.Unlock() if ip == nil { @@ -103,7 +103,7 @@ func (mic *markIPsCache) IsIPPresent(ip net.IP) bool { return isFound } -func (mic *markIPsCache) insertMarkIP(pktMark util.EgressIPMark, ip net.IP) { +func (mic *MarkIPsCache) insertMarkIP(pktMark util.EgressIPMark, ip net.IP) { mic.mu.Lock() defer mic.mu.Unlock() if ip == nil { @@ -113,7 +113,7 @@ func (mic *markIPsCache) insertMarkIP(pktMark util.EgressIPMark, ip net.IP) { mic.IPToMark[ip.String()] = pktMark.ToInt() } -func (mic *markIPsCache) deleteMarkIP(pktMark util.EgressIPMark, ip net.IP) { +func (mic *MarkIPsCache) deleteMarkIP(pktMark util.EgressIPMark, ip net.IP) { mic.mu.Lock() defer mic.mu.Unlock() if ip == nil { @@ -123,7 +123,7 @@ func (mic *markIPsCache) deleteMarkIP(pktMark util.EgressIPMark, ip net.IP) { delete(mic.IPToMark, ip.String()) } -func (mic *markIPsCache) replaceAll(markIPs markIPs) { +func (mic *MarkIPsCache) replaceAll(markIPs markIPs) { mic.mu.Lock() mic.markToIPs = markIPs for mark, ipv4 := range markIPs.v4 { @@ -135,7 +135,7 @@ func (mic *markIPsCache) replaceAll(markIPs markIPs) { mic.mu.Unlock() } -func (mic *markIPsCache) GetIPv4() map[int]string { +func (mic *MarkIPsCache) GetIPv4() map[int]string { mic.mu.Lock() defer mic.mu.Unlock() dupe := make(map[int]string) @@ -148,7 +148,7 @@ func (mic *markIPsCache) GetIPv4() map[int]string { return dupe } -func (mic *markIPsCache) GetIPv6() map[int]string { +func (mic *MarkIPsCache) GetIPv6() map[int]string { mic.mu.Lock() defer mic.mu.Unlock() dupe := make(map[int]string) @@ -161,19 +161,19 @@ func (mic *markIPsCache) GetIPv6() map[int]string { return dupe } -func (mic *markIPsCache) HasSyncdOnce() bool { +func (mic *MarkIPsCache) HasSyncdOnce() bool { mic.mu.Lock() defer mic.mu.Unlock() return mic.hasSyncOnce } -func (mic *markIPsCache) setSyncdOnce() { +func (mic *MarkIPsCache) setSyncdOnce() { mic.mu.Lock() mic.hasSyncOnce = true mic.mu.Unlock() } -type bridgeEIPAddrManager struct { +type BridgeEIPAddrManager struct { nodeName string bridgeName string nodeAnnotationMu sync.Mutex @@ -182,18 +182,18 @@ type bridgeEIPAddrManager struct { nodeLister corev1listers.NodeLister kube kube.Interface addrManager *linkmanager.Controller - cache *markIPsCache + cache *MarkIPsCache } -// newBridgeEIPAddrManager manages EgressIP IPs that must be added to ovs bridges to support EgressIP feature for user +// NewBridgeEIPAddrManager manages EgressIP IPs that must be added to ovs bridges to support EgressIP feature for user // defined networks. It saves the assigned IPs to its respective Node annotation in-order to understand which IPs it assigned // prior to restarting. // It provides the assigned IPs info node IP handler. Node IP handler must not consider assigned EgressIP IPs as possible node IPs. // Openflow manager must generate the SNAT openflow conditional on packet marks and therefore needs access to EIP IPs and associated packet marks. -// bridgeEIPAddrManager must be able to force Openflow manager to resync if EgressIP assignment for the node changes. -func newBridgeEIPAddrManager(nodeName, bridgeName string, linkManager *linkmanager.Controller, - kube kube.Interface, eIPInformer egressipinformers.EgressIPInformer, nodeInformer corev1informers.NodeInformer) *bridgeEIPAddrManager { - return &bridgeEIPAddrManager{ +// BridgeEIPAddrManager must be able to force Openflow manager to resync if EgressIP assignment for the node changes. +func NewBridgeEIPAddrManager(nodeName, bridgeName string, linkManager *linkmanager.Controller, + kube kube.Interface, eIPInformer egressipinformers.EgressIPInformer, nodeInformer corev1informers.NodeInformer) *BridgeEIPAddrManager { + return &BridgeEIPAddrManager{ nodeName: nodeName, // k8 node name bridgeName: bridgeName, // bridge name for which EIP IPs are managed nodeAnnotationMu: sync.Mutex{}, // mu for updating Node annotation @@ -202,15 +202,15 @@ func newBridgeEIPAddrManager(nodeName, bridgeName string, linkManager *linkmanag nodeLister: nodeInformer.Lister(), kube: kube, addrManager: linkManager, - cache: newMarkIPsCache(), // cache to store pkt mark -> EIP IP. + cache: NewMarkIPsCache(), // cache to store pkt mark -> EIP IP. } } -func (g *bridgeEIPAddrManager) GetCache() *markIPsCache { +func (g *BridgeEIPAddrManager) GetCache() *MarkIPsCache { return g.cache } -func (g *bridgeEIPAddrManager) addEgressIP(eip *egressipv1.EgressIP) (bool, error) { +func (g *BridgeEIPAddrManager) AddEgressIP(eip *egressipv1.EgressIP) (bool, error) { var isUpdated bool if !util.IsEgressIPMarkSet(eip.Annotations) { return isUpdated, nil @@ -237,7 +237,7 @@ func (g *bridgeEIPAddrManager) addEgressIP(eip *egressipv1.EgressIP) (bool, erro return isUpdated, nil } -func (g *bridgeEIPAddrManager) updateEgressIP(oldEIP, newEIP *egressipv1.EgressIP) (bool, error) { +func (g *BridgeEIPAddrManager) UpdateEgressIP(oldEIP, newEIP *egressipv1.EgressIP) (bool, error) { var isUpdated bool // at most, one status item for this node will be found. for _, oldStatus := range oldEIP.Status.Items { @@ -293,7 +293,7 @@ func (g *bridgeEIPAddrManager) updateEgressIP(oldEIP, newEIP *egressipv1.EgressI return isUpdated, nil } -func (g *bridgeEIPAddrManager) deleteEgressIP(eip *egressipv1.EgressIP) (bool, error) { +func (g *BridgeEIPAddrManager) DeleteEgressIP(eip *egressipv1.EgressIP) (bool, error) { var isUpdated bool if !util.IsEgressIPMarkSet(eip.Annotations) { return isUpdated, nil @@ -322,7 +322,7 @@ func (g *bridgeEIPAddrManager) deleteEgressIP(eip *egressipv1.EgressIP) (bool, e return isUpdated, nil } -func (g *bridgeEIPAddrManager) syncEgressIP(objs []interface{}) error { +func (g *BridgeEIPAddrManager) SyncEgressIP(objs []interface{}) error { // caller must synchronise annotIPs, err := g.getAnnotationIPs() if err != nil { @@ -380,7 +380,7 @@ func (g *bridgeEIPAddrManager) syncEgressIP(objs []interface{}) error { // addIPToAnnotation adds an address to the collection of existing addresses stored in the nodes annotation. Caller // may repeat addition of addresses without care for duplicate addresses being added. -func (g *bridgeEIPAddrManager) addIPToAnnotation(candidateIP net.IP) error { +func (g *BridgeEIPAddrManager) addIPToAnnotation(candidateIP net.IP) error { g.nodeAnnotationMu.Lock() defer g.nodeAnnotationMu.Unlock() return retry.RetryOnConflict(retry.DefaultRetry, func() error { @@ -412,7 +412,7 @@ func (g *bridgeEIPAddrManager) addIPToAnnotation(candidateIP net.IP) error { // deleteIPsFromAnnotation deletes address from annotation. If multiple users, callers must synchronise. // deletion of address that doesn't exist will not cause an error. -func (g *bridgeEIPAddrManager) deleteIPsFromAnnotation(candidateIPs ...net.IP) error { +func (g *BridgeEIPAddrManager) deleteIPsFromAnnotation(candidateIPs ...net.IP) error { g.nodeAnnotationMu.Lock() defer g.nodeAnnotationMu.Unlock() return retry.RetryOnConflict(retry.DefaultRetry, func() error { @@ -446,7 +446,7 @@ func (g *bridgeEIPAddrManager) deleteIPsFromAnnotation(candidateIPs ...net.IP) e }) } -func (g *bridgeEIPAddrManager) addIPBridge(ip net.IP) error { +func (g *BridgeEIPAddrManager) addIPBridge(ip net.IP) error { link, err := util.GetNetLinkOps().LinkByName(g.bridgeName) if err != nil { return fmt.Errorf("failed to get link obj by name %s: %v", g.bridgeName, err) @@ -454,7 +454,7 @@ func (g *bridgeEIPAddrManager) addIPBridge(ip net.IP) error { return g.addrManager.AddAddress(getEIPBridgeNetlinkAddress(ip, link.Attrs().Index)) } -func (g *bridgeEIPAddrManager) deleteIPBridge(ip net.IP) error { +func (g *BridgeEIPAddrManager) deleteIPBridge(ip net.IP) error { link, err := util.GetNetLinkOps().LinkByName(g.bridgeName) if err != nil { return fmt.Errorf("failed to get link obj by name %s: %v", g.bridgeName, err) @@ -464,7 +464,7 @@ func (g *bridgeEIPAddrManager) deleteIPBridge(ip net.IP) error { // getAnnotationIPs retrieves the egress IP annotation from the current node Nodes object. If multiple users, callers must synchronise. // if annotation isn't present, empty set is returned -func (g *bridgeEIPAddrManager) getAnnotationIPs() ([]net.IP, error) { +func (g *BridgeEIPAddrManager) getAnnotationIPs() ([]net.IP, error) { node, err := g.nodeLister.Get(g.nodeName) if err != nil { return nil, fmt.Errorf("failed to get node %s from lister: %v", g.nodeName, err) diff --git a/go-controller/pkg/node/egressip/gateway_egressip_suite_test.go b/go-controller/pkg/node/egressip/gateway_egressip_suite_test.go new file mode 100644 index 0000000000..d9d627c882 --- /dev/null +++ b/go-controller/pkg/node/egressip/gateway_egressip_suite_test.go @@ -0,0 +1,13 @@ +package egressip + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestNodeSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Node Gateway EgressIP Suite") +} diff --git a/go-controller/pkg/node/gateway_egressip_test.go b/go-controller/pkg/node/egressip/gateway_egressip_test.go similarity index 95% rename from go-controller/pkg/node/gateway_egressip_test.go rename to go-controller/pkg/node/egressip/gateway_egressip_test.go index db43f7450a..07a03a87b6 100644 --- a/go-controller/pkg/node/gateway_egressip_test.go +++ b/go-controller/pkg/node/egressip/gateway_egressip_test.go @@ -1,4 +1,4 @@ -package node +package egressip import ( "fmt" @@ -67,7 +67,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) - isUpdated, err := addrMgr.addEgressIP(eip) + isUpdated, err := addrMgr.AddEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -82,7 +82,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, "", ipV4Addr) - isUpdated, err := addrMgr.addEgressIP(eip) + isUpdated, err := addrMgr.AddEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -97,7 +97,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, "not-an-integer", ipV4Addr) - isUpdated, err := addrMgr.addEgressIP(eip) + isUpdated, err := addrMgr.AddEgressIP(eip) gomega.Expect(err).Should(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -117,7 +117,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, generateAnnotFromIPs(ipV4Addr2)) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) - isUpdated, err := addrMgr.addEgressIP(eip) + isUpdated, err := addrMgr.AddEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -140,7 +140,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { defer stopFn() assignedEIP := getEIPAssignedToNode(nodeName, mark, ipV4Addr) unassignedEIP := getEIPNotAssignedToNode(mark, ipV4Addr) - isUpdated, err := addrMgr.updateEgressIP(unassignedEIP, assignedEIP) + isUpdated, err := addrMgr.UpdateEgressIP(unassignedEIP, assignedEIP) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -162,10 +162,10 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { defer stopFn() assignedEIP := getEIPAssignedToNode(nodeName, mark, ipV4Addr) unassignedEIP := getEIPNotAssignedToNode(mark, ipV4Addr) - isUpdated, err := addrMgr.updateEgressIP(unassignedEIP, assignedEIP) + isUpdated, err := addrMgr.UpdateEgressIP(unassignedEIP, assignedEIP) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - isUpdated, err = addrMgr.updateEgressIP(assignedEIP, unassignedEIP) + isUpdated, err = addrMgr.UpdateEgressIP(assignedEIP, unassignedEIP) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -191,10 +191,10 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { unassignedEIP := getEIPNotAssignedToNode(mark, ipV4Addr) assignedEIP1 := getEIPAssignedToNode(nodeName, mark, ipV4Addr) assignedEIP2 := getEIPAssignedToNode(nodeName, mark2, ipV4Addr2) - isUpdated, err := addrMgr.updateEgressIP(unassignedEIP, assignedEIP1) + isUpdated, err := addrMgr.UpdateEgressIP(unassignedEIP, assignedEIP1) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - isUpdated, err = addrMgr.updateEgressIP(assignedEIP1, assignedEIP2) + isUpdated, err = addrMgr.UpdateEgressIP(assignedEIP1, assignedEIP2) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -221,10 +221,10 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) - isUpdated, err := addrMgr.addEgressIP(eip) + isUpdated, err := addrMgr.AddEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) - isUpdated, err = addrMgr.deleteEgressIP(eip) + isUpdated, err = addrMgr.DeleteEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeTrue()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -240,7 +240,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, generateAnnotFromIPs(ipV4Addr2)) defer stopFn() eip := getEIPNotAssignedToNode(mark, ipV4Addr) - isUpdated, err := addrMgr.deleteEgressIP(eip) + isUpdated, err := addrMgr.DeleteEgressIP(eip) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process a valid EgressIP") gomega.Expect(isUpdated).Should(gomega.BeFalse()) node, err := addrMgr.nodeLister.Get(nodeName) @@ -265,7 +265,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { eipAssigned1 := getEIPAssignedToNode(nodeName, mark, ipV4Addr) eipAssigned2 := getEIPAssignedToNode(nodeName, mark2, ipV4Addr2) eipUnassigned3 := getEIPNotAssignedToNode(mark3, ipV4Addr3) - err := addrMgr.syncEgressIP([]interface{}{eipAssigned1, eipAssigned2, eipUnassigned3}) + err := addrMgr.SyncEgressIP([]interface{}{eipAssigned1, eipAssigned2, eipUnassigned3}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") @@ -289,7 +289,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { defer stopFn() eipAssigned1 := getEIPAssignedToNode(nodeName, mark, ipV4Addr) eipAssigned2 := getEIPAssignedToNode(nodeName, mark2, ipV4Addr2) - err := addrMgr.syncEgressIP([]interface{}{eipAssigned1, eipAssigned2}) + err := addrMgr.SyncEgressIP([]interface{}{eipAssigned1, eipAssigned2}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") @@ -306,7 +306,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) // previously configured IP defer stopFn() eipAssigned := getEIPAssignedToNode(nodeName, "", ipV4Addr) - err := addrMgr.syncEgressIP([]interface{}{eipAssigned}) + err := addrMgr.SyncEgressIP([]interface{}{eipAssigned}) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "should process valid EgressIPs") node, err := addrMgr.nodeLister.Get(nodeName) gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") @@ -315,7 +315,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { }) }) -func initBridgeEIPAddrManager(nodeName, bridgeName string, bridgeEIPAnnot string) (*bridgeEIPAddrManager, func()) { +func initBridgeEIPAddrManager(nodeName, bridgeName string, bridgeEIPAnnot string) (*BridgeEIPAddrManager, func()) { node := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{Name: nodeName, Annotations: map[string]string{}}, } @@ -327,7 +327,7 @@ func initBridgeEIPAddrManager(nodeName, bridgeName string, bridgeEIPAnnot string gomega.Expect(watchFactory.Start()).Should(gomega.Succeed(), "watch factory should start") gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "watch factory creation must succeed") linkManager := linkmanager.NewController(nodeName, true, true, nil) - return newBridgeEIPAddrManager(nodeName, bridgeName, linkManager, &kube.Kube{KClient: client}, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()), + return NewBridgeEIPAddrManager(nodeName, bridgeName, linkManager, &kube.Kube{KClient: client}, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()), watchFactory.Shutdown } diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index db1bcae279..cae74284b7 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -17,6 +17,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/informer" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/egressip" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -49,7 +50,7 @@ type gateway struct { nodePortWatcher informer.ServiceAndEndpointsEventHandler openflowManager *openflowManager nodeIPManager *addressManager - bridgeEIPAddrManager *bridgeEIPAddrManager + bridgeEIPAddrManager *egressip.BridgeEIPAddrManager initFunc func() error readyFunc func() (bool, error) @@ -233,7 +234,7 @@ func (g *gateway) AddEgressIP(eip *egressipv1.EgressIP) error { if !util.IsNetworkSegmentationSupportEnabled() || !config.OVNKubernetesFeature.EnableInterconnect || config.Gateway.Mode == config.GatewayModeDisabled { return nil } - isSyncRequired, err := g.bridgeEIPAddrManager.addEgressIP(eip) + isSyncRequired, err := g.bridgeEIPAddrManager.AddEgressIP(eip) if err != nil { return err } @@ -249,7 +250,7 @@ func (g *gateway) UpdateEgressIP(oldEIP, newEIP *egressipv1.EgressIP) error { if !util.IsNetworkSegmentationSupportEnabled() || !config.OVNKubernetesFeature.EnableInterconnect || config.Gateway.Mode == config.GatewayModeDisabled { return nil } - isSyncRequired, err := g.bridgeEIPAddrManager.updateEgressIP(oldEIP, newEIP) + isSyncRequired, err := g.bridgeEIPAddrManager.UpdateEgressIP(oldEIP, newEIP) if err != nil { return err } @@ -265,7 +266,7 @@ func (g *gateway) DeleteEgressIP(eip *egressipv1.EgressIP) error { if !util.IsNetworkSegmentationSupportEnabled() || !config.OVNKubernetesFeature.EnableInterconnect || config.Gateway.Mode == config.GatewayModeDisabled { return nil } - isSyncRequired, err := g.bridgeEIPAddrManager.deleteEgressIP(eip) + isSyncRequired, err := g.bridgeEIPAddrManager.DeleteEgressIP(eip) if err != nil { return err } @@ -281,7 +282,7 @@ func (g *gateway) SyncEgressIP(eips []interface{}) error { if !util.IsNetworkSegmentationSupportEnabled() || !config.OVNKubernetesFeature.EnableInterconnect || config.Gateway.Mode == config.GatewayModeDisabled { return nil } - if err := g.bridgeEIPAddrManager.syncEgressIP(eips); err != nil { + if err := g.bridgeEIPAddrManager.SyncEgressIP(eips); err != nil { return err } if err := g.Reconcile(); err != nil { @@ -552,7 +553,7 @@ type bridgeConfiguration struct { ofPortPhys string ofPortHost string netConfig map[string]*bridgeUDNConfiguration - eipMarkIPs *markIPsCache + eipMarkIPs *egressip.MarkIPsCache nextHops []net.IP } @@ -606,7 +607,7 @@ func bridgeForInterface(intfName, nodeName, netConfig: map[string]*bridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, - eipMarkIPs: newMarkIPsCache(), + eipMarkIPs: egressip.NewMarkIPsCache(), } if len(gwNextHops) > 0 { res.nextHops = gwNextHops diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 922a68a2bd..bc8317c8de 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -26,6 +26,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/egressip" nodeipt "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iptables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/linkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" @@ -2470,7 +2471,7 @@ func newGateway( } } if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && config.Gateway.Mode != config.GatewayModeDisabled { - gw.bridgeEIPAddrManager = newBridgeEIPAddrManager(nodeName, gwBridge.bridgeName, linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) + gw.bridgeEIPAddrManager = egressip.NewBridgeEIPAddrManager(nodeName, gwBridge.bridgeName, linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) gwBridge.eipMarkIPs = gw.bridgeEIPAddrManager.GetCache() } gw.nodeIPManager = newAddressManager(nodeName, kube, mgmtPort, watchFactory, gwBridge) From b65a01efe4b96ae36c9256c175e6c25d5c903b76 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 12:57:30 +0200 Subject: [PATCH 127/278] [node/bridgeconfig] move [udn]bridgeconfig to ite own package. Make all fields and methods public for now. Create node/util package for shared functions. Signed-off-by: Nadia Pinaeva --- go-controller/.golangci.yml | 4 + .../pkg/node/bridgeconfig/bridgeconfig.go | 423 ++++++++++++++++++ .../default_node_network_controller_test.go | 49 +- go-controller/pkg/node/gateway.go | 234 +--------- go-controller/pkg/node/gateway_init.go | 143 +----- .../pkg/node/gateway_init_linux_test.go | 43 +- .../pkg/node/gateway_localnet_linux_test.go | 11 +- go-controller/pkg/node/gateway_nftables.go | 11 +- go-controller/pkg/node/gateway_shared_intf.go | 327 +++++++------- go-controller/pkg/node/gateway_udn.go | 150 +------ go-controller/pkg/node/gateway_udn_test.go | 73 +-- go-controller/pkg/node/helper_linux.go | 17 - .../pkg/node/node_ip_handler_linux.go | 13 +- .../pkg/node/node_ip_handler_linux_test.go | 3 +- go-controller/pkg/node/openflow_manager.go | 53 +-- go-controller/pkg/node/types/const.go | 6 + go-controller/pkg/node/util/util.go | 92 ++++ .../pkg/node/util/util_suite_test.go | 13 + go-controller/pkg/node/util/util_test.go | 57 +++ 19 files changed, 905 insertions(+), 817 deletions(-) create mode 100644 go-controller/pkg/node/bridgeconfig/bridgeconfig.go create mode 100644 go-controller/pkg/node/types/const.go create mode 100644 go-controller/pkg/node/util/util.go create mode 100644 go-controller/pkg/node/util/util_suite_test.go create mode 100644 go-controller/pkg/node/util/util_test.go diff --git a/go-controller/.golangci.yml b/go-controller/.golangci.yml index d381676a37..91be64adc3 100644 --- a/go-controller/.golangci.yml +++ b/go-controller/.golangci.yml @@ -60,6 +60,10 @@ linters-settings: # Other frequently used deps - pkg: github.com/ovn-kubernetes/libovsdb/ovsdb alias: "" + - pkg: github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/util + alias: nodeutil + - pkg: github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types + alias: nodetypes revive: rules: diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go new file mode 100644 index 0000000000..c3f3beae32 --- /dev/null +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -0,0 +1,423 @@ +package bridgeconfig + +import ( + "fmt" + "net" + "strings" + "sync" + "sync/atomic" + + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/egressip" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" + nodeutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" +) + +// BridgeUDNConfiguration holds the patchport and ctMark +// information for a given network +type BridgeUDNConfiguration struct { + PatchPort string + OfPortPatch string + MasqCTMark string + PktMark string + V4MasqIPs *udn.MasqueradeIPs + V6MasqIPs *udn.MasqueradeIPs + Subnets []config.CIDRNetworkEntry + NodeSubnets []*net.IPNet + Advertised atomic.Bool +} + +func (netConfig *BridgeUDNConfiguration) ShallowCopy() *BridgeUDNConfiguration { + copy := &BridgeUDNConfiguration{ + PatchPort: netConfig.PatchPort, + OfPortPatch: netConfig.OfPortPatch, + MasqCTMark: netConfig.MasqCTMark, + PktMark: netConfig.PktMark, + V4MasqIPs: netConfig.V4MasqIPs, + V6MasqIPs: netConfig.V6MasqIPs, + Subnets: netConfig.Subnets, + NodeSubnets: netConfig.NodeSubnets, + } + netConfig.Advertised.Store(netConfig.Advertised.Load()) + return copy +} + +func (netConfig *BridgeUDNConfiguration) IsDefaultNetwork() bool { + return netConfig.MasqCTMark == nodetypes.CtMarkOVN +} + +func (netConfig *BridgeUDNConfiguration) SetBridgeNetworkOfPortsInternal() error { + ofportPatch, stderr, err := util.GetOVSOfPort("get", "Interface", netConfig.PatchPort, "ofport") + if err != nil { + return fmt.Errorf("failed while waiting on patch port %q to be created by ovn-controller and "+ + "while getting ofport. stderr: %v, error: %v", netConfig.PatchPort, stderr, err) + } + netConfig.OfPortPatch = ofportPatch + return nil +} + +type BridgeConfiguration struct { + sync.Mutex + NodeName string + BridgeName string + UplinkName string + GwIface string + GwIfaceRep string + Ips []*net.IPNet + InterfaceID string + MacAddress net.HardwareAddr + OfPortPhys string + OfPortHost string + NetConfig map[string]*BridgeUDNConfiguration + EipMarkIPs *egressip.MarkIPsCache + NextHops []net.IP +} + +func (b *BridgeConfiguration) GetGatewayIface() string { + // If GwIface is set, then accelerated GW interface is present and we use it. If else use external bridge instead. + if b.GwIface != "" { + return b.GwIface + } + return b.BridgeName +} + +// UpdateInterfaceIPAddresses sets and returns the bridge's current ips +func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { + b.Lock() + defer b.Unlock() + ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(b.GetGatewayIface()) + if err != nil { + return nil, err + } + + // For DPU, here we need to use the DPU host's IP address which is the tenant cluster's + // host internal IP address instead of the DPU's external bridge IP address. + if config.OvnKubeNode.Mode == types.NodeModeDPU { + nodeAddrStr, err := util.GetNodePrimaryIP(node) + if err != nil { + return nil, err + } + nodeAddr := net.ParseIP(nodeAddrStr) + if nodeAddr == nil { + return nil, fmt.Errorf("failed to parse node IP address. %v", nodeAddrStr) + } + ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(nodeAddr, ifAddrs) + if err != nil { + return nil, err + } + } + + b.Ips = ifAddrs + return ifAddrs, nil +} + +func BridgeForInterface(intfName, nodeName, + physicalNetworkName string, + nodeSubnets, gwIPs []*net.IPNet, + gwNextHops []net.IP, + advertised bool) (*BridgeConfiguration, error) { + var intfRep string + var err error + isGWAcclInterface := false + gwIntf := intfName + + defaultNetConfig := &BridgeUDNConfiguration{ + MasqCTMark: nodetypes.CtMarkOVN, + Subnets: config.Default.ClusterSubnets, + NodeSubnets: nodeSubnets, + } + res := BridgeConfiguration{ + NodeName: nodeName, + NetConfig: map[string]*BridgeUDNConfiguration{ + types.DefaultNetworkName: defaultNetConfig, + }, + EipMarkIPs: egressip.NewMarkIPsCache(), + } + if len(gwNextHops) > 0 { + res.NextHops = gwNextHops + } + res.NetConfig[types.DefaultNetworkName].Advertised.Store(advertised) + + if config.Gateway.GatewayAcceleratedInterface != "" { + // Try to get representor for the specified gateway device. + // If function succeeds, then it is either a valid switchdev VF or SF, and we can use this accelerated device + // for node IP, Host Ofport for Openflow etc. + // If failed - error for improper configuration option + intfRep, err = getRepresentor(config.Gateway.GatewayAcceleratedInterface) + if err != nil { + return nil, fmt.Errorf("gateway accelerated interface %s is not valid: %w", config.Gateway.GatewayAcceleratedInterface, err) + } + gwIntf = config.Gateway.GatewayAcceleratedInterface + isGWAcclInterface = true + klog.Infof("For gateway accelerated interface %s representor: %s", config.Gateway.GatewayAcceleratedInterface, intfRep) + } else { + intfRep, err = getRepresentor(gwIntf) + if err == nil { + isGWAcclInterface = true + } + } + + if isGWAcclInterface { + bridgeName, _, err := util.RunOVSVsctl("port-to-br", intfRep) + if err != nil { + return nil, fmt.Errorf("failed to find bridge that has port %s: %w", intfRep, err) + } + link, err := util.GetNetLinkOps().LinkByName(gwIntf) + if err != nil { + return nil, fmt.Errorf("failed to get netdevice link for %s: %w", gwIntf, err) + } + uplinkName, err := util.GetNicName(bridgeName) + if err != nil { + return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) + } + res.BridgeName = bridgeName + res.UplinkName = uplinkName + res.GwIfaceRep = intfRep + res.GwIface = gwIntf + res.MacAddress = link.Attrs().HardwareAddr + } else if bridgeName, _, err := util.RunOVSVsctl("port-to-br", intfName); err == nil { + // This is an OVS bridge's internal port + uplinkName, err := util.GetNicName(bridgeName) + if err != nil { + return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) + } + res.BridgeName = bridgeName + res.GwIface = bridgeName + res.UplinkName = uplinkName + gwIntf = bridgeName + } else if _, _, err := util.RunOVSVsctl("br-exists", intfName); err != nil { + // This is not a OVS bridge. We need to create a OVS bridge + // and add cluster.GatewayIntf as a port of that bridge. + bridgeName, err := util.NicToBridge(intfName) + if err != nil { + return nil, fmt.Errorf("nicToBridge failed for %s: %w", intfName, err) + } + res.BridgeName = bridgeName + res.GwIface = bridgeName + res.UplinkName = intfName + gwIntf = bridgeName + } else { + // gateway interface is an OVS bridge + uplinkName, err := getIntfName(intfName) + if err != nil { + if config.Gateway.Mode == config.GatewayModeLocal && config.Gateway.AllowNoUplink { + klog.Infof("Could not find uplink for %s, setup gateway bridge with no uplink port, egress IP and egress GW will not work", intfName) + } else { + return nil, fmt.Errorf("failed to find intfName for %s: %w", intfName, err) + } + } else { + res.UplinkName = uplinkName + } + res.BridgeName = intfName + res.GwIface = intfName + } + // Now, we get IP addresses for the bridge + if len(gwIPs) > 0 { + // use gwIPs if provided + res.Ips = gwIPs + } else { + // get IP addresses from OVS bridge. If IP does not exist, + // error out. + res.Ips, err = nodeutil.GetNetworkInterfaceIPAddresses(gwIntf) + if err != nil { + return nil, fmt.Errorf("failed to get interface details for %s: %w", gwIntf, err) + } + } + + if !isGWAcclInterface { // We do not have an accelerated device for Gateway interface + res.MacAddress, err = util.GetOVSPortMACAddress(gwIntf) + if err != nil { + return nil, fmt.Errorf("failed to get MAC address for ovs port %s: %w", gwIntf, err) + } + } + + res.InterfaceID, err = bridgedGatewayNodeSetup(nodeName, res.BridgeName, physicalNetworkName) + if err != nil { + return nil, fmt.Errorf("failed to set up shared interface gateway: %v", err) + } + + // the name of the patch port created by ovn-controller is of the form + // patch--to-br-int + defaultNetConfig.PatchPort = (&util.DefaultNetInfo{}).GetNetworkScopedPatchPortName(res.BridgeName, nodeName) + + // for DPU we use the host MAC address for the Gateway configuration + if config.OvnKubeNode.Mode == types.NodeModeDPU { + hostRep, err := util.GetDPUHostInterface(res.BridgeName) + if err != nil { + return nil, err + } + res.MacAddress, err = util.GetSriovnetOps().GetRepresentorPeerMacAddress(hostRep) + if err != nil { + return nil, err + } + } + return &res, nil +} + +func getRepresentor(intfName string) (string, error) { + deviceID, err := util.GetDeviceIDFromNetdevice(intfName) + if err != nil { + return "", err + } + + return util.GetFunctionRepresentorName(deviceID) +} + +// GetBridgePortConfigurations returns a slice of Network port configurations along with the +// uplinkName and physical port's ofport value +func (b *BridgeConfiguration) GetBridgePortConfigurations() ([]*BridgeUDNConfiguration, string, string) { + b.Lock() + defer b.Unlock() + var netConfigs []*BridgeUDNConfiguration + for _, netConfig := range b.NetConfig { + netConfigs = append(netConfigs, netConfig.ShallowCopy()) + } + return netConfigs, b.UplinkName, b.OfPortPhys +} + +// AddNetworkBridgeConfig adds the patchport and ctMark value for the provided netInfo into the bridge configuration cache +func (b *BridgeConfiguration) AddNetworkBridgeConfig( + nInfo util.NetInfo, + nodeSubnets []*net.IPNet, + masqCTMark, pktMark uint, + v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { + b.Lock() + defer b.Unlock() + + netName := nInfo.GetNetworkName() + patchPort := nInfo.GetNetworkScopedPatchPortName(b.BridgeName, b.NodeName) + + _, found := b.NetConfig[netName] + if !found { + netConfig := &BridgeUDNConfiguration{ + PatchPort: patchPort, + MasqCTMark: fmt.Sprintf("0x%x", masqCTMark), + PktMark: fmt.Sprintf("0x%x", pktMark), + V4MasqIPs: v4MasqIPs, + V6MasqIPs: v6MasqIPs, + Subnets: nInfo.Subnets(), + NodeSubnets: nodeSubnets, + } + netConfig.Advertised.Store(util.IsPodNetworkAdvertisedAtNode(nInfo, b.NodeName)) + + b.NetConfig[netName] = netConfig + } else { + klog.Warningf("Trying to update bridge config for network %s which already"+ + "exists in cache...networks are not mutable...ignoring update", nInfo.GetNetworkName()) + } + return nil +} + +// DelNetworkBridgeConfig deletes the provided netInfo from the bridge configuration cache +func (b *BridgeConfiguration) DelNetworkBridgeConfig(nInfo util.NetInfo) { + b.Lock() + defer b.Unlock() + + delete(b.NetConfig, nInfo.GetNetworkName()) +} + +func (b *BridgeConfiguration) GetNetworkBridgeConfig(networkName string) *BridgeUDNConfiguration { + b.Lock() + defer b.Unlock() + return b.NetConfig[networkName] +} + +// GetActiveNetworkBridgeConfigCopy returns a shallow copy of the network configuration corresponding to the +// provided netInfo. +// +// NOTE: if the network configuration can't be found or if the network is not patched by OVN +// yet this returns nil. +func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName string) *BridgeUDNConfiguration { + b.Lock() + defer b.Unlock() + + if netConfig, found := b.NetConfig[networkName]; found && netConfig.OfPortPatch != "" { + return netConfig.ShallowCopy() + } + return nil +} + +func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { + result := make([]*BridgeUDNConfiguration, 0, len(b.NetConfig)) + for _, netConfig := range b.NetConfig { + if netConfig.OfPortPatch == "" { + continue + } + result = append(result, netConfig) + } + return result +} + +func getIntfName(gatewayIntf string) (string, error) { + // The given (or autodetected) interface is an OVS bridge and this could be + // created by us using util.NicToBridge() or it was pre-created by the user. + + // Is intfName a port of gatewayIntf? + intfName, err := util.GetNicName(gatewayIntf) + if err != nil { + return "", err + } + _, stderr, err := util.RunOVSVsctl("get", "interface", intfName, "ofport") + if err != nil { + return "", fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", + intfName, stderr, err) + } + return intfName, nil +} + +// bridgedGatewayNodeSetup enables forwarding on bridge interface, sets up the physical network name mappings for the bridge, +// and returns an ifaceID created from the bridge name and the node name +func bridgedGatewayNodeSetup(nodeName, bridgeName, physicalNetworkName string) (string, error) { + // IPv6 forwarding is enabled globally + if config.IPv4Mode { + // we use forward slash as path separator to allow dotted bridgeName e.g. foo.200 + stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", bridgeName)) + // systctl output enforces dot as path separator + if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(bridgeName, ".", "/")) { + return "", fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v", + bridgeName, stdout, stderr, err) + } + } + + // ovn-bridge-mappings maps a physical network name to a local ovs bridge + // that provides connectivity to that network. It is in the form of physnet1:br1,physnet2:br2. + // Note that there may be multiple ovs bridge mappings, be sure not to override + // the mappings for the other physical network + stdout, stderr, err := util.RunOVSVsctl("--if-exists", "get", "Open_vSwitch", ".", + "external_ids:ovn-bridge-mappings") + if err != nil { + return "", fmt.Errorf("failed to get ovn-bridge-mappings stderr:%s (%v)", stderr, err) + } + // skip the existing mapping setting for the specified physicalNetworkName + mapString := "" + bridgeMappings := strings.Split(stdout, ",") + for _, bridgeMapping := range bridgeMappings { + m := strings.Split(bridgeMapping, ":") + if network := m[0]; network != physicalNetworkName { + if len(mapString) != 0 { + mapString += "," + } + mapString += bridgeMapping + } + } + if len(mapString) != 0 { + mapString += "," + } + mapString += physicalNetworkName + ":" + bridgeName + + _, stderr, err = util.RunOVSVsctl("set", "Open_vSwitch", ".", + fmt.Sprintf("external_ids:ovn-bridge-mappings=%s", mapString)) + if err != nil { + return "", fmt.Errorf("failed to set ovn-bridge-mappings for ovs bridge %s"+ + ", stderr:%s (%v)", bridgeName, stderr, err) + } + + ifaceID := bridgeName + "_" + nodeName + return ifaceID, nil +} diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index 875b0da694..de35b39e8d 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -22,6 +22,7 @@ import ( adminpolicybasedrouteclient "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/clientset/versioned/fake" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube/mocks" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" @@ -809,14 +810,14 @@ var _ = Describe("Node", func() { Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, @@ -921,14 +922,14 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.254.61 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, @@ -1075,14 +1076,14 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.253.61 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, @@ -1186,14 +1187,14 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2001:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, @@ -1354,14 +1355,14 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, @@ -1482,14 +1483,14 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } nc.Gateway = &gateway{ openflowManager: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index cae74284b7..a617249c52 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -17,6 +17,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/informer" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/egressip" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -355,14 +356,14 @@ func setupUDPAggregationUplink(ifname string) error { func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops []net.IP, nodeSubnets, gwIPs []*net.IPNet, advertised bool, nodeAnnotator kube.Annotator) ( - *bridgeConfiguration, *bridgeConfiguration, error) { - gatewayBridge, err := bridgeForInterface(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, gwNextHops, advertised) + *bridgeconfig.BridgeConfiguration, *bridgeconfig.BridgeConfiguration, error) { + gatewayBridge, err := bridgeconfig.BridgeForInterface(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, gwNextHops, advertised) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", gwIntf, err) } - var egressGWBridge *bridgeConfiguration + var egressGWBridge *bridgeconfig.BridgeConfiguration if egressGatewayIntf != "" { - egressGWBridge, err = bridgeForInterface(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, nil, false) + egressGWBridge, err = bridgeconfig.BridgeForInterface(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, nil, false) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", egressGatewayIntf, err) } @@ -381,7 +382,7 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops "IP fragmentation or large TCP/UDP payloads may not be forwarded correctly.") enableGatewayMTU = false } else { - chkPktLengthSupported, err := util.DetectCheckPktLengthSupport(gatewayBridge.bridgeName) + chkPktLengthSupported, err := util.DetectCheckPktLengthSupport(gatewayBridge.BridgeName) if err != nil { return nil, nil, err } @@ -415,9 +416,9 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops } if config.Default.EnableUDPAggregation { - err = setupUDPAggregationUplink(gatewayBridge.uplinkName) + err = setupUDPAggregationUplink(gatewayBridge.UplinkName) if err == nil && egressGWBridge != nil { - err = setupUDPAggregationUplink(egressGWBridge.uplinkName) + err = setupUDPAggregationUplink(egressGWBridge.UplinkName) } if err != nil { klog.Warningf("Could not enable UDP packet aggregation on uplink interface (aggregation will be disabled): %v", err) @@ -433,18 +434,18 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops l3GwConfig := util.L3GatewayConfig{ Mode: config.Gateway.Mode, ChassisID: chassisID, - BridgeID: gatewayBridge.bridgeName, - InterfaceID: gatewayBridge.interfaceID, - MACAddress: gatewayBridge.macAddress, - IPAddresses: gatewayBridge.ips, + BridgeID: gatewayBridge.BridgeName, + InterfaceID: gatewayBridge.InterfaceID, + MACAddress: gatewayBridge.MacAddress, + IPAddresses: gatewayBridge.Ips, NextHops: gwNextHops, NodePortEnable: config.Gateway.NodeportEnable, VLANID: &config.Gateway.VLANID, } if egressGWBridge != nil { - l3GwConfig.EgressGWInterfaceID = egressGWBridge.interfaceID - l3GwConfig.EgressGWMACAddress = egressGWBridge.macAddress - l3GwConfig.EgressGWIPAddresses = egressGWBridge.ips + l3GwConfig.EgressGWInterfaceID = egressGWBridge.InterfaceID + l3GwConfig.EgressGWMACAddress = egressGWBridge.MacAddress + l3GwConfig.EgressGWIPAddresses = egressGWBridge.Ips } err = util.SetL3GatewayConfig(nodeAnnotator, &l3GwConfig) @@ -466,7 +467,7 @@ func (g *gateway) GetGatewayBridgeIface() string { } func (g *gateway) GetGatewayIface() string { - return g.openflowManager.defaultBridge.gwIface + return g.openflowManager.defaultBridge.GetGatewayIface() } // getMaxFrameLength returns the maximum frame size (ignoring VLAN header) that a gateway can handle @@ -481,11 +482,11 @@ func (g *gateway) SetDefaultGatewayBridgeMAC(macAddr net.HardwareAddr) { } func (g *gateway) SetDefaultPodNetworkAdvertised(isPodNetworkAdvertised bool) { - g.openflowManager.defaultBridge.netConfig[types.DefaultNetworkName].advertised.Store(isPodNetworkAdvertised) + g.openflowManager.defaultBridge.NetConfig[types.DefaultNetworkName].Advertised.Store(isPodNetworkAdvertised) } func (g *gateway) GetDefaultPodNetworkAdvertised() bool { - return g.openflowManager.defaultBridge.netConfig[types.DefaultNetworkName].advertised.Load() + return g.openflowManager.defaultBridge.NetConfig[types.DefaultNetworkName].Advertised.Load() } // Reconcile handles triggering updates to different components of a gateway, like OFM, Services @@ -539,202 +540,3 @@ func (g *gateway) updateSNATRules() error { return addLocalGatewayPodSubnetNATRules(subnets...) } - -type bridgeConfiguration struct { - sync.Mutex - nodeName string - bridgeName string - uplinkName string - gwIface string - gwIfaceRep string - ips []*net.IPNet - interfaceID string - macAddress net.HardwareAddr - ofPortPhys string - ofPortHost string - netConfig map[string]*bridgeUDNConfiguration - eipMarkIPs *egressip.MarkIPsCache - nextHops []net.IP -} - -// updateInterfaceIPAddresses sets and returns the bridge's current ips -func (b *bridgeConfiguration) updateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { - b.Lock() - defer b.Unlock() - ifAddrs, err := getNetworkInterfaceIPAddresses(b.gwIface) - if err != nil { - return nil, err - } - - // For DPU, here we need to use the DPU host's IP address which is the tenant cluster's - // host internal IP address instead of the DPU's external bridge IP address. - if config.OvnKubeNode.Mode == types.NodeModeDPU { - nodeAddrStr, err := util.GetNodePrimaryIP(node) - if err != nil { - return nil, err - } - nodeAddr := net.ParseIP(nodeAddrStr) - if nodeAddr == nil { - return nil, fmt.Errorf("failed to parse node IP address. %v", nodeAddrStr) - } - ifAddrs, err = getDPUHostPrimaryIPAddresses(nodeAddr, ifAddrs) - if err != nil { - return nil, err - } - } - - b.ips = ifAddrs - return ifAddrs, nil -} - -func bridgeForInterface(intfName, nodeName, - physicalNetworkName string, - nodeSubnets, gwIPs []*net.IPNet, - gwNextHops []net.IP, - advertised bool) (*bridgeConfiguration, error) { - var intfRep string - var err error - isGWAcclInterface := false - gwIntf := intfName - - defaultNetConfig := &bridgeUDNConfiguration{ - masqCTMark: ctMarkOVN, - subnets: config.Default.ClusterSubnets, - nodeSubnets: nodeSubnets, - } - res := bridgeConfiguration{ - nodeName: nodeName, - netConfig: map[string]*bridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - eipMarkIPs: egressip.NewMarkIPsCache(), - } - if len(gwNextHops) > 0 { - res.nextHops = gwNextHops - } - res.netConfig[types.DefaultNetworkName].advertised.Store(advertised) - - if config.Gateway.GatewayAcceleratedInterface != "" { - // Try to get representor for the specified gateway device. - // If function succeeds, then it is either a valid switchdev VF or SF, and we can use this accelerated device - // for node IP, Host Ofport for Openflow etc. - // If failed - error for improper configuration option - intfRep, err = getRepresentor(config.Gateway.GatewayAcceleratedInterface) - if err != nil { - return nil, fmt.Errorf("gateway accelerated interface %s is not valid: %w", config.Gateway.GatewayAcceleratedInterface, err) - } - gwIntf = config.Gateway.GatewayAcceleratedInterface - isGWAcclInterface = true - klog.Infof("For gateway accelerated interface %s representor: %s", config.Gateway.GatewayAcceleratedInterface, intfRep) - } else { - intfRep, err = getRepresentor(gwIntf) - if err == nil { - isGWAcclInterface = true - } - } - - if isGWAcclInterface { - bridgeName, _, err := util.RunOVSVsctl("port-to-br", intfRep) - if err != nil { - return nil, fmt.Errorf("failed to find bridge that has port %s: %w", intfRep, err) - } - link, err := util.GetNetLinkOps().LinkByName(gwIntf) - if err != nil { - return nil, fmt.Errorf("failed to get netdevice link for %s: %w", gwIntf, err) - } - uplinkName, err := util.GetNicName(bridgeName) - if err != nil { - return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) - } - res.bridgeName = bridgeName - res.uplinkName = uplinkName - res.gwIfaceRep = intfRep - res.gwIface = gwIntf - res.macAddress = link.Attrs().HardwareAddr - } else if bridgeName, _, err := util.RunOVSVsctl("port-to-br", intfName); err == nil { - // This is an OVS bridge's internal port - uplinkName, err := util.GetNicName(bridgeName) - if err != nil { - return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) - } - res.bridgeName = bridgeName - res.gwIface = bridgeName - res.uplinkName = uplinkName - gwIntf = bridgeName - } else if _, _, err := util.RunOVSVsctl("br-exists", intfName); err != nil { - // This is not a OVS bridge. We need to create a OVS bridge - // and add cluster.GatewayIntf as a port of that bridge. - bridgeName, err := util.NicToBridge(intfName) - if err != nil { - return nil, fmt.Errorf("nicToBridge failed for %s: %w", intfName, err) - } - res.bridgeName = bridgeName - res.gwIface = bridgeName - res.uplinkName = intfName - gwIntf = bridgeName - } else { - // gateway interface is an OVS bridge - uplinkName, err := getIntfName(intfName) - if err != nil { - if config.Gateway.Mode == config.GatewayModeLocal && config.Gateway.AllowNoUplink { - klog.Infof("Could not find uplink for %s, setup gateway bridge with no uplink port, egress IP and egress GW will not work", intfName) - } else { - return nil, fmt.Errorf("failed to find intfName for %s: %w", intfName, err) - } - } else { - res.uplinkName = uplinkName - } - res.bridgeName = intfName - res.gwIface = intfName - } - // Now, we get IP addresses for the bridge - if len(gwIPs) > 0 { - // use gwIPs if provided - res.ips = gwIPs - } else { - // get IP addresses from OVS bridge. If IP does not exist, - // error out. - res.ips, err = getNetworkInterfaceIPAddresses(gwIntf) - if err != nil { - return nil, fmt.Errorf("failed to get interface details for %s: %w", gwIntf, err) - } - } - - if !isGWAcclInterface { // We do not have an accelerated device for Gateway interface - res.macAddress, err = util.GetOVSPortMACAddress(gwIntf) - if err != nil { - return nil, fmt.Errorf("failed to get MAC address for ovs port %s: %w", gwIntf, err) - } - } - - res.interfaceID, err = bridgedGatewayNodeSetup(nodeName, res.bridgeName, physicalNetworkName) - if err != nil { - return nil, fmt.Errorf("failed to set up shared interface gateway: %v", err) - } - - // the name of the patch port created by ovn-controller is of the form - // patch--to-br-int - defaultNetConfig.patchPort = (&util.DefaultNetInfo{}).GetNetworkScopedPatchPortName(res.bridgeName, nodeName) - - // for DPU we use the host MAC address for the Gateway configuration - if config.OvnKubeNode.Mode == types.NodeModeDPU { - hostRep, err := util.GetDPUHostInterface(res.bridgeName) - if err != nil { - return nil, err - } - res.macAddress, err = util.GetSriovnetOps().GetRepresentorPeerMacAddress(hostRep) - if err != nil { - return nil, err - } - } - return &res, nil -} - -func getRepresentor(intfName string) (string, error) { - deviceID, err := util.GetDeviceIDFromNetdevice(intfName) - if err != nil { - return "", err - } - - return util.GetFunctionRepresentorName(deviceID) -} diff --git a/go-controller/pkg/node/gateway_init.go b/go-controller/pkg/node/gateway_init.go index 28e0fa669b..4fe0b244fd 100644 --- a/go-controller/pkg/node/gateway_init.go +++ b/go-controller/pkg/node/gateway_init.go @@ -18,96 +18,11 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" + nodeutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) -// bridgedGatewayNodeSetup enables forwarding on bridge interface, sets up the physical network name mappings for the bridge, -// and returns an ifaceID created from the bridge name and the node name -func bridgedGatewayNodeSetup(nodeName, bridgeName, physicalNetworkName string) (string, error) { - // IPv6 forwarding is enabled globally - if config.IPv4Mode { - // we use forward slash as path separator to allow dotted bridgeName e.g. foo.200 - stdout, stderr, err := util.RunSysctl("-w", fmt.Sprintf("net/ipv4/conf/%s/forwarding=1", bridgeName)) - // systctl output enforces dot as path separator - if err != nil || stdout != fmt.Sprintf("net.ipv4.conf.%s.forwarding = 1", strings.ReplaceAll(bridgeName, ".", "/")) { - return "", fmt.Errorf("could not set the correct forwarding value for interface %s: stdout: %v, stderr: %v, err: %v", - bridgeName, stdout, stderr, err) - } - } - - // ovn-bridge-mappings maps a physical network name to a local ovs bridge - // that provides connectivity to that network. It is in the form of physnet1:br1,physnet2:br2. - // Note that there may be multiple ovs bridge mappings, be sure not to override - // the mappings for the other physical network - stdout, stderr, err := util.RunOVSVsctl("--if-exists", "get", "Open_vSwitch", ".", - "external_ids:ovn-bridge-mappings") - if err != nil { - return "", fmt.Errorf("failed to get ovn-bridge-mappings stderr:%s (%v)", stderr, err) - } - // skip the existing mapping setting for the specified physicalNetworkName - mapString := "" - bridgeMappings := strings.Split(stdout, ",") - for _, bridgeMapping := range bridgeMappings { - m := strings.Split(bridgeMapping, ":") - if network := m[0]; network != physicalNetworkName { - if len(mapString) != 0 { - mapString += "," - } - mapString += bridgeMapping - } - } - if len(mapString) != 0 { - mapString += "," - } - mapString += physicalNetworkName + ":" + bridgeName - - _, stderr, err = util.RunOVSVsctl("set", "Open_vSwitch", ".", - fmt.Sprintf("external_ids:ovn-bridge-mappings=%s", mapString)) - if err != nil { - return "", fmt.Errorf("failed to set ovn-bridge-mappings for ovs bridge %s"+ - ", stderr:%s (%v)", bridgeName, stderr, err) - } - - ifaceID := bridgeName + "_" + nodeName - return ifaceID, nil -} - -// getNetworkInterfaceIPAddresses returns the IP addresses for the network interface 'iface'. -func getNetworkInterfaceIPAddresses(iface string) ([]*net.IPNet, error) { - allIPs, err := util.GetFilteredInterfaceV4V6IPs(iface) - if err != nil { - return nil, fmt.Errorf("could not find IP addresses: %v", err) - } - - var ips []*net.IPNet - var foundIPv4 bool - var foundIPv6 bool - for _, ip := range allIPs { - if utilnet.IsIPv6CIDR(ip) { - if config.IPv6Mode && !foundIPv6 { - // For IPv6 addresses with 128 prefix, let's try to find an appropriate subnet - // in the routing table - subnetIP, err := util.GetIPv6OnSubnet(iface, ip) - if err != nil { - return nil, fmt.Errorf("could not find IPv6 address on subnet: %v", err) - } - ips = append(ips, subnetIP) - foundIPv6 = true - } - } else if config.IPv4Mode && !foundIPv4 { - ips = append(ips, ip) - foundIPv4 = true - } - } - if config.IPv4Mode && !foundIPv4 { - return nil, fmt.Errorf("failed to find IPv4 address on interface %s", iface) - } else if config.IPv6Mode && !foundIPv6 { - return nil, fmt.Errorf("failed to find IPv6 address on interface %s", iface) - } - return ips, nil -} - func getGatewayNextHops() ([]net.IP, string, error) { var gatewayNextHops []net.IP var needIPv4NextHop bool @@ -218,52 +133,6 @@ func getGatewayNextHops() ([]net.IP, string, error) { return gatewayNextHops, gatewayIntf, nil } -// getDPUHostPrimaryIPAddresses returns the DPU host IP/Network based on K8s Node IP -// and DPU IP subnet overriden by config config.Gateway.RouterSubnet -func getDPUHostPrimaryIPAddresses(k8sNodeIP net.IP, ifAddrs []*net.IPNet) ([]*net.IPNet, error) { - // Note(adrianc): No Dual-Stack support at this point as we rely on k8s node IP to derive gateway information - // for each node. - var gwIps []*net.IPNet - isIPv4 := utilnet.IsIPv4(k8sNodeIP) - - // override subnet mask via config - if config.Gateway.RouterSubnet != "" { - _, addr, err := net.ParseCIDR(config.Gateway.RouterSubnet) - if err != nil { - return nil, err - } - if utilnet.IsIPv4CIDR(addr) != isIPv4 { - return nil, fmt.Errorf("unexpected gateway router subnet provided (%s). "+ - "does not match Node IP address format", config.Gateway.RouterSubnet) - } - if !addr.Contains(k8sNodeIP) { - return nil, fmt.Errorf("unexpected gateway router subnet provided (%s). "+ - "subnet does not contain Node IP address (%s)", config.Gateway.RouterSubnet, k8sNodeIP) - } - addr.IP = k8sNodeIP - gwIps = append(gwIps, addr) - } else { - // Assume Host and DPU share the same subnet - // in this case just update the matching IPNet with the Host's IP address - for _, addr := range ifAddrs { - if utilnet.IsIPv4CIDR(addr) != isIPv4 { - continue - } - // expect k8s Node IP to be contained in the given subnet - if !addr.Contains(k8sNodeIP) { - continue - } - newAddr := *addr - newAddr.IP = k8sNodeIP - gwIps = append(gwIps, &newAddr) - } - if len(gwIps) == 0 { - return nil, fmt.Errorf("could not find subnet on DPU matching node IP %s", k8sNodeIP) - } - } - return gwIps, nil -} - // getInterfaceByIP retrieves Interface that has `ip` assigned to it func getInterfaceByIP(ip net.IP) (string, error) { links, err := util.GetNetLinkOps().LinkList() @@ -345,7 +214,7 @@ func (nc *DefaultNodeNetworkController) initGatewayPreStart( egressGWInterface = interfaceForEXGW(config.Gateway.EgressGWInterface) } - ifAddrs, err = getNetworkInterfaceIPAddresses(gatewayIntf) + ifAddrs, err = nodeutil.GetNetworkInterfaceIPAddresses(gatewayIntf) if err != nil { return nil, err } @@ -353,7 +222,7 @@ func (nc *DefaultNodeNetworkController) initGatewayPreStart( // For DPU need to use the host IP addr which currently is assumed to be K8s Node cluster // internal IP address. if config.OvnKubeNode.Mode == types.NodeModeDPU { - ifAddrs, err = getDPUHostPrimaryIPAddresses(kubeNodeIP, ifAddrs) + ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(kubeNodeIP, ifAddrs) if err != nil { return nil, err } @@ -474,7 +343,7 @@ func (nc *DefaultNodeNetworkController) initGatewayMainStart(gw *gateway, waiter // interfaceForEXGW takes the interface requested to act as exgw bridge // and returns the name of the bridge if exists, or the interface itself -// if the bridge needs to be created. In this last scenario, bridgeForInterface +// if the bridge needs to be created. In this last scenario, BridgeForInterface // will create the bridge. func interfaceForEXGW(intfName string) string { if _, _, err := util.RunOVSVsctl("br-exists", intfName); err == nil { @@ -510,7 +379,7 @@ func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP) er return err } - ifAddrs, err := getNetworkInterfaceIPAddresses(gatewayIntf) + ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(gatewayIntf) if err != nil { return err } @@ -605,7 +474,7 @@ func CleanupClusterNode(name string) error { func (nc *DefaultNodeNetworkController) updateGatewayMAC(link netlink.Link) error { // TBD-merge for dpu-host mode: if interface mac of the dpu-host interface that connects to the // gateway bridge on the dpu changes, we need to update dpu's gatewayBridge.macAddress L3 gateway - // annotation (see bridgeForInterface) + // annotation (see BridgeForInterface) if config.OvnKubeNode.Mode != types.NodeModeFull { return nil } diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 8bc38dcbf7..9e1fc9213c 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -564,7 +564,7 @@ func shareGatewayInterfaceDPUTest(app *cli.App, testNS ns.NetNS, // exec Mocks fexec := ovntest.NewLooseCompareFakeExec() // gatewayInitInternal - // bridgeForInterface + // BridgeForInterface fexec.AddFakeCmd(&ovntest.ExpectedCmd{ Cmd: "ovs-vsctl --timeout=15 port-to-br " + brphys, Err: fmt.Errorf(""), @@ -1655,47 +1655,6 @@ var _ = Describe("Gateway unit tests", func() { util.SetNetLinkOpMockInst(origNetlinkInst) }) - Context("getDPUHostPrimaryIPAddresses", func() { - - It("returns Gateway IP/Subnet for kubernetes node IP", func() { - _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") - nodeIP := net.ParseIP("10.0.0.11") - expectedGwSubnet := []*net.IPNet{ - {IP: nodeIP, Mask: net.CIDRMask(24, 32)}, - } - gwSubnet, err := getDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) - Expect(err).ToNot(HaveOccurred()) - Expect(gwSubnet).To(Equal(expectedGwSubnet)) - }) - - It("Fails if node IP is not in host subnets", func() { - _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") - nodeIP := net.ParseIP("10.0.1.11") - _, err := getDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) - Expect(err).To(HaveOccurred()) - }) - - It("returns node IP with config.Gateway.RouterSubnet subnet", func() { - config.Gateway.RouterSubnet = "10.1.0.0/16" - _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") - nodeIP := net.ParseIP("10.1.0.11") - expectedGwSubnet := []*net.IPNet{ - {IP: nodeIP, Mask: net.CIDRMask(16, 32)}, - } - gwSubnet, err := getDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) - Expect(err).ToNot(HaveOccurred()) - Expect(gwSubnet).To(Equal(expectedGwSubnet)) - }) - - It("Fails if node IP is not in config.Gateway.RouterSubnet subnet", func() { - config.Gateway.RouterSubnet = "10.1.0.0/16" - _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") - nodeIP := net.ParseIP("10.0.0.11") - _, err := getDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) - Expect(err).To(HaveOccurred()) - }) - }) - Context("getInterfaceByIP", func() { It("Finds correct interface", func() { lnk := &linkMock.Link{} diff --git a/go-controller/pkg/node/gateway_localnet_linux_test.go b/go-controller/pkg/node/gateway_localnet_linux_test.go index 013234e1b1..87ef3aa72c 100644 --- a/go-controller/pkg/node/gateway_localnet_linux_test.go +++ b/go-controller/pkg/node/gateway_localnet_linux_test.go @@ -21,6 +21,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" @@ -56,8 +57,8 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher gwMACParsed, _ := net.ParseMAC(gwMAC) - defaultNetConfig := &bridgeUDNConfiguration{ - ofPortPatch: "patch-breth0_ov", + defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", } fNPW := nodePortWatcher{ @@ -67,9 +68,9 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher serviceInfo: make(map[k8stypes.NamespacedName]*serviceConfig), ofm: &openflowManager{ flowCache: map[string][]string{}, - defaultBridge: &bridgeConfiguration{ - macAddress: gwMACParsed, - netConfig: map[string]*bridgeUDNConfiguration{ + defaultBridge: &bridgeconfig.BridgeConfiguration{ + MacAddress: gwMACParsed, + NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, }, diff --git a/go-controller/pkg/node/gateway_nftables.go b/go-controller/pkg/node/gateway_nftables.go index 6e341466ab..842bb417d1 100644 --- a/go-controller/pkg/node/gateway_nftables.go +++ b/go-controller/pkg/node/gateway_nftables.go @@ -12,6 +12,7 @@ import ( utilnet "k8s.io/utils/net" "sigs.k8s.io/knftables" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -68,10 +69,10 @@ func getNoSNATLoadBalancerIPRules(svcPort corev1.ServicePort, localEndpoints []s // getUDNNodePortMarkNFTRule returns a verdict map element (nftablesUDNMarkNodePortsMap) // with a key composed of the svcPort protocol and port. // The value is a jump to the UDN chain mark if netInfo is provided, or nil that is useful for map entry removal. -func getUDNNodePortMarkNFTRule(svcPort corev1.ServicePort, netInfo *bridgeUDNConfiguration) *knftables.Element { +func getUDNNodePortMarkNFTRule(svcPort corev1.ServicePort, netInfo *bridgeconfig.BridgeUDNConfiguration) *knftables.Element { var val []string if netInfo != nil { - val = []string{fmt.Sprintf("jump %s", GetUDNMarkChain(netInfo.pktMark))} + val = []string{fmt.Sprintf("jump %s", GetUDNMarkChain(netInfo.PktMark))} } return &knftables.Element{ Map: nftablesUDNMarkNodePortsMap, @@ -84,12 +85,12 @@ func getUDNNodePortMarkNFTRule(svcPort corev1.ServicePort, netInfo *bridgeUDNCon // getUDNExternalIPsMarkNFTRules returns a verdict map elements (nftablesUDNMarkExternalIPsV4Map or nftablesUDNMarkExternalIPsV6Map) // with a key composed of the external IP, svcPort protocol and port. // The value is a jump to the UDN chain mark if netInfo is provided, or nil that is useful for map entry removal. -func getUDNExternalIPsMarkNFTRules(svcPort corev1.ServicePort, externalIPs []string, netInfo *bridgeUDNConfiguration) []*knftables.Element { +func getUDNExternalIPsMarkNFTRules(svcPort corev1.ServicePort, externalIPs []string, netInfo *bridgeconfig.BridgeUDNConfiguration) []*knftables.Element { var nftRules []*knftables.Element var val []string if netInfo != nil { - val = []string{fmt.Sprintf("jump %s", GetUDNMarkChain(netInfo.pktMark))} + val = []string{fmt.Sprintf("jump %s", GetUDNMarkChain(netInfo.PktMark))} } for _, externalIP := range externalIPs { mapName := nftablesUDNMarkExternalIPsV4Map @@ -175,7 +176,7 @@ func getGatewayNFTRules(service *corev1.Service, localEndpoints []string, svcHas // getUDNNFTRules generates nftables rules for a UDN service. // If netConfig is nil, the resulting map elements will have empty values, // suitable only for entry removal. -func getUDNNFTRules(service *corev1.Service, netConfig *bridgeUDNConfiguration) []*knftables.Element { +func getUDNNFTRules(service *corev1.Service, netConfig *bridgeconfig.BridgeUDNConfiguration) []*knftables.Element { rules := make([]*knftables.Element, 0) for _, svcPort := range service.Spec.Ports { if util.ServiceTypeHasNodePort(service) { diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index bc8317c8de..6d467a1285 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -26,12 +26,14 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/egressip" nodeipt "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iptables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/linkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" @@ -50,8 +52,7 @@ const ( pmtudOpenFlowCookie = "0x0304" // ovsLocalPort is the name of the OVS bridge local port ovsLocalPort = "LOCAL" - // ctMarkOVN is the conntrack mark value for OVN traffic - ctMarkOVN = "0x1" + // ctMarkHost is the conntrack mark value for host traffic ctMarkHost = "0x2" // ovnKubeNodeSNATMark is used to mark packets that need to be SNAT-ed to nodeIP for @@ -235,7 +236,7 @@ type cidrAndFlags struct { func (npw *nodePortWatcher) updateGatewayIPs(addressManager *addressManager) { // Get Physical IPs of Node, Can be IPV4 IPV6 or both addressManager.gatewayBridge.Lock() - gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.ips) + gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.Ips) addressManager.gatewayBridge.Unlock() npw.gatewayIPLock.Lock() @@ -265,7 +266,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI return nil } - var netConfig *bridgeUDNConfiguration + var netConfig *bridgeconfig.BridgeUDNConfiguration var actions string if add { @@ -273,7 +274,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI if netConfig == nil { return fmt.Errorf("failed to get active network config for network %s", netInfo.GetNetworkName()) } - actions = fmt.Sprintf("output:%s", netConfig.ofPortPatch) + actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) } // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure @@ -353,7 +354,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // table=0, matches on return traffic from service nodePort and sends it out to primary node interface (br-ex) fmt.Sprintf("cookie=%s, priority=110, in_port=%s, dl_src=%s, %s, tp_src=%d, "+ "actions=output:%s", - cookie, netConfig.ofPortPatch, npw.ofm.getDefaultBridgeMAC(), flowProtocol, svcPort.NodePort, npw.ofportPhys)}) + cookie, netConfig.OfPortPatch, npw.ofm.getDefaultBridgeMAC(), flowProtocol, svcPort.NodePort, npw.ofportPhys)}) } } } @@ -423,14 +424,14 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI ipPrefix = "ipv6" } // table 2, user-defined network host -> OVN towards default cluster network services - defaultNetConfig := npw.ofm.defaultBridge.getActiveNetworkBridgeConfigCopy(types.DefaultNetworkName) + defaultNetConfig := npw.ofm.defaultBridge.GetActiveNetworkBridgeConfigCopy(types.DefaultNetworkName) // sample flow: cookie=0xdeff105, duration=2319.685s, table=2, n_packets=496, n_bytes=67111, priority=300, // ip,nw_dst=10.96.0.1 actions=mod_dl_dst:02:42:ac:12:00:03,output:"patch-breth0_ov" // This flow is used for UDNs and advertised UDNs to be able to reach kapi and dns services alone on default network flows := []string{fmt.Sprintf("cookie=%s, priority=300, table=2, %s, %s_dst=%s, "+ "actions=set_field:%s->eth_dst,output:%s", defaultOpenFlowCookie, ipPrefix, ipPrefix, service.Spec.ClusterIP, - npw.ofm.getDefaultBridgeMAC().String(), defaultNetConfig.ofPortPatch)} + npw.ofm.getDefaultBridgeMAC().String(), defaultNetConfig.OfPortPatch)} if util.IsRouteAdvertisementsEnabled() { // if the network is advertised, then for the reply from kapi and dns services to go back // into the UDN's VRF we need flows that statically send this to the local port @@ -443,7 +444,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // sample flow for non-advertised UDNs: cookie=0xdeff105, duration=684.087s, table=0, n_packets=0, n_bytes=0, // idle_age=684, priority=500,ip,in_port=2,nw_src=10.96.0.0/16,nw_dst=169.254.0.0/17 actions=ct(table=3,zone=64001,nat) flows = append(flows, fmt.Sprintf("cookie=%s, priority=490, in_port=%s, ip, ip_src=%s,actions=ct(zone=%d,nat,table=3)", - defaultOpenFlowCookie, defaultNetConfig.ofPortPatch, service.Spec.ClusterIP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, service.Spec.ClusterIP, config.Default.HostMasqConntrackZone)) } npw.ofm.updateFlowCacheEntry(key, flows) } @@ -470,7 +471,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // `actions`: "send to patchport" // `externalIPOrLBIngressIP` is either externalIP.IP or LB.status.ingress.IP // `ipType` is either "External" or "Ingress" -func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, netConfig *bridgeUDNConfiguration, svcPort *corev1.ServicePort, add bool, +func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, netConfig *bridgeconfig.BridgeUDNConfiguration, svcPort *corev1.ServicePort, add bool, hasLocalHostNetworkEp bool, protocol string, actions string, externalIPOrLBIngressIPs []string, ipType string, ofPorts []string) error { for _, externalIPOrLBIngressIP := range externalIPOrLBIngressIPs { @@ -501,7 +502,7 @@ func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, continue } // add the ARP bypass flow regardless of service type or gateway modes since its applicable in all scenarios. - arpFlow := npw.generateARPBypassFlow(ofPorts, netConfig.ofPortPatch, externalIPOrLBIngressIP, cookie) + arpFlow := npw.generateARPBypassFlow(ofPorts, netConfig.OfPortPatch, externalIPOrLBIngressIP, cookie) externalIPFlows = append(externalIPFlows, arpFlow) // This allows external traffic ingress when the svc's ExternalTrafficPolicy is // set to Local, and the backend pod is HostNetworked. We need to add @@ -538,7 +539,7 @@ func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, etpSvcOpenFlowCookie, npw.ofportPhys)) } else if config.Gateway.Mode == config.GatewayModeShared { // add the ICMP Fragmentation flow for shared gateway mode. - icmpFlow := generateICMPFragmentationFlow(externalIPOrLBIngressIP, netConfig.ofPortPatch, npw.ofportPhys, cookie, 110) + icmpFlow := generateICMPFragmentationFlow(externalIPOrLBIngressIP, netConfig.OfPortPatch, npw.ofportPhys, cookie, 110) externalIPFlows = append(externalIPFlows, icmpFlow) // case2 (see function description for details) externalIPFlows = append(externalIPFlows, @@ -549,7 +550,7 @@ func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, // table=0, matches on return traffic from service externalIP or LB ingress and sends it out to primary node interface (br-ex) fmt.Sprintf("cookie=%s, priority=110, in_port=%s, dl_src=%s, %s, %s=%s, tp_src=%d, "+ "actions=output:%s", - cookie, netConfig.ofPortPatch, npw.ofm.getDefaultBridgeMAC(), flowProtocol, nwSrc, externalIPOrLBIngressIP, svcPort.Port, npw.ofportPhys)) + cookie, netConfig.OfPortPatch, npw.ofm.getDefaultBridgeMAC(), flowProtocol, nwSrc, externalIPOrLBIngressIP, svcPort.Port, npw.ofportPhys)) } npw.ofm.updateFlowCacheEntry(key, externalIPFlows) } @@ -707,7 +708,7 @@ func addServiceRules(service *corev1.Service, netInfo util.NetInfo, localEndpoin // For dpu or Full mode var err error var errors []error - var activeNetwork *bridgeUDNConfiguration + var activeNetwork *bridgeconfig.BridgeUDNConfiguration if npw != nil { if err = npw.updateServiceFlowCache(service, netInfo, true, svcHasLocalHostNetEndPnt); err != nil { errors = append(errors, err) @@ -1452,14 +1453,14 @@ func (npwipt *nodePortWatcherIptables) SyncServices(services []interface{}) erro return utilerrors.Join(errors...) } -func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]string, error) { +func flowsForDefaultBridge(bridge *bridgeconfig.BridgeConfiguration, extraIPs []net.IP) ([]string, error) { // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := bridge.ofPortPhys - bridgeMacAddress := bridge.macAddress.String() - ofPortHost := bridge.ofPortHost - bridgeIPs := bridge.ips + ofPortPhys := bridge.OfPortPhys + bridgeMacAddress := bridge.MacAddress.String() + ofPortHost := bridge.OfPortHost + bridgeIPs := bridge.Ips var dftFlows []string // 14 bytes of overhead for ethernet header (does not include VLAN) @@ -1496,12 +1497,12 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st if err != nil { return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table 0, SVC Hairpin from OVN destined to local host, DNAT and go to table 4 dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", - defaultOpenFlowCookie, netConfig.ofPortPatch, config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String(), physicalIP.IP, + defaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String(), physicalIP.IP, config.Default.HostMasqConntrackZone, physicalIP.IP)) } @@ -1520,11 +1521,11 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st continue } - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", - defaultOpenFlowCookie, netConfig.ofPortPatch, ip.String(), physicalIP.IP, + defaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, config.Default.HostMasqConntrackZone)) } } @@ -1559,11 +1560,11 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } // table 0, SVC Hairpin from OVN destined to local host, DNAT to host, send to table 4 - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", - defaultOpenFlowCookie, netConfig.ofPortPatch, config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String(), physicalIP.IP, + defaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String(), physicalIP.IP, config.Default.HostMasqConntrackZone, physicalIP.IP)) } @@ -1582,11 +1583,11 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st continue } - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", - defaultOpenFlowCookie, netConfig.ofPortPatch, ip.String(), physicalIP.IP, + defaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, config.Default.HostMasqConntrackZone)) } } @@ -1633,13 +1634,13 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st // we match on the UDNPodSubnet itself and we also don't SNAT to 169.254.0.2 // sample flow: cookie=0xdeff105, duration=1472.742s, table=0, n_packets=9, n_bytes=666, priority=550 // ip,in_port=LOCAL,nw_src=103.103.0.0/16,nw_dst=10.96.0.0/16 actions=ct(commit,table=2,zone=64001) - for _, netConfig := range bridge.patchedNetConfigs() { - if netConfig.isDefaultNetwork() { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { continue } - if netConfig.advertised.Load() { + if netConfig.Advertised.Load() { var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.subnets { + for _, clusterEntry := range netConfig.Subnets { udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) } // Filter subnets based on the clusterIP service family @@ -1666,19 +1667,19 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st // In UDN match on the whole masquerade subnet to handle replies from UDN enabled services masqDst = masqSubnet } - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table 0, Reply hairpin traffic to host, coming from OVN, unSNAT dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_src=%s, %s_dst=%s,"+ "actions=ct(zone=%d,nat,table=3)", - defaultOpenFlowCookie, netConfig.ofPortPatch, protoPrefix, protoPrefix, svcCIDR, + defaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR, protoPrefix, masqDst, config.Default.HostMasqConntrackZone)) // table 0, Reply traffic coming from OVN to outside, drop it if the DNAT wasn't done either // at the GR load balancer or switch load balancer. It means the correct port wasn't provided. // nodeCIDR->serviceCIDR traffic flow is internal and it shouldn't be carried to outside the cluster dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=115, in_port=%s, %s, %s_dst=%s,"+ - "actions=drop", defaultOpenFlowCookie, netConfig.ofPortPatch, protoPrefix, protoPrefix, svcCIDR)) + "actions=drop", defaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR)) } } @@ -1689,10 +1690,10 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st dftFlows = append(dftFlows, reassemblyFlows...) } if ofPortPhys != "" { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { var actions string if config.Gateway.Mode != config.GatewayModeLocal || config.Gateway.DisablePacketMTUCheck { - actions = fmt.Sprintf("output:%s", netConfig.ofPortPatch) + actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) } else { // packets larger than known acceptable MTU need to go to kernel for // potential fragmentation @@ -1702,26 +1703,26 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st } if config.IPv4Mode { - // table 1, established and related connections in zone 64000 with ct_mark ctMarkOVN go to OVN + // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.masqCTMark, actions)) + "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.masqCTMark, actions)) + "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) } if config.IPv6Mode { - // table 1, established and related connections in zone 64000 with ct_mark ctMarkOVN go to OVN + // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.masqCTMark, actions)) + "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.masqCTMark, actions)) + "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) } } if config.IPv4Mode { @@ -1757,25 +1758,25 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st defaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) } - defaultNetConfig := bridge.netConfig[types.DefaultNetworkName] + defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] // table 2, dispatch from Host -> OVN dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, table=2, "+ "actions=set_field:%s->eth_dst,%soutput:%s", defaultOpenFlowCookie, - bridgeMacAddress, mod_vlan_id, defaultNetConfig.ofPortPatch)) + bridgeMacAddress, mod_vlan_id, defaultNetConfig.OfPortPatch)) // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. if config.IPv4Mode { - for _, netConfig := range bridge.patchedNetConfigs() { - if netConfig.isDefaultNetwork() { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { continue } - srcIPOrSubnet := netConfig.v4MasqIPs.ManagementPort.IP.String() - if util.IsRouteAdvertisementsEnabled() && netConfig.advertised.Load() { + srcIPOrSubnet := netConfig.V4MasqIPs.ManagementPort.IP.String() + if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.subnets { + for _, clusterEntry := range netConfig.Subnets { udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) } // Filter subnets based on the clusterIP service family @@ -1801,20 +1802,20 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+ "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, netConfig.pktMark, - bridgeMacAddress, netConfig.ofPortPatch)) + defaultOpenFlowCookie, netConfig.PktMark, + bridgeMacAddress, netConfig.OfPortPatch)) } } if config.IPv6Mode { - for _, netConfig := range bridge.patchedNetConfigs() { - if netConfig.isDefaultNetwork() { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { continue } - srcIPOrSubnet := netConfig.v6MasqIPs.ManagementPort.IP.String() - if util.IsRouteAdvertisementsEnabled() && netConfig.advertised.Load() { + srcIPOrSubnet := netConfig.V6MasqIPs.ManagementPort.IP.String() + if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.subnets { + for _, clusterEntry := range netConfig.Subnets { udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) } // Filter subnets based on the clusterIP service family @@ -1835,8 +1836,8 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+ "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, netConfig.pktMark, - bridgeMacAddress, netConfig.ofPortPatch)) + defaultOpenFlowCookie, netConfig.PktMark, + bridgeMacAddress, netConfig.OfPortPatch)) } } @@ -1876,13 +1877,13 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st return dftFlows, nil } -func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]string, error) { +func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeconfig.BridgeConfiguration) ([]string, error) { // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := bridge.ofPortPhys - bridgeMacAddress := bridge.macAddress.String() - ofPortHost := bridge.ofPortHost - bridgeIPs := bridge.ips + ofPortPhys := bridge.OfPortPhys + bridgeMacAddress := bridge.MacAddress.String() + ofPortHost := bridge.OfPortHost + bridgeIPs := bridge.Ips var dftFlows []string @@ -1898,8 +1899,8 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin if ofPortPhys != "" { // table 0, we check to see if this dest mac is the shared mac, if so flood to all ports actions := "" - for _, netConfig := range bridge.patchedNetConfigs() { - actions += "output:" + netConfig.ofPortPatch + "," + for _, netConfig := range bridge.PatchedNetConfigs() { + actions += "output:" + netConfig.OfPortPatch + "," } actions += strip_vlan + "NORMAL" dftFlows = append(dftFlows, @@ -1909,13 +1910,13 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=9, table=0, in_port=%s, actions=drop", - defaultOpenFlowCookie, netConfig.ofPortPatch)) + defaultOpenFlowCookie, netConfig.OfPortPatch)) } if config.IPv4Mode { @@ -1924,7 +1925,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be SNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // SNATs these into egressIP prior to reaching external bridge. @@ -1933,32 +1934,32 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%s "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, - config.Default.ConntrackZone, physicalIP.IP, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, + config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.eipMarkIPs != nil { - if netConfig.masqCTMark != ctMarkOVN { - for mark, eip := range bridge.eipMarkIPs.GetIPv4() { + config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { + for mark, eip := range bridge.EipMarkIPs.GetIPv4() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, mark, - config.Default.ConntrackZone, eip, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, + config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) } } } - // table 0, packets coming from pods headed externally. Commit connections with ct_mark ctMarkOVN + // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN // so that reverse direction goes back to the pods. - if netConfig.isDefaultNetwork() { + if netConfig.IsDefaultNetwork() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, "+ "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone, - netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, + netConfig.MasqCTMark, ofPortPhys)) // Allow (a) OVN->host traffic on the same node // (b) host->host traffic on the same node @@ -1970,8 +1971,8 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, ip_src=%s, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, netConfig.v4MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, - physicalIP.IP, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V4MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, + physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) } } @@ -1983,26 +1984,26 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin defaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, ctMarkHost, mod_vlan_id, ofPortPhys)) } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp, nw_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp, nw_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp, nw_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) // We send BFD traffic coming from OVN to outside directly using a higher priority flow if ofPortPhys != "" { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp, tp_dst=3784, actions=output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) } } } @@ -2023,7 +2024,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be DNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // DNATs these into egressIP prior to reaching external bridge. @@ -2032,31 +2033,31 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%s "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, - config.Default.ConntrackZone, physicalIP.IP, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, + config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.eipMarkIPs != nil { - if netConfig.masqCTMark != ctMarkOVN { - for mark, eip := range bridge.eipMarkIPs.GetIPv6() { + config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { + for mark, eip := range bridge.EipMarkIPs.GetIPv6() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, mark, - config.Default.ConntrackZone, eip, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, + config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) } } } - // table 0, packets coming from pods headed externally. Commit connections with ct_mark ctMarkOVN + // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN // so that reverse direction goes back to the pods. - if netConfig.isDefaultNetwork() { + if netConfig.IsDefaultNetwork() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, "+ "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.MasqCTMark, ofPortPhys)) // Allow (a) OVN->host traffic on the same node // (b) host->host traffic on the same node @@ -2068,8 +2069,8 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, ipv6_src=%s, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, netConfig.v6MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, - physicalIP.IP, netConfig.masqCTMark, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V6MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, + physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) } } @@ -2082,26 +2083,26 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp6, ipv6_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp6, ipv6_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp6, ipv6_src=%s, "+ "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.ofPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) if ofPortPhys != "" { // We send BFD traffic coming from OVN to outside directly using a higher priority flow dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp6, tp_dst=3784, actions=output:%s", - defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, ofPortPhys)) + defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) } } } @@ -2117,7 +2118,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // Due to the fact that ovn-controllers on different nodes apply the changes independently, // there is a chance that the pod traffic will reach the egress node before it configures the SNAT flows. // Drop pod traffic that is not SNATed, excluding local pods(required for ICNIv2) - defaultNetConfig := bridge.netConfig[types.DefaultNetworkName] + defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] if config.OVNKubernetesFeature.EnableEgressIP { for _, clusterEntry := range config.Default.ClusterSubnets { cidr := clusterEntry.CIDR @@ -2125,9 +2126,9 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // table 0, drop packets coming from pods headed externally that were not SNATed. dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=104, in_port=%s, %s, %s_src=%s, actions=drop", - defaultOpenFlowCookie, defaultNetConfig.ofPortPatch, ipv, ipv, cidr)) + defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, ipv, ipv, cidr)) } - for _, subnet := range defaultNetConfig.nodeSubnets { + for _, subnet := range defaultNetConfig.NodeSubnets { ipv := getIPv(subnet) if ofPortPhys != "" { // table 0, commit connections from local pods. @@ -2135,21 +2136,21 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=109, in_port=%s, dl_src=%s, %s, %s_src=%s"+ "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, defaultNetConfig.ofPortPatch, bridgeMacAddress, ipv, ipv, subnet, - config.Default.ConntrackZone, ctMarkOVN, ofPortPhys)) + defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, bridgeMacAddress, ipv, ipv, subnet, + config.Default.ConntrackZone, nodetypes.CtMarkOVN, ofPortPhys)) } } } if ofPortPhys != "" { - for _, netConfig := range bridge.patchedNetConfigs() { - isNetworkAdvertised := netConfig.advertised.Load() + for _, netConfig := range bridge.PatchedNetConfigs() { + isNetworkAdvertised := netConfig.Advertised.Load() // disableSNATMultipleGWs only applies to default network - disableSNATMultipleGWs := netConfig.isDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs + disableSNATMultipleGWs := netConfig.IsDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs if !disableSNATMultipleGWs && !isNetworkAdvertised { continue } - output := netConfig.ofPortPatch + output := netConfig.OfPortPatch if isNetworkAdvertised && config.Gateway.Mode == config.GatewayModeLocal { // except if advertised through BGP, go to kernel // TODO: MEG enabled pods should still go through the patch port @@ -2158,7 +2159,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // are assuming MEG & BGP are not used together output = ovsLocalPort } - for _, clusterEntry := range netConfig.subnets { + for _, clusterEntry := range netConfig.Subnets { cidr := clusterEntry.CIDR ipv := getIPv(cidr) dftFlows = append(dftFlows, @@ -2166,9 +2167,9 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin "actions=output:%s", defaultOpenFlowCookie, ipv, ipv, cidr, output)) } - if output == netConfig.ofPortPatch { + if output == netConfig.OfPortPatch { // except node management traffic - for _, subnet := range netConfig.nodeSubnets { + for _, subnet := range netConfig.NodeSubnets { mgmtIP := util.GetNodeManagementIfAddr(subnet) ipv := getIPv(mgmtIP) dftFlows = append(dftFlows, @@ -2197,7 +2198,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // We send BFD traffic both on the host and in ovn dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp6, tp_dst=3784, actions=output:%s,output:%s", - defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.ofPortPatch, ofPortHost)) + defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) } } @@ -2206,7 +2207,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin // We send BFD traffic both on the host and in ovn dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp, tp_dst=3784, actions=output:%s,output:%s", - defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.ofPortPatch, ofPortHost)) + defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) } } @@ -2220,17 +2221,17 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin "actions=output:%s", defaultOpenFlowCookie, ofPortHost)) // Send UDN destined traffic to right patch port - for _, netConfig := range bridge.patchedNetConfigs() { - if netConfig.masqCTMark != ctMarkOVN { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=5, table=11, ct_mark=%s, "+ - "actions=output:%s", defaultOpenFlowCookie, netConfig.masqCTMark, netConfig.ofPortPatch)) + "actions=output:%s", defaultOpenFlowCookie, netConfig.MasqCTMark, netConfig.OfPortPatch)) } } dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=1, table=11, "+ - "actions=output:%s", defaultOpenFlowCookie, defaultNetConfig.ofPortPatch)) + "actions=output:%s", defaultOpenFlowCookie, defaultNetConfig.OfPortPatch)) } // table 1, all other connections do normal processing @@ -2241,15 +2242,15 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin return dftFlows, nil } -func pmtudDropFlows(bridge *bridgeConfiguration, ipAddrs []string) []string { +func pmtudDropFlows(bridge *bridgeconfig.BridgeConfiguration, ipAddrs []string) []string { var flows []string if config.Gateway.Mode != config.GatewayModeShared { return nil } for _, addr := range ipAddrs { - for _, netConfig := range bridge.patchedNetConfigs() { + for _, netConfig := range bridge.PatchedNetConfigs() { flows = append(flows, - generateICMPFragmentationFlow(addr, outputPortDrop, netConfig.ofPortPatch, pmtudOpenFlowCookie, 700)) + generateICMPFragmentationFlow(addr, outputPortDrop, netConfig.OfPortPatch, pmtudOpenFlowCookie, 700)) } } @@ -2262,7 +2263,7 @@ func pmtudDropFlows(bridge *bridgeConfiguration, ipAddrs []string) []string { // when the localnet is mapped to breth0. // The expected srcMAC is the MAC address of breth0 and the expected hostSubnets is the host subnets found on the node // primary interface. -func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { +func hostNetworkNormalActionFlows(netConfig *bridgeconfig.BridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { var flows []string var ipFamily, ipFamilyDest string @@ -2296,7 +2297,7 @@ func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC stri if utilnet.IsIPv6(hostSubnet.IP) != isV6 { continue } - flows = append(flows, formatFlow(netConfig.ofPortPatch, hostSubnet.String(), netConfig.masqCTMark)) + flows = append(flows, formatFlow(netConfig.OfPortPatch, hostSubnet.String(), netConfig.MasqCTMark)) } } @@ -2329,7 +2330,7 @@ func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC stri // Traffic path (a) for ICMP: OVN-> localnet for shared gw mode if config.Gateway.Mode == config.GatewayModeShared { flows = append(flows, - formatICMPFlow(netConfig.ofPortPatch, netConfig.masqCTMark, icmpType)) + formatICMPFlow(netConfig.OfPortPatch, netConfig.MasqCTMark, icmpType)) } // Traffic path (a) for ICMP: OVN->localnet for local gw mode @@ -2340,48 +2341,48 @@ func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC stri return flows } -func setBridgeOfPorts(bridge *bridgeConfiguration) error { +func setBridgeOfPorts(bridge *bridgeconfig.BridgeConfiguration) error { bridge.Lock() defer bridge.Unlock() // Get ofport of patchPort - for _, netConfig := range bridge.netConfig { - if err := netConfig.setBridgeNetworkOfPortsInternal(); err != nil { - return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.patchPort, err) + for _, netConfig := range bridge.NetConfig { + if err := netConfig.SetBridgeNetworkOfPortsInternal(); err != nil { + return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.PatchPort, err) } } - if bridge.uplinkName != "" { + if bridge.UplinkName != "" { // Get ofport of physical interface - ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", bridge.uplinkName, "ofport") + ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", bridge.UplinkName, "ofport") if err != nil { return fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - bridge.uplinkName, stderr, err) + bridge.UplinkName, stderr, err) } - bridge.ofPortPhys = ofportPhys + bridge.OfPortPhys = ofportPhys } // Get ofport representing the host. That is, host representor port in case of DPUs, ovsLocalPort otherwise. if config.OvnKubeNode.Mode == types.NodeModeDPU { var stderr string - hostRep, err := util.GetDPUHostInterface(bridge.bridgeName) + hostRep, err := util.GetDPUHostInterface(bridge.BridgeName) if err != nil { return err } - bridge.ofPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") + bridge.OfPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") if err != nil { return fmt.Errorf("failed to get ofport of host interface %s, stderr: %q, error: %v", hostRep, stderr, err) } } else { var err error - if bridge.gwIfaceRep != "" { - bridge.ofPortHost, _, err = util.RunOVSVsctl("get", "interface", bridge.gwIfaceRep, "ofport") + if bridge.GwIfaceRep != "" { + bridge.OfPortHost, _, err = util.RunOVSVsctl("get", "interface", bridge.GwIfaceRep, "ofport") if err != nil { - return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", bridge.gwIfaceRep, err) + return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", bridge.GwIfaceRep, err) } } else { - bridge.ofPortHost = ovsLocalPort + bridge.OfPortHost = ovsLocalPort } } @@ -2422,8 +2423,8 @@ func newGateway( if exGwBridge != nil { gw.readyFunc = func() (bool, error) { gwBridge.Lock() - for _, netConfig := range gwBridge.netConfig { - ready, err := gatewayReady(netConfig.patchPort) + for _, netConfig := range gwBridge.NetConfig { + ready, err := gatewayReady(netConfig.PatchPort) if err != nil || !ready { gwBridge.Unlock() return false, err @@ -2431,8 +2432,8 @@ func newGateway( } gwBridge.Unlock() exGwBridge.Lock() - for _, netConfig := range exGwBridge.netConfig { - exGWReady, err := gatewayReady(netConfig.patchPort) + for _, netConfig := range exGwBridge.NetConfig { + exGWReady, err := gatewayReady(netConfig.PatchPort) if err != nil || !exGWReady { exGwBridge.Unlock() return false, err @@ -2444,8 +2445,8 @@ func newGateway( } else { gw.readyFunc = func() (bool, error) { gwBridge.Lock() - for _, netConfig := range gwBridge.netConfig { - ready, err := gatewayReady(netConfig.patchPort) + for _, netConfig := range gwBridge.NetConfig { + ready, err := gatewayReady(netConfig.PatchPort) if err != nil || !ready { gwBridge.Unlock() return false, err @@ -2471,8 +2472,8 @@ func newGateway( } } if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && config.Gateway.Mode != config.GatewayModeDisabled { - gw.bridgeEIPAddrManager = egressip.NewBridgeEIPAddrManager(nodeName, gwBridge.bridgeName, linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) - gwBridge.eipMarkIPs = gw.bridgeEIPAddrManager.GetCache() + gw.bridgeEIPAddrManager = egressip.NewBridgeEIPAddrManager(nodeName, gwBridge.BridgeName, linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) + gwBridge.EipMarkIPs = gw.bridgeEIPAddrManager.GetCache() } gw.nodeIPManager = newAddressManager(nodeName, kube, mgmtPort, watchFactory, gwBridge) @@ -2480,15 +2481,15 @@ func newGateway( // Delete stale masquerade resources if there are any. This is to make sure that there // are no Linux resources with IP from old masquerade subnet when masquerade subnet // gets changed as part of day2 operation. - if err := deleteStaleMasqueradeResources(gwBridge.gwIface, nodeName, watchFactory); err != nil { + if err := deleteStaleMasqueradeResources(gwBridge.GetGatewayIface(), nodeName, watchFactory); err != nil { return fmt.Errorf("failed to remove stale masquerade resources: %w", err) } - if err := setNodeMasqueradeIPOnExtBridge(gwBridge.gwIface); err != nil { - return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", gwBridge.gwIface, err) + if err := setNodeMasqueradeIPOnExtBridge(gwBridge.GetGatewayIface()); err != nil { + return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", gwBridge.GetGatewayIface(), err) } - if err := addMasqueradeRoute(routeManager, gwBridge.gwIface, nodeName, gwIPs, watchFactory); err != nil { + if err := addMasqueradeRoute(routeManager, gwBridge.GetGatewayIface(), nodeName, gwIPs, watchFactory); err != nil { return fmt.Errorf("failed to set the node masquerade route to OVN: %v", err) } @@ -2535,7 +2536,7 @@ func newGateway( gw.openflowManager.requestFlowSync() } - if err := addHostMACBindings(gwBridge.gwIface); err != nil { + if err := addHostMACBindings(gwBridge.GetGatewayIface()); err != nil { return fmt.Errorf("failed to add MAC bindings for service routing: %w", err) } @@ -2547,7 +2548,7 @@ func newGateway( } func newNodePortWatcher( - gwBridge *bridgeConfiguration, + gwBridge *bridgeconfig.BridgeConfiguration, ofm *openflowManager, nodeIPManager *addressManager, watchFactory factory.NodeWatchFactory, @@ -2556,10 +2557,10 @@ func newNodePortWatcher( // Get ofport of physical interface ofportPhys, stderr, err := util.GetOVSOfPort("--if-exists", "get", - "interface", gwBridge.uplinkName, "ofport") + "interface", gwBridge.UplinkName, "ofport") if err != nil { return nil, fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - gwBridge.uplinkName, stderr, err) + gwBridge.UplinkName, stderr, err) } // In the shared gateway mode, the NodePort service is handled by the OpenFlow flows configured @@ -2597,11 +2598,11 @@ func newNodePortWatcher( subnets = append(subnets, config.Kubernetes.ServiceCIDRs...) if config.Gateway.DisableForwarding { if err := initExternalBridgeServiceForwardingRules(subnets); err != nil { - return nil, fmt.Errorf("failed to add accept rules in forwarding table for bridge %s: err %v", gwBridge.gwIface, err) + return nil, fmt.Errorf("failed to add accept rules in forwarding table for bridge %s: err %v", gwBridge.GetGatewayIface(), err) } } else { if err := delExternalBridgeServiceForwardingRules(subnets); err != nil { - return nil, fmt.Errorf("failed to delete accept rules in forwarding table for bridge %s: err %v", gwBridge.gwIface, err) + return nil, fmt.Errorf("failed to delete accept rules in forwarding table for bridge %s: err %v", gwBridge.GetGatewayIface(), err) } } @@ -2612,14 +2613,14 @@ func newNodePortWatcher( } // Get Physical IPs of Node, Can be IPV4 IPV6 or both - gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(gwBridge.ips) + gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(gwBridge.Ips) npw := &nodePortWatcher{ dpuMode: dpuMode, gatewayIPv4: gatewayIPv4, gatewayIPv6: gatewayIPv6, ofportPhys: ofportPhys, - gwBridge: gwBridge.bridgeName, + gwBridge: gwBridge.GetGatewayIface(), serviceInfo: make(map[ktypes.NamespacedName]*serviceConfig), nodeIPManager: nodeIPManager, ofm: ofm, diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index d991fc74eb..04a5493f79 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -6,7 +6,6 @@ import ( "net" "slices" "strings" - "sync/atomic" "time" "github.com/vishvananda/netlink" @@ -24,6 +23,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iprulemanager" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/vrfmanager" @@ -92,147 +92,19 @@ type UserDefinedNetworkGateway struct { gwInterfaceIndex int } -// UTILS Needed for UDN (also leveraged for default netInfo) in bridgeConfiguration +// UTILS Needed for UDN (also leveraged for default netInfo) in BridgeConfiguration -// getBridgePortConfigurations returns a slice of Network port configurations along with the -// uplinkName and physical port's ofport value -func (b *bridgeConfiguration) getBridgePortConfigurations() ([]*bridgeUDNConfiguration, string, string) { - b.Lock() - defer b.Unlock() - var netConfigs []*bridgeUDNConfiguration - for _, netConfig := range b.netConfig { - netConfigs = append(netConfigs, netConfig.shallowCopy()) - } - return netConfigs, b.uplinkName, b.ofPortPhys -} - -// addNetworkBridgeConfig adds the patchport and ctMark value for the provided netInfo into the bridge configuration cache -func (b *bridgeConfiguration) addNetworkBridgeConfig( - nInfo util.NetInfo, - nodeSubnets []*net.IPNet, - masqCTMark, pktMark uint, - v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { - b.Lock() - defer b.Unlock() - - netName := nInfo.GetNetworkName() - patchPort := nInfo.GetNetworkScopedPatchPortName(b.bridgeName, b.nodeName) - - _, found := b.netConfig[netName] - if !found { - netConfig := &bridgeUDNConfiguration{ - patchPort: patchPort, - masqCTMark: fmt.Sprintf("0x%x", masqCTMark), - pktMark: fmt.Sprintf("0x%x", pktMark), - v4MasqIPs: v4MasqIPs, - v6MasqIPs: v6MasqIPs, - subnets: nInfo.Subnets(), - nodeSubnets: nodeSubnets, - } - netConfig.advertised.Store(util.IsPodNetworkAdvertisedAtNode(nInfo, b.nodeName)) - - b.netConfig[netName] = netConfig - } else { - klog.Warningf("Trying to update bridge config for network %s which already"+ - "exists in cache...networks are not mutable...ignoring update", nInfo.GetNetworkName()) - } - return nil -} - -// delNetworkBridgeConfig deletes the provided netInfo from the bridge configuration cache -func (b *bridgeConfiguration) delNetworkBridgeConfig(nInfo util.NetInfo) { - b.Lock() - defer b.Unlock() - - delete(b.netConfig, nInfo.GetNetworkName()) -} - -func (b *bridgeConfiguration) getNetworkBridgeConfig(networkName string) *bridgeUDNConfiguration { - b.Lock() - defer b.Unlock() - return b.netConfig[networkName] -} - -// getActiveNetworkBridgeConfigCopy returns a shallow copy of the network configuration corresponding to the -// provided netInfo. -// -// NOTE: if the network configuration can't be found or if the network is not patched by OVN -// yet this returns nil. -func (b *bridgeConfiguration) getActiveNetworkBridgeConfigCopy(networkName string) *bridgeUDNConfiguration { - b.Lock() - defer b.Unlock() - - if netConfig, found := b.netConfig[networkName]; found && netConfig.ofPortPatch != "" { - return netConfig.shallowCopy() - } - return nil -} - -func (b *bridgeConfiguration) patchedNetConfigs() []*bridgeUDNConfiguration { - result := make([]*bridgeUDNConfiguration, 0, len(b.netConfig)) - for _, netConfig := range b.netConfig { - if netConfig.ofPortPatch == "" { - continue - } - result = append(result, netConfig) - } - return result -} - -// END UDN UTILs for bridgeConfiguration - -// bridgeUDNConfiguration holds the patchport and ctMark -// information for a given network -type bridgeUDNConfiguration struct { - patchPort string - ofPortPatch string - masqCTMark string - pktMark string - v4MasqIPs *udn.MasqueradeIPs - v6MasqIPs *udn.MasqueradeIPs - subnets []config.CIDRNetworkEntry - nodeSubnets []*net.IPNet - advertised atomic.Bool -} - -func (netConfig *bridgeUDNConfiguration) shallowCopy() *bridgeUDNConfiguration { - copy := &bridgeUDNConfiguration{ - patchPort: netConfig.patchPort, - ofPortPatch: netConfig.ofPortPatch, - masqCTMark: netConfig.masqCTMark, - pktMark: netConfig.pktMark, - v4MasqIPs: netConfig.v4MasqIPs, - v6MasqIPs: netConfig.v6MasqIPs, - subnets: netConfig.subnets, - nodeSubnets: netConfig.nodeSubnets, - } - netConfig.advertised.Store(netConfig.advertised.Load()) - return copy -} - -func (netConfig *bridgeUDNConfiguration) isDefaultNetwork() bool { - return netConfig.masqCTMark == ctMarkOVN -} - -func (netConfig *bridgeUDNConfiguration) setBridgeNetworkOfPortsInternal() error { - ofportPatch, stderr, err := util.GetOVSOfPort("get", "Interface", netConfig.patchPort, "ofport") - if err != nil { - return fmt.Errorf("failed while waiting on patch port %q to be created by ovn-controller and "+ - "while getting ofport. stderr: %v, error: %v", netConfig.patchPort, stderr, err) - } - netConfig.ofPortPatch = ofportPatch - return nil -} +// END UDN UTILs for BridgeConfiguration -func setBridgeNetworkOfPorts(bridge *bridgeConfiguration, netName string) error { +func setBridgeNetworkOfPorts(bridge *bridgeconfig.BridgeConfiguration, netName string) error { bridge.Lock() defer bridge.Unlock() - netConfig, found := bridge.netConfig[netName] + netConfig, found := bridge.NetConfig[netName] if !found { - return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, bridge.bridgeName) + return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, bridge.BridgeName) } - return netConfig.setBridgeNetworkOfPortsInternal() + return netConfig.SetBridgeNetworkOfPortsInternal() } func NewUserDefinedNetworkGateway(netInfo util.NetInfo, node *corev1.Node, nodeLister listers.NodeLister, @@ -268,7 +140,7 @@ func NewUserDefinedNetworkGateway(netInfo util.NetInfo, node *corev1.Node, nodeL if gw.openflowManager == nil { return nil, fmt.Errorf("openflow manager has not been provided for network: %s", netInfo.GetNetworkName()) } - intfName := gw.openflowManager.defaultBridge.gwIface + intfName := gw.openflowManager.defaultBridge.GetGatewayIface() link, err := util.GetNetLinkOps().LinkByName(intfName) if err != nil { return nil, fmt.Errorf("unable to get link for %s, error: %v", intfName, err) @@ -743,7 +615,7 @@ func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) var retVal []netlink.Route var defaultAnyCIDR *net.IPNet - for _, nextHop := range udng.gateway.openflowManager.defaultBridge.nextHops { + for _, nextHop := range udng.gateway.openflowManager.defaultBridge.NextHops { isV6 := utilnet.IsIPv6(nextHop) _, defaultAnyCIDR, _ = net.ParseCIDR("0.0.0.0/0") if isV6 { @@ -935,11 +807,11 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { // update bridge configuration isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) - netConfig := udng.openflowManager.defaultBridge.getNetworkBridgeConfig(udng.GetNetworkName()) + netConfig := udng.openflowManager.defaultBridge.GetNetworkBridgeConfig(udng.GetNetworkName()) if netConfig == nil { return fmt.Errorf("missing bridge configuration for network %s", udng.GetNetworkName()) } - netConfig.advertised.Store(isNetworkAdvertised) + netConfig.Advertised.Store(isNetworkAdvertised) if err := udng.updateUDNVRFIPRules(isNetworkAdvertised); err != nil { return fmt.Errorf("error while updating ip rule for UDN %s: %s", udng.GetNetworkName(), err) diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 575d8bc9c8..d26cf16910 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -32,6 +32,7 @@ import ( factoryMocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory/mocks" kubemocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube/mocks" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/networkmanager" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iprulemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" @@ -237,12 +238,12 @@ func openflowManagerCheckPorts(ofMgr *openflowManager) { GinkgoHelper() netConfigs, uplink, ofPortPhys := ofMgr.getDefaultBridgePortConfigurations() sort.SliceStable(netConfigs, func(i, j int) bool { - return netConfigs[i].patchPort < netConfigs[j].patchPort + return netConfigs[i].PatchPort < netConfigs[j].PatchPort }) Expect(checkPorts(netConfigs, uplink, ofPortPhys)).To(Succeed()) } -func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeUDNConfiguration, ofPortHost, bridgeMAC string, svcCIDR *net.IPNet) { +func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeconfig.BridgeUDNConfiguration, ofPortHost, bridgeMAC string, svcCIDR *net.IPNet) { By(fmt.Sprintf("Checking default service isolation flows for %s", svcCIDR.String())) var masqIP string @@ -270,7 +271,7 @@ func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeUDNCo ofPortHost, protoPrefix, protoPrefix, masqSubnet, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) { nTable0UDNMasqFlows++ } else if strings.Contains(flow, fmt.Sprintf("priority=100, table=2, actions=set_field:%s->eth_dst,output:%s", - bridgeMAC, defaultConfig.ofPortPatch)) { + bridgeMAC, defaultConfig.OfPortPatch)) { nTable2Flows++ } } @@ -280,7 +281,7 @@ func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeUDNCo Expect(nTable2Flows).To(Equal(1)) } -func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { +func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeconfig.BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { By(fmt.Sprintf("Checking advertsised UDN %s service isolation flows for %s; expected %d flows", netName, svcCIDR.String(), expectedNFlows)) @@ -288,7 +289,7 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN var protoPrefix string var udnAdvertisedSubnets []*net.IPNet var err error - for _, clusterEntry := range netConfig.subnets { + for _, clusterEntry := range netConfig.Subnets { udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) } if utilnet.IsIPv4CIDR(svcCIDR) { @@ -316,17 +317,17 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN Expect(nFlows).To(Equal(expectedNFlows)) } -func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { +func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeconfig.BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows", netName, svcCIDR.String(), expectedNFlows)) var mgmtMasqIP string var protoPrefix string if utilnet.IsIPv4CIDR(svcCIDR) { - mgmtMasqIP = netConfig.v4MasqIPs.ManagementPort.IP.String() + mgmtMasqIP = netConfig.V4MasqIPs.ManagementPort.IP.String() protoPrefix = "ip" } else { - mgmtMasqIP = netConfig.v6MasqIPs.ManagementPort.IP.String() + mgmtMasqIP = netConfig.V6MasqIPs.ManagementPort.IP.String() protoPrefix = "ip6" } @@ -342,9 +343,9 @@ func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfigurat } func getDummyOpenflowManager() *openflowManager { - gwBridge := &bridgeConfiguration{ - gwIface: "breth0", - bridgeName: "breth0", + gwBridge := &bridgeconfig.BridgeConfiguration{ + GwIface: "breth0", + BridgeName: "breth0", } ofm := &openflowManager{ defaultBridge: gwBridge, @@ -769,22 +770,22 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.macAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.ofPortHost + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] + bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() + ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { // UDN Flow udnFlows++ - } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.ofPortPatch)) { + } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.OfPortPatch)) { udnFlows++ } } @@ -810,7 +811,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // default network only + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -1000,22 +1001,22 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.macAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.ofPortHost + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] + bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() + ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { // UDN Flow udnFlows++ - } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.ofPortPatch)) { + } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.OfPortPatch)) { udnFlows++ } } @@ -1041,7 +1042,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // default network only + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -1241,22 +1242,22 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(69)) // 18 UDN Flows and 5 advertisedUDN flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.netConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.macAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.ofPortHost + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] + bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() + ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { // UDN Flow udnFlows++ - } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.ofPortPatch)) { + } else if strings.Contains(flow, fmt.Sprintf("in_port=%s", bridgeUdnConfig.OfPortPatch)) { udnFlows++ } } @@ -1282,7 +1283,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.netConfig).To(HaveLen(1)) // default network only + Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -1482,7 +1483,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() ofm := getDummyOpenflowManager() - ofm.defaultBridge.nextHops = ovntest.MustParseIPs(config.Gateway.NextHop) + ofm.defaultBridge.NextHops = ovntest.MustParseIPs(config.Gateway.NextHop) udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, vrf, nil, &gateway{openflowManager: ofm}) Expect(err).NotTo(HaveOccurred()) mplink, err := netlink.LinkByName(mgtPort) diff --git a/go-controller/pkg/node/helper_linux.go b/go-controller/pkg/node/helper_linux.go index 5e55173a4a..8b46f05315 100644 --- a/go-controller/pkg/node/helper_linux.go +++ b/go-controller/pkg/node/helper_linux.go @@ -153,23 +153,6 @@ func getDefaultGatewayInterfaceByFamily(family int, gwIface string) (string, net return "", net.IP{}, nil } -func getIntfName(gatewayIntf string) (string, error) { - // The given (or autodetected) interface is an OVS bridge and this could be - // created by us using util.NicToBridge() or it was pre-created by the user. - - // Is intfName a port of gatewayIntf? - intfName, err := util.GetNicName(gatewayIntf) - if err != nil { - return "", err - } - _, stderr, err := util.RunOVSVsctl("get", "interface", intfName, "ofport") - if err != nil { - return "", fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - intfName, stderr, err) - } - return intfName, nil -} - // filterRoutesByIfIndex is a helper function that will sieve the provided routes and check // if they match the provided index. This used to be implemented with netlink.RT_FILTER_OIF, // however the problem is that this filtered out MultiPath IPv6 routes which have a LinkIndex of 0. diff --git a/go-controller/pkg/node/node_ip_handler_linux.go b/go-controller/pkg/node/node_ip_handler_linux.go index a0c5ab21e8..a6945531e4 100644 --- a/go-controller/pkg/node/node_ip_handler_linux.go +++ b/go-controller/pkg/node/node_ip_handler_linux.go @@ -20,6 +20,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" @@ -37,21 +38,21 @@ type addressManager struct { syncPeriod time.Duration // compare node primary IP change nodePrimaryAddr net.IP - gatewayBridge *bridgeConfiguration + gatewayBridge *bridgeconfig.BridgeConfiguration OnChanged func() sync.Mutex } // initializes a new address manager which will hold all the IPs on a node -func newAddressManager(nodeName string, k kube.Interface, mgmtPort managementport.Interface, watchFactory factory.NodeWatchFactory, gwBridge *bridgeConfiguration) *addressManager { +func newAddressManager(nodeName string, k kube.Interface, mgmtPort managementport.Interface, watchFactory factory.NodeWatchFactory, gwBridge *bridgeconfig.BridgeConfiguration) *addressManager { return newAddressManagerInternal(nodeName, k, mgmtPort, watchFactory, gwBridge, true) } // newAddressManagerInternal creates a new address manager; this function is // only expose for testcases to disable netlink subscription to ensure // reproducibility of unit tests. -func newAddressManagerInternal(nodeName string, k kube.Interface, mgmtPort managementport.Interface, watchFactory factory.NodeWatchFactory, gwBridge *bridgeConfiguration, useNetlink bool) *addressManager { +func newAddressManagerInternal(nodeName string, k kube.Interface, mgmtPort managementport.Interface, watchFactory factory.NodeWatchFactory, gwBridge *bridgeconfig.BridgeConfiguration, useNetlink bool) *addressManager { mgr := &addressManager{ nodeName: nodeName, watchFactory: watchFactory, @@ -74,7 +75,7 @@ func newAddressManagerInternal(nodeName string, k kube.Interface, mgmtPort manag } if useNetlink { // get updated interface IP addresses for the gateway bridge - ifAddrs, err = gwBridge.updateInterfaceIPAddresses(node) + ifAddrs, err = gwBridge.UpdateInterfaceIPAddresses(node) if err != nil { klog.Errorf("Failed to obtain interface IP addresses for node %s: %v", nodeName, err) return nil @@ -278,7 +279,7 @@ func (c *addressManager) updateNodeAddressAnnotations() error { if c.useNetlink { // get updated interface IP addresses for the gateway bridge - ifAddrs, err = c.gatewayBridge.updateInterfaceIPAddresses(node) + ifAddrs, err = c.gatewayBridge.UpdateInterfaceIPAddresses(node) if err != nil { return err } @@ -437,7 +438,7 @@ func (c *addressManager) isValidNodeIP(addr net.IP, linkIndex int) bool { if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && config.Gateway.Mode != config.GatewayModeDisabled { // Two methods to lookup EIPs assigned to the gateway bridge. Fast path from a shared cache or slow path from node annotations. // At startup, gateway bridge cache gets sync - if c.gatewayBridge.eipMarkIPs != nil && c.gatewayBridge.eipMarkIPs.HasSyncdOnce() && c.gatewayBridge.eipMarkIPs.IsIPPresent(addr) { + if c.gatewayBridge.EipMarkIPs != nil && c.gatewayBridge.EipMarkIPs.HasSyncdOnce() && c.gatewayBridge.EipMarkIPs.IsIPPresent(addr) { return false } else { if eipAddresses, err := c.getPrimaryHostEgressIPs(); err != nil { diff --git a/go-controller/pkg/node/node_ip_handler_linux_test.go b/go-controller/pkg/node/node_ip_handler_linux_test.go index ee10bbfc41..35264a1288 100644 --- a/go-controller/pkg/node/node_ip_handler_linux_test.go +++ b/go-controller/pkg/node/node_ip_handler_linux_test.go @@ -21,6 +21,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" nodemocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/mocks/github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node" @@ -401,7 +402,7 @@ func configureKubeOVNContext(nodeName string, useNetlink bool) *testCtx { mpmock := &nodemocks.ManagementPort{} mpmock.On("GetAddresses").Return([]*net.IPNet{tc.mgmtPortIP4, tc.mgmtPortIP6}) - fakeBridgeConfiguration := &bridgeConfiguration{bridgeName: "breth0", gwIface: "breth0"} + fakeBridgeConfiguration := &bridgeconfig.BridgeConfiguration{BridgeName: "breth0", GwIface: "breth0"} k := &kube.Kube{KClient: tc.fakeClient} tc.ipManager = newAddressManagerInternal(nodeName, k, mpmock, tc.watchFactory, fakeBridgeConfiguration, useNetlink) diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 96b55a52e1..3eaa8a298f 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -13,13 +13,14 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) type openflowManager struct { - defaultBridge *bridgeConfiguration - externalGatewayBridge *bridgeConfiguration + defaultBridge *bridgeconfig.BridgeConfiguration + externalGatewayBridge *bridgeconfig.BridgeConfiguration // flow cache, use map instead of array for readability when debugging flowCache map[string][]string flowMutex sync.Mutex @@ -31,20 +32,20 @@ type openflowManager struct { // UTILs Needed for UDN (also leveraged for default netInfo) in openflowmanager -func (c *openflowManager) getDefaultBridgePortConfigurations() ([]*bridgeUDNConfiguration, string, string) { - return c.defaultBridge.getBridgePortConfigurations() +func (c *openflowManager) getDefaultBridgePortConfigurations() ([]*bridgeconfig.BridgeUDNConfiguration, string, string) { + return c.defaultBridge.GetBridgePortConfigurations() } -func (c *openflowManager) getExGwBridgePortConfigurations() ([]*bridgeUDNConfiguration, string, string) { - return c.externalGatewayBridge.getBridgePortConfigurations() +func (c *openflowManager) getExGwBridgePortConfigurations() ([]*bridgeconfig.BridgeUDNConfiguration, string, string) { + return c.externalGatewayBridge.GetBridgePortConfigurations() } func (c *openflowManager) addNetwork(nInfo util.NetInfo, nodeSubnets []*net.IPNet, masqCTMark, pktMark uint, v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { - if err := c.defaultBridge.addNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { + if err := c.defaultBridge.AddNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { return err } if c.externalGatewayBridge != nil { - if err := c.externalGatewayBridge.addNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { + if err := c.externalGatewayBridge.AddNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { return err } } @@ -52,14 +53,14 @@ func (c *openflowManager) addNetwork(nInfo util.NetInfo, nodeSubnets []*net.IPNe } func (c *openflowManager) delNetwork(nInfo util.NetInfo) { - c.defaultBridge.delNetworkBridgeConfig(nInfo) + c.defaultBridge.DelNetworkBridgeConfig(nInfo) if c.externalGatewayBridge != nil { - c.externalGatewayBridge.delNetworkBridgeConfig(nInfo) + c.externalGatewayBridge.DelNetworkBridgeConfig(nInfo) } } -func (c *openflowManager) getActiveNetwork(nInfo util.NetInfo) *bridgeUDNConfiguration { - return c.defaultBridge.getActiveNetworkBridgeConfigCopy(nInfo.GetNetworkName()) +func (c *openflowManager) getActiveNetwork(nInfo util.NetInfo) *bridgeconfig.BridgeUDNConfiguration { + return c.defaultBridge.GetActiveNetworkBridgeConfigCopy(nInfo.GetNetworkName()) } // END UDN UTILs @@ -67,19 +68,19 @@ func (c *openflowManager) getActiveNetwork(nInfo util.NetInfo) *bridgeUDNConfigu func (c *openflowManager) getDefaultBridgeName() string { c.defaultBridge.Lock() defer c.defaultBridge.Unlock() - return c.defaultBridge.bridgeName + return c.defaultBridge.BridgeName } func (c *openflowManager) getDefaultBridgeMAC() net.HardwareAddr { c.defaultBridge.Lock() defer c.defaultBridge.Unlock() - return c.defaultBridge.macAddress + return c.defaultBridge.MacAddress } func (c *openflowManager) setDefaultBridgeMAC(macAddr net.HardwareAddr) { c.defaultBridge.Lock() defer c.defaultBridge.Unlock() - c.defaultBridge.macAddress = macAddr + c.defaultBridge.MacAddress = macAddr } func (c *openflowManager) updateFlowCacheEntry(key string, flows []string) { @@ -128,7 +129,7 @@ func (c *openflowManager) syncFlows() { flows = append(flows, entry...) } - _, stderr, err := util.ReplaceOFFlows(c.defaultBridge.bridgeName, flows) + _, stderr, err := util.ReplaceOFFlows(c.defaultBridge.BridgeName, flows) if err != nil { klog.Errorf("Failed to add flows, error: %v, stderr, %s, flows: %s", err, stderr, c.flowCache) } @@ -145,7 +146,7 @@ func (c *openflowManager) syncFlows() { flows = append(flows, entry...) } - _, stderr, err := util.ReplaceOFFlows(c.externalGatewayBridge.bridgeName, flows) + _, stderr, err := util.ReplaceOFFlows(c.externalGatewayBridge.BridgeName, flows) if err != nil { klog.Errorf("Failed to add flows, error: %v, stderr, %s, flows: %s", err, stderr, c.exGWFlowCache) } @@ -160,7 +161,7 @@ func (c *openflowManager) syncFlows() { // // -- to handle host -> service access, via masquerading from the host to OVN GR // -- to handle external -> service(ExternalTrafficPolicy: Local) -> host access without SNAT -func newGatewayOpenFlowManager(gwBridge, exGWBridge *bridgeConfiguration) (*openflowManager, error) { +func newGatewayOpenFlowManager(gwBridge, exGWBridge *bridgeconfig.BridgeConfiguration) (*openflowManager, error) { // add health check function to check default OpenFlow flows are on the shared gateway bridge ofm := &openflowManager{ defaultBridge: gwBridge, @@ -262,25 +263,25 @@ func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets [] return nil } -func checkPorts(netConfigs []*bridgeUDNConfiguration, physIntf, ofPortPhys string) error { +func checkPorts(netConfigs []*bridgeconfig.BridgeUDNConfiguration, physIntf, ofPortPhys string) error { // it could be that the ovn-controller recreated the patch between the host OVS bridge and // the integration bridge, as a result the ofport number changed for that patch interface for _, netConfig := range netConfigs { - if netConfig.ofPortPatch == "" { + if netConfig.OfPortPatch == "" { continue } - curOfportPatch, stderr, err := util.GetOVSOfPort("--if-exists", "get", "Interface", netConfig.patchPort, "ofport") + curOfportPatch, stderr, err := util.GetOVSOfPort("--if-exists", "get", "Interface", netConfig.PatchPort, "ofport") if err != nil { - return fmt.Errorf("failed to get ofport of %s, stderr: %q: %w", netConfig.patchPort, stderr, err) + return fmt.Errorf("failed to get ofport of %s, stderr: %q: %w", netConfig.PatchPort, stderr, err) } - if netConfig.ofPortPatch != curOfportPatch { - if netConfig.isDefaultNetwork() { + if netConfig.OfPortPatch != curOfportPatch { + if netConfig.IsDefaultNetwork() { klog.Errorf("Fatal error: patch port %s ofport changed from %s to %s", - netConfig.patchPort, netConfig.ofPortPatch, curOfportPatch) + netConfig.PatchPort, netConfig.OfPortPatch, curOfportPatch) os.Exit(1) } else { - klog.Warningf("UDN patch port %s changed for existing network from %v to %v. Expecting bridge config update.", netConfig.patchPort, netConfig.ofPortPatch, curOfportPatch) + klog.Warningf("UDN patch port %s changed for existing network from %v to %v. Expecting bridge config update.", netConfig.PatchPort, netConfig.OfPortPatch, curOfportPatch) } } } diff --git a/go-controller/pkg/node/types/const.go b/go-controller/pkg/node/types/const.go new file mode 100644 index 0000000000..64f4f15cf6 --- /dev/null +++ b/go-controller/pkg/node/types/const.go @@ -0,0 +1,6 @@ +package types + +const ( + // CtMarkOVN is the conntrack mark value for OVN traffic + CtMarkOVN = "0x1" +) diff --git a/go-controller/pkg/node/util/util.go b/go-controller/pkg/node/util/util.go new file mode 100644 index 0000000000..9ad21a9a8e --- /dev/null +++ b/go-controller/pkg/node/util/util.go @@ -0,0 +1,92 @@ +package util + +import ( + "fmt" + "net" + + net2 "k8s.io/utils/net" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + pkgutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" +) + +// GetNetworkInterfaceIPAddresses returns the IP addresses for the network interface 'iface'. +func GetNetworkInterfaceIPAddresses(iface string) ([]*net.IPNet, error) { + allIPs, err := pkgutil.GetFilteredInterfaceV4V6IPs(iface) + if err != nil { + return nil, fmt.Errorf("could not find IP addresses: %v", err) + } + + var ips []*net.IPNet + var foundIPv4 bool + var foundIPv6 bool + for _, ip := range allIPs { + if net2.IsIPv6CIDR(ip) { + if config.IPv6Mode && !foundIPv6 { + // For IPv6 addresses with 128 prefix, let's try to find an appropriate subnet + // in the routing table + subnetIP, err := pkgutil.GetIPv6OnSubnet(iface, ip) + if err != nil { + return nil, fmt.Errorf("could not find IPv6 address on subnet: %v", err) + } + ips = append(ips, subnetIP) + foundIPv6 = true + } + } else if config.IPv4Mode && !foundIPv4 { + ips = append(ips, ip) + foundIPv4 = true + } + } + if config.IPv4Mode && !foundIPv4 { + return nil, fmt.Errorf("failed to find IPv4 address on interface %s", iface) + } else if config.IPv6Mode && !foundIPv6 { + return nil, fmt.Errorf("failed to find IPv6 address on interface %s", iface) + } + return ips, nil +} + +// GetDPUHostPrimaryIPAddresses returns the DPU host IP/Network based on K8s Node IP +// and DPU IP subnet overriden by config config.Gateway.RouterSubnet +func GetDPUHostPrimaryIPAddresses(k8sNodeIP net.IP, ifAddrs []*net.IPNet) ([]*net.IPNet, error) { + // Note(adrianc): No Dual-Stack support at this point as we rely on k8s node IP to derive gateway information + // for each node. + var gwIps []*net.IPNet + isIPv4 := net2.IsIPv4(k8sNodeIP) + + // override subnet mask via config + if config.Gateway.RouterSubnet != "" { + _, addr, err := net.ParseCIDR(config.Gateway.RouterSubnet) + if err != nil { + return nil, err + } + if net2.IsIPv4CIDR(addr) != isIPv4 { + return nil, fmt.Errorf("unexpected gateway router subnet provided (%s). "+ + "does not match Node IP address format", config.Gateway.RouterSubnet) + } + if !addr.Contains(k8sNodeIP) { + return nil, fmt.Errorf("unexpected gateway router subnet provided (%s). "+ + "subnet does not contain Node IP address (%s)", config.Gateway.RouterSubnet, k8sNodeIP) + } + addr.IP = k8sNodeIP + gwIps = append(gwIps, addr) + } else { + // Assume Host and DPU share the same subnet + // in this case just update the matching IPNet with the Host's IP address + for _, addr := range ifAddrs { + if net2.IsIPv4CIDR(addr) != isIPv4 { + continue + } + // expect k8s Node IP to be contained in the given subnet + if !addr.Contains(k8sNodeIP) { + continue + } + newAddr := *addr + newAddr.IP = k8sNodeIP + gwIps = append(gwIps, &newAddr) + } + if len(gwIps) == 0 { + return nil, fmt.Errorf("could not find subnet on DPU matching node IP %s", k8sNodeIP) + } + } + return gwIps, nil +} diff --git a/go-controller/pkg/node/util/util_suite_test.go b/go-controller/pkg/node/util/util_suite_test.go new file mode 100644 index 0000000000..dc2d625792 --- /dev/null +++ b/go-controller/pkg/node/util/util_suite_test.go @@ -0,0 +1,13 @@ +package util + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestNodeSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Node util Suite") +} diff --git a/go-controller/pkg/node/util/util_test.go b/go-controller/pkg/node/util/util_test.go new file mode 100644 index 0000000000..5ca6cc80a3 --- /dev/null +++ b/go-controller/pkg/node/util/util_test.go @@ -0,0 +1,57 @@ +package util + +import ( + "net" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("node util tests", func() { + BeforeEach(func() { + Expect(config.PrepareTestConfig()).To(Succeed()) + }) + + Context("GetDPUHostPrimaryIPAddresses", func() { + + It("returns Gateway IP/Subnet for kubernetes node IP", func() { + _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") + nodeIP := net.ParseIP("10.0.0.11") + expectedGwSubnet := []*net.IPNet{ + {IP: nodeIP, Mask: net.CIDRMask(24, 32)}, + } + gwSubnet, err := GetDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) + Expect(err).ToNot(HaveOccurred()) + Expect(gwSubnet).To(Equal(expectedGwSubnet)) + }) + + It("Fails if node IP is not in host subnets", func() { + _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") + nodeIP := net.ParseIP("10.0.1.11") + _, err := GetDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) + Expect(err).To(HaveOccurred()) + }) + + It("returns node IP with config.Gateway.RouterSubnet subnet", func() { + config.Gateway.RouterSubnet = "10.1.0.0/16" + _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") + nodeIP := net.ParseIP("10.1.0.11") + expectedGwSubnet := []*net.IPNet{ + {IP: nodeIP, Mask: net.CIDRMask(16, 32)}, + } + gwSubnet, err := GetDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) + Expect(err).ToNot(HaveOccurred()) + Expect(gwSubnet).To(Equal(expectedGwSubnet)) + }) + + It("Fails if node IP is not in config.Gateway.RouterSubnet subnet", func() { + config.Gateway.RouterSubnet = "10.1.0.0/16" + _, dpuSubnet, _ := net.ParseCIDR("10.0.0.101/24") + nodeIP := net.ParseIP("10.0.0.11") + _, err := GetDPUHostPrimaryIPAddresses(nodeIP, []*net.IPNet{dpuSubnet}) + Expect(err).To(HaveOccurred()) + }) + }) +}) From 420d9f1cfd64a828187742f693a44487d6323f1c Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:23:07 +0200 Subject: [PATCH 128/278] [bridgeconfig] make mutex a public field to turn it into internal later Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 26 +++++++------- go-controller/pkg/node/gateway_shared_intf.go | 26 +++++++------- go-controller/pkg/node/gateway_udn.go | 4 +-- go-controller/pkg/node/openflow_manager.go | 36 +++++++++---------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index c3f3beae32..257b8a7059 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -63,7 +63,7 @@ func (netConfig *BridgeUDNConfiguration) SetBridgeNetworkOfPortsInternal() error } type BridgeConfiguration struct { - sync.Mutex + Mutex sync.Mutex NodeName string BridgeName string UplinkName string @@ -89,8 +89,8 @@ func (b *BridgeConfiguration) GetGatewayIface() string { // UpdateInterfaceIPAddresses sets and returns the bridge's current ips func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(b.GetGatewayIface()) if err != nil { return nil, err @@ -272,8 +272,8 @@ func getRepresentor(intfName string) (string, error) { // GetBridgePortConfigurations returns a slice of Network port configurations along with the // uplinkName and physical port's ofport value func (b *BridgeConfiguration) GetBridgePortConfigurations() ([]*BridgeUDNConfiguration, string, string) { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() var netConfigs []*BridgeUDNConfiguration for _, netConfig := range b.NetConfig { netConfigs = append(netConfigs, netConfig.ShallowCopy()) @@ -287,8 +287,8 @@ func (b *BridgeConfiguration) AddNetworkBridgeConfig( nodeSubnets []*net.IPNet, masqCTMark, pktMark uint, v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() netName := nInfo.GetNetworkName() patchPort := nInfo.GetNetworkScopedPatchPortName(b.BridgeName, b.NodeName) @@ -316,15 +316,15 @@ func (b *BridgeConfiguration) AddNetworkBridgeConfig( // DelNetworkBridgeConfig deletes the provided netInfo from the bridge configuration cache func (b *BridgeConfiguration) DelNetworkBridgeConfig(nInfo util.NetInfo) { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() delete(b.NetConfig, nInfo.GetNetworkName()) } func (b *BridgeConfiguration) GetNetworkBridgeConfig(networkName string) *BridgeUDNConfiguration { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() return b.NetConfig[networkName] } @@ -334,8 +334,8 @@ func (b *BridgeConfiguration) GetNetworkBridgeConfig(networkName string) *Bridge // NOTE: if the network configuration can't be found or if the network is not patched by OVN // yet this returns nil. func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName string) *BridgeUDNConfiguration { - b.Lock() - defer b.Unlock() + b.Mutex.Lock() + defer b.Mutex.Unlock() if netConfig, found := b.NetConfig[networkName]; found && netConfig.OfPortPatch != "" { return netConfig.ShallowCopy() diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 6d467a1285..688f25c297 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -235,9 +235,9 @@ type cidrAndFlags struct { func (npw *nodePortWatcher) updateGatewayIPs(addressManager *addressManager) { // Get Physical IPs of Node, Can be IPV4 IPV6 or both - addressManager.gatewayBridge.Lock() + addressManager.gatewayBridge.Mutex.Lock() gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.Ips) - addressManager.gatewayBridge.Unlock() + addressManager.gatewayBridge.Mutex.Unlock() npw.gatewayIPLock.Lock() defer npw.gatewayIPLock.Unlock() @@ -2342,8 +2342,8 @@ func hostNetworkNormalActionFlows(netConfig *bridgeconfig.BridgeUDNConfiguration } func setBridgeOfPorts(bridge *bridgeconfig.BridgeConfiguration) error { - bridge.Lock() - defer bridge.Unlock() + bridge.Mutex.Lock() + defer bridge.Mutex.Unlock() // Get ofport of patchPort for _, netConfig := range bridge.NetConfig { if err := netConfig.SetBridgeNetworkOfPortsInternal(); err != nil { @@ -2422,37 +2422,37 @@ func newGateway( if exGwBridge != nil { gw.readyFunc = func() (bool, error) { - gwBridge.Lock() + gwBridge.Mutex.Lock() for _, netConfig := range gwBridge.NetConfig { ready, err := gatewayReady(netConfig.PatchPort) if err != nil || !ready { - gwBridge.Unlock() + gwBridge.Mutex.Unlock() return false, err } } - gwBridge.Unlock() - exGwBridge.Lock() + gwBridge.Mutex.Unlock() + exGwBridge.Mutex.Lock() for _, netConfig := range exGwBridge.NetConfig { exGWReady, err := gatewayReady(netConfig.PatchPort) if err != nil || !exGWReady { - exGwBridge.Unlock() + exGwBridge.Mutex.Unlock() return false, err } } - exGwBridge.Unlock() + exGwBridge.Mutex.Unlock() return true, nil } } else { gw.readyFunc = func() (bool, error) { - gwBridge.Lock() + gwBridge.Mutex.Lock() for _, netConfig := range gwBridge.NetConfig { ready, err := gatewayReady(netConfig.PatchPort) if err != nil || !ready { - gwBridge.Unlock() + gwBridge.Mutex.Unlock() return false, err } } - gwBridge.Unlock() + gwBridge.Mutex.Unlock() return true, nil } } diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 04a5493f79..827bfe6421 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -97,8 +97,8 @@ type UserDefinedNetworkGateway struct { // END UDN UTILs for BridgeConfiguration func setBridgeNetworkOfPorts(bridge *bridgeconfig.BridgeConfiguration, netName string) error { - bridge.Lock() - defer bridge.Unlock() + bridge.Mutex.Lock() + defer bridge.Mutex.Unlock() netConfig, found := bridge.NetConfig[netName] if !found { diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 3eaa8a298f..12547978f9 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -66,20 +66,20 @@ func (c *openflowManager) getActiveNetwork(nInfo util.NetInfo) *bridgeconfig.Bri // END UDN UTILs func (c *openflowManager) getDefaultBridgeName() string { - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() return c.defaultBridge.BridgeName } func (c *openflowManager) getDefaultBridgeMAC() net.HardwareAddr { - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() return c.defaultBridge.MacAddress } func (c *openflowManager) setDefaultBridgeMAC(macAddr net.HardwareAddr) { - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() c.defaultBridge.MacAddress = macAddr } @@ -118,8 +118,8 @@ func (c *openflowManager) requestFlowSync() { func (c *openflowManager) syncFlows() { // protect gwBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() c.flowMutex.Lock() defer c.flowMutex.Unlock() @@ -135,8 +135,8 @@ func (c *openflowManager) syncFlows() { } if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Lock() - defer c.externalGatewayBridge.Unlock() + c.externalGatewayBridge.Mutex.Lock() + defer c.externalGatewayBridge.Mutex.Unlock() c.exGWFlowMutex.Lock() defer c.exGWFlowMutex.Unlock() @@ -213,14 +213,14 @@ func (c *openflowManager) Run(stopChan <-chan struct{}, doneWg *sync.WaitGroup) func (c *openflowManager) updateBridgePMTUDFlowCache(key string, ipAddrs []string) { // protect defaultBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() dftFlows := pmtudDropFlows(c.defaultBridge, ipAddrs) c.updateFlowCacheEntry(key, dftFlows) if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Lock() - defer c.externalGatewayBridge.Unlock() + c.externalGatewayBridge.Mutex.Lock() + defer c.externalGatewayBridge.Mutex.Unlock() exGWBridgeDftFlows := pmtudDropFlows(c.externalGatewayBridge, ipAddrs) c.updateExBridgeFlowCacheEntry(key, exGWBridgeDftFlows) } @@ -230,8 +230,8 @@ func (c *openflowManager) updateBridgePMTUDFlowCache(key string, ipAddrs []strin // note: this is shared between shared and local gateway modes func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets []*net.IPNet) error { // protect defaultBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Lock() - defer c.defaultBridge.Unlock() + c.defaultBridge.Mutex.Lock() + defer c.defaultBridge.Mutex.Unlock() // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! @@ -251,8 +251,8 @@ func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets [] // we consume ex gw bridge flows only if that is enabled if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Lock() - defer c.externalGatewayBridge.Unlock() + c.externalGatewayBridge.Mutex.Lock() + defer c.externalGatewayBridge.Mutex.Unlock() c.updateExBridgeFlowCacheEntry("NORMAL", []string{fmt.Sprintf("table=0,priority=0,actions=%s\n", util.NormalAction)}) exGWBridgeDftFlows, err := commonFlows(hostSubnets, c.externalGatewayBridge) if err != nil { From 3b073327997c6952c165cff7a6ded6663d907e27 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:33:32 +0200 Subject: [PATCH 129/278] [bridgeconfig] only create BridgeConfigurations inside the package. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 2 +- .../bridgeconfig/bridgeconfig_testutil.go | 21 ++++++ .../default_node_network_controller_test.go | 66 ++++--------------- go-controller/pkg/node/gateway.go | 4 +- .../pkg/node/gateway_localnet_linux_test.go | 14 ++-- go-controller/pkg/node/gateway_udn_test.go | 5 +- .../pkg/node/node_ip_handler_linux_test.go | 2 +- 7 files changed, 42 insertions(+), 72 deletions(-) create mode 100644 go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 257b8a7059..a501c7b641 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -117,7 +117,7 @@ func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]* return ifAddrs, nil } -func BridgeForInterface(intfName, nodeName, +func NewBridgeConfiguration(intfName, nodeName, physicalNetworkName string, nodeSubnets, gwIPs []*net.IPNet, gwNextHops []net.IP, diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go new file mode 100644 index 0000000000..baad614fda --- /dev/null +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go @@ -0,0 +1,21 @@ +package bridgeconfig + +import "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" + +func TestDefaultBridgeConfig() *BridgeConfiguration { + defaultNetConfig := &BridgeUDNConfiguration{ + OfPortPatch: "patch-breth0_ov", + } + return &BridgeConfiguration{ + NetConfig: map[string]*BridgeUDNConfiguration{ + types.DefaultNetworkName: defaultNetConfig, + }, + } +} + +func TestBridgeConfig(brName string) *BridgeConfiguration { + return &BridgeConfiguration{ + BridgeName: brName, + GwIface: brName, + } +} diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index de35b39e8d..a1413a7dd1 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -810,17 +810,10 @@ var _ = Describe("Node", func() { Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } @@ -922,17 +915,10 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.254.61 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } @@ -1076,17 +1062,10 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.253.61 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } @@ -1187,17 +1166,10 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2001:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } @@ -1355,17 +1327,10 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } @@ -1483,17 +1448,10 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } nc.Gateway = &gateway{ openflowManager: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: bridgeconfig.TestDefaultBridgeConfig(), }, } diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index a617249c52..bf28fbb058 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -357,13 +357,13 @@ func setupUDPAggregationUplink(ifname string) error { func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops []net.IP, nodeSubnets, gwIPs []*net.IPNet, advertised bool, nodeAnnotator kube.Annotator) ( *bridgeconfig.BridgeConfiguration, *bridgeconfig.BridgeConfiguration, error) { - gatewayBridge, err := bridgeconfig.BridgeForInterface(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, gwNextHops, advertised) + gatewayBridge, err := bridgeconfig.NewBridgeConfiguration(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, gwNextHops, advertised) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", gwIntf, err) } var egressGWBridge *bridgeconfig.BridgeConfiguration if egressGatewayIntf != "" { - egressGWBridge, err = bridgeconfig.BridgeForInterface(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, nil, false) + egressGWBridge, err = bridgeconfig.NewBridgeConfiguration(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, nil, false) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", egressGatewayIntf, err) } diff --git a/go-controller/pkg/node/gateway_localnet_linux_test.go b/go-controller/pkg/node/gateway_localnet_linux_test.go index 87ef3aa72c..d259bc14e3 100644 --- a/go-controller/pkg/node/gateway_localnet_linux_test.go +++ b/go-controller/pkg/node/gateway_localnet_linux_test.go @@ -57,9 +57,8 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher gwMACParsed, _ := net.ParseMAC(gwMAC) - defaultNetConfig := &bridgeconfig.BridgeUDNConfiguration{ - OfPortPatch: "patch-breth0_ov", - } + defaultBridge := bridgeconfig.TestDefaultBridgeConfig() + defaultBridge.MacAddress = gwMACParsed fNPW := nodePortWatcher{ ofportPhys: "eth0", @@ -67,13 +66,8 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher gatewayIPv6: v6localnetGatewayIP, serviceInfo: make(map[k8stypes.NamespacedName]*serviceConfig), ofm: &openflowManager{ - flowCache: map[string][]string{}, - defaultBridge: &bridgeconfig.BridgeConfiguration{ - MacAddress: gwMACParsed, - NetConfig: map[string]*bridgeconfig.BridgeUDNConfiguration{ - types.DefaultNetworkName: defaultNetConfig, - }, - }, + flowCache: map[string][]string{}, + defaultBridge: defaultBridge, }, networkManager: networkmanager.Default().Interface(), } diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index d26cf16910..8f4082d1c5 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -343,10 +343,7 @@ func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeconfig.Bridge } func getDummyOpenflowManager() *openflowManager { - gwBridge := &bridgeconfig.BridgeConfiguration{ - GwIface: "breth0", - BridgeName: "breth0", - } + gwBridge := bridgeconfig.TestBridgeConfig("breth0") ofm := &openflowManager{ defaultBridge: gwBridge, } diff --git a/go-controller/pkg/node/node_ip_handler_linux_test.go b/go-controller/pkg/node/node_ip_handler_linux_test.go index 35264a1288..aa819cdb8a 100644 --- a/go-controller/pkg/node/node_ip_handler_linux_test.go +++ b/go-controller/pkg/node/node_ip_handler_linux_test.go @@ -402,7 +402,7 @@ func configureKubeOVNContext(nodeName string, useNetlink bool) *testCtx { mpmock := &nodemocks.ManagementPort{} mpmock.On("GetAddresses").Return([]*net.IPNet{tc.mgmtPortIP4, tc.mgmtPortIP6}) - fakeBridgeConfiguration := &bridgeconfig.BridgeConfiguration{BridgeName: "breth0", GwIface: "breth0"} + fakeBridgeConfiguration := bridgeconfig.TestBridgeConfig("breth0") k := &kube.Kube{KClient: tc.fakeClient} tc.ipManager = newAddressManagerInternal(nodeName, k, mpmock, tc.watchFactory, fakeBridgeConfiguration, useNetlink) From a4d421a378217ff0a0b22314d9d6160f9c9d6740 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:37:56 +0200 Subject: [PATCH 130/278] [bridgeconfig] simply move functions around, no change Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index a501c7b641..59d16255c7 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -79,44 +79,6 @@ type BridgeConfiguration struct { NextHops []net.IP } -func (b *BridgeConfiguration) GetGatewayIface() string { - // If GwIface is set, then accelerated GW interface is present and we use it. If else use external bridge instead. - if b.GwIface != "" { - return b.GwIface - } - return b.BridgeName -} - -// UpdateInterfaceIPAddresses sets and returns the bridge's current ips -func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { - b.Mutex.Lock() - defer b.Mutex.Unlock() - ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(b.GetGatewayIface()) - if err != nil { - return nil, err - } - - // For DPU, here we need to use the DPU host's IP address which is the tenant cluster's - // host internal IP address instead of the DPU's external bridge IP address. - if config.OvnKubeNode.Mode == types.NodeModeDPU { - nodeAddrStr, err := util.GetNodePrimaryIP(node) - if err != nil { - return nil, err - } - nodeAddr := net.ParseIP(nodeAddrStr) - if nodeAddr == nil { - return nil, fmt.Errorf("failed to parse node IP address. %v", nodeAddrStr) - } - ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(nodeAddr, ifAddrs) - if err != nil { - return nil, err - } - } - - b.Ips = ifAddrs - return ifAddrs, nil -} - func NewBridgeConfiguration(intfName, nodeName, physicalNetworkName string, nodeSubnets, gwIPs []*net.IPNet, @@ -260,13 +222,42 @@ func NewBridgeConfiguration(intfName, nodeName, return &res, nil } -func getRepresentor(intfName string) (string, error) { - deviceID, err := util.GetDeviceIDFromNetdevice(intfName) +func (b *BridgeConfiguration) GetGatewayIface() string { + // If GwIface is set, then accelerated GW interface is present and we use it. If else use external bridge instead. + if b.GwIface != "" { + return b.GwIface + } + return b.BridgeName +} + +// UpdateInterfaceIPAddresses sets and returns the bridge's current ips +func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(b.GetGatewayIface()) if err != nil { - return "", err + return nil, err } - return util.GetFunctionRepresentorName(deviceID) + // For DPU, here we need to use the DPU host's IP address which is the tenant cluster's + // host internal IP address instead of the DPU's external bridge IP address. + if config.OvnKubeNode.Mode == types.NodeModeDPU { + nodeAddrStr, err := util.GetNodePrimaryIP(node) + if err != nil { + return nil, err + } + nodeAddr := net.ParseIP(nodeAddrStr) + if nodeAddr == nil { + return nil, fmt.Errorf("failed to parse node IP address. %v", nodeAddrStr) + } + ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(nodeAddr, ifAddrs) + if err != nil { + return nil, err + } + } + + b.Ips = ifAddrs + return ifAddrs, nil } // GetBridgePortConfigurations returns a slice of Network port configurations along with the @@ -421,3 +412,12 @@ func bridgedGatewayNodeSetup(nodeName, bridgeName, physicalNetworkName string) ( ifaceID := bridgeName + "_" + nodeName return ifaceID, nil } + +func getRepresentor(intfName string) (string, error) { + deviceID, err := util.GetDeviceIDFromNetdevice(intfName) + if err != nil { + return "", err + } + + return util.GetFunctionRepresentorName(deviceID) +} From cf93ef303ca96a6c5a249508c31e13b9729fac38 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:43:41 +0200 Subject: [PATCH 131/278] [bridgeconfig] start moving methods that use internal mutex to the pkg Update gatewayReady function to only return bool as it always returns nil error. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 24 +++++++++++++++ go-controller/pkg/node/gateway.go | 10 ------- go-controller/pkg/node/gateway_shared_intf.go | 30 ++++--------------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 59d16255c7..7ef6236c9a 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -345,6 +345,30 @@ func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { return result } +// IsGatewayReady checks if patch ports of every netConfig are present. +// used by gateway on newGateway readyFunc +func (b *BridgeConfiguration) IsGatewayReady() bool { + b.Mutex.Lock() + defer b.Mutex.Unlock() + for _, netConfig := range b.NetConfig { + ready := gatewayReady(netConfig.PatchPort) + if !ready { + return false + } + } + return true +} + +func gatewayReady(patchPort string) bool { + // Get ofport of patchPort + ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") + if err != nil || len(ofport) == 0 { + return false + } + klog.Info("Gateway is ready") + return true +} + func getIntfName(gatewayIntf string) (string, error) { // The given (or autodetected) interface is an OVS bridge and this could be // created by us using util.NicToBridge() or it was pre-created by the user. diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index bf28fbb058..948da997d5 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -452,16 +452,6 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops return gatewayBridge, egressGWBridge, err } -func gatewayReady(patchPort string) (bool, error) { - // Get ofport of patchPort - ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") - if err != nil || len(ofport) == 0 { - return false, nil - } - klog.Info("Gateway is ready") - return true, nil -} - func (g *gateway) GetGatewayBridgeIface() string { return g.openflowManager.getDefaultBridgeName() } diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 688f25c297..9a1ae075c2 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -2422,37 +2422,19 @@ func newGateway( if exGwBridge != nil { gw.readyFunc = func() (bool, error) { - gwBridge.Mutex.Lock() - for _, netConfig := range gwBridge.NetConfig { - ready, err := gatewayReady(netConfig.PatchPort) - if err != nil || !ready { - gwBridge.Mutex.Unlock() - return false, err - } + if !gwBridge.IsGatewayReady() { + return false, nil } - gwBridge.Mutex.Unlock() - exGwBridge.Mutex.Lock() - for _, netConfig := range exGwBridge.NetConfig { - exGWReady, err := gatewayReady(netConfig.PatchPort) - if err != nil || !exGWReady { - exGwBridge.Mutex.Unlock() - return false, err - } + if !exGwBridge.IsGatewayReady() { + return false, nil } - exGwBridge.Mutex.Unlock() return true, nil } } else { gw.readyFunc = func() (bool, error) { - gwBridge.Mutex.Lock() - for _, netConfig := range gwBridge.NetConfig { - ready, err := gatewayReady(netConfig.PatchPort) - if err != nil || !ready { - gwBridge.Mutex.Unlock() - return false, err - } + if !gwBridge.IsGatewayReady() { + return false, nil } - gwBridge.Mutex.Unlock() return true, nil } } From 836e0f64ffd874ccbdb46df1ec9f51946f824666 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:49:33 +0200 Subject: [PATCH 132/278] [bridgeconfig] move setBridgeOfPorts to the package. no changes to the function Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 48 +++++++++++++ go-controller/pkg/node/gateway_shared_intf.go | 68 +++---------------- go-controller/pkg/node/types/const.go | 2 + 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 7ef6236c9a..4fb433f4c8 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -359,6 +359,54 @@ func (b *BridgeConfiguration) IsGatewayReady() bool { return true } +func (b *BridgeConfiguration) SetOfPorts() error { + b.Mutex.Lock() + defer b.Mutex.Unlock() + // Get ofport of patchPort + for _, netConfig := range b.NetConfig { + if err := netConfig.SetBridgeNetworkOfPortsInternal(); err != nil { + return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.PatchPort, err) + } + } + + if b.UplinkName != "" { + // Get ofport of physical interface + ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", b.UplinkName, "ofport") + if err != nil { + return fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", + b.UplinkName, stderr, err) + } + b.OfPortPhys = ofportPhys + } + + // Get ofport representing the host. That is, host representor port in case of DPUs, ovsLocalPort otherwise. + if config.OvnKubeNode.Mode == types.NodeModeDPU { + var stderr string + hostRep, err := util.GetDPUHostInterface(b.BridgeName) + if err != nil { + return err + } + + b.OfPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") + if err != nil { + return fmt.Errorf("failed to get ofport of host interface %s, stderr: %q, error: %v", + hostRep, stderr, err) + } + } else { + var err error + if b.GwIfaceRep != "" { + b.OfPortHost, _, err = util.RunOVSVsctl("get", "interface", b.GwIfaceRep, "ofport") + if err != nil { + return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", b.GwIfaceRep, err) + } + } else { + b.OfPortHost = nodetypes.OvsLocalPort + } + } + + return nil +} + func gatewayReady(patchPort string) bool { // Get ofport of patchPort ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 9a1ae075c2..d17992eb80 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -50,8 +50,6 @@ const ( // pmtudOpenFlowCookie identifies the flows used to drop ICMP type (3) destination unreachable, // fragmentation-needed (4) pmtudOpenFlowCookie = "0x0304" - // ovsLocalPort is the name of the OVS bridge local port - ovsLocalPort = "LOCAL" // ctMarkHost is the conntrack mark value for host traffic ctMarkHost = "0x2" @@ -391,7 +389,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI if err != nil { // in the odd case that getting all ports from the bridge should not work, // simply output to LOCAL (this should work well in the vast majority of cases, anyway) - klog.Warningf("Unable to get port list from bridge. Using ovsLocalPort as output only: error: %v", + klog.Warningf("Unable to get port list from bridge. Using OvsLocalPort as output only: error: %v", err) } } @@ -575,7 +573,7 @@ func (npw *nodePortWatcher) generateARPBypassFlow(ofPorts []string, ofPortPatch, // simply output to LOCAL (this should work well in the vast majority of cases, anyway) arpFlow = fmt.Sprintf("cookie=%s, priority=110, in_port=%s, %s, %s=%s, "+ "actions=output:%s", - cookie, npw.ofportPhys, addrResProto, addrResDst, ipAddr, ovsLocalPort) + cookie, npw.ofportPhys, addrResProto, addrResDst, ipAddr, nodetypes.OvsLocalPort) } else { // cover the case where breth0 has more than 3 ports, e.g. if an admin adds a 4th port // and the ExternalIP would be on that port @@ -1552,7 +1550,7 @@ func flowsForDefaultBridge(bridge *bridgeconfig.BridgeConfiguration, extraIPs [] // table0, Geneve packets coming from LOCAL. Skip conntrack and send to external dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp6, udp_dst=%d, "+ - "actions=output:%s", defaultOpenFlowCookie, ovsLocalPort, config.Default.EncapPort, ofPortPhys)) + "actions=output:%s", defaultOpenFlowCookie, nodetypes.OvsLocalPort, config.Default.EncapPort, ofPortPhys)) } physicalIP, err := util.MatchFirstIPNetFamily(true, bridgeIPs) @@ -2157,7 +2155,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeconfig.BridgeConfigurat // but holding this until // https://issues.redhat.com/browse/FDP-646 is fixed, for now we // are assuming MEG & BGP are not used together - output = ovsLocalPort + output = nodetypes.OvsLocalPort } for _, clusterEntry := range netConfig.Subnets { cidr := clusterEntry.CIDR @@ -2175,7 +2173,7 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeconfig.BridgeConfigurat dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=16, table=1, %s, %s_dst=%s, "+ "actions=output:%s", - defaultOpenFlowCookie, ipv, ipv, mgmtIP.IP, ovsLocalPort), + defaultOpenFlowCookie, ipv, ipv, mgmtIP.IP, nodetypes.OvsLocalPort), ) } } @@ -2307,7 +2305,7 @@ func hostNetworkNormalActionFlows(netConfig *bridgeconfig.BridgeUDNConfiguration if utilnet.IsIPv6(hostSubnet.IP) != isV6 { continue } - flows = append(flows, formatFlow(ovsLocalPort, hostSubnet.String(), ctMarkHost)) + flows = append(flows, formatFlow(nodetypes.OvsLocalPort, hostSubnet.String(), ctMarkHost)) } if isV6 { @@ -2335,60 +2333,12 @@ func hostNetworkNormalActionFlows(netConfig *bridgeconfig.BridgeUDNConfiguration // Traffic path (a) for ICMP: OVN->localnet for local gw mode // Traffic path (b) for ICMP: host->localnet for both gw modes - flows = append(flows, formatICMPFlow(ovsLocalPort, ctMarkHost, icmpType)) + flows = append(flows, formatICMPFlow(nodetypes.OvsLocalPort, ctMarkHost, icmpType)) } } return flows } -func setBridgeOfPorts(bridge *bridgeconfig.BridgeConfiguration) error { - bridge.Mutex.Lock() - defer bridge.Mutex.Unlock() - // Get ofport of patchPort - for _, netConfig := range bridge.NetConfig { - if err := netConfig.SetBridgeNetworkOfPortsInternal(); err != nil { - return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.PatchPort, err) - } - } - - if bridge.UplinkName != "" { - // Get ofport of physical interface - ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", bridge.UplinkName, "ofport") - if err != nil { - return fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - bridge.UplinkName, stderr, err) - } - bridge.OfPortPhys = ofportPhys - } - - // Get ofport representing the host. That is, host representor port in case of DPUs, ovsLocalPort otherwise. - if config.OvnKubeNode.Mode == types.NodeModeDPU { - var stderr string - hostRep, err := util.GetDPUHostInterface(bridge.BridgeName) - if err != nil { - return err - } - - bridge.OfPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") - if err != nil { - return fmt.Errorf("failed to get ofport of host interface %s, stderr: %q, error: %v", - hostRep, stderr, err) - } - } else { - var err error - if bridge.GwIfaceRep != "" { - bridge.OfPortHost, _, err = util.RunOVSVsctl("get", "interface", bridge.GwIfaceRep, "ofport") - if err != nil { - return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", bridge.GwIfaceRep, err) - } - } else { - bridge.OfPortHost = ovsLocalPort - } - } - - return nil -} - func newGateway( nodeName string, subnets []*net.IPNet, @@ -2443,12 +2393,12 @@ func newGateway( // Program cluster.GatewayIntf to let non-pod traffic to go to host // stack klog.Info("Creating Gateway Openflow Manager") - err := setBridgeOfPorts(gwBridge) + err := gwBridge.SetOfPorts() if err != nil { return err } if exGwBridge != nil { - err = setBridgeOfPorts(exGwBridge) + err = exGwBridge.SetOfPorts() if err != nil { return err } diff --git a/go-controller/pkg/node/types/const.go b/go-controller/pkg/node/types/const.go index 64f4f15cf6..b486302dd6 100644 --- a/go-controller/pkg/node/types/const.go +++ b/go-controller/pkg/node/types/const.go @@ -3,4 +3,6 @@ package types const ( // CtMarkOVN is the conntrack mark value for OVN traffic CtMarkOVN = "0x1" + // OvsLocalPort is the name of the OVS bridge local port + OvsLocalPort = "LOCAL" ) From b607e93d4b63e063181d86ba64bd37b7eb0e53b9 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 14:57:07 +0200 Subject: [PATCH 133/278] [bridgeconfig] add some getters/setters with lock to the pkg. Make SetBridgeNetworkOfPortsInternal actually internal, rename to setOfPatchPort as it only updates patchPort. Also rename setBridgeNetworkOfPorts to SetNetworkOfPatchPort for the same reason. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 39 ++++++++++++++++++- go-controller/pkg/node/gateway_shared_intf.go | 4 +- go-controller/pkg/node/gateway_udn.go | 20 +--------- go-controller/pkg/node/openflow_manager.go | 12 ++---- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 4fb433f4c8..0dd601cc24 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -52,7 +52,7 @@ func (netConfig *BridgeUDNConfiguration) IsDefaultNetwork() bool { return netConfig.MasqCTMark == nodetypes.CtMarkOVN } -func (netConfig *BridgeUDNConfiguration) SetBridgeNetworkOfPortsInternal() error { +func (netConfig *BridgeUDNConfiguration) setOfPatchPort() error { ofportPatch, stderr, err := util.GetOVSOfPort("get", "Interface", netConfig.PatchPort, "ofport") if err != nil { return fmt.Errorf("failed while waiting on patch port %q to be created by ovn-controller and "+ @@ -364,7 +364,7 @@ func (b *BridgeConfiguration) SetOfPorts() error { defer b.Mutex.Unlock() // Get ofport of patchPort for _, netConfig := range b.NetConfig { - if err := netConfig.SetBridgeNetworkOfPortsInternal(); err != nil { + if err := netConfig.setOfPatchPort(); err != nil { return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.PatchPort, err) } } @@ -407,6 +407,41 @@ func (b *BridgeConfiguration) SetOfPorts() error { return nil } +func (b *BridgeConfiguration) GetIPs() []*net.IPNet { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.Ips +} + +func (b *BridgeConfiguration) GetBridgeName() string { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.BridgeName +} + +func (b *BridgeConfiguration) GetMAC() net.HardwareAddr { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.MacAddress +} + +func (b *BridgeConfiguration) SetMAC(macAddr net.HardwareAddr) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + b.MacAddress = macAddr +} + +func (b *BridgeConfiguration) SetNetworkOfPatchPort(netName string) error { + b.Mutex.Lock() + defer b.Mutex.Unlock() + + netConfig, found := b.NetConfig[netName] + if !found { + return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, b.BridgeName) + } + return netConfig.setOfPatchPort() +} + func gatewayReady(patchPort string) bool { // Get ofport of patchPort ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index d17992eb80..fb902c589c 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -233,9 +233,7 @@ type cidrAndFlags struct { func (npw *nodePortWatcher) updateGatewayIPs(addressManager *addressManager) { // Get Physical IPs of Node, Can be IPV4 IPV6 or both - addressManager.gatewayBridge.Mutex.Lock() - gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.Ips) - addressManager.gatewayBridge.Mutex.Unlock() + gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.GetIPs()) npw.gatewayIPLock.Lock() defer npw.gatewayIPLock.Unlock() diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 827bfe6421..a9d3b92d23 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -23,7 +23,6 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/iprulemanager" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/vrfmanager" @@ -92,21 +91,6 @@ type UserDefinedNetworkGateway struct { gwInterfaceIndex int } -// UTILS Needed for UDN (also leveraged for default netInfo) in BridgeConfiguration - -// END UDN UTILs for BridgeConfiguration - -func setBridgeNetworkOfPorts(bridge *bridgeconfig.BridgeConfiguration, netName string) error { - bridge.Mutex.Lock() - defer bridge.Mutex.Unlock() - - netConfig, found := bridge.NetConfig[netName] - if !found { - return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, bridge.BridgeName) - } - return netConfig.SetBridgeNetworkOfPortsInternal() -} - func NewUserDefinedNetworkGateway(netInfo util.NetInfo, node *corev1.Node, nodeLister listers.NodeLister, kubeInterface kube.Interface, vrfManager *vrfmanager.Controller, ruleManager *iprulemanager.Controller, defaultNetworkGateway Gateway) (*UserDefinedNetworkGateway, error) { @@ -270,12 +254,12 @@ func (udng *UserDefinedNetworkGateway) AddNetwork() error { waiter := newStartupWaiterWithTimeout(waitForPatchPortTimeout) readyFunc := func() (bool, error) { - if err := setBridgeNetworkOfPorts(udng.openflowManager.defaultBridge, udng.GetNetworkName()); err != nil { + if err := udng.openflowManager.defaultBridge.SetNetworkOfPatchPort(udng.GetNetworkName()); err != nil { klog.V(3).Infof("Failed to set network %s's openflow ports for default bridge; error: %v", udng.GetNetworkName(), err) return false, nil } if udng.openflowManager.externalGatewayBridge != nil { - if err := setBridgeNetworkOfPorts(udng.openflowManager.externalGatewayBridge, udng.GetNetworkName()); err != nil { + if err := udng.openflowManager.externalGatewayBridge.SetNetworkOfPatchPort(udng.GetNetworkName()); err != nil { klog.V(3).Infof("Failed to set network %s's openflow ports for secondary bridge; error: %v", udng.GetNetworkName(), err) return false, nil } diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 12547978f9..0b96b2186f 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -66,21 +66,15 @@ func (c *openflowManager) getActiveNetwork(nInfo util.NetInfo) *bridgeconfig.Bri // END UDN UTILs func (c *openflowManager) getDefaultBridgeName() string { - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - return c.defaultBridge.BridgeName + return c.defaultBridge.GetBridgeName() } func (c *openflowManager) getDefaultBridgeMAC() net.HardwareAddr { - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - return c.defaultBridge.MacAddress + return c.defaultBridge.GetMAC() } func (c *openflowManager) setDefaultBridgeMAC(macAddr net.HardwareAddr) { - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - c.defaultBridge.MacAddress = macAddr + c.defaultBridge.SetMAC(macAddr) } func (c *openflowManager) updateFlowCacheEntry(key string, flows []string) { From 28f9c1eccc0e72fadfee4c19db00f0bb02748223 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 16:09:39 +0200 Subject: [PATCH 134/278] [bridgeconfig] move bridge flows generation functions to the pkg. These functions use bridge lock, will convert them to BridgeConfiguration methods later. Move test functions related to flow generation. Signed-off-by: Nadia Pinaeva --- .../bridgeconfig/bridgeconfig_testutil.go | 114 +- .../pkg/node/bridgeconfig/bridgeflows.go | 946 +++++++++++++++++ .../node/default_node_network_controller.go | 3 +- go-controller/pkg/node/egress_service_test.go | 11 +- go-controller/pkg/node/gateway.go | 5 - go-controller/pkg/node/gateway_shared_intf.go | 974 +----------------- go-controller/pkg/node/gateway_udn_test.go | 124 +-- go-controller/pkg/node/openflow_manager.go | 15 +- go-controller/pkg/node/types/const.go | 14 + go-controller/pkg/node/util/util.go | 26 + 10 files changed, 1131 insertions(+), 1101 deletions(-) create mode 100644 go-controller/pkg/node/bridgeconfig/bridgeflows.go diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go index baad614fda..271c555e7e 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go @@ -1,6 +1,19 @@ package bridgeconfig -import "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" +import ( + "fmt" + "net" + "strings" + + net2 "k8s.io/utils/net" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) func TestDefaultBridgeConfig() *BridgeConfiguration { defaultNetConfig := &BridgeUDNConfiguration{ @@ -19,3 +32,102 @@ func TestBridgeConfig(brName string) *BridgeConfiguration { GwIface: brName, } } + +func CheckUDNSvcIsolationOVSFlows(flows []string, netConfig *BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { + By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows", + netName, svcCIDR.String(), expectedNFlows)) + + var mgmtMasqIP string + var protoPrefix string + if net2.IsIPv4CIDR(svcCIDR) { + mgmtMasqIP = netConfig.V4MasqIPs.ManagementPort.IP.String() + protoPrefix = "ip" + } else { + mgmtMasqIP = netConfig.V6MasqIPs.ManagementPort.IP.String() + protoPrefix = "ip6" + } + + var nFlows int + for _, flow := range flows { + if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", + protoPrefix, protoPrefix, mgmtMasqIP)) { + nFlows++ + } + } + + Expect(nFlows).To(Equal(expectedNFlows)) +} + +func CheckAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { + By(fmt.Sprintf("Checking advertised UDN %s service isolation flows for %s; expected %d flows", + netName, svcCIDR.String(), expectedNFlows)) + + var matchingIPFamilySubnet *net.IPNet + var protoPrefix string + var udnAdvertisedSubnets []*net.IPNet + var err error + for _, clusterEntry := range netConfig.Subnets { + udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) + } + if net2.IsIPv4CIDR(svcCIDR) { + matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) + Expect(err).ToNot(HaveOccurred()) + protoPrefix = "ip" + } else { + matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) + Expect(err).ToNot(HaveOccurred()) + protoPrefix = "ip6" + } + + var nFlows int + for _, flow := range flows { + if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", + protoPrefix, protoPrefix, matchingIPFamilySubnet)) { + nFlows++ + } + if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=LOCAL, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=64001,table=2)", + protoPrefix, protoPrefix, matchingIPFamilySubnet, protoPrefix, svcCIDR)) { + nFlows++ + } + } + + Expect(nFlows).To(Equal(expectedNFlows)) +} + +func CheckDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *BridgeUDNConfiguration, ofPortHost, bridgeMAC string, svcCIDR *net.IPNet) { + By(fmt.Sprintf("Checking default service isolation flows for %s", svcCIDR.String())) + + var masqIP string + var masqSubnet string + var protoPrefix string + if net2.IsIPv4CIDR(svcCIDR) { + protoPrefix = "ip" + masqIP = config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String() + masqSubnet = config.Gateway.V4MasqueradeSubnet + } else { + protoPrefix = "ip6" + masqIP = config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String() + masqSubnet = config.Gateway.V6MasqueradeSubnet + } + + var nTable0DefaultFlows int + var nTable0UDNMasqFlows int + var nTable2Flows int + for _, flow := range flows { + if strings.Contains(flow, fmt.Sprintf("priority=500, in_port=%s, %s, %s_dst=%s, actions=ct(commit,zone=%d,nat(src=%s),table=2)", + ofPortHost, protoPrefix, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone, + masqIP)) { + nTable0DefaultFlows++ + } else if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=%d,table=2)", + ofPortHost, protoPrefix, protoPrefix, masqSubnet, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) { + nTable0UDNMasqFlows++ + } else if strings.Contains(flow, fmt.Sprintf("priority=100, table=2, actions=set_field:%s->eth_dst,output:%s", + bridgeMAC, defaultConfig.OfPortPatch)) { + nTable2Flows++ + } + } + + Expect(nTable0DefaultFlows).To(Equal(1)) + Expect(nTable0UDNMasqFlows).To(Equal(1)) + Expect(nTable2Flows).To(Equal(1)) +} diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go new file mode 100644 index 0000000000..5a3467ae21 --- /dev/null +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -0,0 +1,946 @@ +package bridgeconfig + +import ( + "fmt" + "net" + + "k8s.io/klog/v2" + utilnet "k8s.io/utils/net" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" + nodeutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" +) + +func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]string, error) { + // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure + // that dl_src is included in match criteria! + + ofPortPhys := bridge.OfPortPhys + bridgeMacAddress := bridge.MacAddress.String() + ofPortHost := bridge.OfPortHost + bridgeIPs := bridge.Ips + + var dftFlows []string + // 14 bytes of overhead for ethernet header (does not include VLAN) + maxPktLength := getMaxFrameLength() + + strip_vlan := "" + mod_vlan_id := "" + match_vlan := "" + if config.Gateway.VLANID != 0 { + strip_vlan = "strip_vlan," + match_vlan = fmt.Sprintf("dl_vlan=%d,", config.Gateway.VLANID) + mod_vlan_id = fmt.Sprintf("mod_vlan_vid:%d,", config.Gateway.VLANID) + } + + if config.IPv4Mode { + // table0, Geneve packets coming from external. Skip conntrack and go directly to host + // if dest mac is the shared mac send directly to host. + if ofPortPhys != "" { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=205, in_port=%s, dl_dst=%s, udp, udp_dst=%d, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortPhys, bridgeMacAddress, config.Default.EncapPort, + ofPortHost)) + // perform NORMAL action otherwise. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp, udp_dst=%d, "+ + "actions=NORMAL", nodetypes.DefaultOpenFlowCookie, ofPortPhys, config.Default.EncapPort)) + + // table0, Geneve packets coming from LOCAL/Host OFPort. Skip conntrack and go directly to external + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp, udp_dst=%d, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Default.EncapPort, ofPortPhys)) + } + physicalIP, err := util.MatchFirstIPNetFamily(false, bridgeIPs) + if err != nil { + return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) + } + for _, netConfig := range bridge.PatchedNetConfigs() { + // table 0, SVC Hairpin from OVN destined to local host, DNAT and go to table 4 + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ + "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String(), physicalIP.IP, + config.Default.HostMasqConntrackZone, physicalIP.IP)) + } + + // table 0, hairpin from OVN destined to local host (but an additional node IP), send to table 4 + for _, ip := range extraIPs { + if ip.To4() == nil { + continue + } + // not needed for the physical IP + if ip.Equal(physicalIP.IP) { + continue + } + + // not needed for special masquerade IP + if ip.Equal(config.Gateway.MasqueradeIPs.V4HostMasqueradeIP) { + continue + } + + for _, netConfig := range bridge.PatchedNetConfigs() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ + "actions=ct(commit,zone=%d,table=4)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, + config.Default.HostMasqConntrackZone)) + } + } + + // table 0, Reply SVC traffic from Host -> OVN, unSNAT and goto table 5 + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s,"+ + "actions=ct(zone=%d,nat,table=5)", + nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP.String(), config.Default.OVNMasqConntrackZone)) + } + if config.IPv6Mode { + if ofPortPhys != "" { + // table0, Geneve packets coming from external. Skip conntrack and go directly to host + // if dest mac is the shared mac send directly to host. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=205, in_port=%s, dl_dst=%s, udp6, udp_dst=%d, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortPhys, bridgeMacAddress, config.Default.EncapPort, + ofPortHost)) + // perform NORMAL action otherwise. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp6, udp_dst=%d, "+ + "actions=NORMAL", nodetypes.DefaultOpenFlowCookie, ofPortPhys, config.Default.EncapPort)) + + // table0, Geneve packets coming from LOCAL. Skip conntrack and send to external + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp6, udp_dst=%d, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, nodetypes.OvsLocalPort, config.Default.EncapPort, ofPortPhys)) + } + + physicalIP, err := util.MatchFirstIPNetFamily(true, bridgeIPs) + if err != nil { + return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) + } + // table 0, SVC Hairpin from OVN destined to local host, DNAT to host, send to table 4 + for _, netConfig := range bridge.PatchedNetConfigs() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ + "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String(), physicalIP.IP, + config.Default.HostMasqConntrackZone, physicalIP.IP)) + } + + // table 0, hairpin from OVN destined to local host (but an additional node IP), send to table 4 + for _, ip := range extraIPs { + if ip.To4() != nil { + continue + } + // not needed for the physical IP + if ip.Equal(physicalIP.IP) { + continue + } + + // not needed for special masquerade IP + if ip.Equal(config.Gateway.MasqueradeIPs.V6HostMasqueradeIP) { + continue + } + + for _, netConfig := range bridge.PatchedNetConfigs() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ + "actions=ct(commit,zone=%d,table=4)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, + config.Default.HostMasqConntrackZone)) + } + } + + // table 0, Reply SVC traffic from Host -> OVN, unSNAT and goto table 5 + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s,"+ + "actions=ct(zone=%d,nat,table=5)", + nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP.String(), config.Default.OVNMasqConntrackZone)) + } + + var protoPrefix, masqIP, masqSubnet string + + // table 0, packets coming from Host -> Service + for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { + if utilnet.IsIPv4CIDR(svcCIDR) { + protoPrefix = "ip" + masqIP = config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String() + masqSubnet = config.Gateway.V4MasqueradeSubnet + } else { + protoPrefix = "ipv6" + masqIP = config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String() + masqSubnet = config.Gateway.V6MasqueradeSubnet + } + + // table 0, Host (default network) -> OVN towards SVC, SNAT to special IP. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_dst=%s, "+ + "actions=ct(commit,zone=%d,nat(src=%s),table=2)", + nodetypes.DefaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, + svcCIDR, config.Default.HostMasqConntrackZone, masqIP)) + + if util.IsNetworkSegmentationSupportEnabled() { + // table 0, Host (UDNs) -> OVN towards SVC, SNAT to special IP. + // For packets originating from UDN, commit without NATing, those + // have already been SNATed to the masq IP of the UDN. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, "+ + "actions=ct(commit,zone=%d,table=2)", + nodetypes.DefaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, + masqSubnet, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) + if util.IsRouteAdvertisementsEnabled() { + // If the UDN is advertised then instead of matching on the masqSubnet + // we match on the UDNPodSubnet itself and we also don't SNAT to 169.254.0.2 + // sample flow: cookie=0xdeff105, duration=1472.742s, table=0, n_packets=9, n_bytes=666, priority=550 + // ip,in_port=LOCAL,nw_src=103.103.0.0/16,nw_dst=10.96.0.0/16 actions=ct(commit,table=2,zone=64001) + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { + continue + } + if netConfig.Advertised.Load() { + var udnAdvertisedSubnets []*net.IPNet + for _, clusterEntry := range netConfig.Subnets { + udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) + } + // Filter subnets based on the clusterIP service family + // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one + matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(svcCIDR), udnAdvertisedSubnets) + if err != nil { + klog.Infof("Unable to determine UDN subnet for the provided family isIPV6: %t, %v", utilnet.IsIPv6CIDR(svcCIDR), err) + continue + } + + // Use the filtered subnet for the flow compute instead of the masqueradeIP + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, "+ + "actions=ct(commit,zone=%d,table=2)", + nodetypes.DefaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, + matchingIPFamilySubnet.String(), protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) + } + } + } + } + + masqDst := masqIP + if util.IsNetworkSegmentationSupportEnabled() { + // In UDN match on the whole masquerade subnet to handle replies from UDN enabled services + masqDst = masqSubnet + } + for _, netConfig := range bridge.PatchedNetConfigs() { + // table 0, Reply hairpin traffic to host, coming from OVN, unSNAT + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_src=%s, %s_dst=%s,"+ + "actions=ct(zone=%d,nat,table=3)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR, + protoPrefix, masqDst, config.Default.HostMasqConntrackZone)) + // table 0, Reply traffic coming from OVN to outside, drop it if the DNAT wasn't done either + // at the GR load balancer or switch load balancer. It means the correct port wasn't provided. + // nodeCIDR->serviceCIDR traffic flow is internal and it shouldn't be carried to outside the cluster + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=115, in_port=%s, %s, %s_dst=%s,"+ + "actions=drop", nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR)) + } + } + + // table 0, add IP fragment reassembly flows, only needed in SGW mode with + // physical interface attached to bridge + if config.Gateway.Mode == config.GatewayModeShared && ofPortPhys != "" { + reassemblyFlows := generateIPFragmentReassemblyFlow(ofPortPhys) + dftFlows = append(dftFlows, reassemblyFlows...) + } + if ofPortPhys != "" { + for _, netConfig := range bridge.PatchedNetConfigs() { + var actions string + if config.Gateway.Mode != config.GatewayModeLocal || config.Gateway.DisablePacketMTUCheck { + actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) + } else { + // packets larger than known acceptable MTU need to go to kernel for + // potential fragmentation + // introduced specifically for replies to egress traffic not routed + // through the host + actions = fmt.Sprintf("check_pkt_larger(%d)->reg0[0],resubmit(,11)", maxPktLength) + } + + if config.IPv4Mode { + // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+est, ct_mark=%s, "+ + "actions=%s", nodetypes.DefaultOpenFlowCookie, netConfig.MasqCTMark, actions)) + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+rel, ct_mark=%s, "+ + "actions=%s", nodetypes.DefaultOpenFlowCookie, netConfig.MasqCTMark, actions)) + + } + + if config.IPv6Mode { + // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+est, ct_mark=%s, "+ + "actions=%s", nodetypes.DefaultOpenFlowCookie, netConfig.MasqCTMark, actions)) + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+rel, ct_mark=%s, "+ + "actions=%s", nodetypes.DefaultOpenFlowCookie, netConfig.MasqCTMark, actions)) + } + } + if config.IPv4Mode { + // table 1, established and related connections in zone 64000 with ct_mark CtMarkHost go to host + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip, ct_state=+trk+est, ct_mark=%s, "+ + "actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, nodetypes.CtMarkHost, strip_vlan, ofPortHost)) + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip, ct_state=+trk+rel, ct_mark=%s, "+ + "actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, nodetypes.CtMarkHost, strip_vlan, ofPortHost)) + + } + if config.IPv6Mode { + // table 1, established and related connections in zone 64000 with ct_mark CtMarkHost go to host + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip6, ct_state=+trk+est, ct_mark=%s, "+ + "actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, nodetypes.CtMarkHost, strip_vlan, ofPortHost)) + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip6, ct_state=+trk+rel, ct_mark=%s, "+ + "actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, nodetypes.CtMarkHost, strip_vlan, ofPortHost)) + + } + + // table 1, we check to see if this dest mac is the shared mac, if so send to host + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=10, table=1, %s dl_dst=%s, actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) + } + + defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] + + // table 2, dispatch from Host -> OVN + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, table=2, "+ + "actions=set_field:%s->eth_dst,%soutput:%s", nodetypes.DefaultOpenFlowCookie, + bridgeMacAddress, mod_vlan_id, defaultNetConfig.OfPortPatch)) + + // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have + // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. + if config.IPv4Mode { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { + continue + } + srcIPOrSubnet := netConfig.V4MasqIPs.ManagementPort.IP.String() + if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { + var udnAdvertisedSubnets []*net.IPNet + for _, clusterEntry := range netConfig.Subnets { + udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) + } + // Filter subnets based on the clusterIP service family + // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one + matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) + if err != nil { + klog.Infof("Unable to determine IPV4 UDN subnet for the provided family isIPV6: %v", err) + continue + } + + // Use the filtered subnets for the flow compute instead of the masqueradeIP + srcIPOrSubnet = matchingIPFamilySubnet.String() + } + // Drop traffic coming from the masquerade IP or the UDN subnet(for advertised UDNs) to ensure that + // isolation between networks is enforced. This handles the case where a pod on the UDN subnet is sending traffic to + // a service in another UDN. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+ + "actions=drop", + nodetypes.DefaultOpenFlowCookie, srcIPOrSubnet)) + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+ + "actions=set_field:%s->eth_dst,output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.PktMark, + bridgeMacAddress, netConfig.OfPortPatch)) + } + } + + if config.IPv6Mode { + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.IsDefaultNetwork() { + continue + } + srcIPOrSubnet := netConfig.V6MasqIPs.ManagementPort.IP.String() + if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { + var udnAdvertisedSubnets []*net.IPNet + for _, clusterEntry := range netConfig.Subnets { + udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) + } + // Filter subnets based on the clusterIP service family + // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one + matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(true, udnAdvertisedSubnets) + if err != nil { + klog.Infof("Unable to determine IPV6 UDN subnet for the provided family isIPV6: %v", err) + continue + } + + // Use the filtered subnets for the flow compute instead of the masqueradeIP + srcIPOrSubnet = matchingIPFamilySubnet.String() + } + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+ + "actions=drop", + nodetypes.DefaultOpenFlowCookie, srcIPOrSubnet)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+ + "actions=set_field:%s->eth_dst,output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.PktMark, + bridgeMacAddress, netConfig.OfPortPatch)) + } + } + + // table 3, dispatch from OVN -> Host + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, table=3, %s "+ + "actions=move:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[],set_field:%s->eth_dst,%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) + + // table 4, hairpinned pkts that need to go from OVN -> Host + // We need to SNAT and masquerade OVN GR IP, send to table 3 for dispatch to Host + if config.IPv4Mode { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, table=4,ip,"+ + "actions=ct(commit,zone=%d,nat(src=%s),table=3)", + nodetypes.DefaultOpenFlowCookie, config.Default.OVNMasqConntrackZone, config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP.String())) + } + if config.IPv6Mode { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, table=4,ipv6, "+ + "actions=ct(commit,zone=%d,nat(src=%s),table=3)", + nodetypes.DefaultOpenFlowCookie, config.Default.OVNMasqConntrackZone, config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP.String())) + } + // table 5, Host Reply traffic to hairpinned svc, need to unDNAT, send to table 2 + if config.IPv4Mode { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, table=5, ip, "+ + "actions=ct(commit,zone=%d,nat,table=2)", + nodetypes.DefaultOpenFlowCookie, config.Default.HostMasqConntrackZone)) + } + if config.IPv6Mode { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, table=5, ipv6, "+ + "actions=ct(commit,zone=%d,nat,table=2)", + nodetypes.DefaultOpenFlowCookie, config.Default.HostMasqConntrackZone)) + } + return dftFlows, nil +} + +// getMaxFrameLength returns the maximum frame size (ignoring VLAN header) that a gateway can handle +func getMaxFrameLength() int { + return config.Default.MTU + 14 +} + +// generateIPFragmentReassemblyFlow adds flows in table 0 that send packets to a +// specific conntrack zone for reassembly with the same priority as node port +// flows that match on L4 fields. After reassembly packets are reinjected to +// table 0 again. This requires a conntrack immplementation that reassembles +// fragments. This reqreuiment is met for the kernel datapath with the netfilter +// module loaded. This reqreuiment is not met for the userspace datapath. +func generateIPFragmentReassemblyFlow(ofPortPhys string) []string { + flows := make([]string, 0, 2) + if config.IPv4Mode { + flows = append(flows, + fmt.Sprintf("cookie=%s, priority=110, table=0, in_port=%s, ip, nw_frag=yes, actions=ct(table=0,zone=%d)", + nodetypes.DefaultOpenFlowCookie, + ofPortPhys, + config.Default.ReassemblyConntrackZone, + ), + ) + } + if config.IPv6Mode { + flows = append(flows, + fmt.Sprintf("cookie=%s, priority=110, table=0, in_port=%s, ipv6, nw_frag=yes, actions=ct(table=0,zone=%d)", + nodetypes.DefaultOpenFlowCookie, + ofPortPhys, + config.Default.ReassemblyConntrackZone, + ), + ) + } + + return flows +} + +func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]string, error) { + // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure + // that dl_src is included in match criteria! + ofPortPhys := bridge.OfPortPhys + bridgeMacAddress := bridge.MacAddress.String() + ofPortHost := bridge.OfPortHost + bridgeIPs := bridge.Ips + + var dftFlows []string + + strip_vlan := "" + match_vlan := "" + mod_vlan_id := "" + if config.Gateway.VLANID != 0 { + strip_vlan = "strip_vlan," + match_vlan = fmt.Sprintf("dl_vlan=%d,", config.Gateway.VLANID) + mod_vlan_id = fmt.Sprintf("mod_vlan_vid:%d,", config.Gateway.VLANID) + } + + if ofPortPhys != "" { + // table 0, we check to see if this dest mac is the shared mac, if so flood to all ports + actions := "" + for _, netConfig := range bridge.PatchedNetConfigs() { + actions += "output:" + netConfig.OfPortPatch + "," + } + + actions += strip_vlan + "output:" + ofPortHost + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=10, table=0, %s dl_dst=%s, actions=%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, actions)) + } + + // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all + // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). + for _, netConfig := range bridge.PatchedNetConfigs() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=9, table=0, in_port=%s, actions=drop", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch)) + } + + if config.IPv4Mode { + physicalIP, err := util.MatchFirstIPNetFamily(false, bridgeIPs) + if err != nil { + return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) + } + if ofPortPhys != "" { + for _, netConfig := range bridge.PatchedNetConfigs() { + // table0, packets coming from egressIP pods that have mark 1008 on them + // will be SNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR + // SNATs these into egressIP prior to reaching external bridge. + // egressService pods will also undergo this SNAT to nodeIP since these features are tied + // together at the OVN policy level on the distributed router. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%s "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, nodetypes.OvnKubeNodeSNATMark, + config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) + + // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to + // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. + if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && + config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { + for mark, eip := range bridge.EipMarkIPs.GetIPv4() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%d, "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, + config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) + } + } + } + + // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN + // so that reverse direction goes back to the pods. + if netConfig.IsDefaultNetwork() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, "+ + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, + netConfig.MasqCTMark, ofPortPhys)) + + // Allow (a) OVN->host traffic on the same node + // (b) host->host traffic on the same node + if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { + dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...) + } + } else { + // for UDN we additionally SNAT the packet from masquerade IP -> node IP + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, ip_src=%s, "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V4MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, + physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) + } + } + + // table 0, packets coming from host Commit connections with ct_mark CtMarkHost + // so that reverse direction goes back to the host. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, ip, "+ + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), %soutput:%s", + nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, nodetypes.CtMarkHost, mod_vlan_id, ofPortPhys)) + } + if config.Gateway.Mode == config.GatewayModeLocal { + for _, netConfig := range bridge.PatchedNetConfigs() { + // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. + // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp, nw_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp, nw_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp, nw_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + // We send BFD traffic coming from OVN to outside directly using a higher priority flow + if ofPortPhys != "" { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp, tp_dst=3784, actions=output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) + } + } + } + + if ofPortPhys != "" { + // table 0, packets coming from external or other localnet ports. Send it through conntrack and + // resubmit to table 1 to know the state and mark of the connection. + // Note, there are higher priority rules that take care of traffic coming from LOCAL and OVN ports. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=50, ip, actions=ct(zone=%d, nat, table=1)", + nodetypes.DefaultOpenFlowCookie, config.Default.ConntrackZone)) + } + } + + if config.IPv6Mode { + physicalIP, err := util.MatchFirstIPNetFamily(true, bridgeIPs) + if err != nil { + return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) + } + if ofPortPhys != "" { + for _, netConfig := range bridge.PatchedNetConfigs() { + // table0, packets coming from egressIP pods that have mark 1008 on them + // will be DNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR + // DNATs these into egressIP prior to reaching external bridge. + // egressService pods will also undergo this SNAT to nodeIP since these features are tied + // together at the OVN policy level on the distributed router. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%s "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, nodetypes.OvnKubeNodeSNATMark, + config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) + + // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to + // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. + if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && + config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { + for mark, eip := range bridge.EipMarkIPs.GetIPv6() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%d, "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, + config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) + } + } + } + + // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN + // so that reverse direction goes back to the pods. + if netConfig.IsDefaultNetwork() { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, "+ + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.MasqCTMark, ofPortPhys)) + + // Allow (a) OVN->host traffic on the same node + // (b) host->host traffic on the same node + if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { + dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...) + } + } else { + // for UDN we additionally SNAT the packet from masquerade IP -> node IP + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, ipv6_src=%s, "+ + "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V6MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, + physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) + } + } + + // table 0, packets coming from host. Commit connections with ct_mark CtMarkHost + // so that reverse direction goes back to the host. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=100, in_port=%s, ipv6, "+ + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), %soutput:%s", + nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, nodetypes.CtMarkHost, mod_vlan_id, ofPortPhys)) + + } + if config.Gateway.Mode == config.GatewayModeLocal { + for _, netConfig := range bridge.PatchedNetConfigs() { + // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. + // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp6, ipv6_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp6, ipv6_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp6, ipv6_src=%s, "+ + "actions=ct(table=4,zone=%d)", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) + if ofPortPhys != "" { + // We send BFD traffic coming from OVN to outside directly using a higher priority flow + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp6, tp_dst=3784, actions=output:%s", + nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) + } + } + } + if ofPortPhys != "" { + // table 0, packets coming from external. Send it through conntrack and + // resubmit to table 1 to know the state and mark of the connection. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=50, in_port=%s, ipv6, "+ + "actions=ct(zone=%d, nat, table=1)", nodetypes.DefaultOpenFlowCookie, ofPortPhys, config.Default.ConntrackZone)) + } + } + // Egress IP is often configured on a node different from the one hosting the affected pod. + // Due to the fact that ovn-controllers on different nodes apply the changes independently, + // there is a chance that the pod traffic will reach the egress node before it configures the SNAT flows. + // Drop pod traffic that is not SNATed, excluding local pods(required for ICNIv2) + defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] + if config.OVNKubernetesFeature.EnableEgressIP { + for _, clusterEntry := range config.Default.ClusterSubnets { + cidr := clusterEntry.CIDR + ipv := getIPv(cidr) + // table 0, drop packets coming from pods headed externally that were not SNATed. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=104, in_port=%s, %s, %s_src=%s, actions=drop", + nodetypes.DefaultOpenFlowCookie, defaultNetConfig.OfPortPatch, ipv, ipv, cidr)) + } + for _, subnet := range defaultNetConfig.NodeSubnets { + ipv := getIPv(subnet) + if ofPortPhys != "" { + // table 0, commit connections from local pods. + // ICNIv2 requires that local pod traffic can leave the node without SNAT. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=109, in_port=%s, dl_src=%s, %s, %s_src=%s"+ + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", + nodetypes.DefaultOpenFlowCookie, defaultNetConfig.OfPortPatch, bridgeMacAddress, ipv, ipv, subnet, + config.Default.ConntrackZone, nodetypes.CtMarkOVN, ofPortPhys)) + } + } + } + + if ofPortPhys != "" { + for _, netConfig := range bridge.PatchedNetConfigs() { + isNetworkAdvertised := netConfig.Advertised.Load() + // disableSNATMultipleGWs only applies to default network + disableSNATMultipleGWs := netConfig.IsDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs + if !disableSNATMultipleGWs && !isNetworkAdvertised { + continue + } + output := netConfig.OfPortPatch + if isNetworkAdvertised && config.Gateway.Mode == config.GatewayModeLocal { + // except if advertised through BGP, go to kernel + // TODO: MEG enabled pods should still go through the patch port + // but holding this until + // https://issues.redhat.com/browse/FDP-646 is fixed, for now we + // are assuming MEG & BGP are not used together + output = nodetypes.OvsLocalPort + } + for _, clusterEntry := range netConfig.Subnets { + cidr := clusterEntry.CIDR + ipv := getIPv(cidr) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=15, table=1, %s, %s_dst=%s, "+ + "actions=output:%s", + nodetypes.DefaultOpenFlowCookie, ipv, ipv, cidr, output)) + } + if output == netConfig.OfPortPatch { + // except node management traffic + for _, subnet := range netConfig.NodeSubnets { + mgmtIP := util.GetNodeManagementIfAddr(subnet) + ipv := getIPv(mgmtIP) + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=16, table=1, %s, %s_dst=%s, "+ + "actions=output:%s", + nodetypes.DefaultOpenFlowCookie, ipv, ipv, mgmtIP.IP, nodetypes.OvsLocalPort), + ) + } + } + } + + // table 1, we check to see if this dest mac is the shared mac, if so send to host + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=10, table=1, %s dl_dst=%s, actions=%soutput:%s", + nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) + + if config.IPv6Mode { + // REMOVEME(trozet) when https://bugzilla.kernel.org/show_bug.cgi?id=11797 is resolved + // must flood icmpv6 Route Advertisement and Neighbor Advertisement traffic as it fails to create a CT entry + for _, icmpType := range []int{types.RouteAdvertisementICMPType, types.NeighborAdvertisementICMPType} { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=14, table=1,icmp6,icmpv6_type=%d actions=FLOOD", + nodetypes.DefaultOpenFlowCookie, icmpType)) + } + if ofPortPhys != "" { + // We send BFD traffic both on the host and in ovn + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp6, tp_dst=3784, actions=output:%s,output:%s", + nodetypes.DefaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) + } + } + + if config.IPv4Mode { + if ofPortPhys != "" { + // We send BFD traffic both on the host and in ovn + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp, tp_dst=3784, actions=output:%s,output:%s", + nodetypes.DefaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) + } + } + + // packets larger than known acceptable MTU need to go to kernel for + // potential fragmentation + // introduced specifically for replies to egress traffic not routed + // through the host + if config.Gateway.Mode == config.GatewayModeLocal && !config.Gateway.DisablePacketMTUCheck { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=10, table=11, reg0=0x1, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortHost)) + + // Send UDN destined traffic to right patch port + for _, netConfig := range bridge.PatchedNetConfigs() { + if netConfig.MasqCTMark != nodetypes.CtMarkOVN { + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=5, table=11, ct_mark=%s, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, netConfig.MasqCTMark, netConfig.OfPortPatch)) + } + } + + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=1, table=11, "+ + "actions=output:%s", nodetypes.DefaultOpenFlowCookie, defaultNetConfig.OfPortPatch)) + } + + // table 1, all other connections do normal processing + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=0, table=1, actions=output:NORMAL", nodetypes.DefaultOpenFlowCookie)) + } + + return dftFlows, nil +} + +func PmtudDropFlows(bridge *BridgeConfiguration, ipAddrs []string) []string { + var flows []string + if config.Gateway.Mode != config.GatewayModeShared { + return nil + } + for _, addr := range ipAddrs { + for _, netConfig := range bridge.PatchedNetConfigs() { + flows = append(flows, + nodeutil.GenerateICMPFragmentationFlow(addr, nodetypes.OutputPortDrop, netConfig.OfPortPatch, nodetypes.PmtudOpenFlowCookie, 700)) + } + } + + return flows +} + +func getIPv(ipnet *net.IPNet) string { + prefix := "ip" + if utilnet.IsIPv6CIDR(ipnet) { + prefix = "ipv6" + } + return prefix +} + +// hostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic: +// a. from pods in the OVN network to pods in a localnet network, on the same node +// b. from pods on the host to pods in a localnet network, on the same node +// when the localnet is mapped to breth0. +// The expected srcMAC is the MAC address of breth0 and the expected hostSubnets is the host subnets found on the node +// primary interface. +func hostNetworkNormalActionFlows(netConfig *BridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { + var flows []string + var ipFamily, ipFamilyDest string + + if isV6 { + ipFamily = "ipv6" + ipFamilyDest = "ipv6_dst" + } else { + ipFamily = "ip" + ipFamilyDest = "nw_dst" + } + + formatFlow := func(inPort, destIP, ctMark string) string { + // Matching IP traffic will be handled by the bridge instead of being output directly + // to the NIC by the existing flow at prio=100. + flowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, " + + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" + return fmt.Sprintf(flowTemplate, + nodetypes.DefaultOpenFlowCookie, + inPort, + srcMAC, + ipFamily, + ipFamilyDest, + destIP, + config.Default.ConntrackZone, + ctMark) + } + + // Traffic path (a): OVN->localnet for shared gw mode + if config.Gateway.Mode == config.GatewayModeShared { + for _, hostSubnet := range hostSubnets { + if utilnet.IsIPv6(hostSubnet.IP) != isV6 { + continue + } + flows = append(flows, formatFlow(netConfig.OfPortPatch, hostSubnet.String(), netConfig.MasqCTMark)) + } + } + + // Traffic path (a): OVN->localnet for local gw mode + // Traffic path (b): host->localnet for both gw modes + for _, hostSubnet := range hostSubnets { + if utilnet.IsIPv6(hostSubnet.IP) != isV6 { + continue + } + flows = append(flows, formatFlow(nodetypes.OvsLocalPort, hostSubnet.String(), nodetypes.CtMarkHost)) + } + + if isV6 { + // IPv6 neighbor discovery uses ICMPv6 messages sent to a special destination (ff02::1:ff00:0/104) + // that is unrelated to the host subnets matched in the prio=102 flow above. + // Allow neighbor discovery by matching against ICMP type and ingress port. + formatICMPFlow := func(inPort, ctMark string, icmpType int) string { + icmpFlowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, " + + "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" + return fmt.Sprintf(icmpFlowTemplate, + nodetypes.DefaultOpenFlowCookie, + inPort, + srcMAC, + icmpType, + config.Default.ConntrackZone, + ctMark) + } + + for _, icmpType := range []int{types.NeighborSolicitationICMPType, types.NeighborAdvertisementICMPType} { + // Traffic path (a) for ICMP: OVN-> localnet for shared gw mode + if config.Gateway.Mode == config.GatewayModeShared { + flows = append(flows, + formatICMPFlow(netConfig.OfPortPatch, netConfig.MasqCTMark, icmpType)) + } + + // Traffic path (a) for ICMP: OVN->localnet for local gw mode + // Traffic path (b) for ICMP: host->localnet for both gw modes + flows = append(flows, formatICMPFlow(nodetypes.OvsLocalPort, nodetypes.CtMarkHost, icmpType)) + } + } + return flows +} diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index 51dc1571e1..a2bdf34e50 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -45,6 +45,7 @@ import ( nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/ovspinning" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/controller/apbroute" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/healthcheck" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry" @@ -1320,7 +1321,7 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { if config.OVNKubernetesFeature.EnableEgressService { wf := nc.watchFactory.(*factory.WatchFactory) - c, err := egressservice.NewController(nc.stopChan, ovnKubeNodeSNATMark, nc.name, + c, err := egressservice.NewController(nc.stopChan, nodetypes.OvnKubeNodeSNATMark, nc.name, wf.EgressServiceInformer(), wf.ServiceInformer(), wf.EndpointSliceInformer()) if err != nil { return err diff --git a/go-controller/pkg/node/egress_service_test.go b/go-controller/pkg/node/egress_service_test.go index bb4e57f5ca..ca44ac311d 100644 --- a/go-controller/pkg/node/egress_service_test.go +++ b/go-controller/pkg/node/egress_service_test.go @@ -19,6 +19,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/controllers/egressservice" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing" util "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/mocks" @@ -299,7 +300,7 @@ var _ = Describe("Egress Service Operations", func() { c, err := egressservice.NewController( stopChan, - ovnKubeNodeSNATMark, + nodetypes.OvnKubeNodeSNATMark, "node", wf.EgressServiceInformer(), wf.ServiceInformer(), @@ -405,7 +406,7 @@ add element inet ovn-kubernetes egress-service-snat-v4 { 10.128.0.4 comment "nam c, err := egressservice.NewController( stopChan, - ovnKubeNodeSNATMark, + nodetypes.OvnKubeNodeSNATMark, "node", wf.EgressServiceInformer(), wf.ServiceInformer(), @@ -610,7 +611,7 @@ add element inet ovn-kubernetes egress-service-snat-v4 { 10.128.0.3 comment "nam c, err := egressservice.NewController( stopChan, - ovnKubeNodeSNATMark, + nodetypes.OvnKubeNodeSNATMark, "node", wf.EgressServiceInformer(), wf.ServiceInformer(), @@ -805,7 +806,7 @@ add element inet ovn-kubernetes egress-service-snat-v4 { 10.128.0.11 comment "na c, err := egressservice.NewController( stopChan, - ovnKubeNodeSNATMark, + nodetypes.OvnKubeNodeSNATMark, "node", wf.EgressServiceInformer(), wf.ServiceInformer(), @@ -964,7 +965,7 @@ add element inet ovn-kubernetes egress-service-snat-v4 { 10.128.0.11 comment "na c, err := egressservice.NewController( stopChan, - ovnKubeNodeSNATMark, + nodetypes.OvnKubeNodeSNATMark, "node", wf.EgressServiceInformer(), wf.ServiceInformer(), diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 948da997d5..a476783537 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -460,11 +460,6 @@ func (g *gateway) GetGatewayIface() string { return g.openflowManager.defaultBridge.GetGatewayIface() } -// getMaxFrameLength returns the maximum frame size (ignoring VLAN header) that a gateway can handle -func getMaxFrameLength() int { - return config.Default.MTU + 14 -} - // SetDefaultGatewayBridgeMAC updates the mac address for the OFM used to render flows with func (g *gateway) SetDefaultGatewayBridgeMAC(macAddr net.HardwareAddr) { g.openflowManager.setDefaultBridgeMAC(macAddr) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index fb902c589c..8dfe97a3f1 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -34,28 +34,17 @@ import ( nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" + nodeutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/util" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" ) const ( - // defaultOpenFlowCookie identifies default open flow rules added to the host OVS bridge. - // The hex number 0xdeff105, aka defflos, is meant to sound like default flows. - defaultOpenFlowCookie = "0xdeff105" // etpSvcOpenFlowCookie identifies constant open flow rules added to the host OVS // bridge to move packets between host and external for etp=local traffic. // The hex number 0xe745ecf105, represents etp(e74)-service(5ec)-flows which makes it easier for debugging. etpSvcOpenFlowCookie = "0xe745ecf105" - // pmtudOpenFlowCookie identifies the flows used to drop ICMP type (3) destination unreachable, - // fragmentation-needed (4) - pmtudOpenFlowCookie = "0x0304" - - // ctMarkHost is the conntrack mark value for host traffic - ctMarkHost = "0x2" - // ovnKubeNodeSNATMark is used to mark packets that need to be SNAT-ed to nodeIP for - // traffic originating from egressIP and egressService controlled pods towards other nodes in the cluster. - ovnKubeNodeSNATMark = "0x3f0" // nftablesUDNServicePreroutingChain is a base chain registered into the prerouting hook, // and it contains one rule that jumps to nftablesUDNServiceMarkChain. @@ -92,10 +81,6 @@ const ( // to the appropriate network. nftablesUDNMarkExternalIPsV4Map = "udn-mark-external-ips-v4" nftablesUDNMarkExternalIPsV6Map = "udn-mark-external-ips-v6" - - // outputPortDrop is used to signify that there is no output port for an openflow action and the - // rendered action should result in a drop - outputPortDrop = "output-port-drop" ) // configureUDNServicesNFTables configures the nftables chains, rules, and verdict maps @@ -426,7 +411,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // This flow is used for UDNs and advertised UDNs to be able to reach kapi and dns services alone on default network flows := []string{fmt.Sprintf("cookie=%s, priority=300, table=2, %s, %s_dst=%s, "+ "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, ipPrefix, ipPrefix, service.Spec.ClusterIP, + nodetypes.DefaultOpenFlowCookie, ipPrefix, ipPrefix, service.Spec.ClusterIP, npw.ofm.getDefaultBridgeMAC().String(), defaultNetConfig.OfPortPatch)} if util.IsRouteAdvertisementsEnabled() { // if the network is advertised, then for the reply from kapi and dns services to go back @@ -440,7 +425,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI // sample flow for non-advertised UDNs: cookie=0xdeff105, duration=684.087s, table=0, n_packets=0, n_bytes=0, // idle_age=684, priority=500,ip,in_port=2,nw_src=10.96.0.0/16,nw_dst=169.254.0.0/17 actions=ct(table=3,zone=64001,nat) flows = append(flows, fmt.Sprintf("cookie=%s, priority=490, in_port=%s, ip, ip_src=%s,actions=ct(zone=%d,nat,table=3)", - defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, service.Spec.ClusterIP, config.Default.HostMasqConntrackZone)) + nodetypes.DefaultOpenFlowCookie, defaultNetConfig.OfPortPatch, service.Spec.ClusterIP, config.Default.HostMasqConntrackZone)) } npw.ofm.updateFlowCacheEntry(key, flows) } @@ -535,7 +520,7 @@ func (npw *nodePortWatcher) createLbAndExternalSvcFlows(service *corev1.Service, etpSvcOpenFlowCookie, npw.ofportPhys)) } else if config.Gateway.Mode == config.GatewayModeShared { // add the ICMP Fragmentation flow for shared gateway mode. - icmpFlow := generateICMPFragmentationFlow(externalIPOrLBIngressIP, netConfig.OfPortPatch, npw.ofportPhys, cookie, 110) + icmpFlow := nodeutil.GenerateICMPFragmentationFlow(externalIPOrLBIngressIP, netConfig.OfPortPatch, npw.ofportPhys, cookie, 110) externalIPFlows = append(externalIPFlows, icmpFlow) // case2 (see function description for details) externalIPFlows = append(externalIPFlows, @@ -601,31 +586,6 @@ func (npw *nodePortWatcher) generateARPBypassFlow(ofPorts []string, ofPortPatch, return arpFlow } -func generateICMPFragmentationFlow(ipAddr, outputPort, inPort, cookie string, priority int) string { - // we send any ICMP destination unreachable, fragmentation needed to the OVN pipeline too so that - // path MTU discovery continues to work. - icmpMatch := "icmp" - icmpType := 3 - icmpCode := 4 - nwDst := "nw_dst" - if utilnet.IsIPv6String(ipAddr) { - icmpMatch = "icmp6" - icmpType = 2 - icmpCode = 0 - nwDst = "ipv6_dst" - } - - action := fmt.Sprintf("output:%s", outputPort) - if outputPort == outputPortDrop { - action = "drop" - } - - icmpFragmentationFlow := fmt.Sprintf("cookie=%s, priority=%d, in_port=%s, %s, %s=%s, icmp_type=%d, "+ - "icmp_code=%d, actions=%s", - cookie, priority, inPort, icmpMatch, nwDst, ipAddr, icmpType, icmpCode, action) - return icmpFragmentationFlow -} - // getAndDeleteServiceInfo returns the serviceConfig for a service and if it exists and then deletes the entry func (npw *nodePortWatcher) getAndDeleteServiceInfo(index ktypes.NamespacedName) (out *serviceConfig, exists bool) { npw.serviceInfoLock.Lock() @@ -1449,894 +1409,6 @@ func (npwipt *nodePortWatcherIptables) SyncServices(services []interface{}) erro return utilerrors.Join(errors...) } -func flowsForDefaultBridge(bridge *bridgeconfig.BridgeConfiguration, extraIPs []net.IP) ([]string, error) { - // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure - // that dl_src is included in match criteria! - - ofPortPhys := bridge.OfPortPhys - bridgeMacAddress := bridge.MacAddress.String() - ofPortHost := bridge.OfPortHost - bridgeIPs := bridge.Ips - - var dftFlows []string - // 14 bytes of overhead for ethernet header (does not include VLAN) - maxPktLength := getMaxFrameLength() - - strip_vlan := "" - mod_vlan_id := "" - match_vlan := "" - if config.Gateway.VLANID != 0 { - strip_vlan = "strip_vlan," - match_vlan = fmt.Sprintf("dl_vlan=%d,", config.Gateway.VLANID) - mod_vlan_id = fmt.Sprintf("mod_vlan_vid:%d,", config.Gateway.VLANID) - } - - if config.IPv4Mode { - // table0, Geneve packets coming from external. Skip conntrack and go directly to host - // if dest mac is the shared mac send directly to host. - if ofPortPhys != "" { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=205, in_port=%s, dl_dst=%s, udp, udp_dst=%d, "+ - "actions=output:%s", defaultOpenFlowCookie, ofPortPhys, bridgeMacAddress, config.Default.EncapPort, - ofPortHost)) - // perform NORMAL action otherwise. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp, udp_dst=%d, "+ - "actions=NORMAL", defaultOpenFlowCookie, ofPortPhys, config.Default.EncapPort)) - - // table0, Geneve packets coming from LOCAL/Host OFPort. Skip conntrack and go directly to external - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp, udp_dst=%d, "+ - "actions=output:%s", defaultOpenFlowCookie, ofPortHost, config.Default.EncapPort, ofPortPhys)) - } - physicalIP, err := util.MatchFirstIPNetFamily(false, bridgeIPs) - if err != nil { - return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) - } - for _, netConfig := range bridge.PatchedNetConfigs() { - // table 0, SVC Hairpin from OVN destined to local host, DNAT and go to table 4 - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ - "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", - defaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String(), physicalIP.IP, - config.Default.HostMasqConntrackZone, physicalIP.IP)) - } - - // table 0, hairpin from OVN destined to local host (but an additional node IP), send to table 4 - for _, ip := range extraIPs { - if ip.To4() == nil { - continue - } - // not needed for the physical IP - if ip.Equal(physicalIP.IP) { - continue - } - - // not needed for special masquerade IP - if ip.Equal(config.Gateway.MasqueradeIPs.V4HostMasqueradeIP) { - continue - } - - for _, netConfig := range bridge.PatchedNetConfigs() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ - "actions=ct(commit,zone=%d,table=4)", - defaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, - config.Default.HostMasqConntrackZone)) - } - } - - // table 0, Reply SVC traffic from Host -> OVN, unSNAT and goto table 5 - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s,"+ - "actions=ct(zone=%d,nat,table=5)", - defaultOpenFlowCookie, ofPortHost, config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP.String(), config.Default.OVNMasqConntrackZone)) - } - if config.IPv6Mode { - if ofPortPhys != "" { - // table0, Geneve packets coming from external. Skip conntrack and go directly to host - // if dest mac is the shared mac send directly to host. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=205, in_port=%s, dl_dst=%s, udp6, udp_dst=%d, "+ - "actions=output:%s", defaultOpenFlowCookie, ofPortPhys, bridgeMacAddress, config.Default.EncapPort, - ofPortHost)) - // perform NORMAL action otherwise. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp6, udp_dst=%d, "+ - "actions=NORMAL", defaultOpenFlowCookie, ofPortPhys, config.Default.EncapPort)) - - // table0, Geneve packets coming from LOCAL. Skip conntrack and send to external - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, in_port=%s, udp6, udp_dst=%d, "+ - "actions=output:%s", defaultOpenFlowCookie, nodetypes.OvsLocalPort, config.Default.EncapPort, ofPortPhys)) - } - - physicalIP, err := util.MatchFirstIPNetFamily(true, bridgeIPs) - if err != nil { - return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) - } - // table 0, SVC Hairpin from OVN destined to local host, DNAT to host, send to table 4 - for _, netConfig := range bridge.PatchedNetConfigs() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ - "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", - defaultOpenFlowCookie, netConfig.OfPortPatch, config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String(), physicalIP.IP, - config.Default.HostMasqConntrackZone, physicalIP.IP)) - } - - // table 0, hairpin from OVN destined to local host (but an additional node IP), send to table 4 - for _, ip := range extraIPs { - if ip.To4() != nil { - continue - } - // not needed for the physical IP - if ip.Equal(physicalIP.IP) { - continue - } - - // not needed for special masquerade IP - if ip.Equal(config.Gateway.MasqueradeIPs.V6HostMasqueradeIP) { - continue - } - - for _, netConfig := range bridge.PatchedNetConfigs() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ - "actions=ct(commit,zone=%d,table=4)", - defaultOpenFlowCookie, netConfig.OfPortPatch, ip.String(), physicalIP.IP, - config.Default.HostMasqConntrackZone)) - } - } - - // table 0, Reply SVC traffic from Host -> OVN, unSNAT and goto table 5 - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s,"+ - "actions=ct(zone=%d,nat,table=5)", - defaultOpenFlowCookie, ofPortHost, config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP.String(), config.Default.OVNMasqConntrackZone)) - } - - var protoPrefix, masqIP, masqSubnet string - - // table 0, packets coming from Host -> Service - for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { - if utilnet.IsIPv4CIDR(svcCIDR) { - protoPrefix = "ip" - masqIP = config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String() - masqSubnet = config.Gateway.V4MasqueradeSubnet - } else { - protoPrefix = "ipv6" - masqIP = config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String() - masqSubnet = config.Gateway.V6MasqueradeSubnet - } - - // table 0, Host (default network) -> OVN towards SVC, SNAT to special IP. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_dst=%s, "+ - "actions=ct(commit,zone=%d,nat(src=%s),table=2)", - defaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, - svcCIDR, config.Default.HostMasqConntrackZone, masqIP)) - - if util.IsNetworkSegmentationSupportEnabled() { - // table 0, Host (UDNs) -> OVN towards SVC, SNAT to special IP. - // For packets originating from UDN, commit without NATing, those - // have already been SNATed to the masq IP of the UDN. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, "+ - "actions=ct(commit,zone=%d,table=2)", - defaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, - masqSubnet, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) - if util.IsRouteAdvertisementsEnabled() { - // If the UDN is advertised then instead of matching on the masqSubnet - // we match on the UDNPodSubnet itself and we also don't SNAT to 169.254.0.2 - // sample flow: cookie=0xdeff105, duration=1472.742s, table=0, n_packets=9, n_bytes=666, priority=550 - // ip,in_port=LOCAL,nw_src=103.103.0.0/16,nw_dst=10.96.0.0/16 actions=ct(commit,table=2,zone=64001) - for _, netConfig := range bridge.PatchedNetConfigs() { - if netConfig.IsDefaultNetwork() { - continue - } - if netConfig.Advertised.Load() { - var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.Subnets { - udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) - } - // Filter subnets based on the clusterIP service family - // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one - matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(svcCIDR), udnAdvertisedSubnets) - if err != nil { - klog.Infof("Unable to determine UDN subnet for the provided family isIPV6: %t, %v", utilnet.IsIPv6CIDR(svcCIDR), err) - continue - } - - // Use the filtered subnet for the flow compute instead of the masqueradeIP - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, "+ - "actions=ct(commit,zone=%d,table=2)", - defaultOpenFlowCookie, ofPortHost, protoPrefix, protoPrefix, - matchingIPFamilySubnet.String(), protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) - } - } - } - } - - masqDst := masqIP - if util.IsNetworkSegmentationSupportEnabled() { - // In UDN match on the whole masquerade subnet to handle replies from UDN enabled services - masqDst = masqSubnet - } - for _, netConfig := range bridge.PatchedNetConfigs() { - // table 0, Reply hairpin traffic to host, coming from OVN, unSNAT - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_src=%s, %s_dst=%s,"+ - "actions=ct(zone=%d,nat,table=3)", - defaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR, - protoPrefix, masqDst, config.Default.HostMasqConntrackZone)) - // table 0, Reply traffic coming from OVN to outside, drop it if the DNAT wasn't done either - // at the GR load balancer or switch load balancer. It means the correct port wasn't provided. - // nodeCIDR->serviceCIDR traffic flow is internal and it shouldn't be carried to outside the cluster - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=115, in_port=%s, %s, %s_dst=%s,"+ - "actions=drop", defaultOpenFlowCookie, netConfig.OfPortPatch, protoPrefix, protoPrefix, svcCIDR)) - } - } - - // table 0, add IP fragment reassembly flows, only needed in SGW mode with - // physical interface attached to bridge - if config.Gateway.Mode == config.GatewayModeShared && ofPortPhys != "" { - reassemblyFlows := generateIPFragmentReassemblyFlow(ofPortPhys) - dftFlows = append(dftFlows, reassemblyFlows...) - } - if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { - var actions string - if config.Gateway.Mode != config.GatewayModeLocal || config.Gateway.DisablePacketMTUCheck { - actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) - } else { - // packets larger than known acceptable MTU need to go to kernel for - // potential fragmentation - // introduced specifically for replies to egress traffic not routed - // through the host - actions = fmt.Sprintf("check_pkt_larger(%d)->reg0[0],resubmit(,11)", maxPktLength) - } - - if config.IPv4Mode { - // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, ip, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) - - } - - if config.IPv6Mode { - // table 1, established and related connections in zone 64000 with ct_mark CtMarkOVN go to OVN - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, ipv6, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%s", defaultOpenFlowCookie, netConfig.MasqCTMark, actions)) - } - } - if config.IPv4Mode { - // table 1, established and related connections in zone 64000 with ct_mark ctMarkHost go to host - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, ctMarkHost, strip_vlan, ofPortHost)) - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, ctMarkHost, strip_vlan, ofPortHost)) - - } - if config.IPv6Mode { - // table 1, established and related connections in zone 64000 with ct_mark ctMarkHost go to host - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip6, ct_state=+trk+est, ct_mark=%s, "+ - "actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, ctMarkHost, strip_vlan, ofPortHost)) - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=1, %s ip6, ct_state=+trk+rel, ct_mark=%s, "+ - "actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, ctMarkHost, strip_vlan, ofPortHost)) - - } - - // table 1, we check to see if this dest mac is the shared mac, if so send to host - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=1, %s dl_dst=%s, actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) - } - - defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] - - // table 2, dispatch from Host -> OVN - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, table=2, "+ - "actions=set_field:%s->eth_dst,%soutput:%s", defaultOpenFlowCookie, - bridgeMacAddress, mod_vlan_id, defaultNetConfig.OfPortPatch)) - - // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have - // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. - if config.IPv4Mode { - for _, netConfig := range bridge.PatchedNetConfigs() { - if netConfig.IsDefaultNetwork() { - continue - } - srcIPOrSubnet := netConfig.V4MasqIPs.ManagementPort.IP.String() - if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { - var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.Subnets { - udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) - } - // Filter subnets based on the clusterIP service family - // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one - matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) - if err != nil { - klog.Infof("Unable to determine IPV4 UDN subnet for the provided family isIPV6: %v", err) - continue - } - - // Use the filtered subnets for the flow compute instead of the masqueradeIP - srcIPOrSubnet = matchingIPFamilySubnet.String() - } - - // Drop traffic coming from the masquerade IP or the UDN subnet(for advertised UDNs) to ensure that - // isolation between networks is enforced. This handles the case where a pod on the UDN subnet is sending traffic to - // a service in another UDN. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+ - "actions=drop", - defaultOpenFlowCookie, srcIPOrSubnet)) - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+ - "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, netConfig.PktMark, - bridgeMacAddress, netConfig.OfPortPatch)) - } - } - - if config.IPv6Mode { - for _, netConfig := range bridge.PatchedNetConfigs() { - if netConfig.IsDefaultNetwork() { - continue - } - srcIPOrSubnet := netConfig.V6MasqIPs.ManagementPort.IP.String() - if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { - var udnAdvertisedSubnets []*net.IPNet - for _, clusterEntry := range netConfig.Subnets { - udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) - } - // Filter subnets based on the clusterIP service family - // NOTE: We don't support more than 1 subnet CIDR of same family type; we only pick the first one - matchingIPFamilySubnet, err := util.MatchFirstIPNetFamily(true, udnAdvertisedSubnets) - if err != nil { - klog.Infof("Unable to determine IPV6 UDN subnet for the provided family isIPV6: %v", err) - continue - } - - // Use the filtered subnets for the flow compute instead of the masqueradeIP - srcIPOrSubnet = matchingIPFamilySubnet.String() - } - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+ - "actions=drop", - defaultOpenFlowCookie, srcIPOrSubnet)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+ - "actions=set_field:%s->eth_dst,output:%s", - defaultOpenFlowCookie, netConfig.PktMark, - bridgeMacAddress, netConfig.OfPortPatch)) - } - } - - // table 3, dispatch from OVN -> Host - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, table=3, %s "+ - "actions=move:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[],set_field:%s->eth_dst,%soutput:%s", - defaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) - - // table 4, hairpinned pkts that need to go from OVN -> Host - // We need to SNAT and masquerade OVN GR IP, send to table 3 for dispatch to Host - if config.IPv4Mode { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, table=4,ip,"+ - "actions=ct(commit,zone=%d,nat(src=%s),table=3)", - defaultOpenFlowCookie, config.Default.OVNMasqConntrackZone, config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP.String())) - } - if config.IPv6Mode { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, table=4,ipv6, "+ - "actions=ct(commit,zone=%d,nat(src=%s),table=3)", - defaultOpenFlowCookie, config.Default.OVNMasqConntrackZone, config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP.String())) - } - // table 5, Host Reply traffic to hairpinned svc, need to unDNAT, send to table 2 - if config.IPv4Mode { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, table=5, ip, "+ - "actions=ct(commit,zone=%d,nat,table=2)", - defaultOpenFlowCookie, config.Default.HostMasqConntrackZone)) - } - if config.IPv6Mode { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, table=5, ipv6, "+ - "actions=ct(commit,zone=%d,nat,table=2)", - defaultOpenFlowCookie, config.Default.HostMasqConntrackZone)) - } - return dftFlows, nil -} - -func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeconfig.BridgeConfiguration) ([]string, error) { - // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure - // that dl_src is included in match criteria! - ofPortPhys := bridge.OfPortPhys - bridgeMacAddress := bridge.MacAddress.String() - ofPortHost := bridge.OfPortHost - bridgeIPs := bridge.Ips - - var dftFlows []string - - strip_vlan := "" - match_vlan := "" - mod_vlan_id := "" - if config.Gateway.VLANID != 0 { - strip_vlan = "strip_vlan," - match_vlan = fmt.Sprintf("dl_vlan=%d,", config.Gateway.VLANID) - mod_vlan_id = fmt.Sprintf("mod_vlan_vid:%d,", config.Gateway.VLANID) - } - - if ofPortPhys != "" { - // table 0, we check to see if this dest mac is the shared mac, if so flood to all ports - actions := "" - for _, netConfig := range bridge.PatchedNetConfigs() { - actions += "output:" + netConfig.OfPortPatch + "," - } - actions += strip_vlan + "NORMAL" - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=0, %s dl_dst=%s, actions=%s", - defaultOpenFlowCookie, match_vlan, bridgeMacAddress, actions)) - } - - // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all - // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). - for _, netConfig := range bridge.PatchedNetConfigs() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=9, table=0, in_port=%s, actions=drop", - defaultOpenFlowCookie, netConfig.OfPortPatch)) - } - - if config.IPv4Mode { - physicalIP, err := util.MatchFirstIPNetFamily(false, bridgeIPs) - if err != nil { - return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) - } - if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { - // table0, packets coming from egressIP pods that have mark 1008 on them - // will be SNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR - // SNATs these into egressIP prior to reaching external bridge. - // egressService pods will also undergo this SNAT to nodeIP since these features are tied - // together at the OVN policy level on the distributed router. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%s "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, - config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) - - // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to - // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. - if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { - if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range bridge.EipMarkIPs.GetIPv4() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%d, "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, - config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) - } - } - } - - // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN - // so that reverse direction goes back to the pods. - if netConfig.IsDefaultNetwork() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, - netConfig.MasqCTMark, ofPortPhys)) - - // Allow (a) OVN->host traffic on the same node - // (b) host->host traffic on the same node - if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { - dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...) - } - } else { - // for UDN we additionally SNAT the packet from masquerade IP -> node IP - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ip, ip_src=%s, "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V4MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, - physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) - } - } - - // table 0, packets coming from host Commit connections with ct_mark ctMarkHost - // so that reverse direction goes back to the host. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, ip, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), %soutput:%s", - defaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, ctMarkHost, mod_vlan_id, ofPortPhys)) - } - if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.PatchedNetConfigs() { - // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. - // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp, nw_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp, nw_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp, nw_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - // We send BFD traffic coming from OVN to outside directly using a higher priority flow - if ofPortPhys != "" { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp, tp_dst=3784, actions=output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) - } - } - } - - if ofPortPhys != "" { - // table 0, packets coming from external or other localnet ports. Send it through conntrack and - // resubmit to table 1 to know the state and mark of the connection. - // Note, there are higher priority rules that take care of traffic coming from LOCAL and OVN ports. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=50, ip, actions=ct(zone=%d, nat, table=1)", - defaultOpenFlowCookie, config.Default.ConntrackZone)) - } - } - - if config.IPv6Mode { - physicalIP, err := util.MatchFirstIPNetFamily(true, bridgeIPs) - if err != nil { - return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) - } - if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { - // table0, packets coming from egressIP pods that have mark 1008 on them - // will be DNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR - // DNATs these into egressIP prior to reaching external bridge. - // egressService pods will also undergo this SNAT to nodeIP since these features are tied - // together at the OVN policy level on the distributed router. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%s "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)),output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ovnKubeNodeSNATMark, - config.Default.ConntrackZone, physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) - - // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to - // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. - if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { - if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range bridge.EipMarkIPs.GetIPv6() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%d, "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, mark, - config.Default.ConntrackZone, eip, netConfig.MasqCTMark, ofPortPhys)) - } - } - } - - // table 0, packets coming from pods headed externally. Commit connections with ct_mark CtMarkOVN - // so that reverse direction goes back to the pods. - if netConfig.IsDefaultNetwork() { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.MasqCTMark, ofPortPhys)) - - // Allow (a) OVN->host traffic on the same node - // (b) host->host traffic on the same node - if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal { - dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...) - } - } else { - // for UDN we additionally SNAT the packet from masquerade IP -> node IP - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, dl_src=%s, ipv6, ipv6_src=%s, "+ - "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, netConfig.V6MasqIPs.GatewayRouter.IP, config.Default.ConntrackZone, - physicalIP.IP, netConfig.MasqCTMark, ofPortPhys)) - } - } - - // table 0, packets coming from host. Commit connections with ct_mark ctMarkHost - // so that reverse direction goes back to the host. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=100, in_port=%s, ipv6, "+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), %soutput:%s", - defaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, ctMarkHost, mod_vlan_id, ofPortPhys)) - - } - if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.PatchedNetConfigs() { - // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. - // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, tcp6, ipv6_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, udp6, ipv6_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=175, in_port=%s, sctp6, ipv6_src=%s, "+ - "actions=ct(table=4,zone=%d)", - defaultOpenFlowCookie, netConfig.OfPortPatch, physicalIP.IP, config.Default.HostMasqConntrackZone)) - if ofPortPhys != "" { - // We send BFD traffic coming from OVN to outside directly using a higher priority flow - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=650, table=0, in_port=%s, dl_src=%s, udp6, tp_dst=3784, actions=output:%s", - defaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress, ofPortPhys)) - } - } - } - if ofPortPhys != "" { - // table 0, packets coming from external. Send it through conntrack and - // resubmit to table 1 to know the state and mark of the connection. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=50, in_port=%s, ipv6, "+ - "actions=ct(zone=%d, nat, table=1)", defaultOpenFlowCookie, ofPortPhys, config.Default.ConntrackZone)) - } - } - // Egress IP is often configured on a node different from the one hosting the affected pod. - // Due to the fact that ovn-controllers on different nodes apply the changes independently, - // there is a chance that the pod traffic will reach the egress node before it configures the SNAT flows. - // Drop pod traffic that is not SNATed, excluding local pods(required for ICNIv2) - defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] - if config.OVNKubernetesFeature.EnableEgressIP { - for _, clusterEntry := range config.Default.ClusterSubnets { - cidr := clusterEntry.CIDR - ipv := getIPv(cidr) - // table 0, drop packets coming from pods headed externally that were not SNATed. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=104, in_port=%s, %s, %s_src=%s, actions=drop", - defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, ipv, ipv, cidr)) - } - for _, subnet := range defaultNetConfig.NodeSubnets { - ipv := getIPv(subnet) - if ofPortPhys != "" { - // table 0, commit connections from local pods. - // ICNIv2 requires that local pod traffic can leave the node without SNAT. - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=109, in_port=%s, dl_src=%s, %s, %s_src=%s"+ - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s", - defaultOpenFlowCookie, defaultNetConfig.OfPortPatch, bridgeMacAddress, ipv, ipv, subnet, - config.Default.ConntrackZone, nodetypes.CtMarkOVN, ofPortPhys)) - } - } - } - - if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { - isNetworkAdvertised := netConfig.Advertised.Load() - // disableSNATMultipleGWs only applies to default network - disableSNATMultipleGWs := netConfig.IsDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs - if !disableSNATMultipleGWs && !isNetworkAdvertised { - continue - } - output := netConfig.OfPortPatch - if isNetworkAdvertised && config.Gateway.Mode == config.GatewayModeLocal { - // except if advertised through BGP, go to kernel - // TODO: MEG enabled pods should still go through the patch port - // but holding this until - // https://issues.redhat.com/browse/FDP-646 is fixed, for now we - // are assuming MEG & BGP are not used together - output = nodetypes.OvsLocalPort - } - for _, clusterEntry := range netConfig.Subnets { - cidr := clusterEntry.CIDR - ipv := getIPv(cidr) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=15, table=1, %s, %s_dst=%s, "+ - "actions=output:%s", - defaultOpenFlowCookie, ipv, ipv, cidr, output)) - } - if output == netConfig.OfPortPatch { - // except node management traffic - for _, subnet := range netConfig.NodeSubnets { - mgmtIP := util.GetNodeManagementIfAddr(subnet) - ipv := getIPv(mgmtIP) - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=16, table=1, %s, %s_dst=%s, "+ - "actions=output:%s", - defaultOpenFlowCookie, ipv, ipv, mgmtIP.IP, nodetypes.OvsLocalPort), - ) - } - } - } - - // table 1, we check to see if this dest mac is the shared mac, if so send to host - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=1, %s dl_dst=%s, actions=%soutput:%s", - defaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) - - if config.IPv6Mode { - // REMOVEME(trozet) when https://bugzilla.kernel.org/show_bug.cgi?id=11797 is resolved - // must flood icmpv6 Route Advertisement and Neighbor Advertisement traffic as it fails to create a CT entry - for _, icmpType := range []int{types.RouteAdvertisementICMPType, types.NeighborAdvertisementICMPType} { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=14, table=1,icmp6,icmpv6_type=%d actions=FLOOD", - defaultOpenFlowCookie, icmpType)) - } - if ofPortPhys != "" { - // We send BFD traffic both on the host and in ovn - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp6, tp_dst=3784, actions=output:%s,output:%s", - defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) - } - } - - if config.IPv4Mode { - if ofPortPhys != "" { - // We send BFD traffic both on the host and in ovn - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=13, table=1, in_port=%s, udp, tp_dst=3784, actions=output:%s,output:%s", - defaultOpenFlowCookie, ofPortPhys, defaultNetConfig.OfPortPatch, ofPortHost)) - } - } - - // packets larger than known acceptable MTU need to go to kernel for - // potential fragmentation - // introduced specifically for replies to egress traffic not routed - // through the host - if config.Gateway.Mode == config.GatewayModeLocal && !config.Gateway.DisablePacketMTUCheck { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=10, table=11, reg0=0x1, "+ - "actions=output:%s", defaultOpenFlowCookie, ofPortHost)) - - // Send UDN destined traffic to right patch port - for _, netConfig := range bridge.PatchedNetConfigs() { - if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=5, table=11, ct_mark=%s, "+ - "actions=output:%s", defaultOpenFlowCookie, netConfig.MasqCTMark, netConfig.OfPortPatch)) - } - } - - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=1, table=11, "+ - "actions=output:%s", defaultOpenFlowCookie, defaultNetConfig.OfPortPatch)) - } - - // table 1, all other connections do normal processing - dftFlows = append(dftFlows, - fmt.Sprintf("cookie=%s, priority=0, table=1, actions=output:NORMAL", defaultOpenFlowCookie)) - } - - return dftFlows, nil -} - -func pmtudDropFlows(bridge *bridgeconfig.BridgeConfiguration, ipAddrs []string) []string { - var flows []string - if config.Gateway.Mode != config.GatewayModeShared { - return nil - } - for _, addr := range ipAddrs { - for _, netConfig := range bridge.PatchedNetConfigs() { - flows = append(flows, - generateICMPFragmentationFlow(addr, outputPortDrop, netConfig.OfPortPatch, pmtudOpenFlowCookie, 700)) - } - } - - return flows -} - -// hostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic: -// a. from pods in the OVN network to pods in a localnet network, on the same node -// b. from pods on the host to pods in a localnet network, on the same node -// when the localnet is mapped to breth0. -// The expected srcMAC is the MAC address of breth0 and the expected hostSubnets is the host subnets found on the node -// primary interface. -func hostNetworkNormalActionFlows(netConfig *bridgeconfig.BridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string { - var flows []string - var ipFamily, ipFamilyDest string - - if isV6 { - ipFamily = "ipv6" - ipFamilyDest = "ipv6_dst" - } else { - ipFamily = "ip" - ipFamilyDest = "nw_dst" - } - - formatFlow := func(inPort, destIP, ctMark string) string { - // Matching IP traffic will be handled by the bridge instead of being output directly - // to the NIC by the existing flow at prio=100. - flowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, " + - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" - return fmt.Sprintf(flowTemplate, - defaultOpenFlowCookie, - inPort, - srcMAC, - ipFamily, - ipFamilyDest, - destIP, - config.Default.ConntrackZone, - ctMark) - } - - // Traffic path (a): OVN->localnet for shared gw mode - if config.Gateway.Mode == config.GatewayModeShared { - for _, hostSubnet := range hostSubnets { - if utilnet.IsIPv6(hostSubnet.IP) != isV6 { - continue - } - flows = append(flows, formatFlow(netConfig.OfPortPatch, hostSubnet.String(), netConfig.MasqCTMark)) - } - } - - // Traffic path (a): OVN->localnet for local gw mode - // Traffic path (b): host->localnet for both gw modes - for _, hostSubnet := range hostSubnets { - if utilnet.IsIPv6(hostSubnet.IP) != isV6 { - continue - } - flows = append(flows, formatFlow(nodetypes.OvsLocalPort, hostSubnet.String(), ctMarkHost)) - } - - if isV6 { - // IPv6 neighbor discovery uses ICMPv6 messages sent to a special destination (ff02::1:ff00:0/104) - // that is unrelated to the host subnets matched in the prio=102 flow above. - // Allow neighbor discovery by matching against ICMP type and ingress port. - formatICMPFlow := func(inPort, ctMark string, icmpType int) string { - icmpFlowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, " + - "actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL" - return fmt.Sprintf(icmpFlowTemplate, - defaultOpenFlowCookie, - inPort, - srcMAC, - icmpType, - config.Default.ConntrackZone, - ctMark) - } - - for _, icmpType := range []int{types.NeighborSolicitationICMPType, types.NeighborAdvertisementICMPType} { - // Traffic path (a) for ICMP: OVN-> localnet for shared gw mode - if config.Gateway.Mode == config.GatewayModeShared { - flows = append(flows, - formatICMPFlow(netConfig.OfPortPatch, netConfig.MasqCTMark, icmpType)) - } - - // Traffic path (a) for ICMP: OVN->localnet for local gw mode - // Traffic path (b) for ICMP: host->localnet for both gw modes - flows = append(flows, formatICMPFlow(nodetypes.OvsLocalPort, ctMarkHost, icmpType)) - } - } - return flows -} - func newGateway( nodeName string, subnets []*net.IPNet, @@ -2811,36 +1883,6 @@ func updateMasqueradeAnnotation(nodeName string, kube kube.Interface) error { return nil } -// generateIPFragmentReassemblyFlow adds flows in table 0 that send packets to a -// specific conntrack zone for reassembly with the same priority as node port -// flows that match on L4 fields. After reassembly packets are reinjected to -// table 0 again. This requires a conntrack immplementation that reassembles -// fragments. This reqreuiment is met for the kernel datapath with the netfilter -// module loaded. This reqreuiment is not met for the userspace datapath. -func generateIPFragmentReassemblyFlow(ofPortPhys string) []string { - flows := make([]string, 0, 2) - if config.IPv4Mode { - flows = append(flows, - fmt.Sprintf("cookie=%s, priority=110, table=0, in_port=%s, ip, nw_frag=yes, actions=ct(table=0,zone=%d)", - defaultOpenFlowCookie, - ofPortPhys, - config.Default.ReassemblyConntrackZone, - ), - ) - } - if config.IPv6Mode { - flows = append(flows, - fmt.Sprintf("cookie=%s, priority=110, table=0, in_port=%s, ipv6, nw_frag=yes, actions=ct(table=0,zone=%d)", - defaultOpenFlowCookie, - ofPortPhys, - config.Default.ReassemblyConntrackZone, - ), - ) - } - - return flows -} - // deleteStaleMasqueradeResources removes stale Linux resources when config.Gateway.V4MasqueradeSubnet // or config.Gateway.V6MasqueradeSubnet gets changed at day 2. func deleteStaleMasqueradeResources(bridgeName, nodeName string, wf factory.NodeWatchFactory) error { @@ -2974,14 +2016,6 @@ func deleteMasqueradeResources(link netlink.Link, staleMasqueradeIPs *config.Mas return utilerrors.Join(aggregatedErrors...) } -func getIPv(ipnet *net.IPNet) string { - prefix := "ip" - if utilnet.IsIPv6CIDR(ipnet) { - prefix = "ipv6" - } - return prefix -} - // configureAdvertisedUDNIsolationNFTables configures nftables to drop traffic generated locally towards advertised UDN subnets. // It sets up a nftables chain named nftablesUDNBGPOutputChain in the output hook with filter priority which drops // traffic originating from the local node destined to nftablesAdvertisedUDNsSet. diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 8f4082d1c5..5622a226d7 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -21,7 +21,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - utilnet "k8s.io/utils/net" "k8s.io/utils/ptr" "sigs.k8s.io/knftables" @@ -243,105 +242,6 @@ func openflowManagerCheckPorts(ofMgr *openflowManager) { Expect(checkPorts(netConfigs, uplink, ofPortPhys)).To(Succeed()) } -func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeconfig.BridgeUDNConfiguration, ofPortHost, bridgeMAC string, svcCIDR *net.IPNet) { - By(fmt.Sprintf("Checking default service isolation flows for %s", svcCIDR.String())) - - var masqIP string - var masqSubnet string - var protoPrefix string - if utilnet.IsIPv4CIDR(svcCIDR) { - protoPrefix = "ip" - masqIP = config.Gateway.MasqueradeIPs.V4HostMasqueradeIP.String() - masqSubnet = config.Gateway.V4MasqueradeSubnet - } else { - protoPrefix = "ip6" - masqIP = config.Gateway.MasqueradeIPs.V6HostMasqueradeIP.String() - masqSubnet = config.Gateway.V6MasqueradeSubnet - } - - var nTable0DefaultFlows int - var nTable0UDNMasqFlows int - var nTable2Flows int - for _, flow := range flows { - if strings.Contains(flow, fmt.Sprintf("priority=500, in_port=%s, %s, %s_dst=%s, actions=ct(commit,zone=%d,nat(src=%s),table=2)", - ofPortHost, protoPrefix, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone, - masqIP)) { - nTable0DefaultFlows++ - } else if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=%s, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=%d,table=2)", - ofPortHost, protoPrefix, protoPrefix, masqSubnet, protoPrefix, svcCIDR, config.Default.HostMasqConntrackZone)) { - nTable0UDNMasqFlows++ - } else if strings.Contains(flow, fmt.Sprintf("priority=100, table=2, actions=set_field:%s->eth_dst,output:%s", - bridgeMAC, defaultConfig.OfPortPatch)) { - nTable2Flows++ - } - } - - Expect(nTable0DefaultFlows).To(Equal(1)) - Expect(nTable0UDNMasqFlows).To(Equal(1)) - Expect(nTable2Flows).To(Equal(1)) -} - -func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeconfig.BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { - By(fmt.Sprintf("Checking advertsised UDN %s service isolation flows for %s; expected %d flows", - netName, svcCIDR.String(), expectedNFlows)) - - var matchingIPFamilySubnet *net.IPNet - var protoPrefix string - var udnAdvertisedSubnets []*net.IPNet - var err error - for _, clusterEntry := range netConfig.Subnets { - udnAdvertisedSubnets = append(udnAdvertisedSubnets, clusterEntry.CIDR) - } - if utilnet.IsIPv4CIDR(svcCIDR) { - matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) - Expect(err).ToNot(HaveOccurred()) - protoPrefix = "ip" - } else { - matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) - Expect(err).ToNot(HaveOccurred()) - protoPrefix = "ip6" - } - - var nFlows int - for _, flow := range flows { - if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", - protoPrefix, protoPrefix, matchingIPFamilySubnet)) { - nFlows++ - } - if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=LOCAL, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=64001,table=2)", - protoPrefix, protoPrefix, matchingIPFamilySubnet, protoPrefix, svcCIDR)) { - nFlows++ - } - } - - Expect(nFlows).To(Equal(expectedNFlows)) -} - -func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeconfig.BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { - By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows", - netName, svcCIDR.String(), expectedNFlows)) - - var mgmtMasqIP string - var protoPrefix string - if utilnet.IsIPv4CIDR(svcCIDR) { - mgmtMasqIP = netConfig.V4MasqIPs.ManagementPort.IP.String() - protoPrefix = "ip" - } else { - mgmtMasqIP = netConfig.V6MasqIPs.ManagementPort.IP.String() - protoPrefix = "ip6" - } - - var nFlows int - for _, flow := range flows { - if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop", - protoPrefix, protoPrefix, mgmtMasqIP)) { - nFlows++ - } - } - - Expect(nFlows).To(Equal(expectedNFlows)) -} - func getDummyOpenflowManager() *openflowManager { gwBridge := bridgeconfig.TestBridgeConfig("breth0") ofm := &openflowManager{ @@ -792,10 +692,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per UDN for table 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) + bridgeconfig.CheckUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -822,10 +722,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for table 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) + bridgeconfig.CheckUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) @@ -1023,10 +923,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per UDN for tables 0 and 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) + bridgeconfig.CheckUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -1053,10 +953,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for tables 0 and 2 for service isolation. - checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) + bridgeconfig.CheckUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) @@ -1264,10 +1164,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect exactly one flow per advertised UDN for table 2 and table 0 for service isolation. - checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 2) + bridgeconfig.CheckAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 2) } // The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was @@ -1294,10 +1194,10 @@ var _ = Describe("UserDefinedNetworkGateway", func() { for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { // Check flows for default network service CIDR. - checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) + bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) // Expect no more flows per UDN for table 2 and table0 for service isolation. - checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) + bridgeconfig.CheckAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0) } return nil }) diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 0b96b2186f..b8d8d8406e 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -14,6 +14,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/generator/udn" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -210,12 +211,12 @@ func (c *openflowManager) updateBridgePMTUDFlowCache(key string, ipAddrs []strin c.defaultBridge.Mutex.Lock() defer c.defaultBridge.Mutex.Unlock() - dftFlows := pmtudDropFlows(c.defaultBridge, ipAddrs) + dftFlows := bridgeconfig.PmtudDropFlows(c.defaultBridge, ipAddrs) c.updateFlowCacheEntry(key, dftFlows) if c.externalGatewayBridge != nil { c.externalGatewayBridge.Mutex.Lock() defer c.externalGatewayBridge.Mutex.Unlock() - exGWBridgeDftFlows := pmtudDropFlows(c.externalGatewayBridge, ipAddrs) + exGWBridgeDftFlows := bridgeconfig.PmtudDropFlows(c.externalGatewayBridge, ipAddrs) c.updateExBridgeFlowCacheEntry(key, exGWBridgeDftFlows) } } @@ -230,11 +231,11 @@ func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets [] // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - dftFlows, err := flowsForDefaultBridge(c.defaultBridge, hostIPs) + dftFlows, err := bridgeconfig.FlowsForDefaultBridge(c.defaultBridge, hostIPs) if err != nil { return err } - dftCommonFlows, err := commonFlows(hostSubnets, c.defaultBridge) + dftCommonFlows, err := bridgeconfig.CommonFlows(hostSubnets, c.defaultBridge) if err != nil { return err } @@ -248,7 +249,7 @@ func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets [] c.externalGatewayBridge.Mutex.Lock() defer c.externalGatewayBridge.Mutex.Unlock() c.updateExBridgeFlowCacheEntry("NORMAL", []string{fmt.Sprintf("table=0,priority=0,actions=%s\n", util.NormalAction)}) - exGWBridgeDftFlows, err := commonFlows(hostSubnets, c.externalGatewayBridge) + exGWBridgeDftFlows, err := bridgeconfig.CommonFlows(hostSubnets, c.externalGatewayBridge) if err != nil { return err } @@ -357,10 +358,10 @@ func bootstrapOVSFlows(nodeName string) error { // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", - defaultOpenFlowCookie, ofportPatch, bridgeMACAddress)) + nodetypes.DefaultOpenFlowCookie, ofportPatch, bridgeMACAddress)) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=9, table=0, in_port=%s, actions=drop", - defaultOpenFlowCookie, ofportPatch)) + nodetypes.DefaultOpenFlowCookie, ofportPatch)) dftFlows = append(dftFlows, "priority=0, table=0, actions=output:NORMAL") _, stderr, err = util.ReplaceOFFlows(bridge, dftFlows) diff --git a/go-controller/pkg/node/types/const.go b/go-controller/pkg/node/types/const.go index b486302dd6..bdf9c388bf 100644 --- a/go-controller/pkg/node/types/const.go +++ b/go-controller/pkg/node/types/const.go @@ -5,4 +5,18 @@ const ( CtMarkOVN = "0x1" // OvsLocalPort is the name of the OVS bridge local port OvsLocalPort = "LOCAL" + // DefaultOpenFlowCookie identifies default open flow rules added to the host OVS bridge. + // The hex number 0xdeff105, aka defflos, is meant to sound like default flows. + DefaultOpenFlowCookie = "0xdeff105" + // OutputPortDrop is used to signify that there is no output port for an openflow action and the + // rendered action should result in a drop + OutputPortDrop = "output-port-drop" + // OvnKubeNodeSNATMark is used to mark packets that need to be SNAT-ed to nodeIP for + // traffic originating from egressIP and egressService controlled pods towards other nodes in the cluster. + OvnKubeNodeSNATMark = "0x3f0" + // PmtudOpenFlowCookie identifies the flows used to drop ICMP type (3) destination unreachable, + // fragmentation-needed (4) + PmtudOpenFlowCookie = "0x0304" + // CtMarkHost is the conntrack mark value for host traffic + CtMarkHost = "0x2" ) diff --git a/go-controller/pkg/node/util/util.go b/go-controller/pkg/node/util/util.go index 9ad21a9a8e..e04be61b39 100644 --- a/go-controller/pkg/node/util/util.go +++ b/go-controller/pkg/node/util/util.go @@ -7,6 +7,7 @@ import ( net2 "k8s.io/utils/net" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + nodetypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/types" pkgutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -90,3 +91,28 @@ func GetDPUHostPrimaryIPAddresses(k8sNodeIP net.IP, ifAddrs []*net.IPNet) ([]*ne } return gwIps, nil } + +func GenerateICMPFragmentationFlow(ipAddr, outputPort, inPort, cookie string, priority int) string { + // we send any ICMP destination unreachable, fragmentation needed to the OVN pipeline too so that + // path MTU discovery continues to work. + icmpMatch := "icmp" + icmpType := 3 + icmpCode := 4 + nwDst := "nw_dst" + if net2.IsIPv6String(ipAddr) { + icmpMatch = "icmp6" + icmpType = 2 + icmpCode = 0 + nwDst = "ipv6_dst" + } + + action := fmt.Sprintf("output:%s", outputPort) + if outputPort == nodetypes.OutputPortDrop { + action = "drop" + } + + icmpFragmentationFlow := fmt.Sprintf("cookie=%s, priority=%d, in_port=%s, %s, %s=%s, icmp_type=%d, "+ + "icmp_code=%d, actions=%s", + cookie, priority, inPort, icmpMatch, nwDst, ipAddr, icmpType, icmpCode, action) + return icmpFragmentationFlow +} From 5a5e3b6d952ba922057e501965dd57a8739da4b4 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 16:30:26 +0200 Subject: [PATCH 135/278] [bridgeconfig] move flow generation locking into methods. The locking logic is slightly changed, because now bridge is only locked during flow generation and not for the whole openflow_manager update duration. Also only one bridge is now locked at a time. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeflows.go | 94 ++++++++++++------- go-controller/pkg/node/openflow_manager.go | 28 ++---- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go index 5a3467ae21..b642ffda70 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeflows.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -14,14 +14,35 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) -func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]string, error) { +func (b *BridgeConfiguration) DefaultBridgeFlows(hostSubnets []*net.IPNet, extraIPs []net.IP) ([]string, error) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + dftFlows, err := b.flowsForDefaultBridge(extraIPs) + if err != nil { + return nil, err + } + dftCommonFlows, err := b.commonFlows(hostSubnets) + if err != nil { + return nil, err + } + return append(dftFlows, dftCommonFlows...), nil +} + +func (b *BridgeConfiguration) ExternalBridgeFlows(hostSubnets []*net.IPNet) ([]string, error) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.commonFlows(hostSubnets) +} + +// must be called with bridge.mutex held +func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string, error) { // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := bridge.OfPortPhys - bridgeMacAddress := bridge.MacAddress.String() - ofPortHost := bridge.OfPortHost - bridgeIPs := bridge.Ips + ofPortPhys := b.OfPortPhys + bridgeMacAddress := b.MacAddress.String() + ofPortHost := b.OfPortHost + bridgeIPs := b.Ips var dftFlows []string // 14 bytes of overhead for ethernet header (does not include VLAN) @@ -58,7 +79,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st if err != nil { return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table 0, SVC Hairpin from OVN destined to local host, DNAT and go to table 4 dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ @@ -82,7 +103,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st continue } - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", @@ -121,7 +142,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } // table 0, SVC Hairpin from OVN destined to local host, DNAT to host, send to table 4 - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", @@ -144,7 +165,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st continue } - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", @@ -195,7 +216,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st // we match on the UDNPodSubnet itself and we also don't SNAT to 169.254.0.2 // sample flow: cookie=0xdeff105, duration=1472.742s, table=0, n_packets=9, n_bytes=666, priority=550 // ip,in_port=LOCAL,nw_src=103.103.0.0/16,nw_dst=10.96.0.0/16 actions=ct(commit,table=2,zone=64001) - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -228,7 +249,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st // In UDN match on the whole masquerade subnet to handle replies from UDN enabled services masqDst = masqSubnet } - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table 0, Reply hairpin traffic to host, coming from OVN, unSNAT dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_src=%s, %s_dst=%s,"+ @@ -251,7 +272,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st dftFlows = append(dftFlows, reassemblyFlows...) } if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { var actions string if config.Gateway.Mode != config.GatewayModeLocal || config.Gateway.DisablePacketMTUCheck { actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) @@ -319,7 +340,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) } - defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] + defaultNetConfig := b.NetConfig[types.DefaultNetworkName] // table 2, dispatch from Host -> OVN dftFlows = append(dftFlows, @@ -330,7 +351,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. if config.IPv4Mode { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -368,7 +389,7 @@ func FlowsForDefaultBridge(bridge *BridgeConfiguration, extraIPs []net.IP) ([]st } if config.IPv6Mode { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -472,13 +493,14 @@ func generateIPFragmentReassemblyFlow(ofPortPhys string) []string { return flows } -func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]string, error) { +// must be called with bridge.mutex held +func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, error) { // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := bridge.OfPortPhys - bridgeMacAddress := bridge.MacAddress.String() - ofPortHost := bridge.OfPortHost - bridgeIPs := bridge.Ips + ofPortPhys := b.OfPortPhys + bridgeMacAddress := b.MacAddress.String() + ofPortHost := b.OfPortHost + bridgeIPs := b.Ips var dftFlows []string @@ -494,7 +516,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin if ofPortPhys != "" { // table 0, we check to see if this dest mac is the shared mac, if so flood to all ports actions := "" - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { actions += "output:" + netConfig.OfPortPatch + "," } @@ -506,7 +528,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress)) @@ -521,7 +543,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be SNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // SNATs these into egressIP prior to reaching external bridge. @@ -536,9 +558,9 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + config.Gateway.Mode != config.GatewayModeDisabled && b.EipMarkIPs != nil { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range bridge.EipMarkIPs.GetIPv4() { + for mark, eip := range b.EipMarkIPs.GetIPv4() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", @@ -580,7 +602,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, nodetypes.CtMarkHost, mod_vlan_id, ofPortPhys)) } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, @@ -620,7 +642,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be DNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // DNATs these into egressIP prior to reaching external bridge. @@ -635,9 +657,9 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && bridge.EipMarkIPs != nil { + config.Gateway.Mode != config.GatewayModeDisabled && b.EipMarkIPs != nil { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range bridge.EipMarkIPs.GetIPv6() { + for mark, eip := range b.EipMarkIPs.GetIPv6() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", @@ -679,7 +701,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, @@ -714,7 +736,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin // Due to the fact that ovn-controllers on different nodes apply the changes independently, // there is a chance that the pod traffic will reach the egress node before it configures the SNAT flows. // Drop pod traffic that is not SNATed, excluding local pods(required for ICNIv2) - defaultNetConfig := bridge.NetConfig[types.DefaultNetworkName] + defaultNetConfig := b.NetConfig[types.DefaultNetworkName] if config.OVNKubernetesFeature.EnableEgressIP { for _, clusterEntry := range config.Default.ClusterSubnets { cidr := clusterEntry.CIDR @@ -739,7 +761,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin } if ofPortPhys != "" { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { isNetworkAdvertised := netConfig.Advertised.Load() // disableSNATMultipleGWs only applies to default network disableSNATMultipleGWs := netConfig.IsDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs @@ -817,7 +839,7 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortHost)) // Send UDN destined traffic to right patch port - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=5, table=11, ct_mark=%s, "+ @@ -838,13 +860,15 @@ func CommonFlows(hostSubnets []*net.IPNet, bridge *BridgeConfiguration) ([]strin return dftFlows, nil } -func PmtudDropFlows(bridge *BridgeConfiguration, ipAddrs []string) []string { +func (b *BridgeConfiguration) PMTUDDropFlows(ipAddrs []string) []string { + b.Mutex.Lock() + defer b.Mutex.Unlock() var flows []string if config.Gateway.Mode != config.GatewayModeShared { return nil } for _, addr := range ipAddrs { - for _, netConfig := range bridge.PatchedNetConfigs() { + for _, netConfig := range b.PatchedNetConfigs() { flows = append(flows, nodeutil.GenerateICMPFragmentationFlow(addr, nodetypes.OutputPortDrop, netConfig.OfPortPatch, nodetypes.PmtudOpenFlowCookie, 700)) } diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index b8d8d8406e..f7e1bccfe5 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -207,16 +207,10 @@ func (c *openflowManager) Run(stopChan <-chan struct{}, doneWg *sync.WaitGroup) } func (c *openflowManager) updateBridgePMTUDFlowCache(key string, ipAddrs []string) { - // protect defaultBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - - dftFlows := bridgeconfig.PmtudDropFlows(c.defaultBridge, ipAddrs) + dftFlows := c.defaultBridge.PMTUDDropFlows(ipAddrs) c.updateFlowCacheEntry(key, dftFlows) if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Mutex.Lock() - defer c.externalGatewayBridge.Mutex.Unlock() - exGWBridgeDftFlows := bridgeconfig.PmtudDropFlows(c.externalGatewayBridge, ipAddrs) + exGWBridgeDftFlows := c.externalGatewayBridge.PMTUDDropFlows(ipAddrs) c.updateExBridgeFlowCacheEntry(key, exGWBridgeDftFlows) } } @@ -224,35 +218,25 @@ func (c *openflowManager) updateBridgePMTUDFlowCache(key string, ipAddrs []strin // updateBridgeFlowCache generates the "static" per-bridge flows // note: this is shared between shared and local gateway modes func (c *openflowManager) updateBridgeFlowCache(hostIPs []net.IP, hostSubnets []*net.IPNet) error { - // protect defaultBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - dftFlows, err := bridgeconfig.FlowsForDefaultBridge(c.defaultBridge, hostIPs) + dftFlows, err := c.defaultBridge.DefaultBridgeFlows(hostSubnets, hostIPs) if err != nil { return err } - dftCommonFlows, err := bridgeconfig.CommonFlows(hostSubnets, c.defaultBridge) - if err != nil { - return err - } - dftFlows = append(dftFlows, dftCommonFlows...) c.updateFlowCacheEntry("NORMAL", []string{fmt.Sprintf("table=0,priority=0,actions=%s\n", util.NormalAction)}) c.updateFlowCacheEntry("DEFAULT", dftFlows) // we consume ex gw bridge flows only if that is enabled if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Mutex.Lock() - defer c.externalGatewayBridge.Mutex.Unlock() - c.updateExBridgeFlowCacheEntry("NORMAL", []string{fmt.Sprintf("table=0,priority=0,actions=%s\n", util.NormalAction)}) - exGWBridgeDftFlows, err := bridgeconfig.CommonFlows(hostSubnets, c.externalGatewayBridge) + exGWBridgeDftFlows, err := c.externalGatewayBridge.ExternalBridgeFlows(hostSubnets) if err != nil { return err } + + c.updateExBridgeFlowCacheEntry("NORMAL", []string{fmt.Sprintf("table=0,priority=0,actions=%s\n", util.NormalAction)}) c.updateExBridgeFlowCacheEntry("DEFAULT", exGWBridgeDftFlows) } return nil From 4ad1727c088ee8bf7d7f8455d5dae26c55380e29 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:11:52 +0200 Subject: [PATCH 136/278] [bridgeconfig] make most members internal, ensure correct locking. Split internal member into read-only and read-writable, make sure to use mutex in the second case. Rename some methods to remove unneeded "bridge" part of the name. Move GetGatewayIface logic to the bridgeconfig creation. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 213 +++++++++++------- .../bridgeconfig/bridgeconfig_testutil.go | 12 +- .../pkg/node/bridgeconfig/bridgeflows.go | 28 +-- go-controller/pkg/node/gateway.go | 26 +-- .../pkg/node/gateway_localnet_linux_test.go | 2 +- go-controller/pkg/node/gateway_shared_intf.go | 10 +- go-controller/pkg/node/gateway_udn.go | 4 +- go-controller/pkg/node/gateway_udn_test.go | 56 ++--- .../pkg/node/node_ip_handler_linux.go | 3 +- go-controller/pkg/node/openflow_manager.go | 16 +- 10 files changed, 211 insertions(+), 159 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 0dd601cc24..979474eab5 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -63,20 +63,25 @@ func (netConfig *BridgeUDNConfiguration) setOfPatchPort() error { } type BridgeConfiguration struct { - Mutex sync.Mutex - NodeName string - BridgeName string - UplinkName string - GwIface string - GwIfaceRep string - Ips []*net.IPNet - InterfaceID string - MacAddress net.HardwareAddr - OfPortPhys string - OfPortHost string - NetConfig map[string]*BridgeUDNConfiguration - EipMarkIPs *egressip.MarkIPsCache - NextHops []net.IP + Mutex sync.Mutex + + // variables that are only set on creation and never changed + // don't require mutex lock to read + nodeName string + bridgeName string + uplinkName string + gwIface string + gwIfaceRep string + interfaceID string + + // variables that can be updated (read/write access should be done with mutex held) + ofPortHost string + ips []*net.IPNet + macAddress net.HardwareAddr + ofPortPhys string + netConfig map[string]*BridgeUDNConfiguration + eipMarkIPs *egressip.MarkIPsCache + nextHops []net.IP } func NewBridgeConfiguration(intfName, nodeName, @@ -95,16 +100,16 @@ func NewBridgeConfiguration(intfName, nodeName, NodeSubnets: nodeSubnets, } res := BridgeConfiguration{ - NodeName: nodeName, - NetConfig: map[string]*BridgeUDNConfiguration{ + nodeName: nodeName, + netConfig: map[string]*BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, - EipMarkIPs: egressip.NewMarkIPsCache(), + eipMarkIPs: egressip.NewMarkIPsCache(), } if len(gwNextHops) > 0 { - res.NextHops = gwNextHops + res.nextHops = gwNextHops } - res.NetConfig[types.DefaultNetworkName].Advertised.Store(advertised) + res.netConfig[types.DefaultNetworkName].Advertised.Store(advertised) if config.Gateway.GatewayAcceleratedInterface != "" { // Try to get representor for the specified gateway device. @@ -138,20 +143,20 @@ func NewBridgeConfiguration(intfName, nodeName, if err != nil { return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) } - res.BridgeName = bridgeName - res.UplinkName = uplinkName - res.GwIfaceRep = intfRep - res.GwIface = gwIntf - res.MacAddress = link.Attrs().HardwareAddr + res.bridgeName = bridgeName + res.uplinkName = uplinkName + res.gwIfaceRep = intfRep + res.gwIface = gwIntf + res.macAddress = link.Attrs().HardwareAddr } else if bridgeName, _, err := util.RunOVSVsctl("port-to-br", intfName); err == nil { // This is an OVS bridge's internal port uplinkName, err := util.GetNicName(bridgeName) if err != nil { return nil, fmt.Errorf("failed to find nic name for bridge %s: %w", bridgeName, err) } - res.BridgeName = bridgeName - res.GwIface = bridgeName - res.UplinkName = uplinkName + res.bridgeName = bridgeName + res.gwIface = bridgeName + res.uplinkName = uplinkName gwIntf = bridgeName } else if _, _, err := util.RunOVSVsctl("br-exists", intfName); err != nil { // This is not a OVS bridge. We need to create a OVS bridge @@ -160,9 +165,9 @@ func NewBridgeConfiguration(intfName, nodeName, if err != nil { return nil, fmt.Errorf("nicToBridge failed for %s: %w", intfName, err) } - res.BridgeName = bridgeName - res.GwIface = bridgeName - res.UplinkName = intfName + res.bridgeName = bridgeName + res.gwIface = bridgeName + res.uplinkName = intfName gwIntf = bridgeName } else { // gateway interface is an OVS bridge @@ -174,60 +179,62 @@ func NewBridgeConfiguration(intfName, nodeName, return nil, fmt.Errorf("failed to find intfName for %s: %w", intfName, err) } } else { - res.UplinkName = uplinkName + res.uplinkName = uplinkName } - res.BridgeName = intfName - res.GwIface = intfName + res.bridgeName = intfName + res.gwIface = intfName } // Now, we get IP addresses for the bridge if len(gwIPs) > 0 { // use gwIPs if provided - res.Ips = gwIPs + res.ips = gwIPs } else { // get IP addresses from OVS bridge. If IP does not exist, // error out. - res.Ips, err = nodeutil.GetNetworkInterfaceIPAddresses(gwIntf) + res.ips, err = nodeutil.GetNetworkInterfaceIPAddresses(gwIntf) if err != nil { return nil, fmt.Errorf("failed to get interface details for %s: %w", gwIntf, err) } } if !isGWAcclInterface { // We do not have an accelerated device for Gateway interface - res.MacAddress, err = util.GetOVSPortMACAddress(gwIntf) + res.macAddress, err = util.GetOVSPortMACAddress(gwIntf) if err != nil { return nil, fmt.Errorf("failed to get MAC address for ovs port %s: %w", gwIntf, err) } } - res.InterfaceID, err = bridgedGatewayNodeSetup(nodeName, res.BridgeName, physicalNetworkName) + res.interfaceID, err = bridgedGatewayNodeSetup(nodeName, res.bridgeName, physicalNetworkName) if err != nil { return nil, fmt.Errorf("failed to set up shared interface gateway: %v", err) } // the name of the patch port created by ovn-controller is of the form // patch--to-br-int - defaultNetConfig.PatchPort = (&util.DefaultNetInfo{}).GetNetworkScopedPatchPortName(res.BridgeName, nodeName) + defaultNetConfig.PatchPort = (&util.DefaultNetInfo{}).GetNetworkScopedPatchPortName(res.bridgeName, nodeName) // for DPU we use the host MAC address for the Gateway configuration if config.OvnKubeNode.Mode == types.NodeModeDPU { - hostRep, err := util.GetDPUHostInterface(res.BridgeName) + hostRep, err := util.GetDPUHostInterface(res.bridgeName) if err != nil { return nil, err } - res.MacAddress, err = util.GetSriovnetOps().GetRepresentorPeerMacAddress(hostRep) + res.macAddress, err = util.GetSriovnetOps().GetRepresentorPeerMacAddress(hostRep) if err != nil { return nil, err } } + + // If gwIface is set, then accelerated GW interface is present and we use it. Else use external bridge instead. + if res.gwIface == "" { + res.gwIface = res.bridgeName + } + return &res, nil } func (b *BridgeConfiguration) GetGatewayIface() string { - // If GwIface is set, then accelerated GW interface is present and we use it. If else use external bridge instead. - if b.GwIface != "" { - return b.GwIface - } - return b.BridgeName + return b.gwIface } // UpdateInterfaceIPAddresses sets and returns the bridge's current ips @@ -256,24 +263,24 @@ func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]* } } - b.Ips = ifAddrs + b.ips = ifAddrs return ifAddrs, nil } -// GetBridgePortConfigurations returns a slice of Network port configurations along with the +// GetPortConfigurations returns a slice of Network port configurations along with the // uplinkName and physical port's ofport value -func (b *BridgeConfiguration) GetBridgePortConfigurations() ([]*BridgeUDNConfiguration, string, string) { +func (b *BridgeConfiguration) GetPortConfigurations() ([]*BridgeUDNConfiguration, string, string) { b.Mutex.Lock() defer b.Mutex.Unlock() var netConfigs []*BridgeUDNConfiguration - for _, netConfig := range b.NetConfig { + for _, netConfig := range b.netConfig { netConfigs = append(netConfigs, netConfig.ShallowCopy()) } - return netConfigs, b.UplinkName, b.OfPortPhys + return netConfigs, b.uplinkName, b.ofPortPhys } -// AddNetworkBridgeConfig adds the patchport and ctMark value for the provided netInfo into the bridge configuration cache -func (b *BridgeConfiguration) AddNetworkBridgeConfig( +// AddNetworkConfig adds the patchport and ctMark value for the provided netInfo into the bridge configuration cache +func (b *BridgeConfiguration) AddNetworkConfig( nInfo util.NetInfo, nodeSubnets []*net.IPNet, masqCTMark, pktMark uint, @@ -282,9 +289,9 @@ func (b *BridgeConfiguration) AddNetworkBridgeConfig( defer b.Mutex.Unlock() netName := nInfo.GetNetworkName() - patchPort := nInfo.GetNetworkScopedPatchPortName(b.BridgeName, b.NodeName) + patchPort := nInfo.GetNetworkScopedPatchPortName(b.bridgeName, b.nodeName) - _, found := b.NetConfig[netName] + _, found := b.netConfig[netName] if !found { netConfig := &BridgeUDNConfiguration{ PatchPort: patchPort, @@ -295,9 +302,9 @@ func (b *BridgeConfiguration) AddNetworkBridgeConfig( Subnets: nInfo.Subnets(), NodeSubnets: nodeSubnets, } - netConfig.Advertised.Store(util.IsPodNetworkAdvertisedAtNode(nInfo, b.NodeName)) + netConfig.Advertised.Store(util.IsPodNetworkAdvertisedAtNode(nInfo, b.nodeName)) - b.NetConfig[netName] = netConfig + b.netConfig[netName] = netConfig } else { klog.Warningf("Trying to update bridge config for network %s which already"+ "exists in cache...networks are not mutable...ignoring update", nInfo.GetNetworkName()) @@ -305,18 +312,18 @@ func (b *BridgeConfiguration) AddNetworkBridgeConfig( return nil } -// DelNetworkBridgeConfig deletes the provided netInfo from the bridge configuration cache -func (b *BridgeConfiguration) DelNetworkBridgeConfig(nInfo util.NetInfo) { +// DelNetworkConfig deletes the provided netInfo from the bridge configuration cache +func (b *BridgeConfiguration) DelNetworkConfig(nInfo util.NetInfo) { b.Mutex.Lock() defer b.Mutex.Unlock() - delete(b.NetConfig, nInfo.GetNetworkName()) + delete(b.netConfig, nInfo.GetNetworkName()) } -func (b *BridgeConfiguration) GetNetworkBridgeConfig(networkName string) *BridgeUDNConfiguration { +func (b *BridgeConfiguration) GetNetworkConfig(networkName string) *BridgeUDNConfiguration { b.Mutex.Lock() defer b.Mutex.Unlock() - return b.NetConfig[networkName] + return b.netConfig[networkName] } // GetActiveNetworkBridgeConfigCopy returns a shallow copy of the network configuration corresponding to the @@ -328,15 +335,17 @@ func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName strin b.Mutex.Lock() defer b.Mutex.Unlock() - if netConfig, found := b.NetConfig[networkName]; found && netConfig.OfPortPatch != "" { + if netConfig, found := b.netConfig[networkName]; found && netConfig.OfPortPatch != "" { return netConfig.ShallowCopy() } return nil } func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { - result := make([]*BridgeUDNConfiguration, 0, len(b.NetConfig)) - for _, netConfig := range b.NetConfig { + b.Mutex.Lock() + defer b.Mutex.Unlock() + result := make([]*BridgeUDNConfiguration, 0, len(b.netConfig)) + for _, netConfig := range b.netConfig { if netConfig.OfPortPatch == "" { continue } @@ -350,7 +359,7 @@ func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { func (b *BridgeConfiguration) IsGatewayReady() bool { b.Mutex.Lock() defer b.Mutex.Unlock() - for _, netConfig := range b.NetConfig { + for _, netConfig := range b.netConfig { ready := gatewayReady(netConfig.PatchPort) if !ready { return false @@ -363,44 +372,44 @@ func (b *BridgeConfiguration) SetOfPorts() error { b.Mutex.Lock() defer b.Mutex.Unlock() // Get ofport of patchPort - for _, netConfig := range b.NetConfig { + for _, netConfig := range b.netConfig { if err := netConfig.setOfPatchPort(); err != nil { return fmt.Errorf("error setting bridge openflow ports for network with patchport %v: err: %v", netConfig.PatchPort, err) } } - if b.UplinkName != "" { + if b.uplinkName != "" { // Get ofport of physical interface - ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", b.UplinkName, "ofport") + ofportPhys, stderr, err := util.GetOVSOfPort("get", "interface", b.uplinkName, "ofport") if err != nil { return fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - b.UplinkName, stderr, err) + b.uplinkName, stderr, err) } - b.OfPortPhys = ofportPhys + b.ofPortPhys = ofportPhys } // Get ofport representing the host. That is, host representor port in case of DPUs, ovsLocalPort otherwise. if config.OvnKubeNode.Mode == types.NodeModeDPU { var stderr string - hostRep, err := util.GetDPUHostInterface(b.BridgeName) + hostRep, err := util.GetDPUHostInterface(b.bridgeName) if err != nil { return err } - b.OfPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") + b.ofPortHost, stderr, err = util.RunOVSVsctl("get", "interface", hostRep, "ofport") if err != nil { return fmt.Errorf("failed to get ofport of host interface %s, stderr: %q, error: %v", hostRep, stderr, err) } } else { var err error - if b.GwIfaceRep != "" { - b.OfPortHost, _, err = util.RunOVSVsctl("get", "interface", b.GwIfaceRep, "ofport") + if b.gwIfaceRep != "" { + b.ofPortHost, _, err = util.RunOVSVsctl("get", "interface", b.gwIfaceRep, "ofport") if err != nil { - return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", b.GwIfaceRep, err) + return fmt.Errorf("failed to get ofport of bypass rep %s, error: %v", b.gwIfaceRep, err) } } else { - b.OfPortHost = nodetypes.OvsLocalPort + b.ofPortHost = nodetypes.OvsLocalPort } } @@ -410,38 +419,74 @@ func (b *BridgeConfiguration) SetOfPorts() error { func (b *BridgeConfiguration) GetIPs() []*net.IPNet { b.Mutex.Lock() defer b.Mutex.Unlock() - return b.Ips + return b.ips } func (b *BridgeConfiguration) GetBridgeName() string { - b.Mutex.Lock() - defer b.Mutex.Unlock() - return b.BridgeName + return b.bridgeName +} + +func (b *BridgeConfiguration) GetUplinkName() string { + return b.uplinkName } func (b *BridgeConfiguration) GetMAC() net.HardwareAddr { b.Mutex.Lock() defer b.Mutex.Unlock() - return b.MacAddress + return b.macAddress } func (b *BridgeConfiguration) SetMAC(macAddr net.HardwareAddr) { b.Mutex.Lock() defer b.Mutex.Unlock() - b.MacAddress = macAddr + b.macAddress = macAddr } func (b *BridgeConfiguration) SetNetworkOfPatchPort(netName string) error { b.Mutex.Lock() defer b.Mutex.Unlock() - netConfig, found := b.NetConfig[netName] + netConfig, found := b.netConfig[netName] if !found { - return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, b.BridgeName) + return fmt.Errorf("failed to find network %s configuration on bridge %s", netName, b.bridgeName) } return netConfig.setOfPatchPort() } +func (b *BridgeConfiguration) GetInterfaceID() string { + return b.interfaceID +} + +func (b *BridgeConfiguration) GetOfPortHost() string { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.ofPortHost +} + +func (b *BridgeConfiguration) GetEIPMarkIPs() *egressip.MarkIPsCache { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.eipMarkIPs +} + +func (b *BridgeConfiguration) SetEIPMarkIPs(eipMarkIPs *egressip.MarkIPsCache) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + b.eipMarkIPs = eipMarkIPs +} + +func (b *BridgeConfiguration) GetNextHops() []net.IP { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return b.nextHops +} + +func (b *BridgeConfiguration) SetNextHops(nextHops []net.IP) { + b.Mutex.Lock() + defer b.Mutex.Unlock() + b.nextHops = nextHops +} + func gatewayReady(patchPort string) bool { // Get ofport of patchPort ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go index 271c555e7e..d5e3e9d5cd 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go @@ -20,7 +20,7 @@ func TestDefaultBridgeConfig() *BridgeConfiguration { OfPortPatch: "patch-breth0_ov", } return &BridgeConfiguration{ - NetConfig: map[string]*BridgeUDNConfiguration{ + netConfig: map[string]*BridgeUDNConfiguration{ types.DefaultNetworkName: defaultNetConfig, }, } @@ -28,11 +28,17 @@ func TestDefaultBridgeConfig() *BridgeConfiguration { func TestBridgeConfig(brName string) *BridgeConfiguration { return &BridgeConfiguration{ - BridgeName: brName, - GwIface: brName, + bridgeName: brName, + gwIface: brName, } } +func (b *BridgeConfiguration) GetNetConfigLen() int { + b.Mutex.Lock() + defer b.Mutex.Unlock() + return len(b.netConfig) +} + func CheckUDNSvcIsolationOVSFlows(flows []string, netConfig *BridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) { By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows", netName, svcCIDR.String(), expectedNFlows)) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go index b642ffda70..236d7b111a 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeflows.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -39,10 +39,10 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := b.OfPortPhys - bridgeMacAddress := b.MacAddress.String() - ofPortHost := b.OfPortHost - bridgeIPs := b.Ips + ofPortPhys := b.ofPortPhys + bridgeMacAddress := b.macAddress.String() + ofPortHost := b.ofPortHost + bridgeIPs := b.ips var dftFlows []string // 14 bytes of overhead for ethernet header (does not include VLAN) @@ -340,7 +340,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string nodetypes.DefaultOpenFlowCookie, match_vlan, bridgeMacAddress, strip_vlan, ofPortHost)) } - defaultNetConfig := b.NetConfig[types.DefaultNetworkName] + defaultNetConfig := b.netConfig[types.DefaultNetworkName] // table 2, dispatch from Host -> OVN dftFlows = append(dftFlows, @@ -497,10 +497,10 @@ func generateIPFragmentReassemblyFlow(ofPortPhys string) []string { func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, error) { // CAUTION: when adding new flows where the in_port is ofPortPatch and the out_port is ofPortPhys, ensure // that dl_src is included in match criteria! - ofPortPhys := b.OfPortPhys - bridgeMacAddress := b.MacAddress.String() - ofPortHost := b.OfPortHost - bridgeIPs := b.Ips + ofPortPhys := b.ofPortPhys + bridgeMacAddress := b.macAddress.String() + ofPortHost := b.ofPortHost + bridgeIPs := b.ips var dftFlows []string @@ -558,9 +558,9 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && b.EipMarkIPs != nil { + config.Gateway.Mode != config.GatewayModeDisabled && b.eipMarkIPs != nil { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range b.EipMarkIPs.GetIPv4() { + for mark, eip := range b.eipMarkIPs.GetIPv4() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ip, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", @@ -657,9 +657,9 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e // table 0, packets coming from egressIP pods only from user defined networks. If an egressIP is assigned to // this node, then all networks get a flow even if no pods on that network were selected for by this egressIP. if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && - config.Gateway.Mode != config.GatewayModeDisabled && b.EipMarkIPs != nil { + config.Gateway.Mode != config.GatewayModeDisabled && b.eipMarkIPs != nil { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { - for mark, eip := range b.EipMarkIPs.GetIPv6() { + for mark, eip := range b.eipMarkIPs.GetIPv6() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=105, in_port=%s, dl_src=%s, ipv6, pkt_mark=%d, "+ "actions=ct(commit, zone=%d, nat(src=%s), exec(set_field:%s->ct_mark)), output:%s", @@ -736,7 +736,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e // Due to the fact that ovn-controllers on different nodes apply the changes independently, // there is a chance that the pod traffic will reach the egress node before it configures the SNAT flows. // Drop pod traffic that is not SNATed, excluding local pods(required for ICNIv2) - defaultNetConfig := b.NetConfig[types.DefaultNetworkName] + defaultNetConfig := b.netConfig[types.DefaultNetworkName] if config.OVNKubernetesFeature.EnableEgressIP { for _, clusterEntry := range config.Default.ClusterSubnets { cidr := clusterEntry.CIDR diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index a476783537..4fc0004d58 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -382,7 +382,7 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops "IP fragmentation or large TCP/UDP payloads may not be forwarded correctly.") enableGatewayMTU = false } else { - chkPktLengthSupported, err := util.DetectCheckPktLengthSupport(gatewayBridge.BridgeName) + chkPktLengthSupported, err := util.DetectCheckPktLengthSupport(gatewayBridge.GetBridgeName()) if err != nil { return nil, nil, err } @@ -416,9 +416,9 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops } if config.Default.EnableUDPAggregation { - err = setupUDPAggregationUplink(gatewayBridge.UplinkName) + err = setupUDPAggregationUplink(gatewayBridge.GetUplinkName()) if err == nil && egressGWBridge != nil { - err = setupUDPAggregationUplink(egressGWBridge.UplinkName) + err = setupUDPAggregationUplink(egressGWBridge.GetUplinkName()) } if err != nil { klog.Warningf("Could not enable UDP packet aggregation on uplink interface (aggregation will be disabled): %v", err) @@ -427,25 +427,25 @@ func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops } // Set static FDB entry for LOCAL port - if err := util.SetStaticFDBEntry(gatewayBridge.bridgeName, gatewayBridge.bridgeName, gatewayBridge.macAddress); err != nil { + if err := util.SetStaticFDBEntry(gatewayBridge.GetBridgeName(), gatewayBridge.GetBridgeName(), gatewayBridge.GetMAC()); err != nil { return nil, nil, err } l3GwConfig := util.L3GatewayConfig{ Mode: config.Gateway.Mode, ChassisID: chassisID, - BridgeID: gatewayBridge.BridgeName, - InterfaceID: gatewayBridge.InterfaceID, - MACAddress: gatewayBridge.MacAddress, - IPAddresses: gatewayBridge.Ips, + BridgeID: gatewayBridge.GetBridgeName(), + InterfaceID: gatewayBridge.GetInterfaceID(), + MACAddress: gatewayBridge.GetMAC(), + IPAddresses: gatewayBridge.GetIPs(), NextHops: gwNextHops, NodePortEnable: config.Gateway.NodeportEnable, VLANID: &config.Gateway.VLANID, } if egressGWBridge != nil { - l3GwConfig.EgressGWInterfaceID = egressGWBridge.InterfaceID - l3GwConfig.EgressGWMACAddress = egressGWBridge.MacAddress - l3GwConfig.EgressGWIPAddresses = egressGWBridge.Ips + l3GwConfig.EgressGWInterfaceID = egressGWBridge.GetInterfaceID() + l3GwConfig.EgressGWMACAddress = egressGWBridge.GetMAC() + l3GwConfig.EgressGWIPAddresses = egressGWBridge.GetIPs() } err = util.SetL3GatewayConfig(nodeAnnotator, &l3GwConfig) @@ -467,11 +467,11 @@ func (g *gateway) SetDefaultGatewayBridgeMAC(macAddr net.HardwareAddr) { } func (g *gateway) SetDefaultPodNetworkAdvertised(isPodNetworkAdvertised bool) { - g.openflowManager.defaultBridge.NetConfig[types.DefaultNetworkName].Advertised.Store(isPodNetworkAdvertised) + g.openflowManager.defaultBridge.GetNetworkConfig(types.DefaultNetworkName).Advertised.Store(isPodNetworkAdvertised) } func (g *gateway) GetDefaultPodNetworkAdvertised() bool { - return g.openflowManager.defaultBridge.NetConfig[types.DefaultNetworkName].Advertised.Load() + return g.openflowManager.defaultBridge.GetNetworkConfig(types.DefaultNetworkName).Advertised.Load() } // Reconcile handles triggering updates to different components of a gateway, like OFM, Services diff --git a/go-controller/pkg/node/gateway_localnet_linux_test.go b/go-controller/pkg/node/gateway_localnet_linux_test.go index d259bc14e3..e1ff21cd49 100644 --- a/go-controller/pkg/node/gateway_localnet_linux_test.go +++ b/go-controller/pkg/node/gateway_localnet_linux_test.go @@ -58,7 +58,7 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher gwMACParsed, _ := net.ParseMAC(gwMAC) defaultBridge := bridgeconfig.TestDefaultBridgeConfig() - defaultBridge.MacAddress = gwMACParsed + defaultBridge.SetMAC(gwMACParsed) fNPW := nodePortWatcher{ ofportPhys: "eth0", diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 8dfe97a3f1..967828dcd0 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1474,8 +1474,8 @@ func newGateway( } } if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && config.Gateway.Mode != config.GatewayModeDisabled { - gw.bridgeEIPAddrManager = egressip.NewBridgeEIPAddrManager(nodeName, gwBridge.BridgeName, linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) - gwBridge.EipMarkIPs = gw.bridgeEIPAddrManager.GetCache() + gw.bridgeEIPAddrManager = egressip.NewBridgeEIPAddrManager(nodeName, gwBridge.GetBridgeName(), linkManager, kube, watchFactory.EgressIPInformer(), watchFactory.NodeCoreInformer()) + gwBridge.SetEIPMarkIPs(gw.bridgeEIPAddrManager.GetCache()) } gw.nodeIPManager = newAddressManager(nodeName, kube, mgmtPort, watchFactory, gwBridge) @@ -1559,10 +1559,10 @@ func newNodePortWatcher( // Get ofport of physical interface ofportPhys, stderr, err := util.GetOVSOfPort("--if-exists", "get", - "interface", gwBridge.UplinkName, "ofport") + "interface", gwBridge.GetUplinkName(), "ofport") if err != nil { return nil, fmt.Errorf("failed to get ofport of %s, stderr: %q, error: %v", - gwBridge.UplinkName, stderr, err) + gwBridge.GetUplinkName(), stderr, err) } // In the shared gateway mode, the NodePort service is handled by the OpenFlow flows configured @@ -1615,7 +1615,7 @@ func newNodePortWatcher( } // Get Physical IPs of Node, Can be IPV4 IPV6 or both - gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(gwBridge.Ips) + gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(gwBridge.GetIPs()) npw := &nodePortWatcher{ dpuMode: dpuMode, diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index a9d3b92d23..65dde1282f 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -599,7 +599,7 @@ func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) var retVal []netlink.Route var defaultAnyCIDR *net.IPNet - for _, nextHop := range udng.gateway.openflowManager.defaultBridge.NextHops { + for _, nextHop := range udng.gateway.openflowManager.defaultBridge.GetNextHops() { isV6 := utilnet.IsIPv6(nextHop) _, defaultAnyCIDR, _ = net.ParseCIDR("0.0.0.0/0") if isV6 { @@ -791,7 +791,7 @@ func (udng *UserDefinedNetworkGateway) doReconcile() error { // update bridge configuration isNetworkAdvertised := util.IsPodNetworkAdvertisedAtNode(udng.NetInfo, udng.node.Name) - netConfig := udng.openflowManager.defaultBridge.GetNetworkBridgeConfig(udng.GetNetworkName()) + netConfig := udng.openflowManager.defaultBridge.GetNetworkConfig(udng.GetNetworkName()) if netConfig == nil { return fmt.Errorf("missing bridge configuration for network %s", udng.GetNetworkName()) } diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 5622a226d7..862c4b5a7a 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -667,16 +667,16 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost + Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("default") + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("bluenet") + bridgeMAC := udnGateway.openflowManager.defaultBridge.GetMAC().String() + ofPortHost := udnGateway.openflowManager.defaultBridge.GetOfPortHost() for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { @@ -707,8 +707,8 @@ var _ = Describe("UserDefinedNetworkGateway", func() { kubeMock.On("UpdateNodeStatus", cnode).Return(nil) // check if network key gets deleted from annotation Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only + Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -898,16 +898,16 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost + Expect(flowMap["DEFAULT"]).To(HaveLen(64)) // 18 UDN Flows are added by default + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("default") + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("bluenet") + bridgeMAC := udnGateway.openflowManager.defaultBridge.GetMAC().String() + ofPortHost := udnGateway.openflowManager.defaultBridge.GetOfPortHost() for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { @@ -938,8 +938,8 @@ var _ = Describe("UserDefinedNetworkGateway", func() { kubeMock.On("UpdateNodeStatus", cnode).Return(nil) // check if network key gets deleted from annotation Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only + Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -1139,16 +1139,16 @@ var _ = Describe("UserDefinedNetworkGateway", func() { } } Expect(udnFlows).To(Equal(0)) - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // only default network + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // only default network Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(69)) // 18 UDN Flows and 5 advertisedUDN flows are added by default - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(2)) // default network + UDN network - defaultUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["default"] - bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.NetConfig["bluenet"] - bridgeMAC := udnGateway.openflowManager.defaultBridge.MacAddress.String() - ofPortHost := udnGateway.openflowManager.defaultBridge.OfPortHost + Expect(flowMap["DEFAULT"]).To(HaveLen(69)) // 18 UDN Flows and 5 advertisedUDN flows are added by default + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(2)) // default network + UDN network + defaultUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("default") + bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("bluenet") + bridgeMAC := udnGateway.openflowManager.defaultBridge.GetMAC().String() + ofPortHost := udnGateway.openflowManager.defaultBridge.GetOfPortHost() for _, flows := range flowMap { for _, flow := range flows { if strings.Contains(flow, fmt.Sprintf("0x%x", udnGateway.masqCTMark)) { @@ -1179,8 +1179,8 @@ var _ = Describe("UserDefinedNetworkGateway", func() { kubeMock.On("UpdateNodeStatus", cnode).Return(nil) // check if network key gets deleted from annotation Expect(udnGateway.DelNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present - Expect(udnGateway.openflowManager.defaultBridge.NetConfig).To(HaveLen(1)) // default network only + Expect(flowMap["DEFAULT"]).To(HaveLen(46)) // only default network flows are present + Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(1)) // default network only udnFlows = 0 for _, flows := range flowMap { for _, flow := range flows { @@ -1380,7 +1380,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() ofm := getDummyOpenflowManager() - ofm.defaultBridge.NextHops = ovntest.MustParseIPs(config.Gateway.NextHop) + ofm.defaultBridge.SetNextHops(ovntest.MustParseIPs(config.Gateway.NextHop)) udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, vrf, nil, &gateway{openflowManager: ofm}) Expect(err).NotTo(HaveOccurred()) mplink, err := netlink.LinkByName(mgtPort) diff --git a/go-controller/pkg/node/node_ip_handler_linux.go b/go-controller/pkg/node/node_ip_handler_linux.go index a6945531e4..770ec5924e 100644 --- a/go-controller/pkg/node/node_ip_handler_linux.go +++ b/go-controller/pkg/node/node_ip_handler_linux.go @@ -438,7 +438,8 @@ func (c *addressManager) isValidNodeIP(addr net.IP, linkIndex int) bool { if util.IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnableInterconnect && config.Gateway.Mode != config.GatewayModeDisabled { // Two methods to lookup EIPs assigned to the gateway bridge. Fast path from a shared cache or slow path from node annotations. // At startup, gateway bridge cache gets sync - if c.gatewayBridge.EipMarkIPs != nil && c.gatewayBridge.EipMarkIPs.HasSyncdOnce() && c.gatewayBridge.EipMarkIPs.IsIPPresent(addr) { + eipMarkIPs := c.gatewayBridge.GetEIPMarkIPs() + if eipMarkIPs != nil && eipMarkIPs.HasSyncdOnce() && eipMarkIPs.IsIPPresent(addr) { return false } else { if eipAddresses, err := c.getPrimaryHostEgressIPs(); err != nil { diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index f7e1bccfe5..70fd383c70 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -34,19 +34,19 @@ type openflowManager struct { // UTILs Needed for UDN (also leveraged for default netInfo) in openflowmanager func (c *openflowManager) getDefaultBridgePortConfigurations() ([]*bridgeconfig.BridgeUDNConfiguration, string, string) { - return c.defaultBridge.GetBridgePortConfigurations() + return c.defaultBridge.GetPortConfigurations() } func (c *openflowManager) getExGwBridgePortConfigurations() ([]*bridgeconfig.BridgeUDNConfiguration, string, string) { - return c.externalGatewayBridge.GetBridgePortConfigurations() + return c.externalGatewayBridge.GetPortConfigurations() } func (c *openflowManager) addNetwork(nInfo util.NetInfo, nodeSubnets []*net.IPNet, masqCTMark, pktMark uint, v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { - if err := c.defaultBridge.AddNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { + if err := c.defaultBridge.AddNetworkConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { return err } if c.externalGatewayBridge != nil { - if err := c.externalGatewayBridge.AddNetworkBridgeConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { + if err := c.externalGatewayBridge.AddNetworkConfig(nInfo, nodeSubnets, masqCTMark, pktMark, v6MasqIPs, v4MasqIPs); err != nil { return err } } @@ -54,9 +54,9 @@ func (c *openflowManager) addNetwork(nInfo util.NetInfo, nodeSubnets []*net.IPNe } func (c *openflowManager) delNetwork(nInfo util.NetInfo) { - c.defaultBridge.DelNetworkBridgeConfig(nInfo) + c.defaultBridge.DelNetworkConfig(nInfo) if c.externalGatewayBridge != nil { - c.externalGatewayBridge.DelNetworkBridgeConfig(nInfo) + c.externalGatewayBridge.DelNetworkConfig(nInfo) } } @@ -124,7 +124,7 @@ func (c *openflowManager) syncFlows() { flows = append(flows, entry...) } - _, stderr, err := util.ReplaceOFFlows(c.defaultBridge.BridgeName, flows) + _, stderr, err := util.ReplaceOFFlows(c.defaultBridge.GetBridgeName(), flows) if err != nil { klog.Errorf("Failed to add flows, error: %v, stderr, %s, flows: %s", err, stderr, c.flowCache) } @@ -141,7 +141,7 @@ func (c *openflowManager) syncFlows() { flows = append(flows, entry...) } - _, stderr, err := util.ReplaceOFFlows(c.externalGatewayBridge.BridgeName, flows) + _, stderr, err := util.ReplaceOFFlows(c.externalGatewayBridge.GetBridgeName(), flows) if err != nil { klog.Errorf("Failed to add flows, error: %v, stderr, %s, flows: %s", err, stderr, c.exGWFlowCache) } From fa6076bcc115b11321e80c71cef1fa107a3542f7 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:19:55 +0200 Subject: [PATCH 137/278] [bridgeconfig] move nextHops to the gateway where it is used. Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 17 ----------------- go-controller/pkg/node/gateway.go | 6 ++++-- go-controller/pkg/node/gateway_shared_intf.go | 4 +++- go-controller/pkg/node/gateway_udn.go | 2 +- go-controller/pkg/node/gateway_udn_test.go | 4 ++-- 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 979474eab5..c68b7df478 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -81,13 +81,11 @@ type BridgeConfiguration struct { ofPortPhys string netConfig map[string]*BridgeUDNConfiguration eipMarkIPs *egressip.MarkIPsCache - nextHops []net.IP } func NewBridgeConfiguration(intfName, nodeName, physicalNetworkName string, nodeSubnets, gwIPs []*net.IPNet, - gwNextHops []net.IP, advertised bool) (*BridgeConfiguration, error) { var intfRep string var err error @@ -106,9 +104,6 @@ func NewBridgeConfiguration(intfName, nodeName, }, eipMarkIPs: egressip.NewMarkIPsCache(), } - if len(gwNextHops) > 0 { - res.nextHops = gwNextHops - } res.netConfig[types.DefaultNetworkName].Advertised.Store(advertised) if config.Gateway.GatewayAcceleratedInterface != "" { @@ -475,18 +470,6 @@ func (b *BridgeConfiguration) SetEIPMarkIPs(eipMarkIPs *egressip.MarkIPsCache) { b.eipMarkIPs = eipMarkIPs } -func (b *BridgeConfiguration) GetNextHops() []net.IP { - b.Mutex.Lock() - defer b.Mutex.Unlock() - return b.nextHops -} - -func (b *BridgeConfiguration) SetNextHops(nextHops []net.IP) { - b.Mutex.Lock() - defer b.Mutex.Unlock() - b.nextHops = nextHops -} - func gatewayReady(patchPort string) bool { // Get ofport of patchPort ofport, _, err := util.GetOVSOfPort("--if-exists", "get", "interface", patchPort, "ofport") diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 4fc0004d58..9b43fc95a5 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -60,6 +60,8 @@ type gateway struct { watchFactory *factory.WatchFactory // used for retry stopChan <-chan struct{} wg *sync.WaitGroup + + nextHops []net.IP } func (g *gateway) AddService(svc *corev1.Service) error { @@ -357,13 +359,13 @@ func setupUDPAggregationUplink(ifname string) error { func gatewayInitInternal(nodeName, gwIntf, egressGatewayIntf string, gwNextHops []net.IP, nodeSubnets, gwIPs []*net.IPNet, advertised bool, nodeAnnotator kube.Annotator) ( *bridgeconfig.BridgeConfiguration, *bridgeconfig.BridgeConfiguration, error) { - gatewayBridge, err := bridgeconfig.NewBridgeConfiguration(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, gwNextHops, advertised) + gatewayBridge, err := bridgeconfig.NewBridgeConfiguration(gwIntf, nodeName, types.PhysicalNetworkName, nodeSubnets, gwIPs, advertised) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", gwIntf, err) } var egressGWBridge *bridgeconfig.BridgeConfiguration if egressGatewayIntf != "" { - egressGWBridge, err = bridgeconfig.NewBridgeConfiguration(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, nil, false) + egressGWBridge, err = bridgeconfig.NewBridgeConfiguration(egressGatewayIntf, nodeName, types.PhysicalNetworkExGwName, nodeSubnets, nil, false) if err != nil { return nil, nil, fmt.Errorf("bridge for interface failed for %s: %w", egressGatewayIntf, err) } diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 967828dcd0..278a3cbd44 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -1425,7 +1425,9 @@ func newGateway( gatewayMode config.GatewayMode, ) (*gateway, error) { klog.Info("Creating new gateway") - gw := &gateway{} + gw := &gateway{ + nextHops: gwNextHops, + } if gatewayMode == config.GatewayModeLocal { if err := initLocalGateway(subnets, mgmtPort); err != nil { diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 65dde1282f..f10326d1ed 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -599,7 +599,7 @@ func (udng *UserDefinedNetworkGateway) getDefaultRoute(isNetworkAdvertised bool) var retVal []netlink.Route var defaultAnyCIDR *net.IPNet - for _, nextHop := range udng.gateway.openflowManager.defaultBridge.GetNextHops() { + for _, nextHop := range udng.gateway.nextHops { isV6 := utilnet.IsIPv6(nextHop) _, defaultAnyCIDR, _ = net.ParseCIDR("0.0.0.0/0") if isV6 { diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 862c4b5a7a..34848faf7e 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -1380,8 +1380,8 @@ var _ = Describe("UserDefinedNetworkGateway", func() { err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() ofm := getDummyOpenflowManager() - ofm.defaultBridge.SetNextHops(ovntest.MustParseIPs(config.Gateway.NextHop)) - udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, vrf, nil, &gateway{openflowManager: ofm}) + udnGateway, err := NewUserDefinedNetworkGateway(mutableNetInfo, node, nil, nil, vrf, nil, + &gateway{openflowManager: ofm, nextHops: ovntest.MustParseIPs(config.Gateway.NextHop)}) Expect(err).NotTo(HaveOccurred()) mplink, err := netlink.LinkByName(mgtPort) Expect(err).NotTo(HaveOccurred()) From a0c90f267df26a6b7d8eecbb18c67bd23ab2ccd7 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:25:31 +0200 Subject: [PATCH 138/278] [bridgeconfig] make mutex internal. syncFlows only directly uses already protected GetBridgeName() method for bridgeConfig, and flow updates should be protected by the flowMutex. So hopefully I am not breaking anything... Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 66 +++++++++---------- .../bridgeconfig/bridgeconfig_testutil.go | 4 +- .../pkg/node/bridgeconfig/bridgeflows.go | 12 ++-- go-controller/pkg/node/openflow_manager.go | 7 -- 4 files changed, 41 insertions(+), 48 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index c68b7df478..351a44c981 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -63,7 +63,7 @@ func (netConfig *BridgeUDNConfiguration) setOfPatchPort() error { } type BridgeConfiguration struct { - Mutex sync.Mutex + mutex sync.Mutex // variables that are only set on creation and never changed // don't require mutex lock to read @@ -234,8 +234,8 @@ func (b *BridgeConfiguration) GetGatewayIface() string { // UpdateInterfaceIPAddresses sets and returns the bridge's current ips func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]*net.IPNet, error) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(b.GetGatewayIface()) if err != nil { return nil, err @@ -265,8 +265,8 @@ func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]* // GetPortConfigurations returns a slice of Network port configurations along with the // uplinkName and physical port's ofport value func (b *BridgeConfiguration) GetPortConfigurations() ([]*BridgeUDNConfiguration, string, string) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() var netConfigs []*BridgeUDNConfiguration for _, netConfig := range b.netConfig { netConfigs = append(netConfigs, netConfig.ShallowCopy()) @@ -280,8 +280,8 @@ func (b *BridgeConfiguration) AddNetworkConfig( nodeSubnets []*net.IPNet, masqCTMark, pktMark uint, v6MasqIPs, v4MasqIPs *udn.MasqueradeIPs) error { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() netName := nInfo.GetNetworkName() patchPort := nInfo.GetNetworkScopedPatchPortName(b.bridgeName, b.nodeName) @@ -309,15 +309,15 @@ func (b *BridgeConfiguration) AddNetworkConfig( // DelNetworkConfig deletes the provided netInfo from the bridge configuration cache func (b *BridgeConfiguration) DelNetworkConfig(nInfo util.NetInfo) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() delete(b.netConfig, nInfo.GetNetworkName()) } func (b *BridgeConfiguration) GetNetworkConfig(networkName string) *BridgeUDNConfiguration { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.netConfig[networkName] } @@ -327,8 +327,8 @@ func (b *BridgeConfiguration) GetNetworkConfig(networkName string) *BridgeUDNCon // NOTE: if the network configuration can't be found or if the network is not patched by OVN // yet this returns nil. func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName string) *BridgeUDNConfiguration { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() if netConfig, found := b.netConfig[networkName]; found && netConfig.OfPortPatch != "" { return netConfig.ShallowCopy() @@ -337,8 +337,8 @@ func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName strin } func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() result := make([]*BridgeUDNConfiguration, 0, len(b.netConfig)) for _, netConfig := range b.netConfig { if netConfig.OfPortPatch == "" { @@ -352,8 +352,8 @@ func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { // IsGatewayReady checks if patch ports of every netConfig are present. // used by gateway on newGateway readyFunc func (b *BridgeConfiguration) IsGatewayReady() bool { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() for _, netConfig := range b.netConfig { ready := gatewayReady(netConfig.PatchPort) if !ready { @@ -364,8 +364,8 @@ func (b *BridgeConfiguration) IsGatewayReady() bool { } func (b *BridgeConfiguration) SetOfPorts() error { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() // Get ofport of patchPort for _, netConfig := range b.netConfig { if err := netConfig.setOfPatchPort(); err != nil { @@ -412,8 +412,8 @@ func (b *BridgeConfiguration) SetOfPorts() error { } func (b *BridgeConfiguration) GetIPs() []*net.IPNet { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.ips } @@ -426,20 +426,20 @@ func (b *BridgeConfiguration) GetUplinkName() string { } func (b *BridgeConfiguration) GetMAC() net.HardwareAddr { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.macAddress } func (b *BridgeConfiguration) SetMAC(macAddr net.HardwareAddr) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() b.macAddress = macAddr } func (b *BridgeConfiguration) SetNetworkOfPatchPort(netName string) error { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() netConfig, found := b.netConfig[netName] if !found { @@ -453,20 +453,20 @@ func (b *BridgeConfiguration) GetInterfaceID() string { } func (b *BridgeConfiguration) GetOfPortHost() string { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.ofPortHost } func (b *BridgeConfiguration) GetEIPMarkIPs() *egressip.MarkIPsCache { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.eipMarkIPs } func (b *BridgeConfiguration) SetEIPMarkIPs(eipMarkIPs *egressip.MarkIPsCache) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() b.eipMarkIPs = eipMarkIPs } diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go index d5e3e9d5cd..d01c73861e 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go @@ -34,8 +34,8 @@ func TestBridgeConfig(brName string) *BridgeConfiguration { } func (b *BridgeConfiguration) GetNetConfigLen() int { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return len(b.netConfig) } diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go index 236d7b111a..84a7b4ea9c 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeflows.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -15,8 +15,8 @@ import ( ) func (b *BridgeConfiguration) DefaultBridgeFlows(hostSubnets []*net.IPNet, extraIPs []net.IP) ([]string, error) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() dftFlows, err := b.flowsForDefaultBridge(extraIPs) if err != nil { return nil, err @@ -29,8 +29,8 @@ func (b *BridgeConfiguration) DefaultBridgeFlows(hostSubnets []*net.IPNet, extra } func (b *BridgeConfiguration) ExternalBridgeFlows(hostSubnets []*net.IPNet) ([]string, error) { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() return b.commonFlows(hostSubnets) } @@ -861,8 +861,8 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e } func (b *BridgeConfiguration) PMTUDDropFlows(ipAddrs []string) []string { - b.Mutex.Lock() - defer b.Mutex.Unlock() + b.mutex.Lock() + defer b.mutex.Unlock() var flows []string if config.Gateway.Mode != config.GatewayModeShared { return nil diff --git a/go-controller/pkg/node/openflow_manager.go b/go-controller/pkg/node/openflow_manager.go index 70fd383c70..de3a721519 100644 --- a/go-controller/pkg/node/openflow_manager.go +++ b/go-controller/pkg/node/openflow_manager.go @@ -112,10 +112,6 @@ func (c *openflowManager) requestFlowSync() { } func (c *openflowManager) syncFlows() { - // protect gwBridge config from being updated by gw.nodeIPManager - c.defaultBridge.Mutex.Lock() - defer c.defaultBridge.Mutex.Unlock() - c.flowMutex.Lock() defer c.flowMutex.Unlock() @@ -130,9 +126,6 @@ func (c *openflowManager) syncFlows() { } if c.externalGatewayBridge != nil { - c.externalGatewayBridge.Mutex.Lock() - defer c.externalGatewayBridge.Mutex.Unlock() - c.exGWFlowMutex.Lock() defer c.exGWFlowMutex.Unlock() From fd5e7915436a832b7cd18b313a5917532038b62f Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:36:58 +0200 Subject: [PATCH 139/278] [node/gateway] nodePortWatcher should use its own bridgeConfiguration. It used to require addressManager to updateGatewayIPs only to get bridgeConfig from it. We can just give nodePortWatcher its own reference to the bridgeConfig. Signed-off-by: Nadia Pinaeva --- .../pkg/node/gateway_localnet_linux_test.go | 1 + go-controller/pkg/node/gateway_shared_intf.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go-controller/pkg/node/gateway_localnet_linux_test.go b/go-controller/pkg/node/gateway_localnet_linux_test.go index e1ff21cd49..49e4d1ee13 100644 --- a/go-controller/pkg/node/gateway_localnet_linux_test.go +++ b/go-controller/pkg/node/gateway_localnet_linux_test.go @@ -70,6 +70,7 @@ func initFakeNodePortWatcher(iptV4, iptV6 util.IPTablesHelper) *nodePortWatcher defaultBridge: defaultBridge, }, networkManager: networkmanager.Default().Interface(), + gwBridge: bridgeconfig.TestBridgeConfig(""), } return &fNPW } diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index 278a3cbd44..d60783144c 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -190,7 +190,7 @@ type nodePortWatcher struct { gatewayIPv6 string gatewayIPLock sync.Mutex ofportPhys string - gwBridge string + gwBridge *bridgeconfig.BridgeConfiguration // Map of service name to programmed iptables/OF rules serviceInfo map[ktypes.NamespacedName]*serviceConfig serviceInfoLock sync.Mutex @@ -216,9 +216,9 @@ type cidrAndFlags struct { validLifetime int } -func (npw *nodePortWatcher) updateGatewayIPs(addressManager *addressManager) { +func (npw *nodePortWatcher) updateGatewayIPs() { // Get Physical IPs of Node, Can be IPV4 IPV6 or both - gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(addressManager.gatewayBridge.GetIPs()) + gatewayIPv4, gatewayIPv6 := getGatewayFamilyAddrs(npw.gwBridge.GetIPs()) npw.gatewayIPLock.Lock() defer npw.gatewayIPLock.Unlock() @@ -368,7 +368,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI var ofPorts []string // don't get the ports unless we need to as it is a costly operation if (len(extParsedIPs) > 0 || len(ingParsedIPs) > 0) && add { - ofPorts, err = util.GetOpenFlowPorts(npw.gwBridge, false) + ofPorts, err = util.GetOpenFlowPorts(npw.gwBridge.GetGatewayIface(), false) if err != nil { // in the odd case that getting all ports from the bridge should not work, // simply output to LOCAL (this should work well in the vast majority of cases, anyway) @@ -1517,7 +1517,7 @@ func newGateway( } if gw.nodePortWatcher != nil { npw, _ := gw.nodePortWatcher.(*nodePortWatcher) - npw.updateGatewayIPs(gw.nodeIPManager) + npw.updateGatewayIPs() } // Services create OpenFlow flows as well, need to update them all if gw.servicesRetryFramework != nil { @@ -1624,7 +1624,7 @@ func newNodePortWatcher( gatewayIPv4: gatewayIPv4, gatewayIPv6: gatewayIPv6, ofportPhys: ofportPhys, - gwBridge: gwBridge.GetGatewayIface(), + gwBridge: gwBridge, serviceInfo: make(map[ktypes.NamespacedName]*serviceConfig), nodeIPManager: nodeIPManager, ofm: ofm, From f531e3d338e2d73272bda29570089ed15ebb0b36 Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:43:20 +0200 Subject: [PATCH 140/278] [node/gateway] make PatchedNetConfigs internal, remove locking Signed-off-by: Nadia Pinaeva --- .../pkg/node/bridgeconfig/bridgeconfig.go | 5 ++- .../pkg/node/bridgeconfig/bridgeflows.go | 36 +++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 351a44c981..92455b9be6 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -336,9 +336,8 @@ func (b *BridgeConfiguration) GetActiveNetworkBridgeConfigCopy(networkName strin return nil } -func (b *BridgeConfiguration) PatchedNetConfigs() []*BridgeUDNConfiguration { - b.mutex.Lock() - defer b.mutex.Unlock() +// must be called with mutex held +func (b *BridgeConfiguration) patchedNetConfigs() []*BridgeUDNConfiguration { result := make([]*BridgeUDNConfiguration, 0, len(b.netConfig)) for _, netConfig := range b.netConfig { if netConfig.OfPortPatch == "" { diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go index 84a7b4ea9c..d03b88c8de 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeflows.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -79,7 +79,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string if err != nil { return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table 0, SVC Hairpin from OVN destined to local host, DNAT and go to table 4 dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ @@ -103,7 +103,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string continue } - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ip, ip_dst=%s, ip_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", @@ -142,7 +142,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } // table 0, SVC Hairpin from OVN destined to local host, DNAT to host, send to table 4 - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,nat(dst=%s),table=4)", @@ -165,7 +165,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string continue } - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, ipv6, ipv6_dst=%s, ipv6_src=%s,"+ "actions=ct(commit,zone=%d,table=4)", @@ -216,7 +216,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string // we match on the UDNPodSubnet itself and we also don't SNAT to 169.254.0.2 // sample flow: cookie=0xdeff105, duration=1472.742s, table=0, n_packets=9, n_bytes=666, priority=550 // ip,in_port=LOCAL,nw_src=103.103.0.0/16,nw_dst=10.96.0.0/16 actions=ct(commit,table=2,zone=64001) - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -249,7 +249,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string // In UDN match on the whole masquerade subnet to handle replies from UDN enabled services masqDst = masqSubnet } - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table 0, Reply hairpin traffic to host, coming from OVN, unSNAT dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=500, in_port=%s, %s, %s_src=%s, %s_dst=%s,"+ @@ -272,7 +272,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string dftFlows = append(dftFlows, reassemblyFlows...) } if ofPortPhys != "" { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { var actions string if config.Gateway.Mode != config.GatewayModeLocal || config.Gateway.DisablePacketMTUCheck { actions = fmt.Sprintf("output:%s", netConfig.OfPortPatch) @@ -351,7 +351,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. if config.IPv4Mode { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -389,7 +389,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string } if config.IPv6Mode { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } @@ -516,7 +516,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e if ofPortPhys != "" { // table 0, we check to see if this dest mac is the shared mac, if so flood to all ports actions := "" - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { actions += "output:" + netConfig.OfPortPatch + "," } @@ -528,7 +528,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e // table 0, check packets coming from OVN have the correct mac address. Low priority flows that are a catch all // for non-IP packets that would normally be forwarded with NORMAL action (table 0, priority 0 flow). - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=10, table=0, in_port=%s, dl_src=%s, actions=output:NORMAL", nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch, bridgeMacAddress)) @@ -543,7 +543,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e return nil, fmt.Errorf("unable to determine IPv4 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be SNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // SNATs these into egressIP prior to reaching external bridge. @@ -602,7 +602,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e nodetypes.DefaultOpenFlowCookie, ofPortHost, config.Default.ConntrackZone, nodetypes.CtMarkHost, mod_vlan_id, ofPortPhys)) } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, @@ -642,7 +642,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e return nil, fmt.Errorf("unable to determine IPv6 physical IP of host: %v", err) } if ofPortPhys != "" { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table0, packets coming from egressIP pods that have mark 1008 on them // will be DNAT-ed a final time into nodeIP to maintain consistency in traffic even if the GR // DNATs these into egressIP prior to reaching external bridge. @@ -701,7 +701,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e } if config.Gateway.Mode == config.GatewayModeLocal { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { // table 0, any packet coming from OVN send to host in LGW mode, host will take care of sending it outside if needed. // exceptions are traffic for egressIP and egressGW features and ICMP related traffic which will hit the priority 100 flow instead of this. dftFlows = append(dftFlows, @@ -761,7 +761,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e } if ofPortPhys != "" { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { isNetworkAdvertised := netConfig.Advertised.Load() // disableSNATMultipleGWs only applies to default network disableSNATMultipleGWs := netConfig.IsDefaultNetwork() && config.Gateway.DisableSNATMultipleGWs @@ -839,7 +839,7 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e "actions=output:%s", nodetypes.DefaultOpenFlowCookie, ofPortHost)) // Send UDN destined traffic to right patch port - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { if netConfig.MasqCTMark != nodetypes.CtMarkOVN { dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=5, table=11, ct_mark=%s, "+ @@ -868,7 +868,7 @@ func (b *BridgeConfiguration) PMTUDDropFlows(ipAddrs []string) []string { return nil } for _, addr := range ipAddrs { - for _, netConfig := range b.PatchedNetConfigs() { + for _, netConfig := range b.patchedNetConfigs() { flows = append(flows, nodeutil.GenerateICMPFragmentationFlow(addr, nodetypes.OutputPortDrop, netConfig.OfPortPatch, nodetypes.PmtudOpenFlowCookie, 700)) } From 33e20b8361a2939d774bdb4bae9930a61fcb914f Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Wed, 2 Jul 2025 17:57:13 +0200 Subject: [PATCH 141/278] [bridgeconfig] AI suggested fixes. store Advertised values to the copy and not to the original object. isIPv6 should be true in ipv6 case. Signed-off-by: Nadia Pinaeva --- go-controller/pkg/node/bridgeconfig/bridgeconfig.go | 2 +- go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 92455b9be6..4cad9037ad 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -44,7 +44,7 @@ func (netConfig *BridgeUDNConfiguration) ShallowCopy() *BridgeUDNConfiguration { Subnets: netConfig.Subnets, NodeSubnets: netConfig.NodeSubnets, } - netConfig.Advertised.Store(netConfig.Advertised.Load()) + copy.Advertised.Store(netConfig.Advertised.Load()) return copy } diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go index d01c73861e..8395baf06d 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig_testutil.go @@ -80,7 +80,7 @@ func CheckAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *BridgeUDN Expect(err).ToNot(HaveOccurred()) protoPrefix = "ip" } else { - matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(false, udnAdvertisedSubnets) + matchingIPFamilySubnet, err = util.MatchFirstIPNetFamily(true, udnAdvertisedSubnets) Expect(err).ToNot(HaveOccurred()) protoPrefix = "ip6" } From 290eb0385d3107e6f9ff2525a74e0b9f24c94966 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 28 Apr 2025 13:09:45 -0400 Subject: [PATCH 142/278] Add metrics for UDN Signed-off-by: Dan Winship --- .../userdefinednetwork/controller.go | 23 ++++++++++ go-controller/pkg/metrics/cluster_manager.go | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/controller.go b/go-controller/pkg/clustermanager/userdefinednetwork/controller.go index e8c1d74a03..67292bd2ed 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/controller.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/controller.go @@ -37,6 +37,7 @@ import ( userdefinednetworkscheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned/scheme" userdefinednetworkinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/informers/externalversions/userdefinednetwork/v1" userdefinednetworklister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/listers/userdefinednetwork/v1" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) @@ -389,6 +390,14 @@ func (c *Controller) syncUserDefinedNetwork(udn *userdefinednetworkv1.UserDefine return nil, nil } + var role, topology string + if udn.Spec.Layer2 != nil { + role = string(udn.Spec.Layer2.Role) + } else if udn.Spec.Layer3 != nil { + role = string(udn.Spec.Layer3.Role) + } + topology = string(udn.Spec.Topology) + if !udn.DeletionTimestamp.IsZero() { // udn is being deleted if controllerutil.ContainsFinalizer(udn, template.FinalizerUserDefinedNetwork) { if err := c.deleteNAD(udn, udn.Namespace); err != nil { @@ -401,6 +410,7 @@ func (c *Controller) syncUserDefinedNetwork(udn *userdefinednetworkv1.UserDefine return nil, fmt.Errorf("failed to remove finalizer to UserDefinedNetwork: %w", err) } klog.Infof("Finalizer removed from UserDefinedNetworks [%s/%s]", udn.Namespace, udn.Name) + metrics.DecrementUDNCount(role, topology) } return nil, nil @@ -412,6 +422,7 @@ func (c *Controller) syncUserDefinedNetwork(udn *userdefinednetworkv1.UserDefine return nil, fmt.Errorf("failed to add finalizer to UserDefinedNetwork: %w", err) } klog.Infof("Added Finalizer to UserDefinedNetwork [%s/%s]", udn.Namespace, udn.Name) + metrics.IncrementUDNCount(role, topology) } return c.updateNAD(udn, udn.Namespace) @@ -539,6 +550,16 @@ func (c *Controller) syncClusterUDN(cudn *userdefinednetworkv1.ClusterUserDefine cudnName := cudn.Name affectedNamespaces := c.namespaceTracker[cudnName] + var role, topology string + if cudn.Spec.Network.Layer2 != nil { + role = string(cudn.Spec.Network.Layer2.Role) + } else if cudn.Spec.Network.Layer3 != nil { + role = string(cudn.Spec.Network.Layer3.Role) + } else if cudn.Spec.Network.Localnet != nil { + role = string(cudn.Spec.Network.Localnet.Role) + } + topology = string(cudn.Spec.Network.Topology) + if !cudn.DeletionTimestamp.IsZero() { if controllerutil.ContainsFinalizer(cudn, template.FinalizerUserDefinedNetwork) { var errs []error @@ -564,6 +585,7 @@ func (c *Controller) syncClusterUDN(cudn *userdefinednetworkv1.ClusterUserDefine } klog.Infof("Finalizer removed from ClusterUserDefinedNetwork %q", cudn.Name) delete(c.namespaceTracker, cudnName) + metrics.DecrementCUDNCount(role, topology) } return nil, nil @@ -581,6 +603,7 @@ func (c *Controller) syncClusterUDN(cudn *userdefinednetworkv1.ClusterUserDefine return nil, fmt.Errorf("failed to add finalizer to ClusterUserDefinedNetwork %q: %w", cudnName, err) } klog.Infof("Added Finalizer to ClusterUserDefinedNetwork %q", cudnName) + metrics.IncrementCUDNCount(role, topology) } selectedNamespaces, err := c.getSelectedNamespaces(cudn.Spec.NamespaceSelector) diff --git a/go-controller/pkg/metrics/cluster_manager.go b/go-controller/pkg/metrics/cluster_manager.go index f97a338b89..711d4dc026 100644 --- a/go-controller/pkg/metrics/cluster_manager.go +++ b/go-controller/pkg/metrics/cluster_manager.go @@ -91,6 +91,28 @@ var metricEgressIPRebalanceCount = prometheus.NewCounter(prometheus.CounterOpts{ /** EgressIP metrics recorded from cluster-manager ends**/ +var metricUDNCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, + Name: "user_defined_networks", + Help: "The total number of UserDefinedNetworks in the cluster"}, + []string{ + "role", + "topology", + }, +) + +var metricCUDNCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: types.MetricOvnkubeNamespace, + Subsystem: types.MetricOvnkubeSubsystemClusterManager, + Name: "cluster_user_defined_networks", + Help: "The total number of ClusterUserDefinedNetworks in the cluster"}, + []string{ + "role", + "topology", + }, +) + // RegisterClusterManagerBase registers ovnkube cluster manager base metrics with the Prometheus registry. // This function should only be called once. func RegisterClusterManagerBase() { @@ -130,6 +152,8 @@ func RegisterClusterManagerFunctional() { prometheus.MustRegister(metricEgressIPRebalanceCount) prometheus.MustRegister(metricEgressIPCount) } + prometheus.MustRegister(metricUDNCount) + prometheus.MustRegister(metricCUDNCount) if err := prometheus.Register(MetricResourceRetryFailuresCount); err != nil { if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { panic(err) @@ -165,3 +189,23 @@ func RecordEgressIPRebalance(count int) { func RecordEgressIPCount(count float64) { metricEgressIPCount.Set(count) } + +// IncrementUDNCount increments the number of UserDefinedNetworks of the given type +func IncrementUDNCount(role, topology string) { + metricUDNCount.WithLabelValues(role, topology).Inc() +} + +// DecrementUDNCount decrements the number of UserDefinedNetworks of the given type +func DecrementUDNCount(role, topology string) { + metricUDNCount.WithLabelValues(role, topology).Dec() +} + +// IncrementCUDNCount increments the number of ClusterUserDefinedNetworks of the given type +func IncrementCUDNCount(role, topology string) { + metricCUDNCount.WithLabelValues(role, topology).Inc() +} + +// DecrementCUDNCount decrements the number of ClusterUserDefinedNetworks of the given type +func DecrementCUDNCount(role, topology string) { + metricCUDNCount.WithLabelValues(role, topology).Dec() +} From 527c19fcff607cfd96ce36236fbd1441cb198f16 Mon Sep 17 00:00:00 2001 From: Alin Serdean Date: Fri, 18 Jul 2025 13:02:13 +0200 Subject: [PATCH 143/278] Add support for --disable-requestedchassis flag in ovnkube controller This commit adds conditional logic to pass the --disable-requestedchassis flag to the ovnkube controller when the ovn_disable_requestedchassis environment variable is set to "true". The flag is added to the ovnkube-controller-with-node function in dist/images/ovnkube.sh, following the same pattern as other similar configuration flags like --enable-stateless-netpol. This flag is extremely useful when ovnkube is running in DPU mode since its chassis name will differ from the hostname of the DPU host. Signed-off-by: Alin Serdean --- dist/images/ovnkube.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dist/images/ovnkube.sh b/dist/images/ovnkube.sh index 3931d4e180..e016ce4a47 100755 --- a/dist/images/ovnkube.sh +++ b/dist/images/ovnkube.sh @@ -2097,6 +2097,12 @@ ovnkube-controller-with-node() { ovn_stateless_netpol_enable_flag="--enable-stateless-netpol" fi + ovn_disable_requestedchassis_flag= + if [[ ${ovn_disable_requestedchassis} == "true" ]]; then + ovn_disable_requestedchassis_flag="--disable-requestedchassis" + fi + echo "ovn_disable_requestedchassis_flag=${ovn_disable_requestedchassis_flag}" + echo "=============== ovnkube-controller-with-node --init-ovnkube-controller-with-node==========" /usr/bin/ovnkube --init-ovnkube-controller ${K8S_NODE} --init-node ${K8S_NODE} \ ${anp_enabled_flag} \ @@ -2150,6 +2156,7 @@ ovnkube-controller-with-node() { ${ssl_opts} \ ${network_qos_enabled_flag} \ ${ovn_enable_dnsnameresolver_flag} \ + ${ovn_disable_requestedchassis_flag} \ --cluster-subnets ${net_cidr} --k8s-service-cidr=${svc_cidr} \ --export-ovs-metrics \ --gateway-mode=${ovn_gateway_mode} ${ovn_gateway_opts} \ From 293f6dda5712dcc8abe8302d5656dfcf1fb9aa40 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Thu, 17 Jul 2025 19:33:31 -0400 Subject: [PATCH 144/278] ci: run tests only if files other than docs are changed Note: merge_group doesn't support path filters hence it will still run the jobs for queue patches. Signed-off-by: Ihar Hrachyshka --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e2a0067ee6..d9d5d40eec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,10 @@ on: merge_group: pull_request: branches: [ master ] + # Only run jobs if at least one non-doc file is changed + paths-ignore: + - '**/*.md' + - 'mkdocs.yml' schedule: - cron: '0 */12 * * *' workflow_dispatch: From ec378a7bbde21b2fc42d830caaa6cfe16bf3e7ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:56:44 +0000 Subject: [PATCH 145/278] Bump golang.org/x/oauth2 Bumps the go_modules group with 1 update in the /test/conformance directory: [golang.org/x/oauth2](https://github.com/golang/oauth2). Updates `golang.org/x/oauth2` from 0.12.0 to 0.27.0 - [Commits](https://github.com/golang/oauth2/compare/v0.12.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.27.0 dependency-type: indirect dependency-group: go_modules ... Signed-off-by: dependabot[bot] --- test/conformance/go.mod | 3 +-- test/conformance/go.sum | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/test/conformance/go.mod b/test/conformance/go.mod index b3763a3068..de64ed280e 100644 --- a/test/conformance/go.mod +++ b/test/conformance/go.mod @@ -39,12 +39,11 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/test/conformance/go.sum b/test/conformance/go.sum index 1e5b55a8e9..175ec601cc 100644 --- a/test/conformance/go.sum +++ b/test/conformance/go.sum @@ -23,7 +23,6 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -106,14 +105,13 @@ golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2F golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -125,7 +123,6 @@ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= @@ -141,8 +138,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= From 3d32558bd903bcc0703bbc40c4cd3b98064f0b67 Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Thu, 12 Jun 2025 11:04:09 +0530 Subject: [PATCH 146/278] Remove routes of ex gw pods in terminating or not ready state Signed-off-by: arkadeepsen --- .../apbroute/external_controller.go | 9 +- .../apbroute/external_controller_pod.go | 10 + go-controller/pkg/ovn/egressgw.go | 8 + go-controller/pkg/ovn/ovn.go | 17 +- .../k8s.io/kubernetes/pkg/api/v1/pod/util.go | 418 ++++++++++++++++++ go-controller/vendor/modules.txt | 1 + 6 files changed, 459 insertions(+), 4 deletions(-) create mode 100644 go-controller/vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller.go b/go-controller/pkg/ovn/controller/apbroute/external_controller.go index cd034d67b7..73f6208e96 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller.go @@ -22,6 +22,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" + v1pod "k8s.io/kubernetes/pkg/api/v1/pod" adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1" adminpolicybasedrouteinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/informers/externalversions/adminpolicybasedroute/v1" @@ -565,10 +566,14 @@ func (m *externalPolicyManager) onPodUpdate(oldObj, newObj interface{}) { utilruntime.HandleError(errors.New("invalid Pod provided to onPodUpdate()")) return } - // if labels AND assigned Pod IPs AND the multus network status annotations are the same, skip processing changes to the pod. + // if labels AND assigned Pod IPs AND the multus network status annotations AND + // pod PodReady condition AND deletion timestamp (PodTerminating) are + // the same, skip processing changes to the pod. if reflect.DeepEqual(o.Labels, n.Labels) && reflect.DeepEqual(o.Status.PodIPs, n.Status.PodIPs) && - reflect.DeepEqual(o.Annotations[nettypes.NetworkStatusAnnot], n.Annotations[nettypes.NetworkStatusAnnot]) { + reflect.DeepEqual(o.Annotations[nettypes.NetworkStatusAnnot], n.Annotations[nettypes.NetworkStatusAnnot]) && + reflect.DeepEqual(v1pod.GetPodReadyCondition(o.Status), v1pod.GetPodReadyCondition(n.Status)) && + reflect.DeepEqual(o.DeletionTimestamp, n.DeletionTimestamp) { return } m.podQueue.Add(n) diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller_pod.go b/go-controller/pkg/ovn/controller/apbroute/external_controller_pod.go index 9c49c474ba..2b2915f521 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller_pod.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller_pod.go @@ -11,7 +11,10 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" + v1pod "k8s.io/kubernetes/pkg/api/v1/pod" utilnet "k8s.io/utils/net" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) func (m *externalPolicyManager) syncPod(pod *corev1.Pod, routeQueue workqueue.TypedRateLimitingInterface[string]) error { @@ -28,6 +31,13 @@ func (m *externalPolicyManager) syncPod(pod *corev1.Pod, routeQueue workqueue.Ty } func getExGwPodIPs(gatewayPod *corev1.Pod, networkName string) (sets.Set[string], error) { + // If an external gateway pod is in terminating or not ready state then don't return the + // IPs for the external gateway pod + if util.PodTerminating(gatewayPod) || !v1pod.IsPodReadyConditionTrue(gatewayPod.Status) { + klog.Warningf("External gateway pod cannot serve traffic; it's in terminating or not ready state: %s/%s", gatewayPod.Namespace, gatewayPod.Name) + return nil, nil + } + if networkName != "" { return getMultusIPsFromNetworkName(gatewayPod, networkName) } diff --git a/go-controller/pkg/ovn/egressgw.go b/go-controller/pkg/ovn/egressgw.go index 2b8e939585..b607a3b253 100644 --- a/go-controller/pkg/ovn/egressgw.go +++ b/go-controller/pkg/ovn/egressgw.go @@ -15,6 +15,7 @@ import ( ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" + v1pod "k8s.io/kubernetes/pkg/api/v1/pod" utilnet "k8s.io/utils/net" libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" @@ -49,6 +50,13 @@ func (oc *DefaultNetworkController) addPodExternalGW(pod *corev1.Pod) error { klog.Infof("External gateway pod: %s, detected for namespace(s) %s", pod.Name, podRoutingNamespaceAnno) + // If an external gateway pod is in terminating or not ready state then don't add the + // routes for the external gateway pod + if util.PodTerminating(pod) || !v1pod.IsPodReadyConditionTrue(pod.Status) { + klog.Warningf("External gateway pod cannot serve traffic; it's in terminating or not ready state: %s/%s", pod.Namespace, pod.Name) + return nil + } + foundGws, err := getExGwPodIPs(pod) if err != nil { klog.Errorf("Error getting exgw IPs for pod: %s, error: %v", pod.Name, err) diff --git a/go-controller/pkg/ovn/ovn.go b/go-controller/pkg/ovn/ovn.go index 293e23f4aa..07b7b6a83b 100644 --- a/go-controller/pkg/ovn/ovn.go +++ b/go-controller/pkg/ovn/ovn.go @@ -16,6 +16,7 @@ import ( listers "k8s.io/client-go/listers/core/v1" ref "k8s.io/client-go/tools/reference" "k8s.io/klog/v2" + v1pod "k8s.io/kubernetes/pkg/api/v1/pod" libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" @@ -117,6 +118,10 @@ func networkStatusAnnotationsChanged(oldPod, newPod *corev1.Pod) bool { return oldPod.Annotations[nettypes.NetworkStatusAnnot] != newPod.Annotations[nettypes.NetworkStatusAnnot] } +func podBecameReady(oldPod, newPod *corev1.Pod) bool { + return !v1pod.IsPodReadyConditionTrue(oldPod.Status) && v1pod.IsPodReadyConditionTrue(newPod.Status) +} + // ensurePod tries to set up a pod. It returns nil on success and error on failure; failure // indicates the pod set up should be retried later. func (oc *DefaultNetworkController) ensurePod(oldPod, pod *corev1.Pod, addPort bool) error { @@ -131,6 +136,14 @@ func (oc *DefaultNetworkController) ensurePod(oldPod, pod *corev1.Pod, addPort b return oc.ensureRemotePodIP(oldPod, pod, addPort) } + // If an external gateway pod is in terminating or not ready state then remove the + // routes for the external gateway pod + if util.PodTerminating(pod) || !v1pod.IsPodReadyConditionTrue(pod.Status) { + if err := oc.deletePodExternalGW(pod); err != nil { + return fmt.Errorf("ensurePod failed %s/%s: %w", pod.Namespace, pod.Name, err) + } + } + if oc.isPodScheduledinLocalZone(pod) { klog.V(5).Infof("Ensuring zone local for Pod %s/%s in node %s", pod.Namespace, pod.Name, pod.Spec.NodeName) return oc.ensureLocalZonePod(oldPod, pod, addPort) @@ -170,7 +183,7 @@ func (oc *DefaultNetworkController) ensureLocalZonePod(oldPod, pod *corev1.Pod, } } else { // either pod is host-networked or its an update for a normal pod (addPort=false case) - if oldPod == nil || exGatewayAnnotationsChanged(oldPod, pod) || networkStatusAnnotationsChanged(oldPod, pod) { + if oldPod == nil || exGatewayAnnotationsChanged(oldPod, pod) || networkStatusAnnotationsChanged(oldPod, pod) || podBecameReady(oldPod, pod) { if err := oc.addPodExternalGW(pod); err != nil { return fmt.Errorf("addPodExternalGW failed for %s/%s: %w", pod.Namespace, pod.Name, err) } @@ -237,7 +250,7 @@ func (oc *DefaultNetworkController) ensureRemoteZonePod(oldPod, pod *corev1.Pod, } // either pod is host-networked or its an update for a normal pod (addPort=false case) - if oldPod == nil || exGatewayAnnotationsChanged(oldPod, pod) || networkStatusAnnotationsChanged(oldPod, pod) { + if oldPod == nil || exGatewayAnnotationsChanged(oldPod, pod) || networkStatusAnnotationsChanged(oldPod, pod) || podBecameReady(oldPod, pod) { // check if this remote pod is serving as an external GW. If so add the routes in the namespace // associated with this remote pod if err := oc.addPodExternalGW(pod); err != nil { diff --git a/go-controller/vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go b/go-controller/vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go new file mode 100644 index 0000000000..c2fe519714 --- /dev/null +++ b/go-controller/vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go @@ -0,0 +1,418 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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 pod + +import ( + "fmt" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// FindPort locates the container port for the given pod and portName. If the +// targetPort is a number, use that. If the targetPort is a string, look that +// string up in all named ports in all containers in the target pod. If no +// match is found, fail. +func FindPort(pod *v1.Pod, svcPort *v1.ServicePort) (int, error) { + portName := svcPort.TargetPort + switch portName.Type { + case intstr.String: + name := portName.StrVal + for _, container := range pod.Spec.Containers { + for _, port := range container.Ports { + if port.Name == name && port.Protocol == svcPort.Protocol { + return int(port.ContainerPort), nil + } + } + } + // also support sidecar container (initContainer with restartPolicy=Always) + for _, container := range pod.Spec.InitContainers { + if container.RestartPolicy == nil || *container.RestartPolicy != v1.ContainerRestartPolicyAlways { + continue + } + for _, port := range container.Ports { + if port.Name == name && port.Protocol == svcPort.Protocol { + return int(port.ContainerPort), nil + } + } + } + case intstr.Int: + return portName.IntValue(), nil + } + + return 0, fmt.Errorf("no suitable port for manifest: %s", pod.UID) +} + +// ContainerType signifies container type +type ContainerType int + +const ( + // Containers is for normal containers + Containers ContainerType = 1 << iota + // InitContainers is for init containers + InitContainers + // EphemeralContainers is for ephemeral containers + EphemeralContainers +) + +// AllContainers specifies that all containers be visited +const AllContainers ContainerType = InitContainers | Containers | EphemeralContainers + +// AllFeatureEnabledContainers returns a ContainerType mask which includes all container +// types except for the ones guarded by feature gate. +func AllFeatureEnabledContainers() ContainerType { + return AllContainers +} + +// ContainerVisitor is called with each container spec, and returns true +// if visiting should continue. +type ContainerVisitor func(container *v1.Container, containerType ContainerType) (shouldContinue bool) + +// Visitor is called with each object name, and returns true if visiting should continue +type Visitor func(name string) (shouldContinue bool) + +func skipEmptyNames(visitor Visitor) Visitor { + return func(name string) bool { + if len(name) == 0 { + // continue visiting + return true + } + // delegate to visitor + return visitor(name) + } +} + +// VisitContainers invokes the visitor function with a pointer to every container +// spec in the given pod spec with type set in mask. If visitor returns false, +// visiting is short-circuited. VisitContainers returns true if visiting completes, +// false if visiting was short-circuited. +func VisitContainers(podSpec *v1.PodSpec, mask ContainerType, visitor ContainerVisitor) bool { + if mask&InitContainers != 0 { + for i := range podSpec.InitContainers { + if !visitor(&podSpec.InitContainers[i], InitContainers) { + return false + } + } + } + if mask&Containers != 0 { + for i := range podSpec.Containers { + if !visitor(&podSpec.Containers[i], Containers) { + return false + } + } + } + if mask&EphemeralContainers != 0 { + for i := range podSpec.EphemeralContainers { + if !visitor((*v1.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), EphemeralContainers) { + return false + } + } + } + return true +} + +// VisitPodSecretNames invokes the visitor function with the name of every secret +// referenced by the pod spec. If visitor returns false, visiting is short-circuited. +// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited. +// Returns true if visiting completed, false if visiting was short-circuited. +func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool { + visitor = skipEmptyNames(visitor) + for _, reference := range pod.Spec.ImagePullSecrets { + if !visitor(reference.Name) { + return false + } + } + VisitContainers(&pod.Spec, AllContainers, func(c *v1.Container, containerType ContainerType) bool { + return visitContainerSecretNames(c, visitor) + }) + var source *v1.VolumeSource + + for i := range pod.Spec.Volumes { + source = &pod.Spec.Volumes[i].VolumeSource + switch { + case source.AzureFile != nil: + if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) { + return false + } + case source.CephFS != nil: + if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) { + return false + } + case source.Cinder != nil: + if source.Cinder.SecretRef != nil && !visitor(source.Cinder.SecretRef.Name) { + return false + } + case source.FlexVolume != nil: + if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) { + return false + } + case source.Projected != nil: + for j := range source.Projected.Sources { + if source.Projected.Sources[j].Secret != nil { + if !visitor(source.Projected.Sources[j].Secret.Name) { + return false + } + } + } + case source.RBD != nil: + if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) { + return false + } + case source.Secret != nil: + if !visitor(source.Secret.SecretName) { + return false + } + case source.ScaleIO != nil: + if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) { + return false + } + case source.ISCSI != nil: + if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) { + return false + } + case source.StorageOS != nil: + if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) { + return false + } + case source.CSI != nil: + if source.CSI.NodePublishSecretRef != nil && !visitor(source.CSI.NodePublishSecretRef.Name) { + return false + } + } + } + return true +} + +// visitContainerSecretNames returns true unless the visitor returned false when invoked with a secret reference +func visitContainerSecretNames(container *v1.Container, visitor Visitor) bool { + for _, env := range container.EnvFrom { + if env.SecretRef != nil { + if !visitor(env.SecretRef.Name) { + return false + } + } + } + for _, envVar := range container.Env { + if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil { + if !visitor(envVar.ValueFrom.SecretKeyRef.Name) { + return false + } + } + } + return true +} + +// VisitPodConfigmapNames invokes the visitor function with the name of every configmap +// referenced by the pod spec. If visitor returns false, visiting is short-circuited. +// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited. +// Returns true if visiting completed, false if visiting was short-circuited. +func VisitPodConfigmapNames(pod *v1.Pod, visitor Visitor) bool { + visitor = skipEmptyNames(visitor) + VisitContainers(&pod.Spec, AllContainers, func(c *v1.Container, containerType ContainerType) bool { + return visitContainerConfigmapNames(c, visitor) + }) + var source *v1.VolumeSource + for i := range pod.Spec.Volumes { + source = &pod.Spec.Volumes[i].VolumeSource + switch { + case source.Projected != nil: + for j := range source.Projected.Sources { + if source.Projected.Sources[j].ConfigMap != nil { + if !visitor(source.Projected.Sources[j].ConfigMap.Name) { + return false + } + } + } + case source.ConfigMap != nil: + if !visitor(source.ConfigMap.Name) { + return false + } + } + } + return true +} + +// visitContainerConfigmapNames returns true unless the visitor returned false when invoked with a configmap reference +func visitContainerConfigmapNames(container *v1.Container, visitor Visitor) bool { + for _, env := range container.EnvFrom { + if env.ConfigMapRef != nil { + if !visitor(env.ConfigMapRef.Name) { + return false + } + } + } + for _, envVar := range container.Env { + if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil { + if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) { + return false + } + } + } + return true +} + +// GetContainerStatus extracts the status of container "name" from "statuses". +// It returns true if "name" exists, else returns false. +func GetContainerStatus(statuses []v1.ContainerStatus, name string) (v1.ContainerStatus, bool) { + for i := range statuses { + if statuses[i].Name == name { + return statuses[i], true + } + } + return v1.ContainerStatus{}, false +} + +// GetExistingContainerStatus extracts the status of container "name" from "statuses", +// It also returns if "name" exists. +func GetExistingContainerStatus(statuses []v1.ContainerStatus, name string) v1.ContainerStatus { + status, _ := GetContainerStatus(statuses, name) + return status +} + +// GetIndexOfContainerStatus gets the index of status of container "name" from "statuses", +// It returns (index, true) if "name" exists, else returns (0, false). +func GetIndexOfContainerStatus(statuses []v1.ContainerStatus, name string) (int, bool) { + for i := range statuses { + if statuses[i].Name == name { + return i, true + } + } + return 0, false +} + +// IsPodAvailable returns true if a pod is available; false otherwise. +// Precondition for an available pod is that it must be ready. On top +// of that, there are two cases when a pod can be considered available: +// 1. minReadySeconds == 0, or +// 2. LastTransitionTime (is set) + minReadySeconds < current time +func IsPodAvailable(pod *v1.Pod, minReadySeconds int32, now metav1.Time) bool { + if !IsPodReady(pod) { + return false + } + + c := GetPodReadyCondition(pod.Status) + minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second + if minReadySeconds == 0 || (!c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time)) { + return true + } + return false +} + +// IsPodReady returns true if a pod is ready; false otherwise. +func IsPodReady(pod *v1.Pod) bool { + return IsPodReadyConditionTrue(pod.Status) +} + +// IsPodTerminal returns true if a pod is terminal, all containers are stopped and cannot ever regress. +func IsPodTerminal(pod *v1.Pod) bool { + return IsPodPhaseTerminal(pod.Status.Phase) +} + +// IsPodPhaseTerminal returns true if the pod's phase is terminal. +func IsPodPhaseTerminal(phase v1.PodPhase) bool { + return phase == v1.PodFailed || phase == v1.PodSucceeded +} + +// IsPodReadyConditionTrue returns true if a pod is ready; false otherwise. +func IsPodReadyConditionTrue(status v1.PodStatus) bool { + condition := GetPodReadyCondition(status) + return condition != nil && condition.Status == v1.ConditionTrue +} + +// IsContainersReadyConditionTrue returns true if a pod is ready; false otherwise. +func IsContainersReadyConditionTrue(status v1.PodStatus) bool { + condition := GetContainersReadyCondition(status) + return condition != nil && condition.Status == v1.ConditionTrue +} + +// GetPodReadyCondition extracts the pod ready condition from the given status and returns that. +// Returns nil if the condition is not present. +func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition { + _, condition := GetPodCondition(&status, v1.PodReady) + return condition +} + +// GetContainersReadyCondition extracts the containers ready condition from the given status and returns that. +// Returns nil if the condition is not present. +func GetContainersReadyCondition(status v1.PodStatus) *v1.PodCondition { + _, condition := GetPodCondition(&status, v1.ContainersReady) + return condition +} + +// GetPodCondition extracts the provided condition from the given status and returns that. +// Returns nil and -1 if the condition is not present, and the index of the located condition. +func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) { + if status == nil { + return -1, nil + } + return GetPodConditionFromList(status.Conditions, conditionType) +} + +// GetPodConditionFromList extracts the provided condition from the given list of condition and +// returns the index of the condition and the condition. Returns -1 and nil if the condition is not present. +func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) { + if conditions == nil { + return -1, nil + } + for i := range conditions { + if conditions[i].Type == conditionType { + return i, &conditions[i] + } + } + return -1, nil +} + +// UpdatePodCondition updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the +// status has changed. +// Returns true if pod condition has changed or has been added. +func UpdatePodCondition(status *v1.PodStatus, condition *v1.PodCondition) bool { + condition.LastTransitionTime = metav1.Now() + // Try to find this pod condition. + conditionIndex, oldCondition := GetPodCondition(status, condition.Type) + + if oldCondition == nil { + // We are adding new pod condition. + status.Conditions = append(status.Conditions, *condition) + return true + } + // We are updating an existing condition, so we need to check if it has changed. + if condition.Status == oldCondition.Status { + condition.LastTransitionTime = oldCondition.LastTransitionTime + } + + isEqual := condition.Status == oldCondition.Status && + condition.Reason == oldCondition.Reason && + condition.Message == oldCondition.Message && + condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) && + condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime) + + status.Conditions[conditionIndex] = *condition + // Return true if one of the fields have changed. + return !isEqual +} + +// IsRestartableInitContainer returns true if the container has ContainerRestartPolicyAlways. +// This function is not checking if the container passed to it is indeed an init container. +// It is just checking if the container restart policy has been set to always. +func IsRestartableInitContainer(initContainer *v1.Container) bool { + if initContainer == nil || initContainer.RestartPolicy == nil { + return false + } + return *initContainer.RestartPolicy == v1.ContainerRestartPolicyAlways +} diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index 5732a53975..53c3f3b497 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -1176,6 +1176,7 @@ k8s.io/kube-openapi/pkg/util/proto k8s.io/kube-openapi/pkg/validation/spec # k8s.io/kubernetes v1.32.6 ## explicit; go 1.23.0 +k8s.io/kubernetes/pkg/api/v1/pod k8s.io/kubernetes/pkg/apis/core k8s.io/kubernetes/pkg/probe k8s.io/kubernetes/pkg/probe/http From d942a7d81433a4f1dc1d12e59de1feede60254f3 Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Thu, 12 Jun 2025 18:07:52 +0530 Subject: [PATCH 147/278] Add unit tests for ex gw pods in terminating or not ready state Signed-off-by: arkadeepsen --- .../external_controller_namespace_test.go | 22 +- .../apbroute/external_controller_pod_test.go | 187 ++++++ .../external_controller_policy_test.go | 12 +- go-controller/pkg/ovn/egressgw_test.go | 586 ++++++++++++++++++ go-controller/pkg/ovn/pods_test.go | 6 + 5 files changed, 809 insertions(+), 4 deletions(-) diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller_namespace_test.go b/go-controller/pkg/ovn/controller/apbroute/external_controller_namespace_test.go index 57ab01d93b..6f521bf2bb 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller_namespace_test.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller_namespace_test.go @@ -201,14 +201,32 @@ var _ = Describe("OVN External Gateway namespace", func() { "k8s.ovn.org/routing-network": "", nettypes.NetworkStatusAnnot: fmt.Sprintf(network_status, annotatedPodIP)}, }, - Status: corev1.PodStatus{PodIPs: []corev1.PodIP{{IP: annotatedPodIP}}, Phase: corev1.PodRunning}, + Status: corev1.PodStatus{ + PodIPs: []corev1.PodIP{{IP: annotatedPodIP}}, + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + }, + }, } podGW = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: namespaceGW.Name, Labels: map[string]string{"name": "pod"}, Annotations: map[string]string{nettypes.NetworkStatusAnnot: fmt.Sprintf(network_status, dynamicHopHostNetPodIP)}}, - Status: corev1.PodStatus{PodIPs: []corev1.PodIP{{IP: dynamicHopHostNetPodIP}}, Phase: corev1.PodRunning}, + Status: corev1.PodStatus{ + PodIPs: []corev1.PodIP{{IP: dynamicHopHostNetPodIP}}, + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + }, + }, } namespaceTargetWithPod, namespaceTarget2WithPod, namespaceTarget2WithoutPod, namespaceGWWithPod *namespaceWithPods ) diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller_pod_test.go b/go-controller/pkg/ovn/controller/apbroute/external_controller_pod_test.go index 509940c730..7cbbcd7430 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller_pod_test.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller_pod_test.go @@ -448,6 +448,163 @@ var _ = Describe("OVN External Gateway pod", func() { }) }) + + var _ = Context("When pod goes into terminating or not ready state", func() { + + DescribeTable("reconciles a pod gateway in terminating or not ready state that matches two policies", func( + terminating bool, + ) { + initController([]runtime.Object{namespaceGW, namespaceTarget, namespaceTarget2, targetPod1, targetPod2, pod1}, + []runtime.Object{dynamicPolicy, dynamicPolicyDiffTargetNS}) + + expectedPolicy1, expectedRefs1 := expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTarget2WithPod}, + nil, + []*namespaceWithPods{namespaceGWWithPod}, false) + + expectedPolicy2, expectedRefs2 := expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithPod}, + nil, + []*namespaceWithPods{namespaceGWWithPod}, false) + + eventuallyExpectNumberOfPolicies(2) + eventuallyExpectConfig(dynamicPolicy.Name, expectedPolicy1, expectedRefs1) + eventuallyExpectConfig(dynamicPolicyDiffTargetNS.Name, expectedPolicy2, expectedRefs2) + + if terminating { + By("Setting deletion timestamp for the ex gw pod") + setPodDeletionTimestamp(pod1, &metav1.Time{Time: time.Now().Add(1000 * time.Second)}, fakeClient) + } else { + By("Updating the ex gw pod status to mark it as not ready") + setPodConditionReady(pod1, corev1.ConditionFalse, fakeClient) + } + + expectedPolicy1, expectedRefs1 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTarget2WithPod}, + nil, + []*namespaceWithPods{namespaceGWWithoutPod}, false) + + expectedPolicy2, expectedRefs2 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithPod}, + nil, + []*namespaceWithPods{namespaceGWWithoutPod}, false) + + eventuallyExpectNumberOfPolicies(2) + eventuallyExpectConfig(dynamicPolicy.Name, expectedPolicy1, expectedRefs1) + eventuallyExpectConfig(dynamicPolicyDiffTargetNS.Name, expectedPolicy2, expectedRefs2) + }, + Entry("Gateway pod in terminating state", true), + Entry("Gateway pod in not ready state", false), + ) + + DescribeTable("reconciles a pod gateway in terminating or not ready state that does not match any policy", func( + terminating bool, + ) { + noMatchPolicy := newPolicy( + "noMatchPolicy", + &metav1.LabelSelector{MatchLabels: targetNamespace1Match}, + nil, + &metav1.LabelSelector{MatchLabels: gatewayNamespaceMatch}, + &metav1.LabelSelector{MatchLabels: map[string]string{"key": "nomatch"}}, + false, + ) + initController([]runtime.Object{namespaceGW, namespaceTarget, pod1}, []runtime.Object{noMatchPolicy}) + + expectedPolicy, expectedRefs := expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithoutPod}, + nil, + []*namespaceWithPods{namespaceGWWithoutPod}, false) + + eventuallyExpectNumberOfPolicies(1) + eventuallyExpectConfig(noMatchPolicy.Name, expectedPolicy, expectedRefs) + + if terminating { + By("Setting deletion timestamp for the ex gw pod") + setPodDeletionTimestamp(pod1, &metav1.Time{Time: time.Now().Add(1000 * time.Second)}, fakeClient) + } else { + By("Updating the ex gw pod status to mark it as not ready") + setPodConditionReady(pod1, corev1.ConditionFalse, fakeClient) + } + // make sure pod event is handled + time.Sleep(100 * time.Millisecond) + + eventuallyExpectNumberOfPolicies(1) + eventuallyExpectConfig(noMatchPolicy.Name, expectedPolicy, expectedRefs) + }, + Entry("Gateway pod in terminating state", true), + Entry("Gateway pod in not ready state", false), + ) + + DescribeTable("reconciles a pod gateway in terminating or not ready state that is one of two pods that matches two policies", func( + terminating bool, + ) { + initController([]runtime.Object{namespaceGW, namespaceTarget, namespaceTarget2, targetPod1, targetPod2, pod1, pod2}, + []runtime.Object{dynamicPolicy, dynamicPolicyDiffTargetNS}) + namespaceGWWith2Pods := newNamespaceWithPods(namespaceGW.Name, pod1, pod2) + expectedPolicy1, expectedRefs1 := expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTarget2WithPod}, + nil, + []*namespaceWithPods{namespaceGWWith2Pods}, false) + + expectedPolicy2, expectedRefs2 := expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithPod}, + nil, + []*namespaceWithPods{namespaceGWWith2Pods}, false) + + eventuallyExpectNumberOfPolicies(2) + eventuallyExpectConfig(dynamicPolicy.Name, expectedPolicy1, expectedRefs1) + eventuallyExpectConfig(dynamicPolicyDiffTargetNS.Name, expectedPolicy2, expectedRefs2) + + if terminating { + By("Setting deletion timestamp for the ex gw pod") + setPodDeletionTimestamp(pod1, &metav1.Time{Time: time.Now().Add(1000 * time.Second)}, fakeClient) + } else { + By("Updating the ex gw pod status to mark it as not ready") + setPodConditionReady(pod1, corev1.ConditionFalse, fakeClient) + } + + namespaceGWWith1Pod := newNamespaceWithPods(namespaceGW.Name, pod2) + + expectedPolicy1, expectedRefs1 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTarget2WithPod}, + nil, + []*namespaceWithPods{namespaceGWWith1Pod}, false) + + expectedPolicy2, expectedRefs2 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithPod}, + nil, + []*namespaceWithPods{namespaceGWWith1Pod}, false) + + eventuallyExpectNumberOfPolicies(2) + eventuallyExpectConfig(dynamicPolicy.Name, expectedPolicy1, expectedRefs1) + eventuallyExpectConfig(dynamicPolicyDiffTargetNS.Name, expectedPolicy2, expectedRefs2) + + if terminating { + By("Removing deletion timestamp for the ex gw pod") + setPodDeletionTimestamp(pod1, nil, fakeClient) + } else { + By("Updating the ex gw pod status to mark it as ready") + setPodConditionReady(pod1, corev1.ConditionTrue, fakeClient) + } + + expectedPolicy1, expectedRefs1 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTarget2WithPod}, + nil, + []*namespaceWithPods{namespaceGWWith2Pods}, false) + + expectedPolicy2, expectedRefs2 = expectedPolicyStateAndRefs( + []*namespaceWithPods{namespaceTargetWithPod}, + nil, + []*namespaceWithPods{namespaceGWWith2Pods}, false) + + eventuallyExpectNumberOfPolicies(2) + eventuallyExpectConfig(dynamicPolicy.Name, expectedPolicy1, expectedRefs1) + eventuallyExpectConfig(dynamicPolicyDiffTargetNS.Name, expectedPolicy2, expectedRefs2) + }, + Entry("Gateway pod in terminating state", true), + Entry("Gateway pod in not ready state", false), + ) + }) }) func deletePod(pod *corev1.Pod, fakeClient *fake.Clientset) { @@ -478,6 +635,36 @@ func updatePodStatus(pod *corev1.Pod, podStatus corev1.PodStatus) { Expect(err).NotTo(HaveOccurred()) } +func setPodDeletionTimestamp(pod *corev1.Pod, deletionTimestamp *metav1.Time, fakeClient *fake.Clientset) { + p, err := fakeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + incrementResourceVersion(p) + p.DeletionTimestamp = deletionTimestamp + _, err = fakeClient.CoreV1().Pods(pod.Namespace).Update(context.Background(), p, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) +} + +func setPodConditionReady(pod *corev1.Pod, condStatus corev1.ConditionStatus, fakeClient *fake.Clientset) { + p, err := fakeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + incrementResourceVersion(p) + if p.Status.Conditions != nil { + for i := range p.Status.Conditions { + if p.Status.Conditions[i].Type == corev1.PodReady { + p.Status.Conditions[i].Status = condStatus + } + } + } else { + notReadyCondition := corev1.PodCondition{ + Type: corev1.PodReady, + Status: corev1.ConditionFalse, + } + p.Status.Conditions = []corev1.PodCondition{notReadyCondition} + } + _, err = fakeClient.CoreV1().Pods(pod.Namespace).Update(context.Background(), p, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) +} + func incrementResourceVersion(obj metav1.Object) { var rs int64 if obj.GetResourceVersion() != "" { diff --git a/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go b/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go index 2605fad7bc..266312ce2c 100644 --- a/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go +++ b/go-controller/pkg/ovn/controller/apbroute/external_controller_policy_test.go @@ -40,8 +40,16 @@ func newPodWithPhaseAndIP(podName, namespace string, phase corev1.PodPhase, podI p := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: namespace, Labels: labels}, - Spec: corev1.PodSpec{NodeName: "node"}, - Status: corev1.PodStatus{Phase: phase}, + Spec: corev1.PodSpec{NodeName: "node"}, + Status: corev1.PodStatus{ + Phase: phase, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + }, + }, } if len(podIP) > 0 { p.Annotations = map[string]string{nettypes.NetworkStatusAnnot: fmt.Sprintf(network_status, podIP)} diff --git a/go-controller/pkg/ovn/egressgw_test.go b/go-controller/pkg/ovn/egressgw_test.go index 9696d4192b..420f2f26e1 100644 --- a/go-controller/pkg/ovn/egressgw_test.go +++ b/go-controller/pkg/ovn/egressgw_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "sync" + "time" nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "github.com/onsi/ginkgo/v2" @@ -1818,6 +1819,591 @@ var _ = ginkgo.Describe("OVN Egress Gateway Operations", func() { }, ), ) + ginkgo.DescribeTable("reconciles a host networked pod in terminating or not ready state acting as a exgw for another namespace for existing pod", + func(bfd bool, + terminating bool, + beforeUpdateNB []libovsdbtest.TestData, + afterUpdateNB []libovsdbtest.TestData, + expectedNamespaceAnnotation string, + apbExternalRouteCRList *adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList) { + app.Action = func(*cli.Context) error { + + namespaceT := *newNamespace(namespaceName) + namespaceX := *newNamespace(namespace2Name) + t := newTPod( + "node1", + "10.128.1.0/24", + "10.128.1.2", + "10.128.1.1", + "myPod", + "10.128.1.3", + "0a:58:0a:80:01:03", + namespaceT.Name, + ) + gwPod := *newPod(namespaceX.Name, gwPodName, "node2", "9.0.0.1") + gwPod.Annotations = map[string]string{"k8s.ovn.org/routing-namespaces": namespaceT.Name} + if bfd { + gwPod.Annotations["k8s.ovn.org/bfd-enabled"] = "" + } + gwPod.Spec.HostNetwork = true + fakeOvn.startWithDBSetup( + libovsdbtest.TestSetup{ + NBData: []libovsdbtest.TestData{ + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + }, + }, + }, + &corev1.NamespaceList{ + Items: []corev1.Namespace{ + namespaceT, namespaceX, + }, + }, + &corev1.NodeList{ + Items: []corev1.Node{ + *newNode("node1", "192.168.126.202/24"), + *newNode("node2", "192.168.126.50/24"), + }, + }, + &corev1.PodList{ + Items: []corev1.Pod{ + *newPod(t.namespace, t.podName, t.nodeName, t.podIP), + }, + }, + apbExternalRouteCRList, + ) + t.populateLogicalSwitchCache(fakeOvn) + err := fakeOvn.controller.lsManager.AddOrUpdateSwitch("node2", []*net.IPNet{ovntest.MustParseIPNet("10.128.2.0/24")}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + injectNode(fakeOvn) + err = fakeOvn.controller.WatchNamespaces() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = fakeOvn.controller.WatchPods() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + fakeOvn.RunAPBExternalPolicyController() + + _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Pods(namespaceX.Name).Create(context.TODO(), &gwPod, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(beforeUpdateNB)) + gomega.Eventually(func() string { + return getNamespaceAnnotations(fakeOvn.fakeClient.KubeClient, namespaceT.Name)[util.ExternalGatewayPodIPsAnnotation] + }).Should(gomega.Equal("9.0.0.1")) + + if terminating { + ginkgo.By("Setting deletion timestamp for the ex gw pod") + gwPod.DeletionTimestamp = &metav1.Time{Time: time.Now().Add(1000 * time.Second)} + _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Pods(namespaceX.Name).Update(context.TODO(), &gwPod, metav1.UpdateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } else { + ginkgo.By("Updating the ex gw pod status to mark it as not ready") + notReadyCondition := corev1.PodCondition{ + Type: corev1.PodReady, + Status: corev1.ConditionFalse, + } + gwPod.Status.Conditions = []corev1.PodCondition{notReadyCondition} + _, err = fakeOvn.fakeClient.KubeClient.CoreV1().Pods(namespaceX.Name).UpdateStatus(context.TODO(), &gwPod, metav1.UpdateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + + gomega.Eventually(fakeOvn.nbClient).Should(libovsdbtest.HaveData(afterUpdateNB)) + gomega.Eventually(func() string { + return getNamespaceAnnotations(fakeOvn.fakeClient.KubeClient, namespaceT.Name)[util.ExternalGatewayPodIPsAnnotation] + }).Should(gomega.Equal(expectedNamespaceAnnotation)) + for _, apbRoutePolicy := range apbExternalRouteCRList.Items { + checkAPBRouteStatus(fakeOvn, apbRoutePolicy.Name, false) + } + return nil + } + + err := app.Run([]string{app.Name}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }, + ginkgo.Entry("No BFD with ex gw pod in terminating state", false, true, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{}, + ), + ginkgo.Entry("No BFD with ex gw pod in not ready state", false, false, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{}, + ), + ginkgo.Entry("BFD Enabled with ex gw pod in terminating state", true, true, []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.BFD{ + UUID: bfd1NamedUUID, + DstIP: "9.0.0.1", + LogicalPort: "rtoe-GR_node1", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + BFD: &bfd1NamedUUID, + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{}, + ), + ginkgo.Entry("BFD Enabled with ex gw pod in not ready state", true, false, []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.BFD{ + UUID: bfd1NamedUUID, + DstIP: "9.0.0.1", + LogicalPort: "rtoe-GR_node1", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + BFD: &bfd1NamedUUID, + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{}, + ), + ginkgo.Entry("No BFD with ex gw pod in terminating state and with overlapping APB External Route CR and annotation", false, true, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{ + Items: []adminpolicybasedrouteapi.AdminPolicyBasedExternalRoute{ + newPolicy("policy", + &metav1.LabelSelector{MatchLabels: map[string]string{"name": namespaceName}}, + nil, + false, + &metav1.LabelSelector{MatchLabels: map[string]string{"name": namespace2Name}}, + &metav1.LabelSelector{MatchLabels: map[string]string{"name": gwPodName}}, + false, + ""), + }, + }, + ), + ginkgo.Entry("No BFD with ex gw pod in not ready state and with overlapping APB External Route CR and annotation", false, false, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouterStaticRoute{ + UUID: "static-route-1-UUID", + IPPrefix: "10.128.1.3/32", + Nexthop: "9.0.0.1", + Policy: &nbdb.LogicalRouterStaticRoutePolicySrcIP, + OutputPort: &logicalRouterPort, + Options: map[string]string{ + "ecmp_symmetric_reply": "true", + }, + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{"static-route-1-UUID"}, + }, + }, + []libovsdbtest.TestData{ + &nbdb.LogicalSwitchPort{ + UUID: "lsp1", + Addresses: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + ExternalIDs: map[string]string{ + "pod": "true", + "namespace": namespaceName, + }, + Name: "namespace1_myPod", + Options: map[string]string{ + "iface-id-ver": "myPod", + "requested-chassis": "node1", + }, + PortSecurity: []string{"0a:58:0a:80:01:03 10.128.1.3"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node1", + Name: "node1", + Ports: []string{"lsp1"}, + }, + &nbdb.LogicalSwitch{ + UUID: "node2", + Name: "node2", + }, + &nbdb.LogicalRouter{ + UUID: "GR_node1-UUID", + Name: "GR_node1", + StaticRoutes: []string{}, + }, + }, + "", + &adminpolicybasedrouteapi.AdminPolicyBasedExternalRouteList{ + Items: []adminpolicybasedrouteapi.AdminPolicyBasedExternalRoute{ + newPolicy("policy", + &metav1.LabelSelector{MatchLabels: map[string]string{"name": namespaceName}}, + nil, + false, + &metav1.LabelSelector{MatchLabels: map[string]string{"name": namespace2Name}}, + &metav1.LabelSelector{MatchLabels: map[string]string{"name": gwPodName}}, + false, + ""), + }, + }, + ), + ) }) ginkgo.Context("on using bfd", func() { ginkgo.It("should enable bfd only on the namespace gw when set", func() { diff --git a/go-controller/pkg/ovn/pods_test.go b/go-controller/pkg/ovn/pods_test.go index cf1caae6e7..590d34bf3a 100644 --- a/go-controller/pkg/ovn/pods_test.go +++ b/go-controller/pkg/ovn/pods_test.go @@ -124,6 +124,12 @@ func newPod(namespace, name, node, podIP string) *corev1.Pod { Phase: corev1.PodRunning, PodIP: podIP, PodIPs: podIPs, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + }, }, } } From d565fd869e156bd4f299a688794a272647cb0dfe Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Thu, 12 Jun 2025 23:20:39 +0530 Subject: [PATCH 148/278] Add e2e tests for ex gw pods in terminating or not ready state Signed-off-by: arkadeepsen --- test/e2e/external_gateways.go | 213 +++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 27 deletions(-) diff --git a/test/e2e/external_gateways.go b/test/e2e/external_gateways.go index 4a119ae96b..bf3742ea68 100644 --- a/test/e2e/external_gateways.go +++ b/test/e2e/external_gateways.go @@ -42,6 +42,16 @@ const ( anyLink = "any" ) +// GatewayRemovalType defines ways to remove pod as external gateway +type GatewayRemovalType string + +const ( + GatewayUpdate GatewayRemovalType = "GatewayUpdate" + GatewayDelete GatewayRemovalType = "GatewayDelete" + GatewayDeletionTimestamp GatewayRemovalType = "GatewayDeletionTimestamp" + GatewayNotReady GatewayRemovalType = "GatewayNotReady" +) + func getOverrideNetwork() (string, string, string) { // When the env variable is specified, we use a different docker network for // containers acting as external gateways. @@ -875,10 +885,15 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { ginkgo.Entry("IPV6 udp", &addressesv6, "udp"), ginkgo.Entry("IPV6 tcp", &addressesv6, "tcp")) - ginkgo.DescribeTable("ExternalGWPod annotation: Should validate conntrack entry deletion for TCP/UDP traffic via multiple external gateways a.k.a ECMP routes", func(addresses *gatewayTestIPs, protocol string, deletePod bool) { + ginkgo.DescribeTable("ExternalGWPod annotation: Should validate conntrack entry deletion for TCP/UDP traffic via multiple external gateways a.k.a ECMP routes", func(addresses *gatewayTestIPs, protocol string, removalType GatewayRemovalType) { if addresses.srcPodIP == "" || addresses.nodeIP == "" { skipper.Skipf("Skipping as pod ip / node ip are not set pod ip %s node ip %s", addresses.srcPodIP, addresses.nodeIP) } + + if removalType == GatewayNotReady { + recreatePodWithReadinessProbe(f, gatewayPodName2, nodes.Items[1].Name, servingNamespace, sleepCommand, nil) + } + ginkgo.By("Annotate the external gw pods to manage the src app pod namespace") for i, gwPod := range []string{gatewayPodName1, gatewayPodName2} { networkIPs := fmt.Sprintf("\"%s\"", addresses.gatewayIPs[i]) @@ -925,15 +940,9 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { totalPodConnEntries := pokeConntrackEntries(nodeName, addresses.srcPodIP, protocol, nil) gomega.Expect(totalPodConnEntries).To(gomega.Equal(6)) // total conntrack entries for this pod/protocol - if deletePod { - ginkgo.By(fmt.Sprintf("Delete second external gateway pod %s from ns %s", gatewayPodName2, servingNamespace)) - err = f.ClientSet.CoreV1().Pods(servingNamespace).Delete(context.TODO(), gatewayPodName2, metav1.DeleteOptions{}) - framework.ExpectNoError(err, "Delete the gateway pod failed: %v", err) - // give some time to handle pod delete event - time.Sleep(5 * time.Second) - } else { - ginkgo.By("Remove second external gateway pod's routing-namespace annotation") - annotatePodForGateway(gatewayPodName2, servingNamespace, "", addresses.gatewayIPs[1], false) + cleanUpFn := handleGatewayPodRemoval(f, removalType, gatewayPodName2, servingNamespace, addresses.gatewayIPs[1], true) + if cleanUpFn != nil { + defer cleanUpFn() } // ensure the conntrack deletion tracker annotation is updated @@ -973,12 +982,20 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { gomega.Expect(podConnEntriesWithMACLabelsSet).To(gomega.Equal(0)) // we don't have any remaining gateways left gomega.Expect(totalPodConnEntries).To(gomega.Equal(4)) // 6-2 }, - ginkgo.Entry("IPV4 udp", &addressesv4, "udp", false), - ginkgo.Entry("IPV4 tcp", &addressesv4, "tcp", false), - ginkgo.Entry("IPV6 udp", &addressesv6, "udp", false), - ginkgo.Entry("IPV6 tcp", &addressesv6, "tcp", false), - ginkgo.Entry("IPV4 udp + pod delete", &addressesv4, "udp", true), - ginkgo.Entry("IPV6 tcp + pod delete", &addressesv6, "tcp", true), + ginkgo.Entry("IPV4 udp + pod annotation update", &addressesv4, "udp", GatewayUpdate), + ginkgo.Entry("IPV4 tcp + pod annotation update", &addressesv4, "tcp", GatewayUpdate), + ginkgo.Entry("IPV6 udp + pod annotation update", &addressesv6, "udp", GatewayUpdate), + ginkgo.Entry("IPV6 tcp + pod annotation update", &addressesv6, "tcp", GatewayUpdate), + ginkgo.Entry("IPV4 udp + pod delete", &addressesv4, "udp", GatewayDelete), + ginkgo.Entry("IPV6 tcp + pod delete", &addressesv6, "tcp", GatewayDelete), + ginkgo.Entry("IPV4 udp + pod deletion timestamp", &addressesv4, "udp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV4 tcp + pod deletion timestamp", &addressesv4, "tcp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV6 udp + pod deletion timestamp", &addressesv6, "udp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV6 tcp + pod deletion timestamp", &addressesv6, "tcp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV4 udp + pod not ready", &addressesv4, "udp", GatewayNotReady), + ginkgo.Entry("IPV4 tcp + pod not ready", &addressesv4, "tcp", GatewayNotReady), + ginkgo.Entry("IPV6 udp + pod not ready", &addressesv6, "udp", GatewayNotReady), + ginkgo.Entry("IPV6 tcp + pod not ready", &addressesv6, "tcp", GatewayNotReady), ) }) @@ -1983,11 +2000,15 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { ginkgo.Entry("IPV6 udp", &addressesv6, "udp"), ginkgo.Entry("IPV6 tcp", &addressesv6, "tcp")) - ginkgo.DescribeTable("Dynamic Hop: Should validate conntrack entry deletion for TCP/UDP traffic via multiple external gateways a.k.a ECMP routes", func(addresses *gatewayTestIPs, protocol string) { + ginkgo.DescribeTable("Dynamic Hop: Should validate conntrack entry deletion for TCP/UDP traffic via multiple external gateways a.k.a ECMP routes", func(addresses *gatewayTestIPs, protocol string, removalType GatewayRemovalType) { if addresses.srcPodIP == "" || addresses.nodeIP == "" { skipper.Skipf("Skipping as pod ip / node ip are not set pod ip %s node ip %s", addresses.srcPodIP, addresses.nodeIP) } + if removalType == GatewayNotReady { + recreatePodWithReadinessProbe(f, gatewayPodName2, nodes.Items[1].Name, servingNamespace, sleepCommand, map[string]string{"name": gatewayPodName2, "gatewayPod": "true"}) + } + for i, gwPod := range []string{gatewayPodName1, gatewayPodName2} { annotateMultusNetworkStatusInPodGateway(gwPod, servingNamespace, []string{addresses.gatewayIPs[i], addresses.gatewayIPs[i]}) } @@ -2026,10 +2047,10 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { }, time.Minute, 5).Should(gomega.Equal(podConnEntriesWithMACLabelsSet)) gomega.Expect(pokeConntrackEntries(nodeName, addresses.srcPodIP, protocol, nil)).To(gomega.Equal(totalPodConnEntries)) // total conntrack entries for this pod/protocol - ginkgo.By("Remove second external gateway pod's routing-namespace annotation") - p := getGatewayPod(f, servingNamespace, gatewayPodName2) - p.Labels = map[string]string{"name": gatewayPodName2} - updatePod(f, p) + cleanUpFn := handleGatewayPodRemoval(f, removalType, gatewayPodName2, servingNamespace, addresses.gatewayIPs[1], false) + if cleanUpFn != nil { + defer cleanUpFn() + } ginkgo.By("Check if conntrack entries for ECMP routes are removed for the deleted external gateway if traffic is UDP") @@ -2044,7 +2065,7 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { gomega.Expect(pokeConntrackEntries(nodeName, addresses.srcPodIP, protocol, nil)).To(gomega.Equal(totalPodConnEntries)) ginkgo.By("Remove first external gateway pod's routing-namespace annotation") - p = getGatewayPod(f, servingNamespace, gatewayPodName1) + p := getGatewayPod(f, servingNamespace, gatewayPodName1) p.Labels = map[string]string{"name": gatewayPodName1} updatePod(f, p) @@ -2060,11 +2081,19 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { gomega.Expect(pokeConntrackEntries(nodeName, addresses.srcPodIP, protocol, nil)).To(gomega.Equal(totalPodConnEntries)) checkAPBExternalRouteStatus(defaultPolicyName) }, - ginkgo.Entry("IPV4 udp", &addressesv4, "udp"), - ginkgo.Entry("IPV4 tcp", &addressesv4, "tcp"), - ginkgo.Entry("IPV6 udp", &addressesv6, "udp"), - ginkgo.Entry("IPV6 tcp", &addressesv6, "tcp")) - + ginkgo.Entry("IPV4 udp + pod annotation update", &addressesv4, "udp", GatewayUpdate), + ginkgo.Entry("IPV4 tcp + pod annotation update", &addressesv4, "tcp", GatewayUpdate), + ginkgo.Entry("IPV6 udp + pod annotation update", &addressesv6, "udp", GatewayUpdate), + ginkgo.Entry("IPV6 tcp + pod annotation update", &addressesv6, "tcp", GatewayUpdate), + ginkgo.Entry("IPV4 udp + pod deletion timestamp", &addressesv4, "udp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV4 tcp + pod deletion timestamp", &addressesv4, "tcp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV6 udp + pod deletion timestamp", &addressesv6, "udp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV6 tcp + pod deletion timestamp", &addressesv6, "tcp", GatewayDeletionTimestamp), + ginkgo.Entry("IPV4 udp + pod not ready", &addressesv4, "udp", GatewayNotReady), + ginkgo.Entry("IPV4 tcp + pod not ready", &addressesv4, "tcp", GatewayNotReady), + ginkgo.Entry("IPV6 udp + pod not ready", &addressesv6, "udp", GatewayNotReady), + ginkgo.Entry("IPV6 tcp + pod not ready", &addressesv6, "tcp", GatewayNotReady), + ) }) // BFD Tests are dual of external gateway. The only difference is that they enable BFD on ovn and @@ -3595,3 +3624,133 @@ func resetGatewayAnnotations(f *framework.Framework) { annotation}...) } } + +func setupPodWithReadinessProbe(f *framework.Framework, podName, nodeSelector, namespace string, command []string, labels map[string]string) (*corev1.Pod, error) { + // Handle bash -c commands specially to preserve argument structure + if len(command) >= 3 && command[0] == "bash" && command[1] == "-c" { + // Extract the script part and wrap it to preserve logic + script := strings.Join(command[2:], " ") + command = []string{"bash", "-c", "touch /tmp/ready && (" + script + ")"} + } else { + // For non-bash commands, preserve their structure + var quotedArgs []string + for _, arg := range command { + // Escape single quotes and wrap in single quotes + escaped := strings.ReplaceAll(arg, "'", "'\"'\"'") + quotedArgs = append(quotedArgs, "'"+escaped+"'") + } + command = []string{"bash", "-c", "touch /tmp/ready && " + strings.Join(quotedArgs, " ")} + } + return createPod(f, podName, nodeSelector, namespace, command, labels, func(p *corev1.Pod) { + p.Spec.Containers[0].ReadinessProbe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"cat", "/tmp/ready"}, + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 5, + FailureThreshold: 1, + } + }) +} + +func recreatePodWithReadinessProbe(f *framework.Framework, podName, nodeSelector, namespace string, command []string, labels map[string]string) { + ginkgo.By(fmt.Sprintf("Delete second external gateway pod %s from ns %s", podName, namespace)) + err := deletePodWithWaitByName(context.TODO(), f.ClientSet, podName, namespace) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), fmt.Sprintf("Delete second external gateway pod %s from ns %s, failed: %v", podName, namespace, err)) + + ginkgo.By(fmt.Sprintf("Create second external gateway pod %s from ns %s with readiness probe", podName, namespace)) + _, err = setupPodWithReadinessProbe(f, podName, nodeSelector, namespace, command, labels) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), fmt.Sprintf("Create second external gateway pod %s from ns %s with readiness probe, failed: %v", podName, namespace, err)) + gomega.Eventually(func() bool { + var p *corev1.Pod + p, err = f.ClientSet.CoreV1().Pods(namespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return false + } + for _, condition := range p.Status.Conditions { + if condition.Type == corev1.PodReady { + return condition.Status == corev1.ConditionTrue + } + } + return false + }).Should(gomega.Equal(true), fmt.Sprintf("Readiness probe for second external gateway pod %s from ns %s, failed: %v", podName, namespace, err)) +} + +func handleGatewayPodRemoval(f *framework.Framework, removalType GatewayRemovalType, gatewayPodName, servingNamespace, gatewayIP string, isAnnotated bool) func() { + var err error + switch removalType { + case GatewayDelete: + ginkgo.By(fmt.Sprintf("Delete second external gateway pod %s from ns %s", gatewayPodName, servingNamespace)) + err := deletePodWithWaitByName(context.TODO(), f.ClientSet, gatewayPodName, servingNamespace) + framework.ExpectNoError(err, "Delete the gateway pod failed: %v", err) + return nil + case GatewayUpdate: + if isAnnotated { + ginkgo.By("Remove second external gateway pod's routing-namespace annotation") + annotatePodForGateway(gatewayPodName, servingNamespace, "", gatewayIP, false) + return nil + } + + ginkgo.By("Updating external gateway pod labels") + p := getGatewayPod(f, servingNamespace, gatewayPodName) + p.Labels = map[string]string{"name": gatewayPodName} + updatePod(f, p) + return nil + case GatewayDeletionTimestamp: + ginkgo.By("Setting finalizer then deleting external gateway pod with grace period to set deletion timestamp") + p := getGatewayPod(f, servingNamespace, gatewayPodName) + p.Finalizers = append(p.Finalizers, "k8s.ovn.org/external-gw-pod-finalizer") + updatePod(f, p) + gomega.Eventually(func() bool { + p, err = f.ClientSet.CoreV1().Pods(servingNamespace).Get(context.Background(), gatewayPodName, metav1.GetOptions{}) + if err != nil { + return false + } + return strings.Contains(strings.Join(p.GetFinalizers(), ","), "k8s.ovn.org/external-gw-pod-finalizer") + }).Should(gomega.Equal(true), fmt.Sprintf("Update second external gateway pod %s from ns %s with finalizer, failed: %v", gatewayPodName, servingNamespace, err)) + + p = getGatewayPod(f, servingNamespace, gatewayPodName) + err = e2epod.DeletePodWithGracePeriod(context.Background(), f.ClientSet, p, 1000) + framework.ExpectNoError(err, fmt.Sprintf("unable to delete pod with grace period: %s, err: %v", p.Name, err)) + + gomega.Eventually(func() bool { + p, err = f.ClientSet.CoreV1().Pods(servingNamespace).Get(context.Background(), gatewayPodName, metav1.GetOptions{}) + if err != nil { + return false + } + return p.DeletionTimestamp != nil + }).Should(gomega.BeTrue(), fmt.Sprintf("Gateway pod %s in ns %s should have deletion timestamp, failed: %v", gatewayPodName, servingNamespace, err)) + + // return a function to remove the finalizer + return func() { + p = getGatewayPod(f, servingNamespace, gatewayPodName) + p.Finalizers = []string{} + updatePod(f, p) + } + case GatewayNotReady: + ginkgo.By("Remove /tmp/ready in external gateway pod so that readiness probe fails") + _, err = e2ekubectl.RunKubectl(servingNamespace, "exec", gatewayPodName, "--", "rm", "/tmp/ready") + framework.ExpectNoError(err, fmt.Sprintf("unable to remove /tmp/ready in pod: %s, err: %v", gatewayPodName, err)) + gomega.Eventually(func() bool { + var p *corev1.Pod + p, err = f.ClientSet.CoreV1().Pods(servingNamespace).Get(context.Background(), gatewayPodName, metav1.GetOptions{}) + if err != nil { + return false + } + podReadyStatus := corev1.ConditionTrue + for _, condition := range p.Status.Conditions { + if condition.Type == corev1.PodReady { + podReadyStatus = condition.Status + break + } + } + return podReadyStatus == corev1.ConditionFalse + }).WithTimeout(5*time.Minute).Should(gomega.Equal(true), fmt.Sprintf("Mark second external gateway pod %s from ns %s not ready, failed: %v", gatewayPodName, servingNamespace, err)) + return nil + default: + framework.Failf("unexpected GatewayRemovalType passed: %s", removalType) + return nil + } +} From fa12bb26eac95b0730e77ff311f39900fc1669f8 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 21 Jul 2025 12:09:46 -0400 Subject: [PATCH 149/278] Bump fedora from 41 -> 42 Brings in a new OVS that includes fix: https://github.com/openvswitch/ovs/commit/a119828ea608c38611f6ee60e55a7376ca471d6f Signed-off-by: Tim Rozet --- dist/images/Dockerfile.fedora | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dist/images/Dockerfile.fedora b/dist/images/Dockerfile.fedora index 49b8da6872..4ca51e888f 100644 --- a/dist/images/Dockerfile.fedora +++ b/dist/images/Dockerfile.fedora @@ -14,7 +14,7 @@ ARG OVN_FROM=koji ############################################# # Stage to get OVN and OVS RPMs from source # ############################################# -FROM quay.io/fedora/fedora:41 AS ovnbuilder +FROM quay.io/fedora/fedora:42 AS ovnbuilder USER root @@ -78,8 +78,8 @@ RUN git log -n 1 ######################################## # Stage to download OVN RPMs from koji # ######################################## -FROM quay.io/fedora/fedora:41 AS kojidownloader -ARG ovnver=ovn-24.09.2-71.fc41 +FROM quay.io/fedora/fedora:42 AS kojidownloader +ARG ovnver=ovn-24.09.2-71.fc42 USER root @@ -99,14 +99,14 @@ RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] || [ -z "$TARGETPLATFORM"] ; then k ###################################### # Stage to copy OVN RPMs from source # ###################################### -FROM quay.io/fedora/fedora:41 AS source +FROM quay.io/fedora/fedora:42 AS source COPY --from=ovnbuilder /root/ovn/rpm/rpmbuild/RPMS/x86_64/*.rpm / COPY --from=ovnbuilder /root/ovs/rpm/rpmbuild/RPMS/x86_64/*.rpm / #################################### # Stage to copy OVN RPMs from koji # #################################### -FROM quay.io/fedora/fedora:41 AS koji +FROM quay.io/fedora/fedora:42 AS koji COPY --from=kojidownloader /*.rpm / From b4eabd9c09245297a7cb3e89083449bc3a14cdbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:04:23 +0000 Subject: [PATCH 150/278] Bump the go_modules group across 2 directories with 1 update Bumps the go_modules group with 1 update in the /go-controller directory: [golang.org/x/oauth2](https://github.com/golang/oauth2). Bumps the go_modules group with 1 update in the /test/e2e directory: [golang.org/x/oauth2](https://github.com/golang/oauth2). Updates `golang.org/x/oauth2` from 0.23.0 to 0.27.0 - [Commits](https://github.com/golang/oauth2/compare/v0.23.0...v0.27.0) Updates `golang.org/x/oauth2` from 0.23.0 to 0.27.0 - [Commits](https://github.com/golang/oauth2/compare/v0.23.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.27.0 dependency-type: indirect dependency-group: go_modules - dependency-name: golang.org/x/oauth2 dependency-version: 0.27.0 dependency-type: indirect dependency-group: go_modules ... Signed-off-by: dependabot[bot] --- go-controller/go.mod | 2 +- go-controller/go.sum | 4 ++-- .../vendor/golang.org/x/oauth2/README.md | 15 +++++---------- .../vendor/golang.org/x/oauth2/oauth2.go | 2 +- go-controller/vendor/golang.org/x/oauth2/pkce.go | 4 ++-- go-controller/vendor/modules.txt | 4 ++-- test/e2e/go.mod | 2 +- test/e2e/go.sum | 4 ++-- 8 files changed, 16 insertions(+), 21 deletions(-) diff --git a/go-controller/go.mod b/go-controller/go.mod index f40f5001e2..4b12ddd9b5 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -125,7 +125,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.26.0 // indirect diff --git a/go-controller/go.sum b/go-controller/go.sum index 50d5e1270d..436b9bad43 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -945,8 +945,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/go-controller/vendor/golang.org/x/oauth2/README.md b/go-controller/vendor/golang.org/x/oauth2/README.md index 781770c204..48dbb9d84c 100644 --- a/go-controller/vendor/golang.org/x/oauth2/README.md +++ b/go-controller/vendor/golang.org/x/oauth2/README.md @@ -5,15 +5,6 @@ oauth2 package contains a client implementation for OAuth 2.0 spec. -## Installation - -~~~~ -go get golang.org/x/oauth2 -~~~~ - -Or you can manually git clone the repository to -`$(go env GOPATH)/src/golang.org/x/oauth2`. - See pkg.go.dev for further documentation and examples. * [pkg.go.dev/golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) @@ -33,7 +24,11 @@ The main issue tracker for the oauth2 repository is located at https://github.com/golang/oauth2/issues. This repository uses Gerrit for code changes. To learn how to submit changes to -this repository, see https://golang.org/doc/contribute.html. In particular: +this repository, see https://go.dev/doc/contribute. + +The git repository is https://go.googlesource.com/oauth2. + +Note: * Excluding trivial changes, all contributions should be connected to an existing issue. * API changes must go through the [change proposal process](https://go.dev/s/proposal-process) before they can be accepted. diff --git a/go-controller/vendor/golang.org/x/oauth2/oauth2.go b/go-controller/vendor/golang.org/x/oauth2/oauth2.go index 09f6a49b80..74f052aa9f 100644 --- a/go-controller/vendor/golang.org/x/oauth2/oauth2.go +++ b/go-controller/vendor/golang.org/x/oauth2/oauth2.go @@ -56,7 +56,7 @@ type Config struct { // the OAuth flow, after the resource owner's URLs. RedirectURL string - // Scope specifies optional requested permissions. + // Scopes specifies optional requested permissions. Scopes []string // authStyleCache caches which auth style to use when Endpoint.AuthStyle is diff --git a/go-controller/vendor/golang.org/x/oauth2/pkce.go b/go-controller/vendor/golang.org/x/oauth2/pkce.go index 50593b6dfe..6a95da975c 100644 --- a/go-controller/vendor/golang.org/x/oauth2/pkce.go +++ b/go-controller/vendor/golang.org/x/oauth2/pkce.go @@ -21,7 +21,7 @@ const ( // // A fresh verifier should be generated for each authorization. // S256ChallengeOption(verifier) should then be passed to Config.AuthCodeURL -// (or Config.DeviceAccess) and VerifierOption(verifier) to Config.Exchange +// (or Config.DeviceAuth) and VerifierOption(verifier) to Config.Exchange // (or Config.DeviceAccessToken). func GenerateVerifier() string { // "RECOMMENDED that the output of a suitable random number generator be @@ -51,7 +51,7 @@ func S256ChallengeFromVerifier(verifier string) string { } // S256ChallengeOption derives a PKCE code challenge derived from verifier with -// method S256. It should be passed to Config.AuthCodeURL or Config.DeviceAccess +// method S256. It should be passed to Config.AuthCodeURL or Config.DeviceAuth // only. func S256ChallengeOption(verifier string) AuthCodeOption { return challengeOption{ diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index 5732a53975..14bf2d0651 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -504,8 +504,8 @@ golang.org/x/net/ipv6 golang.org/x/net/proxy golang.org/x/net/trace golang.org/x/net/websocket -# golang.org/x/oauth2 v0.23.0 -## explicit; go 1.18 +# golang.org/x/oauth2 v0.27.0 +## explicit; go 1.23.0 golang.org/x/oauth2 golang.org/x/oauth2/internal # golang.org/x/sync v0.12.0 diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 95ac4ff6ae..d87e790619 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -148,7 +148,7 @@ require ( golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 6838af0973..d8a6c5c80c 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -676,8 +676,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From dc437b6327b770d887e339c1d500e95e490d98d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 3 Jul 2025 10:09:55 +0000 Subject: [PATCH 151/278] RouteAdvertisements: appropriately update status even if no updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The status of a RouteAdvertisements might change due to external conditions to the controller. In such cases where the status was bad and the underlying reason for it gets corrected without needing the controller to make further changes, the status would not be updated to reflect it. Signed-off-by: Jaime CaamaƱo Ruiz --- .../routeadvertisements/controller.go | 14 ++++- .../routeadvertisements/controller_test.go | 63 +++++++++++++++---- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller.go b/go-controller/pkg/clustermanager/routeadvertisements/controller.go index 11f7eb79ab..18fb3dbaae 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller.go @@ -951,10 +951,18 @@ func (c *Controller) updateRAStatus(ra *ratypes.RouteAdvertisements, hadUpdates return nil } + var updateStatus bool condition := meta.FindStatusCondition(ra.Status.Conditions, "Accepted") - updateStatus := hadUpdates || condition == nil || condition.ObservedGeneration != ra.Generation - updateStatus = updateStatus || err != nil - + switch { + case condition == nil: + fallthrough + case condition.ObservedGeneration != ra.Generation: + fallthrough + case (err == nil) != (condition.Status == metav1.ConditionTrue): + fallthrough + case hadUpdates: + updateStatus = true + } if !updateStatus { return nil } diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go index 03e9391888..305418425c 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go @@ -47,6 +47,7 @@ type testRA struct { SelectsDefault bool AdvertisePods bool AdvertiseEgressIPs bool + Status *metav1.ConditionStatus } func (tra testRA) RouteAdvertisements() *ratypes.RouteAdvertisements { @@ -92,6 +93,9 @@ func (tra testRA) RouteAdvertisements() *ratypes.RouteAdvertisements { MatchLabels: tra.FRRConfigurationSelector, } } + if tra.Status != nil { + ra.Status.Conditions = []metav1.Condition{{Type: "Accepted", Status: *tra.Status}} + } return ra } @@ -776,6 +780,38 @@ func TestController_reconcile(t *testing.T) { }, expectNADAnnotations: map[string]map[string]string{"default": {types.OvnRouteAdvertisementsKey: "[\"ra\"]"}, "red": {types.OvnRouteAdvertisementsKey: "[\"ra\"]"}}, }, + { + name: "reconciles RouteAdvertisements status even when no other updates are required", + ra: &testRA{Name: "ra", AdvertisePods: true, AdvertiseEgressIPs: true, SelectsDefault: true, Status: ptr.To(metav1.ConditionFalse)}, + frrConfigs: []*testFRRConfig{ + { + Name: "frrConfig", + Namespace: frrNamespace, + Routers: []*testRouter{ + {ASN: 1, Prefixes: []string{"1.1.1.0/24"}, Neighbors: []*testNeighbor{ + {ASN: 1, Address: "1.0.0.100"}, + }}, + }, + }, + { + Labels: map[string]string{types.OvnRouteAdvertisementsKey: "ra"}, + Annotations: map[string]string{types.OvnRouteAdvertisementsKey: "ra/frrConfig/node"}, + NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, + Routers: []*testRouter{ + {ASN: 1, Prefixes: []string{"1.0.1.1/32", "1.1.0.0/24"}, Neighbors: []*testNeighbor{ + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + }}, + }, + }, + }, + nads: []*testNAD{ + {Name: "default", Namespace: "ovn-kubernetes", Network: "default", Annotations: map[string]string{types.OvnRouteAdvertisementsKey: "[\"ra\"]"}}, + }, + nodes: []*testNode{{Name: "node", SubnetsAnnotation: "{\"default\":\"1.1.0.0/24\"}"}}, + eips: []*testEIP{{Name: "eip", EIPs: map[string]string{"node": "1.0.1.1"}}}, + reconcile: "ra", + expectAcceptedStatus: metav1.ConditionTrue, + }, { name: "fails to reconcile a secondary network", ra: &testRA{Name: "ra", AdvertisePods: true, NetworkSelector: map[string]string{"selected": "true"}}, @@ -1005,11 +1041,6 @@ func TestController_reconcile(t *testing.T) { c := NewController(nm.Interface(), wf, fakeClientset) - // prime the default network NAD - if defaultNAD == nil { - defaultNAD, err = c.getOrCreateDefaultNetworkNAD() - g.Expect(err).ToNot(gomega.HaveOccurred()) - } // prime the default network NAD namespace namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -1018,11 +1049,15 @@ func TestController_reconcile(t *testing.T) { } _, err = fakeClientset.KubeClient.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{}) g.Expect(err).ToNot(gomega.HaveOccurred()) - - // update it with the annotation that network manager would set - defaultNAD.Annotations = map[string]string{types.OvnNetworkNameAnnotation: types.DefaultNetworkName} - _, err = fakeClientset.NetworkAttchDefClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(defaultNAD.Namespace).Update(context.Background(), defaultNAD, metav1.UpdateOptions{}) - g.Expect(err).ToNot(gomega.HaveOccurred()) + // prime the default network NAD + if defaultNAD == nil { + defaultNAD, err = c.getOrCreateDefaultNetworkNAD() + g.Expect(err).ToNot(gomega.HaveOccurred()) + // update it with the annotation that network manager would set + defaultNAD.Annotations = map[string]string{types.OvnNetworkNameAnnotation: types.DefaultNetworkName} + _, err = fakeClientset.NetworkAttchDefClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(defaultNAD.Namespace).Update(context.Background(), defaultNAD, metav1.UpdateOptions{}) + g.Expect(err).ToNot(gomega.HaveOccurred()) + } err = wf.Start() g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -1039,7 +1074,13 @@ func TestController_reconcile(t *testing.T) { ) err = nm.Start() - g.Expect(err).ToNot(gomega.HaveOccurred()) + // some test cases start with a bad RA status, avoid asserting + // initial sync in this case as it will fail + if tt.ra == nil || tt.ra.Status == nil || *tt.ra.Status == metav1.ConditionTrue { + g.Expect(err).ToNot(gomega.HaveOccurred()) + } else { + g.Expect(err).To(gomega.HaveOccurred()) + } // we just need the inital sync nm.Stop() From 90e56b92e08238d979a9bc45b9bbd6cfede55dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 19 Jun 2025 13:57:09 +0000 Subject: [PATCH 152/278] e2e: rename testdata package to testscenario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- .../network_segmentation_api_validations.go | 24 +++++++++---------- .../cudn/invalid-scenarios-localnet-mtu.go | 4 ++-- .../invalid-scenarios-localnet-phynetname.go | 4 ++-- .../cudn/invalid-scenarios-localnet-role.go | 4 ++-- .../invalid-scenarios-localnet-subnets.go | 4 ++-- .../cudn/invalid-scenarios-localnet-vlan.go | 4 ++-- .../invalid-scenarios-mismatch-topo-conf.go | 4 ++-- .../cudn/valid-scenarios-localnet.go | 4 ++-- .../{testdata => testscenario}/scenario.go | 2 +- 9 files changed, 27 insertions(+), 27 deletions(-) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-localnet-mtu.go (94%) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-localnet-phynetname.go (97%) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-localnet-role.go (87%) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-localnet-subnets.go (98%) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-localnet-vlan.go (95%) rename test/e2e/{testdata => testscenario}/cudn/invalid-scenarios-mismatch-topo-conf.go (95%) rename test/e2e/{testdata => testscenario}/cudn/valid-scenarios-localnet.go (93%) rename test/e2e/{testdata => testscenario}/scenario.go (90%) diff --git a/test/e2e/network_segmentation_api_validations.go b/test/e2e/network_segmentation_api_validations.go index 0608485b3d..b3b29191fb 100644 --- a/test/e2e/network_segmentation_api_validations.go +++ b/test/e2e/network_segmentation_api_validations.go @@ -6,13 +6,13 @@ import ( e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" - "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" - testdatacudn "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata/cudn" + "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" + testscenariocudn "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario/cudn" ) var _ = Describe("Network Segmentation: API validations", func() { DescribeTable("api-server should reject invalid CRs", - func(scenarios []testdata.ValidateCRScenario) { + func(scenarios []testscenario.ValidateCRScenario) { DeferCleanup(func() { cleanupValidateCRsTest(scenarios) }) @@ -23,16 +23,16 @@ var _ = Describe("Network Segmentation: API validations", func() { Expect(stderr).To(ContainSubstring(s.ExpectedErr)) } }, - Entry("ClusterUserDefinedNetwork, mismatch topology and config", testdatacudn.MismatchTopologyConfig), - Entry("ClusterUserDefinedNetwork, localnet, invalid role", testdatacudn.LocalnetInvalidRole), - Entry("ClusterUserDefinedNetwork, localnet, invalid physicalNetworkName", testdatacudn.LocalnetInvalidPhyNetName), - Entry("ClusterUserDefinedNetwork, localnet, invalid subnets", testdatacudn.LocalnetInvalidSubnets), - Entry("ClusterUserDefinedNetwork, localnet, invalid mtu", testdatacudn.LocalnetInvalidMTU), - Entry("ClusterUserDefinedNetwork, localnet, invalid vlan", testdatacudn.LocalnetInvalidVLAN), + Entry("ClusterUserDefinedNetwork, mismatch topology and config", testscenariocudn.MismatchTopologyConfig), + Entry("ClusterUserDefinedNetwork, localnet, invalid role", testscenariocudn.LocalnetInvalidRole), + Entry("ClusterUserDefinedNetwork, localnet, invalid physicalNetworkName", testscenariocudn.LocalnetInvalidPhyNetName), + Entry("ClusterUserDefinedNetwork, localnet, invalid subnets", testscenariocudn.LocalnetInvalidSubnets), + Entry("ClusterUserDefinedNetwork, localnet, invalid mtu", testscenariocudn.LocalnetInvalidMTU), + Entry("ClusterUserDefinedNetwork, localnet, invalid vlan", testscenariocudn.LocalnetInvalidVLAN), ) DescribeTable("api-server should accept valid CRs", - func(scenarios []testdata.ValidateCRScenario) { + func(scenarios []testscenario.ValidateCRScenario) { DeferCleanup(func() { cleanupValidateCRsTest(scenarios) }) @@ -42,7 +42,7 @@ var _ = Describe("Network Segmentation: API validations", func() { Expect(err).NotTo(HaveOccurred(), "should create valid CR successfully") } }, - Entry("ClusterUserDefinedNetwork, localnet", testdatacudn.LocalnetValid), + Entry("ClusterUserDefinedNetwork, localnet", testscenariocudn.LocalnetValid), ) }) @@ -52,7 +52,7 @@ func runKubectlInputWithFullOutput(namespace string, data string, args ...string return e2ekubectl.NewKubectlCommand(namespace, args...).WithStdinData(data).ExecWithFullOutput() } -func cleanupValidateCRsTest(scenarios []testdata.ValidateCRScenario) { +func cleanupValidateCRsTest(scenarios []testscenario.ValidateCRScenario) { for _, s := range scenarios { e2ekubectl.RunKubectlInput("", s.Manifest, "delete", "-f", "-") } diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-mtu.go similarity index 94% rename from test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go rename to test/e2e/testscenario/cudn/invalid-scenarios-localnet-mtu.go index e1ce9e8c70..d7e3590ffd 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-mtu.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetInvalidMTU = []testdata.ValidateCRScenario{ +var LocalnetInvalidMTU = []testscenario.ValidateCRScenario{ { Description: "invalid MTU - higher than 65536", ExpectedErr: `spec.network.localnet.mtu in body should be less than or equal to 65536`, diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-phynetname.go similarity index 97% rename from test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go rename to test/e2e/testscenario/cudn/invalid-scenarios-localnet-phynetname.go index 83c6664804..171678c9ca 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-phynetname.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetInvalidPhyNetName = []testdata.ValidateCRScenario{ +var LocalnetInvalidPhyNetName = []testscenario.ValidateCRScenario{ { Description: "unset PhysicalNetworkName", ExpectedErr: `spec.network.localnet.physicalNetworkName: Required value`, diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-role.go similarity index 87% rename from test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go rename to test/e2e/testscenario/cudn/invalid-scenarios-localnet-role.go index fad452da04..443f78970a 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-role.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetInvalidRole = []testdata.ValidateCRScenario{ +var LocalnetInvalidRole = []testscenario.ValidateCRScenario{ { Description: "role unset", ExpectedErr: `spec.network.localnet.role: Required value`, diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-subnets.go similarity index 98% rename from test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go rename to test/e2e/testscenario/cudn/invalid-scenarios-localnet-subnets.go index d62a216d48..bd854acdb2 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-subnets.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetInvalidSubnets = []testdata.ValidateCRScenario{ +var LocalnetInvalidSubnets = []testscenario.ValidateCRScenario{ { Description: "unset subnets, and ipam.mode is unset", ExpectedErr: `Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise`, diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-vlan.go similarity index 95% rename from test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go rename to test/e2e/testscenario/cudn/invalid-scenarios-localnet-vlan.go index daa393acdb..8ab71ca8dc 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-localnet-vlan.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetInvalidVLAN = []testdata.ValidateCRScenario{ +var LocalnetInvalidVLAN = []testscenario.ValidateCRScenario{ { Description: "invalid VLAN - invalid mode", ExpectedErr: `spec.network.localnet.vlan.mode: Unsupported value: "Disabled": supported values: "Access`, diff --git a/test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go b/test/e2e/testscenario/cudn/invalid-scenarios-mismatch-topo-conf.go similarity index 95% rename from test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go rename to test/e2e/testscenario/cudn/invalid-scenarios-mismatch-topo-conf.go index 80551a94cd..ddad69d54e 100644 --- a/test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go +++ b/test/e2e/testscenario/cudn/invalid-scenarios-mismatch-topo-conf.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var MismatchTopologyConfig = []testdata.ValidateCRScenario{ +var MismatchTopologyConfig = []testscenario.ValidateCRScenario{ { Description: "topology is localnet but topology config is layer2", ExpectedErr: `spec.localnet is required when topology is Localnet and forbidden otherwise`, diff --git a/test/e2e/testdata/cudn/valid-scenarios-localnet.go b/test/e2e/testscenario/cudn/valid-scenarios-localnet.go similarity index 93% rename from test/e2e/testdata/cudn/valid-scenarios-localnet.go rename to test/e2e/testscenario/cudn/valid-scenarios-localnet.go index a5b188bbfd..d2c7b24d78 100644 --- a/test/e2e/testdata/cudn/valid-scenarios-localnet.go +++ b/test/e2e/testscenario/cudn/valid-scenarios-localnet.go @@ -1,8 +1,8 @@ package cudn -import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata" +import "github.com/ovn-org/ovn-kubernetes/test/e2e/testscenario" -var LocalnetValid = []testdata.ValidateCRScenario{ +var LocalnetValid = []testscenario.ValidateCRScenario{ { Description: "should create localnet topology successfully - minimal", Manifest: ` diff --git a/test/e2e/testdata/scenario.go b/test/e2e/testscenario/scenario.go similarity index 90% rename from test/e2e/testdata/scenario.go rename to test/e2e/testscenario/scenario.go index db96d3b50b..4ee247fd98 100644 --- a/test/e2e/testdata/scenario.go +++ b/test/e2e/testscenario/scenario.go @@ -1,4 +1,4 @@ -package testdata +package testscenario // ValidateCRScenario represent test scenario where a manifest is applied and failed with the expected error type ValidateCRScenario struct { From 3dea4f522d30e5849d060f9d5e0cdc2ff90e34c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 19 Jun 2025 17:27:23 +0000 Subject: [PATCH 153/278] e2e: add RuntimeArgs to container infra provider API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/e2e.go | 10 +++++----- test/e2e/egress_firewall.go | 4 ++-- test/e2e/egress_services.go | 4 ++-- test/e2e/egressip.go | 10 +++++----- test/e2e/external_gateways.go | 14 +++++++------- test/e2e/infraprovider/api/api.go | 19 ++++++++++--------- test/e2e/infraprovider/providers/kind/kind.go | 5 +++-- test/e2e/kubevirt.go | 2 +- test/e2e/multihoming.go | 4 ++-- test/e2e/network_segmentation.go | 2 +- test/e2e/node_ip_mac_migration.go | 2 +- test/e2e/pod.go | 2 +- test/e2e/service.go | 6 +++--- 13 files changed, 43 insertions(+), 41 deletions(-) diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index be1b46bf75..e5bbde7d42 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -712,7 +712,7 @@ var _ = ginkgo.Describe("e2e control plane", func() { } secondaryExternalContainerPort := infraprovider.Get().GetExternalContainerPort() secondaryExternalContainerSpec := infraapi.ExternalContainer{Name: "e2e-ovn-k", Image: images.AgnHost(), - Network: secondaryProviderNetwork, Args: getAgnHostHTTPPortBindCMDArgs(secondaryExternalContainerPort), ExtPort: secondaryExternalContainerPort} + Network: secondaryProviderNetwork, CmdArgs: getAgnHostHTTPPortBindCMDArgs(secondaryExternalContainerPort), ExtPort: secondaryExternalContainerPort} ginkgo.By("creating container on secondary provider network") secondaryExternalContainer, err = providerCtx.CreateExternalContainer(secondaryExternalContainerSpec) framework.ExpectNoError(err, "failed to create external container") @@ -1275,7 +1275,7 @@ var _ = ginkgo.Describe("e2e ingress traffic validation", func() { framework.ExpectNoError(err, "failed to get primary network") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer = infraapi.ExternalContainer{Name: "e2e-ingress", Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external service", externalContainer.String()) }) @@ -1672,7 +1672,7 @@ var _ = ginkgo.Describe("e2e ingress traffic validation", func() { framework.ExpectNoError(err, "failed to get primary network") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer = infraapi.ExternalContainer{Name: "e2e-ingress-add-more", Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "external container %s must be created successfully", externalContainer.Name) @@ -1834,7 +1834,7 @@ var _ = ginkgo.Describe("e2e ingress to host-networked pods traffic validation", framework.ExpectNoError(err, "failed to get primary network") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer = infraapi.ExternalContainer{Name: clientContainerName, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "external container %s must be created successfully", externalContainer.Name) }) @@ -1943,7 +1943,7 @@ var _ = ginkgo.Describe("e2e br-int flow monitoring export validation", func() { primaryProviderNetwork, err := infraprovider.Get().PrimaryNetwork() framework.ExpectNoError(err, "failed to get primary network") collectorExternalContainer := infraapi.ExternalContainer{Name: getContainerName(collectorPort), Image: "cloudflare/goflow", - Network: primaryProviderNetwork, Args: []string{"-kafka=false"}, ExtPort: collectorPort} + Network: primaryProviderNetwork, CmdArgs: []string{"-kafka=false"}, ExtPort: collectorPort} collectorExternalContainer, err = providerCtx.CreateExternalContainer(collectorExternalContainer) if err != nil { framework.Failf("failed to start flow collector container %s: %v", getContainerName(collectorPort), err) diff --git a/test/e2e/egress_firewall.go b/test/e2e/egress_firewall.go index 32974beb1c..abbc26b524 100644 --- a/test/e2e/egress_firewall.go +++ b/test/e2e/egress_firewall.go @@ -197,7 +197,7 @@ var _ = ginkgo.Describe("e2e egress firewall policy validation", feature.EgressF Name: externalContainerName1, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: []string{"netexec", fmt.Sprintf("--http-port=%d", externalContainer1Port)}, + CmdArgs: []string{"netexec", fmt.Sprintf("--http-port=%d", externalContainer1Port)}, ExtPort: externalContainer1Port, } externalContainer1, err = providerCtx.CreateExternalContainer(externalContainer1Spec) @@ -210,7 +210,7 @@ var _ = ginkgo.Describe("e2e egress firewall policy validation", feature.EgressF Name: externalContainerName2, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: []string{"netexec", fmt.Sprintf("--http-port=%d", externalContainer2Port)}, + CmdArgs: []string{"netexec", fmt.Sprintf("--http-port=%d", externalContainer2Port)}, ExtPort: externalContainer2Port, } externalContainer2, err = providerCtx.CreateExternalContainer(externalContainer2Spec) diff --git a/test/e2e/egress_services.go b/test/e2e/egress_services.go index 2afcb2edc8..ee2fec30f4 100644 --- a/test/e2e/egress_services.go +++ b/test/e2e/egress_services.go @@ -85,7 +85,7 @@ var _ = ginkgo.Describe("EgressService", feature.EgressService, func() { framework.ExpectNoError(err, "failed to get primary provider network") externalContainer = infraapi.ExternalContainer{Name: externalContainerName, Image: images.AgnHost(), Network: primaryProviderNetwork, ExtPort: 8080, - Args: getAgnHostHTTPPortBindCMDArgs(8080)} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(8080)} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external container") }) @@ -1239,7 +1239,7 @@ metadata: ginkgo.By(fmt.Sprintf("Creating container %s", net.containerName)) // Setting the --hostname here is important since later we poke the container's /hostname endpoint extContainerSecondaryNet := infraapi.ExternalContainer{Name: net.containerName, Image: images.AgnHost(), Network: network, - Args: []string{"netexec", "--http-port=8080"}, ExtPort: 8080} + CmdArgs: []string{"netexec", "--http-port=8080"}, ExtPort: 8080} extContainerSecondaryNet, err = providerCtx.CreateExternalContainer(extContainerSecondaryNet) ginkgo.By(fmt.Sprintf("Adding a listener for the shared IPv4 %s on %s", sharedIPv4, net.containerName)) out, err := infraprovider.Get().ExecExternalContainerCommand(extContainerSecondaryNet, []string{"ip", "address", "add", sharedIPv4 + "/32", "dev", "lo"}) diff --git a/test/e2e/egressip.go b/test/e2e/egressip.go index d9d281aa7b..1bdc03adec 100644 --- a/test/e2e/egressip.go +++ b/test/e2e/egressip.go @@ -219,7 +219,7 @@ func isSupportedAgnhostForEIP(externalContainer infraapi.ExternalContainer) bool if externalContainer.Image != images.AgnHost() { return false } - if !util.SliceHasStringItem(externalContainer.Args, "netexec") { + if !util.SliceHasStringItem(externalContainer.CmdArgs, "netexec") { return false } return true @@ -754,13 +754,13 @@ var _ = ginkgo.DescribeTableSubtree("e2e egress IP validation", feature.EgressIP // attach containers to the primary network primaryTargetExternalContainerPort := infraprovider.Get().GetExternalContainerPort() primaryTargetExternalContainerSpec := infraapi.ExternalContainer{Name: targetNodeName, Image: images.AgnHost(), - Network: primaryProviderNetwork, Args: getAgnHostHTTPPortBindCMDArgs(primaryTargetExternalContainerPort), ExtPort: primaryTargetExternalContainerPort} + Network: primaryProviderNetwork, CmdArgs: getAgnHostHTTPPortBindCMDArgs(primaryTargetExternalContainerPort), ExtPort: primaryTargetExternalContainerPort} primaryTargetExternalContainer, err = providerCtx.CreateExternalContainer(primaryTargetExternalContainerSpec) framework.ExpectNoError(err, "failed to create external target container on primary network", primaryTargetExternalContainerSpec.String()) primaryDeniedExternalContainerPort := infraprovider.Get().GetExternalContainerPort() primaryDeniedExternalContainerSpec := infraapi.ExternalContainer{Name: deniedTargetNodeName, Image: images.AgnHost(), - Network: primaryProviderNetwork, Args: getAgnHostHTTPPortBindCMDArgs(primaryDeniedExternalContainerPort), ExtPort: primaryDeniedExternalContainerPort} + Network: primaryProviderNetwork, CmdArgs: getAgnHostHTTPPortBindCMDArgs(primaryDeniedExternalContainerPort), ExtPort: primaryDeniedExternalContainerPort} primaryDeniedExternalContainer, err = providerCtx.CreateExternalContainer(primaryDeniedExternalContainerSpec) framework.ExpectNoError(err, "failed to create external denied container on primary network", primaryDeniedExternalContainer.String()) @@ -791,7 +791,7 @@ var _ = ginkgo.DescribeTableSubtree("e2e egress IP validation", feature.EgressIP Name: targetSecondaryNodeName, Image: images.AgnHost(), Network: secondaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(secondaryTargetExternalContainerPort), + CmdArgs: getAgnHostHTTPPortBindCMDArgs(secondaryTargetExternalContainerPort), ExtPort: secondaryTargetExternalContainerPort, } secondaryTargetExternalContainer, err = providerCtx.CreateExternalContainer(secondaryTargetExternalContainerSpec) @@ -2125,7 +2125,7 @@ spec: providerPrimaryNetwork, err := infraprovider.Get().PrimaryNetwork() framework.ExpectNoError(err, "failed to get providers primary network") externalContainerPrimary := infraapi.ExternalContainer{Name: "external-container-for-egressip-mtu-test", Image: images.AgnHost(), - Network: providerPrimaryNetwork, Args: []string{"pause"}, ExtPort: externalContainerPrimaryPort} + Network: providerPrimaryNetwork, CmdArgs: []string{"pause"}, ExtPort: externalContainerPrimaryPort} externalContainerPrimary, err = providerCtx.CreateExternalContainer(externalContainerPrimary) framework.ExpectNoError(err, "failed to create external container: %s", externalContainerPrimary.String()) diff --git a/test/e2e/external_gateways.go b/test/e2e/external_gateways.go index bf3742ea68..c3b2f12198 100644 --- a/test/e2e/external_gateways.go +++ b/test/e2e/external_gateways.go @@ -144,7 +144,7 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { } externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer := infraapi.ExternalContainer{Name: getContainerName(gwContainerNameTemplate, externalContainerPort), - Image: images.AgnHost(), Network: network, ExtPort: externalContainerPort, Args: []string{"pause"}} + Image: images.AgnHost(), Network: network, ExtPort: externalContainerPort, CmdArgs: []string{"pause"}} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to start external gateway test container") if network.Name() == "host" { @@ -238,7 +238,7 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { // start the container that will act as a new external gateway that the tests will be updated to use externalContainer2Port := infraprovider.Get().GetExternalContainerPort() externalContainer2 := infraapi.ExternalContainer{Name: getContainerName(gwContainerNameTemplate2, externalContainerPort), - Image: images.AgnHost(), Network: network, ExtPort: externalContainer2Port, Args: []string{"pause"}} + Image: images.AgnHost(), Network: network, ExtPort: externalContainer2Port, CmdArgs: []string{"pause"}} externalContainer2, err = providerCtx.CreateExternalContainer(externalContainer2) framework.ExpectNoError(err, "failed to start external gateway test container %s", getContainerName(gwContainerNameTemplate2, externalContainerPort)) if network.Name() == "host" { @@ -365,7 +365,7 @@ var _ = ginkgo.Describe("External Gateway", feature.ExternalGateway, func() { } externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer := infraapi.ExternalContainer{Name: getContainerName(gwContainerTemplate, externalContainerPort), Image: images.AgnHost(), Network: network, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to start external gateway test container %s", getContainerName(gwContainerTemplate, externalContainerPort)) if network.Name() == "host" { @@ -2922,9 +2922,9 @@ func setupGatewayContainers(f *framework.Framework, providerCtx infraapi.Context var err error externalContainer1 := infraapi.ExternalContainer{Name: getContainerName(container1Template, uint16(gwTCPPort)), - Image: externalContainerImage, Network: network, Args: []string{}, ExtPort: uint16(gwTCPPort)} + Image: externalContainerImage, Network: network, CmdArgs: []string{}, ExtPort: uint16(gwTCPPort)} externalContainer2 := infraapi.ExternalContainer{Name: getContainerName(container2Template, uint16(gwTCPPort)), - Image: externalContainerImage, Network: network, Args: []string{}, ExtPort: uint16(gwTCPPort)} + Image: externalContainerImage, Network: network, CmdArgs: []string{}, ExtPort: uint16(gwTCPPort)} gwContainers := []infraapi.ExternalContainer{externalContainer1, externalContainer2} addressesv4 := gatewayTestIPs{targetIPs: make([]string, 0)} @@ -3175,12 +3175,12 @@ func setupGatewayContainersForConntrackTest(f *framework.Framework, providerCtx addressesv6 := gatewayTestIPs{gatewayIPs: make([]string, 2)} ginkgo.By("Creating the gateway containers for the UDP test") gwExternalContainer1 := infraapi.ExternalContainer{Name: getContainerName(gwContainer1Template, 12345), - Image: images.IPerf3(), Network: network, Args: []string{}, ExtPort: 12345} + Image: images.IPerf3(), Network: network, CmdArgs: []string{}, ExtPort: 12345} gwExternalContainer1, err = providerCtx.CreateExternalContainer(gwExternalContainer1) framework.ExpectNoError(err, "failed to create external container (%s)", gwExternalContainer1) gwExternalContainer2 := infraapi.ExternalContainer{Name: getContainerName(gwContainer2Template, 12345), - Image: images.IPerf3(), Network: network, Args: []string{}, ExtPort: 12345} + Image: images.IPerf3(), Network: network, CmdArgs: []string{}, ExtPort: 12345} gwExternalContainer2, err = providerCtx.CreateExternalContainer(gwExternalContainer2) framework.ExpectNoError(err, "failed to create external container (%s)", gwExternalContainer2) if network.Name() == "host" { diff --git a/test/e2e/infraprovider/api/api.go b/test/e2e/infraprovider/api/api.go index c654f798c3..81101c622e 100644 --- a/test/e2e/infraprovider/api/api.go +++ b/test/e2e/infraprovider/api/api.go @@ -182,14 +182,15 @@ func (n NetworkInterface) GetMAC() string { } type ExternalContainer struct { - Name string - Image string - Network Network - Entrypoint string - Args []string - ExtPort uint16 - IPv4 string - IPv6 string + Name string + Image string + Network Network + Entrypoint string + CmdArgs []string + ExtPort uint16 + IPv4 string + IPv6 string + RuntimeArgs []string } func (ec ExternalContainer) GetName() string { @@ -227,7 +228,7 @@ func (ec ExternalContainer) IsIPv6() bool { } func (ec ExternalContainer) String() string { - str := fmt.Sprintf("Name: %q, Image: %q, Network: %q, Command: %q", ec.Name, ec.Image, ec.Network, strings.Join(ec.Args, " ")) + str := fmt.Sprintf("Name: %q, Image: %q, Network: %q, RuntimeArgs: %q, Command: %q", ec.Name, ec.Image, ec.Network, strings.Join(ec.RuntimeArgs, " "), strings.Join(ec.CmdArgs, " ")) if ec.IsIPv4() { str = fmt.Sprintf("%s, IPv4 address: %q", str, ec.GetIPv4()) } diff --git a/test/e2e/infraprovider/providers/kind/kind.go b/test/e2e/infraprovider/providers/kind/kind.go index 4d0dc6a226..e6290e7fee 100644 --- a/test/e2e/infraprovider/providers/kind/kind.go +++ b/test/e2e/infraprovider/providers/kind/kind.go @@ -150,9 +150,10 @@ func (c *contextKind) createExternalContainer(container api.ExternalContainer) ( if container.Entrypoint != "" { cmd = append(cmd, "--entrypoint", container.Entrypoint) } + cmd = append(cmd, container.RuntimeArgs...) cmd = append(cmd, container.Image) - if len(container.Args) > 0 { - cmd = append(cmd, container.Args...) + if len(container.CmdArgs) > 0 { + cmd = append(cmd, container.CmdArgs...) } else { if images.AgnHost() == container.Image { cmd = append(cmd, "pause") diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 839301ae11..67ab2e290a 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1762,7 +1762,7 @@ write_files: Name: externalContainerName, Image: images.IPerf3(), Network: providerNetwork, - Args: []string{"sleep infinity"}, + CmdArgs: []string{"sleep infinity"}, ExtPort: externalContainerPort, } externalContainer, err = providerCtx.CreateExternalContainer(externalContainerSpec) diff --git a/test/e2e/multihoming.go b/test/e2e/multihoming.go index 3ad1dd46e7..e82255fc57 100644 --- a/test/e2e/multihoming.go +++ b/test/e2e/multihoming.go @@ -941,7 +941,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Image: images.AgnHost(), Network: underlayNetwork, Entrypoint: "bash", - Args: []string{"-c", fmt.Sprintf("ip a add %s/24 dev eth0 && ./agnhost netexec --http-port=%d", underlayServiceIP, servicePort)}, + CmdArgs: []string{"-c", fmt.Sprintf("ip a add %s/24 dev eth0 && ./agnhost netexec --http-port=%d", underlayServiceIP, servicePort)}, ExtPort: servicePort, } _, err = providerCtx.CreateExternalContainer(serviceContainerSpec) @@ -1310,7 +1310,7 @@ var _ = Describe("Multi Homing", feature.MultiHoming, func() { Network: underlayNetwork, Entrypoint: "bash", ExtPort: servicePort, - Args: []string{"-c", fmt.Sprintf(` + CmdArgs: []string{"-c", fmt.Sprintf(` ip link add link %[1]s name %[2]s type vlan id %[3]d ip link set dev %[2]s up ip a add %[4]s/24 dev %[2]s diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index 659b18acc7..2fe012343b 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -1478,7 +1478,7 @@ spec: Name: externalContainerName, Image: images.AgnHost(), Network: providerPrimaryNetwork, - Args: httpServerContainerCmd(uint16(externalContainerPort)), + CmdArgs: httpServerContainerCmd(uint16(externalContainerPort)), ExtPort: externalContainerPort, } externalContainer, err = providerCtx.CreateExternalContainer(externalContainerSpec) diff --git a/test/e2e/node_ip_mac_migration.go b/test/e2e/node_ip_mac_migration.go index d7b12f4b24..19626e50e6 100644 --- a/test/e2e/node_ip_mac_migration.go +++ b/test/e2e/node_ip_mac_migration.go @@ -132,7 +132,7 @@ spec: framework.ExpectNoError(err, "failed to get primary network") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer = infraapi.ExternalContainer{Name: externalContainerName, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external container") externalContainerIPs[4], externalContainerIPs[6] = externalContainer.GetIPv4(), externalContainer.GetIPv6() diff --git a/test/e2e/pod.go b/test/e2e/pod.go index e43ecee03a..c9a5e5efb7 100644 --- a/test/e2e/pod.go +++ b/test/e2e/pod.go @@ -105,7 +105,7 @@ var _ = ginkgo.Describe("Pod to external server PMTUD", func() { providerPrimaryNetwork, err := infraprovider.Get().PrimaryNetwork() framework.ExpectNoError(err, "failed to get provider primary network") externalContainer = infraapi.ExternalContainer{Name: externalContainerName, Image: images.AgnHost(), Network: providerPrimaryNetwork, - Args: []string{"netexec", "--http-port", fmt.Sprintf("%d", externalContainerPort), "--udp-port", fmt.Sprintf("%d", externalContainerPort)}, + CmdArgs: []string{"netexec", "--http-port", fmt.Sprintf("%d", externalContainerPort), "--udp-port", fmt.Sprintf("%d", externalContainerPort)}, ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external container (%s)", externalContainer) diff --git a/test/e2e/service.go b/test/e2e/service.go index 0df017d523..6e3ff61c27 100644 --- a/test/e2e/service.go +++ b/test/e2e/service.go @@ -812,7 +812,7 @@ var _ = ginkgo.Describe("Services", feature.Service, func() { framework.ExpectNoError(err, "failed to get primary network") externalContainerPort := infraprovider.Get().GetExternalContainerPort() externalContainer := infraapi.ExternalContainer{Name: clientContainerName, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} + CmdArgs: getAgnHostHTTPPortBindCMDArgs(externalContainerPort), ExtPort: externalContainerPort} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "external container %s must be created", externalContainer.Name) @@ -1011,7 +1011,7 @@ var _ = ginkgo.Describe("Services", feature.Service, func() { Name: targetSecondaryContainerName, Image: images.AgnHost(), Network: secondaryProviderNetwork, - Args: getAgnHostHTTPPortBindCMDArgs(serverExternalContainerPort), + CmdArgs: getAgnHostHTTPPortBindCMDArgs(serverExternalContainerPort), ExtPort: serverExternalContainerPort, } serverExternalContainer, err := providerCtx.CreateExternalContainer(serverExternalContainerSpec) @@ -1315,7 +1315,7 @@ spec: ginkgo.By("Creating an external client") externalContainer := infraapi.ExternalContainer{Name: clientContainerName, Image: images.AgnHost(), Network: primaryProviderNetwork, - Args: []string{"pause"}, ExtPort: infraprovider.Get().GetExternalContainerPort()} + CmdArgs: []string{"pause"}, ExtPort: infraprovider.Get().GetExternalContainerPort()} externalContainer, err = providerCtx.CreateExternalContainer(externalContainer) framework.ExpectNoError(err, "failed to create external container", externalContainer) From acef39f4cde22069620c7d078220bce9b6e02c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 19 Jun 2025 17:27:53 +0000 Subject: [PATCH 154/278] e2e: make ExtPort not required in container infra provider API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/infraprovider/api/api.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/e2e/infraprovider/api/api.go b/test/e2e/infraprovider/api/api.go index 81101c622e..1d2d3466fb 100644 --- a/test/e2e/infraprovider/api/api.go +++ b/test/e2e/infraprovider/api/api.go @@ -249,9 +249,6 @@ func (ec ExternalContainer) IsValidPreCreateContainer() (bool, error) { if ec.Network.String() == "" { errs = append(errs, errors.New("network is not set")) } - if ec.ExtPort == 0 { - errs = append(errs, errors.New("port is not set")) - } if len(errs) == 0 { return true, nil } From 926ba1ad397c5b218defb497d1a7ade390e8e7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Wed, 2 Jul 2025 14:01:02 +0000 Subject: [PATCH 155/278] e2e: use index in kind infra inspect templates to allow special characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/infraprovider/providers/kind/kind.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/e2e/infraprovider/providers/kind/kind.go b/test/e2e/infraprovider/providers/kind/kind.go index e6290e7fee..8c068c7411 100644 --- a/test/e2e/infraprovider/providers/kind/kind.go +++ b/test/e2e/infraprovider/providers/kind/kind.go @@ -497,13 +497,13 @@ func (c *contextKind) cleanUp() error { const ( nameFormat = "{{.Name}}" - inspectNetworkIPv4GWKeyStr = "{{ .NetworkSettings.Networks.%s.Gateway }}" - inspectNetworkIPv4AddrKeyStr = "{{ .NetworkSettings.Networks.%s.IPAddress }}" - inspectNetworkIPv4PrefixKeyStr = "{{ .NetworkSettings.Networks.%s.IPPrefixLen }}" - inspectNetworkIPv6GWKeyStr = "{{ .NetworkSettings.Networks.%s.IPv6Gateway }}" - inspectNetworkIPv6AddrKeyStr = "{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}" - inspectNetworkIPv6PrefixKeyStr = "{{ .NetworkSettings.Networks.%s.GlobalIPv6PrefixLen }}" - inspectNetworkMACKeyStr = "{{ .NetworkSettings.Networks.%s.MacAddress }}" + inspectNetworkIPv4GWKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .Gateway }}{{ end }}" + inspectNetworkIPv4AddrKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .IPAddress }}{{ end }}" + inspectNetworkIPv4PrefixKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .IPPrefixLen }}{{ end }}" + inspectNetworkIPv6GWKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .IPv6Gateway }}{{ end }}" + inspectNetworkIPv6AddrKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .GlobalIPv6Address }}{{ end }}" + inspectNetworkIPv6PrefixKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .GlobalIPv6PrefixLen }}{{ end }}" + inspectNetworkMACKeyStr = "{{ with index .NetworkSettings.Networks %q }}{{ .MacAddress }}{{ end }}" inspectNetworkContainersKeyStr = "{{ range $key, $value := .Containers }}{{ printf \"%s\\n\" $value.Name}}{{ end }}'" emptyValue = "" ) From edb05ca1087a69c9fee8bbb7bf30bde36f53ced9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 3 Jul 2025 11:57:08 +0000 Subject: [PATCH 156/278] kind.sh: Use FRRConfiguration label when advertising default network MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid selecting FRRConfigurations that have other purposes. Signed-off-by: Jaime CaamaƱo Ruiz --- dist/templates/ovn-setup.yaml.j2 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dist/templates/ovn-setup.yaml.j2 b/dist/templates/ovn-setup.yaml.j2 index 8112e06670..981a362859 100644 --- a/dist/templates/ovn-setup.yaml.j2 +++ b/dist/templates/ovn-setup.yaml.j2 @@ -89,7 +89,9 @@ spec: networkSelectors: - networkSelectionType: DefaultNetwork nodeSelector: {} - frrConfigurationSelector: {} + frrConfigurationSelector: + matchLabels: + name: receive-all advertisements: - "PodNetwork" {%- endif %} From 5ece8463d577233268d0a8229b814151bc00ac00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 19 Jun 2025 17:29:52 +0000 Subject: [PATCH 157/278] e2e: add VRF-Lite test cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/deploymentconfig/api/api.go | 1 + .../e2e/deploymentconfig/configs/kind/kind.go | 4 + test/e2e/network_segmentation.go | 26 +- test/e2e/route_advertisements.go | 1159 ++++++++++++++++- .../frr-k8s/frrconf.yaml.tmpl | 46 + .../routeadvertisements/frr/daemons.tmpl | 82 ++ .../routeadvertisements/frr/frr.conf.tmpl | 57 + test/e2e/util.go | 124 +- 8 files changed, 1455 insertions(+), 44 deletions(-) create mode 100644 test/e2e/testdata/routeadvertisements/frr-k8s/frrconf.yaml.tmpl create mode 100644 test/e2e/testdata/routeadvertisements/frr/daemons.tmpl create mode 100644 test/e2e/testdata/routeadvertisements/frr/frr.conf.tmpl diff --git a/test/e2e/deploymentconfig/api/api.go b/test/e2e/deploymentconfig/api/api.go index 573ced8cb8..dc43e87c9b 100644 --- a/test/e2e/deploymentconfig/api/api.go +++ b/test/e2e/deploymentconfig/api/api.go @@ -4,6 +4,7 @@ package api // Remove when OVN-Kubernetes exposes its config via an API. type DeploymentConfig interface { OVNKubernetesNamespace() string + FRRK8sNamespace() string ExternalBridgeName() string PrimaryInterfaceName() string } diff --git a/test/e2e/deploymentconfig/configs/kind/kind.go b/test/e2e/deploymentconfig/configs/kind/kind.go index be3f35aa73..d05c6a7061 100644 --- a/test/e2e/deploymentconfig/configs/kind/kind.go +++ b/test/e2e/deploymentconfig/configs/kind/kind.go @@ -33,6 +33,10 @@ func (k kind) OVNKubernetesNamespace() string { return "ovn-kubernetes" } +func (k kind) FRRK8sNamespace() string { + return "frr-k8s-system" +} + func (k kind) ExternalBridgeName() string { return "breth0" } diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index 2fe012343b..dec466f423 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -1887,31 +1887,17 @@ func generateLayer3Subnets(cidrs string) []string { // userDefinedNetworkReadyFunc returns a function that checks for the NetworkCreated condition in the provided udn func userDefinedNetworkReadyFunc(client dynamic.Interface, namespace, name string) func() error { - return func() error { - udn, err := client.Resource(udnGVR).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}, "status") - if err != nil { - return err - } - conditions, err := getConditions(udn) - if err != nil { - return err - } - if len(conditions) == 0 { - return fmt.Errorf("no conditions found in: %v", udn) - } - for _, condition := range conditions { - if condition.Type == "NetworkCreated" && condition.Status == metav1.ConditionTrue { - return nil - } - } - return fmt.Errorf("no NetworkCreated condition found in: %v", udn) - } + return networkReadyFunc(client.Resource(udnGVR).Namespace(namespace), name) } // userDefinedNetworkReadyFunc returns a function that checks for the NetworkCreated condition in the provided cluster udn func clusterUserDefinedNetworkReadyFunc(client dynamic.Interface, name string) func() error { + return networkReadyFunc(client.Resource(clusterUDNGVR), name) +} + +func networkReadyFunc(client dynamic.ResourceInterface, name string) func() error { return func() error { - cUDN, err := client.Resource(clusterUDNGVR).Get(context.Background(), name, metav1.GetOptions{}, "status") + cUDN, err := client.Get(context.Background(), name, metav1.GetOptions{}, "status") if err != nil { return err } diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index a08b80c6b0..08e6f11965 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -2,10 +2,14 @@ package e2e import ( "context" + "embed" "fmt" "math/rand" "net" + "os" + "path/filepath" "strings" + "text/template" "time" @@ -16,6 +20,8 @@ import ( apitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/types" udnv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" udnclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned" + "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -24,6 +30,12 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" "k8s.io/kubernetes/test/e2e/framework" e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" e2enode "k8s.io/kubernetes/test/e2e/framework/node" @@ -34,13 +46,14 @@ import ( utilnet "k8s.io/utils/net" ) +const ( + serverContainerName = "bgpserver" + routerContainerName = "frr" + echoClientPodName = "echo-client-pod" + bgpExternalNetworkName = "bgpnet" +) + var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is advertised", func() { - const ( - serverContainerName = "bgpserver" - routerContainerName = "frr" - echoClientPodName = "echo-client-pod" - bgpExternalNetworkName = "bgpnet" - ) var serverContainerIPs []string var frrContainerIPv4, frrContainerIPv6 string var nodes *corev1.NodeList @@ -236,13 +249,6 @@ var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is }) var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advertised", func() { - const ( - serverContainerName = "bgpserver" - routerContainerName = "frr" - echoClientPodName = "echo-client-pod" - bgpExternalNetworkName = "bgpnet" - placeholder = "PLACEHOLDER_NAMESPACE" - ) var serverContainerIPs []string var frrContainerIPv4, frrContainerIPv6 string var nodes *corev1.NodeList @@ -1061,3 +1067,1130 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }, ), ) + +var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { + + // testing helpers used throughout this testing node + const ( + // FIXME: each test brings its own topology up, and sometimes zebra on + // external FRR container fails to start on the first attempt for + // unknown reasons delaying the overall availability, so we need to use + // long timeouts + timeout = 240 * time.Second + timeoutNOK = 10 * time.Second + netexecPort = 8080 + ) + var netexecPortStr = fmt.Sprintf("%d", netexecPort) + testPodToHostnameAndExpect := func(src *corev1.Pod, dstIP, expect string) { + ginkgo.GinkgoHelper() + hostname, err := e2epodoutput.RunHostCmdWithRetries( + src.Namespace, + src.Name, + fmt.Sprintf("curl --max-time 2 -g -q -s http://%s/hostname", net.JoinHostPort(dstIP, netexecPortStr)), + framework.Poll, + timeout, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(hostname).To(gomega.Equal(expect)) + } + testPodToClientIP := func(src *corev1.Pod, dstIP string) { + ginkgo.GinkgoHelper() + _, err := e2epodoutput.RunHostCmdWithRetries( + src.Namespace, + src.Name, + fmt.Sprintf("curl --max-time 2 -g -q -s http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr)), + framework.Poll, + timeout, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + testPodToClientIPAndExpect := func(src *corev1.Pod, dstIP, expect string) { + ginkgo.GinkgoHelper() + ip, err := e2epodoutput.RunHostCmdWithRetries( + src.Namespace, + src.Name, + fmt.Sprintf("curl --max-time 2 -g -q -s http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr)), + framework.Poll, + timeout, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + ip, _, err = net.SplitHostPort(ip) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(ip).To(gomega.Equal(expect)) + } + testContainerToClientIPAndExpect := func(src, dstIP, expect string) { + ginkgo.GinkgoHelper() + gomega.Eventually(func(g gomega.Gomega) { + // FIXME: using ExecK8NodeCommand instead of + // ExecExternalContainerCommand, they arent any + // different but ExecK8NodeCommand is more convinient + ip, err := infraprovider.Get().ExecK8NodeCommand( + src, + []string{"curl", "--max-time", "2", "-g", "-q", "-s", fmt.Sprintf("http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr))}, + ) + g.Expect(err).NotTo(gomega.HaveOccurred()) + ip, _, err = net.SplitHostPort(ip) + g.Expect(err).NotTo(gomega.HaveOccurred()) + g.Expect(ip).To(gomega.Equal(expect)) + }).WithTimeout(timeout).WithPolling(framework.Poll).Should(gomega.Succeed()) + } + testPodToClientIPNOK := func(src *corev1.Pod, dstIP string) { + gomega.Consistently(func(g gomega.Gomega) { + _, err := e2epodoutput.RunHostCmd( + src.Namespace, + src.Name, + fmt.Sprintf("curl --max-time 2 -g -q -s http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr)), + ) + g.Expect(err).To(gomega.HaveOccurred()) + }).WithTimeout(timeoutNOK).WithPolling(framework.Poll).Should(gomega.Succeed()) + } + testContainerToClientIPNOK := func(src, dstIP string) { + gomega.Consistently(func(g gomega.Gomega) { + _, err := infraprovider.Get().ExecK8NodeCommand( + src, + []string{"curl", "--max-time", "2", "-g", "-q", "-s", fmt.Sprintf("http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr))}, + ) + g.Expect(err).To(gomega.HaveOccurred()) + }).WithTimeout(timeoutNOK).WithPolling(framework.Poll).Should(gomega.Succeed()) + } + + const ( + baseName = "vrflite" + bgpPeerSubnetIPv4 = "172.36.0.0/16" + bgpPeerSubnetIPv6 = "fc00:f853:ccd:36::/64" + // TODO: test with overlaps but we need better isolation from the infra + // provider, docker `--internal` bridge networks with iptables based + // isolation doesn't cut it. macvlan driver might be a better option. + bgpServerSubnetIPv4 = "172.38.0.0/16" + bgpServerSubnetIPv6 = "fc00:f853:ccd:38::/64" + ) + + f := wrappedTestFramework(baseName) + f.SkipNamespaceCreation = true + var ipFamilySet sets.Set[utilnet.IPFamily] + var ictx infraapi.Context + var testBaseName, testSuffix, testNetworkName, bgpServerName string + + ginkgo.BeforeEach(func() { + if !isLocalGWModeEnabled() { + e2eskipper.Skipf("VRF-Lite test cases only supported in Local Gateway mode") + } + ipFamilySet = sets.New(getSupportedIPFamiliesSlice(f.ClientSet)...) + ictx = infraprovider.Get().NewTestContext() + testSuffix = framework.RandomSuffix() + testBaseName = baseName + testSuffix + testNetworkName = testBaseName + bgpServerName = testNetworkName + "-bgpserver" + + // we will create a agnhost server on an extra network peered with BGP + ginkgo.By("Running a BGP network with an agnhost server") + bgpPeerCIDRs := []string{bgpPeerSubnetIPv4, bgpPeerSubnetIPv6} + bgpServerCIDRs := []string{bgpServerSubnetIPv4, bgpServerSubnetIPv6} + gomega.Expect(runBGPNetworkAndServer(f, ictx, testNetworkName, bgpServerName, bgpPeerCIDRs, bgpServerCIDRs)).To(gomega.Succeed()) + }) + + // define networks to test with + const ( + cudnCIDRv4 = "103.103.0.0/16" + cudnCIDRv6 = "2014:100:200::0/60" + ) + var ( + layer3NetworkSpec = &udnv1.NetworkSpec{ + Topology: udnv1.NetworkTopologyLayer3, + Layer3: &udnv1.Layer3Config{ + Role: "Primary", + Subnets: []udnv1.Layer3Subnet{{CIDR: cudnCIDRv4, HostSubnet: 24}, {CIDR: cudnCIDRv6, HostSubnet: 64}}, + }, + } + ) + + matchL3SubnetsByIPFamilies := func(families sets.Set[utilnet.IPFamily], in ...udnv1.Layer3Subnet) (out []udnv1.Layer3Subnet) { + for _, subnet := range in { + if families.Has(utilnet.IPFamilyOfCIDRString(string(subnet.CIDR))) { + out = append(out, subnet) + } + } + return + } + matchL2SubnetsByIPFamilies := func(families sets.Set[utilnet.IPFamily], in ...udnv1.CIDR) (out []udnv1.CIDR) { + for _, subnet := range in { + if families.Has(utilnet.IPFamilyOfCIDRString(string(subnet))) { + out = append(out, subnet) + } + } + return + } + + networksToTest := []ginkgo.TableEntry{ + ginkgo.Entry("Layer 3", layer3NetworkSpec), + } + + ginkgo.DescribeTableSubtree("When the tested network is of type", + func(networkSpec *udnv1.NetworkSpec) { + var testNamespace *corev1.Namespace + var testPod *corev1.Pod + + getSameNode := func() string { + return testPod.Spec.NodeName + } + getDifferentNode := func() string { + ginkgo.GinkgoHelper() + nodes, err := e2enode.GetReadySchedulableNodes(context.Background(), f.ClientSet) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to get ready schedulable nodes") + for _, node := range nodes.Items { + if node.Name != testPod.Spec.NodeName { + return node.Name + } + } + ginkgo.Fail(fmt.Sprintf("Failed to find a different ready schedulable node than %s", testPod.Spec.NodeName)) + return "" + } + + ginkgo.BeforeEach(func() { + var err error + networkSpec.Layer3.Subnets = matchL3SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer3.Subnets...) + + ginkgo.By("Configuring the namespace and network") + testNamespace, err = createNamespaceWithPrimaryNetworkOfType(f, ictx, testBaseName, testNetworkName, cudnAdvertisedVRFLite, networkSpec) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + f.Namespace = testNamespace + + // attach network to the VRF on all nodes + ginkgo.By("Attaching the BGP peer network to the CUDN VRF") + nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + network, err := infraprovider.Get().GetNetwork(testNetworkName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + for _, node := range nodeList.Items { + iface, err := infraprovider.Get().GetK8NodeNetworkInterface(node.Name, network) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + _, err = infraprovider.Get().ExecK8NodeCommand(node.Name, []string{"ip", "link", "set", "dev", iface.InfName, "master", testNetworkName}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // quirk: need to reset IPv6 address + _, err = infraprovider.Get().ExecK8NodeCommand(node.Name, []string{"ip", "address", "add", iface.IPv6 + "/" + iface.IPv6Prefix, "dev", iface.InfName}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + }) + + ginkgo.Describe("When a pod runs on the tested network", func() { + ginkgo.BeforeEach(func() { + ginkgo.By("Running a pod on the tested network namespace") + testPod = e2epod.CreateExecPodOrFail( + context.Background(), + f.ClientSet, + testNamespace.Name, + testNamespace.Name+"-netexec-pod", + func(p *corev1.Pod) { + p.Spec.Containers[0].Args = []string{"netexec"} + }, + ) + }) + + ginkgo.DescribeTable("It can reach an external server on the same network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the pod can reach the external server") + bgpServerNetwork, err := infraprovider.Get().GetNetwork(bgpServerName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + iface, err := infraprovider.Get().GetK8NodeNetworkInterface(bgpServerName, bgpServerNetwork) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + serverIP := getFirstIPStringOfFamily(family, []string{iface.IPv4, iface.IPv6}) + gomega.Expect(serverIP).NotTo(gomega.BeEmpty()) + testPodToHostnameAndExpect(testPod, serverIP, bgpServerName) + + ginkgo.By("Ensuring a request from the pod is not SNATed") + testPodIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + testPod.Namespace, + testPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(testPodIP).ToNot(gomega.BeEmpty()) + testPodToClientIPAndExpect(testPod, serverIP, testPodIP) + }, + ginkgo.Entry("When the network is IPv4", utilnet.IPv4), + ginkgo.Entry("When the network is IPv6", utilnet.IPv6), + ) + + ginkgo.DescribeTable("It can be reached by an external server on the same network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the external server can reach the pod") + bgpServerNetwork, err := infraprovider.Get().GetNetwork(bgpServerName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + iface, err := infraprovider.Get().GetK8NodeNetworkInterface(bgpServerName, bgpServerNetwork) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + serverIP := getFirstIPStringOfFamily(family, []string{iface.IPv4, iface.IPv6}) + gomega.Expect(serverIP).NotTo(gomega.BeEmpty()) + podIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + testPod.Namespace, + testPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(podIP).ToNot(gomega.BeEmpty()) + testContainerToClientIPAndExpect(bgpServerName, podIP, serverIP) + }, + ginkgo.Entry("When the network is IPv4", utilnet.IPv4), + ginkgo.Entry("When the network is IPv6", utilnet.IPv6), + ) + + ginkgo.It("Can reach KAPI service", func() { + ginkgo.By("Ensuring a request from the pod can reach KAPI service") + output, err := e2epodoutput.RunHostCmdWithRetries( + testPod.Namespace, + testPod.Name, + "curl --max-time 2 -g -q -s -k https://kubernetes.default/healthz", + framework.Poll, + timeout, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(output).To(gomega.Equal("ok")) + }) + + ginkgo.DescribeTable("It cannot reach an external server on a different network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the pod cannot reach the external server") + // using the external server setup for the default network + bgpServerNetwork, err := infraprovider.Get().GetNetwork(bgpExternalNetworkName) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + iface, err := infraprovider.Get().GetK8NodeNetworkInterface(serverContainerName, bgpServerNetwork) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + serverIP := getFirstIPStringOfFamily(family, []string{iface.IPv4, iface.IPv6}) + gomega.Expect(serverIP).NotTo(gomega.BeEmpty()) + testPodToClientIPNOK(testPod, serverIP) + }, + ginkgo.Entry("When the network is IPv4", utilnet.IPv4), + ginkgo.Entry("When the network is IPv6", utilnet.IPv6), + ) + + ginkgo.DescribeTable("It cannot be reached by an external server on a different network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the external server cannot reach the pod") + podIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + testPod.Namespace, + testPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(podIP).ToNot(gomega.BeEmpty()) + // using the external server setup for the default network + testContainerToClientIPNOK(serverContainerName, podIP) + }, + ginkgo.Entry("When the network is IPv4", utilnet.IPv4), + ginkgo.Entry("When the network is IPv6", utilnet.IPv6), + ) + + ginkgo.DescribeTableSubtree("It cannot be reached by a cluster node", + func(getNode func() string) { + ginkgo.DescribeTable("", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the node cannot reach the tested network pod") + podIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + testPod.Namespace, + testPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(podIP).ToNot(gomega.BeEmpty()) + testContainerToClientIPNOK(getNode(), podIP) + }, + ginkgo.Entry("When the network is IPv4", utilnet.IPv4), + ginkgo.Entry("When the network is IPv6", utilnet.IPv6), + ) + }, + ginkgo.Entry("When it is the same node", getSameNode), + ginkgo.Entry("When it is a different node", getDifferentNode), + ) + + ginkgo.DescribeTableSubtree("When other pod runs on the tested network", + func(getNode func() string) { + var otherPod *corev1.Pod + + ginkgo.BeforeEach(func() { + ginkgo.By("Running other pod on the tested network namespace") + otherPod = e2epod.CreateExecPodOrFail( + context.Background(), + f.ClientSet, + testNamespace.Name, + testNamespace.Name+"-netexec-pod", + func(p *corev1.Pod) { + p.Spec.Containers[0].Args = []string{"netexec"} + p.Labels = map[string]string{"app": "netexec-pod"} + }, + ) + }) + + ginkgo.DescribeTable("The pods on the tested network can reach each other", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the first pod can reach the second pod") + otherPodIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + otherPod.Namespace, + otherPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(otherPodIP).ToNot(gomega.BeEmpty()) + testPodToClientIP(testPod, otherPodIP) + }, + ginkgo.Entry("When the networks are IPv4", utilnet.IPv4), + ginkgo.Entry("When the networks are IPv6", utilnet.IPv6), + ) + + ginkgo.Describe("Backing a ClusterIP service", func() { + var service *corev1.Service + + ginkgo.BeforeEach(func() { + ginkgo.By("Creating a service backed by the other network pod") + service = e2eservice.CreateServiceSpec( + "service-for-netexec", + "", + false, + otherPod.Labels, + ) + service.Spec.Ports = []corev1.ServicePort{{Port: netexecPort}} + familyPolicy := corev1.IPFamilyPolicyPreferDualStack + service.Spec.IPFamilyPolicy = &familyPolicy + var err error + service, err = f.ClientSet.CoreV1().Services(otherPod.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.DescribeTable("The first pod can reach the ClusterIP service on the same network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the first pod can reach the ClusterIP service") + clusterIP := getFirstIPStringOfFamily(family, service.Spec.ClusterIPs) + gomega.Expect(clusterIP).ToNot(gomega.BeEmpty()) + testPodToClientIP(testPod, clusterIP) + }, + ginkgo.Entry("When the networks are IPv4", utilnet.IPv4), + ginkgo.Entry("When the networks are IPv6", utilnet.IPv6), + ) + }) + }, + ginkgo.Entry("On the same node", getSameNode), + ginkgo.Entry("On a different node", getDifferentNode), + ) + + ginkgo.Describe("When there is other network", func() { + const ( + otherBGPPeerSubnetIPv4 = "172.136.0.0/16" + otherBGPPeerSubnetIPv6 = "fc00:f853:ccd:136::/64" + otherBGPServerSubnetIPv4 = "172.138.0.0/16" + otherBGPServerSubnetIPv6 = "fc00:f853:ccd:138::/64" + otherUDNCIDRv4 = "103.203.0.0/16" + otherUDNCIDRv6 = "2014:200:200::0/60" + ) + + var ( + otherLayer3NetworkSpec = &udnv1.NetworkSpec{ + Topology: udnv1.NetworkTopologyLayer3, + Layer3: &udnv1.Layer3Config{ + Role: "Primary", + Subnets: []udnv1.Layer3Subnet{{CIDR: otherUDNCIDRv4, HostSubnet: 24}, {CIDR: otherUDNCIDRv6, HostSubnet: 64}}, + }, + } + otherLayer2NetworkSpec = &udnv1.NetworkSpec{ + Topology: udnv1.NetworkTopologyLayer2, + Layer2: &udnv1.Layer2Config{ + Role: "Primary", + Subnets: udnv1.DualStackCIDRs{otherUDNCIDRv4, otherUDNCIDRv6}, + }, + } + ) + + otherNetworksToTest := []ginkgo.TableEntry{ + ginkgo.Entry("Default", defaultNetwork, nil), + ginkgo.Entry("Layer 3 UDN non advertised", udn, otherLayer3NetworkSpec), + ginkgo.Entry("Layer 3 CUDN advertised", cudnAdvertised, otherLayer3NetworkSpec), + ginkgo.Entry("Layer 3 CUDN advertised VRF-Lite", cudnAdvertisedVRFLite, otherLayer3NetworkSpec), + ginkgo.Entry("Layer 2 UDN non advertised", udn, otherLayer2NetworkSpec), + } + + ginkgo.DescribeTableSubtree("Of type", + func(networkType networkType, networkSpec *udnv1.NetworkSpec) { + var otherNamespace *corev1.Namespace + var otherNetworkName string + + ginkgo.BeforeEach(func() { + otherNetworkName = testBaseName + "-other" + otherNamespaceName := otherNetworkName + + switch { + case networkSpec == nil: + // noop + case networkSpec.Layer3 != nil: + networkSpec.Layer3.Subnets = matchL3SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer3.Subnets...) + case networkSpec.Layer2 != nil: + networkSpec.Layer2.Subnets = matchL2SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer2.Subnets...) + } + + // we will create a agnhost server on an extra network peered with BGP + switch networkType { + case cudnAdvertisedVRFLite: + ginkgo.By("Running other BGP network with an agnhost server") + otherBGPServerName := otherNetworkName + "-bgpserver" + bgpPeerCIDRs := []string{otherBGPPeerSubnetIPv4, otherBGPPeerSubnetIPv6} + bgpServerCIDRs := []string{otherBGPServerSubnetIPv4, otherBGPServerSubnetIPv6} + gomega.Expect(runBGPNetworkAndServer(f, ictx, otherNetworkName, otherBGPServerName, bgpPeerCIDRs, bgpServerCIDRs)).To(gomega.Succeed()) + case defaultNetwork: + otherNetworkName = "default" + } + + ginkgo.By("Creating the other namespace and network") + var err error + otherNamespace, err = createNamespaceWithPrimaryNetworkOfType(f, ictx, testBaseName, otherNamespaceName, networkType, networkSpec) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.DescribeTableSubtree("And a pod runs on the other network", + func(getNode func() string) { + var otherPod *corev1.Pod + + ginkgo.BeforeEach(func() { + ginkgo.By("Running a pod on the other network namespace") + otherPod = e2epod.CreateExecPodOrFail( + context.Background(), + f.ClientSet, + otherNamespace.Name, + otherNamespace.Name+"-netexec-pod", + func(p *corev1.Pod) { + p.Spec.Containers[0].Args = []string{"netexec"} + p.Spec.NodeName = getNode() + p.Labels = map[string]string{"app": "netexec-pod"} + }, + ) + }) + + ginkgo.DescribeTable("The pod on the tested network cannot reach the pod on the other network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the tested network pod cannot reach the other network pod") + otherPodIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + otherPod.Namespace, + otherPod.Name, + otherNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(otherPodIP).ToNot(gomega.BeEmpty()) + testPodToClientIPNOK(testPod, otherPodIP) + }, + ginkgo.Entry("When the networks are IPv4", utilnet.IPv4), + ginkgo.Entry("When the networks are IPv6", utilnet.IPv6), + ) + + ginkgo.DescribeTable("The pod on the other network cannot reach the pod on the tested network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the other network pod cannot reach the tested network pod") + testPodIP, err := podIPOfFamilyOnPrimaryNetwork( + f.ClientSet, + testPod.Namespace, + testPod.Name, + testNetworkName, + family, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(testPodIP).ToNot(gomega.BeEmpty()) + testPodToClientIPNOK(otherPod, testPodIP) + }, + ginkgo.Entry("When the networks are IPv4", utilnet.IPv4), + ginkgo.Entry("When the networks are IPv6", utilnet.IPv6), + ) + + ginkgo.Describe("Backing a ClusterIP service", func() { + var service *corev1.Service + + ginkgo.BeforeEach(func() { + ginkgo.By("Creating a service backed by the other network pod") + service = e2eservice.CreateServiceSpec( + "service-for-netexec", + "", + false, + otherPod.Labels, + ) + service.Spec.Ports = []corev1.ServicePort{{Port: netexecPort}} + familyPolicy := corev1.IPFamilyPolicyPreferDualStack + service.Spec.IPFamilyPolicy = &familyPolicy + var err error + service, err = f.ClientSet.CoreV1().Services(otherPod.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.DescribeTable("The pod on the tested network cannot reach the service on the other network", + func(family utilnet.IPFamily) { + if !ipFamilySet.Has(family) { + e2eskipper.Skipf("IP family %v not supported", family) + } + ginkgo.By("Ensuring a request from the tested network pod cannot reach the other network pod") + clusterIP := getFirstIPStringOfFamily(family, service.Spec.ClusterIPs) + gomega.Expect(clusterIP).ToNot(gomega.BeEmpty()) + testPodToClientIPNOK(testPod, clusterIP) + }, + ginkgo.Entry("When the networks are IPv4", utilnet.IPv4), + ginkgo.Entry("When the networks are IPv6", utilnet.IPv6), + ) + }) + }, + ginkgo.Entry("On the same node", getSameNode), + ginkgo.Entry("On a different node", getDifferentNode), + ) + }, + otherNetworksToTest, + ) + }) + }) + }, + networksToTest, + ) +}) + +// routeAdvertisementsReadyFunc returns a function that checks for the +// Accepted condition in the provided RouteAdvertisements +func routeAdvertisementsReadyFunc(c raclientset.Clientset, name string) func() error { + return func() error { + ra, err := c.K8sV1().RouteAdvertisements().Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + return err + } + conditionType := "Accepted" + condition := meta.FindStatusCondition(ra.Status.Conditions, conditionType) + if condition == nil { + return fmt.Errorf("no %q condition found in: %v", conditionType, ra) + } + if condition.Status != metav1.ConditionTrue { + return fmt.Errorf("condition %v has unexpected status %v", condition, condition.Status) + } + return nil + } +} + +// templateInputRouter data +type templateInputRouter struct { + VRF string + NeighborsIPv4 []string + NeighborsIPv6 []string + NetworksIPv4 []string + NetworksIPv6 []string +} + +// templateInputFRR data +type templateInputFRR struct { + // Name and Label are used for FRRConfiguration metadata + Name string + Labels map[string]string + Routers []templateInputRouter +} + +// for routeadvertisements test cases we generate configuration from templates embed in the program +// +//go:embed testdata/routeadvertisements +var ratestdata embed.FS +var tmplDir = filepath.Join("testdata", "routeadvertisements") + +const frrImage = "quay.io/frrouting/frr:9.1.3" + +// generateFRRConfiguration to establish a BGP session towards the provided +// neighbors in the network's VRF configured to advertised the provided +// networks. Returns a temporary directory where the configuration is generated. +func generateFRRConfiguration(neighborIPs, advertiseNetworks []string) (directory string, err error) { + // parse configuration templates + var templates *template.Template + templates, err = template.ParseFS(ratestdata, filepath.Join(tmplDir, "frr", "*.tmpl")) + if err != nil { + return "", fmt.Errorf("failed to parse templates: %w", err) + } + + // create the directory that will hold the configuration files + directory, err = os.MkdirTemp("", "frrconf-") + if err != nil { + return "", fmt.Errorf("failed to make temp directory: %w", err) + } + defer func() { + if err != nil { + os.RemoveAll(directory) + } + }() + + // generate external frr configuration executing the templates + networksIPv4, networksIPv6 := splitCIDRStringsByIPFamily(advertiseNetworks) + neighborsIPv4, neighborsIPv6 := splitIPStringsByIPFamily(neighborIPs) + conf := templateInputFRR{ + Routers: []templateInputRouter{ + { + NeighborsIPv4: neighborsIPv4, + NetworksIPv4: networksIPv4, + NeighborsIPv6: neighborsIPv6, + NetworksIPv6: networksIPv6, + }, + }, + } + + err = executeFileTemplate(templates, directory, "frr.conf", conf) + if err != nil { + return "", fmt.Errorf("failed to execute template %q: %w", "frr.conf", err) + } + err = executeFileTemplate(templates, directory, "daemons", nil) + if err != nil { + return "", fmt.Errorf("failed to execute template %q: %w", "daemons", err) + } + + return directory, nil +} + +// generateFRRk8sConfiguration for the provided network (which doubles up as the +// FRRConfiguration instance name, VRF name and used as value of `network` +// label) to establish a BGP session towards the provided neighbors in the +// network's VRF, configured to receive advertisements for the provided +// networks. Returns a temporary directory where the configuration is generated. +func generateFRRk8sConfiguration(networkName string, neighborIPs, receiveNetworks []string) (directory string, err error) { + // parse configuration templates + var templates *template.Template + templates, err = template.ParseFS(ratestdata, filepath.Join(tmplDir, "frr-k8s", "*.tmpl")) + if err != nil { + return "", fmt.Errorf("failed to parse templates: %w", err) + } + + // create the directory that will hold the configuration files + directory, err = os.MkdirTemp("", "frrk8sconf-") + if err != nil { + return "", fmt.Errorf("failed to make temp directory: %w", err) + } + defer func() { + if err != nil { + os.RemoveAll(directory) + } + }() + + receivesIPv4, receivesIPv6 := splitCIDRStringsByIPFamily(receiveNetworks) + neighborsIPv4, neighborsIPv6 := splitIPStringsByIPFamily(neighborIPs) + conf := templateInputFRR{ + Name: networkName, + Labels: map[string]string{"network": networkName}, + Routers: []templateInputRouter{ + { + VRF: networkName, + NeighborsIPv4: neighborsIPv4, + NeighborsIPv6: neighborsIPv6, + NetworksIPv4: receivesIPv4, + NetworksIPv6: receivesIPv6, + }, + }, + } + err = executeFileTemplate(templates, directory, "frrconf.yaml", conf) + if err != nil { + return "", fmt.Errorf("failed to execute template %q: %w", "frrconf.yaml", err) + } + + return directory, nil +} + +// runBGPNetworkAndServer configures a topology appropriate to be used with +// route advertisement test cases. For VRF-Lite test cases, the caller is +// resposible to attach the peer network interface to the CUDN VRF on the nodes. +// +// ----------------- ------------------ --------------- +// | | serverNetwork | | peerNetwork | | +// | external |<--------------- | FRR router |<--( Default / CUDN VRF )-- | cluster | +// | server | | | | | +// ----------------- ------------------ --------------- +func runBGPNetworkAndServer( + f *framework.Framework, + ictx infraapi.Context, + networkName, serverName string, + peerNetworks, + serverNetworks []string, +) error { + // filter networks by supported IP families + families := getSupportedIPFamiliesSlice(f.ClientSet) + peerNetworks = matchCIDRStringsByIPFamily(peerNetworks, families...) + serverNetworks = matchCIDRStringsByIPFamily(serverNetworks, families...) + + // create BGP peer network + bgpPeerNetwork, err := ictx.CreateNetwork(networkName, peerNetworks...) + if err != nil { + return fmt.Errorf("failed to create peer network %v: %w", peerNetworks, err) + } + + // create the server network + serverNetwork, err := ictx.CreateNetwork(serverName, serverNetworks...) + if err != nil { + return fmt.Errorf("failed to create server network %v: %w", serverNetworks, err) + } + + // attach BGP peer network to all nodes + var nodeIPs []string + nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list nodes: %w", err) + } + for _, node := range nodeList.Items { + iface, err := ictx.AttachNetwork(bgpPeerNetwork, node.Name) + if err != nil { + return fmt.Errorf("failed to attach node %q to network: %w", node.Name, err) + } + nodeIPs = append(nodeIPs, iface.IPv4, iface.IPv6) + } + + // run frr container + advertiseNetworks := serverNetworks + frrConfig, err := generateFRRConfiguration(nodeIPs, advertiseNetworks) + if err != nil { + return fmt.Errorf("failed to generate FRR configuration: %w", err) + } + ictx.AddCleanUpFn(func() error { return os.RemoveAll(frrConfig) }) + frr := infraapi.ExternalContainer{ + Name: networkName + "-frr", + Image: frrImage, + Network: bgpPeerNetwork, + RuntimeArgs: []string{"--volume", frrConfig + ":" + filepath.Join(filepath.FromSlash("/"), "etc", "frr")}, + } + frr, err = ictx.CreateExternalContainer(frr) + if err != nil { + return fmt.Errorf("failed to create frr container: %w", err) + } + // enable IPv6 forwarding if required + if frr.IPv6 != "" { + _, err = infraprovider.Get().ExecExternalContainerCommand(frr, []string{"sysctl", "-w", "net.ipv6.conf.all.forwarding=1"}) + if err != nil { + return fmt.Errorf("failed to set enable IPv6 forwading on frr container: %w", err) + } + } + + // connect frr to server network + frrServerNetworkInterface, err := ictx.AttachNetwork(serverNetwork, frr.Name) + if err != nil { + return fmt.Errorf("failed to connect frr to server network: %w", err) + } + + // run server container + server := infraapi.ExternalContainer{ + Name: serverName, + Image: images.AgnHost(), + CmdArgs: []string{"netexec"}, + Network: serverNetwork, + } + _, err = ictx.CreateExternalContainer(server) + if err != nil { + return fmt.Errorf("failed to create BGP server container: %w", err) + } + + // set frr as default gateway for the server + if frrServerNetworkInterface.IPv4 != "" { + _, err = infraprovider.Get().ExecExternalContainerCommand(server, []string{"ip", "route", "add", "default", "via", frrServerNetworkInterface.IPv4}) + if err != nil { + return fmt.Errorf("failed to set default IPv4 gateway on BGP server container: %w", err) + } + } + if frrServerNetworkInterface.IPv6 != "" { + _, err = infraprovider.Get().ExecExternalContainerCommand(server, []string{"ip", "-6", "route", "add", "default", "via", frrServerNetworkInterface.IPv6}) + if err != nil { + return fmt.Errorf("failed to set default IPv6 gateway on BGP server container: %w", err) + } + + } + + // apply FRR-K8s Configuration + receiveNetworks := serverNetworks + frrK8sConfig, err := generateFRRk8sConfiguration(networkName, []string{frr.IPv4, frr.IPv6}, receiveNetworks) + if err != nil { + return fmt.Errorf("failed to generate FRR-k8s configuration: %w", err) + } + ictx.AddCleanUpFn(func() error { return os.RemoveAll(frrK8sConfig) }) + _, err = e2ekubectl.RunKubectl(deploymentconfig.Get().FRRK8sNamespace(), "create", "-f", frrK8sConfig) + if err != nil { + return fmt.Errorf("failed to apply FRRConfiguration: %w", err) + } + ictx.AddCleanUpFn(func() error { + _, err = e2ekubectl.RunKubectl(deploymentconfig.Get().FRRK8sNamespace(), "delete", "-f", frrK8sConfig) + if err != nil { + return fmt.Errorf("failed to delete FRRConfiguration: %w", err) + } + return nil + }) + + return nil +} + +type networkType string + +const ( + defaultNetwork networkType = "DEFAULT" + udn networkType = "UDN" + cudn networkType = "CUDN" + cudnAdvertised networkType = "CUDN_ADVERTISED" + cudnAdvertisedVRFLite networkType = "CUDN_ADVERTISED_VRFLITE" +) + +// createNamespaceWithPrimaryNetworkOfType helper function configures a +// namespace, a optional(C)UDN and an optional RouteAdvertisements as determined +// by `networkType` argument. The RouteAdvertisements is aligned with the +// configuration done with `runBGPNetworkAndServer` for VRF-Lite scenarios. +func createNamespaceWithPrimaryNetworkOfType( + f *framework.Framework, + ictx infraapi.Context, + test, name string, + networkType networkType, + networkSpec *udnv1.NetworkSpec, +) (*corev1.Namespace, error) { + // define some configuration based on the type of namespace/network/advertisement + var targetVRF string + var networkLabels map[string]string + var frrConfigurationLabels map[string]string + switch networkType { + case cudnAdvertised: + networkLabels = map[string]string{"advertise": name} + frrConfigurationLabels = map[string]string{"name": "receive-all"} + case cudnAdvertisedVRFLite: + targetVRF = name + networkLabels = map[string]string{"advertise": name} + frrConfigurationLabels = map[string]string{"network": name} + } + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "e2e-framework": test, + }, + }, + } + if networkType != defaultNetwork { + namespace.Labels[RequiredUDNNamespaceLabel] = "" + } + namespace, err := f.ClientSet.CoreV1().Namespaces().Create( + context.Background(), + namespace, + metav1.CreateOptions{}, + ) + if err != nil { + return nil, fmt.Errorf("failed to create namespace: %w", err) + } + ictx.AddCleanUpFn(func() error { + return f.ClientSet.CoreV1().Namespaces().Delete(context.Background(), namespace.Name, metav1.DeleteOptions{}) + }) + + // just creating a namespace with default network, return + if networkType == defaultNetwork { + return namespace, nil + } + + err = createUserDefinedNetwork( + f, + ictx, + namespace, + name, + networkType != udn, + networkSpec, + networkLabels, + ) + if err != nil { + return nil, fmt.Errorf("failed to create primary network: %w", err) + } + + // not advertised, return + if networkType == udn || networkType == cudn { + return namespace, nil + } + + err = createRouteAdvertisements( + f, + ictx, + name, + targetVRF, + networkLabels, + frrConfigurationLabels, + ) + if err != nil { + return nil, fmt.Errorf("failed to create primary network: %w", err) + } + + return namespace, nil +} + +func createUserDefinedNetwork( + f *framework.Framework, + ictx infraapi.Context, + namespace *corev1.Namespace, + name string, + cudnType bool, + networkSpec *udnv1.NetworkSpec, + networkLabels map[string]string, +) error { + var gvr schema.GroupVersionResource + var gvk schema.GroupVersionKind + var obj runtime.Object + var client dynamic.ResourceInterface + switch { + case cudnType: + gvr = clusterUDNGVR + gvk = schema.GroupVersionKind{ + Group: gvr.Group, + Version: gvr.Version, + Kind: "ClusterUserDefinedNetwork", + } + client = f.DynamicClient.Resource(gvr) + obj = &udnv1.ClusterUserDefinedNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: networkLabels, + }, + Spec: udnv1.ClusterUserDefinedNetworkSpec{ + NamespaceSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "kubernetes.io/metadata.name", + Operator: metav1.LabelSelectorOpIn, + Values: []string{namespace.Name}, + }}}, + Network: *networkSpec, + }, + } + default: + gvr = udnGVR + gvk = schema.GroupVersionKind{ + Group: gvr.Group, + Version: gvr.Version, + Kind: "UserDefinedNetwork", + } + client = f.DynamicClient.Resource(gvr).Namespace(namespace.Name) + obj = &udnv1.UserDefinedNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace.Name, + Labels: networkLabels, + }, + Spec: udnv1.UserDefinedNetworkSpec{ + Topology: networkSpec.Topology, + Layer3: networkSpec.Layer3, + Layer2: networkSpec.Layer2, + }, + } + } + + unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return fmt.Errorf("failed to convert network to unstructured: %w", err) + } + unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap} + ok := unstructuredObj.GetObjectKind() + ok.SetGroupVersionKind(gvk) + + _, err = client.Create(context.Background(), unstructuredObj, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to convert network to unstructured: %w", err) + } + ictx.AddCleanUpFn(func() error { + return client.Delete(context.Background(), name, metav1.DeleteOptions{}) + }) + wait.PollUntilContextTimeout( + context.Background(), + time.Second, + 5*time.Second, + true, + func(ctx context.Context) (bool, error) { + err = networkReadyFunc(client, name)() + return err == nil, nil + }, + ) + if err != nil { + return fmt.Errorf("failed to wait for the network to be ready: %w", err) + } + + return nil +} + +func createRouteAdvertisements( + f *framework.Framework, + ictx infraapi.Context, + name string, + targetVRF string, + networkMatchLabels map[string]string, + frrconfigurationMatchLabels map[string]string, +) error { + ra := &rav1.RouteAdvertisements{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: rav1.RouteAdvertisementsSpec{ + NetworkSelectors: apitypes.NetworkSelectors{ + apitypes.NetworkSelector{ + NetworkSelectionType: apitypes.ClusterUserDefinedNetworks, + ClusterUserDefinedNetworkSelector: &apitypes.ClusterUserDefinedNetworkSelector{ + NetworkSelector: metav1.LabelSelector{ + MatchLabels: networkMatchLabels, + }, + }, + }, + }, + FRRConfigurationSelector: metav1.LabelSelector{ + MatchLabels: frrconfigurationMatchLabels, + }, + NodeSelector: metav1.LabelSelector{}, + Advertisements: []rav1.AdvertisementType{ + rav1.PodNetwork, + }, + TargetVRF: targetVRF, + }, + } + + raClient, err := raclientset.NewForConfig(f.ClientConfig()) + if err != nil { + return fmt.Errorf("failed to create RouteAdvertisements client: %w", err) + } + _, err = raClient.K8sV1().RouteAdvertisements().Create(context.TODO(), ra, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create RouteAdvertisements: %w", err) + } + ictx.AddCleanUpFn(func() error { + return raClient.K8sV1().RouteAdvertisements().Delete(context.Background(), name, metav1.DeleteOptions{}) + }) + wait.PollUntilContextTimeout( + context.Background(), + time.Second, + 5*time.Second, + true, + func(ctx context.Context) (bool, error) { + err = routeAdvertisementsReadyFunc(*raClient, name)() + return err == nil, nil + }, + ) + if err != nil { + return fmt.Errorf("failed to wait for the RouteAdvertisements to be ready: %w", err) + } + + return nil +} diff --git a/test/e2e/testdata/routeadvertisements/frr-k8s/frrconf.yaml.tmpl b/test/e2e/testdata/routeadvertisements/frr-k8s/frrconf.yaml.tmpl new file mode 100644 index 0000000000..ba4b4605ad --- /dev/null +++ b/test/e2e/testdata/routeadvertisements/frr-k8s/frrconf.yaml.tmpl @@ -0,0 +1,46 @@ +{{- define "frrconf.yaml" -}} +apiVersion: frrk8s.metallb.io/v1beta1 +kind: FRRConfiguration +metadata: + name: {{ .Name }} +{{- if .Labels }} + labels: +{{- range $k, $v := .Labels }} + {{ $k }}: {{ $v }} +{{- end }} +{{- end }} +spec: + bgp: + routers: +{{- range $v := .Routers }} + - asn: 64512 +{{- if .VRF }} + vrf: {{ .VRF }} +{{- end }} + neighbors: +{{- range .NeighborsIPv4 }} + - address: {{ . }} + asn: 64512 + disableMP: true + toReceive: + allowed: + mode: filtered + prefixes: +{{- range $v.NetworksIPv4 }} + - prefix: {{ . }} +{{- end }} +{{- end }} +{{- range .NeighborsIPv6 }} + - address: {{ . }} + asn: 64512 + disableMP: true + toReceive: + allowed: + mode: filtered + prefixes: +{{- range $v.NetworksIPv6 }} + - prefix: {{ . }} +{{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/test/e2e/testdata/routeadvertisements/frr/daemons.tmpl b/test/e2e/testdata/routeadvertisements/frr/daemons.tmpl new file mode 100644 index 0000000000..5434bdf418 --- /dev/null +++ b/test/e2e/testdata/routeadvertisements/frr/daemons.tmpl @@ -0,0 +1,82 @@ +{{- define "daemons" -}} +# This file tells the frr package which daemons to start. +# +# Sample configurations for these daemons can be found in +# /usr/share/doc/frr/examples/. +# +# ATTENTION: +# +# When activating a daemon for the first time, a config file, even if it is +# empty, has to be present *and* be owned by the user and group "frr", else +# the daemon will not be started by /etc/init.d/frr. The permissions should +# be u=rw,g=r,o=. +# When using "vtysh" such a config file is also needed. It should be owned by +# group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too. +# +# The watchfrr and zebra daemons are always started. +# +bgpd=yes +ospfd=no +ospf6d=no +ripd=no +ripngd=no +isisd=no +pimd=no +ldpd=no +nhrpd=no +eigrpd=no +babeld=no +sharpd=no +pbrd=no +bfdd=yes +fabricd=no +vrrpd=no + +# +# If this option is set the /etc/init.d/frr script automatically loads +# the config via "vtysh -b" when the servers are started. +# Check /etc/pam.d/frr if you intend to use "vtysh"! +# +vtysh_enable=yes +zebra_options=" -A 127.0.0.1 -s 90000000" +bgpd_options=" -A 127.0.0.1" +ospfd_options=" -A 127.0.0.1" +ospf6d_options=" -A ::1" +ripd_options=" -A 127.0.0.1" +ripngd_options=" -A ::1" +isisd_options=" -A 127.0.0.1" +pimd_options=" -A 127.0.0.1" +ldpd_options=" -A 127.0.0.1" +nhrpd_options=" -A 127.0.0.1" +eigrpd_options=" -A 127.0.0.1" +babeld_options=" -A 127.0.0.1" +sharpd_options=" -A 127.0.0.1" +pbrd_options=" -A 127.0.0.1" +staticd_options="-A 127.0.0.1" +bfdd_options=" -A 127.0.0.1" +fabricd_options="-A 127.0.0.1" +vrrpd_options=" -A 127.0.0.1" + +# configuration profile +# +#frr_profile="traditional" +#frr_profile="datacenter" + +# +# This is the maximum number of FD's that will be available. +# Upon startup this is read by the control files and ulimit +# is called. Uncomment and use a reasonable value for your +# setup if you are expecting a large number of peers in +# say BGP. +#MAX_FDS=1024 + +# The list of daemons to watch is automatically generated by the init script. +#watchfrr_options="" + +# for debugging purposes, you can specify a "wrap" command to start instead +# of starting the daemon directly, e.g. to use valgrind on ospfd: +# ospfd_wrap="/usr/bin/valgrind" +# or you can use "all_wrap" for all daemons, e.g. to use perf record: +# all_wrap="/usr/bin/perf record --call-graph -" +# the normal daemon command is added to this at the end. +{{ end }} diff --git a/test/e2e/testdata/routeadvertisements/frr/frr.conf.tmpl b/test/e2e/testdata/routeadvertisements/frr/frr.conf.tmpl new file mode 100644 index 0000000000..a1beeab410 --- /dev/null +++ b/test/e2e/testdata/routeadvertisements/frr/frr.conf.tmpl @@ -0,0 +1,57 @@ +{{- define "frr.conf" -}} +debug zebra events +debug zebra nht detailed +debug zebra kernel +debug zebra rib detail +debug zebra nexthop detail +debug bgp keepalives +debug bgp neighbor-events +debug bgp nht +debug bgp updates +debug bgp zebra +log stdout debugging +log syslog debugging +log file /etc/frr/frr.log debugging +{{ range .Routers -}} +router bgp 64512 {{ if .VRF }}vrf {{ .VRF }}{{ end }} + no bgp default ipv4-unicast + no bgp default ipv6-unicast + no bgp network import-check +{{- range .NeighborsIPv4 }} + neighbor {{ . }} remote-as 64512 + # zebra has been observed to fail to start for unknown reasons, + # reduce timers to try to minimize delay impact on tests + neighbor {{ . }} timers connect 10 + neighbor {{ . }} timers 15 5 +{{- end }} +{{- range .NeighborsIPv6 }} + neighbor {{ . }} remote-as 64512 + neighbor {{ . }} timers connect 10 + neighbor {{ . }} timers 15 5 +{{- end }} +{{- if .NeighborsIPv4 }} + address-family ipv4 unicast +{{- range .NeighborsIPv4 }} + neighbor {{ . }} route-reflector-client + neighbor {{ . }} activate + neighbor {{ . }} next-hop-self +{{- end }} +{{- range .NetworksIPv4 }} + network {{ . }} +{{- end }} + exit-address-family +{{- end }} +{{- if .NeighborsIPv6 }} + address-family ipv6 unicast +{{- range .NeighborsIPv6 }} + neighbor {{ . }} route-reflector-client + neighbor {{ . }} activate + neighbor {{ . }} next-hop-self +{{- end }} +{{- range .NetworksIPv6 }} + network {{ . }} +{{- end }} + exit-address-family +{{- end }} +{{ end }} +{{ end }} diff --git a/test/e2e/util.go b/test/e2e/util.go index e31bffe724..07bb4f9f76 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -7,9 +7,11 @@ import ( "math/rand" "net" "os" + "path/filepath" "regexp" "strconv" "strings" + "text/template" "time" "github.com/onsi/ginkgo/v2" @@ -30,7 +32,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" - clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework/debug" e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" @@ -167,7 +168,7 @@ func newAgnhostPodOnNode(name, nodeName string, labels map[string]string, comman } // IsIPv6Cluster returns true if the kubernetes default service is IPv6 -func IsIPv6Cluster(c clientset.Interface) bool { +func IsIPv6Cluster(c kubernetes.Interface) bool { // Get the ClusterIP of the kubernetes service created in the default namespace svc, err := c.CoreV1().Services(metav1.NamespaceDefault).Get(context.Background(), "kubernetes", metav1.GetOptions{}) if err != nil { @@ -656,7 +657,7 @@ func waitClusterHealthy(f *framework.Framework, numControlPlanePods int, control // successfully rolled out following an update. // // If allowedNotReadyNodes is -1, this method returns immediately without waiting. -func waitForRollout(c clientset.Interface, ns string, resource string, allowedNotReadyNodes int32, timeout time.Duration) error { +func waitForRollout(c kubernetes.Interface, ns string, resource string, allowedNotReadyNodes int32, timeout time.Duration) error { if allowedNotReadyNodes == -1 { return nil } @@ -1129,24 +1130,24 @@ func randStr(n int) string { return string(b) } -func isCIDRIPFamilySupported(cs clientset.Interface, cidr string) bool { +func isCIDRIPFamilySupported(cs kubernetes.Interface, cidr string) bool { ginkgo.GinkgoHelper() gomega.Expect(cidr).To(gomega.ContainSubstring("/")) isIPv6 := utilnet.IsIPv6CIDRString(cidr) return (isIPv4Supported(cs) && !isIPv6) || (isIPv6Supported(cs) && isIPv6) } -func isIPv4Supported(cs clientset.Interface) bool { +func isIPv4Supported(cs kubernetes.Interface) bool { v4, _ := getSupportedIPFamilies(cs) return v4 } -func isIPv6Supported(cs clientset.Interface) bool { +func isIPv6Supported(cs kubernetes.Interface) bool { _, v6 := getSupportedIPFamilies(cs) return v6 } -func getSupportedIPFamilies(cs clientset.Interface) (bool, bool) { +func getSupportedIPFamilies(cs kubernetes.Interface) (bool, bool) { n, err := e2enode.GetRandomReadySchedulableNode(context.TODO(), cs) framework.ExpectNoError(err, "must fetch a Ready Node") v4NodeAddrs := e2enode.GetAddressesByTypeAndFamily(n, v1.NodeInternalIP, v1.IPv4Protocol) @@ -1154,6 +1155,19 @@ func getSupportedIPFamilies(cs clientset.Interface) (bool, bool) { return len(v4NodeAddrs) > 0, len(v6NodeAddrs) > 0 } +func getSupportedIPFamiliesSlice(cs kubernetes.Interface) []utilnet.IPFamily { + v4, v6 := getSupportedIPFamilies(cs) + switch { + case v4 && v6: + return []utilnet.IPFamily{utilnet.IPv4, utilnet.IPv6} + case v4: + return []utilnet.IPFamily{utilnet.IPv4} + case v6: + return []utilnet.IPFamily{utilnet.IPv6} + } + return nil +} + func isInterconnectEnabled() bool { val, present := os.LookupEnv("OVN_ENABLE_INTERCONNECT") return present && val == "true" @@ -1281,7 +1295,7 @@ func GetNodeIPv6LinkLocalAddressForEth0(nodeName string) (string, error) { // right-most match of the provided regex. Returns a map of subexpression name // to subexpression capture. A zero string name `""` maps to the full expression // capture. -func CaptureContainerOutput(ctx context.Context, c clientset.Interface, namespace, pod, container, regexpr string) (map[string]string, error) { +func CaptureContainerOutput(ctx context.Context, c kubernetes.Interface, namespace, pod, container, regexpr string) (map[string]string, error) { regex, err := regexp.Compile(regexpr) if err != nil { return nil, fmt.Errorf("failed to compile regexp %q: %w", regexpr, err) @@ -1352,9 +1366,62 @@ func matchIPv6StringFamily(ipStrings []string) (string, error) { return util.MatchIPStringFamily(true /*ipv6*/, ipStrings) } +func matchCIDRStringsByIPFamily(cidrs []string, families ...utilnet.IPFamily) []string { + var r []string + familySet := sets.New(families...) + for _, cidr := range cidrs { + if familySet.Has(utilnet.IPFamilyOfCIDRString(cidr)) { + r = append(r, cidr) + } + } + return r +} + +func splitCIDRStringsByIPFamily(cidrs []string) (ipv4 []string, ipv6 []string) { + for _, cidr := range cidrs { + switch { + case utilnet.IsIPv4CIDRString(cidr): + ipv4 = append(ipv4, cidr) + case utilnet.IsIPv6CIDRString(cidr): + ipv6 = append(ipv6, cidr) + } + } + return +} + +func splitIPStringsByIPFamily(ips []string) (ipv4 []string, ipv6 []string) { + for _, ip := range ips { + switch { + case utilnet.IsIPv4String(ip): + ipv4 = append(ipv4, ip) + case utilnet.IsIPv6String(ip): + ipv6 = append(ipv6, ip) + } + } + return +} + +func getFirstCIDROfFamily(family utilnet.IPFamily, ipnets []*net.IPNet) *net.IPNet { + for _, ipnet := range ipnets { + if utilnet.IPFamilyOfCIDR(ipnet) == family { + return ipnet + } + } + return nil +} + +func getFirstIPStringOfFamily(family utilnet.IPFamily, ips []string) string { + for _, ip := range ips { + if utilnet.IPFamilyOfString(ip) == family { + return ip + } + } + return "" +} + // This is a replacement for e2epod.DeletePodWithWait(), which does not handle pods that // may be automatically restarted (https://issues.k8s.io/126785) -func deletePodWithWait(ctx context.Context, c clientset.Interface, pod *v1.Pod) error { +func deletePodWithWait(ctx context.Context, c kubernetes.Interface, pod *v1.Pod) error { if pod == nil { return nil } @@ -1382,7 +1449,7 @@ func deletePodWithWait(ctx context.Context, c clientset.Interface, pod *v1.Pod) // This is a replacement for e2epod.DeletePodWithWaitByName(), which does not handle pods // that may be automatically restarted (https://issues.k8s.io/126785) -func deletePodWithWaitByName(ctx context.Context, c clientset.Interface, podName, podNamespace string) error { +func deletePodWithWaitByName(ctx context.Context, c kubernetes.Interface, podName, podNamespace string) error { pod, err := c.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { @@ -1400,7 +1467,7 @@ func deletePodWithWaitByName(ctx context.Context, c clientset.Interface, podName // This is an alternative version of e2epod.WaitForPodNotFoundInNamespace(), which takes // a UID as well. -func waitForPodNotFoundInNamespace(ctx context.Context, c clientset.Interface, podName, ns string, uid types.UID, timeout time.Duration) error { +func waitForPodNotFoundInNamespace(ctx context.Context, c kubernetes.Interface, podName, ns string, uid types.UID, timeout time.Duration) error { err := framework.Gomega().Eventually(ctx, framework.HandleRetry(func(ctx context.Context) (*v1.Pod, error) { pod, err := c.CoreV1().Pods(ns).Get(ctx, podName, metav1.GetOptions{}) if apierrors.IsNotFound(err) { @@ -1434,3 +1501,38 @@ func getAgnHostHTTPPortBindFullCMD(port uint16) []string { func getAgnHostHTTPPortBindCMDArgs(port uint16) []string { return []string{"netexec", fmt.Sprintf("--http-port=%d", port)} } + +// executeFileTemplate executes `name` template from the provided `templates` +// using `data`as input and writes the results to `directory/name` +func executeFileTemplate(templates *template.Template, directory, name string, data any) error { + f, err := os.OpenFile(filepath.Join(directory, name), os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + return err + } + defer f.Close() + err = templates.ExecuteTemplate(f, name, data) + if err != nil { + return err + } + return nil +} + +// podIPsForUserDefinedPrimaryNetwork returns the v4 or v6 IPs for a pod on the UDN +func podIPOfFamilyOnPrimaryNetwork(k8sClient kubernetes.Interface, podNamespace string, podName string, networkName string, family utilnet.IPFamily) (string, error) { + pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return "", err + } + if networkName != "default" { + networkName = namespacedName(podNamespace, networkName) + } + netStatus, err := userDefinedNetworkStatus(pod, networkName) + if err != nil { + return "", err + } + ipnet := getFirstCIDROfFamily(family, netStatus.IPs) + if ipnet == nil { + return "", nil + } + return ipnet.IP.String(), nil +} From dfc14b4eb310d7c4b643408cb6bcb8ca9a634b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 15 Jul 2025 09:16:46 +0000 Subject: [PATCH 158/278] e2e: refactor podIPOfFamilyOnPrimaryNetwork into more reusable code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/egressip.go | 2 +- test/e2e/multihoming_utils.go | 36 +++++++++++++++++++++++ test/e2e/network_segmentation.go | 37 +++--------------------- test/e2e/network_segmentation_policy.go | 6 ++-- test/e2e/network_segmentation_utils.go | 22 ++++++++++++++ test/e2e/route_advertisements.go | 38 ++++++++++++------------- test/e2e/util.go | 20 ------------- 7 files changed, 85 insertions(+), 76 deletions(-) create mode 100644 test/e2e/network_segmentation_utils.go diff --git a/test/e2e/egressip.go b/test/e2e/egressip.go index 1bdc03adec..b2f75254f7 100644 --- a/test/e2e/egressip.go +++ b/test/e2e/egressip.go @@ -972,7 +972,7 @@ spec: if isClusterDefaultNetwork(netConfigParams) { pod2IP = getPodAddress(pod2Name, f.Namespace.Name) } else { - pod2IP, err = podIPsForUserDefinedPrimaryNetwork( + pod2IP, err = getPodAnnotationIPsForAttachmentByIndex( f.ClientSet, f.Namespace.Name, pod2Name, diff --git a/test/e2e/multihoming_utils.go b/test/e2e/multihoming_utils.go index 2fb10354d4..d7921cef00 100644 --- a/test/e2e/multihoming_utils.go +++ b/test/e2e/multihoming_utils.go @@ -704,3 +704,39 @@ func getNetworkGateway(cli *client.Client, networkName string) (string, error) { return "", fmt.Errorf("Gateway not found for network %q", networkName) } + +func getPodAnnotationForAttachment(pod *v1.Pod, attachmentName string) (PodAnnotation, error) { + podAnnotation, err := unmarshalPodAnnotation(pod.Annotations, attachmentName) + if err != nil { + return PodAnnotation{}, fmt.Errorf("failed to unmarshall annotations for pod %q: %v", pod.Name, err) + } + + return *podAnnotation, nil +} + +func getPodAnnotationIPsForAttachment(k8sClient clientset.Interface, podNamespace string, podName string, attachmentName string) ([]*net.IPNet, error) { + pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + podAnnotation, err := getPodAnnotationForAttachment(pod, attachmentName) + if err != nil { + return nil, err + } + return podAnnotation.IPs, nil +} + +// podIPsForNetworkByIndex returns the v4 or v6 IPs for a pod on the UDN +func getPodAnnotationIPsForAttachmentByIndex(k8sClient clientset.Interface, podNamespace string, podName string, attachmentName string, index int) (string, error) { + ipnets, err := getPodAnnotationIPsForAttachment(k8sClient, podNamespace, podName, attachmentName) + if err != nil { + return "", err + } + if index >= len(ipnets) { + return "", fmt.Errorf("no IP at index %d for attachment %s on pod %s", index, attachmentName, namespacedName(podNamespace, podName)) + } + if len(ipnets) > 2 { + return "", fmt.Errorf("attachment for network %q with more than two IPs", attachmentName) + } + return ipnets[index].IP.String(), nil +} diff --git a/test/e2e/network_segmentation.go b/test/e2e/network_segmentation.go index dec466f423..bb667b2d94 100644 --- a/test/e2e/network_segmentation.go +++ b/test/e2e/network_segmentation.go @@ -175,7 +175,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { for i, cidr := range strings.Split(netConfig.cidr, ",") { if cidr != "" { By("asserting the server pod has an IP from the configured range") - serverIP, err = podIPsForUserDefinedPrimaryNetwork( + serverIP, err = getPodAnnotationIPsForAttachmentByIndex( cs, f.Namespace.Name, serverPodConfig.name, @@ -610,7 +610,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { By("creating pod " + podConfig.name + " in " + podConfig.namespace) pod := runUDNPod(cs, podConfig.namespace, podConfig, nil) pods = append(pods, pod) - podIP, err := podIPsForUserDefinedPrimaryNetwork( + podIP, err := getPodAnnotationIPsForAttachmentByIndex( cs, pod.Namespace, pod.Name, @@ -792,7 +792,7 @@ var _ = Describe("Network Segmentation", feature.NetworkSegmentation, func() { By(fmt.Sprintf("asserting network works in namespace %s", config.namespace)) for i, cidr := range strings.Split(config.cidr, ",") { if cidr != "" { - serverIP, err = podIPsForUserDefinedPrimaryNetwork( + serverIP, err = getPodAnnotationIPsForAttachmentByIndex( cs, config.namespace, serverPodConfig.name, @@ -1756,7 +1756,7 @@ spec: clientPodConfig.nodeSelector = map[string]string{nodeHostnameKey: node2Name} runUDNPod(cs, f.Namespace.Name, serverPodConfig, nil) runUDNPod(cs, f.Namespace.Name, clientPodConfig, nil) - serverIP, err := podIPsForUserDefinedPrimaryNetwork(cs, f.Namespace.Name, serverPodConfig.name, namespacedName(f.Namespace.Name, netConfig.name), 0) + serverIP, err := getPodAnnotationIPsForAttachmentByIndex(cs, f.Namespace.Name, serverPodConfig.name, namespacedName(f.Namespace.Name, netConfig.name), 0) Expect(err).ShouldNot(HaveOccurred(), "UDN pod IP must be retrieved") By("restart OVNKube node pods on client and server Nodes and ensure connectivity") serverPod := getPod(f, serverPodConfig.name) @@ -2275,26 +2275,6 @@ func withNetworkAttachment(networks []nadapi.NetworkSelectionElement) podOption } } -// podIPsForUserDefinedPrimaryNetwork returns the v4 or v6 IPs for a pod on the UDN -func podIPsForUserDefinedPrimaryNetwork(k8sClient clientset.Interface, podNamespace string, podName string, attachmentName string, index int) (string, error) { - pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) - if err != nil { - return "", err - } - netStatus, err := userDefinedNetworkStatus(pod, attachmentName) - if err != nil { - return "", err - } - - if len(netStatus.IPs) == 0 { - return "", fmt.Errorf("attachment for network %q without IPs", attachmentName) - } - if len(netStatus.IPs) > 2 { - return "", fmt.Errorf("attachment for network %q with more than two IPs", attachmentName) - } - return netStatus.IPs[index].IP.String(), nil -} - func podIPsForDefaultNetwork(k8sClient clientset.Interface, podNamespace string, podName string) (string, string, error) { pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) if err != nil { @@ -2304,15 +2284,6 @@ func podIPsForDefaultNetwork(k8sClient clientset.Interface, podNamespace string, return ipv4, ipv6, nil } -func userDefinedNetworkStatus(pod *v1.Pod, networkName string) (PodAnnotation, error) { - netStatus, err := unmarshalPodAnnotation(pod.Annotations, networkName) - if err != nil { - return PodAnnotation{}, fmt.Errorf("failed to unmarshall annotations for pod %q: %v", pod.Name, err) - } - - return *netStatus, nil -} - func runUDNPod(cs clientset.Interface, namespace string, serverPodConfig podConfiguration, podSpecTweak func(*v1.Pod)) *v1.Pod { By(fmt.Sprintf("instantiating the UDN pod %s", serverPodConfig.name)) podSpec := generatePodSpec(serverPodConfig) diff --git a/test/e2e/network_segmentation_policy.go b/test/e2e/network_segmentation_policy.go index ffcf5f728a..2b71ebea5c 100644 --- a/test/e2e/network_segmentation_policy.go +++ b/test/e2e/network_segmentation_policy.go @@ -103,7 +103,7 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ for i, cidr := range strings.Split(netConfig.cidr, ",") { if cidr != "" { ginkgo.By("asserting the server pod has an IP from the configured range") - serverIP, err = podIPsForUserDefinedPrimaryNetwork( + serverIP, err = getPodAnnotationIPsForAttachmentByIndex( cs, f.Namespace.Name, serverPodConfig.name, @@ -231,12 +231,12 @@ var _ = ginkgo.Describe("Network Segmentation: Network Policies", feature.Networ } subnet, err := getNetCIDRSubnet(cidr) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - allowServerPodIP, err = podIPsForUserDefinedPrimaryNetwork(cs, namespaceYellow, allowServerPodConfig.name, + allowServerPodIP, err = getPodAnnotationIPsForAttachmentByIndex(cs, namespaceYellow, allowServerPodConfig.name, namespacedName(namespaceYellow, netConfName), i) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By(fmt.Sprintf("asserting the allow server pod IP %v is from the configured range %v", allowServerPodIP, cidr)) gomega.Expect(inRange(subnet, allowServerPodIP)).To(gomega.Succeed()) - denyServerPodIP, err = podIPsForUserDefinedPrimaryNetwork(cs, namespaceYellow, denyServerPodConfig.name, + denyServerPodIP, err = getPodAnnotationIPsForAttachmentByIndex(cs, namespaceYellow, denyServerPodConfig.name, namespacedName(namespaceYellow, netConfName), i) gomega.Expect(err).NotTo(gomega.HaveOccurred()) ginkgo.By(fmt.Sprintf("asserting the deny server pod IP %v is from the configured range %v", denyServerPodIP, cidr)) diff --git a/test/e2e/network_segmentation_utils.go b/test/e2e/network_segmentation_utils.go new file mode 100644 index 0000000000..960b6889c7 --- /dev/null +++ b/test/e2e/network_segmentation_utils.go @@ -0,0 +1,22 @@ +package e2e + +import ( + "k8s.io/client-go/kubernetes" + "k8s.io/utils/net" +) + +// podIPsForUserDefinedPrimaryNetwork returns the v4 or v6 IPs for a pod on the UDN +func getPodAnnotationIPsForPrimaryNetworkByIPFamily(k8sClient kubernetes.Interface, podNamespace string, podName string, networkName string, family net.IPFamily) (string, error) { + if networkName != "default" { + networkName = namespacedName(podNamespace, networkName) + } + ipnets, err := getPodAnnotationIPsForAttachment(k8sClient, podNamespace, podName, networkName) + if err != nil { + return "", err + } + ipnet := getFirstCIDROfFamily(family, ipnets) + if ipnet == nil { + return "", nil + } + return ipnet.IP.String(), nil +} diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 08e6f11965..3b868558b0 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -407,7 +407,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert ginkgo.By("queries to the external server are not SNATed (uses podIP)") for _, serverContainerIP := range serverContainerIPs { - podIP, err := podIPsForUserDefinedPrimaryNetwork(f.ClientSet, f.Namespace.Name, clientPod.Name, namespacedName(f.Namespace.Name, cUDN.Name), 0) + podIP, err := getPodAnnotationIPsForAttachmentByIndex(f.ClientSet, f.Namespace.Name, clientPod.Name, namespacedName(f.Namespace.Name, cUDN.Name), 0) gomega.Expect(err).NotTo(gomega.HaveOccurred()) framework.ExpectNoError(err, fmt.Sprintf("Getting podIPs for pod %s failed: %v", clientPod.Name, err)) framework.Logf("Client pod IP address=%s", podIP) @@ -426,7 +426,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert 60*time.Second) framework.ExpectNoError(err, fmt.Sprintf("Testing pod to external traffic failed: %v", err)) if isIPv6Supported(f.ClientSet) && utilnet.IsIPv6String(serverContainerIP) { - podIP, err = podIPsForUserDefinedPrimaryNetwork(f.ClientSet, f.Namespace.Name, clientPod.Name, namespacedName(f.Namespace.Name, cUDN.Name), 1) + podIP, err = getPodAnnotationIPsForAttachmentByIndex(f.ClientSet, f.Namespace.Name, clientPod.Name, namespacedName(f.Namespace.Name, cUDN.Name), 1) // For IPv6 addresses, need to handle the brackets in the output outputIP := strings.TrimPrefix(strings.Split(stdout, "]:")[0], "[") gomega.Expect(outputIP).To(gomega.Equal(podIP), @@ -838,9 +838,9 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podsNetA[0] srvPod := podsNetA[1] - clientPodStatus, err := userDefinedNetworkStatus(clientPod, namespacedName(clientPod.Namespace, cudnATemplate.Name)) + clientPodStatus, err := getPodAnnotationForAttachment(clientPod, namespacedName(clientPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", clientPodStatus.IPs[ipFamilyIndex].IP.String(), false }), @@ -850,9 +850,9 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podsNetA[0] srvPod := podsNetA[2] - clientPodStatus, err := userDefinedNetworkStatus(clientPod, namespacedName(clientPod.Namespace, cudnATemplate.Name)) + clientPodStatus, err := getPodAnnotationForAttachment(clientPod, namespacedName(clientPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", clientPodStatus.IPs[ipFamilyIndex].IP.String(), false }), @@ -862,7 +862,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podsNetA[2] srvPod := podNetB - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -873,7 +873,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podsNetA[0] srvPod := podNetB - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -883,7 +883,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podNetDefault srvPod := podNetB - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnBTemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -893,7 +893,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientPod := podNetDefault srvPod := podsNetA[0] - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) return clientPod.Name, clientPod.Namespace, net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -922,7 +922,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientNode := podsNetA[0].Spec.NodeName srvPod := podsNetA[0] - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) return clientNode, "", net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -932,7 +932,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" clientNode := podsNetA[2].Spec.NodeName srvPod := podsNetA[0] - srvPodStatus, err := userDefinedNetworkStatus(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) + srvPodStatus, err := getPodAnnotationForAttachment(srvPod, namespacedName(srvPod.Namespace, cudnATemplate.Name)) framework.ExpectNoError(err) return clientNode, "", net.JoinHostPort(srvPodStatus.IPs[ipFamilyIndex].IP.String(), "8080") + "/clientip", curlConnectionTimeoutCode, true }), @@ -1301,7 +1301,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { testPodToHostnameAndExpect(testPod, serverIP, bgpServerName) ginkgo.By("Ensuring a request from the pod is not SNATed") - testPodIP, err := podIPOfFamilyOnPrimaryNetwork( + testPodIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, testPod.Namespace, testPod.Name, @@ -1328,7 +1328,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) serverIP := getFirstIPStringOfFamily(family, []string{iface.IPv4, iface.IPv6}) gomega.Expect(serverIP).NotTo(gomega.BeEmpty()) - podIP, err := podIPOfFamilyOnPrimaryNetwork( + podIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, testPod.Namespace, testPod.Name, @@ -1381,7 +1381,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { e2eskipper.Skipf("IP family %v not supported", family) } ginkgo.By("Ensuring a request from the external server cannot reach the pod") - podIP, err := podIPOfFamilyOnPrimaryNetwork( + podIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, testPod.Namespace, testPod.Name, @@ -1405,7 +1405,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { e2eskipper.Skipf("IP family %v not supported", family) } ginkgo.By("Ensuring a request from the node cannot reach the tested network pod") - podIP, err := podIPOfFamilyOnPrimaryNetwork( + podIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, testPod.Namespace, testPod.Name, @@ -1448,7 +1448,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { e2eskipper.Skipf("IP family %v not supported", family) } ginkgo.By("Ensuring a request from the first pod can reach the second pod") - otherPodIP, err := podIPOfFamilyOnPrimaryNetwork( + otherPodIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, otherPod.Namespace, otherPod.Name, @@ -1597,7 +1597,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { e2eskipper.Skipf("IP family %v not supported", family) } ginkgo.By("Ensuring a request from the tested network pod cannot reach the other network pod") - otherPodIP, err := podIPOfFamilyOnPrimaryNetwork( + otherPodIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, otherPod.Namespace, otherPod.Name, @@ -1618,7 +1618,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { e2eskipper.Skipf("IP family %v not supported", family) } ginkgo.By("Ensuring a request from the other network pod cannot reach the tested network pod") - testPodIP, err := podIPOfFamilyOnPrimaryNetwork( + testPodIP, err := getPodAnnotationIPsForPrimaryNetworkByIPFamily( f.ClientSet, testPod.Namespace, testPod.Name, diff --git a/test/e2e/util.go b/test/e2e/util.go index 07bb4f9f76..d03559e79e 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1516,23 +1516,3 @@ func executeFileTemplate(templates *template.Template, directory, name string, d } return nil } - -// podIPsForUserDefinedPrimaryNetwork returns the v4 or v6 IPs for a pod on the UDN -func podIPOfFamilyOnPrimaryNetwork(k8sClient kubernetes.Interface, podNamespace string, podName string, networkName string, family utilnet.IPFamily) (string, error) { - pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{}) - if err != nil { - return "", err - } - if networkName != "default" { - networkName = namespacedName(podNamespace, networkName) - } - netStatus, err := userDefinedNetworkStatus(pod, networkName) - if err != nil { - return "", err - } - ipnet := getFirstCIDROfFamily(family, netStatus.IPs) - if ipnet == nil { - return "", nil - } - return ipnet.IP.String(), nil -} From e72e62b359f3566c9e1eb16087b7cba2e9f1bc07 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Tue, 22 Jul 2025 13:11:19 +0000 Subject: [PATCH 159/278] Remove unused portbinding code It's not used since e4f360cc82c4202175b8667398a4dcc7334aafd9 merged. Signed-off-by: Ihar Hrachyshka --- go-controller/pkg/libovsdb/ops/portbinding.go | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 go-controller/pkg/libovsdb/ops/portbinding.go diff --git a/go-controller/pkg/libovsdb/ops/portbinding.go b/go-controller/pkg/libovsdb/ops/portbinding.go deleted file mode 100644 index 0267a794c0..0000000000 --- a/go-controller/pkg/libovsdb/ops/portbinding.go +++ /dev/null @@ -1,53 +0,0 @@ -package ops - -import ( - "fmt" - - libovsdbclient "github.com/ovn-kubernetes/libovsdb/client" - - "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/sbdb" -) - -// UpdatePortBindingSetChassis sets the chassis column of the 'portBinding' row so that the OVN thinks that -// the port binding 'portBinding' is bound on the chassis. Ideally its ovn-controller which claims/binds -// a port binding. But for a remote chassis, we have to bind it as we created the remote chassis -// record for the remote zone nodes. -// TODO (numans) remove this function once OVN supports binding a port binding for a remote -// chassis. -func UpdatePortBindingSetChassis(sbClient libovsdbclient.Client, portBinding *sbdb.PortBinding, chassis *sbdb.Chassis) error { - ch, err := GetChassis(sbClient, chassis) - if err != nil { - return fmt.Errorf("failed to get chassis id %s(%s), error: %v", chassis.Name, chassis.Hostname, err) - } - portBinding.Chassis = &ch.UUID - - opModel := operationModel{ - Model: portBinding, - OnModelUpdates: []interface{}{&portBinding.Chassis}, - ErrNotFound: true, - BulkOp: false, - } - - m := newModelClient(sbClient) - _, err = m.CreateOrUpdate(opModel) - return err -} - -// GetPortBinding looks up a portBinding in SBDB -func GetPortBinding(sbClient libovsdbclient.Client, portBinding *sbdb.PortBinding) (*sbdb.PortBinding, error) { - found := []*sbdb.PortBinding{} - opModel := operationModel{ - Model: portBinding, - ExistingResult: &found, - ErrNotFound: true, - BulkOp: false, - } - - m := newModelClient(sbClient) - err := m.Lookup(opModel) - if err != nil { - return nil, err - } - - return found[0], nil -} From 34b5a46c8f66ffd74e12c3351282e5ce576370c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Fri, 18 Jul 2025 13:28:50 +0000 Subject: [PATCH 160/278] e2e: test against L2 networks in VRF-Lite test cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/route_advertisements.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 3b868558b0..59b3eb4a0b 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -1078,6 +1078,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { // long timeouts timeout = 240 * time.Second timeoutNOK = 10 * time.Second + pollingNOK = 1 * time.Second netexecPort = 8080 ) var netexecPortStr = fmt.Sprintf("%d", netexecPort) @@ -1132,7 +1133,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { ip, _, err = net.SplitHostPort(ip) g.Expect(err).NotTo(gomega.HaveOccurred()) g.Expect(ip).To(gomega.Equal(expect)) - }).WithTimeout(timeout).WithPolling(framework.Poll).Should(gomega.Succeed()) + }).WithTimeout(timeout).WithPolling(pollingNOK).Should(gomega.Succeed()) } testPodToClientIPNOK := func(src *corev1.Pod, dstIP string) { gomega.Consistently(func(g gomega.Gomega) { @@ -1142,7 +1143,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { fmt.Sprintf("curl --max-time 2 -g -q -s http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr)), ) g.Expect(err).To(gomega.HaveOccurred()) - }).WithTimeout(timeoutNOK).WithPolling(framework.Poll).Should(gomega.Succeed()) + }).WithTimeout(timeoutNOK).WithPolling(pollingNOK).Should(gomega.Succeed()) } testContainerToClientIPNOK := func(src, dstIP string) { gomega.Consistently(func(g gomega.Gomega) { @@ -1151,7 +1152,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { []string{"curl", "--max-time", "2", "-g", "-q", "-s", fmt.Sprintf("http://%s/clientip", net.JoinHostPort(dstIP, netexecPortStr))}, ) g.Expect(err).To(gomega.HaveOccurred()) - }).WithTimeout(timeoutNOK).WithPolling(framework.Poll).Should(gomega.Succeed()) + }).WithTimeout(timeoutNOK).WithPolling(pollingNOK).Should(gomega.Succeed()) } const ( @@ -1202,6 +1203,13 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { Subnets: []udnv1.Layer3Subnet{{CIDR: cudnCIDRv4, HostSubnet: 24}, {CIDR: cudnCIDRv6, HostSubnet: 64}}, }, } + layer2NetworkSpec = &udnv1.NetworkSpec{ + Topology: udnv1.NetworkTopologyLayer2, + Layer2: &udnv1.Layer2Config{ + Role: "Primary", + Subnets: udnv1.DualStackCIDRs{cudnCIDRv4, cudnCIDRv6}, + }, + } ) matchL3SubnetsByIPFamilies := func(families sets.Set[utilnet.IPFamily], in ...udnv1.Layer3Subnet) (out []udnv1.Layer3Subnet) { @@ -1223,6 +1231,7 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { networksToTest := []ginkgo.TableEntry{ ginkgo.Entry("Layer 3", layer3NetworkSpec), + ginkgo.Entry("Layer 2", layer2NetworkSpec), } ginkgo.DescribeTableSubtree("When the tested network is of type", @@ -1248,7 +1257,13 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { ginkgo.BeforeEach(func() { var err error - networkSpec.Layer3.Subnets = matchL3SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer3.Subnets...) + + switch { + case networkSpec.Layer3 != nil: + networkSpec.Layer3.Subnets = matchL3SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer3.Subnets...) + case networkSpec.Layer2 != nil: + networkSpec.Layer2.Subnets = matchL2SubnetsByIPFamilies(ipFamilySet, networkSpec.Layer2.Subnets...) + } ginkgo.By("Configuring the namespace and network") testNamespace, err = createNamespaceWithPrimaryNetworkOfType(f, ictx, testBaseName, testNetworkName, cudnAdvertisedVRFLite, networkSpec) @@ -1534,6 +1549,8 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { ginkgo.Entry("Layer 3 CUDN advertised", cudnAdvertised, otherLayer3NetworkSpec), ginkgo.Entry("Layer 3 CUDN advertised VRF-Lite", cudnAdvertisedVRFLite, otherLayer3NetworkSpec), ginkgo.Entry("Layer 2 UDN non advertised", udn, otherLayer2NetworkSpec), + ginkgo.Entry("Layer 2 CUDN advertised", cudnAdvertised, otherLayer2NetworkSpec), + ginkgo.Entry("Layer 2 CUDN advertised VRF-Lite", cudnAdvertisedVRFLite, otherLayer2NetworkSpec), } ginkgo.DescribeTableSubtree("Of type", From d127877f72b68a6ffd0945cf4f2e38925096ab47 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Thu, 12 Jun 2025 18:42:04 +0200 Subject: [PATCH 161/278] build, vendor: consume ipamclaims v0.5.0-alpha This version of IPAMClaims features a conditions array in the status sub-resouce (where IPAM CNIs can report errors), along with a place to register the name of the pod which holds the claim (for better traceability). Signed-off-by: Miguel Duarte Barroso --- go-controller/go.mod | 8 +- go-controller/go.sum | 16 +- .../apis/clientset/versioned/clientset.go | 6 +- .../versioned/fake/clientset_generated.go | 8 +- .../apis/clientset/versioned/fake/doc.go | 2 +- .../apis/clientset/versioned/fake/register.go | 2 +- .../apis/clientset/versioned/scheme/doc.go | 2 +- .../clientset/versioned/scheme/register.go | 2 +- .../typed/ipamclaims/v1alpha1/doc.go | 2 +- .../typed/ipamclaims/v1alpha1/fake/doc.go | 2 +- .../v1alpha1/fake/fake_ipamclaim.go | 135 +++----------- .../v1alpha1/fake/fake_ipamclaims_client.go | 4 +- .../v1alpha1/generated_expansion.go | 2 +- .../typed/ipamclaims/v1alpha1/ipamclaim.go | 165 +++--------------- .../ipamclaims/v1alpha1/ipamclaims_client.go | 12 +- .../informers/externalversions/factory.go | 13 +- .../informers/externalversions/generic.go | 4 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../externalversions/ipamclaims/interface.go | 2 +- .../ipamclaims/v1alpha1/interface.go | 2 +- .../ipamclaims/v1alpha1/ipamclaim.go | 18 +- .../v1alpha1/expansion_generated.go | 2 +- .../listers/ipamclaims/v1alpha1/ipamclaim.go | 53 ++---- .../pkg/crd/ipamclaims/v1alpha1/types.go | 17 +- .../v1alpha1/zz_generated.deepcopy.go | 24 +++ go-controller/vendor/modules.txt | 10 +- test/e2e/go.mod | 8 +- test/e2e/go.sum | 16 +- 28 files changed, 171 insertions(+), 368 deletions(-) diff --git a/go-controller/go.mod b/go-controller/go.mod index 4b12ddd9b5..72e89c3b7a 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -23,7 +23,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47 - github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha + github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc @@ -57,9 +57,9 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 gopkg.in/gcfg.v1 v1.2.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 + k8s.io/api v0.32.5 + k8s.io/apimachinery v0.32.5 + k8s.io/client-go v0.32.5 k8s.io/component-helpers v0.32.3 k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v1.32.6 diff --git a/go-controller/go.sum b/go-controller/go.sum index 436b9bad43..2af1883f7e 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -494,8 +494,8 @@ github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CIm github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47 h1:iSncnlC+rtlNOIpPa3fbqQMhpTscGJIlkiWaPl1VcS4= github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47/go.mod h1:SPaDIyUmwN03Bgn0u/mhoiE4o/+koeKh11VUsdsUX0U= -github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha h1:ss+EP77GlQmh90hGKpnAG4Q3VVxRlB7GoncemaPtO4g= -github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha/go.mod h1:qlR+sKxQ2OGfwhFCuXSd7rJ/GgC38vQBeHKQ7f2YnpI= +github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha h1:b3iHeks/KTzhG2dNanaUZcFEJwJbYBZY16jxCaVv9i8= +github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha/go.mod h1:MGaMX1tJ7MlHDee4/xmqp3guQh+eDiuCLAauqD9K11Q= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 h1:Egj1hEVYNXWFlKpgzAXxe/2o8VNiVcAJLrKzlinILQo= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1/go.mod h1:kEJ4WM849yNmXekuSXLRwb+LaZ9usC06O8JgoAIq+f4= github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 h1:BT3ghAY0q7lWib9rz+tVXDFkm27dJV6SLCn7TunZwo4= @@ -1317,8 +1317,8 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.7/go.mod h1:7hejA1BgBEiSsWljUyRkIjj+AISXO16IwsaDgFjJsQE= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/api v0.32.5 h1:uqjjsYo1kTJr5NIcoIaP9F+TgXgADH7nKQx91FDAhtk= +k8s.io/api v0.32.5/go.mod h1:bXXFU3fGCZ/eFMZvfHZC69PeGbXEL4zzjuPVzOxHF64= k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= @@ -1326,8 +1326,8 @@ k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.7/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.5 h1:6We3aJ6crC0ap8EhsEXcgX3LpI6SEjubpiOMXLROwPM= +k8s.io/apimachinery v0.32.5/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= @@ -1335,8 +1335,8 @@ k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.7/go.mod h1:pGU/tWSzzvsYT7M3npHhoZ3Jh9qJTTIvFvDtWuW31dw= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/client-go v0.32.5 h1:huFmQMzgWu0z4kbWsuZci+Gt4Fo72I4CcrvhToZ/Qp0= +k8s.io/client-go v0.32.5/go.mod h1:Qchw6f9WIVrur7DKojAHpRgGLcANT0RLIvF39Jz58xA= k8s.io/code-generator v0.22.7/go.mod h1:iOZwYADSgFPNGWfqHFfg1V0TNJnl1t0WyZluQp4baqU= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/clientset.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/clientset.go index f374a5c511..6f4518f097 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/clientset.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ limitations under the License. package versioned import ( - "fmt" - "net/http" + fmt "fmt" + http "net/http" k8sv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1" discovery "k8s.io/client-go/discovery" diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/clientset_generated.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/clientset_generated.go index a67d14acb8..eb8da4c265 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/clientset_generated.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,8 +31,12 @@ import ( // NewSimpleClientset returns a clientset that will respond with the provided objects. // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) for _, obj := range objects { diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/doc.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/doc.go index 44e8061b76..64c6b6be35 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/doc.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/register.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/register.go index 3cdc1ac5b1..e6f64d71b9 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/register.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/doc.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/doc.go index 743391c14b..8514bb55f2 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/doc.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/register.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/register.go index d6a1737fdb..522a30ca3e 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/register.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/doc.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/doc.go index faa8377ce2..19ad6aefe7 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/doc.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/doc.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/doc.go index b38fd4c55d..33fd99c15d 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/doc.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaim.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaim.go index 00db990cf9..e410e0b7e3 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaim.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,123 +19,32 @@ limitations under the License. package fake import ( - "context" - v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1" + gentype "k8s.io/client-go/gentype" ) -// FakeIPAMClaims implements IPAMClaimInterface -type FakeIPAMClaims struct { +// fakeIPAMClaims implements IPAMClaimInterface +type fakeIPAMClaims struct { + *gentype.FakeClientWithList[*v1alpha1.IPAMClaim, *v1alpha1.IPAMClaimList] Fake *FakeK8sV1alpha1 - ns string -} - -var ipamclaimsResource = v1alpha1.SchemeGroupVersion.WithResource("ipamclaims") - -var ipamclaimsKind = v1alpha1.SchemeGroupVersion.WithKind("IPAMClaim") - -// Get takes name of the iPAMClaim, and returns the corresponding iPAMClaim object, and an error if there is any. -func (c *FakeIPAMClaims) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPAMClaim, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(ipamclaimsResource, c.ns, name), &v1alpha1.IPAMClaim{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.IPAMClaim), err -} - -// List takes label and field selectors, and returns the list of IPAMClaims that match those selectors. -func (c *FakeIPAMClaims) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPAMClaimList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(ipamclaimsResource, ipamclaimsKind, c.ns, opts), &v1alpha1.IPAMClaimList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha1.IPAMClaimList{ListMeta: obj.(*v1alpha1.IPAMClaimList).ListMeta} - for _, item := range obj.(*v1alpha1.IPAMClaimList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested iPAMClaims. -func (c *FakeIPAMClaims) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(ipamclaimsResource, c.ns, opts)) - -} - -// Create takes the representation of a iPAMClaim and creates it. Returns the server's representation of the iPAMClaim, and an error, if there is any. -func (c *FakeIPAMClaims) Create(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.CreateOptions) (result *v1alpha1.IPAMClaim, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(ipamclaimsResource, c.ns, iPAMClaim), &v1alpha1.IPAMClaim{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.IPAMClaim), err -} - -// Update takes the representation of a iPAMClaim and updates it. Returns the server's representation of the iPAMClaim, and an error, if there is any. -func (c *FakeIPAMClaims) Update(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (result *v1alpha1.IPAMClaim, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(ipamclaimsResource, c.ns, iPAMClaim), &v1alpha1.IPAMClaim{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.IPAMClaim), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeIPAMClaims) UpdateStatus(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (*v1alpha1.IPAMClaim, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(ipamclaimsResource, "status", c.ns, iPAMClaim), &v1alpha1.IPAMClaim{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.IPAMClaim), err -} - -// Delete takes name of the iPAMClaim and deletes it. Returns an error if one occurs. -func (c *FakeIPAMClaims) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(ipamclaimsResource, c.ns, name, opts), &v1alpha1.IPAMClaim{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeIPAMClaims) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(ipamclaimsResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &v1alpha1.IPAMClaimList{}) - return err -} - -// Patch applies the patch and returns the patched iPAMClaim. -func (c *FakeIPAMClaims) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAMClaim, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(ipamclaimsResource, c.ns, name, pt, data, subresources...), &v1alpha1.IPAMClaim{}) - - if obj == nil { - return nil, err +func newFakeIPAMClaims(fake *FakeK8sV1alpha1, namespace string) ipamclaimsv1alpha1.IPAMClaimInterface { + return &fakeIPAMClaims{ + gentype.NewFakeClientWithList[*v1alpha1.IPAMClaim, *v1alpha1.IPAMClaimList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("ipamclaims"), + v1alpha1.SchemeGroupVersion.WithKind("IPAMClaim"), + func() *v1alpha1.IPAMClaim { return &v1alpha1.IPAMClaim{} }, + func() *v1alpha1.IPAMClaimList { return &v1alpha1.IPAMClaimList{} }, + func(dst, src *v1alpha1.IPAMClaimList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.IPAMClaimList) []*v1alpha1.IPAMClaim { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.IPAMClaimList, items []*v1alpha1.IPAMClaim) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*v1alpha1.IPAMClaim), err } diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaims_client.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaims_client.go index adc0c545ed..65c4b4c979 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaims_client.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/fake/fake_ipamclaims_client.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ type FakeK8sV1alpha1 struct { } func (c *FakeK8sV1alpha1) IPAMClaims(namespace string) v1alpha1.IPAMClaimInterface { - return &FakeIPAMClaims{c, namespace} + return newFakeIPAMClaims(c, namespace) } // RESTClient returns a RESTClient that is used to communicate diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/generated_expansion.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/generated_expansion.go index c5c3006e82..b70abd3102 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/generated_expansion.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaim.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaim.go index bfc26c0c5a..f4d088c1b9 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaim.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,15 +19,14 @@ limitations under the License. package v1alpha1 import ( - "context" - "time" + context "context" - v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" + ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" scheme "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // IPAMClaimsGetter has a method to return a IPAMClaimInterface. @@ -38,158 +37,34 @@ type IPAMClaimsGetter interface { // IPAMClaimInterface has methods to work with IPAMClaim resources. type IPAMClaimInterface interface { - Create(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.CreateOptions) (*v1alpha1.IPAMClaim, error) - Update(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (*v1alpha1.IPAMClaim, error) - UpdateStatus(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (*v1alpha1.IPAMClaim, error) + Create(ctx context.Context, iPAMClaim *ipamclaimsv1alpha1.IPAMClaim, opts v1.CreateOptions) (*ipamclaimsv1alpha1.IPAMClaim, error) + Update(ctx context.Context, iPAMClaim *ipamclaimsv1alpha1.IPAMClaim, opts v1.UpdateOptions) (*ipamclaimsv1alpha1.IPAMClaim, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, iPAMClaim *ipamclaimsv1alpha1.IPAMClaim, opts v1.UpdateOptions) (*ipamclaimsv1alpha1.IPAMClaim, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.IPAMClaim, error) - List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.IPAMClaimList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*ipamclaimsv1alpha1.IPAMClaim, error) + List(ctx context.Context, opts v1.ListOptions) (*ipamclaimsv1alpha1.IPAMClaimList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAMClaim, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *ipamclaimsv1alpha1.IPAMClaim, err error) IPAMClaimExpansion } // iPAMClaims implements IPAMClaimInterface type iPAMClaims struct { - client rest.Interface - ns string + *gentype.ClientWithList[*ipamclaimsv1alpha1.IPAMClaim, *ipamclaimsv1alpha1.IPAMClaimList] } // newIPAMClaims returns a IPAMClaims func newIPAMClaims(c *K8sV1alpha1Client, namespace string) *iPAMClaims { return &iPAMClaims{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithList[*ipamclaimsv1alpha1.IPAMClaim, *ipamclaimsv1alpha1.IPAMClaimList]( + "ipamclaims", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *ipamclaimsv1alpha1.IPAMClaim { return &ipamclaimsv1alpha1.IPAMClaim{} }, + func() *ipamclaimsv1alpha1.IPAMClaimList { return &ipamclaimsv1alpha1.IPAMClaimList{} }, + ), } } - -// Get takes name of the iPAMClaim, and returns the corresponding iPAMClaim object, and an error if there is any. -func (c *iPAMClaims) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPAMClaim, err error) { - result = &v1alpha1.IPAMClaim{} - err = c.client.Get(). - Namespace(c.ns). - Resource("ipamclaims"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of IPAMClaims that match those selectors. -func (c *iPAMClaims) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPAMClaimList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1alpha1.IPAMClaimList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("ipamclaims"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested iPAMClaims. -func (c *iPAMClaims) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("ipamclaims"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a iPAMClaim and creates it. Returns the server's representation of the iPAMClaim, and an error, if there is any. -func (c *iPAMClaims) Create(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.CreateOptions) (result *v1alpha1.IPAMClaim, err error) { - result = &v1alpha1.IPAMClaim{} - err = c.client.Post(). - Namespace(c.ns). - Resource("ipamclaims"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(iPAMClaim). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a iPAMClaim and updates it. Returns the server's representation of the iPAMClaim, and an error, if there is any. -func (c *iPAMClaims) Update(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (result *v1alpha1.IPAMClaim, err error) { - result = &v1alpha1.IPAMClaim{} - err = c.client.Put(). - Namespace(c.ns). - Resource("ipamclaims"). - Name(iPAMClaim.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(iPAMClaim). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *iPAMClaims) UpdateStatus(ctx context.Context, iPAMClaim *v1alpha1.IPAMClaim, opts v1.UpdateOptions) (result *v1alpha1.IPAMClaim, err error) { - result = &v1alpha1.IPAMClaim{} - err = c.client.Put(). - Namespace(c.ns). - Resource("ipamclaims"). - Name(iPAMClaim.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(iPAMClaim). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the iPAMClaim and deletes it. Returns an error if one occurs. -func (c *iPAMClaims) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("ipamclaims"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *iPAMClaims) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("ipamclaims"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched iPAMClaim. -func (c *iPAMClaims) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAMClaim, err error) { - result = &v1alpha1.IPAMClaim{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("ipamclaims"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaims_client.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaims_client.go index d6b8684d89..3545777356 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaims_client.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/typed/ipamclaims/v1alpha1/ipamclaims_client.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ limitations under the License. package v1alpha1 import ( - "net/http" + http "net/http" - v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" - "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme" + ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" + scheme "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -85,10 +85,10 @@ func New(c rest.Interface) *K8sV1alpha1Client { } func setConfigDefaults(config *rest.Config) error { - gv := v1alpha1.SchemeGroupVersion + gv := ipamclaimsv1alpha1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/factory.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/factory.go index 8ba00a69fc..7efe7e95a6 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/factory.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ type sharedInformerFactory struct { lock sync.Mutex defaultResync time.Duration customResync map[reflect.Type]time.Duration + transform cache.TransformFunc informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -80,6 +81,14 @@ func WithNamespace(namespace string) SharedInformerOption { } } +// WithTransform sets a transform on all informers. +func WithTransform(transform cache.TransformFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.transform = transform + return factory + } +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -184,6 +193,7 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) + informer.SetTransform(f.transform) f.informers[informerType] = informer return informer @@ -218,6 +228,7 @@ type SharedInformerFactory interface { // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. Start(stopCh <-chan struct{}) // Shutdown marks a factory as shutting down. At that point no new diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/generic.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/generic.go index 94f709e9bb..d5dabd6983 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/generic.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ limitations under the License. package externalversions import ( - "fmt" + fmt "fmt" v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go index 8d1429d5f3..cb5a445987 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/interface.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/interface.go index c93d99e4be..b2cad1c067 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/interface.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/interface.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/interface.go index 1ab51a9ed7..455310ee4d 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/interface.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/ipamclaim.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/ipamclaim.go index fd46dc78b7..8caa586ce5 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/ipamclaim.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/ipamclaims/v1alpha1/ipamclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,13 +19,13 @@ limitations under the License. package v1alpha1 import ( - "context" + context "context" time "time" - ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" + crdipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" versioned "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned" internalinterfaces "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers/externalversions/internalinterfaces" - v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1" + ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // IPAMClaims. type IPAMClaimInformer interface { Informer() cache.SharedIndexInformer - Lister() v1alpha1.IPAMClaimLister + Lister() ipamclaimsv1alpha1.IPAMClaimLister } type iPAMClaimInformer struct { @@ -71,7 +71,7 @@ func NewFilteredIPAMClaimInformer(client versioned.Interface, namespace string, return client.K8sV1alpha1().IPAMClaims(namespace).Watch(context.TODO(), options) }, }, - &ipamclaimsv1alpha1.IPAMClaim{}, + &crdipamclaimsv1alpha1.IPAMClaim{}, resyncPeriod, indexers, ) @@ -82,9 +82,9 @@ func (f *iPAMClaimInformer) defaultInformer(client versioned.Interface, resyncPe } func (f *iPAMClaimInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&ipamclaimsv1alpha1.IPAMClaim{}, f.defaultInformer) + return f.factory.InformerFor(&crdipamclaimsv1alpha1.IPAMClaim{}, f.defaultInformer) } -func (f *iPAMClaimInformer) Lister() v1alpha1.IPAMClaimLister { - return v1alpha1.NewIPAMClaimLister(f.Informer().GetIndexer()) +func (f *iPAMClaimInformer) Lister() ipamclaimsv1alpha1.IPAMClaimLister { + return ipamclaimsv1alpha1.NewIPAMClaimLister(f.Informer().GetIndexer()) } diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/expansion_generated.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/expansion_generated.go index 086ab4ab65..bb37e41381 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/expansion_generated.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/ipamclaim.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/ipamclaim.go index 409fc70d06..474e11b48e 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/ipamclaim.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers/ipamclaims/v1alpha1/ipamclaim.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Kubernetes Authors +Copyright 2025 The Kubernetes Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + ipamclaimsv1alpha1 "github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // IPAMClaimLister helps list IPAMClaims. @@ -30,7 +30,7 @@ import ( type IPAMClaimLister interface { // List lists all IPAMClaims in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1alpha1.IPAMClaim, err error) + List(selector labels.Selector) (ret []*ipamclaimsv1alpha1.IPAMClaim, err error) // IPAMClaims returns an object that can list and get IPAMClaims. IPAMClaims(namespace string) IPAMClaimNamespaceLister IPAMClaimListerExpansion @@ -38,25 +38,17 @@ type IPAMClaimLister interface { // iPAMClaimLister implements the IPAMClaimLister interface. type iPAMClaimLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*ipamclaimsv1alpha1.IPAMClaim] } // NewIPAMClaimLister returns a new IPAMClaimLister. func NewIPAMClaimLister(indexer cache.Indexer) IPAMClaimLister { - return &iPAMClaimLister{indexer: indexer} -} - -// List lists all IPAMClaims in the indexer. -func (s *iPAMClaimLister) List(selector labels.Selector) (ret []*v1alpha1.IPAMClaim, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.IPAMClaim)) - }) - return ret, err + return &iPAMClaimLister{listers.New[*ipamclaimsv1alpha1.IPAMClaim](indexer, ipamclaimsv1alpha1.Resource("ipamclaim"))} } // IPAMClaims returns an object that can list and get IPAMClaims. func (s *iPAMClaimLister) IPAMClaims(namespace string) IPAMClaimNamespaceLister { - return iPAMClaimNamespaceLister{indexer: s.indexer, namespace: namespace} + return iPAMClaimNamespaceLister{listers.NewNamespaced[*ipamclaimsv1alpha1.IPAMClaim](s.ResourceIndexer, namespace)} } // IPAMClaimNamespaceLister helps list and get IPAMClaims. @@ -64,36 +56,15 @@ func (s *iPAMClaimLister) IPAMClaims(namespace string) IPAMClaimNamespaceLister type IPAMClaimNamespaceLister interface { // List lists all IPAMClaims in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1alpha1.IPAMClaim, err error) + List(selector labels.Selector) (ret []*ipamclaimsv1alpha1.IPAMClaim, err error) // Get retrieves the IPAMClaim from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1alpha1.IPAMClaim, error) + Get(name string) (*ipamclaimsv1alpha1.IPAMClaim, error) IPAMClaimNamespaceListerExpansion } // iPAMClaimNamespaceLister implements the IPAMClaimNamespaceLister // interface. type iPAMClaimNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all IPAMClaims in the indexer for a given namespace. -func (s iPAMClaimNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.IPAMClaim, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.IPAMClaim)) - }) - return ret, err -} - -// Get retrieves the IPAMClaim from the indexer for a given namespace and name. -func (s iPAMClaimNamespaceLister) Get(name string) (*v1alpha1.IPAMClaim, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1alpha1.Resource("ipamclaim"), name) - } - return obj.(*v1alpha1.IPAMClaim), nil + listers.ResourceIndexer[*ipamclaimsv1alpha1.IPAMClaim] } diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/types.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/types.go index ca94219215..bb4fc0e97d 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/types.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/types.go @@ -4,13 +4,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0 paths=./... object crd output:artifacts:code=./,config=../../../../artifacts +//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.5 paths=./... object crd output:artifacts:code=./,config=../../../../artifacts -//go:generate go run k8s.io/code-generator/cmd/client-gen@v0.28.0 client-gen --go-header-file ../../../../hack/custom-boilerplate.go.txt --clientset-name versioned --input-base "" --input github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1 --output-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset .. +//go:generate go run k8s.io/code-generator/cmd/client-gen@v0.32.5 --go-header-file ../../../../hack/custom-boilerplate.go.txt --clientset-name versioned --input-base "" --input github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1 --output-pkg github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset --output-dir ./apis/clientset .. -//go:generate go run k8s.io/code-generator/cmd/lister-gen@v0.28.0 lister-gen --go-header-file ../../../../hack/custom-boilerplate.go.txt --input-dirs github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1 --output-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers .. +//go:generate go run k8s.io/code-generator/cmd/lister-gen@v0.32.5 --go-header-file ../../../../hack/custom-boilerplate.go.txt --output-pkg github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers --output-dir ./apis/listers ./ -//go:generate go run k8s.io/code-generator/cmd/informer-gen@v0.28.0 informer-gen --go-header-file ../../../../hack/custom-boilerplate.go.txt --input-dirs github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1 --versioned-clientset-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned --listers-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers --output-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers .. +//go:generate go run k8s.io/code-generator/cmd/informer-gen@v0.32.5 --go-header-file ../../../../hack/custom-boilerplate.go.txt --versioned-clientset-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned --listers-package github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/listers --output-pkg github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/informers --output-dir ./apis/informers ./ // +genclient // +kubebuilder:object:root=true @@ -35,9 +35,14 @@ type IPAMClaimSpec struct { Interface string `json:"interface"` } +// IPAMClaimStatus contains the observed status of the IPAMClaim. type IPAMClaimStatus struct { // The list of IP addresses (v4, v6) that were allocated for the pod interface IPs []string `json:"ips"` + // The name of the pod holding the IPAMClaim + OwnerPod OwnerPod `json:"ownerPod,omitempty"` + // Conditions contains details for one aspect of the current state of this API Resource + Conditions []metav1.Condition `json:"conditions,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -47,3 +52,7 @@ type IPAMClaimList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []IPAMClaim `json:"items"` } + +type OwnerPod struct { + Name string `json:"name"` +} diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/zz_generated.deepcopy.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/zz_generated.deepcopy.go index 737efd7a84..d68e38c3ee 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/zz_generated.deepcopy.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/zz_generated.deepcopy.go @@ -5,6 +5,7 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -90,6 +91,14 @@ func (in *IPAMClaimStatus) DeepCopyInto(out *IPAMClaimStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + out.OwnerPod = in.OwnerPod + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMClaimStatus. @@ -101,3 +110,18 @@ func (in *IPAMClaimStatus) DeepCopy() *IPAMClaimStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerPod) DeepCopyInto(out *OwnerPod) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerPod. +func (in *OwnerPod) DeepCopy() *OwnerPod { + if in == nil { + return nil + } + out := new(OwnerPod) + in.DeepCopyInto(out) + return out +} diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index 8117a157c9..7636490960 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -197,8 +197,8 @@ github.com/juju/errors # github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47 ## explicit; go 1.17 github.com/k8snetworkplumbingwg/govdpa/pkg/kvdpa -# github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha -## explicit; go 1.20 +# github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha +## explicit; go 1.23.0 github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1 github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned github.com/k8snetworkplumbingwg/ipamclaims/pkg/crd/ipamclaims/v1alpha1/apis/clientset/versioned/fake @@ -677,7 +677,7 @@ gopkg.in/warnings.v0 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# k8s.io/api v0.32.3 +# k8s.io/api v0.32.5 ## explicit; go 1.23.0 k8s.io/api/admission/v1 k8s.io/api/admission/v1beta1 @@ -742,7 +742,7 @@ k8s.io/api/storagemigration/v1alpha1 ## explicit; go 1.23.0 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 -# k8s.io/apimachinery v0.32.3 +# k8s.io/apimachinery v0.32.5 ## explicit; go 1.23.0 k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/pkg/api/errors @@ -807,7 +807,7 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/client-go v0.32.3 +# k8s.io/client-go v0.32.5 ## explicit; go 1.23.0 k8s.io/client-go/applyconfigurations k8s.io/client-go/applyconfigurations/admissionregistration/v1 diff --git a/test/e2e/go.mod b/test/e2e/go.mod index d87e790619..d9d67fb0c4 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -6,16 +6,16 @@ toolchain go1.23.6 require ( github.com/google/go-cmp v0.6.0 - github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha + github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/pkg/errors v0.9.1 golang.org/x/sync v0.12.0 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 + k8s.io/api v0.32.5 + k8s.io/apimachinery v0.32.5 + k8s.io/client-go v0.32.5 k8s.io/klog v1.0.0 k8s.io/kubernetes v1.32.6 k8s.io/pod-security-admission v0.32.3 diff --git a/test/e2e/go.sum b/test/e2e/go.sum index d8a6c5c80c..900d7aa612 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -331,8 +331,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47 h1:iSncnlC+rtlNOIpPa3fbqQMhpTscGJIlkiWaPl1VcS4= github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47/go.mod h1:SPaDIyUmwN03Bgn0u/mhoiE4o/+koeKh11VUsdsUX0U= -github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha h1:ss+EP77GlQmh90hGKpnAG4Q3VVxRlB7GoncemaPtO4g= -github.com/k8snetworkplumbingwg/ipamclaims v0.4.0-alpha/go.mod h1:qlR+sKxQ2OGfwhFCuXSd7rJ/GgC38vQBeHKQ7f2YnpI= +github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha h1:b3iHeks/KTzhG2dNanaUZcFEJwJbYBZY16jxCaVv9i8= +github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha/go.mod h1:MGaMX1tJ7MlHDee4/xmqp3guQh+eDiuCLAauqD9K11Q= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 h1:Egj1hEVYNXWFlKpgzAXxe/2o8VNiVcAJLrKzlinILQo= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1/go.mod h1:kEJ4WM849yNmXekuSXLRwb+LaZ9usC06O8JgoAIq+f4= github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 h1:BT3ghAY0q7lWib9rz+tVXDFkm27dJV6SLCn7TunZwo4= @@ -986,19 +986,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.22.7/go.mod h1:7hejA1BgBEiSsWljUyRkIjj+AISXO16IwsaDgFjJsQE= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/api v0.32.5 h1:uqjjsYo1kTJr5NIcoIaP9F+TgXgADH7nKQx91FDAhtk= +k8s.io/api v0.32.5/go.mod h1:bXXFU3fGCZ/eFMZvfHZC69PeGbXEL4zzjuPVzOxHF64= k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= k8s.io/apimachinery v0.22.7/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.5 h1:6We3aJ6crC0ap8EhsEXcgX3LpI6SEjubpiOMXLROwPM= +k8s.io/apimachinery v0.32.5/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= k8s.io/client-go v0.22.7/go.mod h1:pGU/tWSzzvsYT7M3npHhoZ3Jh9qJTTIvFvDtWuW31dw= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/client-go v0.32.5 h1:huFmQMzgWu0z4kbWsuZci+Gt4Fo72I4CcrvhToZ/Qp0= +k8s.io/client-go v0.32.5/go.mod h1:Qchw6f9WIVrur7DKojAHpRgGLcANT0RLIvF39Jz58xA= k8s.io/cloud-provider v0.32.3 h1:WC7KhWrqXsU4b0E4tjS+nBectGiJbr1wuc1TpWXvtZM= k8s.io/cloud-provider v0.32.3/go.mod h1:/fwBfgRPuh16n8vLHT+PPT+Bc4LAEaJYj38opO2wsYY= k8s.io/code-generator v0.22.7/go.mod h1:iOZwYADSgFPNGWfqHFfg1V0TNJnl1t0WyZluQp4baqU= From 5b5bc069fd729ef6921b01ac25e26220f052b9bd Mon Sep 17 00:00:00 2001 From: Alin Serdean Date: Fri, 18 Jul 2025 18:16:22 +0200 Subject: [PATCH 162/278] gateway: Refactor gateway initialization and DPU host handling - Improve DPU host gateway configuration with better IP handling - Fix node interface address handling for DPU mode - Clean up gateway interface selection logic for DPU and DPU-HOST - Update node annotations for DPU host mode - Remove redundant code and improve error handling - Fix typos and improve code readability This change simplifies the gateway initialization flow and improves the handling of DPU (Data Processing Unit) host configurations. The main idea is that the DPU in host mode is the one that dictates how the network looks and the DPU side just follows. Signed-off-by: Alin Serdean --- .../pkg/node/bridgeconfig/bridgeconfig.go | 9 +- .../node/default_node_network_controller.go | 8 +- go-controller/pkg/node/gateway_init.go | 97 +++++++++++++++---- .../pkg/node/gateway_init_linux_test.go | 8 +- .../pkg/node/node_ip_handler_linux.go | 32 ++---- .../pkg/ovn/controller/services/lb_config.go | 10 +- .../pkg/ovn/default_network_controller.go | 10 +- go-controller/pkg/util/node_annotations.go | 39 ++++++++ 8 files changed, 161 insertions(+), 52 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go index 4cad9037ad..4031ff3cc8 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeconfig.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeconfig.go @@ -244,12 +244,15 @@ func (b *BridgeConfiguration) UpdateInterfaceIPAddresses(node *corev1.Node) ([]* // For DPU, here we need to use the DPU host's IP address which is the tenant cluster's // host internal IP address instead of the DPU's external bridge IP address. if config.OvnKubeNode.Mode == types.NodeModeDPU { - nodeAddrStr, err := util.GetNodePrimaryIP(node) + nodeIfAddr, err := util.GetNodePrimaryDPUHostAddrAnnotation(node) if err != nil { return nil, err } - nodeAddr := net.ParseIP(nodeAddrStr) - if nodeAddr == nil { + // For DPU mode, we only support IPv4 for now. + nodeAddrStr := nodeIfAddr.IPv4 + + nodeAddr, _, err := net.ParseCIDR(nodeAddrStr) + if err != nil { return nil, fmt.Errorf("failed to parse node IP address. %v", nodeAddrStr) } ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(nodeAddr, ifAddrs) diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index a2bdf34e50..f1281980a8 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -965,8 +965,12 @@ func (nc *DefaultNodeNetworkController) Init(ctx context.Context) error { // First part of gateway initialization. It will be completed by (nc *DefaultNodeNetworkController) Start() if config.OvnKubeNode.Mode != types.NodeModeDPUHost { + // IPv6 is not supported in DPU enabled nodes, error out if ovnkube is not set in IPv4 mode + if config.IPv6Mode && config.OvnKubeNode.Mode == types.NodeModeDPU { + return fmt.Errorf("IPv6 mode is not supported on a DPU enabled node") + } // Initialize gateway for OVS internal port or representor management port - gw, err := nc.initGatewayPreStart(subnets, nodeAnnotator, nc.mgmtPortController, nodeAddr) + gw, err := nc.initGatewayPreStart(subnets, nodeAnnotator, nc.mgmtPortController) if err != nil { return err } @@ -1059,7 +1063,7 @@ func (nc *DefaultNodeNetworkController) Start(ctx context.Context) error { netdevName = netdevs[0] config.Gateway.Interface = netdevName } - err = nc.initGatewayDPUHost(nc.nodeAddress) + err = nc.initGatewayDPUHost(nc.nodeAddress, nodeAnnotator) if err != nil { return err } diff --git a/go-controller/pkg/node/gateway_init.go b/go-controller/pkg/node/gateway_init.go index 4fe0b244fd..1c2d79c98e 100644 --- a/go-controller/pkg/node/gateway_init.go +++ b/go-controller/pkg/node/gateway_init.go @@ -9,6 +9,8 @@ import ( "github.com/vishvananda/netlink" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -195,7 +197,6 @@ func (nc *DefaultNodeNetworkController) initGatewayPreStart( subnets []*net.IPNet, nodeAnnotator kube.Annotator, mgmtPort managementport.Interface, - kubeNodeIP net.IP, ) (*gateway, error) { klog.Info("Initializing Gateway Functionality for Gateway PreStart") @@ -219,13 +220,39 @@ func (nc *DefaultNodeNetworkController) initGatewayPreStart( return nil, err } - // For DPU need to use the host IP addr which currently is assumed to be K8s Node cluster - // internal IP address. + // For DPU mode, we need to use the host IP address which is stored as a Kubernetes + // node annotation rather than using the gateway interface IP addresses. if config.OvnKubeNode.Mode == types.NodeModeDPU { - ifAddrs, err = nodeutil.GetDPUHostPrimaryIPAddresses(kubeNodeIP, ifAddrs) + // Retrieve the current node object from the Kubernetes API + var node *corev1.Node + if node, err = nc.watchFactory.GetNode(nc.name); err != nil { + return nil, fmt.Errorf("error retrieving node %s: %v", nc.name, err) + } + + // Extract the primary DPU address annotation from the node + nodeIfAddr, err := util.GetNodePrimaryDPUHostAddrAnnotation(node) if err != nil { return nil, err } + // For DPU mode, we only support IPv4 for now. + nodeAddrStr := nodeIfAddr.IPv4 + if nodeAddrStr == "" { + return nil, fmt.Errorf("node primary DPU address annotation is empty for node %s", nc.name) + } + + // Parse the IPv4 address string into IP and network components + nodeIP, nodeAddrs, err := net.ParseCIDR(nodeAddrStr) + if err != nil { + return nil, fmt.Errorf("failed to parse node IP address %s: %v", nodeAddrStr, err) + } + + // Set the parsed IP as the network address + nodeAddrs.IP = nodeIP + + // Create a new slice and replace ifAddrs with the DPU host address + // This overrides the gateway interface addresses for DPU mode + var gwIps []*net.IPNet + ifAddrs = append(gwIps, nodeAddrs) } if err := util.SetNodePrimaryIfAddrs(nodeAnnotator, ifAddrs); err != nil { @@ -359,7 +386,7 @@ func interfaceForEXGW(intfName string) string { return intfName } -func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP) error { +func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP, nodeAnnotator kube.Annotator) error { // A DPU host gateway is complementary to the shared gateway running // on the DPU embedded CPU. it performs some initializations and // watch on services for iptable rule updates and run a loadBalancerHealth checker @@ -367,35 +394,71 @@ func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP) er klog.Info("Initializing Shared Gateway Functionality on DPU host") var err error - // Force gateway interface to be the interface associated with kubeNodeIP - gwIntf, err := getInterfaceByIP(kubeNodeIP) + // Find the network interface that has the Kubernetes node IP assigned to it + // This interface will be used for DPU host gateway operations + kubeIntf, err := getInterfaceByIP(kubeNodeIP) if err != nil { return err } - config.Gateway.Interface = gwIntf - _, gatewayIntf, err := getGatewayNextHops() + // Get all IP addresses (IPv4 and IPv6) configured on the detected interface + ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(kubeIntf) if err != nil { return err } - ifAddrs, err := nodeutil.GetNetworkInterfaceIPAddresses(gatewayIntf) - if err != nil { + // Extract the IPv4 address from the interface addresses for node annotation + nodeIPNet, _ := util.MatchFirstIPNetFamily(false, ifAddrs) + nodeAddrSet := sets.New[string](nodeIPNet.String()) + + // If no gateway interface is explicitly configured, use the detected interface + if config.Gateway.Interface == "" { + config.Gateway.Interface = kubeIntf + } + + // If a different gateway interface is configured than the one with used for the kubernetes node IP, + // get its addresses and add them to the node address set for routing purposes + if config.Gateway.Interface != kubeIntf { + ifAddrs, err = nodeutil.GetNetworkInterfaceIPAddresses(config.Gateway.Interface) + if err != nil { + return err + } + detectedIPNetv4, _ := util.MatchFirstIPNetFamily(false, ifAddrs) + nodeAddrSet.Insert(detectedIPNetv4.String()) + // Use the configured interface for the masquerade route instead of the auto-detected one + kubeIntf = config.Gateway.Interface + } + + // Set the primary DPU address annotation on the node with the interface addresses + if err := util.SetNodePrimaryDPUHostAddr(nodeAnnotator, ifAddrs); err != nil { + klog.Errorf("Unable to set primary IP net label on node, err: %v", err) + return err + } + + // Set the host CIDRs annotation to include all detected network addresses + // This helps with routing decisions for traffic coming from the host + if err := util.SetNodeHostCIDRs(nodeAnnotator, nodeAddrSet); err != nil { + klog.Errorf("Unable to set host-cidrs on node, err: %v", err) return err } + // Apply all node annotations to the Kubernetes node object + if err := nodeAnnotator.Run(); err != nil { + return fmt.Errorf("failed to set node %s annotations: %w", nc.name, err) + } + // Delete stale masquerade resources if there are any. This is to make sure that there // are no Linux resources with IP from old masquerade subnet when masquerade subnet // gets changed as part of day2 operation. - if err := deleteStaleMasqueradeResources(gwIntf, nc.name, nc.watchFactory); err != nil { + if err := deleteStaleMasqueradeResources(kubeIntf, nc.name, nc.watchFactory); err != nil { return fmt.Errorf("failed to remove stale masquerade resources: %w", err) } - if err := setNodeMasqueradeIPOnExtBridge(gwIntf); err != nil { - return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", gwIntf, err) + if err := setNodeMasqueradeIPOnExtBridge(kubeIntf); err != nil { + return fmt.Errorf("failed to set the node masquerade IP on the ext bridge %s: %v", kubeIntf, err) } - if err := addMasqueradeRoute(nc.routeManager, gwIntf, nc.name, ifAddrs, nc.watchFactory); err != nil { + if err := addMasqueradeRoute(nc.routeManager, kubeIntf, nc.name, ifAddrs, nc.watchFactory); err != nil { return fmt.Errorf("failed to set the node masquerade route to OVN: %v", err) } @@ -404,7 +467,7 @@ func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP) er return fmt.Errorf("failed to update masquerade subnet annotation on node: %s, error: %v", nc.name, err) } - err = configureSvcRouteViaInterface(nc.routeManager, gatewayIntf, DummyNextHopIPs()) + err = configureSvcRouteViaInterface(nc.routeManager, config.Gateway.Interface, DummyNextHopIPs()) if err != nil { return err } @@ -430,7 +493,7 @@ func (nc *DefaultNodeNetworkController) initGatewayDPUHost(kubeNodeIP net.IP) er gw.portClaimWatcher = portClaimWatcher } - if err := addHostMACBindings(gwIntf); err != nil { + if err := addHostMACBindings(kubeIntf); err != nil { return fmt.Errorf("failed to add MAC bindings for service routing") } diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 9e1fc9213c..6e8aadc0f5 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -725,6 +725,9 @@ func shareGatewayInterfaceDPUTest(app *cli.App, testNS ns.NetNS, k := &kube.Kube{KClient: kubeFakeClient} nodeAnnotator := kube.NewNodeAnnotator(k, existingNode.Name) + err = util.SetNodePrimaryDPUHostAddr(nodeAnnotator, ovntest.MustParseIPNets(nodeSubnet)) + config.Gateway.RouterSubnet = nodeSubnet + Expect(err).NotTo(HaveOccurred()) err = util.SetNodeHostSubnetAnnotation(nodeAnnotator, ovntest.MustParseIPNets(nodeSubnet)) Expect(err).NotTo(HaveOccurred()) @@ -893,8 +896,11 @@ func shareGatewayInterfaceDPUHostTest(app *cli.App, testNS ns.NetNS, uplinkName, err = testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + k := &kube.Kube{KClient: kubeFakeClient} + + nodeAnnotator := kube.NewNodeAnnotator(k, existingNode.Name) - err := nc.initGatewayDPUHost(net.ParseIP(hostIP)) + err := nc.initGatewayDPUHost(net.ParseIP(hostIP), nodeAnnotator) Expect(err).NotTo(HaveOccurred()) link, err := netlink.LinkByName(uplinkName) diff --git a/go-controller/pkg/node/node_ip_handler_linux.go b/go-controller/pkg/node/node_ip_handler_linux.go index 770ec5924e..dcbbbfc7d6 100644 --- a/go-controller/pkg/node/node_ip_handler_linux.go +++ b/go-controller/pkg/node/node_ip_handler_linux.go @@ -65,27 +65,11 @@ func newAddressManagerInternal(nodeName string, k kube.Interface, mgmtPort manag } mgr.nodeAnnotator = kube.NewNodeAnnotator(k, nodeName) if config.OvnKubeNode.Mode == types.NodeModeDPU { - var ifAddrs []*net.IPNet - - // update k8s.ovn.org/host-cidrs - node, err := watchFactory.GetNode(nodeName) - if err != nil { - klog.Errorf("Failed to get node %s: %v", nodeName, err) - return nil - } - if useNetlink { - // get updated interface IP addresses for the gateway bridge - ifAddrs, err = gwBridge.UpdateInterfaceIPAddresses(node) - if err != nil { - klog.Errorf("Failed to obtain interface IP addresses for node %s: %v", nodeName, err) - return nil - } - } - if err = mgr.updateHostCIDRs(ifAddrs); err != nil { + if err := mgr.updateHostCIDRs(); err != nil { klog.Errorf("Failed to update host-cidrs annotations on node %s: %v", nodeName, err) return nil } - if err = mgr.nodeAnnotator.Run(); err != nil { + if err := mgr.nodeAnnotator.Run(); err != nil { klog.Errorf("Failed to set host-cidrs annotations on node %s: %v", nodeName, err) return nil } @@ -286,7 +270,7 @@ func (c *addressManager) updateNodeAddressAnnotations() error { } // update k8s.ovn.org/host-cidrs - if err = c.updateHostCIDRs(ifAddrs); err != nil { + if err = c.updateHostCIDRs(); err != nil { return err } @@ -316,14 +300,10 @@ func (c *addressManager) updateNodeAddressAnnotations() error { return nil } -func (c *addressManager) updateHostCIDRs(ifAddrs []*net.IPNet) error { +func (c *addressManager) updateHostCIDRs() error { if config.OvnKubeNode.Mode == types.NodeModeDPU { - // For DPU mode, here we need to use the DPU host's IP address which is the tenant cluster's - // host internal IP address instead. - // Currently we are only intentionally supporting IPv4 for DPU here. - nodeIPNetv4, _ := util.MatchFirstIPNetFamily(false, ifAddrs) - nodeAddrSet := sets.New[string](nodeIPNetv4.String()) - return util.SetNodeHostCIDRs(c.nodeAnnotator, nodeAddrSet) + // For DPU mode, we don't need to update the host-cidrs annotation. + return nil } return util.SetNodeHostCIDRs(c.nodeAnnotator, c.cidrs) diff --git a/go-controller/pkg/ovn/controller/services/lb_config.go b/go-controller/pkg/ovn/controller/services/lb_config.go index 7cd2238e81..461827041c 100644 --- a/go-controller/pkg/ovn/controller/services/lb_config.go +++ b/go-controller/pkg/ovn/controller/services/lb_config.go @@ -91,10 +91,16 @@ func makeNodeRouterTargetIPs(node *nodeInfo, c *lbConfig, hostMasqueradeIPV4, ho targetIPsV6 = localIPsV6 } + // TODO: For all scenarios the lbAddress should be set to hostAddressesStr but this is breaking CI needs more investigation + lbAddresses := node.hostAddressesStr() + if config.OvnKubeNode.Mode == types.NodeModeFull { + lbAddresses = node.l3gatewayAddressesStr() + } + // Any targets local to the node need to have a special // harpin IP added, but only for the router LB - targetIPsV4, v4Updated := util.UpdateIPsSlice(targetIPsV4, node.l3gatewayAddressesStr(), []string{hostMasqueradeIPV4}) - targetIPsV6, v6Updated := util.UpdateIPsSlice(targetIPsV6, node.l3gatewayAddressesStr(), []string{hostMasqueradeIPV6}) + targetIPsV4, v4Updated := util.UpdateIPsSlice(targetIPsV4, lbAddresses, []string{hostMasqueradeIPV4}) + targetIPsV6, v6Updated := util.UpdateIPsSlice(targetIPsV6, lbAddresses, []string{hostMasqueradeIPV6}) // Local endpoints are a subset of cluster endpoints, so it is enough to compare their length v4Changed = len(targetIPsV4) != len(c.clusterEndpoints.V4IPs) || v4Updated diff --git a/go-controller/pkg/ovn/default_network_controller.go b/go-controller/pkg/ovn/default_network_controller.go index ed79067e8e..26ad651206 100644 --- a/go-controller/pkg/ovn/default_network_controller.go +++ b/go-controller/pkg/ovn/default_network_controller.go @@ -949,6 +949,7 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int zoneClusterChanged := h.oc.nodeZoneClusterChanged(oldNode, newNode, newNodeIsLocalZoneNode, types.DefaultNetworkName) nodeSubnetChange := nodeSubnetChanged(oldNode, newNode, types.DefaultNetworkName) nodeEncapIPsChanged := util.NodeEncapIPsChanged(oldNode, newNode) + nodePrimaryDPUHostAddrChanged := util.NodePrimaryDPUHostAddrAnnotationChanged(oldNode, newNode) var aggregatedErrors []error if newNodeIsLocalZoneNode { @@ -1006,11 +1007,18 @@ func (h *defaultNetworkControllerEventHandler) UpdateResource(oldObj, newObj int // Also check if node subnet changed, so static routes are properly set // Also check if the node is used to be a hybrid overlay node syncZoneIC = syncZoneIC || h.oc.isLocalZoneNode(oldNode) || nodeSubnetChange || zoneClusterChanged || - switchToOvnNode || nodeEncapIPsChanged + switchToOvnNode || nodeEncapIPsChanged || nodePrimaryDPUHostAddrChanged if syncZoneIC { klog.Infof("Node %q in remote zone %q, network %q, needs interconnect zone sync up. Zone cluster changed: %v", newNode.Name, util.GetNodeZone(newNode), h.oc.GetNetworkName(), zoneClusterChanged) } + // Reprovisioning the DPU (including OVS), which is pinned to a host, will change the system ID but not the node. + if config.OvnKubeNode.Mode == types.NodeModeDPU && nodeChassisChanged(oldNode, newNode) { + if err := h.oc.zoneChassisHandler.DeleteRemoteZoneNode(oldNode); err != nil { + aggregatedErrors = append(aggregatedErrors, err) + } + syncZoneIC = true + } if err := h.oc.addUpdateRemoteNodeEvent(newNode, syncZoneIC); err != nil { aggregatedErrors = append(aggregatedErrors, err) } diff --git a/go-controller/pkg/util/node_annotations.go b/go-controller/pkg/util/node_annotations.go index 4e9a984748..cddd754d60 100644 --- a/go-controller/pkg/util/node_annotations.go +++ b/go-controller/pkg/util/node_annotations.go @@ -97,6 +97,9 @@ const ( // OVNNodeHostCIDRs is used to track the different host IP addresses and subnet masks on the node OVNNodeHostCIDRs = "k8s.ovn.org/host-cidrs" + // OVNNodePrimaryDPUHostAddr is used to track the primary DPU host address on the node + OVNNodePrimaryDPUHostAddr = "k8s.ovn.org/primary-dpu-host-addr" + // OVNNodeSecondaryHostEgressIPs contains EgressIP addresses that aren't managed by OVN. The EIP addresses are assigned to // standard linux interfaces and not interfaces of type OVS. OVNNodeSecondaryHostEgressIPs = "k8s.ovn.org/secondary-host-egress-ips" @@ -1534,3 +1537,39 @@ func ParseNodeEncapIPsAnnotation(node *corev1.Node) ([]string, error) { func NodeEncapIPsChanged(oldNode, newNode *corev1.Node) bool { return oldNode.Annotations[OVNNodeEncapIPs] != newNode.Annotations[OVNNodeEncapIPs] } + +// SetNodePrimaryDPUHostAddr sets the primary DPU host address annotation on a node +func SetNodePrimaryDPUHostAddr(nodeAnnotator kube.Annotator, ifAddrs []*net.IPNet) error { + nodeIPNetv4, _ := MatchFirstIPNetFamily(false, ifAddrs) + nodeIPNetv6, _ := MatchFirstIPNetFamily(true, ifAddrs) + + ifAddrAnnotation := ifAddr{} + if nodeIPNetv4 != nil { + ifAddrAnnotation.IPv4 = nodeIPNetv4.String() + } + if nodeIPNetv6 != nil { + ifAddrAnnotation.IPv6 = nodeIPNetv6.String() + } + return nodeAnnotator.Set(OVNNodePrimaryDPUHostAddr, ifAddrAnnotation) +} + +// NodePrimaryDPUHostAddrAnnotationChanged returns true if the primary DPU host address annotation changed +func NodePrimaryDPUHostAddrAnnotationChanged(oldNode, newNode *corev1.Node) bool { + return oldNode.Annotations[OVNNodePrimaryDPUHostAddr] != newNode.Annotations[OVNNodePrimaryDPUHostAddr] +} + +// GetNodePrimaryDPUHostAddrAnnotation returns the raw primary DPU host address annotation from a node +func GetNodePrimaryDPUHostAddrAnnotation(node *corev1.Node) (*ifAddr, error) { + addrAnnotation, ok := node.Annotations[OVNNodePrimaryDPUHostAddr] + if !ok { + return nil, newAnnotationNotSetError("%s annotation not found for node %q", OVNNodePrimaryDPUHostAddr, node.Name) + } + nodeIfAddr := &ifAddr{} + if err := json.Unmarshal([]byte(addrAnnotation), nodeIfAddr); err != nil { + return nil, fmt.Errorf("failed to unmarshal annotation: %s for node %q, err: %v", OVNNodePrimaryDPUHostAddr, node.Name, err) + } + if nodeIfAddr.IPv4 == "" && nodeIfAddr.IPv6 == "" { + return nil, fmt.Errorf("node: %q does not have any IP information set", node.Name) + } + return nodeIfAddr, nil +} From 45bf0b39b008377b9b7eac8665d48b614bcc386e Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Mon, 21 Jul 2025 12:23:47 +0200 Subject: [PATCH 163/278] Revert "e2e: Use ovnk allocator and reserve IPs" This reverts commit 9fed90c790221bd4b925ba2a4ad57784a1cbea3f. Signed-off-by: Enrique Llorente --- test/e2e/ipalloc/ipalloc.go | 47 +++++++ test/e2e/ipalloc/primaryipalloc.go | 166 +++++++++++++++++++----- test/e2e/ipalloc/primaryipalloc_test.go | 90 ++++++++----- 3 files changed, 238 insertions(+), 65 deletions(-) create mode 100644 test/e2e/ipalloc/ipalloc.go diff --git a/test/e2e/ipalloc/ipalloc.go b/test/e2e/ipalloc/ipalloc.go new file mode 100644 index 0000000000..7decbaa0a1 --- /dev/null +++ b/test/e2e/ipalloc/ipalloc.go @@ -0,0 +1,47 @@ +package ipalloc + +import ( + "fmt" + "math/big" + "net" +) + +type ipAllocator struct { + net *net.IPNet + // base is a cached version of the start IP in the CIDR range as a *big.Int + base *big.Int + // max is the maximum size of the usable addresses in the range + max int + count int +} + +func newIPAllocator(cidr *net.IPNet) *ipAllocator { + return &ipAllocator{net: cidr, base: getBaseInt(cidr.IP), max: limit(cidr)} +} + +func (n *ipAllocator) AllocateNextIP() (net.IP, error) { + if n.count >= n.max { + return net.IP{}, fmt.Errorf("limit of %d reached", n.max) + } + n.base.Add(n.base, big.NewInt(1)) + n.count += 1 + b := n.base.Bytes() + b = append(make([]byte, 16), b...) + return b[len(b)-16:], nil +} + +func getBaseInt(ip net.IP) *big.Int { + return big.NewInt(0).SetBytes(ip.To16()) +} + +func limit(subnet *net.IPNet) int { + ones, bits := subnet.Mask.Size() + if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 127 { + return 0 + } + // limit to 2^8 (256) IPs for e2es + if bits == 128 && (bits-ones) >= 8 { + return int(1) << uint(8) + } + return int(1) << uint(bits-ones) +} diff --git a/test/e2e/ipalloc/primaryipalloc.go b/test/e2e/ipalloc/primaryipalloc.go index 1e7c34bb87..79a0ae5010 100644 --- a/test/e2e/ipalloc/primaryipalloc.go +++ b/test/e2e/ipalloc/primaryipalloc.go @@ -3,20 +3,19 @@ package ipalloc import ( "context" "fmt" - "net" - "sync" - - ipallocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/kubernetes/typed/core/v1" + "net" + "sync" ) // primaryIPAllocator attempts to allocate an IP in the same subnet as a nodes primary network type primaryIPAllocator struct { mu *sync.Mutex - v4 *ipallocator.Range - v6 *ipallocator.Range + v4 *ipAllocator + v6 *ipAllocator nodeClient v1.NodeInterface } @@ -48,37 +47,91 @@ func newPrimaryIPAllocator(nodeClient v1.NodeInterface) (*primaryIPAllocator, er if len(nodes.Items) == 0 { return ipa, fmt.Errorf("expected at least one node but found zero") } + // FIXME: the approach taken here to find the first node IP+mask and then to increment the second last octet wont work in + // all scenarios (node with /24). We should generate an EgressIP compatible with a Node providers primary network and then take care its unique globally. - for _, node := range nodes.Items { + // The approach here is to grab initial starting IP from first node found, increment the second last octet. + // Approach taken here won't work for Nodes handed /24 subnets. + nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&nodes.Items[0]) + if err != nil { + return ipa, fmt.Errorf("failed to parse node primary interface address from Node object: %v", err) + } + if nodePrimaryIPs.V4.IP != nil { + // should be ok with /16 and /64 node primary provider subnets + // TODO; fixme; what about /24 subnet Nodes like GCP + nodePrimaryIPs.V4.IP[len(nodePrimaryIPs.V4.IP)-2]++ + ipa.v4 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V4.IP, Mask: nodePrimaryIPs.V4.Net.Mask}) + } + if nodePrimaryIPs.V6.IP != nil { + nodePrimaryIPs.V6.IP[len(nodePrimaryIPs.V6.IP)-2]++ + ipa.v6 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V6.IP, Mask: nodePrimaryIPs.V6.Net.Mask}) + } + // verify the new starting base IP is within all Nodes subnets + if nodePrimaryIPs.V4.IP != nil { + ipNets, err := getNodePrimaryProviderIPs(nodes.Items, false) + if err != nil { + return ipa, err + } + nextIP, err := ipa.v4.AllocateNextIP() + if err != nil { + return ipa, err + } + if !isIPWithinAllSubnets(ipNets, nextIP) { + return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP) + } + } + if nodePrimaryIPs.V6.IP != nil { + ipNets, err := getNodePrimaryProviderIPs(nodes.Items, true) + if err != nil { + return ipa, err + } + nextIP, err := ipa.v6.AllocateNextIP() + if err != nil { + return ipa, err + } + if !isIPWithinAllSubnets(ipNets, nextIP) { + return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP) + } + } + + return ipa, nil +} + +func getNodePrimaryProviderIPs(nodes []corev1.Node, isIPv6 bool) ([]*net.IPNet, error) { + ipNets := make([]*net.IPNet, 0, len(nodes)) + for _, node := range nodes { nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&node) if err != nil { - return ipa, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err) - } - if nodePrimaryIPs.V4.IP != nil { - if ipa.v4 == nil { - ipa.v4, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V4.Net) - if err != nil { - return ipa, fmt.Errorf("failed to create new CIDR range for IPv4: %v", err) - } - } - if err := ipa.v4.Allocate(nodePrimaryIPs.V4.IP); err != nil { - return ipa, fmt.Errorf("failed to allocate IPv4 %s: %v", nodePrimaryIPs.V4.IP, err) - } - } - if nodePrimaryIPs.V6.IP != nil { - if ipa.v6 == nil { - ipa.v6, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V6.Net) - if err != nil { - return ipa, fmt.Errorf("failed to create new CIDR range for IPv6: %v", err) - } - } - if err := ipa.v6.Allocate(nodePrimaryIPs.V6.IP); err != nil { - return ipa, fmt.Errorf("failed to allocate IPv6 %s: %v", nodePrimaryIPs.V6.IP, err) - } + return nil, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err) } + var mask net.IPMask + var ip net.IP + if isIPv6 { + ip = nodePrimaryIPs.V6.IP + mask = nodePrimaryIPs.V6.Net.Mask + } else { + ip = nodePrimaryIPs.V4.IP + mask = nodePrimaryIPs.V4.Net.Mask + } + if len(ip) == 0 || len(mask) == 0 { + return nil, fmt.Errorf("failed to find Node %s primary Node IP and/or mask", node.Name) + } + ipNets = append(ipNets, &net.IPNet{IP: ip, Mask: mask}) } - return ipa, nil + return ipNets, nil +} + +func isIPWithinAllSubnets(ipNets []*net.IPNet, ip net.IP) bool { + if len(ipNets) == 0 { + return false + } + for _, ipNet := range ipNets { + if !ipNet.Contains(ip) { + return false + } + } + return true } func (pia *primaryIPAllocator) IncrementAndGetNextV4(times int) (net.IP, error) { @@ -95,9 +148,12 @@ func (pia *primaryIPAllocator) AllocateNextV4() (net.IP, error) { if pia.v4 == nil { return nil, fmt.Errorf("IPv4 is not enable ") } + if pia.v4.net == nil { + return nil, fmt.Errorf("IPv4 is not enabled but Allocation request was called") + } pia.mu.Lock() defer pia.mu.Unlock() - return pia.v4.AllocateNext() + return allocateIP(pia.nodeClient, pia.v4.AllocateNextIP) } func (pia *primaryIPAllocator) IncrementAndGetNextV6(times int) (net.IP, error) { @@ -114,7 +170,51 @@ func (pia primaryIPAllocator) AllocateNextV6() (net.IP, error) { if pia.v6 == nil { return nil, fmt.Errorf("IPv6 is not enabled but Allocation request was called") } + if pia.v6.net == nil { + return nil, fmt.Errorf("ipv6 network is not set") + } pia.mu.Lock() defer pia.mu.Unlock() - return pia.v6.AllocateNext() + return allocateIP(pia.nodeClient, pia.v6.AllocateNextIP) +} + +type allocNextFn func() (net.IP, error) + +func allocateIP(nodeClient v1.NodeInterface, allocateFn allocNextFn) (net.IP, error) { + nodeList, err := nodeClient.List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to list nodes: %v", err) + } + for { + nextIP, err := allocateFn() + if err != nil { + return nil, fmt.Errorf("failed to allocated next IP address: %v", err) + } + firstOctet := nextIP[len(nextIP)-1] + // skip 0 and 1 + if firstOctet == 0 || firstOctet == 1 { + continue + } + isConflict, err := isConflictWithExistingHostIPs(nodeList.Items, nextIP) + if err != nil { + return nil, fmt.Errorf("failed to determine if IP conflicts with existing IPs: %v", err) + } + if !isConflict { + return nextIP, nil + } + } +} + +func isConflictWithExistingHostIPs(nodes []corev1.Node, ip net.IP) (bool, error) { + ipStr := ip.String() + for _, node := range nodes { + nodeIPsSet, err := util.ParseNodeHostCIDRsDropNetMask(&node) + if err != nil { + return false, fmt.Errorf("failed to parse node %s primary annotation info: %v", node.Name, err) + } + if nodeIPsSet.Has(ipStr) { + return true, nil + } + } + return false, nil } diff --git a/test/e2e/ipalloc/primaryipalloc_test.go b/test/e2e/ipalloc/primaryipalloc_test.go index 1702afe545..815915b7ea 100644 --- a/test/e2e/ipalloc/primaryipalloc_test.go +++ b/test/e2e/ipalloc/primaryipalloc_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" + utilsnet "k8s.io/utils/net" ) func TestUtilSuite(t *testing.T) { @@ -22,6 +23,40 @@ func TestUtilSuite(t *testing.T) { ginkgo.RunSpecs(t, "node ip alloc suite") } +func TestAllocateNext(t *testing.T) { + tests := []struct { + desc string + input *net.IPNet + output []net.IP + }{ + { + desc: "increments IPv4 address", + input: mustParseCIDRIncIP("192.168.1.5/16"), // mask /24 would fail + output: []net.IP{net.ParseIP("192.168.1.6"), net.ParseIP("192.168.1.7"), net.ParseIP("192.168.1.8")}, + }, + { + desc: "increments IPv6 address", + input: mustParseCIDRIncIP("fc00:f853:ccd:e793::6/64"), + output: []net.IP{net.ParseIP("fc00:f853:ccd:e793::7"), net.ParseIP("fc00:f853:ccd:e793::8"), net.ParseIP("fc00:f853:ccd:e793::9")}, + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) { + nodeIPAlloc := newIPAllocator(tc.input) + for _, expectedIP := range tc.output { + allocatedIP, err := nodeIPAlloc.AllocateNextIP() + if err != nil { + t.Errorf("failed to allocated next IP: %v", err) + } + if !allocatedIP.Equal(expectedIP) { + t.Errorf("Expected IP %q, but got %q", expectedIP.String(), allocatedIP.String()) + } + } + }) + } +} + // mustParseCIDRIncIP parses the IP and CIDR. It adds the IP to the returned IPNet. func mustParseCIDRIncIP(cidr string) *net.IPNet { ip, ipNet, err := net.ParseCIDR(cidr) @@ -43,19 +78,20 @@ type node struct { } func TestIPAlloc(t *testing.T) { - g := gomega.NewWithT(t) - tests := []struct { - desc string - existingPrimaryNodeIPs []node + desc string + existingPrimaryNodeIPs []node + expectedFromAllocateNext []string }{ { - desc: "IPv4", - existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}}, + desc: "IPv4", + existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}}, + expectedFromAllocateNext: []string{"192.168.2.3", "192.168.2.4"}, }, { - desc: "IPv6", - existingPrimaryNodeIPs: []node{{v6: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v6: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}}, + desc: "IPv6", + existingPrimaryNodeIPs: []node{{v4: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v4: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}}, + expectedFromAllocateNext: []string{"fc00:f853:ccd:e793::8", "fc00:f853:ccd:e793::9"}, }, } @@ -67,32 +103,22 @@ func TestIPAlloc(t *testing.T) { t.Errorf(err.Error()) return } - existingIPv4IPs := []string{} - existingIPv6IPs := []string{} - allocatedIPv4IPs := []string{} - allocatedIPv6IPs := []string{} - for _, existingPrimaryNodeIP := range tc.existingPrimaryNodeIPs { - if existingPrimaryNodeIP.v4.ip != "" { - existingIPv4IPs = append(existingIPv4IPs, existingPrimaryNodeIP.v4.ip) - nextIPv4, err := pipa.AllocateNextV4() - g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv4 address") - g.Expect(nextIPv4).ToNot(gomega.BeNil(), "should allocate next IPv4 address") - allocatedIPv4IPs = append(allocatedIPv4IPs, nextIPv4.String()) + for _, expectedIPStr := range tc.expectedFromAllocateNext { + expectedIP := net.ParseIP(expectedIPStr) + var nextIP net.IP + var err error + if utilsnet.IsIPv6(expectedIP) { + nextIP, err = pipa.AllocateNextV6() + } else { + nextIP, err = pipa.AllocateNextV4() } - - if existingPrimaryNodeIP.v6.ip != "" { - existingIPv6IPs = append(existingIPv6IPs, existingPrimaryNodeIP.v6.ip) - nextIPv6, err := pipa.AllocateNextV6() - g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv6 address") - g.Expect(nextIPv6).ToNot(gomega.BeNil(), "should allocate next IPv6 address") - allocatedIPv6IPs = append(allocatedIPv6IPs, nextIPv6.String()) + if err != nil || nextIP == nil { + t.Errorf("failed to allocated next IPv4 or IPv6 address. err %v", err) + return + } + if !nextIP.Equal(expectedIP) { + t.Errorf("expected IP %q, but found %q", expectedIP, nextIP) } - } - if len(existingIPv4IPs) > 0 { - g.Expect(allocatedIPv4IPs).NotTo(gomega.ContainElements(existingIPv4IPs)) - } - if len(existingIPv6IPs) > 0 { - g.Expect(allocatedIPv6IPs).NotTo(gomega.ContainElements(existingIPv6IPs)) } }) } From 6c4bc78badd59ea8f0e5c15c61d74e61dcad3612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 22 Jul 2025 10:21:57 +0000 Subject: [PATCH 164/278] e2e: label RouteAdvertisement test cases & skip extended ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime CaamaƱo Ruiz --- test/e2e/feature/features.go | 1 + test/e2e/label/label.go | 8 ++++++++ test/e2e/route_advertisements.go | 20 ++++++++++++-------- test/scripts/e2e-cp.sh | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/test/e2e/feature/features.go b/test/e2e/feature/features.go index 842b0474e6..e7c3920477 100644 --- a/test/e2e/feature/features.go +++ b/test/e2e/feature/features.go @@ -23,6 +23,7 @@ var ( MultiHoming = New("MultiHoming") NodeIPMACMigration = New("NodeIPMACMigration") OVSCPUPin = New("OVSCPUPin") + RouteAdvertisements = New("RouteAdvertisements") Unidle = New("Unidle") ) diff --git a/test/e2e/label/label.go b/test/e2e/label/label.go index 6f81c9ceb1..61448bf930 100644 --- a/test/e2e/label/label.go +++ b/test/e2e/label/label.go @@ -40,3 +40,11 @@ func processOverrides(s string) string { } return overRide } + +// Extended returns a label used to label extended feature tests. This label +// might be used to label feature tests that are considered not to be testing +// the core functionality of a feature and that might be filtered out for +// various reasons like for example to keep selected job run times down. +func Extended() ginkgo.Labels { + return ginkgo.Label("EXTENDED") +} diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 59b3eb4a0b..f65dd60631 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -21,9 +21,11 @@ import ( udnv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" udnclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned" "github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig" + "github.com/ovn-org/ovn-kubernetes/test/e2e/feature" "github.com/ovn-org/ovn-kubernetes/test/e2e/images" "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" + "github.com/ovn-org/ovn-kubernetes/test/e2e/label" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" @@ -53,7 +55,7 @@ const ( bgpExternalNetworkName = "bgpnet" ) -var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is advertised", func() { +var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is advertised", feature.RouteAdvertisements, func() { var serverContainerIPs []string var frrContainerIPv4, frrContainerIPv6 string var nodes *corev1.NodeList @@ -248,7 +250,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when default podNetwork is }) }) -var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advertised", func() { +var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advertised", feature.RouteAdvertisements, func() { var serverContainerIPs []string var frrContainerIPv4, frrContainerIPv6 string var nodes *corev1.NodeList @@ -525,7 +527,7 @@ var _ = ginkgo.Describe("BGP: Pod to external server when CUDN network is advert ) }) -var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks", +var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks", feature.RouteAdvertisements, func(cudnATemplate, cudnBTemplate *udnv1.ClusterUserDefinedNetwork) { const curlConnectionTimeoutCode = "28" const ( @@ -1068,7 +1070,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" ), ) -var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { +var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", feature.RouteAdvertisements, func() { // testing helpers used throughout this testing node const ( @@ -1545,12 +1547,14 @@ var _ = ginkgo.Describe("BGP: For a VRF-Lite configured network", func() { otherNetworksToTest := []ginkgo.TableEntry{ ginkgo.Entry("Default", defaultNetwork, nil), - ginkgo.Entry("Layer 3 UDN non advertised", udn, otherLayer3NetworkSpec), - ginkgo.Entry("Layer 3 CUDN advertised", cudnAdvertised, otherLayer3NetworkSpec), ginkgo.Entry("Layer 3 CUDN advertised VRF-Lite", cudnAdvertisedVRFLite, otherLayer3NetworkSpec), - ginkgo.Entry("Layer 2 UDN non advertised", udn, otherLayer2NetworkSpec), - ginkgo.Entry("Layer 2 CUDN advertised", cudnAdvertised, otherLayer2NetworkSpec), ginkgo.Entry("Layer 2 CUDN advertised VRF-Lite", cudnAdvertisedVRFLite, otherLayer2NetworkSpec), + // The following testcases are labeled as extended, + // might not be run on all jobs + ginkgo.Entry("Layer 3 UDN non advertised", udn, otherLayer3NetworkSpec, label.Extended()), + ginkgo.Entry("Layer 3 CUDN advertised", cudnAdvertised, otherLayer3NetworkSpec, label.Extended()), + ginkgo.Entry("Layer 2 UDN non advertised", udn, otherLayer2NetworkSpec, label.Extended()), + ginkgo.Entry("Layer 2 CUDN advertised", cudnAdvertised, otherLayer2NetworkSpec, label.Extended()), } ginkgo.DescribeTableSubtree("Of type", diff --git a/test/scripts/e2e-cp.sh b/test/scripts/e2e-cp.sh index cbccc5ee29..096debe8a6 100755 --- a/test/scripts/e2e-cp.sh +++ b/test/scripts/e2e-cp.sh @@ -40,6 +40,14 @@ skip() { SKIPPED_TESTS+=$* } +SKIPPED_LABELED_TESTS="" +skip_label() { + if [ "$SKIPPED_LABELED_TESTS" != "" ]; then + SKIPPED_LABELED_TESTS+=" && " + fi + SKIPPED_LABELED_TESTS+="!($*)" +} + if [ "$PLATFORM_IPV4_SUPPORT" == true ]; then if [ "$PLATFORM_IPV6_SUPPORT" == true ]; then # No support for these features in dual-stack yet @@ -138,6 +146,11 @@ if [ "$ENABLE_ROUTE_ADVERTISEMENTS" != true ]; then skip $BGP_TESTS else if [ "$ADVERTISE_DEFAULT_NETWORK" = true ]; then + # Filter out extended RouteAdvertisements tests to keep job run time down + if [ "$ENABLE_NETWORK_SEGMENTATION" = true ]; then + skip_label "Feature:RouteAdvertisements && EXTENDED" + fi + # Some test don't work when the default network is advertised, either because # the configuration that the test excercises does not make sense for an advertised network, or # there is some bug or functional gap @@ -203,6 +216,7 @@ go test -test.timeout ${GO_TEST_TIMEOUT}m -v . \ -ginkgo.timeout ${TEST_TIMEOUT}m \ -ginkgo.flake-attempts ${FLAKE_ATTEMPTS:-2} \ -ginkgo.skip="${SKIPPED_TESTS}" \ + ${SKIPPED_LABELED_TESTS:+-ginkgo.label-filter="${SKIPPED_LABELED_TESTS}"} \ -ginkgo.junit-report=${E2E_REPORT_DIR}/junit_${E2E_REPORT_PREFIX}report.xml \ -provider skeleton \ -kubeconfig ${KUBECONFIG} \ From b90abc54e7a210c189336803e5c541538c64c5df Mon Sep 17 00:00:00 2001 From: Alin Serdean Date: Tue, 22 Jul 2025 17:30:06 +0200 Subject: [PATCH 165/278] fix: skip gw IP check for DPU and improve gateway initialization readability Skip GW IP check in case ovnkube is running in DPU mode. Extract interface address logic into dedicated helper function to improve code readability and maintainability in gateway initialization. - Add getInterfaceAddressesForNodeMode() helper function - Replace conditional logic with switch statement for better extensibility - Simplify initGatewayPreStart() by removing duplicate error handling - Improve code organization and reduce cognitive complexity Signed-off-by: Alin Gabriel Serdean Signed-off-by: Alin Serdean --- go-controller/pkg/node/gateway_init.go | 72 +++++++++++++------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/go-controller/pkg/node/gateway_init.go b/go-controller/pkg/node/gateway_init.go index 1c2d79c98e..b4d11d69cf 100644 --- a/go-controller/pkg/node/gateway_init.go +++ b/go-controller/pkg/node/gateway_init.go @@ -9,7 +9,6 @@ import ( "github.com/vishvananda/netlink" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -188,6 +187,39 @@ func configureSvcRouteViaInterface(routeManager *routemanager.Controller, iface return nil } +// getNodePrimaryIfAddrs returns the appropriate interface addresses based on the node mode +func getNodePrimaryIfAddrs(watchFactory factory.NodeWatchFactory, nodeName string, gatewayIntf string) ([]*net.IPNet, error) { + switch config.OvnKubeNode.Mode { + case types.NodeModeDPU: + // For DPU mode, use the host IP address from node annotation + node, err := watchFactory.GetNode(nodeName) + if err != nil { + return nil, fmt.Errorf("error retrieving node %s: %v", nodeName, err) + } + + // Extract the primary DPU address annotation from the node + nodeIfAddr, err := util.GetNodePrimaryDPUHostAddrAnnotation(node) + if err != nil { + return nil, err + } + + if nodeIfAddr.IPv4 == "" { + return nil, fmt.Errorf("node primary DPU address annotation is empty for node %s", nodeName) + } + + nodeIP, nodeAddrs, err := net.ParseCIDR(nodeIfAddr.IPv4) + if err != nil { + return nil, fmt.Errorf("failed to parse node IP address %s: %v", nodeIfAddr.IPv4, err) + } + + nodeAddrs.IP = nodeIP + return []*net.IPNet{nodeAddrs}, nil + default: + // For other modes, get network interface IP addresses directly + return nodeutil.GetNetworkInterfaceIPAddresses(gatewayIntf) + } +} + // initGatewayPreStart executes the first part of the gateway initialization for the node. // It creates the gateway object, the node IP manager, openflow manager and node port watcher // once OVN controller is ready and the patch port exists for this node. @@ -215,46 +247,12 @@ func (nc *DefaultNodeNetworkController) initGatewayPreStart( egressGWInterface = interfaceForEXGW(config.Gateway.EgressGWInterface) } - ifAddrs, err = nodeutil.GetNetworkInterfaceIPAddresses(gatewayIntf) + // Get interface addresses based on node mode + ifAddrs, err = getNodePrimaryIfAddrs(nc.watchFactory, nc.name, gatewayIntf) if err != nil { return nil, err } - // For DPU mode, we need to use the host IP address which is stored as a Kubernetes - // node annotation rather than using the gateway interface IP addresses. - if config.OvnKubeNode.Mode == types.NodeModeDPU { - // Retrieve the current node object from the Kubernetes API - var node *corev1.Node - if node, err = nc.watchFactory.GetNode(nc.name); err != nil { - return nil, fmt.Errorf("error retrieving node %s: %v", nc.name, err) - } - - // Extract the primary DPU address annotation from the node - nodeIfAddr, err := util.GetNodePrimaryDPUHostAddrAnnotation(node) - if err != nil { - return nil, err - } - // For DPU mode, we only support IPv4 for now. - nodeAddrStr := nodeIfAddr.IPv4 - if nodeAddrStr == "" { - return nil, fmt.Errorf("node primary DPU address annotation is empty for node %s", nc.name) - } - - // Parse the IPv4 address string into IP and network components - nodeIP, nodeAddrs, err := net.ParseCIDR(nodeAddrStr) - if err != nil { - return nil, fmt.Errorf("failed to parse node IP address %s: %v", nodeAddrStr, err) - } - - // Set the parsed IP as the network address - nodeAddrs.IP = nodeIP - - // Create a new slice and replace ifAddrs with the DPU host address - // This overrides the gateway interface addresses for DPU mode - var gwIps []*net.IPNet - ifAddrs = append(gwIps, nodeAddrs) - } - if err := util.SetNodePrimaryIfAddrs(nodeAnnotator, ifAddrs); err != nil { klog.Errorf("Unable to set primary IP net label on node, err: %v", err) } From df487d267abf9cf225875a44622bf0be661e4204 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Tue, 15 Jul 2025 15:19:11 -0700 Subject: [PATCH 166/278] UDN: verify specific error messages in NAD rendering unit tests Update unit tests to check that the returned error contains the expected message, not just that an error occurred. This ensures the renderer fails for the right reasons, ensuring tests precisely validate failures. Signed-off-by: Lei Huang --- .../template/net-attach-def-template.go | 8 +- .../template/net-attach-def-template_test.go | 113 ++++++++++-------- go-controller/pkg/config/errors.go | 104 ++++++++++++++++ go-controller/pkg/config/utils.go | 13 +- go-controller/pkg/util/multi_network.go | 5 +- go-controller/pkg/util/multi_network_test.go | 24 ++-- go-controller/pkg/util/util.go | 8 ++ 7 files changed, 204 insertions(+), 71 deletions(-) create mode 100644 go-controller/pkg/config/errors.go diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go index 0b3aa61194..02c7912e85 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go @@ -113,7 +113,7 @@ func validateTopology(spec SpecGetter) error { if spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLayer3 && spec.GetLayer3() == nil || spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLayer2 && spec.GetLayer2() == nil || spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLocalnet && spec.GetLocalnet() == nil { - return fmt.Errorf("topology %[1]s is specified but %[1]s config is nil", spec.GetTopology()) + return config.NewTopologyConfigMismatchError(string(spec.GetTopology())) } return nil } @@ -142,10 +142,10 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s return nil, err } if ipamEnabled(cfg.IPAM) && len(cfg.Subnets) == 0 { - return nil, fmt.Errorf("subnets is required with ipam.mode is Enabled or unset") + return nil, config.NewSubnetsRequiredError() } if !ipamEnabled(cfg.IPAM) && len(cfg.Subnets) > 0 { - return nil, fmt.Errorf("subnets must be unset when ipam.mode is Disabled") + return nil, config.NewSubnetsMustBeUnsetError() } netConfSpec.Role = strings.ToLower(string(cfg.Role)) @@ -235,7 +235,7 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error { return nil } if ipam.Lifecycle == userdefinednetworkv1.IPAMLifecyclePersistent && !ipamEnabled(ipam) { - return fmt.Errorf("lifecycle Persistent is only supported when ipam.mode is Enabled") + return config.NewIPAMLifecycleNotSupportedError() } return nil } diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go index ab0593e210..c02109bf0e 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go @@ -9,16 +9,24 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" udnv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("NetAttachDefTemplate", func() { + + // before each test, set the IPv4Mode and IPv6Mode to true + BeforeEach(func() { + config.IPv4Mode = true + config.IPv6Mode = true + }) + DescribeTable("should fail to render NAD spec given", - func(spec *udnv1.UserDefinedNetworkSpec) { + func(spec *udnv1.UserDefinedNetworkSpec, expectedError string) { _, err := RenderNADSpec("foo", "bar", spec) - Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring(expectedError))) }, Entry("invalid layer2 subnets", &udnv1.UserDefinedNetworkSpec{ @@ -27,6 +35,7 @@ var _ = Describe("NetAttachDefTemplate", func() { Subnets: udnv1.DualStackCIDRs{"abc"}, }, }, + config.NewCIDRNotProperlyFormattedError("abc").Error(), ), Entry("invalid layer3 cluster-subnet", &udnv1.UserDefinedNetworkSpec{ @@ -35,6 +44,7 @@ var _ = Describe("NetAttachDefTemplate", func() { Subnets: []udnv1.Layer3Subnet{{CIDR: "!", HostSubnet: 16}}, }, }, + config.NewInvalidCIDRAddressError().Error(), ), Entry("invalid layer3 host-subnet mask", &udnv1.UserDefinedNetworkSpec{ @@ -45,6 +55,7 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }, + config.NewHostSubnetMaskError(24, 24).Error(), // -1 is not a valid host subnet mask, it's converted to 24 ), Entry("layer3 host-subnet mask is smaller then cluster-subnet mask", &udnv1.UserDefinedNetworkSpec{ @@ -55,6 +66,7 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }, + config.NewHostSubnetMaskError(16, 24).Error(), ), Entry("layer3 host-subnet mask equal to cluster-subnet mask", &udnv1.UserDefinedNetworkSpec{ @@ -65,16 +77,7 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }, - ), - Entry("layer3 host-subnet mask is smaller then cluster-subnet mask", - &udnv1.UserDefinedNetworkSpec{ - Topology: udnv1.NetworkTopologyLayer3, - Layer3: &udnv1.Layer3Config{ - Subnets: []udnv1.Layer3Subnet{ - {CIDR: "10.10.0.0/16", HostSubnet: 8}, - }, - }, - }, + config.NewHostSubnetMaskError(24, 24).Error(), ), Entry("invalid layer3 host-subnet; IPv4 mask is bigger then 32", &udnv1.UserDefinedNetworkSpec{ @@ -85,102 +88,98 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }, + config.NewInvalidIPv4HostSubnetError().Error(), ), - Entry("invalid join subnets", + Entry("invalid layer2 join subnets", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, JoinSubnets: udnv1.DualStackCIDRs{"abc"}, }, }, + config.NewCIDRNotProperlyFormattedError("abc").Error(), ), - Entry("invalid dual-stack join subnets, invalid IPv4 CIDR", + Entry("invalid layer2 dual-stack join subnets, invalid IPv4 CIDR", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, - JoinSubnets: udnv1.DualStackCIDRs{"!", "fd50::0/125"}, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, + JoinSubnets: udnv1.DualStackCIDRs{"fd50::0/125", "!"}, }, }, + config.NewCIDRNotProperlyFormattedError("!").Error(), ), - Entry("invalid dual-stack join subnets, invalid IPv6 CIDR", + Entry("invalid layer2 dual-stack join subnets, invalid IPv6 CIDR", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, JoinSubnets: udnv1.DualStackCIDRs{"10.10.0.0/24", "!"}, }, }, + config.NewCIDRNotProperlyFormattedError("!").Error(), ), - Entry("invalid dual-stack join subnets, multiple valid IPv4 CIDRs", - &udnv1.UserDefinedNetworkSpec{ - Topology: udnv1.NetworkTopologyLayer2, - Layer2: &udnv1.Layer2Config{ - Role: udnv1.NetworkRolePrimary, - JoinSubnets: udnv1.DualStackCIDRs{"10.10.0.0/24", "10.20.0.0/24", "10.30.0.0/24"}, - }, - }, - ), - Entry("invalid dual-stack join subnets, multiple valid IPv6 CIDRs", - &udnv1.UserDefinedNetworkSpec{ - Topology: udnv1.NetworkTopologyLayer2, - Layer2: &udnv1.Layer2Config{ - Role: udnv1.NetworkRolePrimary, - JoinSubnets: udnv1.DualStackCIDRs{"fd40::0/125", "fd10::0/125", "fd50::0/125"}, - }, - }, - ), - Entry("invalid dual-stack join subnets, multiple valid IPv4 & IPv6 CIDRs", - &udnv1.UserDefinedNetworkSpec{ - Topology: udnv1.NetworkTopologyLayer2, - Layer2: &udnv1.Layer2Config{ - Role: udnv1.NetworkRolePrimary, - JoinSubnets: udnv1.DualStackCIDRs{"fd40::0/125", "10.10.0.0/24", "fd50::0/125", "10.20.0.0/24"}, - }, - }, - ), + // The validation for max number of subnets is moved to the CRD validation, + // no need to test it here. Entry("invalid join subnets, overlapping with cluster-default join-subnet, IPv4", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, JoinSubnets: udnv1.DualStackCIDRs{"100.64.10.0/24"}, }, }, + config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedJoinSubnet, Subnet: util.MustParseCIDR("100.64.10.0/24")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetJoin, Subnet: util.MustParseCIDR("100.64.0.0/16")}).Error(), ), Entry("invalid join subnets, overlapping with cluster-default join-subnet, IPv6", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, JoinSubnets: udnv1.DualStackCIDRs{"fd98::4/127"}, }, }, + config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedJoinSubnet, Subnet: util.MustParseCIDR("fd98::4/127")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetJoin, Subnet: util.MustParseCIDR("fd98::/64")}).Error(), ), Entry("invalid join subnets, overlapping with cluster-default join-subnet, dual-stack", &udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer2: &udnv1.Layer2Config{ Role: udnv1.NetworkRolePrimary, + Subnets: udnv1.DualStackCIDRs{"10.10.0.0/24"}, JoinSubnets: udnv1.DualStackCIDRs{"100.64.10.0/24", "fd98::4/127"}, }, }, + config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedJoinSubnet, Subnet: util.MustParseCIDR("100.64.10.0/24")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetJoin, Subnet: util.MustParseCIDR("100.64.0.0/16")}).Error(), ), ) DescribeTable("should fail to render NAD manifest, given", - func(obj client.Object) { + func(obj client.Object, expectedError string) { _, err := RenderNetAttachDefManifest(obj, "test") - Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring(expectedError))) }, Entry("UDN, invalid topology: topology layer2 & layer3 config", &udnv1.UserDefinedNetwork{Spec: udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer2)).Error(), ), Entry("UDN, invalid topology: topology layer3 & layer2 config", &udnv1.UserDefinedNetwork{Spec: udnv1.UserDefinedNetworkSpec{ Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer3)).Error(), ), Entry("UDN, invalid IPAM config: IPAM lifecycle & disabled ipam mode", &udnv1.UserDefinedNetwork{Spec: udnv1.UserDefinedNetworkSpec{ @@ -194,6 +193,7 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }}, + config.NewIPAMLifecycleNotSupportedError().Error(), ), Entry("UDN, invalid IPAM config: IPAM enabled & no subnet", &udnv1.UserDefinedNetwork{Spec: udnv1.UserDefinedNetworkSpec{ @@ -206,6 +206,7 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }}, + config.NewSubnetsRequiredError().Error(), ), Entry("UDN, invalid IPAM config: IPAM disabled & subnet", &udnv1.UserDefinedNetwork{Spec: udnv1.UserDefinedNetworkSpec{ @@ -218,39 +219,57 @@ var _ = Describe("NetAttachDefTemplate", func() { }, }, }}, + config.NewSubnetsMustBeUnsetError().Error(), ), Entry("CUDN, invalid topology: topology layer2 & layer3 config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer2)).Error(), ), Entry("CUDN, invalid topology: topology layer2 & localnet config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer2)).Error(), ), Entry("CUDN, invalid topology: topology layer3 & layer2 config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer3)).Error(), ), Entry("CUDN, invalid topology: topology layer3 & localnet config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLayer3)).Error(), ), Entry("CUDN, invalid topology: topology localnet & layer2 config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLocalnet)).Error(), ), Entry("CUDN, invalid topology: topology localnet & layer3 config", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}}, + config.NewTopologyConfigMismatchError(string(udnv1.NetworkTopologyLocalnet)).Error(), + ), + Entry("CUDN, localnet: IPv4 excludeSubnets not in range of subnets", + &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ + Topology: udnv1.NetworkTopologyLocalnet, + Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1", + Subnets: udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"}, + ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30"}, + }, + }}}, + config.NewExcludedSubnetNotContainedError("192.200.0.0/30").Error(), ), - Entry("CUDN, localnet: excludeSubnets not in range of subnets", + Entry("CUDN, localnet: IPv6 excludeSubnets not in range of subnets", &udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{ Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1", Subnets: udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"}, - ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"}, + ExcludeSubnets: []udnv1.CIDR{"2001:aaa::/127"}, }, }}}, + config.NewExcludedSubnetNotContainedError("2001:aaa::/127").Error(), ), ) diff --git a/go-controller/pkg/config/errors.go b/go-controller/pkg/config/errors.go new file mode 100644 index 0000000000..9f634c2509 --- /dev/null +++ b/go-controller/pkg/config/errors.go @@ -0,0 +1,104 @@ +package config + +import "fmt" + +type ValidationErrorType string + +const ( + ErrCIDRNotProperlyFormatted ValidationErrorType = "CIDRNotProperlyFormatted" + ErrInvalidCIDRAddress ValidationErrorType = "InvalidCIDRAddress" + ErrHostSubnetMask ValidationErrorType = "HostSubnetMask" + ErrInvalidIPv4HostSubnet ValidationErrorType = "InvalidIPv4HostSubnet" + ErrSubnetOverlap ValidationErrorType = "SubnetOverlap" + ErrExcludedSubnetNotContained ValidationErrorType = "ExcludedSubnetNotContained" + ErrTopologyConfigMismatch ValidationErrorType = "TopologyConfigMismatch" + ErrIPAMLifecycleNotSupported ValidationErrorType = "IPAMLifecycleNotSupported" + ErrSubnetsRequired ValidationErrorType = "SubnetsRequired" + ErrSubnetsMustBeUnset ValidationErrorType = "SubnetsMustBeUnset" +) + +type ValidationError struct { + Type ValidationErrorType + Message string +} + +func (e *ValidationError) Error() string { + return e.Message +} + +// CIDR Validation Errors +func NewCIDRNotProperlyFormattedError(cidr string) *ValidationError { + return &ValidationError{ + Type: ErrCIDRNotProperlyFormatted, + Message: fmt.Sprintf("CIDR %q not properly formatted", cidr), + } +} + +func NewInvalidCIDRAddressError() *ValidationError { + return &ValidationError{ + Type: ErrInvalidCIDRAddress, + Message: "invalid CIDR address", + } +} + +// Subnet Validation Errors +func NewHostSubnetMaskError(hostSubnetLength, clusterSubnetLength int) *ValidationError { + return &ValidationError{ + Type: ErrHostSubnetMask, + Message: fmt.Sprintf("cannot use a host subnet length mask shorter than or equal to the cluster subnet mask. "+ + "host subnet length: %d, cluster subnet length: %d", hostSubnetLength, clusterSubnetLength), + } +} + +func NewInvalidIPv4HostSubnetError() *ValidationError { + return &ValidationError{ + Type: ErrInvalidIPv4HostSubnet, + Message: "invalid host subnet, IPv4 subnet must be < 32", + } +} + +func NewSubnetOverlapError(a, b ConfigSubnet) *ValidationError { + return &ValidationError{ + Type: ErrSubnetOverlap, + Message: fmt.Sprintf("%s %q overlaps %s %q", + a.SubnetType, a.Subnet.String(), + b.SubnetType, b.Subnet.String()), + } +} + +func NewExcludedSubnetNotContainedError(excludeSubnet interface{}) *ValidationError { + return &ValidationError{ + Type: ErrExcludedSubnetNotContained, + Message: fmt.Sprintf("the provided network subnets do not contain excluded subnets %v", excludeSubnet), + } +} + +// Topology Validation Errors +func NewTopologyConfigMismatchError(topology string) *ValidationError { + return &ValidationError{ + Type: ErrTopologyConfigMismatch, + Message: fmt.Sprintf("topology %[1]s is specified but %[1]s config is nil", topology), + } +} + +// IPAM Validation Errors +func NewIPAMLifecycleNotSupportedError() *ValidationError { + return &ValidationError{ + Type: ErrIPAMLifecycleNotSupported, + Message: "lifecycle Persistent is only supported when ipam.mode is Enabled", + } +} + +func NewSubnetsRequiredError() *ValidationError { + return &ValidationError{ + Type: ErrSubnetsRequired, + Message: "subnets is required with ipam.mode is Enabled or unset", + } +} + +func NewSubnetsMustBeUnsetError() *ValidationError { + return &ValidationError{ + Type: ErrSubnetsMustBeUnset, + Message: "subnets must be unset when ipam.mode is Disabled", + } +} diff --git a/go-controller/pkg/config/utils.go b/go-controller/pkg/config/utils.go index f0f0ff1a6b..20f4e0b35c 100644 --- a/go-controller/pkg/config/utils.go +++ b/go-controller/pkg/config/utils.go @@ -62,7 +62,7 @@ func ParseClusterSubnetEntriesWithDefaults(clusterSubnetCmd string, ipv4HostLeng splitClusterEntry := strings.Split(clusterEntry, "/") if len(splitClusterEntry) < 2 || len(splitClusterEntry) > 3 { - return nil, fmt.Errorf("CIDR %q not properly formatted", clusterEntry) + return nil, NewCIDRNotProperlyFormattedError(clusterEntry) } var err error @@ -78,7 +78,7 @@ func ParseClusterSubnetEntriesWithDefaults(clusterSubnetCmd string, ipv4HostLeng entryMaskLength, _ := parsedClusterEntry.CIDR.Mask.Size() if len(splitClusterEntry) == 3 { if !hostLengthAllowed { - return nil, fmt.Errorf("CIDR %q not properly formatted", clusterEntry) + return nil, NewCIDRNotProperlyFormattedError(clusterEntry) } tmp, err := strconv.Atoi(splitClusterEntry[2]) if err != nil { @@ -100,12 +100,11 @@ func ParseClusterSubnetEntriesWithDefaults(clusterSubnetCmd string, ipv4HostLeng } if !ipv6 && parsedClusterEntry.HostSubnetLength > 32 { - return nil, fmt.Errorf("invalid host subnet, IPv4 subnet must be < 32") + return nil, NewInvalidIPv4HostSubnetError() } if parsedClusterEntry.HostSubnetLength <= entryMaskLength { - return nil, fmt.Errorf("cannot use a host subnet length mask shorter than or equal to the cluster subnet mask. "+ - "host subnet length: %d, cluster subnet length: %d", parsedClusterEntry.HostSubnetLength, entryMaskLength) + return nil, NewHostSubnetMaskError(parsedClusterEntry.HostSubnetLength, entryMaskLength) } } @@ -216,9 +215,7 @@ func (cs *ConfigSubnets) CheckForOverlaps() error { for j := 0; j < i; j++ { sj := cs.Subnets[j] if si.Subnet.Contains(sj.Subnet.IP) || sj.Subnet.Contains(si.Subnet.IP) { - return fmt.Errorf("illegal network configuration: %s %q overlaps %s %q", - si.SubnetType, si.Subnet.String(), - sj.SubnetType, sj.Subnet.String()) + return NewSubnetOverlapError(si, sj) } } } diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index fd91edd3be..367531fb6e 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -1000,8 +1000,7 @@ func parseSubnets(subnetsString, excludeSubnetsString, topology string) ([]confi } } if !found { - return nil, nil, fmt.Errorf("the provided network subnets %v do not contain exluded subnets %v", - subnets, excludeSubnet.CIDR) + return nil, nil, config.NewExcludedSubnetNotContainedError(excludeSubnet.CIDR) } excludeIPNets = append(excludeIPNets, excludeSubnet.CIDR) } @@ -1258,7 +1257,7 @@ func subnetOverlapCheck(netconf *ovncnitypes.NetConf) error { } err = allSubnets.CheckForOverlaps() if err != nil { - return fmt.Errorf("pod or join subnet overlaps with already configured internal subnets: %v", err) + return fmt.Errorf("pod or join subnet overlaps with already configured internal subnets: %w", err) } return nil diff --git a/go-controller/pkg/util/multi_network_test.go b/go-controller/pkg/util/multi_network_test.go index daaaf920a5..8bdfcb3be9 100644 --- a/go-controller/pkg/util/multi_network_test.go +++ b/go-controller/pkg/util/multi_network_test.go @@ -1088,8 +1088,9 @@ func TestSubnetOverlapCheck(t *testing.T) { "netAttachDefName": "ns1/nad1" } `, - expectedError: fmt.Errorf("invalid subnet configuration: pod or join subnet overlaps with already configured internal subnets: " + - "illegal network configuration: user defined subnet \"10.129.0.0/16\" overlaps cluster subnet \"10.128.0.0/14\""), + expectedError: config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedSubnets, Subnet: MustParseCIDR("10.129.0.0/16")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetCluster, Subnet: cidr4}), }, { desc: "return error when IPv4 join subnet in net-attach-def overlaps other subnets", @@ -1104,8 +1105,9 @@ func TestSubnetOverlapCheck(t *testing.T) { "netAttachDefName": "ns1/nad1" } `, - expectedError: fmt.Errorf("invalid subnet configuration: pod or join subnet overlaps with already configured internal subnets: " + - "illegal network configuration: user defined join subnet \"100.64.0.0/24\" overlaps built-in join subnet \"100.64.0.0/16\""), + expectedError: config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedJoinSubnet, Subnet: MustParseCIDR("100.64.0.0/24")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetJoin, Subnet: MustParseCIDR(config.Gateway.V4JoinSubnet)}), }, { desc: "return error when IPv6 POD subnet in net-attach-def overlaps other subnets", @@ -1120,8 +1122,10 @@ func TestSubnetOverlapCheck(t *testing.T) { "netAttachDefName": "ns1/nad1" } `, - expectedError: fmt.Errorf("invalid subnet configuration: pod or join subnet overlaps with already configured internal subnets: " + - "illegal network configuration: user defined subnet \"fe01::/24\" overlaps service subnet \"fe01::/16\""), + expectedError: config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedSubnets, Subnet: MustParseCIDR("fe01::/24")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetService, Subnet: svcCidr6}, + ), }, { desc: "return error when IPv6 join subnet in net-attach-def overlaps other subnets", @@ -1136,8 +1140,10 @@ func TestSubnetOverlapCheck(t *testing.T) { "netAttachDefName": "ns1/nad1" } `, - expectedError: fmt.Errorf("invalid subnet configuration: pod or join subnet overlaps with already configured internal subnets: " + - "illegal network configuration: user defined join subnet \"fd69::/112\" overlaps masquerade subnet \"fd69::/125\""), + expectedError: config.NewSubnetOverlapError( + config.ConfigSubnet{SubnetType: config.UserDefinedJoinSubnet, Subnet: MustParseCIDR("fd69::/112")}, + config.ConfigSubnet{SubnetType: config.ConfigSubnetMasquerade, Subnet: MustParseCIDR(config.Gateway.V6MasqueradeSubnet)}, + ), }, { desc: "excluded subnet should not be considered for overlap check", @@ -1177,7 +1183,7 @@ func TestSubnetOverlapCheck(t *testing.T) { }) if test.expectedError != nil { _, err := ParseNADInfo(networkAttachmentDefinition) - g.Expect(err).To(gomega.MatchError(test.expectedError.Error())) + g.Expect(err).To(gomega.MatchError(gomega.ContainSubstring(test.expectedError.Error()))) } else { _, err := ParseNADInfo(networkAttachmentDefinition) g.Expect(err).NotTo(gomega.HaveOccurred()) diff --git a/go-controller/pkg/util/util.go b/go-controller/pkg/util/util.go index cdcface465..4455de04c9 100644 --- a/go-controller/pkg/util/util.go +++ b/go-controller/pkg/util/util.go @@ -671,3 +671,11 @@ func GetMirroredEndpointSlices(controller, sourceName, namespace string, endpoin } return mirroredEndpointSlices, nil } + +func MustParseCIDR(cidr string) *net.IPNet { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + panic(fmt.Sprintf("failed to parse CIDR %q: %v", cidr, err)) + } + return ipNet +} From 410550fbb05be947ca8b5ad44dc73c3d2141d2ac Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Fri, 21 Mar 2025 08:52:58 +0100 Subject: [PATCH 167/278] Remove support for receiving advertised routes on nodes Today when default network or UDN networks are advertised using RAs the nodes also learn the routes of other nodes' pod subnets in the cluster. Example snippet of exposing a UDN network on non-vrflite usecase: root@ovn-worker2:/# ip r show table 1048 default via 172.18.0.1 dev breth0 mtu 1400 10.96.0.0/16 via 169.254.0.4 dev breth0 mtu 1400 10.244.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 10.244.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 103.103.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 103.103.1.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 169.254.0.3 via 203.203.1.1 dev ovn-k8s-mp12 169.254.0.34 dev ovn-k8s-mp12 mtu 1400 172.26.0.0/16 nhid 41 via 172.18.0.5 dev breth0 proto bgp metric 20 203.203.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 203.203.0.0/16 via 203.203.1.1 dev ovn-k8s-mp12 203.203.1.0/24 dev ovn-k8s-mp12 proto kernel scope link src 203.203.1.2 local 203.203.1.2 dev ovn-k8s-mp12 proto kernel scope host src 203.203.1.2 broadcast 203.203.1.255 dev ovn-k8s-mp12 proto kernel scope link src 203.203.1.2 203.203.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 root@ovn-worker2:/# ip r show table 1046 default via 172.18.0.1 dev breth0 mtu 1400 10.96.0.0/16 via 169.254.0.4 dev breth0 mtu 1400 10.244.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 10.244.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 103.103.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 103.103.0.0/16 via 103.103.2.1 dev ovn-k8s-mp11 103.103.1.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 103.103.2.0/24 dev ovn-k8s-mp11 proto kernel scope link src 103.103.2.2 local 103.103.2.2 dev ovn-k8s-mp11 proto kernel scope host src 103.103.2.2 broadcast 103.103.2.255 dev ovn-k8s-mp11 proto kernel scope link src 103.103.2.2 169.254.0.3 via 103.103.2.1 dev ovn-k8s-mp11 169.254.0.32 dev ovn-k8s-mp11 mtu 1400 172.26.0.0/16 nhid 41 via 172.18.0.5 dev breth0 proto bgp metric 20 203.203.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 203.203.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 root@ovn-worker2:/# this happens because we import routes from the default VRF: prefixes: - 103.103.0.0/24 - 2014:100:200::/64 - 2016:100:200::/64 - 203.203.0.0/24 - asn: 64512 imports: - vrf: default vrf: mp11-udn-vrf - asn: 64512 imports: - vrf: default vrf: mp12-udn-vrf nodeSelector: matchLabels: kubernetes.io/hostname: ovn-worker raw: {} root@ovn-worker2:/# ip r default via 172.18.0.1 dev breth0 mtu 1400 10.96.0.0/16 via 169.254.0.4 dev breth0 mtu 1400 10.244.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 10.244.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 103.103.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 103.103.1.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 169.254.0.3 via 203.203.1.1 dev ovn-k8s-mp12 169.254.0.34 dev ovn-k8s-mp12 mtu 1400 172.26.0.0/16 nhid 41 via 172.18.0.5 dev breth0 proto bgp metric 20 203.203.0.0/24 nhid 39 via 172.18.0.4 dev breth0 proto bgp metric 20 203.203.0.0/16 via 203.203.1.1 dev ovn-k8s-mp12 203.203.1.0/24 dev ovn-k8s-mp12 proto kernel scope link src 203.203.1.2 local 203.203.1.2 dev ovn-k8s-mp12 proto kernel scope host src 203.203.1.2 broadcast 203.203.1.255 dev ovn-k8s-mp12 proto kernel scope link src 203.203.1.2 203.203.2.0/24 nhid 40 via 172.18.0.3 dev breth0 proto bgp metric 20 which directly breaks UDN isolation. In this commit we are going to remove the support for receiving routes. So advertising routes will only advertise routes and we will no longer make the nodes receive these routes. However in the future when we support overlay-mode with BGP, we will need to re-add these routes and design a better isolation model with UDNs within the cluster if that is desired. Signed-off-by: Surya Seetharaman --- .../routeadvertisements/controller.go | 36 ++---------- .../routeadvertisements/controller_test.go | 55 ++++--------------- 2 files changed, 17 insertions(+), 74 deletions(-) diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller.go b/go-controller/pkg/clustermanager/routeadvertisements/controller.go index 18fb3dbaae..774a5341f3 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller.go @@ -593,14 +593,13 @@ func (c *Controller) generateFRRConfiguration( matchedNetworks sets.Set[string], ) (*frrtypes.FRRConfiguration, error) { routers := []frrtypes.Router{} - advertisements := sets.New(ra.Spec.Advertisements...) // go over the source routers for i, router := range source.Spec.BGP.Routers { targetVRF := ra.Spec.TargetVRF var matchedVRF, matchedNetwork string - var receivePrefixes, advertisePrefixes []string + var advertisePrefixes []string // We will use the router if: // - the router VRF matches the target VRF @@ -608,33 +607,25 @@ func (c *Controller) generateFRRConfiguration( // Prepare each scenario with a switch statement and check after that switch { case targetVRF == "auto" && router.VRF == "": - // match on default network/VRF, advertise node prefixes and receive - // any prefix of default network. + // match on default network/VRF, advertise node prefixes matchedVRF = "" matchedNetwork = types.DefaultNetworkName advertisePrefixes = selectedNetworks.hostNetworkSubnets[matchedNetwork] - receivePrefixes = selectedNetworks.networkSubnets[matchedNetwork] case targetVRF == "auto": - // match router.VRF to network.VRF, advertise node prefixes and - // receive any prefix of the matched network + // match router.VRF to network.VRF, advertise node prefixes matchedVRF = router.VRF matchedNetwork = selectedNetworks.networkVRFs[matchedVRF] advertisePrefixes = selectedNetworks.hostNetworkSubnets[matchedNetwork] - receivePrefixes = selectedNetworks.networkSubnets[matchedNetwork] case targetVRF == "": - // match on default network/VRF, advertise node prefixes and - // receive any prefix of selected networks + // match on default network/VRF, advertise node prefixes matchedVRF = "" matchedNetwork = types.DefaultNetworkName advertisePrefixes = selectedNetworks.hostSubnets - receivePrefixes = selectedNetworks.subnets default: - // match router.VRF to network.VRF, advertise node prefixes and - // receive any prefix of selected networks + // match router.VRF to network.VRF, advertise node prefixes matchedVRF = targetVRF matchedNetwork = selectedNetworks.networkVRFs[matchedVRF] advertisePrefixes = selectedNetworks.hostSubnets - receivePrefixes = selectedNetworks.subnets } if matchedVRF != router.VRF || len(advertisePrefixes) == 0 { // either this router VRF does not match the target VRF or we don't @@ -669,7 +660,6 @@ func (c *Controller) generateFRRConfiguration( isIPV6 := utilnet.IsIPv6String(neighbor.Address) advertisePrefixes := util.MatchAllIPNetsStringFamily(isIPV6, advertisePrefixes) - receivePrefixes := util.MatchAllIPNetsStringFamily(isIPV6, receivePrefixes) if len(advertisePrefixes) == 0 { continue } @@ -680,22 +670,6 @@ func (c *Controller) generateFRRConfiguration( Prefixes: advertisePrefixes, }, } - neighbor.ToReceive = frrtypes.Receive{ - Allowed: frrtypes.AllowedInPrefixes{ - Mode: frrtypes.AllowRestricted, - }, - } - if advertisements.Has(ratypes.PodNetwork) { - for _, prefix := range receivePrefixes { - neighbor.ToReceive.Allowed.Prefixes = append(neighbor.ToReceive.Allowed.Prefixes, - frrtypes.PrefixSelector{ - Prefix: prefix, - LE: selectedNetworks.prefixLength[prefix], - GE: selectedNetworks.prefixLength[prefix], - }, - ) - } - } targetRouter.Neighbors = append(targetRouter.Neighbors, neighbor) } if len(targetRouter.Neighbors) == 0 { diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go index 305418425c..cf8d58729f 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go @@ -152,7 +152,6 @@ type testNeighbor struct { ASN uint32 Address string DisableMP *bool - Receive []string Advertise []string } @@ -161,11 +160,6 @@ func (tn testNeighbor) Neighbor() frrapi.Neighbor { ASN: tn.ASN, Address: tn.Address, DisableMP: true, - ToReceive: frrapi.Receive{ - Allowed: frrapi.AllowedInPrefixes{ - Mode: frrapi.AllowRestricted, - }, - }, ToAdvertise: frrapi.Advertise{ Allowed: frrapi.AllowedOutPrefixes{ Mode: frrapi.AllowRestricted, @@ -176,31 +170,6 @@ func (tn testNeighbor) Neighbor() frrapi.Neighbor { if tn.DisableMP != nil { n.DisableMP = *tn.DisableMP } - for _, receive := range tn.Receive { - sep := strings.LastIndex(receive, "/") - if sep == -1 { - continue - } - if isLayer2 := strings.Count(receive, "/") == 1; isLayer2 { - n.ToReceive.Allowed.Prefixes = append(n.ToReceive.Allowed.Prefixes, - frrapi.PrefixSelector{ - Prefix: receive, - }, - ) - continue - } - - first := receive[:sep] - last := receive[sep+1:] - len := ovntest.MustAtoi(last) - n.ToReceive.Allowed.Prefixes = append(n.ToReceive.Allowed.Prefixes, - frrapi.PrefixSelector{ - Prefix: first, - GE: uint32(len), - LE: uint32(len), - }, - ) - } return n } @@ -433,7 +402,7 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.0.1.1/32", "1.1.0.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}}, }}, }}, }, @@ -465,8 +434,8 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.0.1.1/32", "1.1.0.0/24", "fd01::/64", "fd03::ffff:100:101/128"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, - {ASN: 1, Address: "fd02::ffff:100:64", Advertise: []string{"fd01::/64", "fd03::ffff:100:101/128"}, Receive: []string{"fd01::/48/64"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}}, + {ASN: 1, Address: "fd02::ffff:100:64", Advertise: []string{"fd01::/64", "fd03::ffff:100:101/128"}}, }}, }}, }, @@ -503,7 +472,7 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.2.0.0/24", "1.3.0.0/24", "1.4.0.0/16", "1.5.0.0/16"}, Imports: []string{"black", "blue", "green", "red"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.0.0/24", "1.3.0.0/24", "1.4.0.0/16", "1.5.0.0/16"}, Receive: []string{"1.2.0.0/16/24", "1.3.0.0/16/24", "1.4.0.0/16", "1.5.0.0/16"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.0.0/24", "1.3.0.0/24", "1.4.0.0/16", "1.5.0.0/16"}}, }}, {ASN: 1, VRF: "black", Imports: []string{"default"}}, {ASN: 1, VRF: "blue", Imports: []string{"default"}}, @@ -636,7 +605,7 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.0.1.1/32", "1.1.0.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}}, }}, }, }, @@ -744,13 +713,13 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.1.1.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.1.1.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.1.1.0/24"}}, }}, {ASN: 1, VRF: "red", Prefixes: []string{"1.2.1.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.1.0/24"}, Receive: []string{"1.2.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.1.0/24"}}, }}, {ASN: 1, VRF: "green", Prefixes: []string{"1.4.0.0/16"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.4.0.0/16"}, Receive: []string{"1.4.0.0/16"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.4.0.0/16"}}, }}, }, }, @@ -760,7 +729,7 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.1.2.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.1.2.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.1.2.0/24"}}, }}, }, }, @@ -770,10 +739,10 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, Routers: []*testRouter{ {ASN: 1, VRF: "red", Prefixes: []string{"1.2.2.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.2.0/24"}, Receive: []string{"1.2.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.2.2.0/24"}}, }}, {ASN: 1, VRF: "green", Prefixes: []string{"1.4.0.0/16"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.4.0.0/16"}, Receive: []string{"1.4.0.0/16"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.4.0.0/16"}}, }}, }, }, @@ -799,7 +768,7 @@ func TestController_reconcile(t *testing.T) { NodeSelector: map[string]string{"kubernetes.io/hostname": "node"}, Routers: []*testRouter{ {ASN: 1, Prefixes: []string{"1.0.1.1/32", "1.1.0.0/24"}, Neighbors: []*testNeighbor{ - {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}, Receive: []string{"1.1.0.0/16/24"}}, + {ASN: 1, Address: "1.0.0.100", Advertise: []string{"1.0.1.1/32", "1.1.0.0/24"}}, }}, }, }, From ea1b6a018e6d537ac3460474ce7e517fddd312dd Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Tue, 22 Jul 2025 14:14:07 +0200 Subject: [PATCH 168/278] Don't use match as a criteria for isEquivalentMatch This is a temporary commit - we need a proper followup. Please see https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5407 for details. As of today all NATs created by OVN-Kubernetes are unique using the existing 5 tuple algo in IsEquivalentNAT - uuid, type of snat, logicalIP, logicalPort, externalIP, externalIDs. So its OK to get rid of match. But its not the correct way to fix this - in future we might have two NATs with all other fields except match being the same. Signed-off-by: Surya Seetharaman --- go-controller/pkg/libovsdb/ops/router.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go index 18b3931a1f..27e8e38d48 100644 --- a/go-controller/pkg/libovsdb/ops/router.go +++ b/go-controller/pkg/libovsdb/ops/router.go @@ -1035,7 +1035,7 @@ func BuildDNATAndSNATWithMatch( // isEquivalentNAT checks if the `searched` NAT is equivalent to `existing`. // Returns true if the UUID is set in `searched` and matches the UUID of `existing`. // Otherwise, perform the following checks: -// - Compare the Type and Match fields. +// - Compare the Type. // - Compare ExternalIP if it is set in `searched`. // - Compare LogicalIP if the Type in `searched` is SNAT. // - Compare LogicalPort if it is set in `searched`. @@ -1050,10 +1050,6 @@ func isEquivalentNAT(existing *nbdb.NAT, searched *nbdb.NAT) bool { return false } - if searched.Match != existing.Match { - return false - } - // Compare externalIP if it's not empty. if searched.ExternalIP != "" && searched.ExternalIP != existing.ExternalIP { return false From 15adf65d17ffa28a64fce27bbc73491b0800330c Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Thu, 19 Jun 2025 19:12:30 +0200 Subject: [PATCH 169/278] Advertised networks: SNAT Traffic to nodeIP This PR is adding SNAT for advertised UDNs and CDN if the destination of the traffic is towards other nodes in the cluster. This is a design change for BGP from before (where pod->node was not SNATed and podIP was preserved). For normal UDNs we have 2 SNATs: L3 UDN SNATs: 1) this cSNAT is added to ovn_cluster_router for LGW egress traffic and SGW KAPI/DNS traffic: _uuid : 5485a25f-7a83-4dc0-840c-bbfbd0784aad allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-green-network, "k8s.ovn.org/topology"=layer3} external_ip : "169.254.0.38" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "203.203.0.0/24" logical_port : rtos-cluster_udn_tenant.green.network_ovn-control-plane match : "eth.dst == 0a:58:cb:cb:00:02" options : {stateless="false"} priority : 0 type : snat 2) this SNAT is added to GR for SGW egress traffic: _uuid : d85fd65f-e3f3-4d52-95f9-5f88c925aa5a allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-green-network, "k8s.ovn.org/topology"=layer3} external_ip : "169.254.0.37" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "203.203.0.0/16" logical_port : [] match : "" options : {stateless="false"} priority : 0 type : snat for L2, we have the following two SNATs both on GR: _uuid : a4b9942f-ec1a-42ca-81d9-3e4885ff2470 allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-blue-network, "k8s.ovn.org/topology"=layer2} external_ip : "169.254.0.36" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "93.93.0.0/16" logical_port : rtoj-GR_cluster_udn_tenant.blue.network_ovn-control-plane match : "eth.dst == 0a:58:5d:5d:00:02" options : {stateless="false"} priority : 0 type : snat and _uuid : 24164866-da95-4b6f-9c65-8b16fa202758 allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-blue-network, "k8s.ovn.org/topology"=layer2} external_ip : "169.254.0.35" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "93.93.0.0/16" logical_port : [] match : "outport == \"rtoe-GR_cluster_udn_tenant.blue.network_ovn-control-plane\"" options : {stateless="false"} priority : 0 type : snat now with advertised networks these will change to: _uuid : a4b9942f-ec1a-42ca-81d9-3e4885ff2470 allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-blue-network, "k8s.ovn.org/topology"=layer2} external_ip : "169.254.0.36" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "93.93.0.0/16" logical_port : rtoj-GR_cluster_udn_tenant.blue.network_ovn-control-plane match : "eth.dst == 0a:58:5d:5d:00:02 && (ip4.dst == $a712973235162149816)" options : {stateless="false"} priority : 0 type : snat _uuid : 24164866-da95-4b6f-9c65-8b16fa202758 allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-blue-network, "k8s.ovn.org/topology"=layer2} external_ip : "169.254.0.35" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "93.93.0.0/16" logical_port : [] match : "outport == \"rtoe-GR_cluster_udn_tenant.blue.network_ovn-control-plane\" && ip4.dst == $a712973235162149816" options : {stateless="false"} priority : 0 type : snat _uuid : d85fd65f-e3f3-4d52-95f9-5f88c925aa5a allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-green-network, "k8s.ovn.org/topology"=layer3} external_ip : "169.254.0.37" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "203.203.0.0/16" logical_port : [] match : "ip4.dst == $a712973235162149816" options : {stateless="false"} priority : 0 type : snat _uuid : 5485a25f-7a83-4dc0-840c-bbfbd0784aad allowed_ext_ips : [] exempted_ext_ips : [] external_ids : {"k8s.ovn.org/network"=cluster_udn_tenant-green-network, "k8s.ovn.org/topology"=layer3} external_ip : "169.254.0.38" external_mac : [] external_port_range : "32768-60999" gateway_port : [] logical_ip : "203.203.0.0/24" logical_port : rtos-cluster_udn_tenant.green.network_ovn-control-plane match : "eth.dst == 0a:58:cb:cb:00:02 && (ip4.dst == $a712973235162149816)" options : {stateless="false"} priority : 0 type : snat so basically we add this extra match for destination IPs to SNAT to masqueradeIP for that UDN note: with this PR we will break hardware offload for assymmetry traffix for BGP L2 As for the CDN, we have 1 SNAT with no matches on GR and that is being changed to now have a cSNAT in case the default network is advertised. NOTE: In -ds flag mode, the per-pod SNAT will have this match set. NOTE2: For all deleteNAT scenarios we purposefully don't pass snat as a match criteria Signed-off-by: Surya Seetharaman Signed-off-by: Surya Seetharaman --- .../ovn/base_network_controller_secondary.go | 46 ++++++++--- go-controller/pkg/ovn/egressgw.go | 12 +-- go-controller/pkg/ovn/egressip.go | 22 ++++-- go-controller/pkg/ovn/gateway.go | 51 +++++++++++- go-controller/pkg/ovn/gateway_test.go | 32 ++++++++ go-controller/pkg/ovn/master_test.go | 42 +++++++--- go-controller/pkg/ovn/namespace.go | 40 +++++++++- go-controller/pkg/ovn/pods.go | 20 ++++- .../secondary_layer2_network_controller.go | 77 ++++++++----------- .../secondary_layer3_network_controller.go | 44 +++-------- go-controller/pkg/util/multi_network.go | 12 --- 11 files changed, 269 insertions(+), 129 deletions(-) diff --git a/go-controller/pkg/ovn/base_network_controller_secondary.go b/go-controller/pkg/ovn/base_network_controller_secondary.go index f9c6d0b18f..253524ff87 100644 --- a/go-controller/pkg/ovn/base_network_controller_secondary.go +++ b/go-controller/pkg/ovn/base_network_controller_secondary.go @@ -812,7 +812,7 @@ func (oc *BaseSecondaryNetworkController) allowPersistentIPs() bool { // buildUDNEgressSNAT is used to build the conditional SNAT required on L3 and L2 UDNs to // steer traffic correctly via mp0 when leaving OVN to the host -func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets []*net.IPNet, outputPort string) ([]*nbdb.NAT, error) { +func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets []*net.IPNet, outputPort string, isUDNAdvertised bool) ([]*nbdb.NAT, error) { if len(localPodSubnets) == 0 { return nil, nil // nothing to do } @@ -828,10 +828,11 @@ func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets [ types.TopologyExternalID: bsnc.TopologyType(), } for _, localPodSubnet := range localPodSubnets { + ipFamily := utilnet.IPv4 + masqIP, err = udn.AllocateV4MasqueradeIPs(networkID) if utilnet.IsIPv6CIDR(localPodSubnet) { masqIP, err = udn.AllocateV6MasqueradeIPs(networkID) - } else { - masqIP, err = udn.AllocateV4MasqueradeIPs(networkID) + ipFamily = utilnet.IPv6 } if err != nil { return nil, err @@ -839,12 +840,43 @@ func (bsnc *BaseSecondaryNetworkController) buildUDNEgressSNAT(localPodSubnets [ if masqIP == nil { return nil, fmt.Errorf("masquerade IP cannot be empty network %s (%d): %v", bsnc.GetNetworkName(), networkID, err) } - snats = append(snats, libovsdbops.BuildSNATWithMatch(&masqIP.ManagementPort.IP, localPodSubnet, outputPort, - extIDs, getMasqueradeManagementIPSNATMatch(dstMac.String()))) + if !isUDNAdvertised { + snats = append(snats, libovsdbops.BuildSNATWithMatch(&masqIP.ManagementPort.IP, localPodSubnet, outputPort, + extIDs, getMasqueradeManagementIPSNATMatch(dstMac.String()))) + } else { + // For advertised networks, we need to SNAT any traffic leaving the pods from these networks towards the node IPs + // in the cluster. In order to do such a conditional SNAT, we need an address set that contains the node IPs in the cluster. + // Given that egressIP feature already has an address set containing these nodeIPs owned by the default network controller, let's re-use it. + dbIDs := getEgressIPAddrSetDbIDs(NodeIPAddrSetName, types.DefaultNetworkName, DefaultNetworkControllerName) + addrSet, err := bsnc.addressSetFactory.GetAddressSet(dbIDs) + if err != nil { + return nil, fmt.Errorf("cannot ensure that addressSet %s exists: %w", NodeIPAddrSetName, err) + } + ipv4ClusterNodeIPAS, ipv6ClusterNodeIPAS := addrSet.GetASHashNames() + + snats = append(snats, libovsdbops.BuildSNATWithMatch(&masqIP.ManagementPort.IP, localPodSubnet, outputPort, + extIDs, fmt.Sprintf("%s && (%s)", getMasqueradeManagementIPSNATMatch(dstMac.String()), + getClusterNodesDestinationBasedSNATMatch(ipv4ClusterNodeIPAS, ipv6ClusterNodeIPAS, ipFamily)))) + } } return snats, nil } +func getMasqueradeManagementIPSNATMatch(dstMac string) string { + return fmt.Sprintf("eth.dst == %s", dstMac) +} + +// getClusterNodesDestinationBasedSNATMatch creates destination-based SNAT match for the specified IP family +func getClusterNodesDestinationBasedSNATMatch(ipv4ClusterNodeIPAS, ipv6ClusterNodeIPAS string, ipFamily utilnet.IPFamily) string { + var match string + if ipFamily == utilnet.IPv4 { + match = fmt.Sprintf("ip4.dst == $%s", ipv4ClusterNodeIPAS) + } else { + match = fmt.Sprintf("ip6.dst == $%s", ipv6ClusterNodeIPAS) + } + return match +} + func (bsnc *BaseSecondaryNetworkController) ensureDHCP(pod *corev1.Pod, podAnnotation *util.PodAnnotation, lsp *nbdb.LogicalSwitchPort) error { opts := []kubevirt.DHCPConfigsOpt{} @@ -867,10 +899,6 @@ func (bsnc *BaseSecondaryNetworkController) ensureDHCP(pod *corev1.Pod, podAnnot return kubevirt.EnsureDHCPOptionsForLSP(bsnc.controllerName, bsnc.nbClient, pod, podAnnotation.IPs, lsp, opts...) } -func getMasqueradeManagementIPSNATMatch(dstMac string) string { - return fmt.Sprintf("eth.dst == %s", dstMac) -} - func (bsnc *BaseSecondaryNetworkController) requireDHCP(pod *corev1.Pod) bool { // Configure DHCP only for kubevirt VMs layer2 primary udn with subnets return kubevirt.IsPodOwnedByVirtualMachine(pod) && diff --git a/go-controller/pkg/ovn/egressgw.go b/go-controller/pkg/ovn/egressgw.go index b607a3b253..d9d8610aba 100644 --- a/go-controller/pkg/ovn/egressgw.go +++ b/go-controller/pkg/ovn/egressgw.go @@ -589,7 +589,7 @@ func (oc *DefaultNetworkController) deletePodSNAT(nodeName string, extIPs, podIP return nil } // Default network does not set any matches in Pod SNAT - ops, err := deletePodSNATOps(oc.nbClient, nil, oc.GetNetworkScopedGWRouterName(nodeName), extIPs, podIPNets, "") + ops, err := deletePodSNATOps(oc.nbClient, nil, oc.GetNetworkScopedGWRouterName(nodeName), extIPs, podIPNets) if err != nil { return err } @@ -639,8 +639,8 @@ func getExternalIPsGR(watchFactory *factory.WatchFactory, nodeName string) ([]*n // deletePodSNATOps creates ovsdb operation that removes per pod SNAT rules towards the nodeIP that are applied to the GR where the pod resides // used when disableSNATMultipleGWs=true -func deletePodSNATOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, gwRouterName string, extIPs, podIPNets []*net.IPNet, match string) ([]ovsdb.Operation, error) { - nats, err := buildPodSNAT(extIPs, podIPNets, match) +func deletePodSNATOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, gwRouterName string, extIPs, podIPNets []*net.IPNet) ([]ovsdb.Operation, error) { + nats, err := buildPodSNAT(extIPs, podIPNets, "") // for delete, match is not needed - we try to cleanup all the SNATs that match the isEquivalentNAT predicate if err != nil { return nil, err } @@ -657,7 +657,7 @@ func deletePodSNATOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, gwR // addOrUpdatePodSNAT adds or updates per pod SNAT rules towards the nodeIP that are applied to the GR where the pod resides // used when disableSNATMultipleGWs=true func addOrUpdatePodSNAT(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet) error { - ops, err := addOrUpdatePodSNATOps(nbClient, gwRouterName, extIPs, podIfAddrs, nil) + ops, err := addOrUpdatePodSNATOps(nbClient, gwRouterName, extIPs, podIfAddrs, "", nil) if err != nil { return err } @@ -670,9 +670,9 @@ func addOrUpdatePodSNAT(nbClient libovsdbclient.Client, gwRouterName string, ext // addOrUpdatePodSNATOps returns the operation that adds or updates per pod SNAT rules towards the nodeIP that are // applied to the GR where the pod resides // used when disableSNATMultipleGWs=true -func addOrUpdatePodSNATOps(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet, ops []ovsdb.Operation) ([]ovsdb.Operation, error) { +func addOrUpdatePodSNATOps(nbClient libovsdbclient.Client, gwRouterName string, extIPs, podIfAddrs []*net.IPNet, snatMatch string, ops []ovsdb.Operation) ([]ovsdb.Operation, error) { gwRouter := &nbdb.LogicalRouter{Name: gwRouterName} - nats, err := buildPodSNAT(extIPs, podIfAddrs, "") + nats, err := buildPodSNAT(extIPs, podIfAddrs, snatMatch) if err != nil { return nil, err } diff --git a/go-controller/pkg/ovn/egressip.go b/go-controller/pkg/ovn/egressip.go index 5f50cefb95..ed018c0de3 100644 --- a/go-controller/pkg/ovn/egressip.go +++ b/go-controller/pkg/ovn/egressip.go @@ -249,7 +249,7 @@ func NewEIPController( // CASE 3.4: Both Namespace && Pod Selectors on Spec changed // } // -// NOTE: `Spec.EgressIPsā€œ updates for EIP object are not processed here, that is the job of cluster manager +// NOTE: `Spec.EgressIPs" updates for EIP object are not processed here, that is the job of cluster manager // // We only care about `Spec.NamespaceSelector`, `Spec.PodSelector` and `Status` field func (e *EgressIPController) reconcileEgressIP(old, new *egressipv1.EgressIP) (err error) { @@ -2594,9 +2594,21 @@ func (e *EgressIPController) addExternalGWPodSNATOps(ni util.NetInfo, ops []ovsd if err != nil { return nil, err } - ops, err = addOrUpdatePodSNATOps(e.nbClient, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podIPs, ops) - if err != nil { - return nil, err + + // Handle each pod IP individually since each IP family needs its own SNAT match + for _, podIP := range podIPs { + ipFamily := utilnet.IPv4 + if utilnet.IsIPv6CIDR(podIP) { + ipFamily = utilnet.IPv6 + } + snatMatch, err := GetNetworkScopedClusterSubnetSNATMatch(e.nbClient, ni, pod.Spec.NodeName, util.IsPodNetworkAdvertisedAtNode(ni, pod.Spec.NodeName), ipFamily) + if err != nil { + return nil, fmt.Errorf("failed to get SNAT match for node %s for network %s: %w", pod.Spec.NodeName, ni.GetNetworkName(), err) + } + ops, err = addOrUpdatePodSNATOps(e.nbClient, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, []*net.IPNet{podIP}, snatMatch, ops) + if err != nil { + return nil, err + } } klog.V(5).Infof("Adding SNAT on %s since egress node managing %s/%s was the same: %s", pod.Spec.NodeName, pod.Namespace, pod.Name, status.Node) } @@ -2617,7 +2629,7 @@ func (e *EgressIPController) deleteExternalGWPodSNATOps(ni util.NetInfo, ops []o if err != nil { return nil, err } - ops, err = deletePodSNATOps(e.nbClient, ops, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, affectedIPs, "") + ops, err = deletePodSNATOps(e.nbClient, ops, ni.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, affectedIPs) if err != nil { return nil, err } diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index a43adf5368..66bce44dfe 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -25,6 +25,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node" + addressset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/address_set" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/gateway" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/gatewayrouter" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -42,7 +43,6 @@ type GatewayManager struct { nbClient libovsdbclient.Client netInfo util.NetInfo watchFactory *factory.WatchFactory - // Cluster wide Load_Balancer_Group UUID. // Includes all node switches and node gateway routers. clusterLoadBalancerGroupUUID string @@ -764,7 +764,9 @@ func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []* nats := make([]*nbdb.NAT, 0, len(clusterIPSubnet)) var nat *nbdb.NAT - if (!config.Gateway.DisableSNATMultipleGWs || gw.netInfo.IsPrimaryNetwork()) && !gw.isRoutingAdvertised(nodeName) { + // DisableSNATMultipleGWs is only applicable to cluster default network and not to user defined networks. + // For user defined networks, we always add SNAT rules regardless of whether the network is advertised or not. + if !config.Gateway.DisableSNATMultipleGWs || gw.netInfo.IsPrimaryNetwork() { // Default SNAT rules. DisableSNATMultipleGWs=false in LGW (traffic egresses via mp0) always. // We are not checking for gateway mode to be shared explicitly to reduce topology differences. for _, entry := range clusterIPSubnet { @@ -774,7 +776,17 @@ func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []* gw.gwRouterName, err) } - nat = libovsdbops.BuildSNATWithMatch(&externalIP[0], entry, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) + // Get the match for this specific subnet's IP family + ipFamily := utilnet.IPv4 + if utilnet.IsIPv6CIDR(entry) { + ipFamily = utilnet.IPv6 + } + snatMatch, err := GetNetworkScopedClusterSubnetSNATMatch(gw.nbClient, gw.netInfo, nodeName, gw.isRoutingAdvertised(nodeName), ipFamily) + if err != nil { + return fmt.Errorf("failed to get SNAT match for node %s for network %s: %w", nodeName, gw.netInfo.GetNetworkName(), err) + } + + nat = libovsdbops.BuildSNATWithMatch(&externalIP[0], entry, "", extIDs, snatMatch) nats = append(nats, nat) } err = libovsdbops.CreateOrUpdateNATs(gw.nbClient, gwRouter, nats...) @@ -784,7 +796,7 @@ func (gw *GatewayManager) updateGWRouterNAT(nodeName string, clusterIPSubnet []* } else { // ensure we do not have any leftover SNAT entries after an upgrade for _, logicalSubnet := range clusterIPSubnet { - nat = libovsdbops.BuildSNATWithMatch(nil, logicalSubnet, "", extIDs, gw.netInfo.GetNetworkScopedClusterSubnetSNATMatch(nodeName)) + nat = libovsdbops.BuildSNAT(nil, logicalSubnet, "", extIDs) nats = append(nats, nat) } err = libovsdbops.DeleteNATs(gw.nbClient, gwRouter, nats...) @@ -902,6 +914,37 @@ func (gw *GatewayManager) gatewayInit( return nil } +// GetNetworkScopedClusterSubnetSNATMatch returns the match for the SNAT rule for the cluster default network +// and the match for the SNAT rule for the L3/L2 user defined network. +// If the network is not advertised: +// - For Layer2 topology, the match is the output port of the GR to the join switch since in L2 there is only 1 router but two cSNATs. +// - For Layer3 topology, the match is empty. +// If the network is advertised: +// - For Layer2 topology, the match is the output port of the GR to the join switch and the destination must be a nodeIP in the cluster. +// - For Layer3 topology, the match is the destination must be a nodeIP in the cluster. +func GetNetworkScopedClusterSubnetSNATMatch(nbClient libovsdbclient.Client, netInfo util.NetInfo, nodeName string, isNetworkAdvertised bool, ipFamily utilnet.IPFamily) (string, error) { + if !isNetworkAdvertised { + if netInfo.TopologyType() != types.Layer2Topology { + return "", nil + } + return fmt.Sprintf("outport == %q", types.GWRouterToExtSwitchPrefix+netInfo.GetNetworkScopedGWRouterName(nodeName)), nil + } else { + // if the network is advertised, we need to ensure that the SNAT exists with the correct conditional destination match + dbIDs := getEgressIPAddrSetDbIDs(NodeIPAddrSetName, types.DefaultNetworkName, DefaultNetworkControllerName) + addressSetFactory := addressset.NewOvnAddressSetFactory(nbClient, config.IPv4Mode, config.IPv6Mode) + addrSet, err := addressSetFactory.GetAddressSet(dbIDs) + if err != nil { + return "", fmt.Errorf("cannot ensure that addressSet %s exists %v", NodeIPAddrSetName, err) + } + ipv4ClusterNodeIPAS, ipv6ClusterNodeIPAS := addrSet.GetASHashNames() + destinationMatch := getClusterNodesDestinationBasedSNATMatch(ipv4ClusterNodeIPAS, ipv6ClusterNodeIPAS, ipFamily) + if netInfo.TopologyType() != types.Layer2Topology { + return destinationMatch, nil + } + return fmt.Sprintf("outport == %q && (%s)", types.GWRouterToExtSwitchPrefix+netInfo.GetNetworkScopedGWRouterName(nodeName), destinationMatch), nil + } +} + // addExternalSwitch creates a switch connected to the external bridge and connects it to // the gateway router func (gw *GatewayManager) addExternalSwitch(prefix, interfaceID, gatewayRouter, macAddress, physNetworkName string, ipAddresses []*net.IPNet, vlanID *uint) error { diff --git a/go-controller/pkg/ovn/gateway_test.go b/go-controller/pkg/ovn/gateway_test.go index 61f89e831d..893d17ad09 100644 --- a/go-controller/pkg/ovn/gateway_test.go +++ b/go-controller/pkg/ovn/gateway_test.go @@ -65,6 +65,15 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN expectedNodeSwitch *nbdb.LogicalSwitch, nodeName string, clusterIPSubnets []*net.IPNet, hostSubnets []*net.IPNet, l3GatewayConfig *util.L3GatewayConfig, joinLRPIPs, defLRPIPs []*net.IPNet, skipSnat bool, nodeMgmtPortIP, gatewayMTU string) []libovsdbtest.TestData { + return generateGatewayInitExpectedNBWithPodNetworkAdvertised(testData, expectedOVNClusterRouter, expectedNodeSwitch, + nodeName, clusterIPSubnets, hostSubnets, l3GatewayConfig, joinLRPIPs, defLRPIPs, skipSnat, nodeMgmtPortIP, + gatewayMTU, false) // Default to no pod network advertised +} + +func generateGatewayInitExpectedNBWithPodNetworkAdvertised(testData []libovsdbtest.TestData, expectedOVNClusterRouter *nbdb.LogicalRouter, + expectedNodeSwitch *nbdb.LogicalSwitch, nodeName string, clusterIPSubnets []*net.IPNet, hostSubnets []*net.IPNet, + l3GatewayConfig *util.L3GatewayConfig, joinLRPIPs, defLRPIPs []*net.IPNet, skipSnat bool, nodeMgmtPortIP, + gatewayMTU string, isPodNetworkAdvertised bool) []libovsdbtest.TestData { GRName := "GR_" + nodeName gwSwitchPort := types.JoinSwitchToGWRouterPrefix + GRName @@ -214,6 +223,16 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN }, Networks: networks, }) + var egressNodeIPsASv4, egressNodeIPsASv6 *nbdb.AddressSet + if config.OVNKubernetesFeature.EnableEgressIP { + egressNodeIPsASv4, egressNodeIPsASv6 = buildEgressIPNodeAddressSets(physicalIPs) + if config.IPv4Mode { + testData = append(testData, egressNodeIPsASv4) + } + if config.IPv6Mode { + testData = append(testData, egressNodeIPsASv6) + } + } natUUIDs := make([]string, 0, len(clusterIPSubnets)) if !skipSnat { @@ -231,6 +250,19 @@ func generateGatewayInitExpectedNB(testData []libovsdbtest.TestData, expectedOVN if config.Gateway.Mode != config.GatewayModeDisabled { nat.ExternalPortRange = config.DefaultEphemeralPortRange } + if isPodNetworkAdvertised { + // IPv6 pod network + if utilnet.IsIPv6CIDR(subnet) { + if egressNodeIPsASv6 != nil { + nat.Match = fmt.Sprintf("ip6.dst == $%s", egressNodeIPsASv6.Name) + } + } else { + // IPv4 pod network + if egressNodeIPsASv4 != nil { + nat.Match = fmt.Sprintf("ip4.dst == $%s", egressNodeIPsASv4.Name) + } + } + } testData = append(testData, &nat) } } diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index b7b902f740..866b6309fa 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -963,6 +963,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { // Restore global default values before each testcase gomega.Expect(config.PrepareTestConfig()).To(gomega.Succeed()) fakeOvn = NewFakeOVN(true) + config.OVNKubernetesFeature.EnableEgressIP = true app = cli.NewApp() app.Name = "test" @@ -1043,6 +1044,19 @@ var _ = ginkgo.Describe("Default network controller operations", func() { l3GatewayConfig = node1.gatewayConfig(config.GatewayModeLocal, uint(vlanID)) err = util.SetL3GatewayConfig(nodeAnnotator, l3GatewayConfig) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + if config.OVNKubernetesFeature.EnableEgressIP { + physicalIPs := []string{} + for _, ip := range l3GatewayConfig.IPAddresses { + physicalIPs = append(physicalIPs, ip.IP.String()) + } + egressNodeIPsASv4, egressNodeIPsASv6 := buildEgressIPNodeAddressSets(physicalIPs) + if config.IPv4Mode { + dbSetup.NBData = append(dbSetup.NBData, egressNodeIPsASv4) + } + if config.IPv6Mode { + dbSetup.NBData = append(dbSetup.NBData, egressNodeIPsASv6) + } + } err = util.UpdateNodeManagementPortMACAddresses(&testNode, nodeAnnotator, ovntest.MustParseMAC(node1.NodeMgmtPortMAC), types.DefaultNetworkName) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1249,6 +1263,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { "reconciles pod network SNATs from syncGateway", func(condition func(*DefaultNetworkController) error, expectedExtraNATs ...*nbdb.NAT) { app.Action = func(ctx *cli.Context) error { + // Initialize config from CLI flags (including --init-gateways) _, err := config.InitConfig(ctx, nil, nil) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -1258,6 +1273,10 @@ var _ = ginkgo.Describe("Default network controller operations", func() { _, err = fakeClient.KubeClient.CoreV1().Pods(ns.Name).Create(context.TODO(), &pod, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // generate specific test conditions (after base config is set) + err = condition(oc) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + // Let the real code run and ensure OVN database sync gomega.Expect(oc.WatchNodes()).To(gomega.Succeed()) @@ -1268,10 +1287,6 @@ var _ = ginkgo.Describe("Default network controller operations", func() { err = libovsdbops.CreateOrUpdateNATs(nbClient, GR, extraNats...) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - // generate specific test conditions - err = condition(oc) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - // ensure the stale SNAT's are cleaned up gomega.Expect(oc.StartServiceController(wg, false)).To(gomega.Succeed()) subnet := ovntest.MustParseIPNet(node1.NodeSubnet) @@ -1281,19 +1296,23 @@ var _ = ginkgo.Describe("Default network controller operations", func() { err = oc.syncNodeGateway(testNode) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - skipSnat := config.Gateway.DisableSNATMultipleGWs || oc.isPodNetworkAdvertisedAtNode(node1.Name) + skipSnat := config.Gateway.DisableSNATMultipleGWs && !oc.GetNetInfo().IsPrimaryNetwork() var clusterSubnets []*net.IPNet for _, clusterSubnet := range config.Default.ClusterSubnets { clusterSubnets = append(clusterSubnets, clusterSubnet.CIDR) } expectedNBDatabaseState = addNodeLogicalFlowsWithServiceController(nil, expectedOVNClusterRouter, expectedNodeSwitch, expectedClusterRouterPortGroup, expectedClusterPortGroup, &node1, oc.svcTemplateSupport) - expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, - expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, - []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, - skipSnat, node1.NodeMgmtPortIP, "1400") - - if oc.isPodNetworkAdvertisedAtNode(node1.Name) { + if !oc.isPodNetworkAdvertisedAtNode(node1.Name) { + expectedNBDatabaseState = generateGatewayInitExpectedNB(expectedNBDatabaseState, expectedOVNClusterRouter, + expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, + []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, + skipSnat, node1.NodeMgmtPortIP, "1400") + } else { + expectedNBDatabaseState = generateGatewayInitExpectedNBWithPodNetworkAdvertised(expectedNBDatabaseState, expectedOVNClusterRouter, + expectedNodeSwitch, node1.Name, clusterSubnets, []*net.IPNet{subnet}, l3GatewayConfig, + []*net.IPNet{classBIPAddress(node1.LrpIP)}, []*net.IPNet{classBIPAddress(node1.DrLrpIP)}, + skipSnat, node1.NodeMgmtPortIP, "1400", true) addrSet, err := oc.addressSetFactory.GetAddressSet(GetAdvertisedNetworkSubnetsAddressSetDBIDs()) gomega.Expect(err).NotTo(gomega.HaveOccurred()) expectedNBDatabaseState = generateAdvertisedUDNIsolationExpectedNB(expectedNBDatabaseState, oc.GetNetworkName(), oc.GetNetworkID(), clusterSubnets, expectedNodeSwitch, addrSet) @@ -1353,6 +1372,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { "When pod network is advertised and DisableSNATMultipleGWs is false", func(oc *DefaultNetworkController) error { config.Gateway.DisableSNATMultipleGWs = false + config.OVNKubernetesFeature.EnableEgressIP = true mutableNetInfo := util.NewMutableNetInfo(oc.GetNetInfo()) mutableNetInfo.SetPodNetworkAdvertisedVRFs(map[string][]string{"node1": {"vrf"}}) return oc.Reconcile(mutableNetInfo) diff --git a/go-controller/pkg/ovn/namespace.go b/go-controller/pkg/ovn/namespace.go index 01f189228b..07282de4df 100644 --- a/go-controller/pkg/ovn/namespace.go +++ b/go-controller/pkg/ovn/namespace.go @@ -8,10 +8,12 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" + utilnet "k8s.io/utils/net" "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" @@ -234,9 +236,41 @@ func (oc *DefaultNetworkController) updateNamespace(old, newer *corev1.Namespace if err != nil { errors = append(errors, err) } else { - if extIPs, err := getExternalIPsGR(oc.watchFactory, pod.Spec.NodeName); err != nil { - errors = append(errors, err) - } else if err = addOrUpdatePodSNAT(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podAnnotation.IPs); err != nil { + // Helper function to handle the complex SNAT operations + handleSNATOps := func() error { + extIPs, err := getExternalIPsGR(oc.watchFactory, pod.Spec.NodeName) + if err != nil { + return err + } + + var ops []ovsdb.Operation + // Handle each pod IP individually since each IP family needs its own SNAT match + for _, podIP := range podAnnotation.IPs { + ipFamily := utilnet.IPv4 + if utilnet.IsIPv6CIDR(podIP) { + ipFamily = utilnet.IPv6 + } + snatMatch, err := GetNetworkScopedClusterSubnetSNATMatch(oc.nbClient, oc.GetNetInfo(), pod.Spec.NodeName, oc.isPodNetworkAdvertisedAtNode(pod.Spec.NodeName), ipFamily) + if err != nil { + return fmt.Errorf("failed to get SNAT match for node %s for network %s: %v", pod.Spec.NodeName, oc.GetNetworkName(), err) + } + ops, err = addOrUpdatePodSNATOps(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, []*net.IPNet{podIP}, snatMatch, ops) + if err != nil { + return err + } + } + + // Execute all operations in a single transaction + if len(ops) > 0 { + _, err = libovsdbops.TransactAndCheck(oc.nbClient, ops) + if err != nil { + return fmt.Errorf("failed to update SNAT for pod %s on router %s: %v", pod.Name, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), err) + } + } + return nil + } + + if err := handleSNATOps(); err != nil { errors = append(errors, err) } } diff --git a/go-controller/pkg/ovn/pods.go b/go-controller/pkg/ovn/pods.go index 5c3478f3cb..0ad9442e3e 100644 --- a/go-controller/pkg/ovn/pods.go +++ b/go-controller/pkg/ovn/pods.go @@ -12,6 +12,7 @@ import ( corev1 "k8s.io/api/core/v1" ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" + utilnet "k8s.io/utils/net" "github.com/ovn-kubernetes/libovsdb/ovsdb" @@ -310,13 +311,26 @@ func (oc *DefaultNetworkController) addLogicalPort(pod *corev1.Pod) (err error) if err != nil { return err } - } else if config.Gateway.DisableSNATMultipleGWs && !oc.isPodNetworkAdvertisedAtNode(pod.Spec.NodeName) { + } else if config.Gateway.DisableSNATMultipleGWs { // Add NAT rules to pods if disable SNAT is set and does not have // namespace annotations to go through external egress router if extIPs, err := getExternalIPsGR(oc.watchFactory, pod.Spec.NodeName); err != nil { return err - } else if ops, err = addOrUpdatePodSNATOps(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, podAnnotation.IPs, ops); err != nil { - return err + } else { + // Handle each pod IP individually since each IP family needs its own SNAT match + for _, podIP := range podAnnotation.IPs { + ipFamily := utilnet.IPv4 + if utilnet.IsIPv6CIDR(podIP) { + ipFamily = utilnet.IPv6 + } + snatMatch, err := GetNetworkScopedClusterSubnetSNATMatch(oc.nbClient, oc.GetNetInfo(), pod.Spec.NodeName, oc.isPodNetworkAdvertisedAtNode(pod.Spec.NodeName), ipFamily) + if err != nil { + return fmt.Errorf("failed to get SNAT match for node %s for network %s: %v", pod.Spec.NodeName, oc.GetNetworkName(), err) + } + if ops, err = addOrUpdatePodSNATOps(oc.nbClient, oc.GetNetworkScopedGWRouterName(pod.Spec.NodeName), extIPs, []*net.IPNet{podIP}, snatMatch, ops); err != nil { + return err + } + } } } diff --git a/go-controller/pkg/ovn/secondary_layer2_network_controller.go b/go-controller/pkg/ovn/secondary_layer2_network_controller.go index 7ce63fc278..dacf37d090 100644 --- a/go-controller/pkg/ovn/secondary_layer2_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer2_network_controller.go @@ -575,36 +575,40 @@ func (oc *SecondaryLayer2NetworkController) addUpdateLocalNodeEvent(node *corev1 gwManager := oc.gatewayManagerForNode(node.Name) oc.gatewayManagers.Store(node.Name, gwManager) - gwConfig, err := oc.nodeGatewayConfig(node) - if err != nil { - errs = append(errs, err) - oc.gatewaysFailed.Store(node.Name, true) - } else { + err := func() error { + gwConfig, err := oc.nodeGatewayConfig(node) + if err != nil { + return err + } if err := gwManager.SyncGateway( node, gwConfig, ); err != nil { - errs = append(errs, err) - oc.gatewaysFailed.Store(node.Name, true) - } else { - if !util.IsPodNetworkAdvertisedAtNode(oc, node.Name) { - err = oc.addUDNClusterSubnetEgressSNAT(gwConfig.hostSubnets, gwManager.gwRouterName) - if err == nil && util.IsRouteAdvertisementsEnabled() { - err = oc.deleteAdvertisedNetworkIsolation(node.Name) - } - } else { - err = oc.deleteUDNClusterSubnetEgressSNAT(gwConfig.hostSubnets, gwManager.gwRouterName) - if err == nil { - err = oc.addAdvertisedNetworkIsolation(node.Name) + return err + } + isUDNAdvertised := util.IsPodNetworkAdvertisedAtNode(oc, node.Name) + err = oc.addOrUpdateUDNClusterSubnetEgressSNAT(gwConfig.hostSubnets, gwManager.gwRouterName, isUDNAdvertised) + if err != nil { + return err + } + if !isUDNAdvertised { + if util.IsRouteAdvertisementsEnabled() { + if err = oc.deleteAdvertisedNetworkIsolation(node.Name); err != nil { + return err } } - if err != nil { - errs = append(errs, err) - oc.gatewaysFailed.Store(node.Name, true) - } else { - oc.gatewaysFailed.Delete(node.Name) + } else { + if err = oc.addAdvertisedNetworkIsolation(node.Name); err != nil { + return err } } + oc.gatewaysFailed.Delete(node.Name) + return nil + }() + + if err != nil { + errs = append(errs, err) + oc.gatewaysFailed.Store(node.Name, true) } } @@ -741,7 +745,8 @@ func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Node) e return nil } -// addUDNClusterSubnetEgressSNAT adds the SNAT on each node's GR in L2 networks +// addOrUpdateUDNClusterSubnetEgressSNAT adds or updates the SNAT on each node's GR in L2 networks for each UDN +// Based on the isUDNAdvertised flag, the SNAT matches are slightly different // snat eth.dst == d6:cf:fd:2c:a6:44 169.254.0.12 10.128.0.0/14 // snat eth.dst == d6:cf:fd:2c:a6:44 169.254.0.12 2010:100:200::/64 // these SNATs are required for pod2Egress traffic in LGW mode and pod2SameNode traffic in SGW mode to function properly on UDNs @@ -751,9 +756,12 @@ func (oc *SecondaryLayer2NetworkController) deleteNodeEvent(node *corev1.Node) e // externalIP = "169.254.0.12"; which is the masqueradeIP for this L2 UDN // so all in all we want to condionally SNAT all packets that are coming from pods hosted on this node, // which are leaving via UDN's mpX interface to the UDN's masqueradeIP. -func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, gwRouterName string) error { +// If isUDNAdvertised is true, then we want to SNAT all packets that are coming from pods on this network +// leaving towards nodeIPs on the cluster to masqueradeIP. If network is advertise then the SNAT looks like this: +// "eth.dst == 0a:58:5d:5d:00:02 && (ip4.dst == $a712973235162149816)" "169.254.0.36" "93.93.0.0/16" +func (oc *SecondaryLayer2NetworkController) addOrUpdateUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, gwRouterName string, isUDNAdvertised bool) error { outputPort := types.GWRouterToJoinSwitchPrefix + gwRouterName - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort) + nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, isUDNAdvertised) if err != nil { return err } @@ -770,25 +778,6 @@ func (oc *SecondaryLayer2NetworkController) addUDNClusterSubnetEgressSNAT(localP return nil } -func (oc *SecondaryLayer2NetworkController) deleteUDNClusterSubnetEgressSNAT(localPodSubnets []*net.IPNet, routerName string) error { - outputPort := types.GWRouterToJoinSwitchPrefix + routerName - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort) - if err != nil { - return err - } - if len(nats) == 0 { - return nil // nothing to do - } - router := &nbdb.LogicalRouter{ - Name: routerName, - } - if err := libovsdbops.DeleteNATs(oc.nbClient, router, nats...); err != nil { - return fmt.Errorf("failed to delete SNAT for cluster on router: %q for network %q, error: %w", - routerName, oc.GetNetworkName(), err) - } - return nil -} - func (oc *SecondaryLayer2NetworkController) nodeGatewayConfig(node *corev1.Node) (*GatewayConfig, error) { l3GatewayConfig, err := util.ParseNodeL3GatewayAnnotation(node) if err != nil { diff --git a/go-controller/pkg/ovn/secondary_layer3_network_controller.go b/go-controller/pkg/ovn/secondary_layer3_network_controller.go index b2355b9100..e9745fe9b2 100644 --- a/go-controller/pkg/ovn/secondary_layer3_network_controller.go +++ b/go-controller/pkg/ovn/secondary_layer3_network_controller.go @@ -857,7 +857,8 @@ func (oc *SecondaryLayer3NetworkController) addUpdateRemoteNodeEvent(node *corev return err } -// addNodeSubnetEgressSNAT adds the SNAT on each node's ovn-cluster-router in L3 networks +// addOrUpdateUDNNodeSubnetEgressSNAT adds or updates the SNAT on each node's ovn-cluster-router in L3 networks for each UDN +// Based on the isUDNAdvertised flag, the SNAT matches are slightly different // snat eth.dst == d6:cf:fd:2c:a6:44 169.254.0.12 10.128.0.0/24 // snat eth.dst == d6:cf:fd:2c:a6:44 169.254.0.12 2010:100:200::/64 // these SNATs are required for pod2Egress traffic in LGW mode and pod2SameNode traffic in SGW mode to function properly on UDNs @@ -867,9 +868,12 @@ func (oc *SecondaryLayer3NetworkController) addUpdateRemoteNodeEvent(node *corev // externalIP = "169.254.0.12"; which is the masqueradeIP for this L3 UDN // so all in all we want to condionally SNAT all packets that are coming from pods hosted on this node, // which are leaving via UDN's mpX interface to the UDN's masqueradeIP. -func (oc *SecondaryLayer3NetworkController) addUDNNodeSubnetEgressSNAT(localPodSubnets []*net.IPNet, node *corev1.Node) error { +// If isUDNAdvertised is true, then we want to SNAT all packets that are coming from pods on this network +// leaving towards nodeIPs on the cluster to masqueradeIP. If network is advertise then the SNAT looks like this: +// "eth.dst == 0a:58:5d:5d:00:02 && (ip4.dst == $a712973235162149816)" "169.254.0.36" "93.93.0.0/24" +func (oc *SecondaryLayer3NetworkController) addOrUpdateUDNNodeSubnetEgressSNAT(localPodSubnets []*net.IPNet, node *corev1.Node, isUDNAdvertised bool) error { outputPort := types.RouterToSwitchPrefix + oc.GetNetworkScopedName(node.Name) - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort) + nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort, isUDNAdvertised) if err != nil { return fmt.Errorf("failed to build UDN masquerade SNATs for network %q on node %q, err: %w", oc.GetNetworkName(), node.Name, err) @@ -887,28 +891,6 @@ func (oc *SecondaryLayer3NetworkController) addUDNNodeSubnetEgressSNAT(localPodS return nil } -// deleteUDNNodeSubnetEgressSNAT deletes SNAT rule from network specific -// ovn_cluster_router depending on whether the network is advertised or not -func (oc *SecondaryLayer3NetworkController) deleteUDNNodeSubnetEgressSNAT(localPodSubnets []*net.IPNet, node *corev1.Node) error { - outputPort := types.RouterToSwitchPrefix + oc.GetNetworkScopedName(node.Name) - nats, err := oc.buildUDNEgressSNAT(localPodSubnets, outputPort) - if err != nil { - return fmt.Errorf("failed to build UDN masquerade SNATs for network %q on node %q, err: %w", - oc.GetNetworkName(), node.Name, err) - } - if len(nats) == 0 { - return nil // nothing to do - } - router := &nbdb.LogicalRouter{ - Name: oc.GetNetworkScopedClusterRouterName(), - } - if err := libovsdbops.DeleteNATs(oc.nbClient, router, nats...); err != nil { - return fmt.Errorf("failed to delete SNAT for node subnet on router: %q for network %q, error: %w", - oc.GetNetworkScopedClusterRouterName(), oc.GetNetworkName(), err) - } - return nil -} - func (oc *SecondaryLayer3NetworkController) addNode(node *corev1.Node) ([]*net.IPNet, error) { // Node subnet for the secondary layer3 network is allocated by cluster manager. // Make sure that the node is allocated with the subnet before proceeding @@ -923,19 +905,17 @@ func (oc *SecondaryLayer3NetworkController) addNode(node *corev1.Node) ([]*net.I return nil, err } if util.IsNetworkSegmentationSupportEnabled() && oc.IsPrimaryNetwork() { - if !util.IsPodNetworkAdvertisedAtNode(oc, node.Name) { - if err := oc.addUDNNodeSubnetEgressSNAT(hostSubnets, node); err != nil { - return nil, err - } + isUDNAdvertised := util.IsPodNetworkAdvertisedAtNode(oc, node.Name) + if err := oc.addOrUpdateUDNNodeSubnetEgressSNAT(hostSubnets, node, isUDNAdvertised); err != nil { + return nil, err + } + if !isUDNAdvertised { if util.IsRouteAdvertisementsEnabled() { if err := oc.deleteAdvertisedNetworkIsolation(node.Name); err != nil { return nil, err } } } else { - if err := oc.deleteUDNNodeSubnetEgressSNAT(hostSubnets, node); err != nil { - return nil, err - } if err := oc.addAdvertisedNetworkIsolation(node.Name); err != nil { return nil, err } diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index fd91edd3be..841ab001b8 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -82,7 +82,6 @@ type NetInfo interface { GetNetworkScopedExtPortName(bridgeID, nodeName string) string GetNetworkScopedLoadBalancerName(lbName string) string GetNetworkScopedLoadBalancerGroupName(lbGroupName string) string - GetNetworkScopedClusterSubnetSNATMatch(nodeName string) string // GetNetInfo is an identity method used to get the specific NetInfo // implementation @@ -543,10 +542,6 @@ func (nInfo *DefaultNetInfo) GetNetworkScopedLoadBalancerGroupName(lbGroupName s return nInfo.GetNetworkScopedName(lbGroupName) } -func (nInfo *DefaultNetInfo) GetNetworkScopedClusterSubnetSNATMatch(_ string) string { - return "" -} - func (nInfo *DefaultNetInfo) canReconcile(netInfo NetInfo) bool { _, ok := netInfo.(*DefaultNetInfo) return ok @@ -738,13 +733,6 @@ func (nInfo *secondaryNetInfo) GetNetworkScopedLoadBalancerGroupName(lbGroupName return nInfo.GetNetworkScopedName(lbGroupName) } -func (nInfo *secondaryNetInfo) GetNetworkScopedClusterSubnetSNATMatch(nodeName string) string { - if nInfo.TopologyType() != types.Layer2Topology { - return "" - } - return fmt.Sprintf("outport == %q", types.GWRouterToExtSwitchPrefix+nInfo.GetNetworkScopedGWRouterName(nodeName)) -} - // getPrefix returns if the logical entities prefix for this network func (nInfo *secondaryNetInfo) getPrefix() string { return GetSecondaryNetworkPrefix(nInfo.netName) From f32731c22d96b250d3d484a713a83441e3fdb401 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 2 Jul 2025 10:53:40 +0200 Subject: [PATCH 170/278] BGP,UDN,LGW: Ensure both masqueradeIP and podsubnet ip rules are present Given that some traffic like pod->node and pod->nodeport will be SNATed to nodeIP for UDNs, we will need iprules for both masqueradeIP and nodeIP to be present when networks are advertised. This is nothing complicated as keeping the masqueradeIP dangling around doesn't hurt anything (I hope :)) so for pod->node it follows the normal UDN LGW egress traffic flow: 1) pod->switch->ovn_cluster_router 2) SNAT at the router to masIP 3) ovn_cluster_router->switch->mpX 4) goes out and then reply coming from outside will hit these masqueradeIP rules to come back in since we snated to masqueradeIP on the way out, so we need both podsubnet and masqueradeIP rules for advertised networks for all other traffic no SNATing is done Signed-off-by: Surya Seetharaman --- go-controller/pkg/node/gateway_udn.go | 19 +++++++++++-------- go-controller/pkg/node/gateway_udn_test.go | 13 ++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/go-controller/pkg/node/gateway_udn.go b/go-controller/pkg/node/gateway_udn.go index 026ecd94fc..f7d2e27a01 100644 --- a/go-controller/pkg/node/gateway_udn.go +++ b/go-controller/pkg/node/gateway_udn.go @@ -644,17 +644,20 @@ func (udng *UserDefinedNetworkGateway) getV6MasqueradeIP() (*net.IPNet, error) { // constructUDNVRFIPRules constructs rules that redirect matching packets // into the corresponding UDN VRF routing table. -// If the network is not advertised, an example of the rules we set for a -// network is: -// 2000: from all fwmark 0x1001 lookup 1007 -// 2000: from all to 169.254.0.12 lookup 1007 -// 2000: from all fwmark 0x1002 lookup 1009 -// 2000: from all to 169.254.0.14 lookup 1009 -// If the network is advertised, an example of the rules we set for a network is: +// If the network is not advertised, an example of the rules we set for two +// networks is: +// 2000: from all fwmark 0x1001 lookup 1007 +// 2000: from all to 169.254.0.12 lookup 1007 +// 2000: from all fwmark 0x1002 lookup 1009 +// 2000: from all to 169.254.0.14 lookup 1009 +// If the network is advertised, an example of the rules we set for two +// networks is: // 2000: from all fwmark 0x1001 lookup 1007 // 2000: from all to 10.132.0.0/14 lookup 1007 +// 2000: from all to 169.254.0.12 lookup 1007 // 2000: from all fwmark 0x1001 lookup 1009 // 2000: from all to 10.134.0.0/14 lookup 1009 +// 2000: from all to 169.254.0.14 lookup 1009 func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules(isNetworkAdvertised bool) ([]netlink.Rule, []netlink.Rule, error) { var addIPRules []netlink.Rule var delIPRules []netlink.Rule @@ -693,7 +696,7 @@ func (udng *UserDefinedNetworkGateway) constructUDNVRFIPRules(isNetworkAdvertise delIPRules = append(delIPRules, subnetIPRules...) default: addIPRules = append(addIPRules, subnetIPRules...) - delIPRules = append(delIPRules, masqIPRules...) + addIPRules = append(addIPRules, masqIPRules...) } return addIPRules, delIPRules, nil } diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 34848faf7e..0ab0bf573b 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -1625,7 +1625,6 @@ func TestConstructUDNVRFIPRules(t *testing.T) { cidr := "" if config.IPv4Mode { cidr = "100.128.0.0/16/24" - } if config.IPv4Mode && config.IPv6Mode { cidr += ",ae70::/60/64" @@ -1711,8 +1710,6 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { table: 1007, dst: *ovntest.MustParseIPNet("100.128.0.0/16"), }, - }, - deleteRules: []testRule{ { priority: UDNMasqueradeIPRulePriority, family: netlink.FAMILY_V4, @@ -1738,8 +1735,6 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { table: 1009, dst: *ovntest.MustParseIPNet("ae70::/60"), }, - }, - deleteRules: []testRule{ { priority: UDNMasqueradeIPRulePriority, family: netlink.FAMILY_V6, @@ -1777,8 +1772,6 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { table: 1010, dst: *ovntest.MustParseIPNet("ae70::/60"), }, - }, - deleteRules: []testRule{ { priority: UDNMasqueradeIPRulePriority, family: netlink.FAMILY_V4, @@ -1813,9 +1806,9 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { cidr = "100.128.0.0/16/24" } if config.IPv4Mode && config.IPv6Mode { - cidr += ",ae70::/60" + cidr += ",ae70::/60/64" } else if config.IPv6Mode { - cidr = "ae70::/60" + cidr = "ae70::/60/64" } nad := ovntest.GenerateNAD("bluenet", "rednad", "greenamespace", types.Layer3Topology, cidr, types.NetworkRolePrimary) @@ -1844,6 +1837,8 @@ func TestConstructUDNVRFIPRulesPodNetworkAdvertised(t *testing.T) { udnGateway.vrfTableId = test.vrftableID rules, delRules, err := udnGateway.constructUDNVRFIPRules(true) g.Expect(err).ToNot(HaveOccurred()) + g.Expect(rules).To(HaveLen(len(test.expectedRules))) + g.Expect(delRules).To(HaveLen(len(test.deleteRules))) for i, rule := range rules { g.Expect(rule.Priority).To(Equal(test.expectedRules[i].priority)) g.Expect(rule.Table).To(Equal(test.expectedRules[i].table)) From 501bcbff9f08ce32b7b3251f16b389b8a3ccdecc Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 9 Jul 2025 11:10:16 +0200 Subject: [PATCH 171/278] Convert LGW postrouting rules to NFT This commit is a prep-commit that converts the LGW POSTROUTING chain rules from IPT to NFT. Why do we need to do this now? It's because for BGP we want to use the PMTUD remote nodeIP NFT sets to also do conditional masquerading in Local Gateway mode for BGP when traffic leaves UDNs towards other nodes in the cluster or other nodeports. Given PMTUD rules are in NFT but the lgw and udn masquerade rules are in IPT - we'd need to pick one to express all - since we want to move to NFT, its better to go that route. Below is how the rules look like. chain ovn-kube-local-gw-masq { comment "OVN local gateway masquerade" type nat hook postrouting priority srcnat; policy accept; ip saddr 169.254.0.1 masquerade ip6 saddr fd69::1 masquerade jump ovn-kube-pod-subnet-masq jump ovn-kube-udn-masq } chain ovn-kube-pod-subnet-masq { ip saddr 10.244.2.0/24 masquerade ip6 saddr fd00:10:244:1::/64 masquerade } chain ovn-kube-udn-masq { comment "OVN UDN masquerade" ip saddr != 169.254.0.0/29 ip daddr != 10.96.0.0/16 ip saddr 169.254.0.0/17 masquerade ip6 saddr != fd69::/125 ip daddr != fd00:10:96::/112 ip6 saddr fd69::/112 masquerade } This commit was AI-Cursor-gemini/claude assissted under my supervision/prompting/reviewing/back-forth iterations Signed-off-by: Surya Seetharaman --- go-controller/pkg/node/gateway.go | 4 +- .../pkg/node/gateway_init_linux_test.go | 31 +- go-controller/pkg/node/gateway_iptables.go | 167 +--------- go-controller/pkg/node/gateway_localnet.go | 24 +- go-controller/pkg/node/gateway_nftables.go | 299 ++++++++++++++++++ 5 files changed, 342 insertions(+), 183 deletions(-) diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 9b43fc95a5..7f11a0b813 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -522,8 +522,8 @@ func (g *gateway) updateSNATRules() error { subnets := util.IPsToNetworkIPs(g.nodeIPManager.mgmtPort.GetAddresses()...) if g.GetDefaultPodNetworkAdvertised() || config.Gateway.Mode != config.GatewayModeLocal { - return delLocalGatewayPodSubnetNATRules(subnets...) + return delLocalGatewayPodSubnetNFTRules() } - return addLocalGatewayPodSubnetNATRules(subnets...) + return addLocalGatewayPodSubnetNFTRules(subnets...) } diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 6e8aadc0f5..9bc0cc5401 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -80,6 +80,17 @@ add chain inet ovn-kubernetes udn-service-prerouting { type filter hook prerouti add rule inet ovn-kubernetes udn-service-prerouting iifname != %s jump udn-service-mark add chain inet ovn-kubernetes udn-service-output { type filter hook output priority -150 ; comment "UDN services packet mark - Output" ; } add rule inet ovn-kubernetes udn-service-output jump udn-service-mark +add chain inet ovn-kubernetes ovn-kube-udn-masq { comment "OVN UDN masquerade" ; } +add rule inet ovn-kubernetes ovn-kube-udn-masq ip saddr != 169.254.169.0/29 ip daddr != 172.16.1.0/24 ip saddr 169.254.169.0/24 masquerade +add rule inet ovn-kubernetes ovn-kube-local-gw-masq jump ovn-kube-udn-masq +` + +const baseLGWNFTablesRules = ` +add rule inet ovn-kubernetes ovn-kube-local-gw-masq ip saddr 169.254.169.1 masquerade +add chain inet ovn-kubernetes ovn-kube-local-gw-masq { type nat hook postrouting priority 100 ; comment "OVN local gateway masquerade" ; } +add rule inet ovn-kubernetes ovn-kube-local-gw-masq jump ovn-kube-pod-subnet-masq +add rule inet ovn-kubernetes ovn-kube-pod-subnet-masq ip saddr 10.1.1.0/24 masquerade +add chain inet ovn-kubernetes ovn-kube-pod-subnet-masq ` func getBaseNFTRules(mgmtPort string) string { @@ -90,6 +101,10 @@ func getBaseNFTRules(mgmtPort string) string { return ret } +func getBaseLGWNFTablesRules(mgmtPort string) string { + return getBaseNFTRules(mgmtPort) + baseLGWNFTablesRules +} + func shareGatewayInterfaceTest(app *cli.App, testNS ns.NetNS, eth0Name, eth0MAC, eth0GWIP, eth0CIDR string, gatewayVLANID uint, l netlink.Link, hwOffload, setNodeIP bool) { const mtu string = "1234" @@ -1350,10 +1365,6 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0` "OVN-KUBE-EXTERNALIP": []string{ fmt.Sprintf("-p %s -d %s --dport %v -j DNAT --to-destination %s:%v", service.Spec.Ports[0].Protocol, externalIP, service.Spec.Ports[0].Port, service.Spec.ClusterIP, service.Spec.Ports[0].Port), }, - "POSTROUTING": []string{ - "-s 169.254.169.1 -j MASQUERADE", - "-s 10.1.1.0/24 -j MASQUERADE", - }, "OVN-KUBE-ETP": []string{}, "OVN-KUBE-ITP": []string{}, }, @@ -1379,16 +1390,6 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0` "OVN-KUBE-ITP": []string{}, }, } - if util.IsNetworkSegmentationSupportEnabled() { - expectedTables["nat"]["POSTROUTING"] = append(expectedTables["nat"]["POSTROUTING"], - "-j OVN-KUBE-UDN-MASQUERADE", - ) - expectedTables["nat"]["OVN-KUBE-UDN-MASQUERADE"] = append(expectedTables["nat"]["OVN-KUBE-UDN-MASQUERADE"], - "-s 169.254.169.0/29 -j RETURN", // this guarantees we don't SNAT default network masqueradeIPs - "-d 172.16.1.0/24 -j RETURN", // this guarantees we don't SNAT service traffic - "-s 169.254.169.0/24 -j MASQUERADE", // this guarantees we SNAT all UDN MasqueradeIPs traffic leaving the node - ) - } f4 := iptV4.(*util.FakeIPTables) err = f4.MatchState(expectedTables, map[util.FakePolicyKey]string{{ Table: "filter", @@ -1405,7 +1406,7 @@ OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0` err = f6.MatchState(expectedTables, nil) Expect(err).NotTo(HaveOccurred()) - expectedNFT := getBaseNFTRules(types.K8sMgmtIntfName) + expectedNFT := getBaseLGWNFTablesRules(types.K8sMgmtIntfName) err = nodenft.MatchNFTRules(expectedNFT, nft.Dump()) Expect(err).NotTo(HaveOccurred()) diff --git a/go-controller/pkg/node/gateway_iptables.go b/go-controller/pkg/node/gateway_iptables.go index e9b6b12387..90bffbe91f 100644 --- a/go-controller/pkg/node/gateway_iptables.go +++ b/go-controller/pkg/node/gateway_iptables.go @@ -21,11 +21,10 @@ import ( ) const ( - iptableNodePortChain = "OVN-KUBE-NODEPORT" // called from nat-PREROUTING and nat-OUTPUT - iptableExternalIPChain = "OVN-KUBE-EXTERNALIP" // called from nat-PREROUTING and nat-OUTPUT - iptableETPChain = "OVN-KUBE-ETP" // called from nat-PREROUTING only - iptableITPChain = "OVN-KUBE-ITP" // called from mangle-OUTPUT and nat-OUTPUT - iptableUDNMasqueradeChain = "OVN-KUBE-UDN-MASQUERADE" // called from nat-POSTROUTING + iptableNodePortChain = "OVN-KUBE-NODEPORT" // called from nat-PREROUTING and nat-OUTPUT + iptableExternalIPChain = "OVN-KUBE-EXTERNALIP" // called from nat-PREROUTING and nat-OUTPUT + iptableETPChain = "OVN-KUBE-ETP" // called from nat-PREROUTING only + iptableITPChain = "OVN-KUBE-ITP" // called from mangle-OUTPUT and nat-OUTPUT ) func clusterIPTablesProtocols() []iptables.Protocol { @@ -69,29 +68,11 @@ func restoreIptRulesFiltered(rules []nodeipt.Rule, filter map[string]map[string] return nodeipt.RestoreRulesFiltered(rules, filter) } -// appendIptRules adds the provided rules in an append fashion -// i.e each rule gets added at the last position in the chain -func appendIptRules(rules []nodeipt.Rule) error { - return nodeipt.AddRules(rules, true) -} - // deleteIptRules removes provided rules from the chain func deleteIptRules(rules []nodeipt.Rule) error { return nodeipt.DelRules(rules) } -// ensureChain ensures that a chain exists within a table -func ensureChain(table, chain string) error { - for _, proto := range clusterIPTablesProtocols() { - ipt, err := util.GetIPTablesHelper(proto) - if err != nil { - return fmt.Errorf("failed to get IPTables helper to add UDN chain: %v", err) - } - addChaintoTable(ipt, table, chain) - } - return nil -} - func getGatewayInitRules(chain string, proto iptables.Protocol) []nodeipt.Rule { iptRules := []nodeipt.Rule{} if chain == iptableITPChain { @@ -403,123 +384,8 @@ func getLocalGatewayFilterRules(ifname string, cidr *net.IPNet) []nodeipt.Rule { } } -func getLocalGatewayPodSubnetNATRules(cidr *net.IPNet) []nodeipt.Rule { - protocol := getIPTablesProtocol(cidr.IP.String()) - return []nodeipt.Rule{ - { - Table: "nat", - Chain: "POSTROUTING", - Args: []string{ - "-s", cidr.String(), - "-j", "MASQUERADE", - }, - Protocol: protocol, - }, - } -} - -// getUDNMasqueradeRules is only called for local-gateway-mode -func getUDNMasqueradeRules(protocol iptables.Protocol) []nodeipt.Rule { - // the following rules are actively used only for the UDN Feature: - // -A POSTROUTING -j OVN-KUBE-UDN-MASQUERADE - // -A OVN-KUBE-UDN-MASQUERADE -s 169.254.0.0/29 -j RETURN - // -A OVN-KUBE-UDN-MASQUERADE -d 10.96.0.0/16 -j RETURN - // -A OVN-KUBE-UDN-MASQUERADE -s 169.254.0.0/17 -j MASQUERADE - // NOTE: Ordering is important here, the RETURN must come before - // the MASQUERADE rule. Please don't change the ordering. - srcUDNMasqueradePrefix := config.Gateway.V4MasqueradeSubnet - ipFamily := utilnet.IPv4 - if protocol == iptables.ProtocolIPv6 { - srcUDNMasqueradePrefix = config.Gateway.V6MasqueradeSubnet - ipFamily = utilnet.IPv6 - } - // defaultNetworkReservedMasqueradePrefix contains the first 6 IPs in the - // masquerade range that shouldn't be masqueraded. Hence it's always 3 bits (8 - // IPs) wide, regardless of IP family. - _, ipnet, _ := net.ParseCIDR(srcUDNMasqueradePrefix) - _, len := ipnet.Mask.Size() - defaultNetworkReservedMasqueradePrefix := fmt.Sprintf("%s/%d", ipnet.IP.String(), len-3) - - rules := []nodeipt.Rule{ - { - Table: "nat", - Chain: "POSTROUTING", - Args: []string{"-j", iptableUDNMasqueradeChain}, // NOTE: AddRules will take care of creating the chain - Protocol: protocol, - }, - { - Table: "nat", - Chain: iptableUDNMasqueradeChain, - Args: []string{ - "-s", defaultNetworkReservedMasqueradePrefix, - "-j", "RETURN", - }, - Protocol: protocol, - }, - } - for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { - if utilnet.IPFamilyOfCIDR(svcCIDR) != ipFamily { - continue - } - rules = append(rules, - nodeipt.Rule{ - Table: "nat", - Chain: iptableUDNMasqueradeChain, - Args: []string{ - "-d", svcCIDR.String(), - "-j", "RETURN", - }, - Protocol: protocol, - }, - ) - } - rules = append(rules, - nodeipt.Rule{ - Table: "nat", - Chain: iptableUDNMasqueradeChain, - Args: []string{ - "-s", srcUDNMasqueradePrefix, - "-j", "MASQUERADE", - }, - Protocol: protocol, - }, - ) - return rules -} - -func getLocalGatewayNATRules(cidr *net.IPNet) []nodeipt.Rule { - // Allow packets to/from the gateway interface in case defaults deny - protocol := getIPTablesProtocol(cidr.IP.String()) - masqueradeIP := config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP - if protocol == iptables.ProtocolIPv6 { - masqueradeIP = config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP - } - rules := append( - []nodeipt.Rule{ - { - Table: "nat", - Chain: "POSTROUTING", - Args: []string{ - "-s", masqueradeIP.String(), - "-j", "MASQUERADE", - }, - Protocol: protocol, - }, - }, - getLocalGatewayPodSubnetNATRules(cidr)..., - ) - - // FIXME(tssurya): If the feature is disabled we should be removing - // these rules - if util.IsNetworkSegmentationSupportEnabled() { - rules = append(rules, getUDNMasqueradeRules(protocol)...) - } - - return rules -} - -// initLocalGatewayNATRules sets up iptables rules for interfaces -func initLocalGatewayNATRules(ifname string, cidr *net.IPNet) error { +// initLocalGatewayIPTFilterRules sets up iptables rules for interfaces +func initLocalGatewayIPTFilterRules(ifname string, cidr *net.IPNet) error { // Insert the filter table rules because they need to be evaluated BEFORE the DROP rules // we have for forwarding. DO NOT change the ordering; specially important // during SGW->LGW rollouts and restarts. @@ -527,25 +393,8 @@ func initLocalGatewayNATRules(ifname string, cidr *net.IPNet) error { if err != nil { return fmt.Errorf("unable to insert forwarding rules %v", err) } - // append the masquerade rules in POSTROUTING table since that needs to be - // evaluated last. - return appendIptRules(getLocalGatewayNATRules(cidr)) -} - -func addLocalGatewayPodSubnetNATRules(cidrs ...*net.IPNet) error { - var rules []nodeipt.Rule - for _, cidr := range cidrs { - rules = append(rules, getLocalGatewayPodSubnetNATRules(cidr)...) - } - return appendIptRules(rules) -} - -func delLocalGatewayPodSubnetNATRules(cidrs ...*net.IPNet) error { - var rules []nodeipt.Rule - for _, cidr := range cidrs { - rules = append(rules, getLocalGatewayPodSubnetNATRules(cidr)...) - } - return deleteIptRules(rules) + // NOTE: nftables masquerade rules are now handled separately in initLocalGatewayNFTNATRules + return nil } func addChaintoTable(ipt util.IPTablesHelper, tableName, chain string) { diff --git a/go-controller/pkg/node/gateway_localnet.go b/go-controller/pkg/node/gateway_localnet.go index e0cc822844..6b8ed9aa0b 100644 --- a/go-controller/pkg/node/gateway_localnet.go +++ b/go-controller/pkg/node/gateway_localnet.go @@ -17,11 +17,11 @@ import ( func initLocalGateway(hostSubnets []*net.IPNet, mgmtPort managementport.Interface) error { klog.Info("Adding iptables masquerading rules for new local gateway") - if util.IsNetworkSegmentationSupportEnabled() { - if err := ensureChain("nat", iptableUDNMasqueradeChain); err != nil { - return fmt.Errorf("failed to ensure chain %s in NAT table: %w", iptableUDNMasqueradeChain, err) - } - } + + var allCIDRs []*net.IPNet + ifName := mgmtPort.GetInterfaceName() + + // First pass: collect all CIDRs and setup iptables filter rules per interface for _, hostSubnet := range hostSubnets { // local gateway mode uses mp0 as default path for all ingress traffic into OVN nextHop, err := util.MatchFirstIPNetFamily(utilnet.IsIPv6CIDR(hostSubnet), mgmtPort.GetAddresses()) @@ -32,11 +32,21 @@ func initLocalGateway(hostSubnets []*net.IPNet, mgmtPort managementport.Interfac // add iptables masquerading for mp0 to exit the host for egress cidr := nextHop.IP.Mask(nextHop.Mask) cidrNet := &net.IPNet{IP: cidr, Mask: nextHop.Mask} - ifName := mgmtPort.GetInterfaceName() - if err := initLocalGatewayNATRules(ifName, cidrNet); err != nil { + allCIDRs = append(allCIDRs, cidrNet) + + // Setup iptables filter rules for this interface/CIDR + if err := initLocalGatewayIPTFilterRules(ifName, cidrNet); err != nil { return fmt.Errorf("failed to add local NAT rules for: %s, err: %v", ifName, err) } } + + // setup nftables masquerade rules for all CIDRs (v4, v6 or dualstack) + if len(allCIDRs) > 0 { + if err := initLocalGatewayNFTNATRules(allCIDRs...); err != nil { + return fmt.Errorf("failed to setup nftables masquerade rules: %w", err) + } + } + return nil } diff --git a/go-controller/pkg/node/gateway_nftables.go b/go-controller/pkg/node/gateway_nftables.go index 842bb417d1..c2de7aa5e7 100644 --- a/go-controller/pkg/node/gateway_nftables.go +++ b/go-controller/pkg/node/gateway_nftables.go @@ -6,12 +6,14 @@ package node import ( "context" "fmt" + "net" "strings" corev1 "k8s.io/api/core/v1" utilnet "k8s.io/utils/net" "sigs.k8s.io/knftables" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/bridgeconfig" nodenft "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/nftables" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" @@ -27,6 +29,13 @@ import ( // use an "accept" rule to override a later "drop" rule), then those rules will need to // either both be iptables or both be nftables. +// nftables chain names +const ( + nftablesLocalGatewayMasqChain = "ovn-kube-local-gw-masq" + nftablesPodSubnetMasqChain = "ovn-kube-pod-subnet-masq" + nftablesUDNMasqChain = "ovn-kube-udn-masq" +) + // getNoSNATNodePortRules returns elements to add to the "mgmtport-no-snat-nodeports" // set to prevent SNAT of sourceIP when passing through the management port, for an // `externalTrafficPolicy: Local` service with NodePorts. @@ -186,3 +195,293 @@ func getUDNNFTRules(service *corev1.Service, netConfig *bridgeconfig.BridgeUDNCo } return rules } + +// getLocalGatewayPodSubnetMasqueradeNFTRule creates a rule for masquerading traffic from the pod subnet CIDR +// in local gateway node in a seperate chain which is then called from local gateway masquerade chain. +// +// chain ovn-kube-pod-subnet-masq { +// ip saddr 10.244.0.0/24 masquerade +// ip6 saddr fd00:10:244:1::/64 masquerade +// } +func getLocalGatewayPodSubnetMasqueradeNFTRule(cidr *net.IPNet) (*knftables.Rule, error) { + // Create the rule for masquerading traffic from the CIDR + ipPrefix := "ip" + if utilnet.IsIPv6CIDR(cidr) { + ipPrefix = "ip6" + } + + rule := &knftables.Rule{ + Rule: knftables.Concat( + ipPrefix, "saddr", cidr, + "masquerade", + ), + Chain: nftablesPodSubnetMasqChain, + } + + return rule, nil +} + +// getLocalGatewayNATNFTRules returns the nftables rules for local gateway NAT including masquerade IP rule, +// pod subnet rules, and UDN masquerade rules (if network segmentation is enabled). +// This function supports dual-stack by accepting multiple CIDRs and generating rules for all IP families. +// +// chain ovn-kube-local-gw-masq { +// comment "OVN local gateway masquerade" +// type nat hook postrouting priority srcnat; policy accept; +// ip saddr 169.254.0.1 masquerade +// ip6 saddr fd69::1 masquerade +// jump ovn-kube-pod-subnet-masq +// jump ovn-kube-udn-masq +// } +func getLocalGatewayNATNFTRules(cidrs ...*net.IPNet) ([]*knftables.Rule, error) { + var rules []*knftables.Rule + + // Process each CIDR to support dual-stack + for _, cidr := range cidrs { + // Determine IP version and masquerade IP + isIPv6 := utilnet.IsIPv6CIDR(cidr) + var masqueradeIP net.IP + var ipPrefix string + if isIPv6 { + masqueradeIP = config.Gateway.MasqueradeIPs.V6OVNMasqueradeIP + ipPrefix = "ip6" + } else { + masqueradeIP = config.Gateway.MasqueradeIPs.V4OVNMasqueradeIP + ipPrefix = "ip" + } + + // Rule1: Masquerade IP rule for the main chain + masqRule := &knftables.Rule{ + Chain: nftablesLocalGatewayMasqChain, + Rule: knftables.Concat( + ipPrefix, "saddr", masqueradeIP, + "masquerade", + ), + } + rules = append(rules, masqRule) + + // Rule2: Pod subnet NAT rule for the pod subnet chain + podSubnetRule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr) + if err != nil { + return nil, fmt.Errorf("failed to create pod subnet masquerade rule: %w", err) + } + rules = append(rules, podSubnetRule) + } + + // Rule 3: UDN masquerade rules (if network segmentation is enabled) + if util.IsNetworkSegmentationSupportEnabled() { + if config.IPv4Mode { + udnRules, err := getUDNMasqueradeNFTRules(utilnet.IPv4) + if err != nil { + return nil, fmt.Errorf("failed to create IPv4 UDN masquerade rules: %w", err) + } + rules = append(rules, udnRules...) + } + if config.IPv6Mode { + udnRules, err := getUDNMasqueradeNFTRules(utilnet.IPv6) + if err != nil { + return nil, fmt.Errorf("failed to create IPv6 UDN masquerade rules: %w", err) + } + rules = append(rules, udnRules...) + } + } + + return rules, nil +} + +// getUDNMasqueradeNFTRules returns the nftables rules for UDN masquerade. +// Chain creation is handled separately by setupLocalGatewayNATNFTRules. +// +// chain ovn-kube-udn-masq { +// comment "OVN UDN masquerade" +// ip saddr != 169.254.0.0/29 ip daddr != 10.96.0.0/16 ip saddr 169.254.0.0/17 masquerade +// ip6 saddr != fd69::/125 ip daddr != fd00:10:96::/112 ip6 saddr fd69::/112 masquerade +// } +func getUDNMasqueradeNFTRules(ipFamily utilnet.IPFamily) ([]*knftables.Rule, error) { + var rules []*knftables.Rule + + // Determine subnet and IP family + srcUDNMasqueradePrefix := config.Gateway.V4MasqueradeSubnet + ipPrefix := "ip" + if ipFamily == utilnet.IPv6 { + srcUDNMasqueradePrefix = config.Gateway.V6MasqueradeSubnet + ipPrefix = "ip6" + } + + // Calculate reserved masquerade prefix (first 8 IPs) + _, ipnet, err := net.ParseCIDR(srcUDNMasqueradePrefix) + if err != nil { + return nil, fmt.Errorf("failed to parse UDN masquerade subnet: %w", err) + } + _, prefixLen := ipnet.Mask.Size() + defaultNetworkReservedMasqueradePrefix := fmt.Sprintf("%s/%d", ipnet.IP.String(), prefixLen-3) + + // Rule: RETURN for reserved masquerade prefix and service CIDRs + // rest of the traffic is masqueraded + + for _, svcCIDR := range config.Kubernetes.ServiceCIDRs { + if utilnet.IPFamilyOfCIDR(svcCIDR) != ipFamily { + continue + } + masqueradeRule := &knftables.Rule{ + Chain: nftablesUDNMasqChain, + Rule: knftables.Concat( + ipPrefix, "saddr", "!=", defaultNetworkReservedMasqueradePrefix, // this guarantees we don't SNAT default network masqueradeIPs + ipPrefix, "daddr", "!=", svcCIDR, // this guarantees we don't SNAT service traffic + ipPrefix, "saddr", srcUDNMasqueradePrefix, // this guarantees we SNAT all UDN MasqueradeIPs traffic leaving the node + "masquerade", + ), + } + rules = append(rules, masqueradeRule) + } + + return rules, nil +} + +// initLocalGatewayNFTNATRules sets up nftables rules for local gateway NAT functionality +// This function supports dual-stack by accepting multiple CIDRs and generating rules for all IP families +func initLocalGatewayNFTNATRules(cidrs ...*net.IPNet) error { + nft, err := nodenft.GetNFTablesHelper() + if err != nil { + return fmt.Errorf("failed to get nftables helper: %w", err) + } + + // Create transaction and apply all chains and rules + tx := nft.NewTransaction() + + // Create main local gateway masquerade chain + localGwMasqChain := &knftables.Chain{ + Name: nftablesLocalGatewayMasqChain, + Comment: knftables.PtrTo("OVN local gateway masquerade"), + Type: knftables.PtrTo(knftables.NATType), + Hook: knftables.PtrTo(knftables.PostroutingHook), + Priority: knftables.PtrTo(knftables.SNATPriority), + } + tx.Add(localGwMasqChain) + + // Create dedicated pod subnet masquerade chain + podSubnetMasqChain := &knftables.Chain{ + Name: nftablesPodSubnetMasqChain, + } + tx.Add(podSubnetMasqChain) + + // Create UDN masquerade chain only if network segmentation is enabled + var udnMasqChain *knftables.Chain + if util.IsNetworkSegmentationSupportEnabled() { + udnMasqChain = &knftables.Chain{ + Name: nftablesUDNMasqChain, + Comment: knftables.PtrTo("OVN UDN masquerade"), + } + tx.Add(udnMasqChain) + } + + // Flush existing chains to ensure clean state + tx.Flush(localGwMasqChain) + tx.Flush(podSubnetMasqChain) + if util.IsNetworkSegmentationSupportEnabled() { + tx.Flush(udnMasqChain) + } + + // Get the existing local gateway NAT rules + localGwRules, err := getLocalGatewayNATNFTRules(cidrs...) + if err != nil { + return fmt.Errorf("failed to get local gateway NAT rules: %w", err) + } + + // Add the main local gateway NAT rules + for _, rule := range localGwRules { + tx.Add(rule) + } + + // Add jump rule from main chain to pod subnet chain + jumpToPodSubnetRule := &knftables.Rule{ + Chain: nftablesLocalGatewayMasqChain, + Rule: knftables.Concat( + "jump", nftablesPodSubnetMasqChain, + ), + } + tx.Add(jumpToPodSubnetRule) + + // Add jump rule to UDN chain only if network segmentation is enabled + if util.IsNetworkSegmentationSupportEnabled() { + jumpToUDNRule := &knftables.Rule{ + Chain: nftablesLocalGatewayMasqChain, + Rule: knftables.Concat( + "jump", nftablesUDNMasqChain, + ), + } + tx.Add(jumpToUDNRule) + } + + err = nft.Run(context.TODO(), tx) + if err != nil { + return fmt.Errorf("failed to setup local gateway NAT nftables rules: %w", err) + } + + return nil +} + +// addLocalGatewayPodSubnetNFTRules adds nftables rules for pod subnet masquerading for multiple CIDRs +// These rules are added to the dedicated pod subnet masquerade chain. +func addLocalGatewayPodSubnetNFTRules(cidrs ...*net.IPNet) error { + nft, err := nodenft.GetNFTablesHelper() + if err != nil { + return fmt.Errorf("failed to get nftables helper: %w", err) + } + + tx := nft.NewTransaction() + + // Ensure the pod subnet chain exists + podSubnetChain := &knftables.Chain{ + Name: nftablesPodSubnetMasqChain, + } + tx.Add(podSubnetChain) + + // Flush the chain to remove all existing rules + tx.Flush(podSubnetChain) + + for _, cidr := range cidrs { + rule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr) + if err != nil { + return fmt.Errorf("failed to create nftables rules for CIDR %s: %w", cidr.String(), err) + } + + // Add the rule + tx.Add(rule) + } + + if err := nft.Run(context.TODO(), tx); err != nil { + return fmt.Errorf("failed to add pod subnet NAT rules: %w", err) + } + + return nil +} + +// delLocalGatewayPodSubnetNFTRules removes nftables rules for pod subnet masquerading for multiple CIDRs +// Since we use a separate chain, we can simply flush it to remove all pod subnet rules. +func delLocalGatewayPodSubnetNFTRules() error { + nft, err := nodenft.GetNFTablesHelper() + if err != nil { + return fmt.Errorf("failed to get nftables helper: %w", err) + } + + tx := nft.NewTransaction() + + // In shared gateway mode, this chain might not exist if its + // not migration from local gateway mode. In that case, let's + // use the idiomatic way of adding the chain before trying to flush it. + // I anyways also have the knftables.IsNotFound() check in the caller later. + tx.Add(&knftables.Chain{ + Name: nftablesPodSubnetMasqChain, + }) + + // Simply flush the dedicated pod subnet masquerade chain + // This removes all pod subnet masquerade rules at once + tx.Flush(&knftables.Chain{Name: nftablesPodSubnetMasqChain}) + + if err := nft.Run(context.TODO(), tx); err != nil && !knftables.IsNotFound(err) { + return fmt.Errorf("failed to delete pod subnet NAT rules: %w", err) + } + + return nil +} From a67872dc389b80d65029c8a01c56299ed39240b6 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 9 Jul 2025 11:11:50 +0200 Subject: [PATCH 172/278] rename/reuse pmtud nft sets to remote-node-ips let's reuse the pmtud address-set ips of the remote nodes ips also for bgp advertised networks cSNAT Signed-off-by: Surya Seetharaman --- .../node/default_node_network_controller.go | 18 +++++------ .../default_node_network_controller_test.go | 32 +++++++++---------- go-controller/pkg/node/node_nftables.go | 12 +++---- go-controller/pkg/types/const.go | 8 ++--- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index f1281980a8..db7d26802d 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -188,7 +188,7 @@ func NewDefaultNodeNetworkController(cnnci *CommonNodeNetworkControllerInfo, net nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() if err != nil { return nil, fmt.Errorf("failed to setup PMTUD nftables sets: %w", err) } @@ -1528,12 +1528,12 @@ func (nc *DefaultNodeNetworkController) addOrUpdateNode(node *corev1.Node) error klog.Infof("Adding remote node %q, IP: %s to PMTUD blocking rules", node.Name, nodeIP) if utilnet.IsIPv4(nodeIP) { nftElems = append(nftElems, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv4, + Set: types.NFTRemoteNodeIPsv4, Key: []string{nodeIP.String()}, }) } else { nftElems = append(nftElems, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv6, + Set: types.NFTRemoteNodeIPsv6, Key: []string{nodeIP.String()}, }) } @@ -1557,12 +1557,12 @@ func removePMTUDNodeNFTRules(nodeIPs []net.IP) error { // Remove IPs from NFT sets if utilnet.IsIPv4(nodeIP) { nftElems = append(nftElems, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv4, + Set: types.NFTRemoteNodeIPsv4, Key: []string{nodeIP.String()}, }) } else { nftElems = append(nftElems, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv6, + Set: types.NFTRemoteNodeIPsv6, Key: []string{nodeIP.String()}, }) } @@ -1622,21 +1622,21 @@ func (nc *DefaultNodeNetworkController) syncNodes(objs []interface{}) error { // Remove IPs from NFT sets if utilnet.IsIPv4(nodeIP) { keepNFTSetElemsV4 = append(keepNFTSetElemsV4, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv4, + Set: types.NFTRemoteNodeIPsv4, Key: []string{nodeIP.String()}, }) } else { keepNFTSetElemsV6 = append(keepNFTSetElemsV6, &knftables.Element{ - Set: types.NFTNoPMTUDRemoteNodeIPsv6, + Set: types.NFTRemoteNodeIPsv6, Key: []string{nodeIP.String()}, }) } } } - if err := recreateNFTSet(types.NFTNoPMTUDRemoteNodeIPsv4, keepNFTSetElemsV4); err != nil { + if err := recreateNFTSet(types.NFTRemoteNodeIPsv4, keepNFTSetElemsV4); err != nil { errors = append(errors, err) } - if err := recreateNFTSet(types.NFTNoPMTUDRemoteNodeIPsv6, keepNFTSetElemsV6); err != nil { + if err := recreateNFTSet(types.NFTRemoteNodeIPsv6, keepNFTSetElemsV6); err != nil { errors = append(errors, err) } diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index a1413a7dd1..24c3141357 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -38,18 +38,18 @@ import ( const v4PMTUDNFTRules = ` add table inet ovn-kubernetes -add rule inet ovn-kubernetes no-pmtud ip daddr @no-pmtud-remote-node-ips-v4 meta l4proto icmp icmp type 3 icmp code 4 counter drop +add rule inet ovn-kubernetes no-pmtud ip daddr @remote-node-ips-v4 meta l4proto icmp icmp type 3 icmp code 4 counter drop add chain inet ovn-kubernetes no-pmtud { type filter hook output priority 0 ; comment "Block egress needs frag/packet too big to remote k8s nodes" ; } -add set inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { type ipv4_addr ; comment "Block egress ICMP needs frag to remote Kubernetes nodes" ; } -add set inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { type ipv6_addr ; comment "Block egress ICMPv6 packet too big to remote Kubernetes nodes" ; } +add set inet ovn-kubernetes remote-node-ips-v4 { type ipv4_addr ; comment "Block egress ICMP needs frag to remote Kubernetes nodes" ; } +add set inet ovn-kubernetes remote-node-ips-v6 { type ipv6_addr ; comment "Block egress ICMPv6 packet too big to remote Kubernetes nodes" ; } ` const v6PMTUDNFTRules = ` add table inet ovn-kubernetes -add rule inet ovn-kubernetes no-pmtud meta l4proto icmpv6 icmpv6 type 2 icmpv6 code 0 ip6 daddr @no-pmtud-remote-node-ips-v6 counter drop +add rule inet ovn-kubernetes no-pmtud meta l4proto icmpv6 icmpv6 type 2 icmpv6 code 0 ip6 daddr @remote-node-ips-v6 counter drop add chain inet ovn-kubernetes no-pmtud { type filter hook output priority 0 ; comment "Block egress needs frag/packet too big to remote k8s nodes" ; } -add set inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { type ipv4_addr ; comment "Block egress ICMP needs frag to remote Kubernetes nodes" ; } -add set inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { type ipv6_addr ; comment "Block egress ICMPv6 packet too big to remote Kubernetes nodes" ; } +add set inet ovn-kubernetes remote-node-ips-v4 { type ipv4_addr ; comment "Block egress ICMP needs frag to remote Kubernetes nodes" ; } +add set inet ovn-kubernetes remote-node-ips-v6 { type ipv6_addr ; comment "Block egress ICMPv6 packet too big to remote Kubernetes nodes" ; } ` var _ = Describe("Node", func() { @@ -806,7 +806,7 @@ var _ = Describe("Node", func() { cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) @@ -830,7 +830,7 @@ var _ = Describe("Node", func() { err = nc.WatchNodes() Expect(err).NotTo(HaveOccurred()) nftRules := v4PMTUDNFTRules + ` -add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.254.61 } +add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.254.61 } ` err = nodenft.MatchNFTRules(nftRules, nft.Dump()) Expect(err).NotTo(HaveOccurred()) @@ -911,7 +911,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.254.61 } cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) @@ -935,7 +935,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.254.61 } err = nc.WatchNodes() Expect(err).NotTo(HaveOccurred()) nftRules := v4PMTUDNFTRules + ` -add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.253.61 } +add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.253.61 } ` err = nodenft.MatchNFTRules(nftRules, nft.Dump()) Expect(err).NotTo(HaveOccurred()) @@ -1058,7 +1058,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.253.61 } cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) @@ -1082,7 +1082,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v4 { 169.254.253.61 } err = nc.WatchNodes() Expect(err).NotTo(HaveOccurred()) nftRules := v6PMTUDNFTRules + ` -add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2001:db8:1::4 } +add element inet ovn-kubernetes remote-node-ips-v6 { 2001:db8:1::4 } ` err = nodenft.MatchNFTRules(nftRules, nft.Dump()) Expect(err).NotTo(HaveOccurred()) @@ -1162,7 +1162,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2001:db8:1::4 } cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) @@ -1186,7 +1186,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2001:db8:1::4 } err = nc.WatchNodes() Expect(err).NotTo(HaveOccurred()) nftRules := v6PMTUDNFTRules + ` -add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } +add element inet ovn-kubernetes remote-node-ips-v6 { 2002:db8:1::4 } ` err = nodenft.MatchNFTRules(nftRules, nft.Dump()) Expect(err).NotTo(HaveOccurred()) @@ -1323,7 +1323,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) @@ -1444,7 +1444,7 @@ add element inet ovn-kubernetes no-pmtud-remote-node-ips-v6 { 2002:db8:1::4 } cnnci := NewCommonNodeNetworkControllerInfo(kubeFakeClient, fakeClient.AdminPolicyRouteClient, wf, nil, nodeName, routeManager) nc = newDefaultNodeNetworkController(cnnci, stop, wg, routeManager, nil) nc.initRetryFrameworkForNode() - err = setupPMTUDNFTSets() + err = setupRemoteNodeNFTSets() Expect(err).NotTo(HaveOccurred()) err = setupPMTUDNFTChain() Expect(err).NotTo(HaveOccurred()) diff --git a/go-controller/pkg/node/node_nftables.go b/go-controller/pkg/node/node_nftables.go index e52a8970a4..ca4afc9ac2 100644 --- a/go-controller/pkg/node/node_nftables.go +++ b/go-controller/pkg/node/node_nftables.go @@ -13,8 +13,8 @@ import ( const nftPMTUDChain = "no-pmtud" -// setupPMTUDNFTSets sets up the NFT sets that contain remote Kubernetes node IPs -func setupPMTUDNFTSets() error { +// setupRemoteNodeNFTSets sets up the NFT sets that contain remote Kubernetes node IPs +func setupRemoteNodeNFTSets() error { nft, err := nodenft.GetNFTablesHelper() if err != nil { return fmt.Errorf("failed to get nftables helper: %w", err) @@ -22,12 +22,12 @@ func setupPMTUDNFTSets() error { tx := nft.NewTransaction() tx.Add(&knftables.Set{ - Name: types.NFTNoPMTUDRemoteNodeIPsv4, + Name: types.NFTRemoteNodeIPsv4, Comment: knftables.PtrTo("Block egress ICMP needs frag to remote Kubernetes nodes"), Type: "ipv4_addr", }) tx.Add(&knftables.Set{ - Name: types.NFTNoPMTUDRemoteNodeIPsv6, + Name: types.NFTRemoteNodeIPsv6, Comment: knftables.PtrTo("Block egress ICMPv6 packet too big to remote Kubernetes nodes"), Type: "ipv6_addr", }) @@ -68,7 +68,7 @@ func setupPMTUDNFTChain() error { tx.Add(&knftables.Rule{ Chain: nftPMTUDChain, Rule: knftables.Concat( - "ip daddr @"+types.NFTNoPMTUDRemoteNodeIPsv4, + "ip daddr @"+types.NFTRemoteNodeIPsv4, "meta l4proto icmp", "icmp type 3", // type 3 == Destination Unreachable "icmp code 4", // code 4 indicates fragmentation needed @@ -85,7 +85,7 @@ func setupPMTUDNFTChain() error { "meta l4proto icmpv6", // match on ICMPv6 packets "icmpv6 type 2", // type 2 == Packet Too Big (PMTUD) "icmpv6 code 0", // code 0 for that message - "ip6 daddr @"+types.NFTNoPMTUDRemoteNodeIPsv6, + "ip6 daddr @"+types.NFTRemoteNodeIPsv6, counterIfDebug, "drop", // drop the packet ), diff --git a/go-controller/pkg/types/const.go b/go-controller/pkg/types/const.go index 8ba7269cad..523da8e27b 100644 --- a/go-controller/pkg/types/const.go +++ b/go-controller/pkg/types/const.go @@ -312,13 +312,13 @@ const ( // CUDNPrefix of all CUDN network names CUDNPrefix = "cluster_udn_" - // NFTNoPMTUDRemoteNodeIPsv4 is a set used to track remote node IPs that do not belong to + // NFTRemoteNodeIPsv4 is a set used to track remote node v4IPs that do not belong to // the local node's subnet. - NFTNoPMTUDRemoteNodeIPsv4 = "no-pmtud-remote-node-ips-v4" + NFTRemoteNodeIPsv4 = "remote-node-ips-v4" - // NFTNoPMTUDRemoteNodeIPsv6 is a set used to track remote node IPs that do not belong to + // NFTRemoteNodeIPsv6 is a set used to track remote node v6IPs that do not belong to // the local node's subnet. - NFTNoPMTUDRemoteNodeIPsv6 = "no-pmtud-remote-node-ips-v6" + NFTRemoteNodeIPsv6 = "remote-node-ips-v6" // Metrics MetricOvnkubeNamespace = "ovnkube" From 04d48c314db6568287855f6d24cec14c9347e7f6 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 9 Jul 2025 11:38:57 +0200 Subject: [PATCH 173/278] BGP, default network, LGW: Conditionally Masquerade This commit is valid only for default networks as mentioned in title. It's because unlike in UDNs where we do cSNATs in OVN on router at the edge before it leaves to node, for CDN everything happens on the node side already - so we can leverage the nodeIP masquerade bits. if network is advertised: chain ovn-kube-pod-subnet-masq { ip saddr 10.244.2.0/24 ip daddr @remote-node-ips-v4 masquerade ip6 saddr fd00:10:244:3::/64 ip6 daddr @remote-node-ips-v6 masquerade } else: chain ovn-kube-pod-subnet-masq { ip saddr 10.244.2.0/24 masquerade ip6 saddr fd00:10:244:3::/64 masquerade } Signed-off-by: Surya Seetharaman --- go-controller/pkg/node/gateway.go | 4 +-- go-controller/pkg/node/gateway_nftables.go | 33 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/go-controller/pkg/node/gateway.go b/go-controller/pkg/node/gateway.go index 7f11a0b813..fa812377e7 100644 --- a/go-controller/pkg/node/gateway.go +++ b/go-controller/pkg/node/gateway.go @@ -521,9 +521,9 @@ func (g *gateway) addAllServices() []error { func (g *gateway) updateSNATRules() error { subnets := util.IPsToNetworkIPs(g.nodeIPManager.mgmtPort.GetAddresses()...) - if g.GetDefaultPodNetworkAdvertised() || config.Gateway.Mode != config.GatewayModeLocal { + if config.Gateway.Mode != config.GatewayModeLocal { return delLocalGatewayPodSubnetNFTRules() } - return addLocalGatewayPodSubnetNFTRules(subnets...) + return addOrUpdateLocalGatewayPodSubnetNFTRules(g.GetDefaultPodNetworkAdvertised(), subnets...) } diff --git a/go-controller/pkg/node/gateway_nftables.go b/go-controller/pkg/node/gateway_nftables.go index c2de7aa5e7..71a4d23b9e 100644 --- a/go-controller/pkg/node/gateway_nftables.go +++ b/go-controller/pkg/node/gateway_nftables.go @@ -203,16 +203,32 @@ func getUDNNFTRules(service *corev1.Service, netConfig *bridgeconfig.BridgeUDNCo // ip saddr 10.244.0.0/24 masquerade // ip6 saddr fd00:10:244:1::/64 masquerade // } -func getLocalGatewayPodSubnetMasqueradeNFTRule(cidr *net.IPNet) (*knftables.Rule, error) { +// +// If isAdvertisedNetwork is true, masquerade only when destination matches remote node IPs. +// Rules look like: +// ip saddr 10.244.0.0/24 ip daddr @remote-node-ips-v4 masquerade +// ip6 saddr fd00:10:244:1::/64 ip6 daddr @remote-node-ips-v6 masquerade +func getLocalGatewayPodSubnetMasqueradeNFTRule(cidr *net.IPNet, isAdvertisedNetwork bool) (*knftables.Rule, error) { // Create the rule for masquerading traffic from the CIDR - ipPrefix := "ip" + var ipPrefix string + var remoteNodeSetName string if utilnet.IsIPv6CIDR(cidr) { ipPrefix = "ip6" + remoteNodeSetName = types.NFTRemoteNodeIPsv6 + } else { + ipPrefix = "ip" + remoteNodeSetName = types.NFTRemoteNodeIPsv4 } + // If network is advertised, only masquerade if destination is a remote node IP + var optionalDestRules []string + if isAdvertisedNetwork { + optionalDestRules = []string{ipPrefix, "daddr", "@", remoteNodeSetName} + } rule := &knftables.Rule{ Rule: knftables.Concat( ipPrefix, "saddr", cidr, + optionalDestRules, "masquerade", ), Chain: nftablesPodSubnetMasqChain, @@ -261,7 +277,7 @@ func getLocalGatewayNATNFTRules(cidrs ...*net.IPNet) ([]*knftables.Rule, error) rules = append(rules, masqRule) // Rule2: Pod subnet NAT rule for the pod subnet chain - podSubnetRule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr) + podSubnetRule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr, false) if err != nil { return nil, fmt.Errorf("failed to create pod subnet masquerade rule: %w", err) } @@ -421,9 +437,12 @@ func initLocalGatewayNFTNATRules(cidrs ...*net.IPNet) error { return nil } -// addLocalGatewayPodSubnetNFTRules adds nftables rules for pod subnet masquerading for multiple CIDRs +// addOrUpdateLocalGatewayPodSubnetNFTRules adds nftables rules for pod subnet masquerading for multiple CIDRs // These rules are added to the dedicated pod subnet masquerade chain. -func addLocalGatewayPodSubnetNFTRules(cidrs ...*net.IPNet) error { +// If the rules already exist, they are updated. +// If isAdvertisedNetwork is true, the masquerade rules also get a destination match +// that matches the remote node IP set. +func addOrUpdateLocalGatewayPodSubnetNFTRules(isAdvertisedNetwork bool, cidrs ...*net.IPNet) error { nft, err := nodenft.GetNFTablesHelper() if err != nil { return fmt.Errorf("failed to get nftables helper: %w", err) @@ -438,10 +457,12 @@ func addLocalGatewayPodSubnetNFTRules(cidrs ...*net.IPNet) error { tx.Add(podSubnetChain) // Flush the chain to remove all existing rules + // if network toggles between advertised and non-advertised, we need to flush the chain and re-add correct rules tx.Flush(podSubnetChain) + // Add the new rules for each CIDR for _, cidr := range cidrs { - rule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr) + rule, err := getLocalGatewayPodSubnetMasqueradeNFTRule(cidr, isAdvertisedNetwork) if err != nil { return fmt.Errorf("failed to create nftables rules for CIDR %s: %w", cidr.String(), err) } From 8a65723f1d90d2360c9a3f965e89eb124c026e3b Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 2 Jul 2025 11:16:52 +0200 Subject: [PATCH 174/278] Add E2E's for these traffic flows 1) remove the l2 failure limitation since we now use nodeIPs reply knows how to go back to src node since we have routes for that 2) add udn pod -> default network nodeport service (same and diff node) 3) add udn pod -> udn network nodeport service (same and diff node) - same network 4) add udn pod -> udn network nodeport service (same and diff node) - different network Signed-off-by: Surya Seetharaman Signed-off-by: Surya Seetharaman --- test/e2e/route_advertisements.go | 113 ++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index f65dd60631..d46d0a9409 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -28,7 +28,6 @@ import ( "github.com/ovn-org/ovn-kubernetes/test/e2e/label" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -626,7 +625,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" } // create host networked Pods - _, err := createPod(f, node.Name+"-hostnet-ep", node.Name, f.Namespace.Name, []string{}, map[string]string{}, func(p *v1.Pod) { + _, err := createPod(f, node.Name+"-hostnet-ep", node.Name, f.Namespace.Name, []string{}, map[string]string{}, func(p *corev1.Pod) { p.Spec.Containers[0].Args = args p.Spec.HostNetwork = true }) @@ -652,6 +651,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" svc.Spec.Ports = []corev1.ServicePort{{Port: 8080}} familyPolicy := corev1.IPFamilyPolicyPreferDualStack svc.Spec.IPFamilyPolicy = &familyPolicy + svc.Spec.Type = corev1.ServiceTypeNodePort svcNetA, err = f.ClientSet.CoreV1().Services(pod.Namespace).Create(context.Background(), svc, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -675,6 +675,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" svc.Name = fmt.Sprintf("service-default") svc.Namespace = "default" svc.Spec.Selector = pod.Labels + svc.Spec.Type = corev1.ServiceTypeNodePort svcNetDefault, err = f.ClientSet.CoreV1().Services(pod.Namespace).Create(context.Background(), svc, metav1.CreateOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) @@ -754,6 +755,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" } if svcNetDefault != nil { err = f.ClientSet.CoreV1().Services(svcNetDefault.Namespace).Delete(context.Background(), svcNetDefault.Name, metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) svcNetDefault = nil } @@ -954,11 +956,11 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" // options [mss 1360,sackOK,TS val 3006752321 ecr 0,nop,wscale 7], length 0 // 10:59:55.352404 ovn-k8s-mp87 In ifindex 186 0a:58:5d:5d:01:01 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 63, id 57264, // offset 0, flags [DF], proto TCP (6), length 60) - // 93.93.1.5.36363 > 172.18.0.2.25022: Flags [S], cksum 0xe0b7 (correct), seq 3879759281, win 65280, + // 169.154.169.12.36363 > 172.18.0.2.25022: Flags [S], cksum 0xe0b7 (correct), seq 3879759281, win 65280, // options [mss 1360,sackOK,TS val 3006752321 ecr 0,nop,wscale 7], length 0 // 10:59:55.352461 ovn-k8s-mp87 Out ifindex 186 0a:58:5d:5d:01:02 ethertype IPv4 (0x0800), length 60: (tos 0x0, ttl 64, id 0, // offset 0, flags [DF], proto TCP (6), length 40) - // 172.18.0.2.25022 > 93.93.1.5.36363: Flags [R.], cksum 0x609d (correct), seq 0, ack 3879759282, win 0, length 0 + // 172.18.0.2.25022 > 169.154.169.12.36363: Flags [R.], cksum 0x609d (correct), seq 0, ack 3879759282, win 0, length 0 // 10:59:55.352927 319594f193d4d_3 Out ifindex 191 0a:58:5d:5d:01:02 ethertype IPv4 (0x0800), length 60: (tos 0x0, ttl 64, id 0, // offset 0, flags [DF], proto TCP (6), length 40) // 172.18.0.2.25022 > 93.93.1.5.36363: Flags [R.], cksum 0x609d (correct), seq 0, ack 1, win 0, length 0 @@ -971,25 +973,90 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), podsNetA[2].Spec.NodeName, metav1.GetOptions{}) framework.ExpectNoError(err) nodeIP := node.Status.Addresses[ipFamilyIndex].Address - errBool := false - out := "" - if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { - // FIXME: this should be removed once we add the SNAT for pod->node traffic - // We now permit asymmetric traffic on LGW. This prevents the issue from occurring with IPv6. - // However, for IPv4 LGW rp_filter is still blocking the replies. - // The situation is different on SGW as we don't allow asymmetric traffic at all, which is why IPv6 traffic fails there too. - if ipFamilyIndex == ipFamilyV4 || !isLocalGWModeEnabled() { - // FIXME: fix assymmetry in L2 UDNs - // bad behaviour: packet is coming from other node -> entering eth0 -> bretho and here kernel drops the packet since - // rp_filter is set to 1 in breth0 and there is an iprule that sends the packet to mpX interface so kernel sees the packet - // having return path different from the incoming interface. - // The SNAT to nodeIP should fix this. - // this causes curl timeout with code 28 - errBool = true - out = curlConnectionTimeoutCode - } - } - return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/hostname", out, errBool + + clientNode, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), clientPod.Spec.NodeName, metav1.GetOptions{}) + framework.ExpectNoError(err) + clientNodeIP := clientNode.Status.Addresses[ipFamilyIndex].Address + // pod -> node traffic should use the node's IP as the source for advertised UDNs. + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/clientip", clientNodeIP, false + }), + ginkgo.Entry("UDN pod to the same node nodeport service in default network should work (should it? :)...)", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // podsNetA[0] is on nodes[0]. We need the same node. Let's hit the nodeport on nodes[0]. + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[0].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetDefault.Spec.Ports[0].NodePort + + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false + }), + ginkgo.Entry("UDN pod to a different node nodeport service in default network should work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // podsNetA[0] is on nodes[0]. We need a different node. podNetDefault is on nodes[1]. + // The service is backed by podNetDefault. Let's hit the nodeport on nodes[2]. + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[2].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetDefault.Spec.Ports[0].NodePort + + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false + }), + ginkgo.Entry("UDN pod to the same node nodeport service in same UDN network should work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // The service is backed by pods in podsNetA. + // We want to hit the nodeport on the same node. + // client is on nodes[0]. Let's hit nodeport on nodes[0]. + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[0].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetA.Spec.Ports[0].NodePort + + // The service can be backed by any of the pods in podsNetA, so we can't reliably check the output hostname. + // Just check that the connection is successful. + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false + }), + ginkgo.Entry("UDN pod to a different node nodeport service in same UDN network should work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // The service is backed by pods in podsNetA. + // We want to hit the nodeport on a different node. + // client is on nodes[0]. Let's hit nodeport on nodes[2]. + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[2].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetA.Spec.Ports[0].NodePort + + // sourceIP will be joinSubnetIP for nodeports, so only using hostname endpoint + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false + }), + ginkgo.Entry("UDN pod to the same node nodeport service in different UDN network should not work", + // FIXME: This test should work: https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5419 + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[0].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetB.Spec.Ports[0].NodePort + + // sourceIP will be joinSubnetIP for nodeports, so only using hostname endpoint + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", curlConnectionTimeoutCode, true + }), + ginkgo.Entry("UDN pod to a different node nodeport service in different UDN network should work", + func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { + clientPod := podsNetA[0] + // The service is backed by podNetB. + // We want to hit the nodeport on a different node from the client. + // client is on nodes[0]. Let's hit nodeport on nodes[2]. + node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[2].Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + nodeIP := node.Status.Addresses[ipFamilyIndex].Address + nodePort := svcNetB.Spec.Ports[0].NodePort + + // sourceIP will be joinSubnetIP for nodeports, so only using hostname endpoint + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false }), ) From 10ea4ab4a2222577f54b03d84491c6abd7e17d23 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Fri, 11 Jul 2025 14:01:33 +0200 Subject: [PATCH 175/278] Add masqueradeIP flows back for advertised networks in breth0 In the previous commits we added SNATing to nodeIP for the following traffic flows: pod -> nodes pod -> nodeports when pods are part of advertised networks. Prior to SNATing to nodeIPs they are SNATed at the ovn_cluster_router to masqueradeIP before being sent into the host. In commit https://github.com/ovn-kubernetes/ovn-kubernetes/commit/75dd73fb645bff6c30e8c08c9b7f711d82996601 we had converted all UDN flows that matched on masqueradeIP as the source on breth0 for UDN pods to services traffic flow to instead match on the podsubnets. However given we have pod to node and pod to nodeport traffic flows using masqueradeIP as the SNAT we need to now re-add the masqueradeIP flows as well to ensure that nodeports isolation between UDNs work correctly. Before this commit: In LGW/SGW flow is: UDN pod -> samenodeIP:nodeport in default network -> SNATed to masqueradeIP of that UDN -> sent to host -> SNATed to clusterIP -> hits the default flow in table=2 in br-ex: cookie=0xdeff105, duration=15690.053s, table=2, n_packets=0, n_bytes=0, idle_age=15690, priority=100 actions=mod_dl_dst:6e:4d:97:c0:3c:97,output:2 and sends to patch port of default network and this traffic starts working when it shouldn't. (I mean eventually we want this to work, see https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5410 but that's a future issue - outside my PR's scope) In case of L3 UDN advertised pod -> nodeport service in default or other UDN network: https://github.com/ovn-kubernetes/ovn-kubernetes/pull/4705/commits/d63887ed167da260d3f26c71ec06e520d89a4b0f is the commit where we added logic to match on srcIP of the traffic and accordingly route it into the respective UDN patchports. So there we use the masqueradeIP of a particular UDN to determine what the source of the traffic was and route it into that particular UDN's patchport where it would backhole if there was no matching clusterIP NAT entry there, and this is how isolation was guaranteed. Recently this was changed to a hard drop: https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5351/commits/dcc403c1ddf11e30e6990699616405f6dc47dd71 For l2 topology the logic is same as above for clusterIPs but for nodeports the GR itself drops the packets destined towards the other networks as there is no LB entry present on the GR as the destination IP is that of the router itself. That's how isolation works there: sample trace: next; 10. ls_out_apply_port_sec (northd.c:6039): 1, priority 0, uuid 2aa6ebd5 output; /* output to "stor-cluster_udn_tenant.blue.network_ovn_layer2_switch", type "l3gateway" */ ingress(dp="GR_cluster_udn_tenant.blue.network_ovn-worker2", inport="rtos-cluster_udn_tenant.blue.network_ovn_layer2_switch") ----------------------------------------------------------------------------------------------------------------------------- 0. lr_in_admission (northd.c:13232): eth.dst == 0a:58:64:41:00:03 && inport == "rtos-cluster_udn_tenant.blue.network_ovn_layer2_switch", priority 50, uuid 7f9af183 reg9[1] = check_pkt_larger(1414); xreg0[0..47] = 0a:58:64:41:00:03; next; 1. lr_in_lookup_neighbor (northd.c:13420): 1, priority 0, uuid d2672052 reg9[2] = 1; next; 2. lr_in_learn_neighbor (northd.c:13430): reg9[2] == 1 || reg9[3] == 0, priority 100, uuid 84ca0ef4 mac_cache_use; next; 3. lr_in_ip_input (northd.c:12824): ip4.dst == {172.18.0.4}, priority 60, uuid ea41c4e7 drop; Without this fix: [FAIL] BGP: isolation between advertised networks Layer3 connectivity between networks [It] pod in the UDN should not be able to access a default network service the above test will work in LGW when it should not work like is the case for non-advertised UDNs. This commit adds back the masqueradeIP flow as well for advertised networks that drops all packets that didn't get routed on the higher priority pkt_mark flows at 250. when 2 UDNs are advertised: this PR added back these two flows with masqueradeIP match: cookie=0xdeff105, duration=127.593s, table=2, n_packets=0, n_bytes=0, priority=200,ip,nw_src=169.254.0.12 actions=drop cookie=0xdeff105, duration=127.534s, table=2, n_packets=0, n_bytes=0, priority=200,ip,nw_src=169.254.0.14 actions=drop Signed-off-by: Surya Seetharaman --- .../pkg/node/bridgeconfig/bridgeflows.go | 25 +++++++++++-------- go-controller/pkg/node/gateway_udn_test.go | 6 +++-- test/e2e/route_advertisements.go | 5 ++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/go-controller/pkg/node/bridgeconfig/bridgeflows.go b/go-controller/pkg/node/bridgeconfig/bridgeflows.go index d03b88c8de..200c1540ec 100644 --- a/go-controller/pkg/node/bridgeconfig/bridgeflows.go +++ b/go-controller/pkg/node/bridgeconfig/bridgeflows.go @@ -349,13 +349,12 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string bridgeMacAddress, mod_vlan_id, defaultNetConfig.OfPortPatch)) // table 2, priority 200, dispatch from UDN -> Host -> OVN. These packets have - // already been SNATed to the UDN's masq IP or have been marked with the UDN's packet mark. + // already been SNATed to the UDN's masquerade IP or have been marked with the UDN's packet mark. if config.IPv4Mode { for _, netConfig := range b.patchedNetConfigs() { if netConfig.IsDefaultNetwork() { continue } - srcIPOrSubnet := netConfig.V4MasqIPs.ManagementPort.IP.String() if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { var udnAdvertisedSubnets []*net.IPNet for _, clusterEntry := range netConfig.Subnets { @@ -368,9 +367,14 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string klog.Infof("Unable to determine IPV4 UDN subnet for the provided family isIPV6: %v", err) continue } - - // Use the filtered subnets for the flow compute instead of the masqueradeIP - srcIPOrSubnet = matchingIPFamilySubnet.String() + // In addition to the masqueradeIP based flows, we also need the podsubnet based flows for + // advertised networks since UDN pod to clusterIP is unSNATed and we need this traffic to be taken into + // the correct patch port of it's own network where it's a deadend if the clusterIP is not part of + // that UDN network and works if it is part of the UDN network. + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+ + "actions=drop", + nodetypes.DefaultOpenFlowCookie, matchingIPFamilySubnet.String())) } // Drop traffic coming from the masquerade IP or the UDN subnet(for advertised UDNs) to ensure that // isolation between networks is enforced. This handles the case where a pod on the UDN subnet is sending traffic to @@ -378,7 +382,7 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+ "actions=drop", - nodetypes.DefaultOpenFlowCookie, srcIPOrSubnet)) + nodetypes.DefaultOpenFlowCookie, netConfig.V4MasqIPs.ManagementPort.IP.String())) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+ @@ -393,7 +397,6 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string if netConfig.IsDefaultNetwork() { continue } - srcIPOrSubnet := netConfig.V6MasqIPs.ManagementPort.IP.String() if util.IsRouteAdvertisementsEnabled() && netConfig.Advertised.Load() { var udnAdvertisedSubnets []*net.IPNet for _, clusterEntry := range netConfig.Subnets { @@ -407,13 +410,15 @@ func (b *BridgeConfiguration) flowsForDefaultBridge(extraIPs []net.IP) ([]string continue } - // Use the filtered subnets for the flow compute instead of the masqueradeIP - srcIPOrSubnet = matchingIPFamilySubnet.String() + dftFlows = append(dftFlows, + fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+ + "actions=drop", + nodetypes.DefaultOpenFlowCookie, matchingIPFamilySubnet.String())) } dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+ "actions=drop", - nodetypes.DefaultOpenFlowCookie, srcIPOrSubnet)) + nodetypes.DefaultOpenFlowCookie, netConfig.V6MasqIPs.ManagementPort.IP.String())) dftFlows = append(dftFlows, fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+ "actions=set_field:%s->eth_dst,output:%s", diff --git a/go-controller/pkg/node/gateway_udn_test.go b/go-controller/pkg/node/gateway_udn_test.go index 0ab0bf573b..bd05aacd57 100644 --- a/go-controller/pkg/node/gateway_udn_test.go +++ b/go-controller/pkg/node/gateway_udn_test.go @@ -1143,7 +1143,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() { Expect(udnGateway.AddNetwork()).To(Succeed()) flowMap = udnGateway.gateway.openflowManager.flowCache - Expect(flowMap["DEFAULT"]).To(HaveLen(69)) // 18 UDN Flows and 5 advertisedUDN flows are added by default + Expect(flowMap["DEFAULT"]).To(HaveLen(71)) // 18 UDN Flows, 5 advertisedUDN flows, and 2 packet mark flows (IPv4+IPv6) are added by default Expect(udnGateway.openflowManager.defaultBridge.GetNetConfigLen()).To(Equal(2)) // default network + UDN network defaultUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("default") bridgeUdnConfig := udnGateway.openflowManager.defaultBridge.GetNetworkConfig("bluenet") @@ -1166,7 +1166,9 @@ var _ = Describe("UserDefinedNetworkGateway", func() { // Check flows for default network service CIDR. bridgeconfig.CheckDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR) - // Expect exactly one flow per advertised UDN for table 2 and table 0 for service isolation. + // Expect exactly two flow per advertised UDN for table 2 and table 0 for service isolation. + // but one of the flows used by advertised UDNs is already tracked and used by default UDNs hence not + // counted here but in the check above for default svc isolation flows. bridgeconfig.CheckAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 2) } diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index d46d0a9409..026dfc5901 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -980,7 +980,8 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" // pod -> node traffic should use the node's IP as the source for advertised UDNs. return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(hostNetworkPort)) + "/clientip", clientNodeIP, false }), - ginkgo.Entry("UDN pod to the same node nodeport service in default network should work (should it? :)...)", + ginkgo.Entry("UDN pod to the same node nodeport service in default network should not work", + // FIXME: https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5410 func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { clientPod := podsNetA[0] // podsNetA[0] is on nodes[0]. We need the same node. Let's hit the nodeport on nodes[0]. @@ -989,7 +990,7 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" nodeIP := node.Status.Addresses[ipFamilyIndex].Address nodePort := svcNetDefault.Spec.Ports[0].NodePort - return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", "", false + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", curlConnectionTimeoutCode, true }), ginkgo.Entry("UDN pod to a different node nodeport service in default network should work", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { From 8f5b3d4688db4ba150431a41c49f9a5e569d8228 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Sat, 12 Jul 2025 21:12:52 +0200 Subject: [PATCH 176/278] Change priority of ovn-kube-local-gw-masq to 101 Currently there are two bugs around using priority 100 for ovn-kube-local-gw-masq chain. EgressIPs multinic rules are still in legacy IPT: [0:0] -A OVN-KUBE-EGRESS-IP-MULTI-NIC -s 10.244.2.6/32 -o eth1 -j SNAT --to-source 10.10.10.105 [0:0] -A OVN-KUBE-EGRESS-IP-MULTI-NIC -s 10.244.0.3/32 -o eth1 -j SNAT --to-source 10.10.10.105 [1:60] -A OVN-KUBE-EGRESS-IP-MULTI-NIC -s 10.244.1.3/32 -o eth1 -j SNAT --to-source 10.10.10.105 and in netfilter the priority of NAT POSTROUTNG HOOK is 100 and not configurable. NF_IP_PRI_NAT_SRC in netfilter and for NFTables its the same value 100 for NAT POSTROUTING hook and its called "srcnat" in knftables and set to 100. and this is the priority used by egress service feature since that is already converted to NFT: chain egress-services { type nat hook postrouting priority srcnat; policy accept; meta mark 0x000003f0 return comment "DoNotSNAT" snat ip to ip saddr map @egress-service-snat-v4 snat ip6 to ip6 saddr map @egress-service-snat-v6 } and now that we have converted POSTROUTING rules for local-gw as well to NFT, those rules were already at priority 100. Unlike IPT rules where we could jump to EIP and ESVC chains before masquerade rules got hit, here those chains in NFT are all parallel at same priority 100 and we don't know which one will be hit first. Hence we need to change the priority of ovn-kube-local-gw-masq so that EIP/ESVC rules are hit before the default masquerade rules W/O this change EIP/ESVC tests fail in CI Signed-off-by: Surya Seetharaman --- go-controller/pkg/node/gateway_init_linux_test.go | 2 +- go-controller/pkg/node/gateway_nftables.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/node/gateway_init_linux_test.go b/go-controller/pkg/node/gateway_init_linux_test.go index 9bc0cc5401..06fe88aace 100644 --- a/go-controller/pkg/node/gateway_init_linux_test.go +++ b/go-controller/pkg/node/gateway_init_linux_test.go @@ -87,7 +87,7 @@ add rule inet ovn-kubernetes ovn-kube-local-gw-masq jump ovn-kube-udn-masq const baseLGWNFTablesRules = ` add rule inet ovn-kubernetes ovn-kube-local-gw-masq ip saddr 169.254.169.1 masquerade -add chain inet ovn-kubernetes ovn-kube-local-gw-masq { type nat hook postrouting priority 100 ; comment "OVN local gateway masquerade" ; } +add chain inet ovn-kubernetes ovn-kube-local-gw-masq { type nat hook postrouting priority 101 ; comment "OVN local gateway masquerade" ; } add rule inet ovn-kubernetes ovn-kube-local-gw-masq jump ovn-kube-pod-subnet-masq add rule inet ovn-kubernetes ovn-kube-pod-subnet-masq ip saddr 10.1.1.0/24 masquerade add chain inet ovn-kubernetes ovn-kube-pod-subnet-masq diff --git a/go-controller/pkg/node/gateway_nftables.go b/go-controller/pkg/node/gateway_nftables.go index 71a4d23b9e..b38f2baebb 100644 --- a/go-controller/pkg/node/gateway_nftables.go +++ b/go-controller/pkg/node/gateway_nftables.go @@ -366,12 +366,18 @@ func initLocalGatewayNFTNATRules(cidrs ...*net.IPNet) error { tx := nft.NewTransaction() // Create main local gateway masquerade chain + // Use priority 101 instead of defaultknftables.SNATPriority (100) to ensure + // iptables egress IP rules in OVN-KUBE-EGRESS-IP-MULTI-NIC chain run first + // this also ensure for egress-services, the + // chain egress-services { + // type nat hook postrouting priority srcnat; policy accept; + // is called before the local gateway masquerade chain localGwMasqChain := &knftables.Chain{ Name: nftablesLocalGatewayMasqChain, Comment: knftables.PtrTo("OVN local gateway masquerade"), Type: knftables.PtrTo(knftables.NATType), Hook: knftables.PtrTo(knftables.PostroutingHook), - Priority: knftables.PtrTo(knftables.SNATPriority), + Priority: knftables.PtrTo(knftables.BaseChainPriority("101")), } tx.Add(localGwMasqChain) From 659010cfd44af66c071e347ed1e2051494fec68e Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Mon, 21 Jul 2025 09:59:41 +0200 Subject: [PATCH 177/278] Add all remote nodeIPs for the PMTUD/BGP remote node NFT set Prior to this change, the remote PMTUD address sets were only considering the primary IP of the node. While that was OK for PMTUD use case perhaps but for BGP now that we reuse this address set in NFT we need to consider all the IPs on the remote nodes. So this commit changes code from using node internal IPs to using the HostCIDRs annotation Signed-off-by: Surya Seetharaman --- .../node/default_node_network_controller.go | 90 ++++++++++--------- .../default_node_network_controller_test.go | 24 +++++ go-controller/pkg/node/obj_retry_node.go | 53 ++++++----- 3 files changed, 105 insertions(+), 62 deletions(-) diff --git a/go-controller/pkg/node/default_node_network_controller.go b/go-controller/pkg/node/default_node_network_controller.go index db7d26802d..47ba8f6262 100644 --- a/go-controller/pkg/node/default_node_network_controller.go +++ b/go-controller/pkg/node/default_node_network_controller.go @@ -1515,23 +1515,32 @@ func (nc *DefaultNodeNetworkController) WatchNodes() error { func (nc *DefaultNodeNetworkController) addOrUpdateNode(node *corev1.Node) error { var nftElems []*knftables.Element var addrs []string - for _, address := range node.Status.Addresses { - if address.Type != corev1.NodeInternalIP { - continue - } - nodeIP := net.ParseIP(address.Address) - if nodeIP == nil { - continue - } + // Use GetNodeAddresses to get all node IPs (including current node for openflow) + ipsv4, ipsv6, err := util.GetNodeAddresses(config.IPv4Mode, config.IPv6Mode, node) + if err != nil { + return fmt.Errorf("failed to get node addresses for node %q: %w", node.Name, err) + } + + // Process IPv4 addresses + for _, nodeIP := range ipsv4 { addrs = append(addrs, nodeIP.String()) klog.Infof("Adding remote node %q, IP: %s to PMTUD blocking rules", node.Name, nodeIP) - if utilnet.IsIPv4(nodeIP) { + // Only add to nftables if this is remote node + if node.Name != nc.name { nftElems = append(nftElems, &knftables.Element{ Set: types.NFTRemoteNodeIPsv4, Key: []string{nodeIP.String()}, }) - } else { + } + } + + // Process IPv6 addresses + for _, nodeIP := range ipsv6 { + addrs = append(addrs, nodeIP.String()) + klog.Infof("Adding remote node %q, IP: %s to PMTUD blocking rules", node.Name, nodeIP) + // Only add to nftables if this is remote node + if node.Name != nc.name { nftElems = append(nftElems, &knftables.Element{ Set: types.NFTRemoteNodeIPsv6, Key: []string{nodeIP.String()}, @@ -1578,18 +1587,18 @@ func removePMTUDNodeNFTRules(nodeIPs []net.IP) error { func (nc *DefaultNodeNetworkController) deleteNode(node *corev1.Node) { gw := nc.Gateway.(*gateway) gw.openflowManager.deleteFlowsByKey(getPMTUDKey(node.Name)) - ipsToRemove := make([]net.IP, 0) - for _, address := range node.Status.Addresses { - if address.Type != corev1.NodeInternalIP { - continue - } - nodeIP := net.ParseIP(address.Address) - if nodeIP == nil { - continue - } - ipsToRemove = append(ipsToRemove, nodeIP) + + // Use GetNodeAddresses to get node IPs + ipsv4, ipsv6, err := util.GetNodeAddresses(config.IPv4Mode, config.IPv6Mode, node) + if err != nil { + klog.Errorf("Failed to get node addresses for node %q: %v", node.Name, err) + return } + ipsToRemove := make([]net.IP, 0, len(ipsv4)+len(ipsv6)) + ipsToRemove = append(ipsToRemove, ipsv4...) + ipsToRemove = append(ipsToRemove, ipsv6...) + klog.Infof("Deleting NFT elements for node: %s", node.Name) if err := removePMTUDNodeNFTRules(ipsToRemove); err != nil { klog.Errorf("Failed to delete nftables rules for PMTUD blocking for node %q: %v", node.Name, err) @@ -1610,27 +1619,28 @@ func (nc *DefaultNodeNetworkController) syncNodes(objs []interface{}) error { if node.Name == nc.name { continue } - for _, address := range node.Status.Addresses { - if address.Type != corev1.NodeInternalIP { - continue - } - nodeIP := net.ParseIP(address.Address) - if nodeIP == nil { - continue - } - // Remove IPs from NFT sets - if utilnet.IsIPv4(nodeIP) { - keepNFTSetElemsV4 = append(keepNFTSetElemsV4, &knftables.Element{ - Set: types.NFTRemoteNodeIPsv4, - Key: []string{nodeIP.String()}, - }) - } else { - keepNFTSetElemsV6 = append(keepNFTSetElemsV6, &knftables.Element{ - Set: types.NFTRemoteNodeIPsv6, - Key: []string{nodeIP.String()}, - }) - } + // Use GetNodeAddresses to get node IPs + ipsv4, ipsv6, err := util.GetNodeAddresses(config.IPv4Mode, config.IPv6Mode, node) + if err != nil { + klog.Errorf("Failed to get node addresses for node %q: %v", node.Name, err) + continue + } + + // Process IPv4 addresses + for _, nodeIP := range ipsv4 { + keepNFTSetElemsV4 = append(keepNFTSetElemsV4, &knftables.Element{ + Set: types.NFTRemoteNodeIPsv4, + Key: []string{nodeIP.String()}, + }) + } + + // Process IPv6 addresses + for _, nodeIP := range ipsv6 { + keepNFTSetElemsV6 = append(keepNFTSetElemsV6, &knftables.Element{ + Set: types.NFTRemoteNodeIPsv6, + Key: []string{nodeIP.String()}, + }) } } if err := recreateNFTSet(types.NFTRemoteNodeIPsv4, keepNFTSetElemsV4); err != nil { diff --git a/go-controller/pkg/node/default_node_network_controller_test.go b/go-controller/pkg/node/default_node_network_controller_test.go index 24c3141357..366ee881d6 100644 --- a/go-controller/pkg/node/default_node_network_controller_test.go +++ b/go-controller/pkg/node/default_node_network_controller_test.go @@ -755,6 +755,9 @@ var _ = Describe("Node", func() { node := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIP+"/24"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -769,6 +772,9 @@ var _ = Describe("Node", func() { otherNode := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteNodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", otherNodeIP+"/24"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -860,6 +866,9 @@ add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.254.61 } node := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIP+"/24"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -874,6 +883,9 @@ add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.254.61 } otherNode := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteNodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", otherSubnetNodeIP+"/24"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -1007,6 +1019,9 @@ add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.253.61 } node := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIP+"/64"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -1021,6 +1036,9 @@ add element inet ovn-kubernetes remote-node-ips-v4 { 169.254.253.61 } otherNode := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteNodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", otherNodeIP+"/64"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -1111,6 +1129,9 @@ add element inet ovn-kubernetes remote-node-ips-v6 { 2001:db8:1::4 } node := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", nodeIP+"/64"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ @@ -1125,6 +1146,9 @@ add element inet ovn-kubernetes remote-node-ips-v6 { 2001:db8:1::4 } otherNode := corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteNodeName, + Annotations: map[string]string{ + util.OVNNodeHostCIDRs: fmt.Sprintf("[\"%s\"]", otherSubnetNodeIP+"/64"), + }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ diff --git a/go-controller/pkg/node/obj_retry_node.go b/go-controller/pkg/node/obj_retry_node.go index 9c9657678e..646cca2ac3 100644 --- a/go-controller/pkg/node/obj_retry_node.go +++ b/go-controller/pkg/node/obj_retry_node.go @@ -238,34 +238,43 @@ func (h *nodeEventHandler) UpdateResource(oldObj, newObj interface{}, _ bool) er return nil } - // remote node that is changing - ipsToKeep := map[string]bool{} - for _, address := range newNode.Status.Addresses { - if address.Type != corev1.NodeInternalIP { - continue + if util.NodeHostCIDRsAnnotationChanged(oldNode, newNode) { + // remote node that is changing + // Use GetNodeAddresses to get new node IPs + newIPsv4, newIPsv6, err := util.GetNodeAddresses(config.IPv4Mode, config.IPv6Mode, newNode) + if err != nil { + return fmt.Errorf("failed to get addresses for new node %q: %w", newNode.Name, err) } - nodeIP := net.ParseIP(address.Address) - if nodeIP == nil { - continue + + ipsToKeep := map[string]bool{} + for _, nodeIP := range newIPsv4 { + ipsToKeep[nodeIP.String()] = true } - ipsToKeep[nodeIP.String()] = true - } - ipsToRemove := make([]net.IP, 0) - for _, address := range oldNode.Status.Addresses { - if address.Type != corev1.NodeInternalIP { - continue + for _, nodeIP := range newIPsv6 { + ipsToKeep[nodeIP.String()] = true } - nodeIP := net.ParseIP(address.Address) - if nodeIP == nil { - continue + + // Use GetNodeAddresses to get old node IPs + oldIPsv4, oldIPsv6, err := util.GetNodeAddresses(config.IPv4Mode, config.IPv6Mode, oldNode) + if err != nil { + return fmt.Errorf("failed to get addresses for old node %q: %w", oldNode.Name, err) } - if _, exists := ipsToKeep[nodeIP.String()]; !exists { - ipsToRemove = append(ipsToRemove, nodeIP) + + ipsToRemove := make([]net.IP, 0) + for _, nodeIP := range oldIPsv4 { + if _, exists := ipsToKeep[nodeIP.String()]; !exists { + ipsToRemove = append(ipsToRemove, nodeIP) + } + } + for _, nodeIP := range oldIPsv6 { + if _, exists := ipsToKeep[nodeIP.String()]; !exists { + ipsToRemove = append(ipsToRemove, nodeIP) + } } - } - if err := removePMTUDNodeNFTRules(ipsToRemove); err != nil { - return fmt.Errorf("error removing node %q stale NFT rules during update: %w", oldNode.Name, err) + if err := removePMTUDNodeNFTRules(ipsToRemove); err != nil { + return fmt.Errorf("error removing node %q stale NFT rules during update: %w", oldNode.Name, err) + } } return h.nc.addOrUpdateNode(newNode) From 0635caef5d9a60f132b99889dd8a1376663c4294 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Mon, 21 Jul 2025 22:42:32 +0200 Subject: [PATCH 178/278] cleanupStalePodSNATs: Don't blow all SNATs for advertised Networks Signed-off-by: Surya Seetharaman --- go-controller/pkg/ovn/gateway.go | 11 +++++------ go-controller/pkg/ovn/master_test.go | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/go-controller/pkg/ovn/gateway.go b/go-controller/pkg/ovn/gateway.go index 66bce44dfe..d652dbcd8a 100644 --- a/go-controller/pkg/ovn/gateway.go +++ b/go-controller/pkg/ovn/gateway.go @@ -143,8 +143,8 @@ func WithLoadBalancerGroups(routerLBGroup, clusterLBGroup, switchLBGroup string) } // cleanupStalePodSNATs removes pod SNATs against nodeIP for the given node if -// the SNAT.logicalIP isn't an active podIP, the pod network is being advertised -// on this node or disableSNATMultipleGWs=false. We don't have to worry about +// the SNAT.logicalIP isn't an active podIP, or disableSNATMultipleGWs=false. +// We don't have to worry about // missing SNATs that should be added because addLogicalPort takes care of this // for all pods when RequestRetryObjs is called for each node add. // Other non-pod SNATs like join subnet SNATs are ignored. @@ -154,11 +154,11 @@ func WithLoadBalancerGroups(routerLBGroup, clusterLBGroup, switchLBGroup string) // pod->nodeSNATs which won't get cleared up unless explicitly deleted. // NOTE2: egressIP SNATs are synced in EIP controller. func (gw *GatewayManager) cleanupStalePodSNATs(nodeName string, nodeIPs []*net.IPNet, gwLRPIPs []net.IP) error { - // collect all the pod IPs for which we should be doing the SNAT; if the pod - // network is advertised or DisableSNATMultipleGWs==false we consider all + // collect all the pod IPs for which we should be doing the SNAT; + // if DisableSNATMultipleGWs==false we consider all // the SNATs stale podIPsWithSNAT := sets.New[string]() - if !gw.isRoutingAdvertised(nodeName) && config.Gateway.DisableSNATMultipleGWs { + if config.Gateway.DisableSNATMultipleGWs { pods, err := gw.watchFactory.GetAllPods() if err != nil { return fmt.Errorf("unable to list existing pods on node: %s, %w", @@ -231,7 +231,6 @@ func (gw *GatewayManager) cleanupStalePodSNATs(nodeName string, nodeIPs []*net.I } natsToDelete = append(natsToDelete, routerNat) } - if len(natsToDelete) > 0 { err := libovsdbops.DeleteNATs(gw.nbClient, gatewayRouter, natsToDelete...) if err != nil { diff --git a/go-controller/pkg/ovn/master_test.go b/go-controller/pkg/ovn/master_test.go index 866b6309fa..197ecf5c24 100644 --- a/go-controller/pkg/ovn/master_test.go +++ b/go-controller/pkg/ovn/master_test.go @@ -1259,6 +1259,12 @@ var _ = ginkgo.Describe("Default network controller operations", func() { newNodeSNAT("stale-nodeNAT-UUID-3", "10.0.0.3", Node1GatewayRouterIP), newNodeSNAT("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3"), } + extraNatsWithMatch := []*nbdb.NAT{ // used for pod network advertised test + newNodeSNATWithMatch("stale-nodeNAT-UUID-1", "10.1.0.3", Node1GatewayRouterIP, "ip4.dst == $a712973235162149816"), + newNodeSNATWithMatch("stale-nodeNAT-UUID-2", "10.2.0.3", Node1GatewayRouterIP, "ip4.dst == $a712973235162149816"), + newNodeSNATWithMatch("stale-nodeNAT-UUID-3", "10.0.0.3", Node1GatewayRouterIP, "ip4.dst == $a712973235162149816"), + newNodeSNATWithMatch("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3", "ip4.dst == $a712973235162149816"), + } ginkgo.DescribeTable( "reconciles pod network SNATs from syncGateway", func(condition func(*DefaultNetworkController) error, expectedExtraNATs ...*nbdb.NAT) { @@ -1284,7 +1290,11 @@ var _ = ginkgo.Describe("Default network controller operations", func() { GR := &nbdb.LogicalRouter{ Name: types.GWRouterPrefix + node1.Name, } - err = libovsdbops.CreateOrUpdateNATs(nbClient, GR, extraNats...) + if !oc.isPodNetworkAdvertisedAtNode(node1.Name) { + err = libovsdbops.CreateOrUpdateNATs(nbClient, GR, extraNats...) + } else { + err = libovsdbops.CreateOrUpdateNATs(nbClient, GR, extraNatsWithMatch...) + } gomega.Expect(err).NotTo(gomega.HaveOccurred()) // ensure the stale SNAT's are cleaned up @@ -1366,7 +1376,10 @@ var _ = ginkgo.Describe("Default network controller operations", func() { mutableNetInfo.SetPodNetworkAdvertisedVRFs(map[string][]string{"node1": {"vrf"}}) return oc.Reconcile(mutableNetInfo) }, - newNodeSNAT("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3"), // won't be deleted on this node but will be deleted on the node whose IP is 172.16.16.3 since this pod belongs to this node + // won't be deleted on this node since this pod belongs to node-1 and is advertised so we keep this SNAT + newNodeSNATWithMatch("stale-nodeNAT-UUID-3", "10.0.0.3", Node1GatewayRouterIP, "ip4.dst == $a712973235162149816"), + // won't be deleted on this node but will be deleted on the node whose IP is 172.16.16.3 since this pod belongs to node-1 + newNodeSNATWithMatch("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3", "ip4.dst == $a712973235162149816"), ), ginkgo.Entry( "When pod network is advertised and DisableSNATMultipleGWs is false", @@ -1377,7 +1390,7 @@ var _ = ginkgo.Describe("Default network controller operations", func() { mutableNetInfo.SetPodNetworkAdvertisedVRFs(map[string][]string{"node1": {"vrf"}}) return oc.Reconcile(mutableNetInfo) }, - newNodeSNAT("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3"), // won't be deleted on this node but will be deleted on the node whose IP is 172.16.16.3 since this pod belongs to this node + newNodeSNATWithMatch("stale-nodeNAT-UUID-4", "10.0.0.3", "172.16.16.3", "ip4.dst == $a712973235162149816"), // won't be deleted on this node but will be deleted on the node whose IP is 172.16.16.3 since this pod belongs to this node ), ) @@ -1982,6 +1995,12 @@ func newNodeSNAT(uuid, logicalIP, externalIP string) *nbdb.NAT { } } +func newNodeSNATWithMatch(uuid, logicalIP, externalIP, match string) *nbdb.NAT { + nat := newNodeSNAT(uuid, logicalIP, externalIP) + nat.Match = match + return nat +} + func TestController_syncNodes(t *testing.T) { gomega.RegisterFailHandler(ginkgo.Fail) From 5056d4da4e8177e32f9aa00121acc43d712d369a Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Wed, 23 Jul 2025 11:17:08 +0200 Subject: [PATCH 179/278] Fix CreateOrUpdateNATs to update non-default values When using the onModelUpdatesAllNonDefault() from NAT updates, it wasn't updating match value when we wanted to reset it. So when we went from advertised network to non-advertised network, we were not changing the SNAT match and hence traffic was still going out with podIP instead of nodeIP. This commit fixes that. Signed-off-by: Surya Seetharaman --- go-controller/pkg/libovsdb/ops/router.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/libovsdb/ops/router.go b/go-controller/pkg/libovsdb/ops/router.go index 27e8e38d48..5f0ce594d4 100644 --- a/go-controller/pkg/libovsdb/ops/router.go +++ b/go-controller/pkg/libovsdb/ops/router.go @@ -932,6 +932,11 @@ func RemoveLoadBalancersFromLogicalRouterOps(nbClient libovsdbclient.Client, ops return ops, err } +func getNATMutableFields(nat *nbdb.NAT) []interface{} { + return []interface{}{&nat.Type, &nat.ExternalIP, &nat.LogicalIP, &nat.LogicalPort, &nat.ExternalMAC, + &nat.ExternalIDs, &nat.Match, &nat.Options, &nat.ExternalPortRange, &nat.GatewayPort, &nat.Priority} +} + func buildNAT( natType nbdb.NATType, externalIP string, @@ -1152,7 +1157,7 @@ func CreateOrUpdateNATsOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation } opModel := operationModel{ Model: inputNat, - OnModelUpdates: onModelUpdatesAllNonDefault(), + OnModelUpdates: getNATMutableFields(inputNat), ErrNotFound: false, BulkOp: false, DoAfter: func() { router.Nat = append(router.Nat, inputNat.UUID) }, @@ -1280,7 +1285,7 @@ func UpdateNATOps(nbClient libovsdbclient.Client, ops []ovsdb.Operation, nats .. opModel := []operationModel{ { Model: nat, - OnModelUpdates: onModelUpdatesAllNonDefault(), + OnModelUpdates: getNATMutableFields(nat), ErrNotFound: true, BulkOp: false, }, From bcd06566dd363440e6e0719e9ddec35425bae5fa Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Mon, 28 Jul 2025 16:38:13 +0200 Subject: [PATCH 180/278] Bump OVN to 25.03 Signed-off-by: Surya Seetharaman --- dist/images/Dockerfile.fedora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/images/Dockerfile.fedora b/dist/images/Dockerfile.fedora index 4ca51e888f..fc42191887 100644 --- a/dist/images/Dockerfile.fedora +++ b/dist/images/Dockerfile.fedora @@ -79,7 +79,7 @@ RUN git log -n 1 # Stage to download OVN RPMs from koji # ######################################## FROM quay.io/fedora/fedora:42 AS kojidownloader -ARG ovnver=ovn-24.09.2-71.fc42 +ARG ovnver=ovn-25.03.1-42.fc42 USER root From e8fc76449f4f2a3259c4576de41abe6f3cd2110f Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Mon, 28 Jul 2025 20:35:55 +0200 Subject: [PATCH 181/278] UDN,L2: UDN pod in networkA to nodePort on networkB works for IPV6! See https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5419 for details But the traffic flow looks like this for Layer3(v4 and v6) and Layer2(v4): pod in UDN A -> sameNodeIP:NodePort i.e 172.18.0.2:30724 pod (102.102.2.4)-> ovn-switch->ovn-cluster-router (SNAT to masqueradeIP 169.254.0.14)-> LRP send to mpX -> in the host (IPTable DNAT from nodePort to clusterIP 10.96.96.233:8080) send to breth0 breth0 flows reroute packet to UDN B's patchport hits the GR of UDNB and DNATs from clusterIP to backend pod that lives on another node (103.103.1.5) at the same time SNAT to joinIP in OVN router i.e 100.65.0.4 reponse comes back from remote pod and then we see ARP requests trying to understand how to reach the masqueradeIP of the other network which makes total sense - so reply fails NetworkB doesn't know how to reach back to NetworkA's masqueradeIP which is the srcIP. root@ovn-control-plane:/# tcpdump -i any -nneev port 36363 or port 30724 or host 102.102.2.4 or host 169.254.0.14 or host 100.65.0.4 tcpdump: data link type LINUX_SLL2 tcpdump: listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes 08:55:14.083364 865a53b516350_3 P ifindex 19 0a:58:66:66:02:04 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 64, id 53100, offset 0, flags [DF], proto TCP (6), length 60) 102.102.2.4.42720 > 172.18.0.2.30724: Flags [S], cksum 0x14ad (incorrect -> 0x5e6c), seq 432663101, win 65280, options [mss 1360,sackOK,TS val 1239378349 ecr 0,nop,wscale 7], length 0 08:55:14.084049 ovn-k8s-mp2 In ifindex 14 0a:58:66:66:02:01 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 63, id 53100, offset 0, flags [DF], proto TCP (6), length 60) 169.254.0.14.42826 > 172.18.0.2.30724: Flags [S], cksum 0x1c60 (correct), seq 432663101, win 65280, options [mss 1360,sackOK,TS val 1239378349 ecr 0,nop,wscale 7], length 0 08:55:14.084069 breth0 Out ifindex 6 6a:ed:17:fb:28:bd ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 62, id 53100, offset 0, flags [DF], proto TCP (6), length 60) 169.254.0.14.42826 > 10.96.96.233.8080: Flags [S], cksum 0xb59f (correct), seq 432663101, win 65280, options [mss 1360,sackOK,TS val 1239378349 ecr 0,nop,wscale 7], length 0 08:55:14.084470 genev_sys_6081 Out ifindex 7 0a:58:64:58:00:04 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 60, id 53100, offset 0, flags [DF], proto TCP (6), length 60) 100.65.0.4.42826 > 103.103.1.5.8080: Flags [S], cksum 0xfe43 (correct), seq 432663101, win 65280, options [mss 1360,sackOK,TS val 1239378349 ecr 0,nop,wscale 7], length 0 08:55:14.085494 genev_sys_6081 P ifindex 7 0a:58:64:58:00:02 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 60) 103.103.1.5.8080 > 100.65.0.4.42826: Flags [S.], cksum 0x1f4f (correct), seq 3390013464, ack 432663102, win 64704, options [mss 1360,sackOK,TS val 1866737591 ecr 1239378349,nop,wscale 7], length 0 08:55:14.086130 eth0 Out ifindex 2 6a:ed:17:fb:28:bd ethertype ARP (0x0806), length 48: Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.0.14 tell 169.254.0.15, length 28 08:55:14.086172 breth0 B ifindex 6 6a:ed:17:fb:28:bd ethertype ARP (0x0806), length 48: Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.0.14 tell 169.254.0.15, length 28 08:55:15.100558 genev_sys_6081 P ifindex 7 0a:58:64:58:00:02 ethertype IPv4 (0x0800), length 80: (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 60) 103.103.1.5.8080 > 100.65.0.4.42826: Flags [S.], cksum 0xccdf (incorrect -> 0x1b57), seq 3390013464, ack 432663102, win 64704, options [mss 1360,sackOK,TS val 1866738607 ecr 1239378349,nop,wscale 7], length 0 08:55:15.101090 eth0 Out ifindex 2 6a:ed:17:fb:28:bd ethertype ARP (0x0806), length 48: Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.0.14 tell 169.254.0.15, length 28 08:55:15.101124 breth0 B ifindex 6 6a:ed:17:fb:28:bd ethertype ARP (0x0806), length 48: Ethernet (len 6), IPv4 (len 4), Request who-has 169.254.0.14 tell 169.254.0.15, length 28 ^ its the same for Layer3 v6 as well and same for Layer2 v4 ^^ but Layer2 v6 is weird thanks to: // cookie=0xdeff105, duration=173.245s, table=1, n_packets=0, n_bytes=0, idle_age=173, priority=14,icmp6,icmp_type=134 actions=FLOOD // cookie=0xdeff105, duration=173.245s, table=1, n_packets=8, n_bytes=640, idle_age=4, priority=14,icmp6,icmp_type=136 actions=FLOOD these two flows on breth0 - these seem to be flooding the NDP requests between the GR's of all networks somehow and v6 works. So test is acknowledging this inconsistency and calling this out. Signed-off-by: Surya Seetharaman --- test/e2e/route_advertisements.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/e2e/route_advertisements.go b/test/e2e/route_advertisements.go index 026dfc5901..36c0c5c950 100644 --- a/test/e2e/route_advertisements.go +++ b/test/e2e/route_advertisements.go @@ -1035,15 +1035,40 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks" }), ginkgo.Entry("UDN pod to the same node nodeport service in different UDN network should not work", // FIXME: This test should work: https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5419 + // This traffic flow is expected to work eventually but doesn't work today on Layer3 (v4 and v6) and Layer2 (v4 only) networks. + // Reason it doesn't work today is because UDN networks don't have MAC bindings for masqueradeIPs of other networks. + // Traffic flow: UDN pod in network A -> samenode nodeIP:nodePort service of networkB + // UDN pod in networkA -> ovn-switch -> ovn-cluster-router (SNAT to masqueradeIP of networkA) -> mpX interface -> + // enters the host and hits IPTables rules to DNAT to clusterIP:Port of service of networkB. + // Then it hits the pkt_mark flows on breth0 and get's sent into networkB's patchport where it hits the GR. + // On the GR we DNAT to backend pod and SNAT to joinIP. + // Reply: Pod replies and now OVN in networkB tries to ARP for the masqueradeIP of networkA which is the source and simply + // fails as it doesn't know how to reach this masqueradeIP. + // There is also inconsistency in behaviour within Layer2 networks for how IPv4 works and how IPv6 works where the traffic + // works on ipv6 because of the flows described below. func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { clientPod := podsNetA[0] node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodes.Items[0].Name, metav1.GetOptions{}) framework.ExpectNoError(err) nodeIP := node.Status.Addresses[ipFamilyIndex].Address nodePort := svcNetB.Spec.Ports[0].NodePort + out := curlConnectionTimeoutCode + errBool := true + if ipFamilyIndex == ipFamilyV6 && cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 { + // For Layer2 networks, we have these flows we add on breth0: + // cookie=0xdeff105, duration=173.245s, table=1, n_packets=0, n_bytes=0, idle_age=173, priority=14,icmp6,icmp_type=134 actions=FLOOD + // cookie=0xdeff105, duration=173.245s, table=1, n_packets=8, n_bytes=640, idle_age=4, priority=14,icmp6,icmp_type=136 actions=FLOOD + // which floods the Router Advertisement (RA, type 134) and Neighbor Advertisement (NA, type 136) + // Given on Layer2 the GR has the SNATs for both masqueradeIPs this works perfectly well and + // the networks are able to NDP for the masqueradeIPs for the other networks. + // This doesn't work on Layer3 networks since masqueradeIP SNATs are present on the ovn-cluster-router in that case. + // See the tcpdump on the issue: https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5410 for more details. + out = "" + errBool = false + } // sourceIP will be joinSubnetIP for nodeports, so only using hostname endpoint - return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", curlConnectionTimeoutCode, true + return clientPod.Name, clientPod.Namespace, net.JoinHostPort(nodeIP, fmt.Sprint(nodePort)) + "/hostname", out, errBool }), ginkgo.Entry("UDN pod to a different node nodeport service in different UDN network should work", func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) { From 9b21fc066d954bacd511a899c3bca464fc97e21a Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Tue, 29 Jul 2025 11:09:19 +0200 Subject: [PATCH 182/278] Change OVN-Kubernetes community meeting time Makes this EMEA/US friendly. Signed-off-by: Surya Seetharaman --- MEETINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MEETINGS.md b/MEETINGS.md index 701459788f..8964025628 100644 --- a/MEETINGS.md +++ b/MEETINGS.md @@ -6,7 +6,7 @@ All are welcome to join our meetings! If you want to discuss something with the ## Meeting time -We meet alternate Monday's at 6:00 PM CET/CEST. +We meet alternate Monday's at 5:00 PM CET/CEST. In order to figure out when our next meeting is, please check our agenda for previous meeting history. The meetings last up to 1 hour. From cc6fe11a70b5c215883f4abf9d099523cc457956 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Thu, 19 Jun 2025 10:48:16 +0100 Subject: [PATCH 183/278] udn, pre assigned port net ids: provision the default net NAD CR The "pre assigned port net ids" feature requires a NAD for the `default` network to be provisioned. This commit pre-provisions that NAD whenever the feature - EnableCustomNetworkConfig - is enabled, upon starting the cluster manager. Signed-off-by: Miguel Duarte Barroso --- .../pkg/clustermanager/clustermanager_test.go | 107 +++++++++++++++++- .../routeadvertisements/controller.go | 30 +---- .../routeadvertisements/controller_test.go | 2 +- .../userdefinednetwork/controller.go | 6 + go-controller/pkg/util/multi_network.go | 6 + go-controller/pkg/util/nad.go | 46 ++++++++ 6 files changed, 161 insertions(+), 36 deletions(-) create mode 100644 go-controller/pkg/util/nad.go diff --git a/go-controller/pkg/clustermanager/clustermanager_test.go b/go-controller/pkg/clustermanager/clustermanager_test.go index f97de8fc3f..66535f4c8a 100644 --- a/go-controller/pkg/clustermanager/clustermanager_test.go +++ b/go-controller/pkg/clustermanager/clustermanager_test.go @@ -34,10 +34,9 @@ const ( var _ = ginkgo.Describe("Cluster Manager", func() { var ( - app *cli.App - f *factory.WatchFactory - stopChan chan struct{} - wg *sync.WaitGroup + app *cli.App + f *factory.WatchFactory + wg *sync.WaitGroup ) const ( @@ -54,12 +53,10 @@ var _ = ginkgo.Describe("Cluster Manager", func() { app = cli.NewApp() app.Name = "test" app.Flags = config.Flags - stopChan = make(chan struct{}) wg = &sync.WaitGroup{} }) ginkgo.AfterEach(func() { - close(stopChan) if f != nil { f.Shutdown() } @@ -1436,4 +1433,102 @@ var _ = ginkgo.Describe("Cluster Manager", func() { }) }) + ginkgo.Context("starting the cluster manager", func() { + const networkName = "default" + + var fakeClient *util.OVNClusterManagerClientset + + ginkgo.BeforeEach(func() { + fakeClient = util.GetOVNClientset().GetClusterManagerClientset() + }) + + ginkgo.When("the required features are not enabled", func() { + ginkgo.It("does *not* automatically provision a NAD for the default network", func() { + app.Action = func(ctx *cli.Context) error { + _, err := config.InitConfig(ctx, nil, nil) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + f, err = factory.NewClusterManagerWatchFactory(fakeClient) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + clusterMngr, err := clusterManager(fakeClient, f) + gomega.Expect(clusterMngr).NotTo(gomega.BeNil()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(clusterMngr.Start(ctx.Context)).To(gomega.Succeed()) + + _, err = fakeClient.NetworkAttchDefClient. + K8sCniCncfIoV1(). + NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace). + Get( + context.Background(), + networkName, + metav1.GetOptions{}, + ) + gomega.Expect(err).To( + gomega.MatchError("network-attachment-definitions.k8s.cni.cncf.io \"default\" not found"), + ) + + return nil + } + gomega.Expect(app.Run([]string{app.Name})).To(gomega.Succeed()) + }) + }) + + ginkgo.When("the multi-network, network-segmentation, and preconfigured-udn-addresses features are enabled", func() { + ginkgo.BeforeEach(func() { + config.OVNKubernetesFeature.EnableMultiNetwork = true + config.OVNKubernetesFeature.EnableNetworkSegmentation = true + config.OVNKubernetesFeature.EnablePreconfiguredUDNAddresses = true + }) + + ginkgo.It("automatically provisions a NAD for the default network", func() { + app.Action = func(ctx *cli.Context) error { + _, err := config.InitConfig(ctx, nil, nil) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + f, err = factory.NewClusterManagerWatchFactory(fakeClient) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + clusterMngr, err := clusterManager(fakeClient, f) + gomega.Expect(clusterMngr).NotTo(gomega.BeNil()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + c, cancel := context.WithCancel(ctx.Context) + defer cancel() + gomega.Expect(clusterMngr.Start(c)).To(gomega.Succeed()) + defer clusterMngr.Stop() + + nad, err := fakeClient.NetworkAttchDefClient. + K8sCniCncfIoV1(). + NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace). + Get( + context.Background(), + networkName, + metav1.GetOptions{}, + ) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + const expectedNADContents = `{"cniVersion": "0.4.0", "name": "ovn-kubernetes", "type": "ovn-k8s-cni-overlay"}` + gomega.Expect(nad.Spec.Config).To(gomega.Equal(expectedNADContents)) + + return nil + } + gomega.Expect(app.Run([]string{app.Name})).To(gomega.Succeed()) + }) + }) + }) + }) + +func clusterManager(client *util.OVNClusterManagerClientset, f *factory.WatchFactory) (*ClusterManager, error) { + if err := f.Start(); err != nil { + return nil, fmt.Errorf("failed to start the CM watch factory: %w", err) + } + + clusterMngr, err := NewClusterManager(client, f, "identity", nil) + if err != nil { + return nil, fmt.Errorf("failed to start the CM watch factory: %w", err) + } + + return clusterMngr, nil +} diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller.go b/go-controller/pkg/clustermanager/routeadvertisements/controller.go index 18fb3dbaae..903f5622f2 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller.go @@ -1016,7 +1016,7 @@ func (c *Controller) getSelectedNADs(networkSelectors apitypes.NetworkSelectors) case apitypes.DefaultNetwork: // if we are selecting the default networkdefault network label, // make sure a NAD exists for it - nad, err := c.getOrCreateDefaultNetworkNAD() + nad, err := util.EnsureDefaultNetworkNAD(c.nadLister, c.nadClient) if err != nil { return nil, fmt.Errorf("failed to get/create default network NAD: %w", err) } @@ -1047,34 +1047,6 @@ func (c *Controller) getSelectedNADs(networkSelectors apitypes.NetworkSelectors) return selected, nil } -// getOrCreateDefaultNetworkNAD ensure that a well-known NAD exists for the -// default network in ovn-k namespace. -func (c *Controller) getOrCreateDefaultNetworkNAD() (*nadtypes.NetworkAttachmentDefinition, error) { - nad, err := c.nadLister.NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace).Get(types.DefaultNetworkName) - if err != nil && !apierrors.IsNotFound(err) { - return nil, err - } - if nad != nil { - return nad, nil - } - return c.nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace).Create( - context.Background(), - &nadtypes.NetworkAttachmentDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: types.DefaultNetworkName, - Namespace: config.Kubernetes.OVNConfigNamespace, - }, - Spec: nadtypes.NetworkAttachmentDefinitionSpec{ - Config: fmt.Sprintf("{\"cniVersion\": \"0.4.0\", \"name\": \"ovn-kubernetes\", \"type\": \"%s\"}", config.CNI.Plugin), - }, - }, - // note we don't set ourselves as field manager for this create as we - // want to process the resulting event that would otherwise be filtered - // out in nadNeedsUpdate - metav1.CreateOptions{}, - ) -} - // getEgressIPsByNodesByNetworks iterates all existing egress IPs that apply to // any of the provided networks and returns a "node -> network -> eips" // map. diff --git a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go index 305418425c..86c711987e 100644 --- a/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go +++ b/go-controller/pkg/clustermanager/routeadvertisements/controller_test.go @@ -1051,7 +1051,7 @@ func TestController_reconcile(t *testing.T) { g.Expect(err).ToNot(gomega.HaveOccurred()) // prime the default network NAD if defaultNAD == nil { - defaultNAD, err = c.getOrCreateDefaultNetworkNAD() + defaultNAD, err = util.EnsureDefaultNetworkNAD(c.nadLister, c.nadClient) g.Expect(err).ToNot(gomega.HaveOccurred()) // update it with the annotation that network manager would set defaultNAD.Annotations = map[string]string{types.OvnNetworkNameAnnotation: types.DefaultNetworkName} diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/controller.go b/go-controller/pkg/clustermanager/userdefinednetwork/controller.go index 67292bd2ed..14963b9ed9 100644 --- a/go-controller/pkg/clustermanager/userdefinednetwork/controller.go +++ b/go-controller/pkg/clustermanager/userdefinednetwork/controller.go @@ -150,6 +150,12 @@ func (c *Controller) Run() error { return fmt.Errorf("unable to start user-defined network controller: %v", err) } + if util.IsPreconfiguredUDNAddressesEnabled() { + if _, err := util.EnsureDefaultNetworkNAD(c.nadLister, c.nadClient); err != nil { + return fmt.Errorf("failed to ensure default network nad exists: %w", err) + } + } + return nil } diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index fd91edd3be..d4f8b2a948 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -1378,6 +1378,12 @@ func IsRouteAdvertisementsEnabled() bool { return config.OVNKubernetesFeature.EnableMultiNetwork && config.OVNKubernetesFeature.EnableRouteAdvertisements } +// IsPreconfiguredUDNAddressesEnabled indicates if user defined IPs / MAC +// addresses can be set in primary UDNs +func IsPreconfiguredUDNAddressesEnabled() bool { + return IsNetworkSegmentationSupportEnabled() && config.OVNKubernetesFeature.EnablePreconfiguredUDNAddresses +} + func DoesNetworkRequireIPAM(netInfo NetInfo) bool { return !((netInfo.TopologyType() == types.Layer2Topology || netInfo.TopologyType() == types.LocalnetTopology) && len(netInfo.Subnets()) == 0) } diff --git a/go-controller/pkg/util/nad.go b/go-controller/pkg/util/nad.go new file mode 100644 index 0000000000..3a220e2b82 --- /dev/null +++ b/go-controller/pkg/util/nad.go @@ -0,0 +1,46 @@ +package util + +import ( + "context" + "fmt" + + nadtypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + nadclientset "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned" + nadlisters "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/listers/k8s.cni.cncf.io/v1" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" +) + +// EnsureDefaultNetworkNAD ensures that a well-known NAD exists for the +// default network in ovn-k namespace. This will allow the users to customize +// the primary UDN attachments with static IPs, and/or MAC address requests, by +// using the multus-cni `default network` feature. +func EnsureDefaultNetworkNAD(nadLister nadlisters.NetworkAttachmentDefinitionLister, nadClient nadclientset.Interface) (*nadtypes.NetworkAttachmentDefinition, error) { + nad, err := nadLister.NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace).Get(types.DefaultNetworkName) + if err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + if nad != nil { + return nad, nil + } + return nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(config.Kubernetes.OVNConfigNamespace).Create( + context.Background(), + &nadtypes.NetworkAttachmentDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: types.DefaultNetworkName, + Namespace: config.Kubernetes.OVNConfigNamespace, + }, + Spec: nadtypes.NetworkAttachmentDefinitionSpec{ + Config: fmt.Sprintf("{\"cniVersion\": \"0.4.0\", \"name\": \"ovn-kubernetes\", \"type\": \"%s\"}", config.CNI.Plugin), + }, + }, + // note we don't set ourselves as field manager for this create as we + // want to process the resulting event that would otherwise be filtered + // out in nadNeedsUpdate + metav1.CreateOptions{}, + ) +} From b85c0f5f291fe964b68048878e79f5863358e2a8 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 30 Jul 2025 11:03:20 +0100 Subject: [PATCH 184/278] chore: Update libovsdb bindings to ovn 25.03 Signed-off-by: Dave Tucker --- go-controller/Makefile | 3 +- go-controller/pkg/nbdb/load_balancer.go | 20 +- .../pkg/nbdb/logical_router_policy.go | 51 +++++ .../pkg/nbdb/logical_router_static_route.go | 67 +++++-- go-controller/pkg/nbdb/logical_switch_port.go | 25 +++ go-controller/pkg/nbdb/model.go | 62 +++++- go-controller/pkg/nbdb/ssl.go | 6 + go-controller/pkg/sbdb/acl_id.go | 54 ++++++ go-controller/pkg/sbdb/advertised_route.go | 124 ++++++++++++ go-controller/pkg/sbdb/ecmp_nexthop.go | 105 ++++++++++ go-controller/pkg/sbdb/learned_route.go | 105 ++++++++++ go-controller/pkg/sbdb/model.go | 180 +++++++++++++++++- go-controller/pkg/sbdb/ssl.go | 6 + 13 files changed, 780 insertions(+), 28 deletions(-) create mode 100644 go-controller/pkg/sbdb/acl_id.go create mode 100644 go-controller/pkg/sbdb/advertised_route.go create mode 100644 go-controller/pkg/sbdb/ecmp_nexthop.go create mode 100644 go-controller/pkg/sbdb/learned_route.go diff --git a/go-controller/Makefile b/go-controller/Makefile index 4c86486ce2..f27bb979e5 100644 --- a/go-controller/Makefile +++ b/go-controller/Makefile @@ -22,8 +22,7 @@ else CONTAINER_RUNTIME=docker endif CONTAINER_RUNNABLE ?= $(shell $(CONTAINER_RUNTIME) -v > /dev/null 2>&1; echo $$?) -# FIXME(tssurya): In one week when OVN 24.09 is released change the schema version -OVN_SCHEMA_VERSION ?= 8efac26f6637fc +OVN_SCHEMA_VERSION ?= v25.03.1 OVS_VERSION ?= v2.17.0 ifeq ($(NOROOT),TRUE) C_ARGS = -e NOROOT=TRUE diff --git a/go-controller/pkg/nbdb/load_balancer.go b/go-controller/pkg/nbdb/load_balancer.go index 8bddd25f4a..553bc48dda 100644 --- a/go-controller/pkg/nbdb/load_balancer.go +++ b/go-controller/pkg/nbdb/load_balancer.go @@ -13,15 +13,17 @@ type ( ) var ( - LoadBalancerProtocolTCP LoadBalancerProtocol = "tcp" - LoadBalancerProtocolUDP LoadBalancerProtocol = "udp" - LoadBalancerProtocolSCTP LoadBalancerProtocol = "sctp" - LoadBalancerSelectionFieldsEthSrc LoadBalancerSelectionFields = "eth_src" - LoadBalancerSelectionFieldsEthDst LoadBalancerSelectionFields = "eth_dst" - LoadBalancerSelectionFieldsIPSrc LoadBalancerSelectionFields = "ip_src" - LoadBalancerSelectionFieldsIPDst LoadBalancerSelectionFields = "ip_dst" - LoadBalancerSelectionFieldsTpSrc LoadBalancerSelectionFields = "tp_src" - LoadBalancerSelectionFieldsTpDst LoadBalancerSelectionFields = "tp_dst" + LoadBalancerProtocolTCP LoadBalancerProtocol = "tcp" + LoadBalancerProtocolUDP LoadBalancerProtocol = "udp" + LoadBalancerProtocolSCTP LoadBalancerProtocol = "sctp" + LoadBalancerSelectionFieldsEthSrc LoadBalancerSelectionFields = "eth_src" + LoadBalancerSelectionFieldsEthDst LoadBalancerSelectionFields = "eth_dst" + LoadBalancerSelectionFieldsIPSrc LoadBalancerSelectionFields = "ip_src" + LoadBalancerSelectionFieldsIPDst LoadBalancerSelectionFields = "ip_dst" + LoadBalancerSelectionFieldsIpv6Src LoadBalancerSelectionFields = "ipv6_src" + LoadBalancerSelectionFieldsIpv6Dst LoadBalancerSelectionFields = "ipv6_dst" + LoadBalancerSelectionFieldsTpSrc LoadBalancerSelectionFields = "tp_src" + LoadBalancerSelectionFieldsTpDst LoadBalancerSelectionFields = "tp_dst" ) // LoadBalancer defines an object in Load_Balancer table diff --git a/go-controller/pkg/nbdb/logical_router_policy.go b/go-controller/pkg/nbdb/logical_router_policy.go index 51b29ea706..377ef213d0 100644 --- a/go-controller/pkg/nbdb/logical_router_policy.go +++ b/go-controller/pkg/nbdb/logical_router_policy.go @@ -15,6 +15,7 @@ var ( LogicalRouterPolicyActionAllow LogicalRouterPolicyAction = "allow" LogicalRouterPolicyActionDrop LogicalRouterPolicyAction = "drop" LogicalRouterPolicyActionReroute LogicalRouterPolicyAction = "reroute" + LogicalRouterPolicyActionJump LogicalRouterPolicyAction = "jump" ) // LogicalRouterPolicy defines an object in Logical_Router_Policy table @@ -22,7 +23,9 @@ type LogicalRouterPolicy struct { UUID string `ovsdb:"_uuid"` Action LogicalRouterPolicyAction `ovsdb:"action"` BFDSessions []string `ovsdb:"bfd_sessions"` + Chain *string `ovsdb:"chain"` ExternalIDs map[string]string `ovsdb:"external_ids"` + JumpChain *string `ovsdb:"jump_chain"` Match string `ovsdb:"match"` Nexthop *string `ovsdb:"nexthop"` Nexthops []string `ovsdb:"nexthops"` @@ -66,6 +69,28 @@ func equalLogicalRouterPolicyBFDSessions(a, b []string) bool { return true } +func (a *LogicalRouterPolicy) GetChain() *string { + return a.Chain +} + +func copyLogicalRouterPolicyChain(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalLogicalRouterPolicyChain(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + func (a *LogicalRouterPolicy) GetExternalIDs() map[string]string { return a.ExternalIDs } @@ -96,6 +121,28 @@ func equalLogicalRouterPolicyExternalIDs(a, b map[string]string) bool { return true } +func (a *LogicalRouterPolicy) GetJumpChain() *string { + return a.JumpChain +} + +func copyLogicalRouterPolicyJumpChain(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalLogicalRouterPolicyJumpChain(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + func (a *LogicalRouterPolicy) GetMatch() string { return a.Match } @@ -187,7 +234,9 @@ func (a *LogicalRouterPolicy) GetPriority() int { func (a *LogicalRouterPolicy) DeepCopyInto(b *LogicalRouterPolicy) { *b = *a b.BFDSessions = copyLogicalRouterPolicyBFDSessions(a.BFDSessions) + b.Chain = copyLogicalRouterPolicyChain(a.Chain) b.ExternalIDs = copyLogicalRouterPolicyExternalIDs(a.ExternalIDs) + b.JumpChain = copyLogicalRouterPolicyJumpChain(a.JumpChain) b.Nexthop = copyLogicalRouterPolicyNexthop(a.Nexthop) b.Nexthops = copyLogicalRouterPolicyNexthops(a.Nexthops) b.Options = copyLogicalRouterPolicyOptions(a.Options) @@ -212,7 +261,9 @@ func (a *LogicalRouterPolicy) Equals(b *LogicalRouterPolicy) bool { return a.UUID == b.UUID && a.Action == b.Action && equalLogicalRouterPolicyBFDSessions(a.BFDSessions, b.BFDSessions) && + equalLogicalRouterPolicyChain(a.Chain, b.Chain) && equalLogicalRouterPolicyExternalIDs(a.ExternalIDs, b.ExternalIDs) && + equalLogicalRouterPolicyJumpChain(a.JumpChain, b.JumpChain) && a.Match == b.Match && equalLogicalRouterPolicyNexthop(a.Nexthop, b.Nexthop) && equalLogicalRouterPolicyNexthops(a.Nexthops, b.Nexthops) && diff --git a/go-controller/pkg/nbdb/logical_router_static_route.go b/go-controller/pkg/nbdb/logical_router_static_route.go index 205741626c..ceccb8ac78 100644 --- a/go-controller/pkg/nbdb/logical_router_static_route.go +++ b/go-controller/pkg/nbdb/logical_router_static_route.go @@ -8,25 +8,36 @@ import "github.com/ovn-kubernetes/libovsdb/model" const LogicalRouterStaticRouteTable = "Logical_Router_Static_Route" type ( - LogicalRouterStaticRoutePolicy = string + LogicalRouterStaticRoutePolicy = string + LogicalRouterStaticRouteSelectionFields = string ) var ( - LogicalRouterStaticRoutePolicySrcIP LogicalRouterStaticRoutePolicy = "src-ip" - LogicalRouterStaticRoutePolicyDstIP LogicalRouterStaticRoutePolicy = "dst-ip" + LogicalRouterStaticRoutePolicySrcIP LogicalRouterStaticRoutePolicy = "src-ip" + LogicalRouterStaticRoutePolicyDstIP LogicalRouterStaticRoutePolicy = "dst-ip" + LogicalRouterStaticRouteSelectionFieldsEthSrc LogicalRouterStaticRouteSelectionFields = "eth_src" + LogicalRouterStaticRouteSelectionFieldsEthDst LogicalRouterStaticRouteSelectionFields = "eth_dst" + LogicalRouterStaticRouteSelectionFieldsIPProto LogicalRouterStaticRouteSelectionFields = "ip_proto" + LogicalRouterStaticRouteSelectionFieldsIPSrc LogicalRouterStaticRouteSelectionFields = "ip_src" + LogicalRouterStaticRouteSelectionFieldsIPDst LogicalRouterStaticRouteSelectionFields = "ip_dst" + LogicalRouterStaticRouteSelectionFieldsIpv6Src LogicalRouterStaticRouteSelectionFields = "ipv6_src" + LogicalRouterStaticRouteSelectionFieldsIpv6Dst LogicalRouterStaticRouteSelectionFields = "ipv6_dst" + LogicalRouterStaticRouteSelectionFieldsTpSrc LogicalRouterStaticRouteSelectionFields = "tp_src" + LogicalRouterStaticRouteSelectionFieldsTpDst LogicalRouterStaticRouteSelectionFields = "tp_dst" ) // LogicalRouterStaticRoute defines an object in Logical_Router_Static_Route table type LogicalRouterStaticRoute struct { - UUID string `ovsdb:"_uuid"` - BFD *string `ovsdb:"bfd"` - ExternalIDs map[string]string `ovsdb:"external_ids"` - IPPrefix string `ovsdb:"ip_prefix"` - Nexthop string `ovsdb:"nexthop"` - Options map[string]string `ovsdb:"options"` - OutputPort *string `ovsdb:"output_port"` - Policy *LogicalRouterStaticRoutePolicy `ovsdb:"policy"` - RouteTable string `ovsdb:"route_table"` + UUID string `ovsdb:"_uuid"` + BFD *string `ovsdb:"bfd"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + IPPrefix string `ovsdb:"ip_prefix"` + Nexthop string `ovsdb:"nexthop"` + Options map[string]string `ovsdb:"options"` + OutputPort *string `ovsdb:"output_port"` + Policy *LogicalRouterStaticRoutePolicy `ovsdb:"policy"` + RouteTable string `ovsdb:"route_table"` + SelectionFields []LogicalRouterStaticRouteSelectionFields `ovsdb:"selection_fields"` } func (a *LogicalRouterStaticRoute) GetUUID() string { @@ -171,6 +182,34 @@ func (a *LogicalRouterStaticRoute) GetRouteTable() string { return a.RouteTable } +func (a *LogicalRouterStaticRoute) GetSelectionFields() []LogicalRouterStaticRouteSelectionFields { + return a.SelectionFields +} + +func copyLogicalRouterStaticRouteSelectionFields(a []LogicalRouterStaticRouteSelectionFields) []LogicalRouterStaticRouteSelectionFields { + if a == nil { + return nil + } + b := make([]LogicalRouterStaticRouteSelectionFields, len(a)) + copy(b, a) + return b +} + +func equalLogicalRouterStaticRouteSelectionFields(a, b []LogicalRouterStaticRouteSelectionFields) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + func (a *LogicalRouterStaticRoute) DeepCopyInto(b *LogicalRouterStaticRoute) { *b = *a b.BFD = copyLogicalRouterStaticRouteBFD(a.BFD) @@ -178,6 +217,7 @@ func (a *LogicalRouterStaticRoute) DeepCopyInto(b *LogicalRouterStaticRoute) { b.Options = copyLogicalRouterStaticRouteOptions(a.Options) b.OutputPort = copyLogicalRouterStaticRouteOutputPort(a.OutputPort) b.Policy = copyLogicalRouterStaticRoutePolicy(a.Policy) + b.SelectionFields = copyLogicalRouterStaticRouteSelectionFields(a.SelectionFields) } func (a *LogicalRouterStaticRoute) DeepCopy() *LogicalRouterStaticRoute { @@ -204,7 +244,8 @@ func (a *LogicalRouterStaticRoute) Equals(b *LogicalRouterStaticRoute) bool { equalLogicalRouterStaticRouteOptions(a.Options, b.Options) && equalLogicalRouterStaticRouteOutputPort(a.OutputPort, b.OutputPort) && equalLogicalRouterStaticRoutePolicy(a.Policy, b.Policy) && - a.RouteTable == b.RouteTable + a.RouteTable == b.RouteTable && + equalLogicalRouterStaticRouteSelectionFields(a.SelectionFields, b.SelectionFields) } func (a *LogicalRouterStaticRoute) EqualsModel(b model.Model) bool { diff --git a/go-controller/pkg/nbdb/logical_switch_port.go b/go-controller/pkg/nbdb/logical_switch_port.go index b211672bff..87994fdc72 100644 --- a/go-controller/pkg/nbdb/logical_switch_port.go +++ b/go-controller/pkg/nbdb/logical_switch_port.go @@ -21,6 +21,7 @@ type LogicalSwitchPort struct { Name string `ovsdb:"name"` Options map[string]string `ovsdb:"options"` ParentName *string `ovsdb:"parent_name"` + Peer *string `ovsdb:"peer"` PortSecurity []string `ovsdb:"port_security"` Tag *int `ovsdb:"tag"` TagRequest *int `ovsdb:"tag_request"` @@ -284,6 +285,28 @@ func equalLogicalSwitchPortParentName(a, b *string) bool { return *a == *b } +func (a *LogicalSwitchPort) GetPeer() *string { + return a.Peer +} + +func copyLogicalSwitchPortPeer(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalLogicalSwitchPortPeer(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + func (a *LogicalSwitchPort) GetPortSecurity() []string { return a.PortSecurity } @@ -394,6 +417,7 @@ func (a *LogicalSwitchPort) DeepCopyInto(b *LogicalSwitchPort) { b.MirrorRules = copyLogicalSwitchPortMirrorRules(a.MirrorRules) b.Options = copyLogicalSwitchPortOptions(a.Options) b.ParentName = copyLogicalSwitchPortParentName(a.ParentName) + b.Peer = copyLogicalSwitchPortPeer(a.Peer) b.PortSecurity = copyLogicalSwitchPortPortSecurity(a.PortSecurity) b.Tag = copyLogicalSwitchPortTag(a.Tag) b.TagRequest = copyLogicalSwitchPortTagRequest(a.TagRequest) @@ -428,6 +452,7 @@ func (a *LogicalSwitchPort) Equals(b *LogicalSwitchPort) bool { a.Name == b.Name && equalLogicalSwitchPortOptions(a.Options, b.Options) && equalLogicalSwitchPortParentName(a.ParentName, b.ParentName) && + equalLogicalSwitchPortPeer(a.Peer, b.Peer) && equalLogicalSwitchPortPortSecurity(a.PortSecurity, b.PortSecurity) && equalLogicalSwitchPortTag(a.Tag, b.Tag) && equalLogicalSwitchPortTagRequest(a.TagRequest, b.TagRequest) && diff --git a/go-controller/pkg/nbdb/model.go b/go-controller/pkg/nbdb/model.go index 9fbe25db4f..07ca7e0e97 100644 --- a/go-controller/pkg/nbdb/model.go +++ b/go-controller/pkg/nbdb/model.go @@ -52,7 +52,7 @@ func FullDatabaseModel() (model.ClientDBModel, error) { var schema = `{ "name": "OVN_Northbound", - "version": "7.6.0", + "version": "7.11.0", "tables": { "ACL": { "columns": { @@ -819,6 +819,8 @@ var schema = `{ "eth_dst", "ip_src", "ip_dst", + "ipv6_src", + "ipv6_dst", "tp_src", "tp_dst" ] @@ -1026,7 +1028,8 @@ var schema = `{ [ "allow", "drop", - "reroute" + "reroute", + "jump" ] ] } @@ -1043,6 +1046,15 @@ var schema = `{ "max": "unlimited" } }, + "chain": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, "external_ids": { "type": { "key": { @@ -1055,6 +1067,15 @@ var schema = `{ "max": "unlimited" } }, + "jump_chain": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, "match": { "type": "string" }, @@ -1187,7 +1208,7 @@ var schema = `{ "key": { "type": "string" }, - "min": 1, + "min": 0, "max": "unlimited" } }, @@ -1301,6 +1322,29 @@ var schema = `{ }, "route_table": { "type": "string" + }, + "selection_fields": { + "type": { + "key": { + "type": "string", + "enum": [ + "set", + [ + "eth_src", + "eth_dst", + "ip_proto", + "ip_src", + "ip_dst", + "ipv6_src", + "ipv6_dst", + "tp_src", + "tp_dst" + ] + ] + }, + "min": 0, + "max": "unlimited" + } } } }, @@ -1532,6 +1576,15 @@ var schema = `{ "max": 1 } }, + "peer": { + "type": { + "key": { + "type": "string" + }, + "min": 0, + "max": 1 + } + }, "port_security": { "type": { "key": { @@ -2092,6 +2145,9 @@ var schema = `{ "ssl_ciphers": { "type": "string" }, + "ssl_ciphersuites": { + "type": "string" + }, "ssl_protocols": { "type": "string" } diff --git a/go-controller/pkg/nbdb/ssl.go b/go-controller/pkg/nbdb/ssl.go index 847ea8c362..0f01efc978 100644 --- a/go-controller/pkg/nbdb/ssl.go +++ b/go-controller/pkg/nbdb/ssl.go @@ -16,6 +16,7 @@ type SSL struct { ExternalIDs map[string]string `ovsdb:"external_ids"` PrivateKey string `ovsdb:"private_key"` SSLCiphers string `ovsdb:"ssl_ciphers"` + SSLCiphersuites string `ovsdb:"ssl_ciphersuites"` SSLProtocols string `ovsdb:"ssl_protocols"` } @@ -73,6 +74,10 @@ func (a *SSL) GetSSLCiphers() string { return a.SSLCiphers } +func (a *SSL) GetSSLCiphersuites() string { + return a.SSLCiphersuites +} + func (a *SSL) GetSSLProtocols() string { return a.SSLProtocols } @@ -105,6 +110,7 @@ func (a *SSL) Equals(b *SSL) bool { equalSSLExternalIDs(a.ExternalIDs, b.ExternalIDs) && a.PrivateKey == b.PrivateKey && a.SSLCiphers == b.SSLCiphers && + a.SSLCiphersuites == b.SSLCiphersuites && a.SSLProtocols == b.SSLProtocols } diff --git a/go-controller/pkg/sbdb/acl_id.go b/go-controller/pkg/sbdb/acl_id.go new file mode 100644 index 0000000000..5c62c53fe2 --- /dev/null +++ b/go-controller/pkg/sbdb/acl_id.go @@ -0,0 +1,54 @@ +// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package sbdb + +import "github.com/ovn-kubernetes/libovsdb/model" + +const ACLIDTable = "ACL_ID" + +// ACLID defines an object in ACL_ID table +type ACLID struct { + UUID string `ovsdb:"_uuid"` + ID int `ovsdb:"id"` +} + +func (a *ACLID) GetUUID() string { + return a.UUID +} + +func (a *ACLID) GetID() int { + return a.ID +} + +func (a *ACLID) DeepCopyInto(b *ACLID) { + *b = *a +} + +func (a *ACLID) DeepCopy() *ACLID { + b := new(ACLID) + a.DeepCopyInto(b) + return b +} + +func (a *ACLID) CloneModelInto(b model.Model) { + c := b.(*ACLID) + a.DeepCopyInto(c) +} + +func (a *ACLID) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *ACLID) Equals(b *ACLID) bool { + return a.UUID == b.UUID && + a.ID == b.ID +} + +func (a *ACLID) EqualsModel(b model.Model) bool { + c := b.(*ACLID) + return a.Equals(c) +} + +var _ model.CloneableModel = &ACLID{} +var _ model.ComparableModel = &ACLID{} diff --git a/go-controller/pkg/sbdb/advertised_route.go b/go-controller/pkg/sbdb/advertised_route.go new file mode 100644 index 0000000000..6704be7d4d --- /dev/null +++ b/go-controller/pkg/sbdb/advertised_route.go @@ -0,0 +1,124 @@ +// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package sbdb + +import "github.com/ovn-kubernetes/libovsdb/model" + +const AdvertisedRouteTable = "Advertised_Route" + +// AdvertisedRoute defines an object in Advertised_Route table +type AdvertisedRoute struct { + UUID string `ovsdb:"_uuid"` + Datapath string `ovsdb:"datapath"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + IPPrefix string `ovsdb:"ip_prefix"` + LogicalPort string `ovsdb:"logical_port"` + TrackedPort *string `ovsdb:"tracked_port"` +} + +func (a *AdvertisedRoute) GetUUID() string { + return a.UUID +} + +func (a *AdvertisedRoute) GetDatapath() string { + return a.Datapath +} + +func (a *AdvertisedRoute) GetExternalIDs() map[string]string { + return a.ExternalIDs +} + +func copyAdvertisedRouteExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalAdvertisedRouteExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func (a *AdvertisedRoute) GetIPPrefix() string { + return a.IPPrefix +} + +func (a *AdvertisedRoute) GetLogicalPort() string { + return a.LogicalPort +} + +func (a *AdvertisedRoute) GetTrackedPort() *string { + return a.TrackedPort +} + +func copyAdvertisedRouteTrackedPort(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAdvertisedRouteTrackedPort(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AdvertisedRoute) DeepCopyInto(b *AdvertisedRoute) { + *b = *a + b.ExternalIDs = copyAdvertisedRouteExternalIDs(a.ExternalIDs) + b.TrackedPort = copyAdvertisedRouteTrackedPort(a.TrackedPort) +} + +func (a *AdvertisedRoute) DeepCopy() *AdvertisedRoute { + b := new(AdvertisedRoute) + a.DeepCopyInto(b) + return b +} + +func (a *AdvertisedRoute) CloneModelInto(b model.Model) { + c := b.(*AdvertisedRoute) + a.DeepCopyInto(c) +} + +func (a *AdvertisedRoute) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *AdvertisedRoute) Equals(b *AdvertisedRoute) bool { + return a.UUID == b.UUID && + a.Datapath == b.Datapath && + equalAdvertisedRouteExternalIDs(a.ExternalIDs, b.ExternalIDs) && + a.IPPrefix == b.IPPrefix && + a.LogicalPort == b.LogicalPort && + equalAdvertisedRouteTrackedPort(a.TrackedPort, b.TrackedPort) +} + +func (a *AdvertisedRoute) EqualsModel(b model.Model) bool { + c := b.(*AdvertisedRoute) + return a.Equals(c) +} + +var _ model.CloneableModel = &AdvertisedRoute{} +var _ model.ComparableModel = &AdvertisedRoute{} diff --git a/go-controller/pkg/sbdb/ecmp_nexthop.go b/go-controller/pkg/sbdb/ecmp_nexthop.go new file mode 100644 index 0000000000..2b0124a788 --- /dev/null +++ b/go-controller/pkg/sbdb/ecmp_nexthop.go @@ -0,0 +1,105 @@ +// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package sbdb + +import "github.com/ovn-kubernetes/libovsdb/model" + +const ECMPNexthopTable = "ECMP_Nexthop" + +// ECMPNexthop defines an object in ECMP_Nexthop table +type ECMPNexthop struct { + UUID string `ovsdb:"_uuid"` + Datapath string `ovsdb:"datapath"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + MAC string `ovsdb:"mac"` + Nexthop string `ovsdb:"nexthop"` + Port string `ovsdb:"port"` +} + +func (a *ECMPNexthop) GetUUID() string { + return a.UUID +} + +func (a *ECMPNexthop) GetDatapath() string { + return a.Datapath +} + +func (a *ECMPNexthop) GetExternalIDs() map[string]string { + return a.ExternalIDs +} + +func copyECMPNexthopExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalECMPNexthopExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func (a *ECMPNexthop) GetMAC() string { + return a.MAC +} + +func (a *ECMPNexthop) GetNexthop() string { + return a.Nexthop +} + +func (a *ECMPNexthop) GetPort() string { + return a.Port +} + +func (a *ECMPNexthop) DeepCopyInto(b *ECMPNexthop) { + *b = *a + b.ExternalIDs = copyECMPNexthopExternalIDs(a.ExternalIDs) +} + +func (a *ECMPNexthop) DeepCopy() *ECMPNexthop { + b := new(ECMPNexthop) + a.DeepCopyInto(b) + return b +} + +func (a *ECMPNexthop) CloneModelInto(b model.Model) { + c := b.(*ECMPNexthop) + a.DeepCopyInto(c) +} + +func (a *ECMPNexthop) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *ECMPNexthop) Equals(b *ECMPNexthop) bool { + return a.UUID == b.UUID && + a.Datapath == b.Datapath && + equalECMPNexthopExternalIDs(a.ExternalIDs, b.ExternalIDs) && + a.MAC == b.MAC && + a.Nexthop == b.Nexthop && + a.Port == b.Port +} + +func (a *ECMPNexthop) EqualsModel(b model.Model) bool { + c := b.(*ECMPNexthop) + return a.Equals(c) +} + +var _ model.CloneableModel = &ECMPNexthop{} +var _ model.ComparableModel = &ECMPNexthop{} diff --git a/go-controller/pkg/sbdb/learned_route.go b/go-controller/pkg/sbdb/learned_route.go new file mode 100644 index 0000000000..8cab3636de --- /dev/null +++ b/go-controller/pkg/sbdb/learned_route.go @@ -0,0 +1,105 @@ +// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package sbdb + +import "github.com/ovn-kubernetes/libovsdb/model" + +const LearnedRouteTable = "Learned_Route" + +// LearnedRoute defines an object in Learned_Route table +type LearnedRoute struct { + UUID string `ovsdb:"_uuid"` + Datapath string `ovsdb:"datapath"` + ExternalIDs map[string]string `ovsdb:"external_ids"` + IPPrefix string `ovsdb:"ip_prefix"` + LogicalPort string `ovsdb:"logical_port"` + Nexthop string `ovsdb:"nexthop"` +} + +func (a *LearnedRoute) GetUUID() string { + return a.UUID +} + +func (a *LearnedRoute) GetDatapath() string { + return a.Datapath +} + +func (a *LearnedRoute) GetExternalIDs() map[string]string { + return a.ExternalIDs +} + +func copyLearnedRouteExternalIDs(a map[string]string) map[string]string { + if a == nil { + return nil + } + b := make(map[string]string, len(a)) + for k, v := range a { + b[k] = v + } + return b +} + +func equalLearnedRouteExternalIDs(a, b map[string]string) bool { + if (a == nil) != (b == nil) { + return false + } + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true +} + +func (a *LearnedRoute) GetIPPrefix() string { + return a.IPPrefix +} + +func (a *LearnedRoute) GetLogicalPort() string { + return a.LogicalPort +} + +func (a *LearnedRoute) GetNexthop() string { + return a.Nexthop +} + +func (a *LearnedRoute) DeepCopyInto(b *LearnedRoute) { + *b = *a + b.ExternalIDs = copyLearnedRouteExternalIDs(a.ExternalIDs) +} + +func (a *LearnedRoute) DeepCopy() *LearnedRoute { + b := new(LearnedRoute) + a.DeepCopyInto(b) + return b +} + +func (a *LearnedRoute) CloneModelInto(b model.Model) { + c := b.(*LearnedRoute) + a.DeepCopyInto(c) +} + +func (a *LearnedRoute) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *LearnedRoute) Equals(b *LearnedRoute) bool { + return a.UUID == b.UUID && + a.Datapath == b.Datapath && + equalLearnedRouteExternalIDs(a.ExternalIDs, b.ExternalIDs) && + a.IPPrefix == b.IPPrefix && + a.LogicalPort == b.LogicalPort && + a.Nexthop == b.Nexthop +} + +func (a *LearnedRoute) EqualsModel(b model.Model) bool { + c := b.(*LearnedRoute) + return a.Equals(c) +} + +var _ model.CloneableModel = &LearnedRoute{} +var _ model.ComparableModel = &LearnedRoute{} diff --git a/go-controller/pkg/sbdb/model.go b/go-controller/pkg/sbdb/model.go index c5420638e5..0d9fe177bf 100644 --- a/go-controller/pkg/sbdb/model.go +++ b/go-controller/pkg/sbdb/model.go @@ -13,7 +13,9 @@ import ( // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb func FullDatabaseModel() (model.ClientDBModel, error) { return model.NewClientDBModel("OVN_Southbound", map[string]model.Model{ + "ACL_ID": &ACLID{}, "Address_Set": &AddressSet{}, + "Advertised_Route": &AdvertisedRoute{}, "BFD": &BFD{}, "Chassis": &Chassis{}, "Chassis_Private": &ChassisPrivate{}, @@ -24,6 +26,7 @@ func FullDatabaseModel() (model.ClientDBModel, error) { "DHCPv6_Options": &DHCPv6Options{}, "DNS": &DNS{}, "Datapath_Binding": &DatapathBinding{}, + "ECMP_Nexthop": &ECMPNexthop{}, "Encap": &Encap{}, "FDB": &FDB{}, "Gateway_Chassis": &GatewayChassis{}, @@ -31,6 +34,7 @@ func FullDatabaseModel() (model.ClientDBModel, error) { "HA_Chassis_Group": &HAChassisGroup{}, "IGMP_Group": &IGMPGroup{}, "IP_Multicast": &IPMulticast{}, + "Learned_Route": &LearnedRoute{}, "Load_Balancer": &LoadBalancer{}, "Logical_DP_Group": &LogicalDPGroup{}, "Logical_Flow": &LogicalFlow{}, @@ -52,8 +56,22 @@ func FullDatabaseModel() (model.ClientDBModel, error) { var schema = `{ "name": "OVN_Southbound", - "version": "20.37.0", + "version": "20.41.0", "tables": { + "ACL_ID": { + "columns": { + "id": { + "type": { + "key": { + "type": "integer", + "minInteger": 0, + "maxInteger": 32767 + } + } + } + }, + "isRoot": true + }, "Address_Set": { "columns": { "addresses": { @@ -76,6 +94,63 @@ var schema = `{ ], "isRoot": true }, + "Advertised_Route": { + "columns": { + "datapath": { + "type": { + "key": { + "type": "uuid", + "refTable": "Datapath_Binding", + "refType": "strong" + } + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "ip_prefix": { + "type": "string" + }, + "logical_port": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port_Binding", + "refType": "strong" + } + } + }, + "tracked_port": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port_Binding", + "refType": "strong" + }, + "min": 0, + "max": 1 + } + } + }, + "indexes": [ + [ + "datapath", + "logical_port", + "ip_prefix", + "tracked_port" + ] + ], + "isRoot": true + }, "BFD": { "columns": { "chassis_name": { @@ -576,6 +651,57 @@ var schema = `{ ], "isRoot": true }, + "ECMP_Nexthop": { + "columns": { + "datapath": { + "type": { + "key": { + "type": "uuid", + "refTable": "Datapath_Binding", + "refType": "strong" + }, + "min": 1, + "max": 1 + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "mac": { + "type": "string" + }, + "nexthop": { + "type": "string" + }, + "port": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port_Binding", + "refType": "strong" + }, + "min": 1, + "max": 1 + } + } + }, + "indexes": [ + [ + "nexthop", + "port" + ] + ], + "isRoot": true + }, "Encap": { "columns": { "chassis_name": { @@ -932,6 +1058,55 @@ var schema = `{ ], "isRoot": true }, + "Learned_Route": { + "columns": { + "datapath": { + "type": { + "key": { + "type": "uuid", + "refTable": "Datapath_Binding", + "refType": "strong" + } + } + }, + "external_ids": { + "type": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "min": 0, + "max": "unlimited" + } + }, + "ip_prefix": { + "type": "string" + }, + "logical_port": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port_Binding", + "refType": "strong" + } + } + }, + "nexthop": { + "type": "string" + } + }, + "indexes": [ + [ + "datapath", + "logical_port", + "ip_prefix", + "nexthop" + ] + ], + "isRoot": true + }, "Load_Balancer": { "columns": { "datapath_group": { @@ -1741,6 +1916,9 @@ var schema = `{ "ssl_ciphers": { "type": "string" }, + "ssl_ciphersuites": { + "type": "string" + }, "ssl_protocols": { "type": "string" } diff --git a/go-controller/pkg/sbdb/ssl.go b/go-controller/pkg/sbdb/ssl.go index 08c8e641cf..eccda6dff3 100644 --- a/go-controller/pkg/sbdb/ssl.go +++ b/go-controller/pkg/sbdb/ssl.go @@ -16,6 +16,7 @@ type SSL struct { ExternalIDs map[string]string `ovsdb:"external_ids"` PrivateKey string `ovsdb:"private_key"` SSLCiphers string `ovsdb:"ssl_ciphers"` + SSLCiphersuites string `ovsdb:"ssl_ciphersuites"` SSLProtocols string `ovsdb:"ssl_protocols"` } @@ -73,6 +74,10 @@ func (a *SSL) GetSSLCiphers() string { return a.SSLCiphers } +func (a *SSL) GetSSLCiphersuites() string { + return a.SSLCiphersuites +} + func (a *SSL) GetSSLProtocols() string { return a.SSLProtocols } @@ -105,6 +110,7 @@ func (a *SSL) Equals(b *SSL) bool { equalSSLExternalIDs(a.ExternalIDs, b.ExternalIDs) && a.PrivateKey == b.PrivateKey && a.SSLCiphers == b.SSLCiphers && + a.SSLCiphersuites == b.SSLCiphersuites && a.SSLProtocols == b.SSLProtocols } From 575a08cb4e433f0a54c8a6815acaac9fef6edd31 Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Tue, 29 Jul 2025 17:59:55 +0530 Subject: [PATCH 185/278] dnsnameresolver: fix ever growing address set Signed-off-by: arkadeepsen --- go-controller/pkg/ovn/dns_name_resolver/external_dns.go | 2 +- .../pkg/ovn/dns_name_resolver/external_dns_tracker.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns.go index cd542c48e1..1f676afb88 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns.go @@ -159,7 +159,7 @@ func (extEgDNS *ExternalEgressDNS) reconcileDNSNameResolver(key string) error { addresses = append(addresses, resolvedAddress.IP) } } - err = extEgDNS.dnsTracker.addDNSName(dnsName, addresses) + err = extEgDNS.dnsTracker.addOrUpdateDNSName(dnsName, addresses) return err } diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go index 730bd026af..b8cd861e97 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns_tracker.go @@ -59,8 +59,8 @@ func newDNSTracker(addressSetFactory addressset.AddressSetFactory, controllerNam } } -// addDNSName is called whenever a DNS name is needed to be added or updated. -func (dnsTracker *dnsTracker) addDNSName(dnsName string, addresses []string) error { +// addOrUpdateDNSName is called whenever a DNS name is needed to be added or updated. +func (dnsTracker *dnsTracker) addOrUpdateDNSName(dnsName string, addresses []string) error { dnsTracker.dnsLock.Lock() defer dnsTracker.dnsLock.Unlock() @@ -92,7 +92,7 @@ func (dnsTracker *dnsTracker) addDNSName(dnsName string, addresses []string) err addresses = filteredIPs } - if err := resolvedName.dnsAddressSet.AddAddresses(addresses); err != nil { + if err := resolvedName.dnsAddressSet.SetAddresses(addresses); err != nil { return fmt.Errorf("cannot add IPs to AddressSet for DNS name %s: %v", dnsName, err) } From 4780a5e2323ecbd5ed807085b56d5ba0547c002d Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Wed, 30 Jul 2025 21:49:37 +0530 Subject: [PATCH 186/278] dnsnameresolver: add unit test for DNSNameResolver resource update Signed-off-by: arkadeepsen --- .../dns_name_resolver/external_dns_test.go | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go b/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go index 72661bd4ed..bfe050f288 100644 --- a/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go +++ b/go-controller/pkg/ovn/dns_name_resolver/external_dns_test.go @@ -52,21 +52,15 @@ func newDNSNameResolverObject(name, namespace, dnsName string, addresses []strin } func expectDNSNameWithAddresses(extEgDNS *ExternalEgressDNS, dnsName string, expectedAddresses []string) { - var resolvedName *dnsResolvedName - err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, true, func(context.Context) (done bool, err error) { - var exists bool - resolvedName, exists = extEgDNS.getResolvedName(dnsName) + gomega.Eventually(func() []string { + resolvedName, exists := extEgDNS.getResolvedName(dnsName) if !exists { - return false, nil + return []string{} } - - return true, nil - }) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - v4, v6 := resolvedName.dnsAddressSet.GetAddresses() - ipStrings := append(v4, v6...) - gomega.Expect(ipStrings).To(gomega.ConsistOf(expectedAddresses)) + v4, v6 := resolvedName.dnsAddressSet.GetAddresses() + ipStrings := append(v4, v6...) + return ipStrings + }).Should(gomega.ConsistOf(expectedAddresses)) } var _ = ginkgo.Describe("Egress Firewall External DNS Operations", func() { @@ -227,6 +221,37 @@ var _ = ginkgo.Describe("Egress Firewall External DNS Operations", func() { }) }) + ginkgo.Context("on dns name resolver resource update", func() { + ginkgo.It("Should update addresses for a dns name", func() { + start() + + config.IPv4Mode = true + config.IPv6Mode = true + + addresses := []string{"1.1.1.1", "2.2.2.2", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"} + dnsNameResolver := newDNSNameResolverObject("dns-default", config.Kubernetes.OVNConfigNamespace, dnsName, addresses) + + _, err := fakeClient.OCPNetworkClient.NetworkV1alpha1().DNSNameResolvers(dnsNameResolver.Namespace). + Create(context.TODO(), dnsNameResolver, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + expectDNSNameWithAddresses(extEgDNS, dnsName, addresses) + + addresses = []string{"2.2.2.2", "3.3.3.3", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:7335"} + var resolvedAddresses []ocpnetworkapiv1alpha1.DNSNameResolverResolvedAddress + for _, address := range addresses { + resolvedAddresses = append(resolvedAddresses, ocpnetworkapiv1alpha1.DNSNameResolverResolvedAddress{IP: address}) + } + dnsNameResolver.Status.ResolvedNames[0].ResolvedAddresses = resolvedAddresses + + _, err = fakeClient.OCPNetworkClient.NetworkV1alpha1().DNSNameResolvers(dnsNameResolver.Namespace). + Update(context.TODO(), dnsNameResolver, metav1.UpdateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + expectDNSNameWithAddresses(extEgDNS, dnsName, addresses) + }) + }) + ginkgo.It("Should not delete added addresses if DNS name is still used in a namespace", func() { start() From 03ccdf9884148ef741c82205618388c4a3d4fedf Mon Sep 17 00:00:00 2001 From: nithyar Date: Tue, 29 Jul 2025 08:07:51 -0700 Subject: [PATCH 187/278] Bump ubuntu to 25.04 Signed-off-by: nithyar --- dist/images/Dockerfile.ubuntu | 4 +--- dist/images/Dockerfile.ubuntu.arm64 | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dist/images/Dockerfile.ubuntu b/dist/images/Dockerfile.ubuntu index 10addc57d4..7fedefa624 100644 --- a/dist/images/Dockerfile.ubuntu +++ b/dist/images/Dockerfile.ubuntu @@ -8,14 +8,12 @@ # # So this file will change over time. -FROM ubuntu:24.10 +FROM ubuntu:25.04 USER root RUN apt-get update && apt-get install -y iproute2 curl software-properties-common util-linux nftables -RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - - # Install OVS and OVN packages. RUN apt-get update && apt-get install -y openvswitch-switch openvswitch-common ovn-central ovn-common ovn-host diff --git a/dist/images/Dockerfile.ubuntu.arm64 b/dist/images/Dockerfile.ubuntu.arm64 index 48a408b036..3830641cf0 100644 --- a/dist/images/Dockerfile.ubuntu.arm64 +++ b/dist/images/Dockerfile.ubuntu.arm64 @@ -8,14 +8,12 @@ # # So this file will change over time. -FROM ubuntu:24.10 +FROM ubuntu:25.04 USER root RUN apt-get update && apt-get install -y iproute2 curl software-properties-common util-linux nftables -RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - - # Install OVS and OVN packages. RUN apt-get update && apt-get install -y openvswitch-switch openvswitch-common ovn-central ovn-common ovn-host From 6241b2798a1947adbff2ba63f7eb4faf068cccbb Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Wed, 30 Jul 2025 12:51:35 +0530 Subject: [PATCH 188/278] dnsnameresolver: add e2e test to verify connectivity after DNS name TTL expiry Signed-off-by: arkadeepsen --- test/e2e/egress_firewall.go | 100 ++++++++++++++++++++++++++++++++++++ test/e2e/util.go | 5 ++ 2 files changed, 105 insertions(+) diff --git a/test/e2e/egress_firewall.go b/test/e2e/egress_firewall.go index abbc26b524..1805cf6a20 100644 --- a/test/e2e/egress_firewall.go +++ b/test/e2e/egress_firewall.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/test/e2e/framework" e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" e2enode "k8s.io/kubernetes/test/e2e/framework/node" + e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" utilnet "k8s.io/utils/net" ) @@ -664,4 +665,103 @@ spec: table.Entry("", false), table.Entry("with chaos testing using many dnsNames", true), ) + + ginkgo.Context("with DNS name resolver", func() { + ginkgo.BeforeEach(func() { + // DNS resolution for external DNS names does not work on IPv6 clusters. Skip + // the test if DNS name resolver is not enabled or IPv4 is not supported. + if !isDNSNameResolverEnabled() { + e2eskipper.Skipf("DNS name resolver is not enabled") + return + } + if !isIPv4Supported(f.ClientSet) { + e2eskipper.Skipf("IPv4 is not supported") + return + } + }) + + getMinTTLForDNSName := func(dnsName string, srcPodName string) int { + // Get the minimum TTL for the DNS name from the nslookup output. + // Ignore the error as it will always return an error because the + // cluster local DNS lookup will fail for the following DNS names: + // - ..svc. + // - .svc.. + // - . + nslookupOutput, _ := e2ekubectl.RunKubectl(f.Namespace.Name, "exec", srcPodName, "--", "nslookup", "-debug", "-timeout=2", dnsName) + lines := strings.Split(nslookupOutput, "\n") + minTTL := -1 + for i := 0; i < len(lines); i++ { + answerLine := strings.TrimSpace(lines[i]) + // Skip lines until we find the answer line for the DNS name + if !strings.HasPrefix(answerLine, fmt.Sprintf("-> %s", dnsName)) { + continue + } + // Find the TTL line for the DNS name + for i++; i < len(lines); i++ { + ttlLine := strings.TrimSpace(lines[i]) + if strings.HasPrefix(ttlLine, "ttl =") { + // Extract TTL value + ttlParts := strings.Split(ttlLine, "ttl =") + if len(ttlParts) == 2 { + ttlStr := strings.TrimSpace(ttlParts[1]) + ttl, err := strconv.Atoi(ttlStr) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to parse TTL value '%s': %v", ttlStr, err) + + // Update minimum TTL + if minTTL == -1 || ttl < minTTL { + minTTL = ttl + } + } + break + } + } + } + return minTTL + } + + ginkgo.It("Should validate that egressfirewall policy functionality for allowed DNS name", func() { + dnsName := "www.google.com" + srcPodName := "e2e-egress-fw-src-pod" + + // egress firewall crd yaml configuration + var egressFirewallConfig = fmt.Sprintf(`kind: EgressFirewall +apiVersion: k8s.ovn.org/v1 +metadata: + name: default + namespace: %s +spec: + egress: + - type: Allow + to: + dnsName: %s + - type: Deny + to: + cidrSelector: %s +`, f.Namespace.Name, dnsName, denyAllCIDR) + applyEF(egressFirewallConfig, f.Namespace.Name) + + // create the pod that will be used as the source for the connectivity test + createSrcPod(srcPodName, serverNodeInfo.name, retryInterval, retryTimeout, f) + + ginkgo.By(fmt.Sprintf("Verifying connectivity to DNS name %s is permitted", dnsName)) + url := fmt.Sprintf("https://%s", dnsName) + _, err := e2ekubectl.RunKubectl(f.Namespace.Name, "exec", srcPodName, "--", "curl", "-g", "--max-time", "5", url) + framework.ExpectNoError(err, "failed to curl DNS name %s", dnsName) + + ginkgo.By(fmt.Sprintf("Getting the minimum TTL for DNS name %s", dnsName)) + minTTL := getMinTTLForDNSName(dnsName, srcPodName) + gomega.Expect(minTTL).NotTo(gomega.Equal(-1), "failed to parse nslookup output for DNS name %s", dnsName) + framework.Logf("Minimum TTL for DNS name %s is %d", dnsName, minTTL) + + ginkgo.By(fmt.Sprintf("Waiting for the minimum TTL + 5 seconds for IP addresses of DNS name %s to be refreshed", dnsName)) + time.Sleep(time.Duration(minTTL+5) * time.Second) + + ginkgo.By(fmt.Sprintf("Verifying connectivity to DNS name %s is still permitted", dnsName)) + _, err = e2ekubectl.RunKubectl(f.Namespace.Name, "exec", srcPodName, "--", "curl", "-g", "--max-time", "5", url) + framework.ExpectNoError(err, "failed to curl DNS name %s", dnsName) + + framework.Logf("Deleting EgressFirewall in namespace %s", f.Namespace.Name) + e2ekubectl.RunKubectlOrDie(f.Namespace.Name, "delete", "egressfirewall", "default") + }) + }) }) diff --git a/test/e2e/util.go b/test/e2e/util.go index d03559e79e..8680dfcb73 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -1516,3 +1516,8 @@ func executeFileTemplate(templates *template.Template, directory, name string, d } return nil } + +func isDNSNameResolverEnabled() bool { + val, present := os.LookupEnv("OVN_ENABLE_DNSNAMERESOLVER") + return present && val == "true" +} From 8eb02f9f38dbf4e6c9f91ec4aaf4a837895d0b97 Mon Sep 17 00:00:00 2001 From: arkadeepsen Date: Wed, 30 Jul 2025 18:29:54 +0530 Subject: [PATCH 189/278] dnsnameresolver: run tests on dualstack instead of IPv6 only support Signed-off-by: arkadeepsen --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9d5d40eec..da04265476 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -454,14 +454,14 @@ jobs: - {"target": "shard-conformance", "ha": "HA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default"} - {"target": "shard-conformance", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "shard-conformance", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - - {"target": "control-plane", "ha": "HA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-disabled", "dns-name-resolver": "enable-dns-name-resolver"} + - {"target": "control-plane", "ha": "HA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-disabled"} - {"target": "control-plane", "ha": "HA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-disabled", "traffic-flow-tests": "1,2,3"} - {"target": "control-plane-helm","ha": "HA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-disabled", "dns-name-resolver": "enable-dns-name-resolver"} - {"target": "control-plane-helm","ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "dns-name-resolver": "enable-dns-name-resolver"} - {"target": "control-plane", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "dns-name-resolver": "enable-dns-name-resolver"} - {"target": "control-plane", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "control-plane", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "2br", "ic": "ic-single-node-zones"} - - {"target": "control-plane", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "2br", "ic": "ic-single-node-zones", "dns-name-resolver": "enable-dns-name-resolver"} + - {"target": "control-plane", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "2br", "ic": "ic-single-node-zones"} - {"target": "multi-homing", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv4", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-disabled"} - {"target": "multi-homing-helm", "ha": "HA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-disabled", "network-segmentation": "enable-network-segmentation"} - {"target": "node-ip-mac-migration", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-disabled"} @@ -481,8 +481,8 @@ jobs: - {"target": "network-segmentation", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "dualstack", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-disabled"} - {"target": "network-segmentation", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "network-segmentation", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - - {"target": "bgp", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation"} - - {"target": "bgp", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "dualstack", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation"} + - {"target": "bgp", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation", "dns-name-resolver": "enable-dns-name-resolver"} + - {"target": "bgp", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "dualstack", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "routeadvertisements": "advertise-default", "network-segmentation": "enable-network-segmentation", "dns-name-resolver": "enable-dns-name-resolver"} - {"target": "traffic-flow-test-only","ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "traffic-flow-tests": "1-24", "network-segmentation": "enable-network-segmentation"} - {"target": "tools", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "network-segmentation": "enable-network-segmentation"} needs: [ build-pr ] From 5180a461f483853341d4d382efd805a66d836284 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Wed, 25 Jun 2025 07:58:53 +0200 Subject: [PATCH 190/278] bump: network-attachment-definition-client 1.7.7 We need to bump it to allow NSE with both ipam claims reference and IPRequest. Signed-off-by: Enrique Llorente --- .../ovn-k8s-cni-overlay.go | 10 +- go-controller/go.mod | 5 +- go-controller/go.sum | 11 +- .../Masterminds/semver/v3/.gitignore | 1 + .../Masterminds/semver/v3/.golangci.yml | 27 + .../Masterminds/semver/v3/CHANGELOG.md | 214 ++++++ .../Masterminds/semver/v3/LICENSE.txt | 19 + .../github.com/Masterminds/semver/v3/Makefile | 30 + .../Masterminds/semver/v3/README.md | 258 +++++++ .../Masterminds/semver/v3/SECURITY.md | 19 + .../Masterminds/semver/v3/collection.go | 24 + .../Masterminds/semver/v3/constraints.go | 594 ++++++++++++++++ .../github.com/Masterminds/semver/v3/doc.go | 184 +++++ .../Masterminds/semver/v3/version.go | 639 ++++++++++++++++++ .../containernetworking/cni/libcni/api.go | 226 ++++++- .../containernetworking/cni/libcni/conf.go | 69 +- .../cni/libcni/multierror.go | 58 ++ .../cni/pkg/invoke/delegate.go | 25 +- .../cni/pkg/invoke/exec.go | 16 +- .../cni/pkg/invoke/os_unix.go | 1 + .../cni/pkg/ns/ns_linux.go | 50 ++ .../cni/pkg/ns/ns_windows.go | 21 + .../containernetworking/cni/pkg/skel/skel.go | 227 +++++-- .../cni/pkg/types/100/types.go | 57 +- .../containernetworking/cni/pkg/types/args.go | 4 +- .../cni/pkg/types/create/create.go | 3 + .../cni/pkg/types/types.go | 131 +++- .../cni/pkg/utils/utils.go | 6 +- .../cni/pkg/version/version.go | 9 +- .../pkg/apis/k8s.cni.cncf.io/v1/types.go | 10 +- .../pkg/utils/net-attach-def.go | 112 +++ go-controller/vendor/modules.txt | 10 +- test/e2e/go.mod | 5 +- test/e2e/go.sum | 11 +- 34 files changed, 2944 insertions(+), 142 deletions(-) create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/Makefile create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/README.md create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/collection.go create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/doc.go create mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/version.go create mode 100644 go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go create mode 100644 go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_linux.go create mode 100644 go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_windows.go diff --git a/go-controller/cmd/ovn-k8s-cni-overlay/ovn-k8s-cni-overlay.go b/go-controller/cmd/ovn-k8s-cni-overlay/ovn-k8s-cni-overlay.go index 621d1e01d1..88d94faeb5 100644 --- a/go-controller/cmd/ovn-k8s-cni-overlay/ovn-k8s-cni-overlay.go +++ b/go-controller/cmd/ovn-k8s-cni-overlay/ovn-k8s-cni-overlay.go @@ -20,10 +20,12 @@ func main() { p := cni.NewCNIPlugin("") c.Action = func(_ *cli.Context) error { - skel.PluginMain( - p.CmdAdd, - p.CmdCheck, - p.CmdDel, + skel.PluginMainFuncs( + skel.CNIFuncs{ + Add: p.CmdAdd, + Check: p.CmdCheck, + Del: p.CmdDel, + }, version.All, bv.BuildString("ovn-k8s-cni-overlay")) return nil diff --git a/go-controller/go.mod b/go-controller/go.mod index 72e89c3b7a..afd8cdfe11 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -10,7 +10,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e github.com/cenkalti/backoff/v4 v4.3.0 - github.com/containernetworking/cni v1.1.2 + github.com/containernetworking/cni v1.2.0-rc1 github.com/containernetworking/plugins v1.2.0 github.com/coreos/go-iptables v0.6.0 github.com/fsnotify/fsnotify v1.7.0 @@ -25,7 +25,7 @@ require ( github.com/k8snetworkplumbingwg/govdpa v0.1.5-0.20230926073613-07c1031aea47 github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 - github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875 github.com/mdlayher/ndp v1.0.1 @@ -73,6 +73,7 @@ require ( ) require ( + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/hub v1.0.1 // indirect diff --git a/go-controller/go.sum b/go-controller/go.sum index 2af1883f7e..6d72aee42e 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -55,6 +55,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JacobTanenbaum/arping v0.0.0-20240209152419-3987db83bd51/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -199,8 +201,8 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= -github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/cni v1.2.0-rc1 h1:AKI3+pXtgY4PDLN9+50o9IaywWVuey0Jkw3Lvzp0HCY= +github.com/containernetworking/cni v1.2.0-rc1/go.mod h1:Lt0TQcZQVDju64fYxUhDziTgXCDe3Olzi9I4zZJLWHg= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU= @@ -498,8 +500,8 @@ github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha h1:b3iHeks/KTzhG2dNanaUZ github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha/go.mod h1:MGaMX1tJ7MlHDee4/xmqp3guQh+eDiuCLAauqD9K11Q= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 h1:Egj1hEVYNXWFlKpgzAXxe/2o8VNiVcAJLrKzlinILQo= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1/go.mod h1:kEJ4WM849yNmXekuSXLRwb+LaZ9usC06O8JgoAIq+f4= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 h1:BT3ghAY0q7lWib9rz+tVXDFkm27dJV6SLCn7TunZwo4= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0/go.mod h1:wxt2YWRVItDtaQmVSmaN5ubE2L1c9CiNoHQwSJnM8Ko= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc h1:v6+jUd70AayPbIRgTYUNpnBLG5cBPTY0+10y80CZeMk= github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc/go.mod h1:jyWzGe6ZtYiPq6ih6aXCOy6mZ49Y9mNyBOLBBXnli+k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -603,7 +605,6 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore b/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore new file mode 100644 index 0000000000..6b061e6174 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore @@ -0,0 +1 @@ +_fuzz/ \ No newline at end of file diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml b/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml new file mode 100644 index 0000000000..fbc6332592 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml @@ -0,0 +1,27 @@ +run: + deadline: 2m + +linters: + disable-all: true + enable: + - misspell + - govet + - staticcheck + - errcheck + - unparam + - ineffassign + - nakedret + - gocyclo + - dupl + - goimports + - revive + - gosec + - gosimple + - typecheck + - unused + +linters-settings: + gofmt: + simplify: true + dupl: + threshold: 600 diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md b/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md new file mode 100644 index 0000000000..f12626423a --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md @@ -0,0 +1,214 @@ +# Changelog + +## 3.2.0 (2022-11-28) + +### Added + +- #190: Added text marshaling and unmarshaling +- #167: Added JSON marshalling for constraints (thanks @SimonTheLeg) +- #173: Implement encoding.TextMarshaler and encoding.TextUnmarshaler on Version (thanks @MarkRosemaker) +- #179: Added New() version constructor (thanks @kazhuravlev) + +### Changed + +- #182/#183: Updated CI testing setup + +### Fixed + +- #186: Fixing issue where validation of constraint section gave false positives +- #176: Fix constraints check with *-0 (thanks @mtt0) +- #181: Fixed Caret operator (^) gives unexpected results when the minor version in constraint is 0 (thanks @arshchimni) +- #161: Fixed godoc (thanks @afirth) + +## 3.1.1 (2020-11-23) + +### Fixed + +- #158: Fixed issue with generated regex operation order that could cause problem + +## 3.1.0 (2020-04-15) + +### Added + +- #131: Add support for serializing/deserializing SQL (thanks @ryancurrah) + +### Changed + +- #148: More accurate validation messages on constraints + +## 3.0.3 (2019-12-13) + +### Fixed + +- #141: Fixed issue with <= comparison + +## 3.0.2 (2019-11-14) + +### Fixed + +- #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos) + +## 3.0.1 (2019-09-13) + +### Fixed + +- #125: Fixes issue with module path for v3 + +## 3.0.0 (2019-09-12) + +This is a major release of the semver package which includes API changes. The Go +API is compatible with ^1. The Go API was not changed because many people are using +`go get` without Go modules for their applications and API breaking changes cause +errors which we have or would need to support. + +The changes in this release are the handling based on the data passed into the +functions. These are described in the added and changed sections below. + +### Added + +- StrictNewVersion function. This is similar to NewVersion but will return an + error if the version passed in is not a strict semantic version. For example, + 1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly + speaking semantic versions. This function is faster, performs fewer operations, + and uses fewer allocations than NewVersion. +- Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint. + The Makefile contains the operations used. For more information on you can start + on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing +- Now using Go modules + +### Changed + +- NewVersion has proper prerelease and metadata validation with error messages + to signal an issue with either of them +- ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the + version is >=1 the ^ ranges works the same as v1. For major versions of 0 the + rules have changed. The minor version is treated as the stable version unless + a patch is specified and then it is equivalent to =. One difference from npm/js + is that prereleases there are only to a specific version (e.g. 1.2.3). + Prereleases here look over multiple versions and follow semantic version + ordering rules. This pattern now follows along with the expected and requested + handling of this packaged by numerous users. + +## 1.5.0 (2019-09-11) + +### Added + +- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) + +### Changed + +- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) +- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) +- #72: Adding docs comment pointing to vert for a cli +- #71: Update the docs on pre-release comparator handling +- #89: Test with new go versions (thanks @thedevsaddam) +- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) + +### Fixed + +- #78: Fix unchecked error in example code (thanks @ravron) +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case +- #97: Fixed copyright file for proper display on GitHub +- #107: Fix handling prerelease when sorting alphanum and num +- #109: Fixed where Validate sometimes returns wrong message on error + +## 1.4.2 (2018-04-10) + +### Changed + +- #72: Updated the docs to point to vert for a console appliaction +- #71: Update the docs on pre-release comparator handling + +### Fixed + +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case + +## 1.4.1 (2018-04-02) + +### Fixed + +- Fixed #64: Fix pre-release precedence issue (thanks @uudashr) + +## 1.4.0 (2017-10-04) + +### Changed + +- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) + +## 1.3.1 (2017-07-10) + +### Fixed + +- Fixed #57: number comparisons in prerelease sometimes inaccurate + +## 1.3.0 (2017-05-02) + +### Added + +- #45: Added json (un)marshaling support (thanks @mh-cbon) +- Stability marker. See https://masterminds.github.io/stability/ + +### Fixed + +- #51: Fix handling of single digit tilde constraint (thanks @dgodd) + +### Changed + +- #55: The godoc icon moved from png to svg + +## 1.2.3 (2017-04-03) + +### Fixed + +- #46: Fixed 0.x.x and 0.0.x in constraints being treated as * + +## Release 1.2.2 (2016-12-13) + +### Fixed + +- #34: Fixed issue where hyphen range was not working with pre-release parsing. + +## Release 1.2.1 (2016-11-28) + +### Fixed + +- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" + properly. + +## Release 1.2.0 (2016-11-04) + +### Added + +- #20: Added MustParse function for versions (thanks @adamreese) +- #15: Added increment methods on versions (thanks @mh-cbon) + +### Fixed + +- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and + might not satisfy the intended compatibility. The change here ignores pre-releases + on constraint checks (e.g., ~ or ^) when a pre-release is not part of the + constraint. For example, `^1.2.3` will ignore pre-releases while + `^1.2.3-alpha` will include them. + +## Release 1.1.1 (2016-06-30) + +### Changed + +- Issue #9: Speed up version comparison performance (thanks @sdboyer) +- Issue #8: Added benchmarks (thanks @sdboyer) +- Updated Go Report Card URL to new location +- Updated Readme to add code snippet formatting (thanks @mh-cbon) +- Updating tagging to v[SemVer] structure for compatibility with other tools. + +## Release 1.1.0 (2016-03-11) + +- Issue #2: Implemented validation to provide reasons a versions failed a + constraint. + +## Release 1.0.1 (2015-12-31) + +- Fixed #1: * constraint failing on valid versions. + +## Release 1.0.0 (2015-10-20) + +- Initial release diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt b/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt new file mode 100644 index 0000000000..9ff7da9c48 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2014-2019, Matt Butcher and Matt Farina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile b/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile new file mode 100644 index 0000000000..0e7b5c7138 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile @@ -0,0 +1,30 @@ +GOPATH=$(shell go env GOPATH) +GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint + +.PHONY: lint +lint: $(GOLANGCI_LINT) + @echo "==> Linting codebase" + @$(GOLANGCI_LINT) run + +.PHONY: test +test: + @echo "==> Running tests" + GO111MODULE=on go test -v + +.PHONY: test-cover +test-cover: + @echo "==> Running Tests with coverage" + GO111MODULE=on go test -cover . + +.PHONY: fuzz +fuzz: + @echo "==> Running Fuzz Tests" + go test -fuzz=FuzzNewVersion -fuzztime=15s . + go test -fuzz=FuzzStrictNewVersion -fuzztime=15s . + go test -fuzz=FuzzNewConstraint -fuzztime=15s . + +$(GOLANGCI_LINT): + # Install golangci-lint. The configuration for it is in the .golangci.yml + # file in the root of the repository + echo ${GOPATH} + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1 diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/README.md b/go-controller/vendor/github.com/Masterminds/semver/v3/README.md new file mode 100644 index 0000000000..eab8cac3b7 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/README.md @@ -0,0 +1,258 @@ +# SemVer + +The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: + +* Parse semantic versions +* Sort semantic versions +* Check if a semantic version fits within a set of constraints +* Optionally work with a `v` prefix + +[![Stability: +Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) +[![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/semver/actions) +[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3) +[![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) + +If you are looking for a command line tool for version comparisons please see +[vert](https://github.com/Masterminds/vert) which uses this library. + +## Package Versions + +Note, import `github.com/github.com/Masterminds/semver/v3` to use the latest version. + +There are three major versions fo the `semver` package. + +* 3.x.x is the stable and active version. This version is focused on constraint + compatibility for range handling in other tools from other languages. It has + a similar API to the v1 releases. The development of this version is on the master + branch. The documentation for this version is below. +* 2.x was developed primarily for [dep](https://github.com/golang/dep). There are + no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer). + There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x). +* 1.x.x is the original release. It is no longer maintained. You should use the + v3 release instead. You can read the documentation for the 1.x.x release + [here](https://github.com/Masterminds/semver/blob/release-1/README.md). + +## Parsing Semantic Versions + +There are two functions that can parse semantic versions. The `StrictNewVersion` +function only parses valid version 2 semantic versions as outlined in the +specification. The `NewVersion` function attempts to coerce a version into a +semantic version and parse it. For example, if there is a leading v or a version +listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid +semantic version (e.g., 1.2.0). In both cases a `Version` object is returned +that can be sorted, compared, and used in constraints. + +When parsing a version an error is returned if there is an issue parsing the +version. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+build345") + +The version object has methods to get the parts of the version, compare it to +other versions, convert the version back into a string, and get the original +string. Getting the original string is useful if the semantic version was coerced +into a valid form. + +## Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + +```go +raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} +vs := make([]*semver.Version, len(raw)) +for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v +} + +sort.Sort(semver.Collection(vs)) +``` + +## Checking Version Constraints + +There are two methods for comparing versions. One uses comparison methods on +`Version` instances and the other uses `Constraints`. There are some important +differences to notes between these two methods of comparison. + +1. When two versions are compared using functions such as `Compare`, `LessThan`, + and others it will follow the specification and always include prereleases + within the comparison. It will provide an answer that is valid with the + comparison section of the spec at https://semver.org/#spec-item-11 +2. When constraint checking is used for checks or validation it will follow a + different set of rules that are common for ranges with tools like npm/js + and Rust/Cargo. This includes considering prereleases to be invalid if the + ranges does not include one. If you want to have it include pre-releases a + simple solution is to include `-0` in your range. +3. Constraint ranges can have some complex rules including the shorthand use of + ~ and ^. For more details on those see the options below. + +There are differences between the two methods or checking versions because the +comparison methods on `Version` follow the specification while comparison ranges +are not part of the specification. Different packages and tools have taken it +upon themselves to come up with range rules. This has resulted in differences. +For example, npm/js and Cargo/Rust follow similar patterns while PHP has a +different pattern for ^. The comparison features in this package follow the +npm/js and Cargo/Rust lead because applications using it have followed similar +patters with their versions. + +Checking a version against version constraints is one of the most featureful +parts of the package. + +```go +c, err := semver.NewConstraint(">= 1.2.3") +if err != nil { + // Handle constraint not being parsable. +} + +v, err := semver.NewVersion("1.3") +if err != nil { + // Handle version not being parsable. +} +// Check if the version meets the constraints. The a variable will be true. +a := c.Check(v) +``` + +### Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of space or comma separated AND comparisons. These are then separated by || (OR) +comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + +* `=`: equal (aliased to no operator) +* `!=`: not equal +* `>`: greater than +* `<`: less than +* `>=`: greater than or equal to +* `<=`: less than or equal to + +### Working With Prerelease Versions + +Pre-releases, for those not familiar with them, are used for software releases +prior to stable or generally available releases. Examples of prereleases include +development, alpha, beta, and release candidate releases. A prerelease may be +a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the +order of precedence, prereleases come before their associated releases. In this +example `1.2.3-beta.1 < 1.2.3`. + +According to the Semantic Version specification prereleases may not be +API compliant with their release counterpart. It says, + +> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. + +SemVer comparisons using constraints without a prerelease comparator will skip +prerelease versions. For example, `>=1.2.3` will skip prereleases when looking +at a list of releases while `>=1.2.3-0` will evaluate and find prereleases. + +The reason for the `0` as a pre-release version in the example comparison is +because pre-releases can only contain ASCII alphanumerics and hyphens (along with +`.` separators), per the spec. Sorting happens in ASCII sort order, again per the +spec. The lowest character is a `0` in ASCII sort order +(see an [ASCII Table](http://www.asciitable.com/)) + +Understanding ASCII sort ordering is important because A-Z comes before a-z. That +means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case +sensitivity doesn't apply here. This is due to ASCII sort ordering which is what +the spec specifies. + +### Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + +* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5` +* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` + +### Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the patch level comparison (see tilde below). For example, + +* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `>= 1.2.x` is equivalent to `>= 1.2.0` +* `<= 2.x` is equivalent to `< 3` +* `*` is equivalent to `>= 0.0.0` + +### Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + +* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` +* `~1` is equivalent to `>= 1, < 2` +* `~2.3` is equivalent to `>= 2.3, < 2.4` +* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `~1.x` is equivalent to `>= 1, < 2` + +### Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes once a stable +(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts +as the API stability level. This is useful when comparisons of API versions as a +major change is API breaking. For example, + +* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` +* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` +* `^2.3` is equivalent to `>= 2.3, < 3` +* `^2.x` is equivalent to `>= 2.0.0, < 3` +* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` +* `^0.2` is equivalent to `>=0.2.0 <0.3.0` +* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` +* `^0.0` is equivalent to `>=0.0.0 <0.1.0` +* `^0` is equivalent to `>=0.0.0 <1.0.0` + +## Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + +```go +c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") +if err != nil { + // Handle constraint not being parseable. +} + +v, err := semver.NewVersion("1.3") +if err != nil { + // Handle version not being parseable. +} + +// Validate a version against a constraint. +a, msgs := c.Validate(v) +// a is false +for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" +} +``` + +## Contribute + +If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) +or [create a pull request](https://github.com/Masterminds/semver/pulls). + +## Security + +Security is an important consideration for this project. The project currently +uses the following tools to help discover security issues: + +* [CodeQL](https://github.com/Masterminds/semver) +* [gosec](https://github.com/securego/gosec) +* Daily Fuzz testing + +If you believe you have found a security vulnerability you can privately disclose +it through the [GitHub security page](https://github.com/Masterminds/semver/security). diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md b/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md new file mode 100644 index 0000000000..a30a66b1f7 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions + +The following versions of semver are currently supported: + +| Version | Supported | +| ------- | ------------------ | +| 3.x | :white_check_mark: | +| 2.x | :x: | +| 1.x | :x: | + +Fixes are only released for the latest minor version in the form of a patch release. + +## Reporting a Vulnerability + +You can privately disclose a vulnerability through GitHubs +[private vulnerability reporting](https://github.com/Masterminds/semver/security/advisories) +mechanism. diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go b/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go new file mode 100644 index 0000000000..a78235895f --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go @@ -0,0 +1,24 @@ +package semver + +// Collection is a collection of Version instances and implements the sort +// interface. See the sort package for more details. +// https://golang.org/pkg/sort/ +type Collection []*Version + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c Collection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c Collection) Less(i, j int) bool { + return c[i].LessThan(c[j]) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c Collection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go b/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go new file mode 100644 index 0000000000..8461c7ed90 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go @@ -0,0 +1,594 @@ +package semver + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" +) + +// Constraints is one or more constraint that a semantic version can be +// checked against. +type Constraints struct { + constraints [][]*constraint +} + +// NewConstraint returns a Constraints instance that a Version instance can +// be checked against. If there is a parse error it will be returned. +func NewConstraint(c string) (*Constraints, error) { + + // Rewrite - ranges into a comparison operation. + c = rewriteRange(c) + + ors := strings.Split(c, "||") + or := make([][]*constraint, len(ors)) + for k, v := range ors { + + // TODO: Find a way to validate and fetch all the constraints in a simpler form + + // Validate the segment + if !validConstraintRegex.MatchString(v) { + return nil, fmt.Errorf("improper constraint: %s", v) + } + + cs := findConstraintRegex.FindAllString(v, -1) + if cs == nil { + cs = append(cs, v) + } + result := make([]*constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s) + if err != nil { + return nil, err + } + + result[i] = pc + } + or[k] = result + } + + o := &Constraints{constraints: or} + return o, nil +} + +// Check tests if a version satisfies the constraints. +func (cs Constraints) Check(v *Version) bool { + // TODO(mattfarina): For v4 of this library consolidate the Check and Validate + // functions as the underlying functions make that possible now. + // loop over the ORs and check the inner ANDs + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if check, _ := c.check(v); !check { + joy = false + break + } + } + + if joy { + return true + } + } + + return false +} + +// Validate checks if a version satisfies a constraint. If not a slice of +// reasons for the failure are returned in addition to a bool. +func (cs Constraints) Validate(v *Version) (bool, []error) { + // loop over the ORs and check the inner ANDs + var e []error + + // Capture the prerelease message only once. When it happens the first time + // this var is marked + var prerelesase bool + for _, o := range cs.constraints { + joy := true + for _, c := range o { + // Before running the check handle the case there the version is + // a prerelease and the check is not searching for prereleases. + if c.con.pre == "" && v.pre != "" { + if !prerelesase { + em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + e = append(e, em) + prerelesase = true + } + joy = false + + } else { + + if _, err := c.check(v); err != nil { + e = append(e, err) + joy = false + } + } + } + + if joy { + return true, []error{} + } + } + + return false, e +} + +func (cs Constraints) String() string { + buf := make([]string, len(cs.constraints)) + var tmp bytes.Buffer + + for k, v := range cs.constraints { + tmp.Reset() + vlen := len(v) + for kk, c := range v { + tmp.WriteString(c.string()) + + // Space separate the AND conditions + if vlen > 1 && kk < vlen-1 { + tmp.WriteString(" ") + } + } + buf[k] = tmp.String() + } + + return strings.Join(buf, " || ") +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (cs *Constraints) UnmarshalText(text []byte) error { + temp, err := NewConstraint(string(text)) + if err != nil { + return err + } + + *cs = *temp + + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (cs Constraints) MarshalText() ([]byte, error) { + return []byte(cs.String()), nil +} + +var constraintOps map[string]cfunc +var constraintRegex *regexp.Regexp +var constraintRangeRegex *regexp.Regexp + +// Used to find individual constraints within a multi-constraint string +var findConstraintRegex *regexp.Regexp + +// Used to validate an segment of ANDs is valid +var validConstraintRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +func init() { + constraintOps = map[string]cfunc{ + "": constraintTildeOrEqual, + "=": constraintTildeOrEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~": constraintTilde, + "~>": constraintTilde, + "^": constraintCaret, + } + + ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^` + + constraintRegex = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + ops, + cvRegex)) + + constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( + `\s*(%s)\s+-\s+(%s)\s*`, + cvRegex, cvRegex)) + + findConstraintRegex = regexp.MustCompile(fmt.Sprintf( + `(%s)\s*(%s)`, + ops, + cvRegex)) + + // The first time a constraint shows up will look slightly different from + // future times it shows up due to a leading space or comma in a given + // string. + validConstraintRegex = regexp.MustCompile(fmt.Sprintf( + `^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`, + ops, + cvRegex, + ops, + cvRegex)) +} + +// An individual constraint +type constraint struct { + // The version used in the constraint check. For example, if a constraint + // is '<= 2.0.0' the con a version instance representing 2.0.0. + con *Version + + // The original parsed version (e.g., 4.x from != 4.x) + orig string + + // The original operator for the constraint + origfunc string + + // When an x is used as part of the version (e.g., 1.x) + minorDirty bool + dirty bool + patchDirty bool +} + +// Check if a version meets the constraint +func (c *constraint) check(v *Version) (bool, error) { + return constraintOps[c.origfunc](v, c) +} + +// String prints an individual constraint into a string +func (c *constraint) string() string { + return c.origfunc + c.orig +} + +type cfunc func(v *Version, c *constraint) (bool, error) + +func parseConstraint(c string) (*constraint, error) { + if len(c) > 0 { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + cs := &constraint{ + orig: m[2], + origfunc: m[1], + } + + ver := m[2] + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) || m[3] == "" { + ver = fmt.Sprintf("0.0.0%s", m[6]) + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs.con = con + cs.minorDirty = minorDirty + cs.patchDirty = patchDirty + cs.dirty = dirty + + return cs, nil + } + + // The rest is the special case where an empty string was passed in which + // is equivalent to * or >=0.0.0 + con, err := StrictNewVersion("0.0.0") + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs := &constraint{ + con: con, + orig: c, + origfunc: "", + minorDirty: false, + patchDirty: false, + dirty: true, + } + return cs, nil +} + +// Constraint functions +func constraintNotEqual(v *Version, c *constraint) (bool, error) { + if c.dirty { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if c.con.Major() != v.Major() { + return true, nil + } + if c.con.Minor() != v.Minor() && !c.minorDirty { + return true, nil + } else if c.minorDirty { + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } else if c.con.Patch() != v.Patch() && !c.patchDirty { + return true, nil + } else if c.patchDirty { + // Need to handle prereleases if present + if v.Prerelease() != "" || c.con.Prerelease() != "" { + eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + } + + eq := v.Equal(c.con) + if eq { + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + + return true, nil +} + +func constraintGreaterThan(v *Version, c *constraint) (bool, error) { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + var eq bool + + if !c.dirty { + eq = v.Compare(c.con) == 1 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } + + if v.Major() > c.con.Major() { + return true, nil + } else if v.Major() < c.con.Major() { + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } else if c.minorDirty { + // This is a range case such as >11. When the version is something like + // 11.1.0 is it not > 11. For that we would need 12 or higher + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } else if c.patchDirty { + // This is for ranges such as >11.1. A version of 11.1.1 is not greater + // which one of 11.2.1 is greater + eq = v.Minor() > c.con.Minor() + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } + + // If we have gotten here we are not comparing pre-preleases and can use the + // Compare function to accomplish that. + eq = v.Compare(c.con) == 1 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) +} + +func constraintLessThan(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + eq := v.Compare(c.con) < 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) +} + +func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + eq := v.Compare(c.con) >= 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than %s", v, c.orig) +} + +func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + var eq bool + + if !c.dirty { + eq = v.Compare(c.con) <= 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } + + if v.Major() > c.con.Major() { + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } + + return true, nil +} + +// ~*, ~>* --> >= 0.0.0 (any) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 +func constraintTilde(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if v.LessThan(c.con) { + return false, fmt.Errorf("%s is less than %s", v, c.orig) + } + + // ~0.0.0 is a special case where all constraints are accepted. It's + // equivalent to >= 0.0.0. + if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && + !c.minorDirty && !c.patchDirty { + return true, nil + } + + if v.Major() != c.con.Major() { + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + + if v.Minor() != c.con.Minor() && !c.minorDirty { + return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) + } + + return true, nil +} + +// When there is a .x (dirty) status it automatically opts in to ~. Otherwise +// it's a straight = +func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if c.dirty { + return constraintTilde(v, c) + } + + eq := v.Equal(c.con) + if eq { + return true, nil + } + + return false, fmt.Errorf("%s is not equal to %s", v, c.orig) +} + +// ^* --> (any) +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2 --> >=1.2.0 <2.0.0 +// ^1 --> >=1.0.0 <2.0.0 +// ^0.2.3 --> >=0.2.3 <0.3.0 +// ^0.2 --> >=0.2.0 <0.3.0 +// ^0.0.3 --> >=0.0.3 <0.0.4 +// ^0.0 --> >=0.0.0 <0.1.0 +// ^0 --> >=0.0.0 <1.0.0 +func constraintCaret(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + // This less than handles prereleases + if v.LessThan(c.con) { + return false, fmt.Errorf("%s is less than %s", v, c.orig) + } + + var eq bool + + // ^ when the major > 0 is >=x.y.z < x+1 + if c.con.Major() > 0 || c.minorDirty { + + // ^ has to be within a major range for > 0. Everything less than was + // filtered out with the LessThan call above. This filters out those + // that greater but not within the same major range. + eq = v.Major() == c.con.Major() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + + // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 + if c.con.Major() == 0 && v.Major() > 0 { + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + // If the con Minor is > 0 it is not dirty + if c.con.Minor() > 0 || c.patchDirty { + eq = v.Minor() == c.con.Minor() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) + } + // ^ when the minor is 0 and minor > 0 is =0.0.z + if c.con.Minor() == 0 && v.Minor() > 0 { + return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig) + } + + // At this point the major is 0 and the minor is 0 and not dirty. The patch + // is not dirty so we need to check if they are equal. If they are not equal + eq = c.con.Patch() == v.Patch() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) +} + +func isX(x string) bool { + switch x { + case "x", "*", "X": + return true + default: + return false + } +} + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go b/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go new file mode 100644 index 0000000000..74f97caa57 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go @@ -0,0 +1,184 @@ +/* +Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. + +Specifically it provides the ability to: + + - Parse semantic versions + - Sort semantic versions + - Check if a semantic version fits within a set of constraints + - Optionally work with a `v` prefix + +# Parsing Semantic Versions + +There are two functions that can parse semantic versions. The `StrictNewVersion` +function only parses valid version 2 semantic versions as outlined in the +specification. The `NewVersion` function attempts to coerce a version into a +semantic version and parse it. For example, if there is a leading v or a version +listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid +semantic version (e.g., 1.2.0). In both cases a `Version` object is returned +that can be sorted, compared, and used in constraints. + +When parsing a version an optional error can be returned if there is an issue +parsing the version. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+b345") + +The version object has methods to get the parts of the version, compare it to +other versions, convert the version back into a string, and get the original +string. For more details please see the documentation +at https://godoc.org/github.com/Masterminds/semver. + +# Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) + +# Checking Version Constraints and Comparing Versions + +There are two methods for comparing versions. One uses comparison methods on +`Version` instances and the other is using Constraints. There are some important +differences to notes between these two methods of comparison. + + 1. When two versions are compared using functions such as `Compare`, `LessThan`, + and others it will follow the specification and always include prereleases + within the comparison. It will provide an answer valid with the comparison + spec section at https://semver.org/#spec-item-11 + 2. When constraint checking is used for checks or validation it will follow a + different set of rules that are common for ranges with tools like npm/js + and Rust/Cargo. This includes considering prereleases to be invalid if the + ranges does not include on. If you want to have it include pre-releases a + simple solution is to include `-0` in your range. + 3. Constraint ranges can have some complex rules including the shorthard use of + ~ and ^. For more details on those see the options below. + +There are differences between the two methods or checking versions because the +comparison methods on `Version` follow the specification while comparison ranges +are not part of the specification. Different packages and tools have taken it +upon themselves to come up with range rules. This has resulted in differences. +For example, npm/js and Cargo/Rust follow similar patterns which PHP has a +different pattern for ^. The comparison features in this package follow the +npm/js and Cargo/Rust lead because applications using it have followed similar +patters with their versions. + +Checking a version against version constraints is one of the most featureful +parts of the package. + + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parsable. + } + + v, err := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parsable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) + +# Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma or space separated AND comparisons. These are then separated by || (OR) +comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. This can also be written as +`">= 1.2, < 3.0.0 || >= 4.2.3"` + +The basic comparisons are: + + - `=`: equal (aliased to no operator) + - `!=`: not equal + - `>`: greater than + - `<`: less than + - `>=`: greater than or equal to + - `<=`: less than or equal to + +# Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + + - `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` + - `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` + +# Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the tilde operation. For example, + + - `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` + - `>= 1.2.x` is equivalent to `>= 1.2.0` + - `<= 2.x` is equivalent to `<= 3` + - `*` is equivalent to `>= 0.0.0` + +Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + + - `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0` + - `~1` is equivalent to `>= 1, < 2` + - `~2.3` is equivalent to `>= 2.3 < 2.4` + - `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` + - `~1.x` is equivalent to `>= 1 < 2` + +Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes once a stable +(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts +as the API stability level. This is useful when comparisons of API versions as a +major change is API breaking. For example, + + - `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` + - `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` + - `^2.3` is equivalent to `>= 2.3, < 3` + - `^2.x` is equivalent to `>= 2.0.0, < 3` + - `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` + - `^0.2` is equivalent to `>=0.2.0 <0.3.0` + - `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` + - `^0.0` is equivalent to `>=0.0.0 <0.1.0` + - `^0` is equivalent to `>=0.0.0 <1.0.0` + +# Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + + c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + + // Validate a version against a constraint. + a, msgs := c.Validate(v) + // a is false + for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" + } +*/ +package semver diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/version.go b/go-controller/vendor/github.com/Masterminds/semver/v3/version.go new file mode 100644 index 0000000000..7c4bed3347 --- /dev/null +++ b/go-controller/vendor/github.com/Masterminds/semver/v3/version.go @@ -0,0 +1,639 @@ +package semver + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +// The compiled version of the regex created at init() is cached here so it +// only needs to be created once. +var versionRegex *regexp.Regexp + +var ( + // ErrInvalidSemVer is returned a version is found to be invalid when + // being parsed. + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + + // ErrEmptyString is returned when an empty string is passed in for parsing. + ErrEmptyString = errors.New("Version string empty") + + // ErrInvalidCharacters is returned when invalid characters are found as + // part of a version + ErrInvalidCharacters = errors.New("Invalid characters in version") + + // ErrSegmentStartsZero is returned when a version segment starts with 0. + // This is invalid in SemVer. + ErrSegmentStartsZero = errors.New("Version segment starts with 0") + + // ErrInvalidMetadata is returned when the metadata is an invalid format + ErrInvalidMetadata = errors.New("Invalid Metadata string") + + // ErrInvalidPrerelease is returned when the pre-release is an invalid format + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") +) + +// semVerRegex is the regular expression used to parse a semantic version. +const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +// Version represents a single semantic version. +type Version struct { + major, minor, patch uint64 + pre string + metadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + semVerRegex + "$") +} + +const ( + num string = "0123456789" + allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num +) + +// StrictNewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. Only parses valid semantic versions. +// Performs checking that can find errors within the version. +// If you want to coerce a version such as 1 or 1.2 and parse it as the 1.x +// releases of semver did, use the NewVersion() function. +func StrictNewVersion(v string) (*Version, error) { + // Parsing here does not use RegEx in order to increase performance and reduce + // allocations. + + if len(v) == 0 { + return nil, ErrEmptyString + } + + // Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build + parts := strings.SplitN(v, ".", 3) + if len(parts) != 3 { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + original: v, + } + + // check for prerelease or build metadata + var extra []string + if strings.ContainsAny(parts[2], "-+") { + // Start with the build metadata first as it needs to be on the right + extra = strings.SplitN(parts[2], "+", 2) + if len(extra) > 1 { + // build metadata found + sv.metadata = extra[1] + parts[2] = extra[0] + } + + extra = strings.SplitN(parts[2], "-", 2) + if len(extra) > 1 { + // prerelease found + sv.pre = extra[1] + parts[2] = extra[0] + } + } + + // Validate the number segments are valid. This includes only having positive + // numbers and no leading 0's. + for _, p := range parts { + if !containsOnly(p, num) { + return nil, ErrInvalidCharacters + } + + if len(p) > 1 && p[0] == '0' { + return nil, ErrSegmentStartsZero + } + } + + // Extract the major, minor, and patch elements onto the returned Version + var err error + sv.major, err = strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return nil, err + } + + sv.minor, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return nil, err + } + + sv.patch, err = strconv.ParseUint(parts[2], 10, 64) + if err != nil { + return nil, err + } + + // No prerelease or build metadata found so returning now as a fastpath. + if sv.pre == "" && sv.metadata == "" { + return sv, nil + } + + if sv.pre != "" { + if err = validatePrerelease(sv.pre); err != nil { + return nil, err + } + } + + if sv.metadata != "" { + if err = validateMetadata(sv.metadata); err != nil { + return nil, err + } + } + + return sv, nil +} + +// NewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. If the version is SemVer-ish it +// attempts to convert it to SemVer. If you want to validate it was a strict +// semantic version at parse time see StrictNewVersion(). +func NewVersion(v string) (*Version, error) { + m := versionRegex.FindStringSubmatch(v) + if m == nil { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + metadata: m[8], + pre: m[5], + original: v, + } + + var err error + sv.major, err = strconv.ParseUint(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + + if m[2] != "" { + sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + } else { + sv.minor = 0 + } + + if m[3] != "" { + sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + } else { + sv.patch = 0 + } + + // Perform some basic due diligence on the extra parts to ensure they are + // valid. + + if sv.pre != "" { + if err = validatePrerelease(sv.pre); err != nil { + return nil, err + } + } + + if sv.metadata != "" { + if err = validateMetadata(sv.metadata); err != nil { + return nil, err + } + } + + return sv, nil +} + +// New creates a new instance of Version with each of the parts passed in as +// arguments instead of parsing a version string. +func New(major, minor, patch uint64, pre, metadata string) *Version { + v := Version{ + major: major, + minor: minor, + patch: patch, + pre: pre, + metadata: metadata, + original: "", + } + + v.original = v.String() + + return &v +} + +// MustParse parses a given version and panics on error. +func MustParse(v string) *Version { + sv, err := NewVersion(v) + if err != nil { + panic(err) + } + return sv +} + +// String converts a Version object to a string. +// Note, if the original version contained a leading v this version will not. +// See the Original() method to retrieve the original value. Semantic Versions +// don't contain a leading v per the spec. Instead it's optional on +// implementation. +func (v Version) String() string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original value passed in to be parsed. +func (v *Version) Original() string { + return v.original +} + +// Major returns the major version. +func (v Version) Major() uint64 { + return v.major +} + +// Minor returns the minor version. +func (v Version) Minor() uint64 { + return v.minor +} + +// Patch returns the patch version. +func (v Version) Patch() uint64 { + return v.patch +} + +// Prerelease returns the pre-release version. +func (v Version) Prerelease() string { + return v.pre +} + +// Metadata returns the metadata on the version. +func (v Version) Metadata() string { + return v.metadata +} + +// originalVPrefix returns the original 'v' prefix if any. +func (v Version) originalVPrefix() string { + // Note, only lowercase v is supported as a prefix by the parser. + if v.original != "" && v.original[:1] == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps current patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrerelease defines the prerelease value. +// Value must not include the required 'hyphen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 { + if err := validatePrerelease(prerelease); err != nil { + return vNext, err + } + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 { + if err := validateMetadata(metadata); err != nil { + return vNext, err + } + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// LessThan tests if one version is less than another one. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// GreaterThan tests if one version is greater than another one. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// Equal tests if two versions are equal to each other. +// Note, versions can be equal with different metadata since metadata +// is not considered part of the comparable version. +func (v *Version) Equal(o *Version) bool { + return v.Compare(o) == 0 +} + +// Compare compares this version to another one. It returns -1, 0, or 1 if +// the version smaller, equal, or larger than the other version. +// +// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is +// lower than the version without a prerelease. Compare always takes into account +// prereleases. If you want to work with ranges using typical range syntaxes that +// skip prereleases if the range is not looking for them use constraints. +func (v *Version) Compare(o *Version) int { + // Compare the major, minor, and patch version for differences. If a + // difference is found return the comparison. + if d := compareSegment(v.Major(), o.Major()); d != 0 { + return d + } + if d := compareSegment(v.Minor(), o.Minor()); d != 0 { + return d + } + if d := compareSegment(v.Patch(), o.Patch()); d != 0 { + return d + } + + // At this point the major, minor, and patch versions are the same. + ps := v.pre + po := o.Prerelease() + + if ps == "" && po == "" { + return 0 + } + if ps == "" { + return 1 + } + if po == "" { + return -1 + } + + return comparePrerelease(ps, po) +} + +// UnmarshalJSON implements JSON.Unmarshaler interface. +func (v *Version) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + return nil +} + +// MarshalJSON implements JSON.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (v *Version) UnmarshalText(text []byte) error { + temp, err := NewVersion(string(text)) + if err != nil { + return err + } + + *v = *temp + + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (v Version) MarshalText() ([]byte, error) { + return []byte(v.String()), nil +} + +// Scan implements the SQL.Scanner interface. +func (v *Version) Scan(value interface{}) error { + var s string + s, _ = value.(string) + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + return nil +} + +// Value implements the Driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} + +func compareSegment(v, o uint64) int { + if v < o { + return -1 + } + if v > o { + return 1 + } + + return 0 +} + +func comparePrerelease(v, o string) int { + // split the prelease versions by their part. The separator, per the spec, + // is a . + sparts := strings.Split(v, ".") + oparts := strings.Split(o, ".") + + // Find the longer length of the parts to know how many loop iterations to + // go through. + slen := len(sparts) + olen := len(oparts) + + l := slen + if olen > slen { + l = olen + } + + // Iterate over each part of the prereleases to compare the differences. + for i := 0; i < l; i++ { + // Since the lentgh of the parts can be different we need to create + // a placeholder. This is to avoid out of bounds issues. + stemp := "" + if i < slen { + stemp = sparts[i] + } + + otemp := "" + if i < olen { + otemp = oparts[i] + } + + d := comparePrePart(stemp, otemp) + if d != 0 { + return d + } + } + + // Reaching here means two versions are of equal value but have different + // metadata (the part following a +). They are not identical in string form + // but the version comparison finds them to be equal. + return 0 +} + +func comparePrePart(s, o string) int { + // Fastpath if they are equal + if s == o { + return 0 + } + + // When s or o are empty we can use the other in an attempt to determine + // the response. + if s == "" { + if o != "" { + return -1 + } + return 1 + } + + if o == "" { + if s != "" { + return 1 + } + return -1 + } + + // When comparing strings "99" is greater than "103". To handle + // cases like this we need to detect numbers and compare them. According + // to the semver spec, numbers are always positive. If there is a - at the + // start like -99 this is to be evaluated as an alphanum. numbers always + // have precedence over alphanum. Parsing as Uints because negative numbers + // are ignored. + + oi, n1 := strconv.ParseUint(o, 10, 64) + si, n2 := strconv.ParseUint(s, 10, 64) + + // The case where both are strings compare the strings + if n1 != nil && n2 != nil { + if s > o { + return 1 + } + return -1 + } else if n1 != nil { + // o is a string and s is a number + return -1 + } else if n2 != nil { + // s is a string and o is a number + return 1 + } + // Both are numbers + if si > oi { + return 1 + } + return -1 +} + +// Like strings.ContainsAny but does an only instead of any. +func containsOnly(s string, comp string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(comp, r) + }) == -1 +} + +// From the spec, "Identifiers MUST comprise only +// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. +// Numeric identifiers MUST NOT include leading zeroes.". These segments can +// be dot separated. +func validatePrerelease(p string) error { + eparts := strings.Split(p, ".") + for _, p := range eparts { + if containsOnly(p, num) { + if len(p) > 1 && p[0] == '0' { + return ErrSegmentStartsZero + } + } else if !containsOnly(p, allowed) { + return ErrInvalidPrerelease + } + } + + return nil +} + +// From the spec, "Build metadata MAY be denoted by +// appending a plus sign and a series of dot separated identifiers immediately +// following the patch or pre-release version. Identifiers MUST comprise only +// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty." +func validateMetadata(m string) error { + eparts := strings.Split(m, ".") + for _, p := range eparts { + if !containsOnly(p, allowed) { + return ErrInvalidMetadata + } + } + return nil +} diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go index 0d82a2dd3c..5c7f3b028c 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go @@ -15,7 +15,7 @@ package libcni // Note this is the actual implementation of the CNI specification, which -// is reflected in the https://github.com/containernetworking/cni/blob/master/SPEC.md file +// is reflected in the SPEC.md file. // it is typically bundled into runtime providers (i.e. containerd or cri-o would use this // before calling runc or hcsshim). It is also bundled into CNI providers as well, for example, // to add an IP to a container, to parse the configuration of the CNI and so on. @@ -24,9 +24,9 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" + "sort" "strings" "github.com/containernetworking/cni/pkg/invoke" @@ -38,6 +38,8 @@ import ( var ( CacheDir = "/var/lib/cni" + // slightly awkward wording to preserve anyone matching on error strings + ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command") ) const ( @@ -77,6 +79,20 @@ type NetworkConfigList struct { Bytes []byte } +type NetworkAttachment struct { + ContainerID string + Network string + IfName string + Config []byte + NetNS string + CniArgs [][2]string + CapabilityArgs map[string]interface{} +} + +type GCArgs struct { + ValidAttachments []types.GCAttachment +} + type CNI interface { AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error @@ -92,6 +108,11 @@ type CNI interface { ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) + + GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error + GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error + + GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) } type CNIConfig struct { @@ -139,8 +160,11 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ if err != nil { return nil, err } + if rt != nil { + return injectRuntimeConfig(orig, rt) + } - return injectRuntimeConfig(orig, rt) + return orig, nil } // This function takes a libcni RuntimeConf structure and injects values into @@ -195,6 +219,7 @@ type cachedInfo struct { Config []byte `json:"config"` IfName string `json:"ifName"` NetworkName string `json:"networkName"` + NetNS string `json:"netns,omitempty"` CniArgs [][2]string `json:"cniArgs,omitempty"` CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"` RawResult map[string]interface{} `json:"result,omitempty"` @@ -229,6 +254,7 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, Config: config, IfName: rt.IfName, NetworkName: netName, + NetNS: rt.NetNS, CniArgs: rt.Args, CapabilityArgs: rt.CapabilityArgs, } @@ -254,11 +280,11 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, if err != nil { return err } - if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil { + if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil { return err } - return ioutil.WriteFile(fname, newBytes, 0600) + return os.WriteFile(fname, newBytes, 0o600) } func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error { @@ -277,7 +303,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R if err != nil { return nil, nil, err } - bytes, err = ioutil.ReadFile(fname) + bytes, err = os.ReadFile(fname) if err != nil { // Ignore read errors; the cached result may not exist on-disk return nil, nil, nil @@ -305,7 +331,7 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim if err != nil { return nil, err } - data, err := ioutil.ReadFile(fname) + data, err := os.ReadFile(fname) if err != nil { // Ignore read errors; the cached result may not exist on-disk return nil, nil @@ -333,7 +359,7 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) if err != nil { return nil, err } - fdata, err := ioutil.ReadFile(fname) + fdata, err := os.ReadFile(fname) if err != nil { // Ignore read errors; the cached result may not exist on-disk return nil, nil @@ -390,6 +416,65 @@ func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) return c.getCachedConfig(net.Network.Name, rt) } +// GetCachedAttachments returns a list of network attachments from the cache. +// The returned list will be filtered by the containerID if the value is not empty. +func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) { + dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results") + entries, err := os.ReadDir(dirPath) + if err != nil { + return nil, err + } + + fileNames := make([]string, 0, len(entries)) + for _, e := range entries { + fileNames = append(fileNames, e.Name()) + } + sort.Strings(fileNames) + + attachments := []*NetworkAttachment{} + for _, fname := range fileNames { + if len(containerID) > 0 { + part := fmt.Sprintf("-%s-", containerID) + pos := strings.Index(fname, part) + if pos <= 0 || pos+len(part) >= len(fname) { + continue + } + } + + cacheFile := filepath.Join(dirPath, fname) + bytes, err := os.ReadFile(cacheFile) + if err != nil { + continue + } + + cachedInfo := cachedInfo{} + + if err := json.Unmarshal(bytes, &cachedInfo); err != nil { + continue + } + if cachedInfo.Kind != CNICacheV1 { + continue + } + if len(containerID) > 0 && cachedInfo.ContainerID != containerID { + continue + } + if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" { + continue + } + + attachments = append(attachments, &NetworkAttachment{ + ContainerID: cachedInfo.ContainerID, + Network: cachedInfo.NetworkName, + IfName: cachedInfo.IfName, + Config: cachedInfo.Config, + NetNS: cachedInfo.NetNS, + CniArgs: cachedInfo.CniArgs, + CapabilityArgs: cachedInfo.CapabilityArgs, + }) + } + return attachments, nil +} + func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { c.ensureExec() pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) @@ -453,7 +538,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { return err } else if !gtet { - return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion) + return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp) } if list.DisableCheck { @@ -497,9 +582,9 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { return err } else if gtet { - cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt) - if err != nil { - return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err) + if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil { + _ = c.cacheDel(list.Name, rt) + cachedResult = nil } } @@ -509,7 +594,10 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err) } } - _ = c.cacheDel(list.Name, rt) + + if cachedResult != nil { + _ = c.cacheDel(list.Name, rt) + } return nil } @@ -547,7 +635,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { return err } else if !gtet { - return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion) + return fmt.Errorf("configuration version %q %w", net.Network.CNIVersion, ErrorCheckNotSupp) } cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) @@ -666,6 +754,116 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers return invoke.GetVersionInfo(ctx, pluginPath, c.exec) } +// GCNetworkList will do two things +// - dump the list of cached attachments, and issue deletes as necessary +// - issue a GC to the underlying plugins (if the version is high enough) +func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error { + // First, get the list of cached attachments + cachedAttachments, err := c.GetCachedAttachments("") + if err != nil { + return nil + } + + validAttachments := make(map[types.GCAttachment]interface{}, len(args.ValidAttachments)) + for _, a := range args.ValidAttachments { + validAttachments[a] = nil + } + + var errs []error + + for _, cachedAttachment := range cachedAttachments { + if cachedAttachment.Network != list.Name { + continue + } + // we found this attachment + gca := types.GCAttachment{ + ContainerID: cachedAttachment.ContainerID, + IfName: cachedAttachment.IfName, + } + if _, ok := validAttachments[gca]; ok { + continue + } + // otherwise, this attachment wasn't valid and we should issue a CNI DEL + rt := RuntimeConf{ + ContainerID: cachedAttachment.ContainerID, + NetNS: cachedAttachment.NetNS, + IfName: cachedAttachment.IfName, + Args: cachedAttachment.CniArgs, + CapabilityArgs: cachedAttachment.CapabilityArgs, + } + if err := c.DelNetworkList(ctx, list, &rt); err != nil { + errs = append(errs, fmt.Errorf("failed to delete stale attachment %s %s: %w", rt.ContainerID, rt.IfName, err)) + } + } + + // now, if the version supports it, issue a GC + if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt { + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + "cni.dev/valid-attachments": args.ValidAttachments, + } + for _, plugin := range list.Plugins { + // build config here + pluginConfig, err := InjectConf(plugin, inject) + if err != nil { + errs = append(errs, fmt.Errorf("failed to generate configuration to GC plugin %s: %w", plugin.Network.Type, err)) + } + if err := c.gcNetwork(ctx, pluginConfig); err != nil { + errs = append(errs, fmt.Errorf("failed to GC plugin %s: %w", plugin.Network.Type, err)) + } + } + } + + return joinErrors(errs...) +} + +func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + args := c.args("GC", &RuntimeConf{}) + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) +} + +func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error { + // If the version doesn't support status, abort. + if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt { + return nil + } + + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + } + + for _, plugin := range list.Plugins { + // build config here + pluginConfig, err := InjectConf(plugin, inject) + if err != nil { + return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err) + } + if err := c.getStatusNetwork(ctx, pluginConfig); err != nil { + return err // Don't collect errors here, so we return a clean error code. + } + } + return nil +} + +func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *NetworkConfig) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + args := c.args("STATUS", &RuntimeConf{}) + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) +} + // ===== func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { return &invoke.Args{ diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go index 3cd6a59d1c..6c5d99de98 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -16,13 +16,17 @@ package libcni import ( "encoding/json" + "errors" "fmt" - "io/ioutil" "os" "path/filepath" "sort" + "strings" + + "github.com/Masterminds/semver/v3" "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" ) type NotFoundError struct { @@ -54,7 +58,7 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { } func ConfFromFile(filename string) (*NetworkConfig, error) { - bytes, err := ioutil.ReadFile(filename) + bytes, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("error reading %s: %w", filename, err) } @@ -85,11 +89,63 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { } } + rawVersions, ok := rawList["cniVersions"] + if ok { + // Parse the current package CNI version + currentVersion, err := semver.NewVersion(version.Current()) + if err != nil { + panic("CNI version is invalid semver!") + } + + rvs, ok := rawVersions.([]interface{}) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs) + } + vs := make([]*semver.Version, 0, len(rvs)) + for i, rv := range rvs { + v, ok := rv.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv) + } + if v, err := semver.NewVersion(v); err != nil { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err) + } else if !v.GreaterThan(currentVersion) { + // Skip versions "greater" than this implementation of the spec + vs = append(vs, v) + } + } + + // if cniVersion was already set, append it to the list for sorting. + if cniVersion != "" { + if v, err := semver.NewVersion(cniVersion); err != nil { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err) + } else if !v.GreaterThan(currentVersion) { + // ignore any versions higher than the current implemented spec version + vs = append(vs, v) + } + } + sort.Sort(semver.Collection(vs)) + if len(vs) > 0 { + cniVersion = vs[len(vs)-1].String() + } + } + disableCheck := false if rawDisableCheck, ok := rawList["disableCheck"]; ok { disableCheck, ok = rawDisableCheck.(bool) if !ok { - return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck) + disableCheckStr, ok := rawDisableCheck.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck) + } + switch { + case strings.ToLower(disableCheckStr) == "false": + disableCheck = false + case strings.ToLower(disableCheckStr) == "true": + disableCheck = true + default: + return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck value %q", disableCheckStr) + } } } @@ -129,7 +185,7 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { } func ConfListFromFile(filename string) (*NetworkConfigList, error) { - bytes, err := ioutil.ReadFile(filename) + bytes, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("error reading %s: %w", filename, err) } @@ -138,7 +194,7 @@ func ConfListFromFile(filename string) (*NetworkConfigList, error) { func ConfFiles(dir string, extensions []string) ([]string, error) { // In part, adapted from rkt/networking/podenv.go#listFiles - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) switch { case err == nil: // break case os.IsNotExist(err): @@ -206,7 +262,8 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) { singleConf, err := LoadConf(dir, name) if err != nil { // A little extra logic so the error makes sense - if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok { + var ncfErr NoConfigsFoundError + if len(files) != 0 && errors.As(err, &ncfErr) { // Config lists found but no config files found return nil, NotFoundError{dir, name} } diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go new file mode 100644 index 0000000000..100fb8392d --- /dev/null +++ b/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go @@ -0,0 +1,58 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Copyright the CNI authors +// +// 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. +// +// Adapted from errors/join.go from go 1.20 +// This package can be removed once the toolchain is updated to 1.20 + +package libcni + +func joinErrors(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &multiError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type multiError struct { + errs []error +} + +func (e *multiError) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) +} diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go index 8defe4dd39..c8b548e7c6 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -51,25 +51,34 @@ func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exe // DelegateCheck calls the given delegate plugin with the CNI CHECK action and // JSON configuration func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK") +} + +func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) error { pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) if err != nil { return err } - // DelegateCheck will override the original CNI_COMMAND env from process with CHECK - return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec) + return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), realExec) } // DelegateDel calls the given delegate plugin with the CNI DEL action and // JSON configuration func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { - pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) - if err != nil { - return err - } + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL") +} - // DelegateDel will override the original CNI_COMMAND env from process with DEL - return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec) +// DelegateStatus calls the given delegate plugin with the CNI STATUS action and +// JSON configuration +func DelegateStatus(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "STATUS") +} + +// DelegateGC calls the given delegate plugin with the CNI GC action and +// JSON configuration +func DelegateGC(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "GC") } // return CNIArgs used by delegation diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go index 3ad07aa8f2..a5e015fc92 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -81,17 +81,17 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) { // object to ExecPluginWithResult() to verify the incoming stdin and environment // and provide a tailored response: // -//import ( +// import ( // "encoding/json" // "path" // "strings" -//) +// ) // -//type fakeExec struct { +// type fakeExec struct { // version.PluginDecoder -//} +// } // -//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { // net := &types.NetConf{} // err := json.Unmarshal(stdinData, net) // if err != nil { @@ -109,14 +109,14 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) { // } // } // return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil -//} +// } // -//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) { +// func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) { // if len(paths) > 0 { // return path.Join(paths[0], plugin), nil // } // return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths) -//} +// } func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) { if exec == nil { diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go index 9bcfb45536..ed0999bd0e 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package invoke diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_linux.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_linux.go new file mode 100644 index 0000000000..3d58e75d6c --- /dev/null +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_linux.go @@ -0,0 +1,50 @@ +// Copyright 2022 CNI authors +// +// 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 ns + +import ( + "runtime" + + "github.com/vishvananda/netns" + + "github.com/containernetworking/cni/pkg/types" +) + +// Returns an object representing the current OS thread's network namespace +func getCurrentNS() (netns.NsHandle, error) { + // Lock the thread in case other goroutine executes in it and changes its + // network namespace after getCurrentThreadNetNSPath(), otherwise it might + // return an unexpected network namespace. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + return netns.Get() +} + +func CheckNetNS(nsPath string) (bool, *types.Error) { + ns, err := netns.GetFromPath(nsPath) + // Let plugins check whether nsPath from args is valid. Also support CNI DEL for empty nsPath as already-deleted nsPath. + if err != nil { + return false, nil + } + defer ns.Close() + + pluginNS, err := getCurrentNS() + if err != nil { + return false, types.NewError(types.ErrInvalidNetNS, "get plugin's netns failed", "") + } + defer pluginNS.Close() + + return pluginNS.Equal(ns), nil +} diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_windows.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_windows.go new file mode 100644 index 0000000000..cffe136178 --- /dev/null +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_windows.go @@ -0,0 +1,21 @@ +// Copyright 2022 CNI authors +// +// 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 ns + +import "github.com/containernetworking/cni/pkg/types" + +func CheckNetNS(nsPath string) (bool, *types.Error) { + return false, nil +} diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/skel/skel.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/skel/skel.go index cb8781972d..f29cf34594 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/skel/skel.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/skel/skel.go @@ -19,13 +19,14 @@ package skel import ( "bytes" "encoding/json" + "errors" "fmt" "io" - "io/ioutil" "log" "os" "strings" + "github.com/containernetworking/cni/pkg/ns" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/utils" "github.com/containernetworking/cni/pkg/version" @@ -34,12 +35,13 @@ import ( // CmdArgs captures all the arguments passed in to the plugin // via both env vars and stdin type CmdArgs struct { - ContainerID string - Netns string - IfName string - Args string - Path string - StdinData []byte + ContainerID string + Netns string + IfName string + Args string + Path string + NetnsOverride string + StdinData []byte } type dispatcher struct { @@ -55,21 +57,25 @@ type dispatcher struct { type reqForCmdEntry map[string]bool func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { - var cmd, contID, netns, ifName, args, path string + var cmd, contID, netns, ifName, args, path, netnsOverride string vars := []struct { - name string - val *string - reqForCmd reqForCmdEntry + name string + val *string + reqForCmd reqForCmdEntry + validateFn func(string) *types.Error }{ { "CNI_COMMAND", &cmd, reqForCmdEntry{ - "ADD": true, - "CHECK": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, + "GC": true, + "STATUS": true, }, + nil, }, { "CNI_CONTAINERID", @@ -79,6 +85,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CHECK": true, "DEL": true, }, + utils.ValidateContainerID, }, { "CNI_NETNS", @@ -88,6 +95,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CHECK": true, "DEL": false, }, + nil, }, { "CNI_IFNAME", @@ -97,6 +105,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CHECK": true, "DEL": true, }, + utils.ValidateInterfaceName, }, { "CNI_ARGS", @@ -106,15 +115,29 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CHECK": false, "DEL": false, }, + nil, }, { "CNI_PATH", &path, reqForCmdEntry{ - "ADD": true, - "CHECK": true, - "DEL": true, + "ADD": true, + "CHECK": true, + "DEL": true, + "GC": true, + "STATUS": true, + }, + nil, + }, + { + "CNI_NETNS_OVERRIDE", + &netnsOverride, + reqForCmdEntry{ + "ADD": false, + "CHECK": false, + "DEL": false, }, + nil, }, } @@ -125,6 +148,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" { argsMissing = append(argsMissing, v.name) } + } else if v.reqForCmd[cmd] && v.validateFn != nil { + if err := v.validateFn(*v.val); err != nil { + return "", nil, err + } } } @@ -137,18 +164,25 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { t.Stdin = bytes.NewReader(nil) } - stdinData, err := ioutil.ReadAll(t.Stdin) + stdinData, err := io.ReadAll(t.Stdin) if err != nil { return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "") } + if cmd != "VERSION" { + if err := validateConfig(stdinData); err != nil { + return "", nil, err + } + } + cmdArgs := &CmdArgs{ - ContainerID: contID, - Netns: netns, - IfName: ifName, - Args: args, - Path: path, - StdinData: stdinData, + ContainerID: contID, + Netns: netns, + IfName: ifName, + Args: args, + Path: path, + StdinData: stdinData, + NetnsOverride: netnsOverride, } return cmd, cmdArgs, nil } @@ -163,8 +197,13 @@ func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo ver return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details()) } + if toCall == nil { + return nil + } + if err = toCall(cmdArgs); err != nil { - if e, ok := err.(*types.Error); ok { + var e *types.Error + if errors.As(err, &e) { // don't wrap Error in Error return e } @@ -190,7 +229,7 @@ func validateConfig(jsonBytes []byte) *types.Error { return nil } -func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { +func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error { cmd, cmdArgs, err := t.getCmdArgsFromEnv() if err != nil { // Print the about string to stderr when no command is set @@ -202,21 +241,20 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, return err } - if cmd != "VERSION" { - if err = validateConfig(cmdArgs.StdinData); err != nil { - return err - } - if err = utils.ValidateContainerID(cmdArgs.ContainerID); err != nil { + switch cmd { + case "ADD": + err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Add) + if err != nil { return err } - if err = utils.ValidateInterfaceName(cmdArgs.IfName); err != nil { - return err + if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" { + isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns) + if checkErr != nil { + return checkErr + } else if isPluginNetNS { + return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "") + } } - } - - switch cmd { - case "ADD": - err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd) case "CHECK": configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) if err != nil { @@ -232,7 +270,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, if err != nil { return types.NewError(types.ErrDecodingFailure, err.Error(), "") } else if gtet { - if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Check); err != nil { return err } return nil @@ -240,7 +278,62 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, } return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "") case "DEL": - err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel) + err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Del) + if err != nil { + return err + } + if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" { + isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns) + if checkErr != nil { + return checkErr + } else if isPluginNetNS { + return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "") + } + } + case "GC": + configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } + if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if !gtet { + return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow GC", "") + } + for _, pluginVersion := range versionInfo.SupportedVersions() { + gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if gtet { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.GC); err != nil { + return err + } + return nil + } + } + return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow GC", "") + case "STATUS": + configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } + if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if !gtet { + return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow STATUS", "") + } + for _, pluginVersion := range versionInfo.SupportedVersions() { + gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if gtet { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Status); err != nil { + return err + } + return nil + } + } + return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow STATUS", "") case "VERSION": if err := versionInfo.Encode(t.Stdout); err != nil { return types.NewError(types.ErrIOFailure, err.Error(), "") @@ -264,13 +357,63 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, // // To let this package automatically handle errors and call os.Exit(1) for you, // use PluginMain() instead. +// +// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncsWithError instead. func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { + return PluginMainFuncsWithError(CNIFuncs{Add: cmdAdd, Check: cmdCheck, Del: cmdDel}, versionInfo, about) +} + +// CNIFuncs contains a group of callback command funcs to be passed in as +// parameters to the core "main" for a plugin. +type CNIFuncs struct { + Add func(_ *CmdArgs) error + Del func(_ *CmdArgs) error + Check func(_ *CmdArgs) error + GC func(_ *CmdArgs) error + Status func(_ *CmdArgs) error +} + +// PluginMainFuncsWithError is the core "main" for a plugin. It accepts +// callback functions defined within CNIFuncs and returns an error. +// +// The caller must also specify what CNI spec versions the plugin supports. +// +// It is the responsibility of the caller to check for non-nil error return. +// +// For a plugin to comply with the CNI spec, it must print any error to stdout +// as JSON and then exit with nonzero status code. +// +// To let this package automatically handle errors and call os.Exit(1) for you, +// use PluginMainFuncs() instead. +func PluginMainFuncsWithError(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error { return (&dispatcher{ Getenv: os.Getenv, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, - }).pluginMain(cmdAdd, cmdCheck, cmdDel, versionInfo, about) + }).pluginMain(funcs, versionInfo, about) +} + +// PluginMainFuncs is the core "main" for a plugin which includes automatic error handling. +// This is a newer alternative func to PluginMain which abstracts CNI commands within a +// CNIFuncs interface. +// +// The caller must also specify what CNI spec versions the plugin supports. +// +// The caller can specify an "about" string, which is printed on stderr +// when no CNI_COMMAND is specified. The recommended output is "CNI plugin v" +// +// When an error occurs in any func in CNIFuncs, PluginMainFuncs will print the error +// as JSON to stdout and call os.Exit(1). +// +// To have more control over error handling, use PluginMainFuncsWithError() instead. +func PluginMainFuncs(funcs CNIFuncs, versionInfo version.PluginInfo, about string) { + if e := PluginMainFuncsWithError(funcs, versionInfo, about); e != nil { + if err := e.Print(); err != nil { + log.Print("Error writing error JSON to stdout: ", err) + } + os.Exit(1) + } } // PluginMain is the core "main" for a plugin which includes automatic error handling. @@ -284,6 +427,8 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio // as JSON to stdout and call os.Exit(1). // // To have more control over error handling, use PluginMainWithError() instead. +// +// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncs instead. func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) { if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil { if err := e.Print(); err != nil { diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/100/types.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/100/types.go index 0e1e8b857b..f58b91206d 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/100/types.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/100/types.go @@ -26,9 +26,10 @@ import ( convert "github.com/containernetworking/cni/pkg/types/internal" ) -const ImplementedSpecVersion string = "1.0.0" +// The types did not change between v1.0 and v1.1 +const ImplementedSpecVersion string = "1.1.0" -var supportedVersions = []string{ImplementedSpecVersion} +var supportedVersions = []string{"1.0.0", "1.1.0"} // Register converters for all versions less than the implemented spec version func init() { @@ -38,10 +39,14 @@ func init() { convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x) convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x) convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x) + convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100) // Down-converters convert.RegisterConverter("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x) convert.RegisterConverter("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("1.1.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x) + convert.RegisterConverter("1.1.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("1.1.0", []string{"1.0.0"}, convertFrom100) // Creator convert.RegisterCreator(supportedVersions, NewResult) @@ -90,12 +95,49 @@ type Result struct { DNS types.DNS `json:"dns,omitempty"` } +// Note: DNS should be omit if DNS is empty but default Marshal function +// will output empty structure hence need to write a Marshal function +func (r *Result) MarshalJSON() ([]byte, error) { + // use type alias to escape recursion for json.Marshal() to MarshalJSON() + type fixObjType = Result + + bytes, err := json.Marshal(fixObjType(*r)) //nolint:all + if err != nil { + return nil, err + } + + fixupObj := make(map[string]interface{}) + if err := json.Unmarshal(bytes, &fixupObj); err != nil { + return nil, err + } + + if r.DNS.IsEmpty() { + delete(fixupObj, "dns") + } + + return json.Marshal(fixupObj) +} + +// convertFrom100 does nothing except set the version; the types are the same +func convertFrom100(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*Result) + + result := &Result{ + CNIVersion: toVersion, + Interfaces: fromResult.Interfaces, + IPs: fromResult.IPs, + Routes: fromResult.Routes, + DNS: fromResult.DNS, + } + return result, nil +} + func convertFrom02x(from types.Result, toVersion string) (types.Result, error) { result040, err := convert.Convert(from, "0.4.0") if err != nil { return nil, err } - result100, err := convertFrom04x(result040, ImplementedSpecVersion) + result100, err := convertFrom04x(result040, toVersion) if err != nil { return nil, err } @@ -226,9 +268,12 @@ func (r *Result) PrintTo(writer io.Writer) error { // Interface contains values about the created interfaces type Interface struct { - Name string `json:"name"` - Mac string `json:"mac,omitempty"` - Sandbox string `json:"sandbox,omitempty"` + Name string `json:"name"` + Mac string `json:"mac,omitempty"` + Mtu int `json:"mtu,omitempty"` + Sandbox string `json:"sandbox,omitempty"` + SocketPath string `json:"socketPath,omitempty"` + PciID string `json:"pciID,omitempty"` } func (i *Interface) String() string { diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/args.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/args.go index 7516f03ef5..68a602bfdb 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/args.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/args.go @@ -26,8 +26,8 @@ import ( type UnmarshallableBool bool // UnmarshalText implements the encoding.TextUnmarshaler interface. -// Returns boolean true if the string is "1" or "[Tt]rue" -// Returns boolean false if the string is "0" or "[Ff]alse" +// Returns boolean true if the string is "1" or "true" or "True" +// Returns boolean false if the string is "0" or "false" or "Falseā€ func (b *UnmarshallableBool) UnmarshalText(data []byte) error { s := strings.ToLower(string(data)) switch s { diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/create/create.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/create/create.go index ed28b33e8e..452cb62201 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/create/create.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/create/create.go @@ -19,6 +19,9 @@ import ( "fmt" "github.com/containernetworking/cni/pkg/types" + _ "github.com/containernetworking/cni/pkg/types/020" + _ "github.com/containernetworking/cni/pkg/types/040" + _ "github.com/containernetworking/cni/pkg/types/100" convert "github.com/containernetworking/cni/pkg/types/internal" ) diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go index fba17dfc0f..193ac46ef8 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -64,16 +64,55 @@ type NetConf struct { Type string `json:"type,omitempty"` Capabilities map[string]bool `json:"capabilities,omitempty"` IPAM IPAM `json:"ipam,omitempty"` - DNS DNS `json:"dns"` + DNS DNS `json:"dns,omitempty"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` PrevResult Result `json:"-"` + + // ValidAttachments is only supplied when executing a GC operation + ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"` +} + +// GCAttachment is the parameters to a GC call -- namely, +// the container ID and ifname pair that represents a +// still-valid attachment. +type GCAttachment struct { + ContainerID string `json:"containerID"` + IfName string `json:"ifname"` +} + +// Note: DNS should be omit if DNS is empty but default Marshal function +// will output empty structure hence need to write a Marshal function +func (n *NetConf) MarshalJSON() ([]byte, error) { + // use type alias to escape recursion for json.Marshal() to MarshalJSON() + type fixObjType = NetConf + + bytes, err := json.Marshal(fixObjType(*n)) //nolint:all + if err != nil { + return nil, err + } + + fixupObj := make(map[string]interface{}) + if err := json.Unmarshal(bytes, &fixupObj); err != nil { + return nil, err + } + + if n.DNS.IsEmpty() { + delete(fixupObj, "dns") + } + + return json.Marshal(fixupObj) } type IPAM struct { Type string `json:"type,omitempty"` } +// IsEmpty returns true if IPAM structure has no value, otherwise return false +func (i *IPAM) IsEmpty() bool { + return i.Type == "" +} + // NetConfList describes an ordered list of networks. type NetConfList struct { CNIVersion string `json:"cniVersion,omitempty"` @@ -116,31 +155,48 @@ type DNS struct { Options []string `json:"options,omitempty"` } +// IsEmpty returns true if DNS structure has no value, otherwise return false +func (d *DNS) IsEmpty() bool { + if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 { + return true + } + return false +} + func (d *DNS) Copy() *DNS { if d == nil { return nil } to := &DNS{Domain: d.Domain} - for _, ns := range d.Nameservers { - to.Nameservers = append(to.Nameservers, ns) - } - for _, s := range d.Search { - to.Search = append(to.Search, s) - } - for _, o := range d.Options { - to.Options = append(to.Options, o) - } + to.Nameservers = append(to.Nameservers, d.Nameservers...) + to.Search = append(to.Search, d.Search...) + to.Options = append(to.Options, d.Options...) return to } type Route struct { - Dst net.IPNet - GW net.IP + Dst net.IPNet + GW net.IP + MTU int + AdvMSS int + Priority int + Table *int + Scope *int } func (r *Route) String() string { - return fmt.Sprintf("%+v", *r) + table := "" + if r.Table != nil { + table = fmt.Sprintf("%d", *r.Table) + } + + scope := "" + if r.Scope != nil { + scope = fmt.Sprintf("%d", *r.Scope) + } + + return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope) } func (r *Route) Copy() *Route { @@ -148,14 +204,30 @@ func (r *Route) Copy() *Route { return nil } - return &Route{ - Dst: r.Dst, - GW: r.GW, + route := &Route{ + Dst: r.Dst, + GW: r.GW, + MTU: r.MTU, + AdvMSS: r.AdvMSS, + Priority: r.Priority, + Scope: r.Scope, + } + + if r.Table != nil { + table := *r.Table + route.Table = &table } + + if r.Scope != nil { + scope := *r.Scope + route.Scope = &scope + } + + return route } // Well known error codes -// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes +// see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes const ( ErrUnknown uint = iota // 0 ErrIncompatibleCNIVersion // 1 @@ -165,6 +237,7 @@ const ( ErrIOFailure // 5 ErrDecodingFailure // 6 ErrInvalidNetworkConfig // 7 + ErrInvalidNetNS // 8 ErrTryAgainLater uint = 11 ErrInternal uint = 999 ) @@ -200,8 +273,13 @@ func (e *Error) Print() error { // JSON (un)marshallable types type route struct { - Dst IPNet `json:"dst"` - GW net.IP `json:"gw,omitempty"` + Dst IPNet `json:"dst"` + GW net.IP `json:"gw,omitempty"` + MTU int `json:"mtu,omitempty"` + AdvMSS int `json:"advmss,omitempty"` + Priority int `json:"priority,omitempty"` + Table *int `json:"table,omitempty"` + Scope *int `json:"scope,omitempty"` } func (r *Route) UnmarshalJSON(data []byte) error { @@ -212,13 +290,24 @@ func (r *Route) UnmarshalJSON(data []byte) error { r.Dst = net.IPNet(rt.Dst) r.GW = rt.GW + r.MTU = rt.MTU + r.AdvMSS = rt.AdvMSS + r.Priority = rt.Priority + r.Table = rt.Table + r.Scope = rt.Scope + return nil } func (r Route) MarshalJSON() ([]byte, error) { rt := route{ - Dst: IPNet(r.Dst), - GW: r.GW, + Dst: IPNet(r.Dst), + GW: r.GW, + MTU: r.MTU, + AdvMSS: r.AdvMSS, + Priority: r.Priority, + Table: r.Table, + Scope: r.Scope, } return json.Marshal(rt) diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/utils/utils.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/utils/utils.go index b8ec388745..1981d25569 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/utils/utils.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/utils/utils.go @@ -36,7 +36,6 @@ var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`) // ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters func ValidateContainerID(containerID string) *types.Error { - if containerID == "" { return types.NewError(types.ErrUnknownContainer, "missing containerID", "") } @@ -48,7 +47,6 @@ func ValidateContainerID(containerID string) *types.Error { // ValidateNetworkName will validate that the supplied networkName does not contain invalid characters func ValidateNetworkName(networkName string) *types.Error { - if networkName == "" { return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "") } @@ -58,11 +56,11 @@ func ValidateNetworkName(networkName string) *types.Error { return nil } -// ValidateInterfaceName will validate the interface name based on the three rules below +// ValidateInterfaceName will validate the interface name based on the four rules below // 1. The name must not be empty // 2. The name must be less than 16 characters // 3. The name must not be "." or ".." -// 3. The name must not contain / or : or any whitespace characters +// 4. The name must not contain / or : or any whitespace characters // ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024 func ValidateInterfaceName(ifName string) *types.Error { if len(ifName) == 0 { diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/version/version.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/version/version.go index 1326f8038e..a4d442c8ec 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/version/version.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -19,13 +19,12 @@ import ( "fmt" "github.com/containernetworking/cni/pkg/types" - types100 "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/cni/pkg/types/create" ) // Current reports the version of the CNI spec implemented by this library func Current() string { - return types100.ImplementedSpecVersion + return "1.1.0" } // Legacy PluginInfo describes a plugin that is backwards compatible with the @@ -35,8 +34,10 @@ func Current() string { // // Any future CNI spec versions which meet this definition should be added to // this list. -var Legacy = PluginSupports("0.1.0", "0.2.0") -var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0") +var ( + Legacy = PluginSupports("0.1.0", "0.2.0") + All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0") +) // VersionsFrom returns a list of versions starting from min, inclusive func VersionsStartingFrom(min string) PluginInfo { diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go index 7e202ed8d0..042a5867e0 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go @@ -2,9 +2,9 @@ package v1 import ( "encoding/json" - "errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // +genclient @@ -107,6 +107,7 @@ type NetworkStatus struct { Interface string `json:"interface,omitempty"` IPs []string `json:"ips,omitempty"` Mac string `json:"mac,omitempty"` + Mtu int `json:"mtu,omitempty"` Default bool `json:"default,omitempty"` DNS DNS `json:"dns,omitempty"` DeviceInfo *DeviceInfo `json:"device-info,omitempty"` @@ -176,9 +177,6 @@ func (nse *NetworkSelectionElement) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &netSelectionElement); err != nil { return err } - if len(netSelectionElement.IPRequest) > 0 && netSelectionElement.IPAMClaimReference != "" { - return TooManyIPSources - } *nse = NetworkSelectionElement(netSelectionElement) return nil } @@ -197,5 +195,3 @@ type NoK8sNetworkError struct { } func (e *NoK8sNetworkError) Error() string { return string(e.Message) } - -var TooManyIPSources = errors.New("cannot provide a static IP and a reference of an IPAM claim in the same network selection element") diff --git a/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils/net-attach-def.go b/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils/net-attach-def.go index 4bca1645fb..acebe13a4a 100644 --- a/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils/net-attach-def.go +++ b/go-controller/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils/net-attach-def.go @@ -122,6 +122,117 @@ func GetNetworkStatus(pod *corev1.Pod) ([]v1.NetworkStatus, error) { return netStatuses, err } +// gatewayInterfaceIndex determines the index of the first interface that has a gateway +func gatewayInterfaceIndex(ips []*cni100.IPConfig) int { + for _, ipConfig := range ips { + if ipConfig.Gateway != nil && ipConfig.Interface != nil { + return *ipConfig.Interface + } + } + return -1 +} + +// CreateNetworkStatuses creates an array of NetworkStatus from CNI result +// Not to be confused with CreateNetworkStatus (singular) +// This is the preferred method and picks up when CNI ADD results contain multiple container interfaces +func CreateNetworkStatuses(r cnitypes.Result, networkName string, defaultNetwork bool, dev *v1.DeviceInfo) ([]*v1.NetworkStatus, error) { + var networkStatuses []*v1.NetworkStatus + // indexMap is from original CNI result index to networkStatuses index + indexMap := make(map[int]int) + + // Convert whatever the IPAM result was into the current Result type + result, err := cni100.NewResultFromResult(r) + if err != nil { + return nil, fmt.Errorf("error converting the type.Result to cni100.Result: %v", err) + } + + if len(result.Interfaces) == 1 { + networkStatus, err := CreateNetworkStatus(r, networkName, defaultNetwork, dev) + return []*v1.NetworkStatus{networkStatus}, err + } + + // Discover default routes upfront and reuse them if necessary. + var useDefaultRoute []string + for _, route := range result.Routes { + if isDefaultRoute(route) { + useDefaultRoute = append(useDefaultRoute, route.GW.String()) + } + } + + // Same for DNS + v1dns := convertDNS(result.DNS) + + // Check for a gateway-associated interface, we'll use this later if we did to mark as the default. + gwInterfaceIdx := -1 + if defaultNetwork { + gwInterfaceIdx = gatewayInterfaceIndex(result.IPs) + } + + // Initialize NetworkStatus for each container interface (e.g. with sandbox present) + indexOfFoundPodInterface := 0 + foundFirstSandboxIface := false + didSetDefault := false + for i, iface := range result.Interfaces { + if iface.Sandbox != "" { + isDefault := false + + // If there's a gateway listed for this interface index found in the ips, we mark that interface as default + // notably, we use the first one we find. + if defaultNetwork && i == gwInterfaceIdx && !didSetDefault { + isDefault = true + didSetDefault = true + } + + // Otherwise, if we didn't find it, we use the first sandbox interface. + if defaultNetwork && gwInterfaceIdx == -1 && !foundFirstSandboxIface { + isDefault = true + foundFirstSandboxIface = true + } + + ns := &v1.NetworkStatus{ + Name: networkName, + Default: isDefault, + Interface: iface.Name, + Mac: iface.Mac, + Mtu: iface.Mtu, + IPs: []string{}, + Gateway: useDefaultRoute, + DeviceInfo: dev, + DNS: *v1dns, + } + networkStatuses = append(networkStatuses, ns) + // Map original index to the new slice index + indexMap[i] = indexOfFoundPodInterface + indexOfFoundPodInterface++ + } + } + + var defaultNetworkStatus *v1.NetworkStatus + if len(networkStatuses) > 0 { + // Set the default network status to the last network status. + defaultNetworkStatus = networkStatuses[len(networkStatuses)-1] + } + + // Map IPs to network interface based on index + for _, ipConfig := range result.IPs { + if ipConfig.Interface != nil { + originalIndex := *ipConfig.Interface + if newIndex, ok := indexMap[originalIndex]; ok { + ns := networkStatuses[newIndex] + ns.IPs = append(ns.IPs, ipConfig.Address.IP.String()) + } + } else { + // If the IPs don't specify the interface assign the IP to the default network status. This keeps the behaviour + // consistent with previous multus versions. + if defaultNetworkStatus != nil { + defaultNetworkStatus.IPs = append(defaultNetworkStatus.IPs, ipConfig.Address.IP.String()) + } + } + } + + return networkStatuses, nil +} + // CreateNetworkStatus create NetworkStatus from CNI result func CreateNetworkStatus(r cnitypes.Result, networkName string, defaultNetwork bool, dev *v1.DeviceInfo) (*v1.NetworkStatus, error) { netStatus := &v1.NetworkStatus{} @@ -139,6 +250,7 @@ func CreateNetworkStatus(r cnitypes.Result, networkName string, defaultNetwork b if ifs.Sandbox != "" { netStatus.Interface = ifs.Name netStatus.Mac = ifs.Mac + netStatus.Mtu = ifs.Mtu } } diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index 7636490960..e3578ea166 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -1,3 +1,6 @@ +# github.com/Masterminds/semver/v3 v3.2.1 +## explicit; go 1.18 +github.com/Masterminds/semver/v3 # github.com/Microsoft/go-winio v0.6.2 ## explicit; go 1.21 github.com/Microsoft/go-winio @@ -56,10 +59,11 @@ github.com/cespare/xxhash/v2 # github.com/containerd/cgroups v1.1.0 ## explicit; go 1.17 github.com/containerd/cgroups/stats/v1 -# github.com/containernetworking/cni v1.1.2 -## explicit; go 1.14 +# github.com/containernetworking/cni v1.2.0-rc1 +## explicit; go 1.18 github.com/containernetworking/cni/libcni github.com/containernetworking/cni/pkg/invoke +github.com/containernetworking/cni/pkg/ns github.com/containernetworking/cni/pkg/skel github.com/containernetworking/cni/pkg/types github.com/containernetworking/cni/pkg/types/020 @@ -229,7 +233,7 @@ github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/client/informers/externa github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/client/informers/externalversions/k8s.cni.cncf.io/v1beta2 github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/client/listers/k8s.cni.cncf.io/v1beta1 github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/client/listers/k8s.cni.cncf.io/v1beta2 -# github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 +# github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 ## explicit; go 1.21 github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1 diff --git a/test/e2e/go.mod b/test/e2e/go.mod index d9d67fb0c4..a6a913d424 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 - github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/pkg/errors v0.9.1 @@ -25,6 +25,7 @@ require ( require ( cel.dev/expr v0.18.0 // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect @@ -39,7 +40,7 @@ require ( github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/ttrpc v1.2.5 // indirect - github.com/containernetworking/cni v1.1.2 // indirect + github.com/containernetworking/cni v1.2.0-rc1 // indirect github.com/coreos/go-iptables v0.6.0 // indirect github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb // indirect github.com/coreos/go-semver v0.3.1 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 900d7aa612..9df045a32f 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -51,6 +51,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -95,8 +97,8 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= -github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= -github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/cni v1.2.0-rc1 h1:AKI3+pXtgY4PDLN9+50o9IaywWVuey0Jkw3Lvzp0HCY= +github.com/containernetworking/cni v1.2.0-rc1/go.mod h1:Lt0TQcZQVDju64fYxUhDziTgXCDe3Olzi9I4zZJLWHg= github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU= github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4= github.com/coreos/butane v0.18.0 h1:WDeUC/dX1MUUVPwiqsQetQZsShNKk+2lrRXlC4ZhnZA= @@ -335,8 +337,8 @@ github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha h1:b3iHeks/KTzhG2dNanaUZ github.com/k8snetworkplumbingwg/ipamclaims v0.5.0-alpha/go.mod h1:MGaMX1tJ7MlHDee4/xmqp3guQh+eDiuCLAauqD9K11Q= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1 h1:Egj1hEVYNXWFlKpgzAXxe/2o8VNiVcAJLrKzlinILQo= github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1/go.mod h1:kEJ4WM849yNmXekuSXLRwb+LaZ9usC06O8JgoAIq+f4= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0 h1:BT3ghAY0q7lWib9rz+tVXDFkm27dJV6SLCn7TunZwo4= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.6.0/go.mod h1:wxt2YWRVItDtaQmVSmaN5ubE2L1c9CiNoHQwSJnM8Ko= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc h1:v6+jUd70AayPbIRgTYUNpnBLG5cBPTY0+10y80CZeMk= github.com/k8snetworkplumbingwg/sriovnet v1.2.1-0.20230427090635-4929697df2dc/go.mod h1:jyWzGe6ZtYiPq6ih6aXCOy6mZ49Y9mNyBOLBBXnli+k= github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= @@ -413,7 +415,6 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= From fa7558fc9c822bd3dce4c4d7c2290bfc6022c0a8 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 1 Jul 2025 09:43:58 +0200 Subject: [PATCH 191/278] go, deps: Pin CNI library to v1.2.3 to prevent OVN-K parsing issues Pin the 'containernetworking/cni' library dependency to v1.2.3. Version 1.3.0 introduced a breaking change where a new MarshalJSON function in the CNI library causes it to ignore custom OVN-K fields within 'nad.spec.config', leading to parsing failures. Signed-off-by: Enrique Llorente --- go-controller/go.mod | 3 +- go-controller/go.sum | 6 +- .../Masterminds/semver/v3/.gitignore | 1 - .../Masterminds/semver/v3/.golangci.yml | 27 - .../Masterminds/semver/v3/CHANGELOG.md | 214 ------ .../Masterminds/semver/v3/LICENSE.txt | 19 - .../github.com/Masterminds/semver/v3/Makefile | 30 - .../Masterminds/semver/v3/README.md | 258 ------- .../Masterminds/semver/v3/SECURITY.md | 19 - .../Masterminds/semver/v3/collection.go | 24 - .../Masterminds/semver/v3/constraints.go | 594 ---------------- .../github.com/Masterminds/semver/v3/doc.go | 184 ----- .../Masterminds/semver/v3/version.go | 639 ------------------ .../containernetworking/cni/libcni/api.go | 38 +- .../containernetworking/cni/libcni/conf.go | 79 ++- .../cni/libcni/multierror.go | 58 -- .../cni/pkg/ns/ns_darwin.go | 21 + .../cni/pkg/types/types.go | 12 +- .../cni/pkg/version/plugin.go | 24 + go-controller/vendor/modules.txt | 7 +- test/e2e/go.mod | 3 +- test/e2e/go.sum | 6 +- 22 files changed, 138 insertions(+), 2128 deletions(-) delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/Makefile delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/README.md delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/collection.go delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/doc.go delete mode 100644 go-controller/vendor/github.com/Masterminds/semver/v3/version.go delete mode 100644 go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go create mode 100644 go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_darwin.go diff --git a/go-controller/go.mod b/go-controller/go.mod index afd8cdfe11..0a4e54196b 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -10,7 +10,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e github.com/cenkalti/backoff/v4 v4.3.0 - github.com/containernetworking/cni v1.2.0-rc1 + github.com/containernetworking/cni v1.2.3 github.com/containernetworking/plugins v1.2.0 github.com/coreos/go-iptables v0.6.0 github.com/fsnotify/fsnotify v1.7.0 @@ -73,7 +73,6 @@ require ( ) require ( - github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/hub v1.0.1 // indirect diff --git a/go-controller/go.sum b/go-controller/go.sum index 6d72aee42e..d0bde9c817 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -55,8 +55,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JacobTanenbaum/arping v0.0.0-20240209152419-3987db83bd51/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -201,8 +199,8 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.2.0-rc1 h1:AKI3+pXtgY4PDLN9+50o9IaywWVuey0Jkw3Lvzp0HCY= -github.com/containernetworking/cni v1.2.0-rc1/go.mod h1:Lt0TQcZQVDju64fYxUhDziTgXCDe3Olzi9I4zZJLWHg= +github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= +github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU= diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore b/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore deleted file mode 100644 index 6b061e6174..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_fuzz/ \ No newline at end of file diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml b/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml deleted file mode 100644 index fbc6332592..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/.golangci.yml +++ /dev/null @@ -1,27 +0,0 @@ -run: - deadline: 2m - -linters: - disable-all: true - enable: - - misspell - - govet - - staticcheck - - errcheck - - unparam - - ineffassign - - nakedret - - gocyclo - - dupl - - goimports - - revive - - gosec - - gosimple - - typecheck - - unused - -linters-settings: - gofmt: - simplify: true - dupl: - threshold: 600 diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md b/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md deleted file mode 100644 index f12626423a..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md +++ /dev/null @@ -1,214 +0,0 @@ -# Changelog - -## 3.2.0 (2022-11-28) - -### Added - -- #190: Added text marshaling and unmarshaling -- #167: Added JSON marshalling for constraints (thanks @SimonTheLeg) -- #173: Implement encoding.TextMarshaler and encoding.TextUnmarshaler on Version (thanks @MarkRosemaker) -- #179: Added New() version constructor (thanks @kazhuravlev) - -### Changed - -- #182/#183: Updated CI testing setup - -### Fixed - -- #186: Fixing issue where validation of constraint section gave false positives -- #176: Fix constraints check with *-0 (thanks @mtt0) -- #181: Fixed Caret operator (^) gives unexpected results when the minor version in constraint is 0 (thanks @arshchimni) -- #161: Fixed godoc (thanks @afirth) - -## 3.1.1 (2020-11-23) - -### Fixed - -- #158: Fixed issue with generated regex operation order that could cause problem - -## 3.1.0 (2020-04-15) - -### Added - -- #131: Add support for serializing/deserializing SQL (thanks @ryancurrah) - -### Changed - -- #148: More accurate validation messages on constraints - -## 3.0.3 (2019-12-13) - -### Fixed - -- #141: Fixed issue with <= comparison - -## 3.0.2 (2019-11-14) - -### Fixed - -- #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos) - -## 3.0.1 (2019-09-13) - -### Fixed - -- #125: Fixes issue with module path for v3 - -## 3.0.0 (2019-09-12) - -This is a major release of the semver package which includes API changes. The Go -API is compatible with ^1. The Go API was not changed because many people are using -`go get` without Go modules for their applications and API breaking changes cause -errors which we have or would need to support. - -The changes in this release are the handling based on the data passed into the -functions. These are described in the added and changed sections below. - -### Added - -- StrictNewVersion function. This is similar to NewVersion but will return an - error if the version passed in is not a strict semantic version. For example, - 1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly - speaking semantic versions. This function is faster, performs fewer operations, - and uses fewer allocations than NewVersion. -- Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint. - The Makefile contains the operations used. For more information on you can start - on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing -- Now using Go modules - -### Changed - -- NewVersion has proper prerelease and metadata validation with error messages - to signal an issue with either of them -- ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the - version is >=1 the ^ ranges works the same as v1. For major versions of 0 the - rules have changed. The minor version is treated as the stable version unless - a patch is specified and then it is equivalent to =. One difference from npm/js - is that prereleases there are only to a specific version (e.g. 1.2.3). - Prereleases here look over multiple versions and follow semantic version - ordering rules. This pattern now follows along with the expected and requested - handling of this packaged by numerous users. - -## 1.5.0 (2019-09-11) - -### Added - -- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) - -### Changed - -- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) -- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) -- #72: Adding docs comment pointing to vert for a cli -- #71: Update the docs on pre-release comparator handling -- #89: Test with new go versions (thanks @thedevsaddam) -- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) - -### Fixed - -- #78: Fix unchecked error in example code (thanks @ravron) -- #70: Fix the handling of pre-releases and the 0.0.0 release edge case -- #97: Fixed copyright file for proper display on GitHub -- #107: Fix handling prerelease when sorting alphanum and num -- #109: Fixed where Validate sometimes returns wrong message on error - -## 1.4.2 (2018-04-10) - -### Changed - -- #72: Updated the docs to point to vert for a console appliaction -- #71: Update the docs on pre-release comparator handling - -### Fixed - -- #70: Fix the handling of pre-releases and the 0.0.0 release edge case - -## 1.4.1 (2018-04-02) - -### Fixed - -- Fixed #64: Fix pre-release precedence issue (thanks @uudashr) - -## 1.4.0 (2017-10-04) - -### Changed - -- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) - -## 1.3.1 (2017-07-10) - -### Fixed - -- Fixed #57: number comparisons in prerelease sometimes inaccurate - -## 1.3.0 (2017-05-02) - -### Added - -- #45: Added json (un)marshaling support (thanks @mh-cbon) -- Stability marker. See https://masterminds.github.io/stability/ - -### Fixed - -- #51: Fix handling of single digit tilde constraint (thanks @dgodd) - -### Changed - -- #55: The godoc icon moved from png to svg - -## 1.2.3 (2017-04-03) - -### Fixed - -- #46: Fixed 0.x.x and 0.0.x in constraints being treated as * - -## Release 1.2.2 (2016-12-13) - -### Fixed - -- #34: Fixed issue where hyphen range was not working with pre-release parsing. - -## Release 1.2.1 (2016-11-28) - -### Fixed - -- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" - properly. - -## Release 1.2.0 (2016-11-04) - -### Added - -- #20: Added MustParse function for versions (thanks @adamreese) -- #15: Added increment methods on versions (thanks @mh-cbon) - -### Fixed - -- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and - might not satisfy the intended compatibility. The change here ignores pre-releases - on constraint checks (e.g., ~ or ^) when a pre-release is not part of the - constraint. For example, `^1.2.3` will ignore pre-releases while - `^1.2.3-alpha` will include them. - -## Release 1.1.1 (2016-06-30) - -### Changed - -- Issue #9: Speed up version comparison performance (thanks @sdboyer) -- Issue #8: Added benchmarks (thanks @sdboyer) -- Updated Go Report Card URL to new location -- Updated Readme to add code snippet formatting (thanks @mh-cbon) -- Updating tagging to v[SemVer] structure for compatibility with other tools. - -## Release 1.1.0 (2016-03-11) - -- Issue #2: Implemented validation to provide reasons a versions failed a - constraint. - -## Release 1.0.1 (2015-12-31) - -- Fixed #1: * constraint failing on valid versions. - -## Release 1.0.0 (2015-10-20) - -- Initial release diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt b/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt deleted file mode 100644 index 9ff7da9c48..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2014-2019, Matt Butcher and Matt Farina - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile b/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile deleted file mode 100644 index 0e7b5c7138..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -GOPATH=$(shell go env GOPATH) -GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint - -.PHONY: lint -lint: $(GOLANGCI_LINT) - @echo "==> Linting codebase" - @$(GOLANGCI_LINT) run - -.PHONY: test -test: - @echo "==> Running tests" - GO111MODULE=on go test -v - -.PHONY: test-cover -test-cover: - @echo "==> Running Tests with coverage" - GO111MODULE=on go test -cover . - -.PHONY: fuzz -fuzz: - @echo "==> Running Fuzz Tests" - go test -fuzz=FuzzNewVersion -fuzztime=15s . - go test -fuzz=FuzzStrictNewVersion -fuzztime=15s . - go test -fuzz=FuzzNewConstraint -fuzztime=15s . - -$(GOLANGCI_LINT): - # Install golangci-lint. The configuration for it is in the .golangci.yml - # file in the root of the repository - echo ${GOPATH} - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1 diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/README.md b/go-controller/vendor/github.com/Masterminds/semver/v3/README.md deleted file mode 100644 index eab8cac3b7..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/README.md +++ /dev/null @@ -1,258 +0,0 @@ -# SemVer - -The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: - -* Parse semantic versions -* Sort semantic versions -* Check if a semantic version fits within a set of constraints -* Optionally work with a `v` prefix - -[![Stability: -Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) -[![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/semver/actions) -[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) - -If you are looking for a command line tool for version comparisons please see -[vert](https://github.com/Masterminds/vert) which uses this library. - -## Package Versions - -Note, import `github.com/github.com/Masterminds/semver/v3` to use the latest version. - -There are three major versions fo the `semver` package. - -* 3.x.x is the stable and active version. This version is focused on constraint - compatibility for range handling in other tools from other languages. It has - a similar API to the v1 releases. The development of this version is on the master - branch. The documentation for this version is below. -* 2.x was developed primarily for [dep](https://github.com/golang/dep). There are - no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer). - There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x). -* 1.x.x is the original release. It is no longer maintained. You should use the - v3 release instead. You can read the documentation for the 1.x.x release - [here](https://github.com/Masterminds/semver/blob/release-1/README.md). - -## Parsing Semantic Versions - -There are two functions that can parse semantic versions. The `StrictNewVersion` -function only parses valid version 2 semantic versions as outlined in the -specification. The `NewVersion` function attempts to coerce a version into a -semantic version and parse it. For example, if there is a leading v or a version -listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid -semantic version (e.g., 1.2.0). In both cases a `Version` object is returned -that can be sorted, compared, and used in constraints. - -When parsing a version an error is returned if there is an issue parsing the -version. For example, - - v, err := semver.NewVersion("1.2.3-beta.1+build345") - -The version object has methods to get the parts of the version, compare it to -other versions, convert the version back into a string, and get the original -string. Getting the original string is useful if the semantic version was coerced -into a valid form. - -## Sorting Semantic Versions - -A set of versions can be sorted using the `sort` package from the standard library. -For example, - -```go -raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} -vs := make([]*semver.Version, len(raw)) -for i, r := range raw { - v, err := semver.NewVersion(r) - if err != nil { - t.Errorf("Error parsing version: %s", err) - } - - vs[i] = v -} - -sort.Sort(semver.Collection(vs)) -``` - -## Checking Version Constraints - -There are two methods for comparing versions. One uses comparison methods on -`Version` instances and the other uses `Constraints`. There are some important -differences to notes between these two methods of comparison. - -1. When two versions are compared using functions such as `Compare`, `LessThan`, - and others it will follow the specification and always include prereleases - within the comparison. It will provide an answer that is valid with the - comparison section of the spec at https://semver.org/#spec-item-11 -2. When constraint checking is used for checks or validation it will follow a - different set of rules that are common for ranges with tools like npm/js - and Rust/Cargo. This includes considering prereleases to be invalid if the - ranges does not include one. If you want to have it include pre-releases a - simple solution is to include `-0` in your range. -3. Constraint ranges can have some complex rules including the shorthand use of - ~ and ^. For more details on those see the options below. - -There are differences between the two methods or checking versions because the -comparison methods on `Version` follow the specification while comparison ranges -are not part of the specification. Different packages and tools have taken it -upon themselves to come up with range rules. This has resulted in differences. -For example, npm/js and Cargo/Rust follow similar patterns while PHP has a -different pattern for ^. The comparison features in this package follow the -npm/js and Cargo/Rust lead because applications using it have followed similar -patters with their versions. - -Checking a version against version constraints is one of the most featureful -parts of the package. - -```go -c, err := semver.NewConstraint(">= 1.2.3") -if err != nil { - // Handle constraint not being parsable. -} - -v, err := semver.NewVersion("1.3") -if err != nil { - // Handle version not being parsable. -} -// Check if the version meets the constraints. The a variable will be true. -a := c.Check(v) -``` - -### Basic Comparisons - -There are two elements to the comparisons. First, a comparison string is a list -of space or comma separated AND comparisons. These are then separated by || (OR) -comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a -comparison that's greater than or equal to 1.2 and less than 3.0.0 or is -greater than or equal to 4.2.3. - -The basic comparisons are: - -* `=`: equal (aliased to no operator) -* `!=`: not equal -* `>`: greater than -* `<`: less than -* `>=`: greater than or equal to -* `<=`: less than or equal to - -### Working With Prerelease Versions - -Pre-releases, for those not familiar with them, are used for software releases -prior to stable or generally available releases. Examples of prereleases include -development, alpha, beta, and release candidate releases. A prerelease may be -a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the -order of precedence, prereleases come before their associated releases. In this -example `1.2.3-beta.1 < 1.2.3`. - -According to the Semantic Version specification prereleases may not be -API compliant with their release counterpart. It says, - -> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. - -SemVer comparisons using constraints without a prerelease comparator will skip -prerelease versions. For example, `>=1.2.3` will skip prereleases when looking -at a list of releases while `>=1.2.3-0` will evaluate and find prereleases. - -The reason for the `0` as a pre-release version in the example comparison is -because pre-releases can only contain ASCII alphanumerics and hyphens (along with -`.` separators), per the spec. Sorting happens in ASCII sort order, again per the -spec. The lowest character is a `0` in ASCII sort order -(see an [ASCII Table](http://www.asciitable.com/)) - -Understanding ASCII sort ordering is important because A-Z comes before a-z. That -means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case -sensitivity doesn't apply here. This is due to ASCII sort ordering which is what -the spec specifies. - -### Hyphen Range Comparisons - -There are multiple methods to handle ranges and the first is hyphens ranges. -These look like: - -* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5` -* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` - -### Wildcards In Comparisons - -The `x`, `X`, and `*` characters can be used as a wildcard character. This works -for all comparison operators. When used on the `=` operator it falls -back to the patch level comparison (see tilde below). For example, - -* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` -* `>= 1.2.x` is equivalent to `>= 1.2.0` -* `<= 2.x` is equivalent to `< 3` -* `*` is equivalent to `>= 0.0.0` - -### Tilde Range Comparisons (Patch) - -The tilde (`~`) comparison operator is for patch level ranges when a minor -version is specified and major level changes when the minor number is missing. -For example, - -* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` -* `~1` is equivalent to `>= 1, < 2` -* `~2.3` is equivalent to `>= 2.3, < 2.4` -* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` -* `~1.x` is equivalent to `>= 1, < 2` - -### Caret Range Comparisons (Major) - -The caret (`^`) comparison operator is for major level changes once a stable -(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts -as the API stability level. This is useful when comparisons of API versions as a -major change is API breaking. For example, - -* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` -* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` -* `^2.3` is equivalent to `>= 2.3, < 3` -* `^2.x` is equivalent to `>= 2.0.0, < 3` -* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` -* `^0.2` is equivalent to `>=0.2.0 <0.3.0` -* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` -* `^0.0` is equivalent to `>=0.0.0 <0.1.0` -* `^0` is equivalent to `>=0.0.0 <1.0.0` - -## Validation - -In addition to testing a version against a constraint, a version can be validated -against a constraint. When validation fails a slice of errors containing why a -version didn't meet the constraint is returned. For example, - -```go -c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") -if err != nil { - // Handle constraint not being parseable. -} - -v, err := semver.NewVersion("1.3") -if err != nil { - // Handle version not being parseable. -} - -// Validate a version against a constraint. -a, msgs := c.Validate(v) -// a is false -for _, m := range msgs { - fmt.Println(m) - - // Loops over the errors which would read - // "1.3 is greater than 1.2.3" - // "1.3 is less than 1.4" -} -``` - -## Contribute - -If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) -or [create a pull request](https://github.com/Masterminds/semver/pulls). - -## Security - -Security is an important consideration for this project. The project currently -uses the following tools to help discover security issues: - -* [CodeQL](https://github.com/Masterminds/semver) -* [gosec](https://github.com/securego/gosec) -* Daily Fuzz testing - -If you believe you have found a security vulnerability you can privately disclose -it through the [GitHub security page](https://github.com/Masterminds/semver/security). diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md b/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md deleted file mode 100644 index a30a66b1f7..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/SECURITY.md +++ /dev/null @@ -1,19 +0,0 @@ -# Security Policy - -## Supported Versions - -The following versions of semver are currently supported: - -| Version | Supported | -| ------- | ------------------ | -| 3.x | :white_check_mark: | -| 2.x | :x: | -| 1.x | :x: | - -Fixes are only released for the latest minor version in the form of a patch release. - -## Reporting a Vulnerability - -You can privately disclose a vulnerability through GitHubs -[private vulnerability reporting](https://github.com/Masterminds/semver/security/advisories) -mechanism. diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go b/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go deleted file mode 100644 index a78235895f..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/collection.go +++ /dev/null @@ -1,24 +0,0 @@ -package semver - -// Collection is a collection of Version instances and implements the sort -// interface. See the sort package for more details. -// https://golang.org/pkg/sort/ -type Collection []*Version - -// Len returns the length of a collection. The number of Version instances -// on the slice. -func (c Collection) Len() int { - return len(c) -} - -// Less is needed for the sort interface to compare two Version objects on the -// slice. If checks if one is less than the other. -func (c Collection) Less(i, j int) bool { - return c[i].LessThan(c[j]) -} - -// Swap is needed for the sort interface to replace the Version objects -// at two different positions in the slice. -func (c Collection) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go b/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go deleted file mode 100644 index 8461c7ed90..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/constraints.go +++ /dev/null @@ -1,594 +0,0 @@ -package semver - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "strings" -) - -// Constraints is one or more constraint that a semantic version can be -// checked against. -type Constraints struct { - constraints [][]*constraint -} - -// NewConstraint returns a Constraints instance that a Version instance can -// be checked against. If there is a parse error it will be returned. -func NewConstraint(c string) (*Constraints, error) { - - // Rewrite - ranges into a comparison operation. - c = rewriteRange(c) - - ors := strings.Split(c, "||") - or := make([][]*constraint, len(ors)) - for k, v := range ors { - - // TODO: Find a way to validate and fetch all the constraints in a simpler form - - // Validate the segment - if !validConstraintRegex.MatchString(v) { - return nil, fmt.Errorf("improper constraint: %s", v) - } - - cs := findConstraintRegex.FindAllString(v, -1) - if cs == nil { - cs = append(cs, v) - } - result := make([]*constraint, len(cs)) - for i, s := range cs { - pc, err := parseConstraint(s) - if err != nil { - return nil, err - } - - result[i] = pc - } - or[k] = result - } - - o := &Constraints{constraints: or} - return o, nil -} - -// Check tests if a version satisfies the constraints. -func (cs Constraints) Check(v *Version) bool { - // TODO(mattfarina): For v4 of this library consolidate the Check and Validate - // functions as the underlying functions make that possible now. - // loop over the ORs and check the inner ANDs - for _, o := range cs.constraints { - joy := true - for _, c := range o { - if check, _ := c.check(v); !check { - joy = false - break - } - } - - if joy { - return true - } - } - - return false -} - -// Validate checks if a version satisfies a constraint. If not a slice of -// reasons for the failure are returned in addition to a bool. -func (cs Constraints) Validate(v *Version) (bool, []error) { - // loop over the ORs and check the inner ANDs - var e []error - - // Capture the prerelease message only once. When it happens the first time - // this var is marked - var prerelesase bool - for _, o := range cs.constraints { - joy := true - for _, c := range o { - // Before running the check handle the case there the version is - // a prerelease and the check is not searching for prereleases. - if c.con.pre == "" && v.pre != "" { - if !prerelesase { - em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - e = append(e, em) - prerelesase = true - } - joy = false - - } else { - - if _, err := c.check(v); err != nil { - e = append(e, err) - joy = false - } - } - } - - if joy { - return true, []error{} - } - } - - return false, e -} - -func (cs Constraints) String() string { - buf := make([]string, len(cs.constraints)) - var tmp bytes.Buffer - - for k, v := range cs.constraints { - tmp.Reset() - vlen := len(v) - for kk, c := range v { - tmp.WriteString(c.string()) - - // Space separate the AND conditions - if vlen > 1 && kk < vlen-1 { - tmp.WriteString(" ") - } - } - buf[k] = tmp.String() - } - - return strings.Join(buf, " || ") -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -func (cs *Constraints) UnmarshalText(text []byte) error { - temp, err := NewConstraint(string(text)) - if err != nil { - return err - } - - *cs = *temp - - return nil -} - -// MarshalText implements the encoding.TextMarshaler interface. -func (cs Constraints) MarshalText() ([]byte, error) { - return []byte(cs.String()), nil -} - -var constraintOps map[string]cfunc -var constraintRegex *regexp.Regexp -var constraintRangeRegex *regexp.Regexp - -// Used to find individual constraints within a multi-constraint string -var findConstraintRegex *regexp.Regexp - -// Used to validate an segment of ANDs is valid -var validConstraintRegex *regexp.Regexp - -const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + - `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + - `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` - -func init() { - constraintOps = map[string]cfunc{ - "": constraintTildeOrEqual, - "=": constraintTildeOrEqual, - "!=": constraintNotEqual, - ">": constraintGreaterThan, - "<": constraintLessThan, - ">=": constraintGreaterThanEqual, - "=>": constraintGreaterThanEqual, - "<=": constraintLessThanEqual, - "=<": constraintLessThanEqual, - "~": constraintTilde, - "~>": constraintTilde, - "^": constraintCaret, - } - - ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^` - - constraintRegex = regexp.MustCompile(fmt.Sprintf( - `^\s*(%s)\s*(%s)\s*$`, - ops, - cvRegex)) - - constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( - `\s*(%s)\s+-\s+(%s)\s*`, - cvRegex, cvRegex)) - - findConstraintRegex = regexp.MustCompile(fmt.Sprintf( - `(%s)\s*(%s)`, - ops, - cvRegex)) - - // The first time a constraint shows up will look slightly different from - // future times it shows up due to a leading space or comma in a given - // string. - validConstraintRegex = regexp.MustCompile(fmt.Sprintf( - `^(\s*(%s)\s*(%s)\s*)((?:\s+|,\s*)(%s)\s*(%s)\s*)*$`, - ops, - cvRegex, - ops, - cvRegex)) -} - -// An individual constraint -type constraint struct { - // The version used in the constraint check. For example, if a constraint - // is '<= 2.0.0' the con a version instance representing 2.0.0. - con *Version - - // The original parsed version (e.g., 4.x from != 4.x) - orig string - - // The original operator for the constraint - origfunc string - - // When an x is used as part of the version (e.g., 1.x) - minorDirty bool - dirty bool - patchDirty bool -} - -// Check if a version meets the constraint -func (c *constraint) check(v *Version) (bool, error) { - return constraintOps[c.origfunc](v, c) -} - -// String prints an individual constraint into a string -func (c *constraint) string() string { - return c.origfunc + c.orig -} - -type cfunc func(v *Version, c *constraint) (bool, error) - -func parseConstraint(c string) (*constraint, error) { - if len(c) > 0 { - m := constraintRegex.FindStringSubmatch(c) - if m == nil { - return nil, fmt.Errorf("improper constraint: %s", c) - } - - cs := &constraint{ - orig: m[2], - origfunc: m[1], - } - - ver := m[2] - minorDirty := false - patchDirty := false - dirty := false - if isX(m[3]) || m[3] == "" { - ver = fmt.Sprintf("0.0.0%s", m[6]) - dirty = true - } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { - minorDirty = true - dirty = true - ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) - } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { - dirty = true - patchDirty = true - ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) - } - - con, err := NewVersion(ver) - if err != nil { - - // The constraintRegex should catch any regex parsing errors. So, - // we should never get here. - return nil, errors.New("constraint Parser Error") - } - - cs.con = con - cs.minorDirty = minorDirty - cs.patchDirty = patchDirty - cs.dirty = dirty - - return cs, nil - } - - // The rest is the special case where an empty string was passed in which - // is equivalent to * or >=0.0.0 - con, err := StrictNewVersion("0.0.0") - if err != nil { - - // The constraintRegex should catch any regex parsing errors. So, - // we should never get here. - return nil, errors.New("constraint Parser Error") - } - - cs := &constraint{ - con: con, - orig: c, - origfunc: "", - minorDirty: false, - patchDirty: false, - dirty: true, - } - return cs, nil -} - -// Constraint functions -func constraintNotEqual(v *Version, c *constraint) (bool, error) { - if c.dirty { - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - if c.con.Major() != v.Major() { - return true, nil - } - if c.con.Minor() != v.Minor() && !c.minorDirty { - return true, nil - } else if c.minorDirty { - return false, fmt.Errorf("%s is equal to %s", v, c.orig) - } else if c.con.Patch() != v.Patch() && !c.patchDirty { - return true, nil - } else if c.patchDirty { - // Need to handle prereleases if present - if v.Prerelease() != "" || c.con.Prerelease() != "" { - eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is equal to %s", v, c.orig) - } - return false, fmt.Errorf("%s is equal to %s", v, c.orig) - } - } - - eq := v.Equal(c.con) - if eq { - return false, fmt.Errorf("%s is equal to %s", v, c.orig) - } - - return true, nil -} - -func constraintGreaterThan(v *Version, c *constraint) (bool, error) { - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - var eq bool - - if !c.dirty { - eq = v.Compare(c.con) == 1 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) - } - - if v.Major() > c.con.Major() { - return true, nil - } else if v.Major() < c.con.Major() { - return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) - } else if c.minorDirty { - // This is a range case such as >11. When the version is something like - // 11.1.0 is it not > 11. For that we would need 12 or higher - return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) - } else if c.patchDirty { - // This is for ranges such as >11.1. A version of 11.1.1 is not greater - // which one of 11.2.1 is greater - eq = v.Minor() > c.con.Minor() - if eq { - return true, nil - } - return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) - } - - // If we have gotten here we are not comparing pre-preleases and can use the - // Compare function to accomplish that. - eq = v.Compare(c.con) == 1 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) -} - -func constraintLessThan(v *Version, c *constraint) (bool, error) { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - eq := v.Compare(c.con) < 0 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) -} - -func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { - - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - eq := v.Compare(c.con) >= 0 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is less than %s", v, c.orig) -} - -func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - var eq bool - - if !c.dirty { - eq = v.Compare(c.con) <= 0 - if eq { - return true, nil - } - return false, fmt.Errorf("%s is greater than %s", v, c.orig) - } - - if v.Major() > c.con.Major() { - return false, fmt.Errorf("%s is greater than %s", v, c.orig) - } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { - return false, fmt.Errorf("%s is greater than %s", v, c.orig) - } - - return true, nil -} - -// ~*, ~>* --> >= 0.0.0 (any) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 -func constraintTilde(v *Version, c *constraint) (bool, error) { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - if v.LessThan(c.con) { - return false, fmt.Errorf("%s is less than %s", v, c.orig) - } - - // ~0.0.0 is a special case where all constraints are accepted. It's - // equivalent to >= 0.0.0. - if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && - !c.minorDirty && !c.patchDirty { - return true, nil - } - - if v.Major() != c.con.Major() { - return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) - } - - if v.Minor() != c.con.Minor() && !c.minorDirty { - return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) - } - - return true, nil -} - -// When there is a .x (dirty) status it automatically opts in to ~. Otherwise -// it's a straight = -func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - if c.dirty { - return constraintTilde(v, c) - } - - eq := v.Equal(c.con) - if eq { - return true, nil - } - - return false, fmt.Errorf("%s is not equal to %s", v, c.orig) -} - -// ^* --> (any) -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2 --> >=1.2.0 <2.0.0 -// ^1 --> >=1.0.0 <2.0.0 -// ^0.2.3 --> >=0.2.3 <0.3.0 -// ^0.2 --> >=0.2.0 <0.3.0 -// ^0.0.3 --> >=0.0.3 <0.0.4 -// ^0.0 --> >=0.0.0 <0.1.0 -// ^0 --> >=0.0.0 <1.0.0 -func constraintCaret(v *Version, c *constraint) (bool, error) { - // If there is a pre-release on the version but the constraint isn't looking - // for them assume that pre-releases are not compatible. See issue 21 for - // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { - return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) - } - - // This less than handles prereleases - if v.LessThan(c.con) { - return false, fmt.Errorf("%s is less than %s", v, c.orig) - } - - var eq bool - - // ^ when the major > 0 is >=x.y.z < x+1 - if c.con.Major() > 0 || c.minorDirty { - - // ^ has to be within a major range for > 0. Everything less than was - // filtered out with the LessThan call above. This filters out those - // that greater but not within the same major range. - eq = v.Major() == c.con.Major() - if eq { - return true, nil - } - return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) - } - - // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 - if c.con.Major() == 0 && v.Major() > 0 { - return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) - } - // If the con Minor is > 0 it is not dirty - if c.con.Minor() > 0 || c.patchDirty { - eq = v.Minor() == c.con.Minor() - if eq { - return true, nil - } - return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) - } - // ^ when the minor is 0 and minor > 0 is =0.0.z - if c.con.Minor() == 0 && v.Minor() > 0 { - return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig) - } - - // At this point the major is 0 and the minor is 0 and not dirty. The patch - // is not dirty so we need to check if they are equal. If they are not equal - eq = c.con.Patch() == v.Patch() - if eq { - return true, nil - } - return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) -} - -func isX(x string) bool { - switch x { - case "x", "*", "X": - return true - default: - return false - } -} - -func rewriteRange(i string) string { - m := constraintRangeRegex.FindAllStringSubmatch(i, -1) - if m == nil { - return i - } - o := i - for _, v := range m { - t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11]) - o = strings.Replace(o, v[0], t, 1) - } - - return o -} diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go b/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go deleted file mode 100644 index 74f97caa57..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/doc.go +++ /dev/null @@ -1,184 +0,0 @@ -/* -Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. - -Specifically it provides the ability to: - - - Parse semantic versions - - Sort semantic versions - - Check if a semantic version fits within a set of constraints - - Optionally work with a `v` prefix - -# Parsing Semantic Versions - -There are two functions that can parse semantic versions. The `StrictNewVersion` -function only parses valid version 2 semantic versions as outlined in the -specification. The `NewVersion` function attempts to coerce a version into a -semantic version and parse it. For example, if there is a leading v or a version -listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid -semantic version (e.g., 1.2.0). In both cases a `Version` object is returned -that can be sorted, compared, and used in constraints. - -When parsing a version an optional error can be returned if there is an issue -parsing the version. For example, - - v, err := semver.NewVersion("1.2.3-beta.1+b345") - -The version object has methods to get the parts of the version, compare it to -other versions, convert the version back into a string, and get the original -string. For more details please see the documentation -at https://godoc.org/github.com/Masterminds/semver. - -# Sorting Semantic Versions - -A set of versions can be sorted using the `sort` package from the standard library. -For example, - - raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} - vs := make([]*semver.Version, len(raw)) - for i, r := range raw { - v, err := semver.NewVersion(r) - if err != nil { - t.Errorf("Error parsing version: %s", err) - } - - vs[i] = v - } - - sort.Sort(semver.Collection(vs)) - -# Checking Version Constraints and Comparing Versions - -There are two methods for comparing versions. One uses comparison methods on -`Version` instances and the other is using Constraints. There are some important -differences to notes between these two methods of comparison. - - 1. When two versions are compared using functions such as `Compare`, `LessThan`, - and others it will follow the specification and always include prereleases - within the comparison. It will provide an answer valid with the comparison - spec section at https://semver.org/#spec-item-11 - 2. When constraint checking is used for checks or validation it will follow a - different set of rules that are common for ranges with tools like npm/js - and Rust/Cargo. This includes considering prereleases to be invalid if the - ranges does not include on. If you want to have it include pre-releases a - simple solution is to include `-0` in your range. - 3. Constraint ranges can have some complex rules including the shorthard use of - ~ and ^. For more details on those see the options below. - -There are differences between the two methods or checking versions because the -comparison methods on `Version` follow the specification while comparison ranges -are not part of the specification. Different packages and tools have taken it -upon themselves to come up with range rules. This has resulted in differences. -For example, npm/js and Cargo/Rust follow similar patterns which PHP has a -different pattern for ^. The comparison features in this package follow the -npm/js and Cargo/Rust lead because applications using it have followed similar -patters with their versions. - -Checking a version against version constraints is one of the most featureful -parts of the package. - - c, err := semver.NewConstraint(">= 1.2.3") - if err != nil { - // Handle constraint not being parsable. - } - - v, err := semver.NewVersion("1.3") - if err != nil { - // Handle version not being parsable. - } - // Check if the version meets the constraints. The a variable will be true. - a := c.Check(v) - -# Basic Comparisons - -There are two elements to the comparisons. First, a comparison string is a list -of comma or space separated AND comparisons. These are then separated by || (OR) -comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a -comparison that's greater than or equal to 1.2 and less than 3.0.0 or is -greater than or equal to 4.2.3. This can also be written as -`">= 1.2, < 3.0.0 || >= 4.2.3"` - -The basic comparisons are: - - - `=`: equal (aliased to no operator) - - `!=`: not equal - - `>`: greater than - - `<`: less than - - `>=`: greater than or equal to - - `<=`: less than or equal to - -# Hyphen Range Comparisons - -There are multiple methods to handle ranges and the first is hyphens ranges. -These look like: - - - `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` - - `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` - -# Wildcards In Comparisons - -The `x`, `X`, and `*` characters can be used as a wildcard character. This works -for all comparison operators. When used on the `=` operator it falls -back to the tilde operation. For example, - - - `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` - - `>= 1.2.x` is equivalent to `>= 1.2.0` - - `<= 2.x` is equivalent to `<= 3` - - `*` is equivalent to `>= 0.0.0` - -Tilde Range Comparisons (Patch) - -The tilde (`~`) comparison operator is for patch level ranges when a minor -version is specified and major level changes when the minor number is missing. -For example, - - - `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0` - - `~1` is equivalent to `>= 1, < 2` - - `~2.3` is equivalent to `>= 2.3 < 2.4` - - `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` - - `~1.x` is equivalent to `>= 1 < 2` - -Caret Range Comparisons (Major) - -The caret (`^`) comparison operator is for major level changes once a stable -(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts -as the API stability level. This is useful when comparisons of API versions as a -major change is API breaking. For example, - - - `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` - - `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` - - `^2.3` is equivalent to `>= 2.3, < 3` - - `^2.x` is equivalent to `>= 2.0.0, < 3` - - `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` - - `^0.2` is equivalent to `>=0.2.0 <0.3.0` - - `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` - - `^0.0` is equivalent to `>=0.0.0 <0.1.0` - - `^0` is equivalent to `>=0.0.0 <1.0.0` - -# Validation - -In addition to testing a version against a constraint, a version can be validated -against a constraint. When validation fails a slice of errors containing why a -version didn't meet the constraint is returned. For example, - - c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") - if err != nil { - // Handle constraint not being parseable. - } - - v, _ := semver.NewVersion("1.3") - if err != nil { - // Handle version not being parseable. - } - - // Validate a version against a constraint. - a, msgs := c.Validate(v) - // a is false - for _, m := range msgs { - fmt.Println(m) - - // Loops over the errors which would read - // "1.3 is greater than 1.2.3" - // "1.3 is less than 1.4" - } -*/ -package semver diff --git a/go-controller/vendor/github.com/Masterminds/semver/v3/version.go b/go-controller/vendor/github.com/Masterminds/semver/v3/version.go deleted file mode 100644 index 7c4bed3347..0000000000 --- a/go-controller/vendor/github.com/Masterminds/semver/v3/version.go +++ /dev/null @@ -1,639 +0,0 @@ -package semver - -import ( - "bytes" - "database/sql/driver" - "encoding/json" - "errors" - "fmt" - "regexp" - "strconv" - "strings" -) - -// The compiled version of the regex created at init() is cached here so it -// only needs to be created once. -var versionRegex *regexp.Regexp - -var ( - // ErrInvalidSemVer is returned a version is found to be invalid when - // being parsed. - ErrInvalidSemVer = errors.New("Invalid Semantic Version") - - // ErrEmptyString is returned when an empty string is passed in for parsing. - ErrEmptyString = errors.New("Version string empty") - - // ErrInvalidCharacters is returned when invalid characters are found as - // part of a version - ErrInvalidCharacters = errors.New("Invalid characters in version") - - // ErrSegmentStartsZero is returned when a version segment starts with 0. - // This is invalid in SemVer. - ErrSegmentStartsZero = errors.New("Version segment starts with 0") - - // ErrInvalidMetadata is returned when the metadata is an invalid format - ErrInvalidMetadata = errors.New("Invalid Metadata string") - - // ErrInvalidPrerelease is returned when the pre-release is an invalid format - ErrInvalidPrerelease = errors.New("Invalid Prerelease string") -) - -// semVerRegex is the regular expression used to parse a semantic version. -const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + - `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + - `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` - -// Version represents a single semantic version. -type Version struct { - major, minor, patch uint64 - pre string - metadata string - original string -} - -func init() { - versionRegex = regexp.MustCompile("^" + semVerRegex + "$") -} - -const ( - num string = "0123456789" - allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num -) - -// StrictNewVersion parses a given version and returns an instance of Version or -// an error if unable to parse the version. Only parses valid semantic versions. -// Performs checking that can find errors within the version. -// If you want to coerce a version such as 1 or 1.2 and parse it as the 1.x -// releases of semver did, use the NewVersion() function. -func StrictNewVersion(v string) (*Version, error) { - // Parsing here does not use RegEx in order to increase performance and reduce - // allocations. - - if len(v) == 0 { - return nil, ErrEmptyString - } - - // Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build - parts := strings.SplitN(v, ".", 3) - if len(parts) != 3 { - return nil, ErrInvalidSemVer - } - - sv := &Version{ - original: v, - } - - // check for prerelease or build metadata - var extra []string - if strings.ContainsAny(parts[2], "-+") { - // Start with the build metadata first as it needs to be on the right - extra = strings.SplitN(parts[2], "+", 2) - if len(extra) > 1 { - // build metadata found - sv.metadata = extra[1] - parts[2] = extra[0] - } - - extra = strings.SplitN(parts[2], "-", 2) - if len(extra) > 1 { - // prerelease found - sv.pre = extra[1] - parts[2] = extra[0] - } - } - - // Validate the number segments are valid. This includes only having positive - // numbers and no leading 0's. - for _, p := range parts { - if !containsOnly(p, num) { - return nil, ErrInvalidCharacters - } - - if len(p) > 1 && p[0] == '0' { - return nil, ErrSegmentStartsZero - } - } - - // Extract the major, minor, and patch elements onto the returned Version - var err error - sv.major, err = strconv.ParseUint(parts[0], 10, 64) - if err != nil { - return nil, err - } - - sv.minor, err = strconv.ParseUint(parts[1], 10, 64) - if err != nil { - return nil, err - } - - sv.patch, err = strconv.ParseUint(parts[2], 10, 64) - if err != nil { - return nil, err - } - - // No prerelease or build metadata found so returning now as a fastpath. - if sv.pre == "" && sv.metadata == "" { - return sv, nil - } - - if sv.pre != "" { - if err = validatePrerelease(sv.pre); err != nil { - return nil, err - } - } - - if sv.metadata != "" { - if err = validateMetadata(sv.metadata); err != nil { - return nil, err - } - } - - return sv, nil -} - -// NewVersion parses a given version and returns an instance of Version or -// an error if unable to parse the version. If the version is SemVer-ish it -// attempts to convert it to SemVer. If you want to validate it was a strict -// semantic version at parse time see StrictNewVersion(). -func NewVersion(v string) (*Version, error) { - m := versionRegex.FindStringSubmatch(v) - if m == nil { - return nil, ErrInvalidSemVer - } - - sv := &Version{ - metadata: m[8], - pre: m[5], - original: v, - } - - var err error - sv.major, err = strconv.ParseUint(m[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) - } - - if m[2] != "" { - sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) - if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) - } - } else { - sv.minor = 0 - } - - if m[3] != "" { - sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) - if err != nil { - return nil, fmt.Errorf("Error parsing version segment: %s", err) - } - } else { - sv.patch = 0 - } - - // Perform some basic due diligence on the extra parts to ensure they are - // valid. - - if sv.pre != "" { - if err = validatePrerelease(sv.pre); err != nil { - return nil, err - } - } - - if sv.metadata != "" { - if err = validateMetadata(sv.metadata); err != nil { - return nil, err - } - } - - return sv, nil -} - -// New creates a new instance of Version with each of the parts passed in as -// arguments instead of parsing a version string. -func New(major, minor, patch uint64, pre, metadata string) *Version { - v := Version{ - major: major, - minor: minor, - patch: patch, - pre: pre, - metadata: metadata, - original: "", - } - - v.original = v.String() - - return &v -} - -// MustParse parses a given version and panics on error. -func MustParse(v string) *Version { - sv, err := NewVersion(v) - if err != nil { - panic(err) - } - return sv -} - -// String converts a Version object to a string. -// Note, if the original version contained a leading v this version will not. -// See the Original() method to retrieve the original value. Semantic Versions -// don't contain a leading v per the spec. Instead it's optional on -// implementation. -func (v Version) String() string { - var buf bytes.Buffer - - fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) - if v.pre != "" { - fmt.Fprintf(&buf, "-%s", v.pre) - } - if v.metadata != "" { - fmt.Fprintf(&buf, "+%s", v.metadata) - } - - return buf.String() -} - -// Original returns the original value passed in to be parsed. -func (v *Version) Original() string { - return v.original -} - -// Major returns the major version. -func (v Version) Major() uint64 { - return v.major -} - -// Minor returns the minor version. -func (v Version) Minor() uint64 { - return v.minor -} - -// Patch returns the patch version. -func (v Version) Patch() uint64 { - return v.patch -} - -// Prerelease returns the pre-release version. -func (v Version) Prerelease() string { - return v.pre -} - -// Metadata returns the metadata on the version. -func (v Version) Metadata() string { - return v.metadata -} - -// originalVPrefix returns the original 'v' prefix if any. -func (v Version) originalVPrefix() string { - // Note, only lowercase v is supported as a prefix by the parser. - if v.original != "" && v.original[:1] == "v" { - return v.original[:1] - } - return "" -} - -// IncPatch produces the next patch version. -// If the current version does not have prerelease/metadata information, -// it unsets metadata and prerelease values, increments patch number. -// If the current version has any of prerelease or metadata information, -// it unsets both values and keeps current patch value -func (v Version) IncPatch() Version { - vNext := v - // according to http://semver.org/#spec-item-9 - // Pre-release versions have a lower precedence than the associated normal version. - // according to http://semver.org/#spec-item-10 - // Build metadata SHOULD be ignored when determining version precedence. - if v.pre != "" { - vNext.metadata = "" - vNext.pre = "" - } else { - vNext.metadata = "" - vNext.pre = "" - vNext.patch = v.patch + 1 - } - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// IncMinor produces the next minor version. -// Sets patch to 0. -// Increments minor number. -// Unsets metadata. -// Unsets prerelease status. -func (v Version) IncMinor() Version { - vNext := v - vNext.metadata = "" - vNext.pre = "" - vNext.patch = 0 - vNext.minor = v.minor + 1 - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// IncMajor produces the next major version. -// Sets patch to 0. -// Sets minor to 0. -// Increments major number. -// Unsets metadata. -// Unsets prerelease status. -func (v Version) IncMajor() Version { - vNext := v - vNext.metadata = "" - vNext.pre = "" - vNext.patch = 0 - vNext.minor = 0 - vNext.major = v.major + 1 - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext -} - -// SetPrerelease defines the prerelease value. -// Value must not include the required 'hyphen' prefix. -func (v Version) SetPrerelease(prerelease string) (Version, error) { - vNext := v - if len(prerelease) > 0 { - if err := validatePrerelease(prerelease); err != nil { - return vNext, err - } - } - vNext.pre = prerelease - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext, nil -} - -// SetMetadata defines metadata value. -// Value must not include the required 'plus' prefix. -func (v Version) SetMetadata(metadata string) (Version, error) { - vNext := v - if len(metadata) > 0 { - if err := validateMetadata(metadata); err != nil { - return vNext, err - } - } - vNext.metadata = metadata - vNext.original = v.originalVPrefix() + "" + vNext.String() - return vNext, nil -} - -// LessThan tests if one version is less than another one. -func (v *Version) LessThan(o *Version) bool { - return v.Compare(o) < 0 -} - -// GreaterThan tests if one version is greater than another one. -func (v *Version) GreaterThan(o *Version) bool { - return v.Compare(o) > 0 -} - -// Equal tests if two versions are equal to each other. -// Note, versions can be equal with different metadata since metadata -// is not considered part of the comparable version. -func (v *Version) Equal(o *Version) bool { - return v.Compare(o) == 0 -} - -// Compare compares this version to another one. It returns -1, 0, or 1 if -// the version smaller, equal, or larger than the other version. -// -// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is -// lower than the version without a prerelease. Compare always takes into account -// prereleases. If you want to work with ranges using typical range syntaxes that -// skip prereleases if the range is not looking for them use constraints. -func (v *Version) Compare(o *Version) int { - // Compare the major, minor, and patch version for differences. If a - // difference is found return the comparison. - if d := compareSegment(v.Major(), o.Major()); d != 0 { - return d - } - if d := compareSegment(v.Minor(), o.Minor()); d != 0 { - return d - } - if d := compareSegment(v.Patch(), o.Patch()); d != 0 { - return d - } - - // At this point the major, minor, and patch versions are the same. - ps := v.pre - po := o.Prerelease() - - if ps == "" && po == "" { - return 0 - } - if ps == "" { - return 1 - } - if po == "" { - return -1 - } - - return comparePrerelease(ps, po) -} - -// UnmarshalJSON implements JSON.Unmarshaler interface. -func (v *Version) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - temp, err := NewVersion(s) - if err != nil { - return err - } - v.major = temp.major - v.minor = temp.minor - v.patch = temp.patch - v.pre = temp.pre - v.metadata = temp.metadata - v.original = temp.original - return nil -} - -// MarshalJSON implements JSON.Marshaler interface. -func (v Version) MarshalJSON() ([]byte, error) { - return json.Marshal(v.String()) -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -func (v *Version) UnmarshalText(text []byte) error { - temp, err := NewVersion(string(text)) - if err != nil { - return err - } - - *v = *temp - - return nil -} - -// MarshalText implements the encoding.TextMarshaler interface. -func (v Version) MarshalText() ([]byte, error) { - return []byte(v.String()), nil -} - -// Scan implements the SQL.Scanner interface. -func (v *Version) Scan(value interface{}) error { - var s string - s, _ = value.(string) - temp, err := NewVersion(s) - if err != nil { - return err - } - v.major = temp.major - v.minor = temp.minor - v.patch = temp.patch - v.pre = temp.pre - v.metadata = temp.metadata - v.original = temp.original - return nil -} - -// Value implements the Driver.Valuer interface. -func (v Version) Value() (driver.Value, error) { - return v.String(), nil -} - -func compareSegment(v, o uint64) int { - if v < o { - return -1 - } - if v > o { - return 1 - } - - return 0 -} - -func comparePrerelease(v, o string) int { - // split the prelease versions by their part. The separator, per the spec, - // is a . - sparts := strings.Split(v, ".") - oparts := strings.Split(o, ".") - - // Find the longer length of the parts to know how many loop iterations to - // go through. - slen := len(sparts) - olen := len(oparts) - - l := slen - if olen > slen { - l = olen - } - - // Iterate over each part of the prereleases to compare the differences. - for i := 0; i < l; i++ { - // Since the lentgh of the parts can be different we need to create - // a placeholder. This is to avoid out of bounds issues. - stemp := "" - if i < slen { - stemp = sparts[i] - } - - otemp := "" - if i < olen { - otemp = oparts[i] - } - - d := comparePrePart(stemp, otemp) - if d != 0 { - return d - } - } - - // Reaching here means two versions are of equal value but have different - // metadata (the part following a +). They are not identical in string form - // but the version comparison finds them to be equal. - return 0 -} - -func comparePrePart(s, o string) int { - // Fastpath if they are equal - if s == o { - return 0 - } - - // When s or o are empty we can use the other in an attempt to determine - // the response. - if s == "" { - if o != "" { - return -1 - } - return 1 - } - - if o == "" { - if s != "" { - return 1 - } - return -1 - } - - // When comparing strings "99" is greater than "103". To handle - // cases like this we need to detect numbers and compare them. According - // to the semver spec, numbers are always positive. If there is a - at the - // start like -99 this is to be evaluated as an alphanum. numbers always - // have precedence over alphanum. Parsing as Uints because negative numbers - // are ignored. - - oi, n1 := strconv.ParseUint(o, 10, 64) - si, n2 := strconv.ParseUint(s, 10, 64) - - // The case where both are strings compare the strings - if n1 != nil && n2 != nil { - if s > o { - return 1 - } - return -1 - } else if n1 != nil { - // o is a string and s is a number - return -1 - } else if n2 != nil { - // s is a string and o is a number - return 1 - } - // Both are numbers - if si > oi { - return 1 - } - return -1 -} - -// Like strings.ContainsAny but does an only instead of any. -func containsOnly(s string, comp string) bool { - return strings.IndexFunc(s, func(r rune) bool { - return !strings.ContainsRune(comp, r) - }) == -1 -} - -// From the spec, "Identifiers MUST comprise only -// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. -// Numeric identifiers MUST NOT include leading zeroes.". These segments can -// be dot separated. -func validatePrerelease(p string) error { - eparts := strings.Split(p, ".") - for _, p := range eparts { - if containsOnly(p, num) { - if len(p) > 1 && p[0] == '0' { - return ErrSegmentStartsZero - } - } else if !containsOnly(p, allowed) { - return ErrInvalidPrerelease - } - } - - return nil -} - -// From the spec, "Build metadata MAY be denoted by -// appending a plus sign and a series of dot separated identifiers immediately -// following the patch or pre-release version. Identifiers MUST comprise only -// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty." -func validateMetadata(m string) error { - eparts := strings.Split(m, ".") - for _, p := range eparts { - if !containsOnly(p, allowed) { - return ErrInvalidMetadata - } - } - return nil -} diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go index 5c7f3b028c..201a12e977 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/go-controller/vendor/github.com/containernetworking/cni/libcni/api.go @@ -23,6 +23,7 @@ package libcni import ( "context" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -75,6 +76,7 @@ type NetworkConfigList struct { Name string CNIVersion string DisableCheck bool + DisableGC bool Plugins []*NetworkConfig Bytes []byte } @@ -113,6 +115,8 @@ type CNI interface { GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) + + GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) } type CNIConfig struct { @@ -422,6 +426,9 @@ func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachme dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results") entries, err := os.ReadDir(dirPath) if err != nil { + if os.IsNotExist(err) { + return nil, nil + } return nil, err } @@ -595,9 +602,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, } } - if cachedResult != nil { - _ = c.cacheDel(list.Name, rt) - } + _ = c.cacheDel(list.Name, rt) return nil } @@ -758,15 +763,23 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers // - dump the list of cached attachments, and issue deletes as necessary // - issue a GC to the underlying plugins (if the version is high enough) func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error { + // If DisableGC is set, then don't bother GCing at all. + if list.DisableGC { + return nil + } + // First, get the list of cached attachments cachedAttachments, err := c.GetCachedAttachments("") if err != nil { return nil } - validAttachments := make(map[types.GCAttachment]interface{}, len(args.ValidAttachments)) - for _, a := range args.ValidAttachments { - validAttachments[a] = nil + var validAttachments map[types.GCAttachment]interface{} + if args != nil { + validAttachments = make(map[types.GCAttachment]interface{}, len(args.ValidAttachments)) + for _, a := range args.ValidAttachments { + validAttachments[a] = nil + } } var errs []error @@ -799,10 +812,15 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, // now, if the version supports it, issue a GC if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt { inject := map[string]interface{}{ - "name": list.Name, - "cniVersion": list.CNIVersion, - "cni.dev/valid-attachments": args.ValidAttachments, + "name": list.Name, + "cniVersion": list.CNIVersion, } + if args != nil { + inject["cni.dev/valid-attachments"] = args.ValidAttachments + // #1101: spec used incorrect variable name + inject["cni.dev/attachments"] = args.ValidAttachments + } + for _, plugin := range list.Plugins { // build config here pluginConfig, err := InjectConf(plugin, inject) @@ -815,7 +833,7 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, } } - return joinErrors(errs...) + return errors.Join(errs...) } func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error { diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go index 6c5d99de98..1d1b821c63 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/go-controller/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -20,11 +20,10 @@ import ( "fmt" "os" "path/filepath" + "slices" "sort" "strings" - "github.com/Masterminds/semver/v3" - "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/version" ) @@ -92,24 +91,20 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { rawVersions, ok := rawList["cniVersions"] if ok { // Parse the current package CNI version - currentVersion, err := semver.NewVersion(version.Current()) - if err != nil { - panic("CNI version is invalid semver!") - } - rvs, ok := rawVersions.([]interface{}) if !ok { return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs) } - vs := make([]*semver.Version, 0, len(rvs)) + vs := make([]string, 0, len(rvs)) for i, rv := range rvs { v, ok := rv.(string) if !ok { return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv) } - if v, err := semver.NewVersion(v); err != nil { + gt, err := version.GreaterThan(v, version.Current()) + if err != nil { return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err) - } else if !v.GreaterThan(currentVersion) { + } else if !gt { // Skip versions "greater" than this implementation of the spec vs = append(vs, v) } @@ -117,41 +112,65 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { // if cniVersion was already set, append it to the list for sorting. if cniVersion != "" { - if v, err := semver.NewVersion(cniVersion); err != nil { + gt, err := version.GreaterThan(cniVersion, version.Current()) + if err != nil { return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err) - } else if !v.GreaterThan(currentVersion) { + } else if !gt { // ignore any versions higher than the current implemented spec version - vs = append(vs, v) + vs = append(vs, cniVersion) } } - sort.Sort(semver.Collection(vs)) + slices.SortFunc[[]string](vs, func(v1, v2 string) int { + if v1 == v2 { + return 0 + } + if gt, _ := version.GreaterThan(v1, v2); gt { + return 1 + } + return -1 + }) if len(vs) > 0 { - cniVersion = vs[len(vs)-1].String() + cniVersion = vs[len(vs)-1] } } - disableCheck := false - if rawDisableCheck, ok := rawList["disableCheck"]; ok { - disableCheck, ok = rawDisableCheck.(bool) + readBool := func(key string) (bool, error) { + rawVal, ok := rawList[key] if !ok { - disableCheckStr, ok := rawDisableCheck.(string) - if !ok { - return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck) - } - switch { - case strings.ToLower(disableCheckStr) == "false": - disableCheck = false - case strings.ToLower(disableCheckStr) == "true": - disableCheck = true - default: - return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck value %q", disableCheckStr) - } + return false, nil + } + if b, ok := rawVal.(bool); ok { + return b, nil } + + s, ok := rawVal.(string) + if !ok { + return false, fmt.Errorf("error parsing configuration list: invalid type %T for %s", rawVal, key) + } + s = strings.ToLower(s) + switch s { + case "false": + return false, nil + case "true": + return true, nil + } + return false, fmt.Errorf("error parsing configuration list: invalid value %q for %s", s, key) + } + + disableCheck, err := readBool("disableCheck") + if err != nil { + return nil, err + } + + disableGC, err := readBool("disableGC") + if err != nil { + return nil, err } list := &NetworkConfigList{ Name: name, DisableCheck: disableCheck, + DisableGC: disableGC, CNIVersion: cniVersion, Bytes: bytes, } diff --git a/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go b/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go deleted file mode 100644 index 100fb8392d..0000000000 --- a/go-controller/vendor/github.com/containernetworking/cni/libcni/multierror.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Copyright the CNI authors -// -// 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. -// -// Adapted from errors/join.go from go 1.20 -// This package can be removed once the toolchain is updated to 1.20 - -package libcni - -func joinErrors(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &multiError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type multiError struct { - errs []error -} - -func (e *multiError) Error() string { - var b []byte - for i, err := range e.errs { - if i > 0 { - b = append(b, '\n') - } - b = append(b, err.Error()...) - } - return string(b) -} diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_darwin.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_darwin.go new file mode 100644 index 0000000000..cffe136178 --- /dev/null +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/ns/ns_darwin.go @@ -0,0 +1,21 @@ +// Copyright 2022 CNI authors +// +// 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 ns + +import "github.com/containernetworking/cni/pkg/types" + +func CheckNetNS(nsPath string) (bool, *types.Error) { + return false, nil +} diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go index 193ac46ef8..8453bb5d87 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -56,8 +56,8 @@ func (n *IPNet) UnmarshalJSON(data []byte) error { return nil } -// NetConf describes a network. -type NetConf struct { +// NetConfType describes a network. +type NetConfType struct { CNIVersion string `json:"cniVersion,omitempty"` Name string `json:"name,omitempty"` @@ -73,6 +73,9 @@ type NetConf struct { ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"` } +// NetConf is defined as different type as custom MarshalJSON() and issue #1096 +type NetConf NetConfType + // GCAttachment is the parameters to a GC call -- namely, // the container ID and ifname pair that represents a // still-valid attachment. @@ -83,11 +86,11 @@ type GCAttachment struct { // Note: DNS should be omit if DNS is empty but default Marshal function // will output empty structure hence need to write a Marshal function -func (n *NetConf) MarshalJSON() ([]byte, error) { +func (n *NetConfType) MarshalJSON() ([]byte, error) { // use type alias to escape recursion for json.Marshal() to MarshalJSON() type fixObjType = NetConf - bytes, err := json.Marshal(fixObjType(*n)) //nolint:all + bytes, err := json.Marshal(fixObjType(*n)) if err != nil { return nil, err } @@ -119,6 +122,7 @@ type NetConfList struct { Name string `json:"name,omitempty"` DisableCheck bool `json:"disableCheck,omitempty"` + DisableGC bool `json:"disableGC,omitempty"` Plugins []*NetConf `json:"plugins,omitempty"` } diff --git a/go-controller/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/go-controller/vendor/github.com/containernetworking/cni/pkg/version/plugin.go index 17b22b6b0c..e3bd375bca 100644 --- a/go-controller/vendor/github.com/containernetworking/cni/pkg/version/plugin.go +++ b/go-controller/vendor/github.com/containernetworking/cni/pkg/version/plugin.go @@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) { } return false, nil } + +// GreaterThan returns true if the first version is greater than the second +func GreaterThan(version, otherVersion string) (bool, error) { + firstMajor, firstMinor, firstMicro, err := ParseVersion(version) + if err != nil { + return false, err + } + + secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion) + if err != nil { + return false, err + } + + if firstMajor > secondMajor { + return true, nil + } else if firstMajor == secondMajor { + if firstMinor > secondMinor { + return true, nil + } else if firstMinor == secondMinor && firstMicro > secondMicro { + return true, nil + } + } + return false, nil +} diff --git a/go-controller/vendor/modules.txt b/go-controller/vendor/modules.txt index e3578ea166..7564f7a89c 100644 --- a/go-controller/vendor/modules.txt +++ b/go-controller/vendor/modules.txt @@ -1,6 +1,3 @@ -# github.com/Masterminds/semver/v3 v3.2.1 -## explicit; go 1.18 -github.com/Masterminds/semver/v3 # github.com/Microsoft/go-winio v0.6.2 ## explicit; go 1.21 github.com/Microsoft/go-winio @@ -59,8 +56,8 @@ github.com/cespare/xxhash/v2 # github.com/containerd/cgroups v1.1.0 ## explicit; go 1.17 github.com/containerd/cgroups/stats/v1 -# github.com/containernetworking/cni v1.2.0-rc1 -## explicit; go 1.18 +# github.com/containernetworking/cni v1.2.3 +## explicit; go 1.21 github.com/containernetworking/cni/libcni github.com/containernetworking/cni/pkg/invoke github.com/containernetworking/cni/pkg/ns diff --git a/test/e2e/go.mod b/test/e2e/go.mod index a6a913d424..f336f1c7ce 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -25,7 +25,6 @@ require ( require ( cel.dev/expr v0.18.0 // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect @@ -40,7 +39,7 @@ require ( github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/ttrpc v1.2.5 // indirect - github.com/containernetworking/cni v1.2.0-rc1 // indirect + github.com/containernetworking/cni v1.2.3 // indirect github.com/coreos/go-iptables v0.6.0 // indirect github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb // indirect github.com/coreos/go-semver v0.3.1 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 9df045a32f..ce895b56ed 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -51,8 +51,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -97,8 +95,8 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= -github.com/containernetworking/cni v1.2.0-rc1 h1:AKI3+pXtgY4PDLN9+50o9IaywWVuey0Jkw3Lvzp0HCY= -github.com/containernetworking/cni v1.2.0-rc1/go.mod h1:Lt0TQcZQVDju64fYxUhDziTgXCDe3Olzi9I4zZJLWHg= +github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= +github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU= github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4= github.com/coreos/butane v0.18.0 h1:WDeUC/dX1MUUVPwiqsQetQZsShNKk+2lrRXlC4ZhnZA= From 13bbcf264d26d6c2d292765d23c739c611b67302 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Thu, 19 Jun 2025 11:30:05 +0200 Subject: [PATCH 192/278] udn, util: Add ip, mac and ipamclaimref request to active network Signed-off-by: Enrique Llorente --- go-controller/pkg/util/multi_network.go | 42 +++++- go-controller/pkg/util/multi_network_test.go | 149 ++++++++++++++++++- 2 files changed, 182 insertions(+), 9 deletions(-) diff --git a/go-controller/pkg/util/multi_network.go b/go-controller/pkg/util/multi_network.go index bf1d3d6993..7819824d3e 100644 --- a/go-controller/pkg/util/multi_network.go +++ b/go-controller/pkg/util/multi_network.go @@ -1309,6 +1309,21 @@ func GetPodNADToNetworkMapping(pod *corev1.Pod, nInfo NetInfo) (bool, map[string return true, networkSelections, nil } +// overrideActiveNSEWithDefaultNSE overrides the provided active NetworkSelectionElement with the IP and MAC requests from +// the default NetworkSelectionElement after validating its namespace and name. +func overrideActiveNSEWithDefaultNSE(defaultNSE, activeNSE *nettypes.NetworkSelectionElement) error { + if defaultNSE.Namespace != config.Kubernetes.OVNConfigNamespace { + return fmt.Errorf("unexpected default NSE namespace %q, expected %q", defaultNSE.Namespace, config.Kubernetes.OVNConfigNamespace) + } + if defaultNSE.Name != types.DefaultNetworkName { + return fmt.Errorf("unexpected default NSE name %q, expected %q", defaultNSE.Name, types.DefaultNetworkName) + } + activeNSE.IPRequest = defaultNSE.IPRequest + activeNSE.MacRequest = defaultNSE.MacRequest + activeNSE.IPAMClaimReference = defaultNSE.IPAMClaimReference + return nil +} + // GetPodNADToNetworkMappingWithActiveNetwork will call `GetPodNADToNetworkMapping` passing "nInfo" which correspond // to the NetInfo representing the NAD, the resulting NetworkSelectingElements will be decorated with the ones // from found active network @@ -1337,18 +1352,39 @@ func GetPodNADToNetworkMappingWithActiveNetwork(pod *corev1.Pod, nInfo NetInfo, if len(networkSelections) == 0 { networkSelections = map[string]*nettypes.NetworkSelectionElement{} } - networkSelections[activeNetworkNADs[0]] = &nettypes.NetworkSelectionElement{ + + activeNSE := &nettypes.NetworkSelectionElement{ Namespace: activeNetworkNADKey[0], Name: activeNetworkNADKey[1], } - if nInfo.IsPrimaryNetwork() && AllowsPersistentIPs(nInfo) { + // Feature gate integration: EnablePreconfiguredUDNAddresses controls default network IP/MAC transfer to active network + if IsPreconfiguredUDNAddressesEnabled() { + // Limit the static ip and mac requests to the layer2 primary UDN when EnablePreconfiguredUDNAddresses is enabled, we + // don't need to explicitly check this is primary UDN since + // the "active network" concept is exactly that. + if activeNetwork.TopologyType() == types.Layer2Topology { + defaultNSE, err := GetK8sPodDefaultNetworkSelection(pod) + if err != nil { + return false, nil, fmt.Errorf("failed getting default-network annotation for pod %q: %w", pod.Namespace+"/"+pod.Name, err) + } + // If there are static IPs and MACs at the default NSE, override the active NSE with them + if defaultNSE != nil { + if err := overrideActiveNSEWithDefaultNSE(defaultNSE, activeNSE); err != nil { + return false, nil, err + } + } + } + } + + if nInfo.IsPrimaryNetwork() && AllowsPersistentIPs(nInfo) && activeNSE.IPAMClaimReference == "" { ipamClaimName, wasPersistentIPRequested := pod.Annotations[OvnUDNIPAMClaimName] if wasPersistentIPRequested { - networkSelections[activeNetworkNADs[0]].IPAMClaimReference = ipamClaimName + activeNSE.IPAMClaimReference = ipamClaimName } } + networkSelections[activeNetworkNADs[0]] = activeNSE return true, networkSelections, nil } diff --git a/go-controller/pkg/util/multi_network_test.go b/go-controller/pkg/util/multi_network_test.go index daaaf920a5..2c953464be 100644 --- a/go-controller/pkg/util/multi_network_test.go +++ b/go-controller/pkg/util/multi_network_test.go @@ -862,6 +862,7 @@ func TestGetPodNADToNetworkMappingWithActiveNetwork(t *testing.T) { expectedError error expectedIsAttachmentRequested bool expectedNetworkSelectionElements map[string]*nadv1.NetworkSelectionElement + enablePreconfiguredUDNAddresses bool } tests := []testConfig{ @@ -1011,10 +1012,143 @@ func TestGetPodNADToNetworkMappingWithActiveNetwork(t *testing.T) { }, }, }, + { + desc: "the network configuration for a primary layer2 UDN receive pod requesting IP, MAC and IPAMClaimRef on default network annotation for it", + inputNetConf: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPrimaryUDNConfig: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPodAnnotations: map[string]string{ + nadv1.NetworkAttachmentAnnot: GetNADName(namespaceName, "another-network"), + DefNetworkAnnotation: `[{"namespace": "ovn-kubernetes", "name": "default", "ips": ["192.168.0.3/24", "fda6::3/48"], "mac": "aa:bb:cc:dd:ee:ff", "ipam-claim-reference": "my-ipam-claim"}]`, + }, + expectedIsAttachmentRequested: true, + expectedNetworkSelectionElements: map[string]*nadv1.NetworkSelectionElement{ + "ns1/attachment1": { + Name: "attachment1", + Namespace: "ns1", + IPRequest: []string{"192.168.0.3/24", "fda6::3/48"}, + MacRequest: "aa:bb:cc:dd:ee:ff", + IPAMClaimReference: "my-ipam-claim", + }, + }, + enablePreconfiguredUDNAddresses: true, + }, + { + desc: "the network configuration for a primary layer2 UDN receive pod requesting IP and MAC on default network annotation for it, but with unexpected namespace", + inputNetConf: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPrimaryUDNConfig: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPodAnnotations: map[string]string{ + DefNetworkAnnotation: `[{"namespace": "other-namespace", "name": "default", "ips": ["192.168.0.3/24", "fda6::3/48"], "mac": "aa:bb:cc:dd:ee:ff"}]`, + }, + enablePreconfiguredUDNAddresses: true, + expectedError: fmt.Errorf(`unexpected default NSE namespace "other-namespace", expected "ovn-kubernetes"`), + }, + { + desc: "the network configuration for a primary layer2 UDN receive pod requesting IP and MAC on default network annotation for it, but with unexpected name", + inputNetConf: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPrimaryUDNConfig: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPodAnnotations: map[string]string{ + DefNetworkAnnotation: `[{"namespace": "ovn-kubernetes", "name": "unexpected-name", "ips": ["192.168.0.3/24", "fda6::3/48"], "mac": "aa:bb:cc:dd:ee:ff"}]`, + }, + enablePreconfiguredUDNAddresses: true, + expectedError: fmt.Errorf(`unexpected default NSE name "unexpected-name", expected "default"`), + }, + + { + desc: "default-network ips and mac is is ignored for Layer3 topology", + inputNetConf: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer3Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPrimaryUDNConfig: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer3Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPodAnnotations: map[string]string{ + nadv1.NetworkAttachmentAnnot: GetNADName(namespaceName, "another-network"), + DefNetworkAnnotation: `[{"namespace": "ovn-kubernetes", "name": "default", "ips": ["192.168.0.3/24", "fda6::3/48"], "mac": "aa:bb:cc:dd:ee:ff"}]`, + }, + expectedIsAttachmentRequested: true, + expectedNetworkSelectionElements: map[string]*nadv1.NetworkSelectionElement{ + "ns1/attachment1": { + Name: "attachment1", + Namespace: "ns1", + IPRequest: nil, + MacRequest: "", + }, + }, + enablePreconfiguredUDNAddresses: true, + }, + { + desc: "default-network with bad format", + inputNetConf: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPrimaryUDNConfig: &ovncnitypes.NetConf{ + NetConf: cnitypes.NetConf{Name: networkName}, + Topology: ovntypes.Layer2Topology, + NADName: GetNADName(namespaceName, attachmentName), + Role: ovntypes.NetworkRolePrimary, + }, + inputPodAnnotations: map[string]string{ + nadv1.NetworkAttachmentAnnot: GetNADName(namespaceName, "another-network"), + DefNetworkAnnotation: `[{"foo}`, + }, + enablePreconfiguredUDNAddresses: true, + expectedError: fmt.Errorf(`failed getting default-network annotation for pod "/test-pod": %w`, fmt.Errorf(`GetK8sPodDefaultNetwork: failed to parse CRD object: parsePodNetworkAnnotation: failed to parse pod Network Attachment Selection Annotation JSON format: unexpected end of JSON input`)), + }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { g := gomega.NewWithT(t) + + t.Cleanup(func() { + _ = config.PrepareTestConfig() + }) + + // Set custom network config based on test requirements + config.OVNKubernetesFeature.EnablePreconfiguredUDNAddresses = test.enablePreconfiguredUDNAddresses + if test.enablePreconfiguredUDNAddresses { + config.OVNKubernetesFeature.EnableMultiNetwork = true + config.OVNKubernetesFeature.EnableNetworkSegmentation = true + } + netInfo, err := NewNetInfo(test.inputNetConf) g.Expect(err).ToNot(gomega.HaveOccurred()) if test.inputNetConf.NADName != "" { @@ -1048,11 +1182,14 @@ func TestGetPodNADToNetworkMappingWithActiveNetwork(t *testing.T) { primaryUDNNetInfo, ) - if err != nil { + if test.expectedError != nil { + g.Expect(err).To(gomega.HaveOccurred(), "unexpected success operation, epecting error") g.Expect(err).To(gomega.MatchError(test.expectedError)) + } else { + g.Expect(err).ToNot(gomega.HaveOccurred()) + g.Expect(isAttachmentRequested).To(gomega.Equal(test.expectedIsAttachmentRequested)) + g.Expect(networkSelectionElements).To(gomega.Equal(test.expectedNetworkSelectionElements)) } - g.Expect(isAttachmentRequested).To(gomega.Equal(test.expectedIsAttachmentRequested)) - g.Expect(networkSelectionElements).To(gomega.Equal(test.expectedNetworkSelectionElements)) }) } } @@ -1261,10 +1398,10 @@ func TestNewNetInfo(t *testing.T) { config.IPv6Mode = test.ipv6Cluster g := gomega.NewWithT(t) _, err := NewNetInfo(inputNetConf) - if test.expectedError == nil { - g.Expect(err).ToNot(gomega.HaveOccurred()) + if test.expectedError != nil { + g.Expect(err).To(gomega.MatchError(test.expectedError), "should return an error for invalid network configuration") } else { - g.Expect(err).To(gomega.MatchError(test.expectedError.Error())) + g.Expect(err).NotTo(gomega.HaveOccurred(), "should not return an error for valid network configuration") } }) } From 532d9919bb2ea5dfe22b68f851804d902e162dda Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Mon, 23 Jun 2025 07:29:41 +0200 Subject: [PATCH 193/278] allocator: Allow static IP with ipam Signed-off-by: Enrique Llorente --- .../pkg/allocator/pod/pod_annotation.go | 33 ++- .../pkg/allocator/pod/pod_annotation_test.go | 201 +++++++++++++----- go-controller/pkg/util/pod_annotation.go | 2 +- 3 files changed, 178 insertions(+), 58 deletions(-) diff --git a/go-controller/pkg/allocator/pod/pod_annotation.go b/go-controller/pkg/allocator/pod/pod_annotation.go index 940952f7ff..cca6761845 100644 --- a/go-controller/pkg/allocator/pod/pod_annotation.go +++ b/go-controller/pkg/allocator/pod/pod_annotation.go @@ -207,6 +207,28 @@ func allocatePodAnnotationWithTunnelID( return pod, podAnnotation, nil } +// validateStaticIPRequest checks if a static IP request can be honored when IPAM is enabled for the given network. +func validateStaticIPRequest(netInfo util.NetInfo, podDesc string) error { + // Allow static IPs with IPAM only for primary networks with layer2 topology when EnablePreconfiguredUDNAddresses is enabled + // Feature gate integration: EnablePreconfiguredUDNAddresses controls static IP allocation with IPAM + if !util.IsPreconfiguredUDNAddressesEnabled() { + // Feature is disabled, reject static IPs with IPAM + return fmt.Errorf("cannot allocate a static IP request with IPAM for pod %s (custom network configuration disabled)", podDesc) + } + if !netInfo.IsPrimaryNetwork() { + // Static IP requests with IPAM are only supported on primary networks + return fmt.Errorf("cannot allocate a static IP request with IPAM for pod %s: only supported on primary networks", podDesc) + } + if netInfo.TopologyType() != types.Layer2Topology { + // Static IP requests with IPAM are only supported on layer2 topology networks. + // On other topologies, we cannot distinguish between already allocated IPs and + // IPs excluded from allocation, making it impossible to safely honor static IP + // requests when IPAM is enabled. + return fmt.Errorf("cannot allocate a static IP request with IPAM for pod %s: layer2 topology is required, but network has topology %q", podDesc, netInfo.TopologyType()) + } + return nil +} + // allocatePodAnnotationWithRollback allocates the PodAnnotation which includes // IPs, a mac address, routes, gateways and an ID. Returns the allocated pod // annotation and a pod with that annotation set. Returns a nil pod and the existing @@ -330,13 +352,11 @@ func allocatePodAnnotationWithRollback( } hasIPAMClaim = ipamClaim != nil && len(ipamClaim.Status.IPs) > 0 } + if hasIPAM && hasStaticIPRequest { - // for now we can't tell apart already allocated IPs from IPs excluded - // from allocation so we can't really honor static IP requests when - // there is IPAM as we don't really know if the requested IP should not - // be allocated or was already allocated by the same pod - err = fmt.Errorf("cannot allocate a static IP request with IPAM for pod %s", podDesc) - return + if err = validateStaticIPRequest(netInfo, podDesc); err != nil { + return + } } // we need to update the annotation if it is missing IPs or MAC @@ -348,6 +368,7 @@ func allocatePodAnnotationWithRollback( if hasIPRequest { tentative.IPs, err = util.ParseIPNets(network.IPRequest) if err != nil { + klog.Warningf("Failed parsing IPRequest %+v for pod %s: %v", network.IPRequest, podDesc, err) return } } else if hasIPAMClaim { diff --git a/go-controller/pkg/allocator/pod/pod_annotation_test.go b/go-controller/pkg/allocator/pod/pod_annotation_test.go index 7930bd0e11..a01a8f7bcd 100644 --- a/go-controller/pkg/allocator/pod/pod_annotation_test.go +++ b/go-controller/pkg/allocator/pod/pod_annotation_test.go @@ -109,25 +109,28 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { reallocate bool } tests := []struct { - name string - args args - ipam bool - idAllocation bool - persistentIPAllocation bool - role string - podAnnotation *util.PodAnnotation - invalidNetworkAnnotation bool - wantUpdatedPod bool - wantGeneratedMac bool - wantPodAnnotation *util.PodAnnotation - wantReleasedIPs []*net.IPNet - wantReleasedIPsOnRollback []*net.IPNet - wantReleaseID bool - wantRelasedIDOnRollback bool - wantErr bool - isSingleStackIPv4 bool - isSingleStackIPv6 bool - multiNetworkDisabled bool + name string + args args + netInfo util.NetInfo + nadName string + ipam bool + idAllocation bool + persistentIPAllocation bool + enablePreconfiguredUDNAddresses bool + role string + podAnnotation *util.PodAnnotation + invalidNetworkAnnotation bool + wantUpdatedPod bool + wantGeneratedMac bool + wantPodAnnotation *util.PodAnnotation + wantReleasedIPs []*net.IPNet + wantReleasedIPsOnRollback []*net.IPNet + wantReleaseID bool + wantRelasedIDOnRollback bool + wantErr bool + isSingleStackIPv4 bool + isSingleStackIPv6 bool + multiNetworkDisabled bool }{ { // on secondary L2 networks with no IPAM, we expect to generate a @@ -195,8 +198,9 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { { // on networks with IPAM, expect error if static IP request present // in the network selection annotation - name: "expect error, static ip request, IPAM", - ipam: true, + name: "expect error, static ip request, IPAM, non layer2", + netInfo: &util.DefaultNetInfo{}, + nadName: types.DefaultNetworkName, args: args{ network: &nadapi.NetworkSelectionElement{ IPRequest: []string{"192.168.0.3/24"}, @@ -540,9 +544,9 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { wantReleasedIPs: ovntest.MustParseIPNets("192.168.0.3/24"), }, { - // on networks with IPAM, honor a MAC request through the network + // on networks with IPAM, honor a IP and MAC request through the network // selection element - name: "expect requested MAC", + name: "expect requested MAC, IPAM", ipam: true, args: args{ network: &nadapi.NetworkSelectionElement{ @@ -575,6 +579,99 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { wantReleasedIPsOnRollback: ovntest.MustParseIPNets("192.168.0.3/24"), role: types.NetworkRolePrimary, // has to be primary network for default routes to be set }, + { + // on primary networks with IPAM and layer2 topology, expect success when EnablePreconfiguredUDNAddresses is enabled + name: "expect success, static IP and MAC with IPAM on primary network when EnablePreconfiguredUDNAddresses is enabled", + ipam: true, + enablePreconfiguredUDNAddresses: true, + role: types.NetworkRolePrimary, // has to be primary network for default routes to be set + persistentIPAllocation: true, + args: args{ + network: &nadapi.NetworkSelectionElement{ + MacRequest: requestedMAC, + IPRequest: []string{"192.168.0.101/24"}, + }, + ipAllocator: &ipAllocatorStub{ + nextIPs: ovntest.MustParseIPNets("192.168.0.3/24"), + }, + }, + wantUpdatedPod: true, + wantPodAnnotation: &util.PodAnnotation{ + IPs: ovntest.MustParseIPNets("192.168.0.101/24"), + MAC: requestedMACParsed, + Gateways: []net.IP{ovntest.MustParseIP("192.168.0.1").To4()}, + Routes: []util.PodRoute{ + { + Dest: ovntest.MustParseIPNet("100.65.0.0/16"), + NextHop: ovntest.MustParseIP("192.168.0.1").To4(), + }, + }, + Role: types.NetworkRolePrimary, + }, + wantReleasedIPsOnRollback: ovntest.MustParseIPNets("192.168.0.101/24"), + }, + { + // on primary networks with IPAM and layer2 topology, expect success when EnablePreconfiguredUDNAddresses is enabled + name: "expect success, just static IP with IPAM on primary network when EnablePreconfiguredUDNAddresses is enabled", + ipam: true, + enablePreconfiguredUDNAddresses: true, + persistentIPAllocation: true, + role: types.NetworkRolePrimary, + args: args{ + network: &nadapi.NetworkSelectionElement{ + IPRequest: []string{"192.168.0.101/24"}, + }, + ipAllocator: &ipAllocatorStub{ + nextIPs: ovntest.MustParseIPNets("192.168.0.101/24"), + }, + }, + wantUpdatedPod: true, + wantPodAnnotation: &util.PodAnnotation{ + IPs: ovntest.MustParseIPNets("192.168.0.101/24"), + MAC: util.IPAddrToHWAddr(ovntest.MustParseIPNets("192.168.0.101/24")[0].IP), + Gateways: []net.IP{ovntest.MustParseIP("192.168.0.1").To4()}, + Routes: []util.PodRoute{ + { + Dest: &net.IPNet{ + IP: ovntest.MustParseIP("100.65.0.0").To4(), + Mask: net.CIDRMask(16, 32), + }, + NextHop: ovntest.MustParseIP("192.168.0.1").To4(), + }, + }, + Role: types.NetworkRolePrimary, + }, + wantReleasedIPsOnRollback: ovntest.MustParseIPNets("192.168.0.101/24"), + }, + + { + // on networks with IPAM and layer2 topology, expect error when EnablePreconfiguredUDNAddresses is false + name: "expect error, static IP with IPAM on layer2 when EnablePreconfiguredUDNAddresses is false", + ipam: true, + role: types.NetworkRolePrimary, + persistentIPAllocation: true, + // enablePreconfiguredUDNAddresses defaults to false + args: args{ + network: &nadapi.NetworkSelectionElement{ + IPRequest: []string{"192.168.0.101/24"}, + }, + }, + wantErr: true, + }, + { + // with preconfigured UDN address feature enabled still continue failing with secondary layer2 with ipam + static IPs + name: "expect error, static IP with IPAM on secondary network when EnablePreconfiguredUDNAddresses is enabled", + ipam: true, + enablePreconfiguredUDNAddresses: true, + persistentIPAllocation: true, + args: args{ + network: &nadapi.NetworkSelectionElement{ + IPRequest: []string{"192.168.0.101/24"}, + }, + }, + role: types.NetworkRoleSecondary, + wantErr: true, + }, { // on networks with IPAM, expect error on an invalid network // selection element @@ -777,6 +874,7 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { config.OVNKubernetesFeature.EnableInterconnect = tt.idAllocation config.OVNKubernetesFeature.EnableMultiNetwork = !tt.multiNetworkDisabled config.OVNKubernetesFeature.EnableNetworkSegmentation = true + config.OVNKubernetesFeature.EnablePreconfiguredUDNAddresses = tt.enablePreconfiguredUDNAddresses config.IPv4Mode = true if tt.isSingleStackIPv6 { config.IPv4Mode = false @@ -785,32 +883,33 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { if tt.isSingleStackIPv4 { config.IPv6Mode = false } - var netInfo util.NetInfo - netInfo = &util.DefaultNetInfo{} - nadName := types.DefaultNetworkName - if !tt.ipam || tt.idAllocation || tt.persistentIPAllocation || tt.args.ipamClaim != nil { - nadName = util.GetNADName(network.Namespace, network.Name) - var subnets string - if tt.ipam { - subnets = "192.168.0.0/24,2001:db8::/64" - if tt.isSingleStackIPv4 { - subnets = "192.168.0.0/24" - } else if tt.isSingleStackIPv6 { - subnets = "2001:db8::/64" + if tt.netInfo == nil { + tt.netInfo = &util.DefaultNetInfo{} + tt.nadName = types.DefaultNetworkName + if !tt.ipam || tt.idAllocation || tt.persistentIPAllocation || tt.args.ipamClaim != nil { + tt.nadName = util.GetNADName(network.Namespace, network.Name) + var subnets string + if tt.ipam { + subnets = "192.168.0.0/24,2001:db8::/64" + if tt.isSingleStackIPv4 { + subnets = "192.168.0.0/24" + } else if tt.isSingleStackIPv6 { + subnets = "2001:db8::/64" + } + } + tt.netInfo, err = util.NewNetInfo(&ovncnitypes.NetConf{ + Topology: types.Layer2Topology, + NetConf: cnitypes.NetConf{ + Name: network.Name, + }, + NADName: tt.nadName, + Subnets: subnets, + AllowPersistentIPs: tt.persistentIPAllocation, + Role: tt.role, + }) + if err != nil { + t.Fatalf("failed to create NetInfo: %v", err) } - } - netInfo, err = util.NewNetInfo(&ovncnitypes.NetConf{ - Topology: types.Layer2Topology, - NetConf: cnitypes.NetConf{ - Name: network.Name, - }, - NADName: nadName, - Subnets: subnets, - AllowPersistentIPs: tt.persistentIPAllocation, - Role: tt.role, - }) - if err != nil { - t.Fatalf("failed to create NetInfo: %v", err) } } @@ -836,7 +935,7 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { }, } if tt.podAnnotation != nil { - pod.Annotations, err = util.MarshalPodAnnotation(nil, tt.podAnnotation, nadName) + pod.Annotations, err = util.MarshalPodAnnotation(nil, tt.podAnnotation, tt.nadName) if err != nil { t.Fatalf("failed to set pod annotations: %v", err) } @@ -862,7 +961,7 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { pod, podAnnotation, rollback, err := allocatePodAnnotationWithRollback( tt.args.ipAllocator, tt.args.idAllocator, - netInfo, + tt.netInfo, node, pod, network, @@ -887,7 +986,7 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { if tt.args.ipAllocator != nil { releasedIPs := tt.args.ipAllocator.(*ipAllocatorStub).releasedIPs - g.Expect(releasedIPs).To(gomega.Equal(tt.wantReleasedIPsOnRollback), "Release IP on rollback behaved unexpectedly") + g.Expect(releasedIPs).To(gomega.Equal(tt.wantReleasedIPsOnRollback), "Release IP on rollback behaved unexpectedly: %s", tt.netInfo.TopologyType()) } if tt.args.idAllocator != nil { diff --git a/go-controller/pkg/util/pod_annotation.go b/go-controller/pkg/util/pod_annotation.go index b5c46a804f..0dc9f6af8a 100644 --- a/go-controller/pkg/util/pod_annotation.go +++ b/go-controller/pkg/util/pod_annotation.go @@ -53,7 +53,7 @@ import ( const ( // OvnPodAnnotationName is the constant string representing the POD annotation key OvnPodAnnotationName = "k8s.ovn.org/pod-networks" - // DefNetworkAnnotation is the pod annotation for the cluster-wide default network + // DefNetworkAnnotation is the pod annotation for the cluster-wide active network DefNetworkAnnotation = "v1.multus-cni.io/default-network" // OvnUDNIPAMClaimName is used for workload owners to instruct OVN-K which // IPAMClaim will hold the allocation for the workload From 6b76f22e2719e91bf2f5a865dd47e505d322a819 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Tue, 1 Jul 2025 10:00:57 +0200 Subject: [PATCH 194/278] gh, actions: Enable custon net conf for net-seg and virt Signed-off-by: Enrique Llorente --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9d5d40eec..516dec2f43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -513,6 +513,7 @@ jobs: TRAFFIC_FLOW_TESTS: "${{ matrix.traffic-flow-tests }}" ENABLE_ROUTE_ADVERTISEMENTS: "${{ matrix.routeadvertisements != '' }}" ADVERTISE_DEFAULT_NETWORK: "${{ matrix.routeadvertisements == 'advertise-default' }}" + ENABLE_PRE_CONF_UDN_ADDR: "${{ ( matrix.target == 'network-segmentation' || matrix.target == 'kv-live-migration' ) && matrix.ic == 'ic-single-node-zones' }}" steps: - name: Install VRF kernel module From 5679396a1c2206803b3dc770881ef2c621eb851d Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Mon, 23 Jun 2025 14:54:06 +0200 Subject: [PATCH 195/278] e2e, kv: Add p-udn test for static ip and mac Signed-off-by: Enrique Llorente --- test/e2e/kubevirt.go | 68 +++++++++++++++++++++++++++++++++++----- test/e2e/kubevirt/pod.go | 17 ++++++++++ test/e2e/util.go | 24 ++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/test/e2e/kubevirt.go b/test/e2e/kubevirt.go index 67ab2e290a..19f628b464 100644 --- a/test/e2e/kubevirt.go +++ b/test/e2e/kubevirt.go @@ -1531,12 +1531,15 @@ fi cmd func() string } var ( - cudn *udnv1.ClusterUserDefinedNetwork - vm *kubevirtv1.VirtualMachine - vmi *kubevirtv1.VirtualMachineInstance - cidrIPv4 = "10.128.0.0/24" - cidrIPv6 = "2010:100:200::0/60" - restart = testCommand{ + cudn *udnv1.ClusterUserDefinedNetwork + vm *kubevirtv1.VirtualMachine + vmi *kubevirtv1.VirtualMachineInstance + cidrIPv4 = "10.128.0.0/24" + cidrIPv6 = "2010:100:200::0/60" + staticIPv4 = "10.128.0.101" + staticIPv6 = "2010:100:200::101" + staticMAC = "02:00:00:00:00:01" + restart = testCommand{ description: "restart", cmd: func() { By("Restarting vm") @@ -1619,6 +1622,29 @@ write_files: }, } + virtualMachineWithUDNAndStaticIPsAndMAC = resourceCommand{ + description: "VirtualMachine with interface binding for UDN and statics IPs and MAC", + cmd: func() string { + GinkgoHelper() + if !isPreConfiguredUdnAddressesEnabled() { + Skip("ENABLE_PRE_CONF_UDN_ADDR not configured") + } + + annotations, err := kubevirt.GenerateAddressesAnnotations("net1", filterIPs(fr.ClientSet, staticIPv4, staticIPv6)) + Expect(err).NotTo(HaveOccurred()) + + vm = fedoraWithTestToolingVM(nil /*labels*/, annotations, nil, /*nodeSelector*/ + kubevirtv1.NetworkSource{ + Pod: &kubevirtv1.PodNetwork{}, + }, userDataWithIperfServer, networkDataDualStack) + vm.Spec.Template.Spec.Domain.Devices.Interfaces[0].Bridge = nil + vm.Spec.Template.Spec.Domain.Devices.Interfaces[0].Binding = &kubevirtv1.PluginBinding{Name: "l2bridge"} + vm.Spec.Template.Spec.Domain.Devices.Interfaces[0].MacAddress = staticMAC + createVirtualMachine(vm) + return vm.Name + }, + } + virtualMachineInstance = resourceCommand{ description: "VirtualMachineInstance", cmd: func() string { @@ -1669,6 +1695,8 @@ write_files: topology udnv1.NetworkTopology role udnv1.NetworkRole ingress string + ipRequests []string + macRequest string } var ( containerNetwork = func(td testData) (infraapi.Network, error) { @@ -1817,6 +1845,12 @@ ip route add %[3]s via %[4]s step = by(vmi.Name, "Wait for addresses at the virtual machine") expectedNumberOfAddresses := len(dualCIDRs) expectedAddreses := virtualMachineAddressesFromStatus(vmi, expectedNumberOfAddresses) + if _, hasIPRequests := vmi.Annotations[kubevirt.AddressesAnnotation]; hasIPRequests { + Expect(expectedAddreses).To(ConsistOf(filterIPs(fr.ClientSet, staticIPv4, staticIPv6)), "expected addresses should be consistent with the static IPs") + } + if vmi.Spec.Domain.Devices.Interfaces[0].MacAddress != "" { + Expect(vmi.Spec.Domain.Devices.Interfaces[0].MacAddress).To(Equal(vmi.Status.Interfaces[0].MAC), "expected mac address should be consistent with the static MAC") + } expectedAddresesAtGuest := expectedAddreses testPodsIPs := podsMultusNetworkIPs(iperfServerTestPods, podNetworkStatusByNetConfigPredicate(namespace, cudn.Name, strings.ToLower(string(td.role)))) @@ -1988,6 +2022,25 @@ ip route add %[3]s via %[4]s topology: udnv1.NetworkTopologyLayer2, role: udnv1.NetworkRolePrimary, }), + Entry(nil, testData{ + resource: virtualMachineWithUDNAndStaticIPsAndMAC, + test: liveMigrate, + topology: udnv1.NetworkTopologyLayer2, + role: udnv1.NetworkRolePrimary, + }), + Entry(nil, testData{ + resource: virtualMachineWithUDNAndStaticIPsAndMAC, + test: restart, + topology: udnv1.NetworkTopologyLayer2, + role: udnv1.NetworkRolePrimary, + }), + Entry(nil, testData{ + resource: virtualMachineWithUDNAndStaticIPsAndMAC, + test: liveMigrate, + topology: udnv1.NetworkTopologyLayer2, + role: udnv1.NetworkRolePrimary, + ingress: "routed", + }), Entry(nil, testData{ resource: virtualMachineWithUDN, test: liveMigrate, @@ -2170,7 +2223,6 @@ ip route add %[3]s via %[4]s vmiIPv4 = "10.128.0.100/24" vmiIPv6 = "2010:100:200::100/60" vmiMAC = "0A:58:0A:80:00:64" - cidrs = []string{ipv4CIDR, ipv6CIDR} staticIPsNetworkData = func(ips []string) (string, error) { type Ethernet struct { Addresses []string `json:"addresses,omitempty"` @@ -2209,7 +2261,7 @@ chpasswd: { expire: False } selectedNodes = workerNodeList.Items Expect(selectedNodes).NotTo(BeEmpty()) - iperfServerTestPods, err = createIperfServerPods(selectedNodes, cudn.Name, cudn.Spec.Network.Localnet.Role, filterCIDRs(fr.ClientSet, cidrs...)) + iperfServerTestPods, err = createIperfServerPods(selectedNodes, cudn.Name, cudn.Spec.Network.Localnet.Role, filterCIDRs(fr.ClientSet, ipv4CIDR, ipv6CIDR)) Expect(err).NotTo(HaveOccurred()) networkData, err := staticIPsNetworkData(filterCIDRs(fr.ClientSet, vmiIPv4, vmiIPv6)) diff --git a/test/e2e/kubevirt/pod.go b/test/e2e/kubevirt/pod.go index 1293e1acc5..b6153e731a 100644 --- a/test/e2e/kubevirt/pod.go +++ b/test/e2e/kubevirt/pod.go @@ -1,6 +1,7 @@ package kubevirt import ( + "encoding/json" "fmt" infraapi "github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api" @@ -11,6 +12,10 @@ import ( kubevirtv1 "kubevirt.io/api/core/v1" ) +const ( + AddressesAnnotation = "network.kubevirt.io/addresses" +) + func GenerateFakeVirtLauncherPod(namespace, vmName string) *corev1.Pod { return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -48,3 +53,15 @@ kill -9 $pid } return nil } + +func GenerateAddressesAnnotations(networkName string, addresses []string) (map[string]string, error) { + staticIPs, err := json.Marshal(map[string][]string{ + networkName: addresses, + }) + if err != nil { + return nil, fmt.Errorf("failed to marshal static IPs: %w", err) + } + return map[string]string{ + AddressesAnnotation: string(staticIPs), + }, nil +} diff --git a/test/e2e/util.go b/test/e2e/util.go index d03559e79e..ff7cfe66d1 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework/debug" e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" @@ -1137,6 +1138,12 @@ func isCIDRIPFamilySupported(cs kubernetes.Interface, cidr string) bool { return (isIPv4Supported(cs) && !isIPv6) || (isIPv6Supported(cs) && isIPv6) } +func isIPFamilySupported(cs clientset.Interface, cidr string) bool { + ginkgo.GinkgoHelper() + isIPv6 := utilnet.IsIPv6String(cidr) + return (isIPv4Supported(cs) && !isIPv6) || (isIPv6Supported(cs) && isIPv6) +} + func isIPv4Supported(cs kubernetes.Interface) bool { v4, _ := getSupportedIPFamilies(cs) return v4 @@ -1147,6 +1154,17 @@ func isIPv6Supported(cs kubernetes.Interface) bool { return v6 } +func filterIPs(cs clientset.Interface, cidrs ...string) []string { + var supportedCIDRs []string + for _, cidr := range cidrs { + if !isIPFamilySupported(cs, cidr) { + continue + } + supportedCIDRs = append(supportedCIDRs, cidr) + } + return supportedCIDRs +} + func getSupportedIPFamilies(cs kubernetes.Interface) (bool, bool) { n, err := e2enode.GetRandomReadySchedulableNode(context.TODO(), cs) framework.ExpectNoError(err, "must fetch a Ready Node") @@ -1183,6 +1201,12 @@ func isLocalGWModeEnabled() bool { return present && val == "local" } +func isPreConfiguredUdnAddressesEnabled() bool { + ovnKubeNamespace := deploymentconfig.Get().OVNKubernetesNamespace() + val := getTemplateContainerEnv(ovnKubeNamespace, "daemonset/ovnkube-node", getNodeContainerName(), "OVN_PRE_CONF_UDN_ADDR_ENABLE") + return val == "true" +} + func singleNodePerZone() bool { if singleNodePerZoneResult == nil { args := []string{"get", "pods", "--selector=app=ovnkube-node", "-o", "jsonpath={.items[0].spec.containers[*].name}"} From d9f26e45cadd8172acf3a678b38c7cc936a32885 Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Thu, 19 Jun 2025 09:44:26 +0200 Subject: [PATCH 196/278] e2e: Add happy test Signed-off-by: Enrique Llorente --- ...segmentation_default_network_annotation.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 test/e2e/network_segmentation_default_network_annotation.go diff --git a/test/e2e/network_segmentation_default_network_annotation.go b/test/e2e/network_segmentation_default_network_annotation.go new file mode 100644 index 0000000000..11849186f5 --- /dev/null +++ b/test/e2e/network_segmentation_default_network_annotation.go @@ -0,0 +1,106 @@ +package e2e + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + nadapi "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" + + udnv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1" + udnclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1/apis/clientset/versioned" +) + +var _ = Describe("Network Segmentation: Default network multus annotation", func() { + var ( + f = wrappedTestFramework("default-network-annotation") + ) + f.SkipNamespaceCreation = true + + type testCase struct { + ips []string + mac string + } + DescribeTable("when added with static IP and MAC to a pod belonging to primary UDN", func(tc testCase) { + if !isPreConfiguredUdnAddressesEnabled() { + Skip("ENABLE_PRE_CONF_UDN_ADDR not configured") + } + tc.ips = filterCIDRs(f.ClientSet, tc.ips...) + namespace, err := f.CreateNamespace(context.TODO(), f.BaseName, map[string]string{ + "e2e-framework": f.BaseName, + RequiredUDNNamespaceLabel: "", + }) + Expect(err).NotTo(HaveOccurred(), "Should create namespace for test") + f.Namespace = namespace + + // Create the UDN client using the framework's config + udnClient, err := udnclientset.NewForConfig(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred(), "Should create UDN client") + + // Define the UserDefinedNetwork object + udn := &udnv1.UserDefinedNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "l2network", + Namespace: f.Namespace.Name, + }, + Spec: udnv1.UserDefinedNetworkSpec{ + Topology: udnv1.NetworkTopologyLayer2, + Layer2: &udnv1.Layer2Config{ + Role: udnv1.NetworkRolePrimary, + Subnets: filterDualStackCIDRs(f.ClientSet, []udnv1.CIDR{ + udnv1.CIDR("103.0.0.0/16"), + udnv1.CIDR("2014:100:200::0/60"), + }), + }, + }, + } + + // Create the resource in the generated namespace + By("Create a UserDefinedNetwork with Layer2 topology and wait for availability") + udn, err = udnClient.K8sV1().UserDefinedNetworks(f.Namespace.Name).Create(context.TODO(), udn, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "Should create UserDefinedNetwork") + Eventually(userDefinedNetworkReadyFunc(f.DynamicClient, udn.Namespace, udn.Name), 5*time.Second, time.Second).Should(Succeed()) + + // Create the Pod in the generated namespace + By("Create a Pod with the default network annotation and wait for readiness") + ips, err := json.Marshal(tc.ips) + Expect(err).NotTo(HaveOccurred(), "Should marshal IPs for annotation") + + // Define the Pod object with the specified annotation + By("Creating the pod with the default network annotation and wait for readiness") + pod := e2epod.NewAgnhostPod(f.Namespace.Name, "static-ip-mac-pod", nil, nil, nil) + pod.Annotations = map[string]string{ + "v1.multus-cni.io/default-network": fmt.Sprintf(`[{"name":"default", "namespace":"ovn-kubernetes", "mac":%q, "ips": %s}]`, tc.mac, string(ips)), + } + pod.Spec.Containers[0].Command = []string{"sleep", "infinity"} + pod = e2epod.NewPodClient(f).CreateSync(context.TODO(), pod) + + netStatus, err := podNetworkStatus(pod, func(status nadapi.NetworkStatus) bool { + return status.Default + }) + Expect(err).NotTo(HaveOccurred(), "Should get network status from pod") + Expect(netStatus).To(HaveLen(1), "Should have one network status for the default network") + var exposedIPs []string + + // Remove the CIDR from the IPs to expose only the IPs + for _, ip := range tc.ips { + exposedIPs = append(exposedIPs, strings.Split(ip, "/")[0]) + } + Expect(netStatus[0].IPs).To(ConsistOf(exposedIPs), "Should have the IPs specified in the default network annotation") + Expect(strings.ToLower(netStatus[0].Mac)).To(Equal(strings.ToLower(tc.mac)), "Should have the MAC specified in the default network annotation") + + }, + + Entry("should create the pod with the specified static IP and MAC address", testCase{ + ips: []string{"103.0.0.3/16", "2014:100:200::3/60"}, + mac: "02:A1:B2:C3:D4:E5", + }), + ) +}) From a3de8680a5a8f599a34abd710938720993c2774a Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Fri, 1 Aug 2025 11:30:27 +0200 Subject: [PATCH 197/278] allocator, pod: Validate consistency of ipRequest and ipamClaims IPs Ensure that a pod's requested IP (via ipRequest) matches the IP provisioned by ipamClaims in its status. Failure to match should prevent pod creation, as users are unable to alter a pod's requested IPs once set. Signed-off-by: Enrique Llorente --- .../pkg/allocator/pod/pod_annotation.go | 11 +++++++-- .../pkg/allocator/pod/pod_annotation_test.go | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/go-controller/pkg/allocator/pod/pod_annotation.go b/go-controller/pkg/allocator/pod/pod_annotation.go index cca6761845..31164d3a7c 100644 --- a/go-controller/pkg/allocator/pod/pod_annotation.go +++ b/go-controller/pkg/allocator/pod/pod_annotation.go @@ -208,7 +208,7 @@ func allocatePodAnnotationWithTunnelID( } // validateStaticIPRequest checks if a static IP request can be honored when IPAM is enabled for the given network. -func validateStaticIPRequest(netInfo util.NetInfo, podDesc string) error { +func validateStaticIPRequest(netInfo util.NetInfo, network *nadapi.NetworkSelectionElement, ipamClaim *ipamclaimsapi.IPAMClaim, podDesc string) error { // Allow static IPs with IPAM only for primary networks with layer2 topology when EnablePreconfiguredUDNAddresses is enabled // Feature gate integration: EnablePreconfiguredUDNAddresses controls static IP allocation with IPAM if !util.IsPreconfiguredUDNAddressesEnabled() { @@ -226,6 +226,13 @@ func validateStaticIPRequest(netInfo util.NetInfo, podDesc string) error { // requests when IPAM is enabled. return fmt.Errorf("cannot allocate a static IP request with IPAM for pod %s: layer2 topology is required, but network has topology %q", podDesc, netInfo.TopologyType()) } + if ipamClaim != nil && len(ipamClaim.Status.IPs) > 0 { + for _, ipRequest := range network.IPRequest { + if !util.IsItemInSlice(ipamClaim.Status.IPs, ipRequest) { + return fmt.Errorf("cannot allocate a static IP request with IPAM for pod %q: the pod references an ipam claim with IPs not containing the requested IP %q", podDesc, ipRequest) + } + } + } return nil } @@ -354,7 +361,7 @@ func allocatePodAnnotationWithRollback( } if hasIPAM && hasStaticIPRequest { - if err = validateStaticIPRequest(netInfo, podDesc); err != nil { + if err = validateStaticIPRequest(netInfo, network, ipamClaim, podDesc); err != nil { return } } diff --git a/go-controller/pkg/allocator/pod/pod_annotation_test.go b/go-controller/pkg/allocator/pod/pod_annotation_test.go index a01a8f7bcd..946ca79070 100644 --- a/go-controller/pkg/allocator/pod/pod_annotation_test.go +++ b/go-controller/pkg/allocator/pod/pod_annotation_test.go @@ -658,6 +658,29 @@ func Test_allocatePodAnnotationWithRollback(t *testing.T) { }, wantErr: true, }, + { + // on networks with IPAM and layer2 topology, expect error when IPAMClaims status IPs do not match requested IPs + name: "expect error, static IP with IPAM on layer2 when IPAMClaims status IPs do not match requested IPs", + ipam: true, + role: types.NetworkRolePrimary, + persistentIPAllocation: true, + enablePreconfiguredUDNAddresses: true, + args: args{ + network: &nadapi.NetworkSelectionElement{ + IPRequest: []string{"192.168.0.101/24"}, + IPAMClaimReference: "my-ipam-claim", + }, + ipamClaim: &ipamclaimsapi.IPAMClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ipam-claim", + }, + Status: ipamclaimsapi.IPAMClaimStatus{ + IPs: []string{"192.168.0.200/24"}, + }, + }, + }, + wantErr: true, + }, { // with preconfigured UDN address feature enabled still continue failing with secondary layer2 with ipam + static IPs name: "expect error, static IP with IPAM on secondary network when EnablePreconfiguredUDNAddresses is enabled", From d49c46c030a6dd54d879e2da9143815fc290c80a Mon Sep 17 00:00:00 2001 From: Enrique Llorente Date: Fri, 1 Aug 2025 13:17:49 +0200 Subject: [PATCH 198/278] gh, actions: Add multihoming + net-seg + static IPs to test workflow This commit adds the necessary configurations to the GitHub Actions workflow to test multihoming, network segmentation, and static IPs. It includes changes to the test.yml file to enable these tests. Signed-off-by: Enrique Llorente --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 516dec2f43..cd0a35e4c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -467,7 +467,7 @@ jobs: - {"target": "node-ip-mac-migration", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv6", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-disabled"} - {"target": "node-ip-mac-migration", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} - {"target": "compact-mode", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv4", "disable-snat-multiple-gws": "snatGW", "second-bridge": "1br", "ic": "ic-disabled"} - - {"target": "multi-homing", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} + - {"target": "multi-homing", "ha": "noHA", "gateway-mode": "local", "ipfamily": "dualstack", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones", "network-segmentation": "enable-network-segmentation"} - {"target": "multi-node-zones", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv4", "disable-snat-multiple-gws": "SnatGW", "second-bridge": "1br", "ic": "ic-multi-node-zones", "num-workers": "3", "num-nodes-per-zone": "2"} - {"target": "external-gateway", "ha": "noHA", "gateway-mode": "shared", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "2br", "ic": "ic-single-node-zones"} - {"target": "external-gateway", "ha": "noHA", "gateway-mode": "local", "ipfamily": "ipv4", "disable-snat-multiple-gws": "noSnatGW", "second-bridge": "1br", "ic": "ic-single-node-zones"} @@ -513,7 +513,7 @@ jobs: TRAFFIC_FLOW_TESTS: "${{ matrix.traffic-flow-tests }}" ENABLE_ROUTE_ADVERTISEMENTS: "${{ matrix.routeadvertisements != '' }}" ADVERTISE_DEFAULT_NETWORK: "${{ matrix.routeadvertisements == 'advertise-default' }}" - ENABLE_PRE_CONF_UDN_ADDR: "${{ ( matrix.target == 'network-segmentation' || matrix.target == 'kv-live-migration' ) && matrix.ic == 'ic-single-node-zones' }}" + ENABLE_PRE_CONF_UDN_ADDR: "${{ ( ( matrix.target == 'multi-homing' && matrix.network-segmentation == 'enable-network-segmentation' ) || matrix.target == 'kv-live-migration' ) && matrix.ic == 'ic-single-node-zones' }}" steps: - name: Install VRF kernel module From c0fad85f2931a070b318a9b02913046e138e4769 Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Tue, 29 Jul 2025 07:09:29 +0530 Subject: [PATCH 199/278] Remove NetworkUnavailable condition from node The NodeNetworkUnavailable condition can be set after ovn-k processed the node successfully so we cannot do the early exit without checking for this. Order of events: 1. Node is added without the NodeNetworkUnavailable condition 2. OVN-Kubernetes reconciles the node 3. Condition is added by an external entity 4. We never remove it because we exit early Hence this commit adds NodeNetworkUnavailable condition check for node update event and ensures h.clearInitialNodeNetworkUnavailableCondition method is called at least once to clear this condition. Signed-off-by: Periyasamy Palanisamy --- .../pkg/clustermanager/network_cluster_controller.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/go-controller/pkg/clustermanager/network_cluster_controller.go b/go-controller/pkg/clustermanager/network_cluster_controller.go index ef2ac665ae..f31e9ec8aa 100644 --- a/go-controller/pkg/clustermanager/network_cluster_controller.go +++ b/go-controller/pkg/clustermanager/network_cluster_controller.go @@ -16,6 +16,7 @@ import ( cache "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" + k8snodeutil "k8s.io/component-helpers/node/util" "k8s.io/klog/v2" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/id" @@ -576,7 +577,10 @@ func (h *networkClusterControllerEventHandler) UpdateResource(oldObj, newObj int // 1. we missed an add event (bug in kapi informer code) // 2. a user removed the annotation on the node // Either way to play it safe for now do a partial json unmarshal check - if !nodeFailed && util.NoHostSubnet(oldNode) == util.NoHostSubnet(newNode) && !h.ncc.nodeAllocator.NeedsNodeAllocation(newNode) { + _, nodeCondition := k8snodeutil.GetNodeCondition(&newNode.Status, corev1.NodeNetworkUnavailable) + nodeNetworkUnavailable := nodeCondition != nil && nodeCondition.Status == corev1.ConditionTrue + if !nodeFailed && util.NoHostSubnet(oldNode) == util.NoHostSubnet(newNode) && + !h.ncc.nodeAllocator.NeedsNodeAllocation(newNode) && !nodeNetworkUnavailable { // no other node updates would require us to reconcile again return nil } From eaf91a72d591bfbdc228b8490de8f404d378f779 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 1 Aug 2025 17:33:18 -0700 Subject: [PATCH 200/278] fix flow update error Recent commit fd5e79154 reintroduce the flow update error that was fixed by commit 00542735: when gateway accelerated interface is used. When gateway accelerated interface is used, we noticed the error messages 'gateway_shared_intf.go:392] Unable to get port list from bridge. ... failed to get list of ports on bridge "enp1s0f0v0":, stderr: "ovs-ofctl: enp1s0f0v0 is not a bridge or a socket\n" ...'. Signed-off-by: Yun Zhou --- go-controller/pkg/node/gateway_shared_intf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-controller/pkg/node/gateway_shared_intf.go b/go-controller/pkg/node/gateway_shared_intf.go index d60783144c..0fdfe229ff 100644 --- a/go-controller/pkg/node/gateway_shared_intf.go +++ b/go-controller/pkg/node/gateway_shared_intf.go @@ -368,7 +368,7 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI var ofPorts []string // don't get the ports unless we need to as it is a costly operation if (len(extParsedIPs) > 0 || len(ingParsedIPs) > 0) && add { - ofPorts, err = util.GetOpenFlowPorts(npw.gwBridge.GetGatewayIface(), false) + ofPorts, err = util.GetOpenFlowPorts(npw.gwBridge.GetBridgeName(), false) if err != nil { // in the odd case that getting all ports from the bridge should not work, // simply output to LOCAL (this should work well in the vast majority of cases, anyway) From 94944843516cbd21a2ed7ba1e2df59b6c8f95cfd Mon Sep 17 00:00:00 2001 From: Tullio Sebastiani Date: Mon, 4 Aug 2025 11:12:50 +0200 Subject: [PATCH 201/278] fixes fedora image build script Signed-off-by: Tullio Sebastiani --- dist/images/Dockerfile.fedora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/images/Dockerfile.fedora b/dist/images/Dockerfile.fedora index fc42191887..c60e89921d 100644 --- a/dist/images/Dockerfile.fedora +++ b/dist/images/Dockerfile.fedora @@ -93,7 +93,7 @@ RUN echo "Running on $BUILDPLATFORM, building for $TARGETPLATFORM" # Final stage RUN dnf install --best --refresh -y --setopt=tsflags=nodocs koji -RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] || [ -z "$TARGETPLATFORM"] ; then koji download-build $ovnver --arch=x86_64 ; \ +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] || [ -z "$TARGETPLATFORM" ] ; then koji download-build $ovnver --arch=x86_64 ; \ else koji download-build $ovnver --arch=aarch64 ; fi ###################################### From e18ed9aee3c1f56998c3f6ad4d147632d5da7a02 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Mon, 4 Aug 2025 11:15:10 -0400 Subject: [PATCH 202/278] docs: remove dead link to topology google document Fixes #5328 Signed-off-by: Ihar Hrachyshka --- docs/design/topology.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/design/topology.md b/docs/design/topology.md index 36dc78b3e1..d3353a4487 100644 --- a/docs/design/topology.md +++ b/docs/design/topology.md @@ -66,7 +66,3 @@ It is distributed across the nodes in the cluster and is responsible for routing traffic between the different zones. FIXME: This page is lazily written, there is so much more to do here. - -## References - -* https://docs.google.com/presentation/d/1BtkYAO30gI3v6ah2hS6XTGtt6JBHNRHh64vhGEtfLEM/edit#slide=id.gfb215b3717_0_3299 \ No newline at end of file From a5b979944f04ed4b56be753c6c24e0f635349fb3 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Tue, 5 Aug 2025 17:29:56 +0000 Subject: [PATCH 203/278] kind.sh: Don't build go-controller twice make fedora-image target already depends on go-controller. We are wasting cycles building the same thing twice. Signed-off-by: Ihar Hrachyshka --- contrib/kind.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/contrib/kind.sh b/contrib/kind.sh index af1c0f537c..8fbdbbebdf 100755 --- a/contrib/kind.sh +++ b/contrib/kind.sh @@ -858,9 +858,6 @@ build_ovn_image() { if [ "$OVN_IMAGE" == local ]; then set_ovn_image - # Build binaries - make -C ${DIR}/../go-controller - # Build image make -C ${DIR}/../dist/images IMAGE="${OVN_IMAGE}" OVN_REPO="${OVN_REPO}" OVN_GITREF="${OVN_GITREF}" OCI_BIN="${OCI_BIN}" fedora-image From bd7ebabf650bda47d47ec396d2f3dde09f78eec6 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Fri, 27 Jun 2025 08:37:01 +0200 Subject: [PATCH 204/278] Add UDN user facing docs Signed-off-by: Surya Seetharaman --- .../mirrored-endpointslices.md | 135 ---- .../images/KubeletHealthchecks-Part1.png | Bin 0 -> 36819 bytes .../images/KubeletHealthchecks-Part2.png | Bin 0 -> 56072 bytes .../images/L2DeepDive-2segments.png | Bin 0 -> 190174 bytes .../images/L3DeepDive.png | Bin 0 -> 212259 bytes .../images/Layer2VMMigration.png | Bin 0 -> 70570 bytes .../images/localnet-topology.png | Bin 0 -> 102401 bytes .../images/native-namespace-isolation.png | Bin 0 -> 296503 bytes .../images/overlappingpodIPs.png | Bin 0 -> 166785 bytes .../images/tenant-isolation-lighter.png | Bin 0 -> 345958 bytes .../user-defined-networks.md | 692 ++++++++++++++++++ mkdocs.yml | 2 + 12 files changed, 694 insertions(+), 135 deletions(-) delete mode 100644 docs/features/multiple-networks/mirrored-endpointslices.md create mode 100644 docs/features/user-defined-networks/images/KubeletHealthchecks-Part1.png create mode 100644 docs/features/user-defined-networks/images/KubeletHealthchecks-Part2.png create mode 100644 docs/features/user-defined-networks/images/L2DeepDive-2segments.png create mode 100644 docs/features/user-defined-networks/images/L3DeepDive.png create mode 100644 docs/features/user-defined-networks/images/Layer2VMMigration.png create mode 100644 docs/features/user-defined-networks/images/localnet-topology.png create mode 100644 docs/features/user-defined-networks/images/native-namespace-isolation.png create mode 100644 docs/features/user-defined-networks/images/overlappingpodIPs.png create mode 100644 docs/features/user-defined-networks/images/tenant-isolation-lighter.png create mode 100644 docs/features/user-defined-networks/user-defined-networks.md diff --git a/docs/features/multiple-networks/mirrored-endpointslices.md b/docs/features/multiple-networks/mirrored-endpointslices.md deleted file mode 100644 index 39f8615779..0000000000 --- a/docs/features/multiple-networks/mirrored-endpointslices.md +++ /dev/null @@ -1,135 +0,0 @@ -# EndpointSlices mirror controller for User-Defined Networks - -## Summary - -Pods that use a [user-defined network](https://github.com/trozet/enhancements/blob/multiple_networks/enhancements/network/user-defined-network-segmentation.md) as their primary network will still have the cluster default network IP in their status. For services this results in the EndpointSlices providing the IPs of the cluster default network in the Kubernetes API. To enable services support for primary user-defined networks, the EndpointSlices mirror controller was introduced to create custom EndpointSlices with user-defined network IP addresses extracted from OVN-Kubernetes annotations. - -## Implementation - -The introduced controller duplicates the default EndpointSlices, creating new copies that include IP addresses from primary user-defined network. It bypasses EndpointSlices in namespaces that do not have a user-defined primary network. The controller lacks specific logic for selecting endpoints, it only replicates those generated by the default controller and replaces the IP addresses. For host-networked pods, the controller retains the same IP addresses as the default controller. Custom EndpointSlices not created by the default controller are not processed. - -The default EndpointSlices controller creates objects that contain the following labels: - -- `endpointslice.kubernetes.io/managed-by:endpointslice-controller.k8s.io` - Indicates that the EndpointSlice is managed by the default Kubernetes EndpointSlice controller. -- `kubernetes.io/service-name:` - The service that this EndpointSlice belongs to, used by the default network service controller. - -The EndpointSlices mirror controller uses a separate set of labels: - -- `endpointslice.kubernetes.io/managed-by:endpointslice-mirror-controller.k8s.ovn.org` - Indicates that the EndpointSlice is managed by the mirror controller. -- `k8s.ovn.org/service-name:` - The service that this mirrored EndpointSlice belongs to, used by the user-defined network service controller. Note that the label key is different from the default EndpointSlice. -- `k8s.ovn.org/source-endpointslice-version:` - The last reconciled resource version from the default EndpointSlice. - -and annotations (Label values have a length limit of 63 characters): -- `k8s.ovn.org/endpointslice-network:` - The user-defined network that the IP addresses in the mirrored EndpointSlice belong to. -- `k8s.ovn.org/source-endpointslice:` - The name of the default EndpointSlice that was the source of the mirrored EndpointSlice. - - -### Example - -With the following NetworkAttachmentDefinition: - -```yaml -apiVersion: k8s.cni.cncf.io/v1 -kind: NetworkAttachmentDefinition -metadata: - name: l3-network - namespace: nad-l3 -spec: - config: |2 - { - "cniVersion": "1.0.0", - "name": "l3-network", - "type": "ovn-k8s-cni-overlay", - "topology":"layer3", - "subnets": "10.128.0.0/16/24", - "mtu": 1300, - "netAttachDefName": "nad-l3/l3-network", - "role": "primary" - } -``` - -We can observe the following EndpointSlices created for a one-replica deployment exposed through a `sample-deployment` service: - - - - - - - - - -
Default EndpointSliceMirrored EndpointSlice
- -```yaml -kind: EndpointSlice -apiVersion: discovery.k8s.io/v1 -metadata: - name: sample-deployment-rkk4n - generateName: sample-deployment- - generation: 1 - labels: - app: l3pod - endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io - kubernetes.io/service-name: sample-deployment - name: sample-deployment-rkk4n - namespace: nad-l3 - resourceVersion: "31533" -addressType: IPv4 -endpoints: -- addresses: - - 10.244.1.17 - conditions: - ready: true - serving: true - terminating: false - nodeName: ovn-worker - targetRef: - kind: Pod - name: sample-deployment-6b64bd4868-7ftt6 - namespace: nad-l3 - uid: 6eb5d05c-cff4-467d-bc1b-890443750463 -ports: -- name: "" - port: 80 - protocol: TCP -``` - - - -```yaml -kind: EndpointSlice -apiVersion: discovery.k8s.io/v1 -metadata: - name: l3-network-sample-deployment-hgkmw - generateName: l3-network-sample-deployment- - labels: - endpointslice.kubernetes.io/managed-by: endpointslice-mirror-controller.k8s.ovn.org - k8s.ovn.org/service-name: sample-deployment - k8s.ovn.org/source-endpointslice-version: "31533" - annotations: - k8s.ovn.org/endpointslice-network: l3-network - k8s.ovn.org/source-endpointslice: sample-deployment-rkk4n - namespace: nad-l3 - resourceVersion: "31535" -addressType: IPv4 -endpoints: -- addresses: - - 10.128.1.3 - conditions: - ready: true - serving: true - terminating: false - nodeName: ovn-worker - targetRef: - kind: Pod - name: sample-deployment-6b64bd4868-7ftt6 - namespace: nad-l3 - uid: 6eb5d05c-cff4-467d-bc1b-890443750463 -ports: -- name: "" - port: 80 - protocol: TCP - -``` - -
diff --git a/docs/features/user-defined-networks/images/KubeletHealthchecks-Part1.png b/docs/features/user-defined-networks/images/KubeletHealthchecks-Part1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ca1233bc74da2f95c670ee4de43ce75b178cc8d GIT binary patch literal 36819 zcmc$GWmr{P*EWixG}0TSQM$WD8l<~xD=m$Lgd&}rR!X|01*AmyKCh{6B=mk*+_QNJ)JQfM0)lw!ZxE2n%xkF_460hLjTV`eSJZ)r1)I`V+Z} z(+7{+t^EoQx5I>8KgVaJ@Be;N87*kt+Y=R+>kGcKgQv)-Lu2SvUoCciEaO4Cygb{I zPvrLV^J_!Ceoi1U{|`?UJc!%gVps^=b#JjNfz8xmsoRE?5T8khn&x_00>39C-uR_=!Jv$zxsi}!dGL(?h zyw@L{`10b&(B`-IjiA1_KC=48^MpcNa=1v}YA|0tMW|c)j4C z8@Fv-XeQ=)4Q-2h36G?pygJ+%moc**uM8u=e9Rk3uTEbmK2q=T2CTskp1|*H*`374 zVo(PU!6XxA)UV-edjz`@xjErKbAenFiBZoeGqlTJ>GT9XqLPiFZEBPXf2{q@&N%P= z&%VA=jY6ICS|I#F-KwGN%N&d7ozsK0^OHTrL~i-S8q-z`U0q!jjvt6Odh!;n)ZarD zTMGK(Bl|+MqVUW#IC5tfm!WKIFE1|$JM_?wJ5x>j2ba$%P zsmy$Y3)PKgP~F_b5DHG>vtp|7n3~DPScOG@w&&g=`bBrtla8{N9hqp)p%XREA^RWV z
>SzIMso=)6w^#gLt?>}<%jiNrE!DkRG8Z%sFxY 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 DMSW7| literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/images/L2DeepDive-2segments.png b/docs/features/user-defined-networks/images/L2DeepDive-2segments.png new file mode 100644 index 0000000000000000000000000000000000000000..b04dda3f11cce9018782b22a589f97afbad01385 GIT binary patch literal 190174 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

Q=X#h+1O7Y8jQnO4WyErpvD<| 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%0A{ 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%0DdV6d~j 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

369K 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�}|2Zu*>i`m6ed6rC|>)~2x6p{y$3 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 ON@$MY;%RS=F(%LR9|KM7rC$WH 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 CQY^** literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/images/localnet-topology.png b/docs/features/user-defined-networks/images/localnet-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..9d3782ca51f0554c63f67883bf055de7a89891ea GIT binary patch literal 102401 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%0A4%>Kxl>0^$am4Br2dyX?i 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 literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/images/native-namespace-isolation.png b/docs/features/user-defined-networks/images/native-namespace-isolation.png new file mode 100644 index 0000000000000000000000000000000000000000..adeb18d565e87e431f3f0d8030df6483d1531619 GIT binary patch literal 296503 zcmeEt^;cAF8}1$$Wat?{K$@YYmCg|c5Redq2I-KNZlnefkyctIM39y)Ns*N9RJyx4 z+wc9(TE6SVA8^*;7uLX@XJ$Y9j_baz>j~FTQzXKt#s>g^Na;RW698~l005c}y8-^C z{zva2_zUtvQ&AQu?4?}=07gIweebb{;YJ#s&*S04jy})3c)?H5EO_rk0;-sFZ^G z?YqUk%F?Z8vB&l<_=OCEbmsQVi|xx9k-OWMTh+^q_YqjKC>RXF#0U)zfW!ayFKSW& zH_zyS_y4E=|MgA)+25u5e_QLHYcpwJ7}P9Lf40j1^@IO&F}R}C|6lQczfb?~?PG2= zE=7XO9sEs4&X7zrb8Rq81r5(V=s`U;zeldg{MO5DB6 z^flyvNh1UGXBng`+Y%Pz{IvmUIGKx(9Z}$I|Kay+#eaQ&UdElNt=>DQrSYme(0G!!1Wy^B8T=+9JTv~92#|xkm(f7d{=RO0 z?64$@lE`9kYNl^G`#faY@d(d&_V zh0I3Fi;rO;vK~T!;q4jz`o*Pqoxf^ZrV5Nv5-QA{T;{bTQ=28T+A0z_;3>o&8#)-U z0tM#a3Fqhb+&BOA1oz%G5kvJ=QGHDP_(b?m+i$CnrS)O=4v%@2qxzH#D~VS8=>dHl z;E^+q(gyy29?*XSV1o=wjx|L|JpLp}%6+Nb*QDK3xN{*QH@g3gT1BTc4kQ=S)Fess zfl%>ZJ0k+6M(pTdc{Nw;cJ>?+J)a9CD423dFna1;fHHd0aBzol(=y<3veA8W!GBe5 zsSIG65_!@4JKs{duA7$6V)r7sBw1C9Z0;1`=kAye|Mxbg-+P9Oc>MhS@vxTKrF$Qh zv)jiFC#=i*-@|2s0Y*U6*hbDq?XWO#2T}f(hm;U6%x1InPC9nd_g;8j1 zZsy`HfPVnTnCKX2<_W275ivNM?c3;fRMwmhLUkSrB>~@(Aam$I*1wl#gzCeF5Z`=_ z31=zuCEt&xxoir6-$!f zaU7r9;WU{C7nkRKTFKCo5%N_8gxiAuZmOtsc-8ZXJxEBcdj>Ju64^uuh%#orum6Nd zb8lPI*}h@skRkBsX^GD(oC!Y2&W5kWz@3Nx$_{kcVi{=5>a_>-PI{e0;= zWAC%Z2~QuT1(y)R=t~0=4XeFI%F@bNd;S&bUhqI~c3vr_toe6nM`D0>p!5AUI&Afl-{Q3eD{pbi zb?a^QxyJ0j(hmjmfOF(~RKNgI|K#Q&N}7}icnJ&=3EM@XF^niYKmqR)c*88L*PTx9 zg`Txsgihv;{Hpqx`mRqe(C=r~T7KZ1#f8~?8D}l913?!g*RQ?GEfZk0`wM?CLcJ&h z+v4aYI@!E=fz;XXJ&p*z{bG#kjAxI4wIYb?(0#sD5hJ$Ys%bng} zs>cgQOv1@Zy6Ijl|xH!P)VrBrAJ z9{ip3nUrMOJ_{|_n>jC4fM>)=x|H9KiMpEOigYCw)3_&(e^|7QN(mu^HX#7!2LC+* zAYHLl(XKf=<+i#P50GLa`>(}P@CX~-POCKTr^p9bVl_G43l>%kumH~dF6THKgK@0f z_sMcdB@T@P{z*p-NnN8P_YTWp9!-&6^K=H9^Jl);6 zq7ce}z?N^smBF--6O^N~!U=MWOTL{nfmaQti9}@tE3<&3OX$!DV!?g6FnPC2Xt>H(CFvDa@d5Z2vGo1yB(J zAwZMF7Hx(H!K9VFeY$~>Q6txIXyN)spacn^z}6y4&N8_P4d?-O;x*TPj0nfvH$!^A z#AXV61ZRve6qH+y*OX$NjbgFmBaN>5sPudWITu>7Bu(?7`c?;e#!EwX9}`aj$5DKk!NWUO`Y-lP}m-wu_5f> z3HxKk_c&wwQgNoms_2ursLM#dhPk{|o>OAocJ_bD>^C?Djr5mojVvv0SAtx_iNhLq zw`#YySTE4X7(KXCLyCdwpu#ZtlRfg&HBAK5=lVQ1-sGqBuHMZkg+>*$Os~nslo>AK z$NJzCyc!oYK>LgJ!LWJb#c1_{65dHC~Y?9Lpy(5$x54iVYNVWaX z%Pvw4)dDm8uP96H_%?$BC*h~}Ze}gEW+CHsLc(7g5=wE4ZOv;hx;e`e7fv}7Kr9Ykr10thZYSl~` zoUXMf|GpD*+z`0~ZV2sL$fvKuA-uKDhpVJPcd)HApZr;-p9XdUkEqYlp*#-FFs5p! z$-7yroH^>6R)63>Cv-o0ozE)S&Dp@fp!Qchv*K&X?ut#f{U0APjBaO(=9$(m1l95d zN>fKk%cCEaQE7cpa^9rxs;Dm~!}7y>tg09b>eIlh86veu-G=@d6h=n-H(9D8-B~XJ=EcKX z4KT+OJqInS?rP&}m1#p{f45UO7OvrO>rV009bX_-zTUkUgZ?7|DGYjDkbABnrJ^?XYLvaZk!e(U?bn z3Q=9(nN*>!QrnLV?Ea`*M5)hsV%?lBH`%{*eM);w@$awB360=Nm&WS6l45+-2k#dD z(k5=cm^Ns7tD?*L&>pdpl-kHr-m}ujCzgBujt*IeH=h`1DYHNBC3Bel`k$hCV2C9h zY|{uBL~*;D}OXe zh%Bw4b%A;cgDl5m>E-Id(^j>j+u1j{wvbIl7I!#VPAo2)IUBpPqK5v3VBlExP~{?{ zQ&r?%cgV=M_8UV5duMvNjY)hiwep!o$&W8_8LL`Jpt2qx@;=Ru{{b*z8E2aRMXz2W z5C+*22LR)dZcIhu9F)${kKH7k{a8=4JpkuSD?wASqWR!kJs0%MVzMIAv}6pofl}u- zejtE1C1iIR&`|4k9TgzGI9On;a}=CElKxJFm-2wQX0Ln`!2p zoBskW;_8_PHMu0L<=64=MJ@@LU>W53lE&`Kiv6Ah2E6@Rs=97CaGs;PF6gQj{jW8$ z3q9xWSzZ5w7|w4%_};(8{6PF<`@o4gF$Gq(WtZHthw6MtSHf5DfiY}g5kW@(S>B#Y zPg`5m}#3Yu9isS^P5JEdp<0>3-0mIoqW|G zb|YK@4QIvE5VpgAp_)7Ff53;5&TbK;Y5A_#he&gx5>?A@*p%Au+^K9Md2g=7DFzuG z!&OM{qO3I@pUuzOHs<1Oy>K*h?>bVC%7R{gUvam7o_P!s%QIBT5ZKJZf!<2IlP0KA*F* ze~ZwF*t@*y%U%~hzE1i@Wr!0Xubd}?g<4Rt|MfkFnWw9U8Zo};$N~fdC`i*HM%$XI zJ=CneCH7F72=FSU@3Ry`Oxq{3O&)Q7REVOspFGM&yI9=8JLCuE9*L&FV-_JYR#feG z07enh#g1scu~PFcF^q>aIEe(zcb+j|0|c0&(t6Rxj(E+B>5-=&ep=xnF6jX^YoyZS^}+irdtzu8 zRl!@tVIQ{}&UyXQFJK3DG^8zIB-^u<4zon)){N-#wu;S!op>4PY*R0Pbf8_Lt8u52Ay$dJUyaV=`(^b#q;=e#Cvx`3=C21Qci>%R^df zS6`xQH2*btqhL_k%q){OSHOb?u<|2BHf^tS<@$80PwS7YY6ki_c^=+{I=);EhB3UW zKYP{3l$r0bWUY7`7D)eqkveKd0d0rJ9rx&z3em@eq^LS&QEoMl;QnN2n!GfT_tTST$USnm?!zy;&;O!X3qt=+JPU+pqI{)`W6}pdw>&uX)O+aI< zop0;+7r2u`jVhzT=r~u3d!eW5^bZPjon*Dn!v50(iik%zXTR7&KXwQqq|B3-T(%?` zaJ*)UMqGV!Qz(5^R8+3d&b(gdX2hWS4qXlWMXM2UiH&xXgW%JOFq>|~Y6{^cxQW57 zhaEJ?;A@to;KAWf6J(AUv33=*6yMX~Fo|!JF85BYJh{tIL;`|zQ66$H3gE^W)hsbo zvbMo_k4$<^`Q~3;9Yh{*j=fV`!bMyYNjL*58+4Sm<9uB&uwVm^w3&a+*EG`De zZK_eTs6%%+gCV!Zu>Ok!qo{?}9wnOB|LU)}1>pNkTn1^b4Y;@l={7!|jJcnMg>X3Z zbZ{CJ&mQ{pn5L$1-@|kNdvqA9$nU5zl{8_0+NG`ABF+CoQ`bF)6|%Ucx!+;$d~C9|$r!out*-*CSBd`$1g8M|4;!?Yx{* z!kIGH>GaD|>*=@FEKWwRhmFc2PTbSgD?D$K39Xm^>1V4?9u^Wn{@&w2eFR5s;Sz_+ zM{_{5S)Jk2{I4@TcJ>+H+K(rQ6LG0R?{+>34ss;1>d(=ZF>!9IccSO-`s}w|a!pXM zgJ)PD6{AP=bahGJ+hTEa#cSS`nAqJ*bzM20RnBt@-;I6A?Vaqd6rRC&Qe;e(u{Ne7 zNPS6GIjjM&DLAp3qb?C2-WO_6p%t zXiW7)Nza}gi?$;QVw^~_lbqdWDJ4YE`TJv5YICfw z;?H|T0@L>&Y>brzy%2LG4lc(*EK`O4WIHlP_A7q;xfqU-f~E>Tb_1KRZR6MHqkDPL zykskMIcE&nL!EwGBp{Nm32OSVm`MLxc(0Q)*U|n<%h{(AqsH&U>aKl3{yDbb$ekMc z79+9!#BCzcb!tf>=lsXbMe_&$4eX|ddyJ&v7cfNQvS~$XSH!I4Y;>5u4>s@=yJ=Q@ z38M1#IcB}5S)$I81(Bk1tx1oh8!TS0O) zw^Og9W;g#(KyN}(ops5kkoWCc(vM#njq;`L4Q~POUi_!wK+k@44c0SurSbx0*5 zp+}xrslSFw3z}kCY?9r43=HVl745h>(U}e}79Im0w-92Szr)b2yXL334F)T*BcT|N zQe&zO*6B0;Z%nE+-9gghn@^PUydSj}x2m=tILSTX%C>N-_mI3S6Lb2cLUU!Zyv_{Y zy^mb*Sa+bLprEh!yYzZJ+(+V^p@>#Xrnz5Ycm7J7`>0R9z{t67lwEGpOnhc&hu=sZ zJ$eDqw^wpzhl;jMsoSly_E(md6B2~TwsnU2`tiGScfvI`jCHj7)$C2Vry;#1b+w)f{eF9kjjCl@KtEy+{ct ztj{bE8T;*G2^Ej(>YOsyy_%WWx=c8-6)eV|CgTPAzC>BK{dfe!2;HpgGk+=X9#Yd; z{fCZrVFl8ERV=`EHZ~^Sv2T&Nhdj`Pov^&Q_sWT4-1B%wAYBq+V3uql{_U;%-m$Xm z=XG&!>Zy49PaF51QMG626(uAlDm2gUh*z>CcGhh0H0`d7rH1N#ASQNdo7u5H99v|J zK~+{&d1xCOGxf=AQt}|3OAXY<#6#+*=yn?O;*~wPL0W>3(PZ*sVCcF6tN_(p7zFbV z5NXJP>19Qrl(jBS(Bngmx!!WQ*P3xF^jo-_M50;|1I@vFNl7xsMCn@_vRyzz5s~I7)7%Mq*jIZ2Q$kwD}N~x|} zK?T*mD?ql?=v1`gQ}cDf{}&m zwD>{Mv)NpmlD*Z<@5AWD9gnxBxfL?*p*LNnD${}` zW@&hHO!m8`h1{%cWu!n{CS&XuPDQ6}z*^4?M#Z-ZyRsxR-I5y!t+5KSA&4=E5u7YCma{)aw!cSL58TocAWdI9G(nkl~Do2n*AfdhRc!F)O^HqQ(yqweYC% z&SjPExc9|rZ8|mbi z`GS*^@khOuNfsHJ>D7C)1@U|MbPKp?V3vuoYPia}lIsSLMIXDb!(#j-y@w0xrCY@4 z^l!AQEEh?J+v=_kLu+@bibuzO)%-Km|B?v|fO@4{)y8G`r5kw1pd8rgboTtenFq^e zrr^cv8PC&Z_T@o0;pylo{y!jmaU7zW&U-igMw43Jw!6n^|P zK9MdD&LkqDV=6>UFSrq3Tt6LWYOYB(P(&$AuQ}CyL+jgv_;@fw>S{0{EI~6vWrQZ> z)sw0ucL>4nP~!)SUZ(A@RtJZMT9GA5#}XZU7cyO?epMh)e@mulFnSFEGF+=>mYfRK z&qx+?Z>6>-C&g=aluy8%FF*WTAw+(5qi!_Ui5WVTN5*IZpHQu{ZKDD592{I+5(a~U zxjkdi37Y3l^;vJOw>|Dmm<}F{NT|Eip)1U$=jtOq-5t| z-DB$Q)J8~Cwf$zirq5nLbj_Yrm&}38&fV`WuJGNWP-7pa>93jKd@epIsM_E@84(>7 zqwm%4fazGJP2;(1%?_=JMS=Lk0rIVH>O70?ea_PT2Ctt4ICSXSUQ3(FuTiLNmy#8Uk*!dwewMz8r;Tyqh87D<70tfw%8121r=1NlX}~4h z;p$feD;x7d5nYZhf>MbrTt6&qCM+Kqa@nnEKA_0(T3-Rr*D z@s<@Y3`T>#%N1*K{-(;|biD-YobMFDWcUhnP;ip0vm)tFXMSM>EcB|{m**3~5TY;vua#e}1?f%Hl|apdxF zH##_*-juQ5(WK=|T$=Dy?F;;Y%Ky?`aIAay2fM{Zbo=qu;)e&wzy>wW1Yv)Dl4>He zx3ZZkc=5gYVln*h>#LqGKxG`XMSo8k2vw!x9XyHq%m97)LZAJmsCVz@TgR@$X4wo( z{bYDhp8oZk-recJ(C*z9_E{(h()QrU^i9hRV%H<>L|4bpNhf0^rUc;BeZ_}<;4ys8 zEpj{C?`KJ&QJ`Nd1`!r4`(8LYn%M0@HhEZrvHBa-sQT#PI>1=s8JBaSESj}mGr3BX zaWZ$|@sjljeasE=bs{t=h+Wzie$sH`M=C6&)#I${_deK2d-St+T=*+<&gfffiF5Un z{q)79_n1q1BsW>^HW%d0_@5DfRhU82fiRB|x`%1{prlfSZuXW8H3QRI03Wh{yHZw3 zzQ-RjrQSr^%qKD}+_rk4OaCR9@V~?T-n)P#RNr=ba*h-IhZ6LGJV3Q_Cj&>BQ#v7) z!?3SV>VD>Os*`<5F(p^_Nt)UpWkwn={_)UMdkjSK^ERr7`icMg!Ld*B=Hsuau!Bf8 zDM?yR{XX5$u9caH;X-L22=^G*J@Z@(mZ%UEA2U1)W%Tr^u)?6A+U=A=!BSCcosl&# z3m^!Cj6%%@yaRAD??cB6PlZM61vdKgK2mnNxN^%bC@8o;DEL~vcV^x%BHMZ*xTdBi zNt5gR=1SL(AFqc5qvT^;em}_`imBF?F&Hg^0_+IuB+VZ}ukVnIw1hIa9pBC_+j2Ywr-h;>%I37?fHf{TFsTz54U ze3zG!&1~{$w?z9D8DC|BEza3VPMd%DO09C9V%ph(S0@BOtX$5tY8H)>X**xV_6vWM zxt^)Sz|ljVnfDd1wYBx>6JK9n+06S( zd}p_TZXh5iZhst&?%T08%zlhadL+5sZ{_Cz1Uy2-G!mR1@Gf(ku!qa#s)SHw-Vura#*UC^LI|0Himm-vme(QXg6X>$&7UveNT5;$yDGJ z5~5tEj8f=hMJo2WqG_Ll%8@lhaG{2)55(Ioa7_6zVSM*Z4 z4%XJj#+9bc`{oz4&nJ%zoJvj!qpRyQG&CIBCARCzvQ6vFq!}slE*oZN-^VX3U)G~_ z3`Qv-8wsdwU*LomNe{+|iAzzc+80yh&?#F+WbYSpw|8U?8pQ$IzlteA{03Wn z$)w(Ev0;#QtaECn;qr^fv!2kQOM5vmMJ5jtginzsmb5cjPTZ-{k^3ZV^~Ew98l1&K zj%$)+f$4TDH4THSSIi&!-sPM+J~sl(CbIv$dOGVhe5D>5QB-t_Pr74~uQzTT=0-B> z(`9*5%bwe|O;WXek2?TISLYNx!{jy|l6eTqcf0nrpGx{AOc?=$3!6U$>y@5t|Jc*y3kdk$!mN`HTzG zp~m`j;b<%)u#$>-+9SvF%Y^)kDh6^D2W-ILc z@TNjAdW$6eeD88PFD+;%zO5TeG`PvFt*zZrTl%nm77onS7{~QcQh4XmgnpaR2w&(3 zlBS6K0xF?$B-Qjn={=n<2IZhLC*rXA zE*B?xRWYn`iG;YEc|D>ab_QYs4Yj7T54RJ7QIlnZ7g@(X;w*KZTemZF1a&{hizO~MvpoI9ZU>AdBRiC4oE$!q1TYj?v^O~C3$I6$biy;F81Ag<4Z$(5W zrL??Vj+hXtt8Yy?sH1urKPcXL);+CZYP#6elOnSH>Oa&-hKt(=H8r*WBH?)JQoFL^ zV#g_P$Uik}H8ck`kn`~h10sOkjDKsEMdSgg>Pvm>N&z!ue>AnwZ5thm>%tr^2CHP@ z4~2z=QQ4zq`LKgqQ*@x(e4v1}%!ii{f73hNBZSvk4No6b->NnqG_{gsn%)aIEqiWR zK_|5qwa)#56zd)WrkQzsA6D~luskl5n6AcYedNoAajBNiI`Ipeq@l7}sF!rF3c5&Q zXDMtn=uzA65iccUucsYJFTsla2)Te}4>{J<{*-$^4X$QrlY8CrVJZ2CkqRGuS`(sA#cxV~=;}y3{T&m{mDC}{Jzlob zOrD-vEV$WT5JxG^@Sc~0<1Gz-$W|{}9#yPq&TSuu;vEg4-F9kz6)gazJ#}x`&$FZ!=5IK5eYLjaMVkULx!(z!X&hIgp7yO0184wg z;TJU$PG$%F0+O|1!3lO+`{YiXZ zZlEgDwh|jKa(p^M%$xQOBX?s$WiyjV!MNb#;!4S~p4jL8G?r35UgZAZ(WAk@6k$6P zKAP7lVgnU+6wZSQ6}SzAl8YToA0CZG;U;Js=lp21 z?P=y}X=M7_SpIm11)Y=ilYs6HDX}S7#>Kq$8#Qyj-Md}5zaF2fCYmSMPgU(+>VL^# zP~Q|Jv!|?JB@Len`dn-3ygypr0v{hL6iUdfRc5f?=tsB>pVb$ll z*Uk#$3^Ai=9jMwPIgj%#F*HY+%V#n7gQFX2={?y$=&24~cxftvT%X~O_fq@0&66+CbbDPH%T#)51g1;2kzrSZI%m?-|dI_Rd#Os_v{Cq1*eGG zhU!s;Q&Qi~6}=pB6VqGOfE}=4#oYgOPfe4d^(|ueJk+>c!c`0`wI-7lr!JrN-Br(e z)m5W#wqwXRG4tEz%)F9@Aho5p6$wH)VuaLKcrUMN`;mI zNs+1hRc>8>{I;h{Yj1_EZ;jylo{WtR^27(53e%gZI&TRqe0T1 z>U$-&egZCBJzy*T4wBymU0Nhw#F1b!n|%E9LAu#PX^=0`Z$Wy6z4%kznIH6i>m4@^ zGo28*n!DYqt98lGVPtr4Is(#EgGNW?r;T65!no;Rfs*W*>u0JUogEN5-up0n*n)-h z)J>9mA&^!<4G<}^M=J0u0My& zX(tI6G6ia;VM*?ZI8lm($-Zx^u#0B=@bLsHz+Nk)TE^4(M0h?(zkOy1gj+IUy-Hu= zx*s}nvFFLRYVmX}^dwG$1DKU+?@b|&sGcqMH%Jcb}fnzkf`QbuJnhfa7)a-8IrQ9bwm1iUh@Zv%Wq1 z!_4I?=9v3WrsTZc6v$4p_9T?^1UkbW9ub;3T-g2~P|m}nGyUwYnK^q4KjbyYb_M+u z&jb?}^vDS;-tNr`9OzwF9z9e6=19hyRO*xJCcLF=-P?oG&zGX8fjKjsG$Pl1Wl`Gn z3lOYSMDnDCqx$$j?qPpPX@TDLc7QOew}-a;n1 zx-F(^-v1CUtsQDN$}@IW_7}vy-o2hmSXAE3ullfL=W?AsKE&^_2Md>)HQ-{oJaWNO zQcP@QMiP8`-A9?)2SbRxA`g4uf}g*as`*3R)`@2|wdK7oz56qdIL$S^yipee%aXYI zOBMZvipF3}WcZ38Fvi8^7g28+zM_u#zCT~H`s1T=UfIdZFx+c5Kva(N(3XW%EJ5U& zPM9?Psj{k?G*|}Fug{{%MQrxjagW6mE&4jGjM6ROO84>l-I8e|UO4 z7%bRzQh#Bkp4GhkdP-~mR=(@gc!xzOe5I#f z(&fY`RwT$|DmQVE0Ld9wf@Uha9LOsnpfDI^65k@+(Z18yM}(r0vpstYBh0vFzX$RBYj;5MAt?~uZ{8H zs~u%pJ!cKMOd#(RjG_sog&lC`9DlV-VQ8LuJ>?+R+Qriavg7TBBAc(DA{Y7yTb}hr zqF3z$rNsy-icVD$;_8SGoM`IN=ohMkw)@G;VZ$06kK5*Le9D(^Z`^ZX}`*d^jgD@Gwgr=Sqo7ri9 z#N{Vs`TjRxn{|&RUYV|^PQ1It3L~_kZ@VevjtrQLF_0xt9GMk(C&&Beyj&(36rALc zy0*znsZaMoq?lw_vlFx)uW1*4=i|?n-3`30{p$S1l`_PN2s_SMFpt6Lsb^Syy+q<@ zye6Jv*smMSykWD4PeP4plgbZifT|zwXYjj9PfjD+#&$6$WMSD>lu3GER3>vAm)Ks| zp1)|}zip5{Pm6QrF?GSLN7kh9@n>U)9MM-hwydLRrZAK!=*&?=8+G=+D*3T$vs`NA(|TV#yh}#-fL~uH)^C}>*wa4rw~Dd% z?K+I$=Lf&yAjem=BJz(t7&&nrFwGH);glIG1AYjkjK_U60LT8+9V=c0UB&?lbSTY+ zv+rD3-n;j0db@64t@#D}>fzq>ub)HMn3YZ`?<4&`#m?`(YP?yuGOT$G&V5rpatbXnQ%FE1!YRsb zlBAZ7&pbK>RCO)(R(QRo_B=d^JQk4_lSekHgYeUwow4aj?CK|Wa zl{OsxwzKc_V}ic7^vm93_DL@xTH=g2HtycMPE``M;&V3SOs@4rJ>B>p9|P*)Lqvwn zoTG}5&pV@ycYF-jzM~bJ86cIiZQJyUR?Hcch)z7Dy{palE{fU7@#!j!TSAknXU#-e zi61_SZxgK$6c&EHMO7ffM3v81m3a#g0L*!q(a66gx7sU?y<#GZ=&+#n5o5d<-iU|S~Eg=*SiOp7ap|mCw`p(&F*A~n6z6{vCk@9I~Vc7S}ead;JSJg9&`0y z2V*8^-)Y*xfAW0UmIv#9t=@=6%BPV__*zaaD>l1z{EZ|mh(bJf+= z4F%Qq#*R@bHJD?oa$Q0!|IzNP&2E<=>zY{kLZ%?`j?5qm(JQ8ZOr3~$rT zOMvnLA=Kk0e5M#;NC0W5+O6mecW2~0RnG)*5FS)?+&rr!^atk!_yl4*2`bis&p(oU zi2|l#D>iA#r^QZQ;_|{C2zKHj1a3n?aSHY(AO%%qJu$!9yZ{}mfBVl_$3RKO;%5_k zj^l*}bxIqwDR1YzUp(z|*@N|Tax3VU)pZkxkXj zIr`YMo75|$I$%k8W3w;(wA4xD%9frku@j_DQ$qXidNwDfys+nV`(foyJ;2kd`We^v z;vi@-PY!ujVx2q-@t#9{g);^c-zJIx=3YDSFS`MQ(0WsyWntS=(u=Od~!n@59KIY!E9 zyG{RK=84L|UJzt_hWmlZ@Yr3%;2H>c$~f=bRgvh>U^?*}_)cu0x%Ud?)D}Sif8(!} zM(}eCWH$#D)K@lKUXrb_Ke@gM<8`5iL#}-7Hwo2*ng1iM6!*d5fqoe}Ht#-xNKCdV z0rkZ5klFs!2)<(kGBGDaMHH~jSWm7ZC<}4;++#Q4R$-zWPQ`e^l~jm7qLxxgtom&B zfX?3K$_f9Yq?M;cpb38rx z@EkPY5Vcq()k$CnrpxVSMVY_)<6BxB0i`TE^hI6^`x5 zjTOJD?a4-6r7WMnDV%)(Ju@3Hm2R8!z>mW;*d()MJb)>KPP<&6*x|fl?6q=_nV^dM z2ynVqpK+$YQyE)hjKr!R#f=@^Z%>fZrJjEQZN)1W)3>OhC70%Q^-drG=47DdlAFK4isXBNyte0OSqhmMCgZW_bB&nw(O-1BcmTajE1@{UkC78I$=`mtOo zG)3DL&teZ$*wC&=iuO|4%4cdvnm76lPuyi48I^W4I&9^PK+TeAskU?fC-&0=q6c$Q zmWFzN!F3Iuf|K%8Gm|5C{@U3O&axU?)Ibb;n8c~=XbJ9z$%(X`DbpfYe6RxzvCEB84ZL$&&(_S3b=6Z}!w#gJhRwQV8Qq~~ z*b?rx`_p?4>{4FE@)LT6i-aMey`EbxM+IvyWwmun_Tz>bAKcj>M#DzArXvE)216^a;z6In2)rnx$(-sPY%kL+n|%H z_a(Nyhu5=vk*)}Y-%ZHtA7&`#Fp`%8?=RnbSow3@8X!>)B@So^S#{R7G#NHBpg0hz zZzNlj8N*4D8N)l=-VUzmNoC`SljVcV8}O)(7*)*X8~3~zYlhpD zO|KK_f<|Y>g7&3u=wcyOD3fHoBm<8My1v&;F>`1rn-oL=5aZ!2fBNue>bNhhfm6Em zNI=W#86P`MVxQsNeW7@5BWDqJ0SIEHma58y9n{AIY@aRvH2Kx3@%cyoth+?4DlE6h zKxnW_g8$8Uyn)t>`0T*^yFsBIEA5kPwGOwTcc=vhhM_F)47JFvvlM1EG6yJ<&GX$MLTkw^R*K~SSrX9pMK+9kO+=0!90Kou?ujBOi zAvX{|sa}f_0~t60*pU$t3Wa)=ucg-YJYT#zX4bUbb>E4@RK;M%S}ysMB<3lV^JpFw z)#4oh&q#mbIte7%}$l4gFJgVI<5SllLPk7 z%)$E+v~$C3hvJB7o8+a@(`%gi8-|NJq$~9k?saNtt+zRy#$J+gQ&r>k=68HfcCLKS z4(9tvi{v%T6-`Y|1spP2iM;R*3@_~;DR1HeQsRZ4tA|`_0L?Nf+n$YMJkID#m`0ty zzo=QOpW_aO$x&nV83)W;p=j?8&5G+$oqz6?sfYKX;*Q$}^Vq?>8l!)HdEA>Vc9mN9 z{mnQn9B~eTCFRVjg;i#_?bW6uHy_hUm=M%26YW#%=f; zq77fmFA{eL+kOwLlQtRR5l-47eNB5m>r}erkTlvw78!YRl3vba`xU%*R&Fhl%1i3| z^Pn3F)ZSo8^>Km+?$pt#-Q|Ngh(000#rIF;rkVO*%>KueUKX*f@<%&~D1WRr1&2 z&kvQIVtdedHbfV%n4SfuV?GrevV>^GYDe#0f(90t9a)q@5#xt8zoo^p-YDffqpYFL z0u)K2IURVcL189f%_+Owq|H{=jvgMhPc2EX6Q&T_6vIps&~nHW4i}QjbM#0R=^>dP zt~q{|cFH7J$%1t7OwfYuCaPAdE)AkIYXVT9F~nl#z;z8DQIy|kw?}u6gcYo_yMG9w zfrQsV%ys6yYqw@$UOOgy?$FO{+;q6f!r0PEO)*ajiW-8>nliD^esRy zzx2>MO&lj+U;ro~nLMJ8C6oiSBY6$$Yw<7B$^O`>Y~kUGSO zr~cKccK2r1f(X#hd=g-Tx~-44c+tIr-E0xuoejW@OTo9KJC{5{Y+V)md-Lu5I~9v0aiOD2l;aT9JN$lnhHB#{<`V` z{-9a@3W9FyG_+tgExY_QJE{U`3vEV1tG8N`Tie+~H4>e_fxsN|1<}V3pU28VECW6C za#&7ChmAQY6XQUC@m#3It|j}s{h2ZJkXSuPe**%l&o z8`MbyW1w^)g0Hqhb_2|a{|aSO&kwj-u1!Oe*)O3j6S{TOjq-17g^--LT__i>US5nh z6Fy!)9?dF#EG_@^_oI*b>sUlVVw+km1F`W7NI-rXt-PdU3wHT+zkiF+BqSFp6G3}rA ziFX_v0Pk{}`QZzh`SBWnF%WS~9*Gi($d7&6UAsdoc23X@h97RRyIRM;+weo}`^tBF zmAm_z)sf;grY3y;&n3_RI|_25g{lCOnaKKv7EfrXQn$)^0YGduRC;b)WGMS}Kfoo* zG*t=g&+JHqs$8b{Q%@gh4T7BjMdKFe0b4V_FANS9V^Ff#04qxJ^lyKE#NP5rRS8p6 zs82k7yYGSV-$}WP>XgUG`0eHgiIy(5h`JT`*W1z&-+lLZP%$W9=BrBOrhZ%r6fz0! zug`Iw1Oh8!HJ&omSgH88xsYaYv9-BtHk)N3;Hm;3VEs937^t&V4C^(jErw*nxyeuC zQfw{?wRO!g6!f_Xuw(QPx?clZ5O$&xH@RbV4!JA(fZ***M^ot4mGtLGIAZ+ZaS>mt zhX}R1q0x957~?TvI=ZLDf4LN{vBr%KFXxnh1SCuaLnpDOKVD6W%_DiMNZ)OC(~r1~ z@me(Q{?^^@!9eY~IxJa0D_nkfnSlAbz>Xl6Eij$pH2P~+h%Vj7@_FvtXL}n)2)bWf5#Gt3^)yCY|gO-^Qe6LDRD^DXBt=J}_~iUYln3;o-h ztE-5FEhH0zHb-e}8{Z?JUw*b6Hl9Z?73ue1S6VCZ(nH+CGhWUqX@}SQ=*@=a?QIU_0_}TF zBp-}*;tpydzK0Bh;Guted}SX}9pbQm)Oqq<+;))^fBXpWW>txvzlsaZX-Ad*{iNs7 z3%8SEXYD%*eWJf&GczBvT&*OsyDJ82@=rdH!Z# zPvT39+n0GVIRAK%0mvSj`q3QyXjZmZRa;YJ{u%<^VF98)JYT&@Q+aohHAtaaXSX_p zWYO5}c8%efXo%m>=R+zLOV48#JNNXq(v}Y4oR5q}yC5V*b zTN(aD3B3Q2p%Zj}&Rgf$^?A8&-XiTr>3vUh-o_^7#Fabm%Pj_IqoFLL<@Sw)Zs2!y z;D)BxiqKg%u%M%)rq;8zv04jG<(Us6$u@axZzT!>td4iA)-@;)&k8qA= z^DXW@27bTuP0d>c+ACJ|{FGa1S-rKb747wI> z@34TymcZ|fw#zSa)+6^(KGcd(0;r%28D8v@aP`T&M3u7-k^ERch=I93AHZlszf02s z8*bC@`qxbnGkWac+MeeTa@1j$d>adI5Bpg0ZKJxOdm-&?^7+y3J!BE zS+e#sLb-HKUZd$URXud4uvG7l1t(uGzV)NgjJlyrRv0Rre0u3jibH@+B5u#$-%5IM z#_Z&n!wWyBF-G@~t3(jHpb!@wu8vq(+^o^DSpd&HPMj~{TDbdIQ|`7(vxCl)M1PWm zv$~%`|G3_fl>5(pCr@6e#SS-pk;RVFl{b3odS$x7i0M%o)n;=gnMb{?uE5}U9245h z=0hS6Fz&D5DM%2YA|C_`;7PY6FJd`h?k~bTSJ<4$@&hkDAbD>FfBFiQV@w~j zg~l2ZA3bY009q>truQ2`Cte$ac=DgG`)H+Q5Wy|B)3xb#*}HBks^pKr;m}93S4qJYmqyUdKE#u9q3owNidrF9*~2% z=!LxT_xqDXE({SsMn(3qb{Vx_=UY%3JyyMP^nQiP+qWPjdEuUvi8x6AYCOQ^B6KUona`>F`sLK z5l`1^9ESFs`QR&0vH>6uk`U1QShxqticM>zAcyv`YY3ZNll1qiX0m8Q0<|Zd_KQG0^#MWS6}8d^KlA7wyUaQ_JvfIZmd|xkO#1@=_GEsVaYv zoe1SyQS3An{b642aV}Fszho#`58l7sne?p?Fv;tK6GhXuxu=Wal@+XLH!o#XxE#v$ z-rnD2kvl!~!~LQfA3xfsw+7p{?H3K*$#eI=*Cs^LpK<5W{{L`{tu zuKaNV!HhBSJUD~@SV)&0cS;F-`LD6bzFx=yn@8DcQTfkv3>l*h%HoE*`O0iIY|<%l zam)c_WM?7L*~_4EB4RB@TXxVfE4P}0PO2^0qi6G`2cw_A3<@zH#@cj+!)0>N_KYQP z%k93(9(zJD6X8w$xRzl2R)*Yx5j5Z?Ks7KfWxa`F<+tJ`cDuB|cQ@c3+;4Ul`?9cF zC(m!Ndie(9@yv|eDe5{HmIt`%t!m`s`gci2^Yw{~0CL0u_&UF^s(_R1#O$E=;wu@I)#tDVWFT5Yy_-{V60(Iou*8CxH*0 zwkys0} z7B@nsOPCTzbR5~qUnVV85G!ihoQ6_G6`H1@ebn!#nxW1aS%TUO-A+bYtMn20kSb@h zL}tH94~tHphS7ODMqG3KZb3zR2Gj4?lkI(v-IOrcLufW!RVB*#*@O~`w2^aa;JBgIo+7q9jcK^=AAB{~%c{A@!NJH_t#BAZX(SRotIiO3b4iw*+Pa{`7aZ@3paG4s zUqS{2>i+^m-C}ZcNZ^NT(_=Ic3+f+VwYzQqfjHIoU=)47-=o62Sre2KP5pOZUty`b z6;to=mOp21hTmgmS;D=4<2=e-jxTelV&CQ zpsz6lS14>GzNr+Yac_vGGt#vm*?zQwl@y6PfRn^}{n@X=R7(e3CRo7~%Mk?P*Sakb z7G*1z=mwcq_$3DBuQMGftYolZ=NufY8&_RN_jntO_4-oWpL#(=aP7lTad@))`Bk{04=Wk@0f(9z|!{*A87Lc#l83o0!Dmu9g&;pWB zkKWV)DxPy)IIVhi7%ari?xW&(aV8g^^RTZDMv_`-4XEQnFvqY$9fVR?e^*O?T}21r zc9r?wtO|hKm~ls`SQr69szxg9^;r1Mqg6uu-7se`yGM^thfT<;^ z&VU(mCf@#V$vmjTy=crf(D;w8;8lSQJe_R|l7mxo#hlv56rs8Km;nRchf?tXND>xt z0QzY3-va45C`e`oe01YmUEK$R5`%`cVAEah1uAUng0 zxsl>bzRC995vwG9`8z!^=q9gB^fK?gsKV?oy~>)As8(sW(52FvIc}X$Px=1iFef zhpW)rtxi(y`k~(wR;ay@1#F|}Eq&Jafq&aInJtJaMNF{&1Eq64K;(F5^!$>_UTka3 zr8z#Q24a3Du#x_*S@^{(uIknf8TlQM#c5S(f$4<}oy^b-EE9!?C2cG{!QpQmT1233 zeP{73Rz*}bR3$_Ta|6XxZ`N$HAQ0Sm+L#(k&v>tFukmN^r}J2Waf0{X#btd0A zI|{?xGNCl4SJs{m4z5zhD>Qdeh;)Oyeo{jS2@C0jRpVq}zx%f#rjU-D!JtFnMMPBZ z(;TIA`Ol%L-EyDe(-gj08?(3RUh*p>x{q;M;~B`f^22|``*VOK+F|llw(%H69O}Kp zxc`Y(kT^u)i)7V%>V0Q!h^i#o?}-k5*{LaB=*9vT<>k&^sS{g= z3cr-oJ$=;sl%vKKN%@1hoO%A1XRmhlBwjxdZpr59={t4M5=i(XVg#@uDnaq1$|&oJ z!5vTdS3mkqlNJ=Tt78PN+pt*oTLjY!bz7REUNOc%GM5b-DYYN2S&~Bz82q3uQnD`D z$x*(8FfpPTSLu>*ilClfG!nGxv1Q zaPD^P)e35x`*k$y#@LzF?jMAXMDv})jyWqMPRCXff*3<33gPkR{`rRGhazi(XQ(dx z(U-BAo5tEh?wx9=jM<|Sg*@$jFJsLb-uB`(c7{zzlzj8W^xhH0_12q9Z+90~A|Hs^ z;dOzGgA4=U&n5~HIGgKlX~+wm#uo|@k21wX!a8+~;xg{!+AlSfmP0Oj=uzY1FQ;P{ zA03l+3M~%it91zri?a5o*#xh90+}eFB}ATKv5vr1jmGWY=C8UMTW6PY~#*rkFIo_O;AJN>{GfomdLd>}H}3ibpj<#VnimR(0?SqBpzl!QIH*uA3v-7#Oz%mN!hn`pmDU9iva^n9f z;E_#;z&M}<&Qbz|XW80-3KTyZ_w?h4wH+s+m_oXF#~3?{^~@S3kLd?{n0~LH3aBt^ z3{Ia<)y{rkjlT!J&|1nQ@P+z9n5ck1@&8EaZ+yScJwwhrFfw43x)qIx@pX) zX;~dwV+M}a*hv1p55Bqi^~3u)&fGoq%!xYtz=2cCv755s<>8Qlq^x&>)Ae6aWk|NF zRLWULB{PXBfzvQRt959EdEa>!`+pfbTs>W%7B#j-ltFFcIe`#bc+m~xa zH(ou;XFjXrzi`p9p6$?$|c6*jX5_AHrbYod7}aj|j?v zu1o3nM_$08+b{SfbrmSrvU<-3qlf0|$$KZ)8|G(dN6Jo z&A%AMdY@VD5Ec6LZ2h^MwoiW=TmEo<>i2TfrS*8HvAgpcE3X5uLkOkuQ)=!Y1GWns z)wk+G)IDp10o1|3&eJTL!MJc7n*AM0hH7H8F|$(V$hHL#X8-k$WB-JjnzLsk=BRPW zv6;=bBLGCsP-U_l$D8T+aO%O=9Os0lZN6FuWfg6=Sj3hzwR)kG^b9xU*=F|h8(s7{Ajr04lz9cwJsYGT;{nM9GDwJ?OR&_ls9+L~U2Ts(fEFYq~;B`vNh^sR%^F5=td0 zU|HJztrL%U`~|9?3Rp0Hr310=KYGzIKaftcweE97)xr}N;#qIzMU0PuY9x!3-Hh;x%W~dk$pBV-bULRy2Y5Qvg=V zWpbuSfy26pz8>~X^&0X*LeBh}R-vKiQaG^F1YqInLYz5Z)(l#Mp@AdRK4cK*`Fl&< zcHC3I`F2q<-`#jZQ6sKbh4}6@SN+eOIXkm%=@Gmtq1W{t_?aklHq;UcX!c8FnZl0D zQ=K8BoiLt0%1%OI9XM}&T-F`8pZi@k;z%QeZ5U;yeaczk#KnKVf)fMOJA)EVw)bfT zMbO+d`=$cC?=U30!D<-a=+gn}kc*nqgAGNQgCFn>0gro+)R@BGO^?c|k0933N3q66 zSChS2#_<^Gc;V|?)wa*U@iYZ5L-jy~^@MOSc<3K9t7uTI=4d9h8!egB?5+%I8I+ed z$y-tF*l`?k<}xfgt`jf(ZxaQ zC<7P|8lG7!{#vD@5Z31#f<*8#Q1y|6uxR2fQuGo!;ywd5eoHms>upy?<#>$?Q(8Wc z7!*L^W1;C{G5r}hY^4*d?`j)5hx1(El@k$fMXB%LUyAgif~?dQm9YtYi$WqWyMHM+ zPBpf(sz3w+_#tj-QO4Fbp9l=LNEZ1n(Zc{^o*c>YY1X!!^+YN|04Q8Jzk%xaMcrt# zd|n(X9SgjftvUz;I5d)1qZfs+P!mVHCJBhRT5LAc59Wd0yY_qX;vaJD`!%e1GLOS4 zp)6${@b1%LIN8p5!igDfC6&t#C8pna&IVIA!H*vEv~yg+-aP!CZomy$NtYPB-_O;I z1Y>-B5GTz!`<$$v+}fswP5k~F@ClMgN>Dn=Phs!oJzvieM#`#{01yT(He?0>2ayQ4 z-=_imiUTp__qLJIwwgfL!*5Oei2(_@2SRf+=WPkk5my6sBnzctm;oBO~eJ7F!@1u{$B`!Z~ZxPLv{Ifb^W!oT0)LCMg*$EaW(iUPv80n_|M$Ab`+V7^I%((@=y%S zRP2cQJVeUN3-`FYHoQha*AG+f;BIF{E61&;apdF0rggt=Au8!)YRuRaH5cy zMkzVBhMqzWViy*Y41sKB*sgq#?1b+Zc-PIb{v*n@f~x ze3RS1((%gj7d8Kxaw%tx42fR>yhjfEiUX6skfE4g&!wOEov}FyXxE1IIZr%E3+c2= z2lOa&=%8u6)?EA}uOD=&#{B#3WF@*i+cdzQU7~GLG6rWqx7_dJvz1g1FFqPXv?_D* zdFXzlRBw&M0{=!+-0m|?cg6*sUqAf*Qcla_V*#?7E5>Y8S}OK{ri;$(x-Dn!{n1S~ z+zthe($`R$c6wcK-w_uCK-3-3X{MuzQ^QKI8mI;e#r^8{MF-+a(3#m{=7>#@xhKk- z99o-tqOYyOharLp3cvNG;F=#j$g5^Dw|QGN0g37E-hR8O?#`NFr=`ZnaW2NcJ)(+$ z9a9nMA;lo7EY9ZYDV8^HUUO4^)j?tWZh=s2=^OR=?RYQ!({?gwnQyPY2(QqxZaz6C zfHLnDmA(Vqi>j;eQ9c(BFcxbcCcqDjdOq4}9J;1tEp3Y204U**Cek8k&jLfVyE9p8 zNFv~5Ooopdfq@zD4x9e^yegn_%J$(h)bGG8Usu!A@ZCDFUzLdP>-TP}@k{O^!^{JT zFVNf7*@uYMt*PTk9=U!Z_v=$!xbD{zhPH{m*#2>or}3p^P*48@8dswNtcxTN;!Vi) z$75l-uRwX+FX|QmkU+QmCahomhMrBxYhw5&qGqZ!Sv!d}E9z)!949e286l$#Bi6SY5? z6~}hOw)UTQZx_%rUmRWcUi77imC$L;*2f3{sirNe zFjr5aLONO90fm1t)$B|ITHoldVAvemFEc@kbjx$<2$&w@T|Ml=L%Ne4%$jI+W2Y`*fUr?m~@J~WYd7p7_fWLiRsriW8a2*HG*;Mav_JFVL z>;7DR$1L+h8!n1<`8^8}zl!rX&zP(mKOWW053@~W+EoH(3LApp~;{+D0D8L$#eKz`|sVUw3L0X3FYu~lvw;3cj7 zX-{;}R+JOFQ0BKPFwj^C(JKph%!KpG@u?=^8?bK2{9&3x&kP+XFg!O)SzK)AR;@X{ zDuH?v9hsJHb$oOH`JQ$rGCXfJi*%Q^qyI>LRwj15s4Ya4i&_M#$ejYNmZI0Up&@9uNBP>9s0-E1`| zc_~Q)Yc>LbYqw(^R}8rNx~j!MrEa8GJCXzI11xPDUol;^So5JmwYIUe2_r>f6aMZN zsS>k*_JVxym$Yw!_BMd`y-wM&Ogp%yaq%B;2mJHy)juD|Cn?EQUb)fa2e{dIxo+_9 z{%E&Q+MG~B&$oWSZ_SA2%!vL>r)*E>2> z{aCzblAfu`vP#jEmym-e7znBwy}E+hJ-T*Ph<2x5Taky!ibb)aP&)6lvVoWuweu;Y z5C2Zp)3^Hj$qfJ#a#p}F!^83Ua99|{{_;qk*O>qfDr!ry7^D9Kg?FPP6flZPf@h2O5#?t^6f* zj`+6Iu?E0T^E>Eh63@7<>ed_tw4?#z{Lveag8{aCg2b(TxY}HkM{+ISV|oEd{w5B< z&0nQlu2~9l*;n(-{%~@+nF8832b$s?*czcv(MIw>NhL&dFstHU1zGRZ?!wC-_z_%M(Hh{+-IPJ?f|jjcmg%34~)CQ_+CE} z7T3JIXKd8VyU=lC)g@USyR~g*Al(M2TREPuq+ZashF<_W_g_WT6ELm~3pwZwt7*Ho z@JY#P%adEfhH}ODKCa98&9NK0oXb%PCW~m6Y$_X!5nzGItG?N(dd3t4F1j@&4hpMc zlFKdNz;yy@Gs=+i)Ww{?z0Mpx1JeFAmxCH>O;d$O0%zQ3LF|P}OP-{W`$aNQBy$?u zDU?6BZS%=lTe5A~P-U;Er$JRB)Gk5)nowW%LVya-NUB{YHQWuZA?CS^MEouW2(@i+ z8+PTu&8(UpW;}iRuzQoD4%jt_BbA2j+Rh$S_MuxoaAQIa=wmx3(-RY}DEENk8Mm~g zo5hD=0L?E6ya@wBX!9{;k2)6It&U}XIZg@W{0pEj-Ulry0P?5-(~ux>Enzc2VZJi5 zXwIg~vphr3OAeYJKangvQb>C<0WukkgbJClu)=0FmGPN2uoZ94oo?hL)&S;3V$Tol zH%2$}N5JzqZKH2!_TdDZh%-jt?=?XI9=dM&!!gEDk2yp2UI!N157(x9T;C4yj+VDC zF|7mk?3xDJt11?Nmvf-q;rG_4QGthse)SNmc!nvKXgAmd`lC@7WzLSFry*31BoI^* zks?Q*_Gn0>$XbvOXCk^es2)2Fa)|PjgA@XNUk{!;!-#XLniau07@lz3*=)}Hr0c4&F%0}R$ zda{5DZakjB32pVtDfF0&5&#zgTYFXFCEZ}~Tk*?8#9y%@-i6NZw(!qtZI$L>bve$V zxzc30EfYF`Hn7sA)y4ikJM>kBs|-J60c;bw^2vEjXQX_7p09N?*4>y#w3VV-l5*o= zAJ0bT%P~hP`zI1r1DvM$WSOTyjaP)BeMJ}wSqvqw*A$Y?2&;CTViR=j5w+K_0Eooe zrpyZ#R4pdkVkQyP9pTveS6B25ymcj8vp7LQ+H>his1`o^$o5d86*M_K{%WS3rEsma4+c6{&RKUq4{6Wpt&E@JWyL2xJJj{a$g4 zdaLtOeL+vZ3oZZ^5T^fiRMVYGasFxlis7ZnQ(pQ%R{X#v8V)#wNd7VdkzTPQ;ouWO zKWaJ6mnH7*ea7I{hEf5gDsm&}>UOQrYn|JN8;*)e$*65UOcXT-Dw=(qZ)C-Xskpup%?2iV>Fd2-XPEN0(r)L!1y#j0ktw9yPFpNXce3UhUo`1j>>;&#`@KH?1 zX07C%)!^Aq7KitCmQACi#9JUt*{lGC52(rvU+HqSn){$V#c0E24Q{g^H!|9~)}P*R zCnw78=E5f&1KxQZJ4C{&81T&eYkDt30Xzdfyj3Ts7WnXJx_d>~1gZ|LKtVq;1VRs6 zcW_~w#oDhZC)!xERjh@|GkU#yFr6RdwwWY}mJ<3WY^#BCdTPRQh~w$ewP~qkkNP<3 z&l~Q*US${O0v+BfS|*8u!Am{;qRmb1)oL&@g_q9{QuL~9Aa|z$2$81~wS=}}eod2u zcqpKg+cD1hic-V5=Q{699C`k`I~5-SLI6*w{r9Z+CjE_NYhsc~@-$`PT! zT7kTHB!_ZAPWIh@?Nu;2qZ1){DXD!eex~CHK)5CeTMEmISa*ZzfyDttdU0|g4hUM` zJ-^<`e-Kr5*wi#vRGS9+%%>fy1hQ}v)ajodPMj1inJ13q*-6PgECAs^14{5LF!pN5 zf$#--i0)bibnMdj8IHDG=2F855pGq%pImb-XTS=y^x}hJ zelwldfCWe^s8FgQ(@-I(4 zD7Aldb|K+>n;hw_!~ynwy0`Z}S=!9b@hccGyic}31Nc_XcSi^A;f!Wxz1ZXJfwU!-O^S^PjF-kWfR4YB!rd`Mw1mBcKc1yQUfv&>WlQ zzN<*#6KuDM&~{{Q2(6{xX({aZBhd>6<8P#=D0eD|QIRVpLc76kQZ0!gJ$$bi ztos3IF(GlV>hbro*`VF>cp^jS^k4(Tz zQOS7xYz_UO+?O(h%J6fe?~$GioJIQ{LVdTB7QT*p?KhxrM3(ODGr7%N$AJeNrCy}W zjOsH&8G~T*dk%41C_QvbTH#?O&eJDSiyd65r=O5h&U;Br&+h-cFM8mFR%cVnc%oC5 ziF1jNGA&%BQ;dMefo|PTV2>1qYhcmok4tQUlmqYh+aF{ej!J{i{;)T5xuy+a0wmT< zVlK>IvQYdQZa~MWksovb=mfG;bgKcdHJkvqkpsBRd)QBId&;1;9~I-Pi)6Ldy$r^v zmTx|a09_?asN%*Vy6y|pH{!Q)NS*<0Ie^xCwn2Xe(7kei?k&iZqYtQ+Vbb3jys3j! z<*hLH+1KIZXU-nUnKRpMBVL7`yB98KprYI{P=y-AVe&&e$L+e(?}UUC#B4$_p<0me zpAgRW-22pZ)$ojiqmUN}BkMctf^|Bg62pN)k8@Cc{hFu?@w*$aVnDAv{`uFlMnY*H zi=9cc+YH2)hxXI?1OYI*$gzUOdVe+cH{*F{b+*DzUcl8$W%k>|VG70sknhkBGt0vc zVBk^hGE7I?IzD)tXe67|1R#rPV=6H?IN0Yq@o%pm4wIIkNSIEHex|vo1$6Y49bMc- zApBiZ{kBCH)oH+%JNXbEeb0qUOCX z60$KFvvQ1o0{t$kF*(gP!BJTb1EKoppV~&1_7Bs1?+mOtSD>45>;PA?024Wl{P8(p zMv`Exm==W1>ztAWH30UHgu0XbU|fe_(A;rvXKD7J*GH2BTpQ3!f7IAe$MbllKcuIXyLcML7{8-crME83Lgi2!7!ubOlu7& zQ)~if-2}ce007P(G0s7ya=07X`hxch3O15CWClIwg!11+-~=QqSi0YRp8I}HHS~zo zyJRt?NfB`1v&+TPM?eZPCjga`QLr4iP8z|#bsnRiGZ)#0AD4EdQ}Fo!Op&L^cl|?= z7cjFL@a^-U>zrNzs{gLyu^1qSLzU%WW`gL`Fnjtzl@2V?YH-Ep)&v!xjdf@Aq^5ND zB%`jF%aYVCFKIM2shsfz94@yR{)sqeaTL4KNodlVjg{%RAf7)q<$c0u@D)cbLVY}C z;g}30lXdDakFj?T@SQrT5NXJ?z4a-GRZmb1bDakR`cX(;3bQ_rRvX4cK7j zmpF6yEUuU!&3-fqZ$P;|7-ynD3CO3CI^N>JtYhCsn8qUTd-!g5kbKZ$AFs|JYJ-o9 z=%FXqqCy>Lao1xDv;_k~|6*|wV06U0CEOLX&ZlGp3KK-_btPad6zz=7rUiPD{?7(9@5hJ>`}tT+B4{*%lhNi+VK-p=>ra{F>d>C&ONUS1<8lij zZ5h~!AoGrZw4biB*RU_t!|nyUoBjsEifPbTDC?CLWm{tnl_x!+Sz>tJcXO;nB`_%W zF_pLj3Pc!(XO|hBuUAxP;@y(&jNNCHVn`uW-S;{7hlj<#ZaE$BB}#C0OTw2k|nB;FxIET&Bg z>0ZJ7doyqCN%9^EKI@2So}5;QWbQQry0NO;CxtAsLEtrCrhuw%0vxh~ccP?y2<#QU zL)!UoPVlHXyMXU*-Axeb{uB7#Dg(HG)g3G$7FCzJ}JadJaP$`$0dcJj`2IMN_=4RW@jCnz}bhm^*2Juz~hNt!}pet zn+XDSx<0?P|MU6Ras|?o%W>3N^1A<@psq8lI^$v5QtfkL8@<%%XZ~=7VXFzmH|hO( zKINkO?Aun&RfJlH zX*BPuea-fkw|>d4iJE#q7y|qGVff1gs*gb#*TwD*^b9t+oXHf>8 z+XJ{2w?HeoIuJ`%fsCH8j|4EU5HD)&>!h6dF1A*gP2$t6xzu|SIX)KCjsj3SmanbZc#Luv ze$_(p6vJ}Sn?>9|-r9Kl&==?*n%U6f*W5{n3NOW@8D>qbAkwul4kK>(!Sus`k#`{8d?{u9ZSHHfLZ?3%vL+(rq>fluKdtskh z{Z9M%*YBUPGyYQDnuI4v*=Wm>ir9QN4U ztQW5OT@c7+oFQWcGEk>S>0I1Y?C{Y0ixToRr4z(x_Xr+gQKxN&Rg-^pt2}=eg|l%q zDys()?cHmlrIyFTdaW@Nk9_ScGzYZ2lQ@MTd~t0?>YRGmR$M@?IF#v+{#xgc$-@R; z3}?Ex;ko$1Zi6aJzj~td*JrpwEJ532xpdDB5C86Xs2s!I;l+1jcVFSrcX3U%_P^dE z8>AkOse?S-lTGy2iI7HneJOs{)WF8uac`Z!2%3A?vGs{IeIXm0{%tG0!m%Sa^IHc} zx})OcH?~DpX*+%BAdy(cp1DqtN7owt5Sfm!y7dGWzWealTd{M`I%5?00=y~5F1Uy1 z$J6e}&-Ax_2?O(^2aj@~o?e!6Jn)wPp6l3#mtbDMI^Jd=)Si?)8B$aRP!}=U`RX^4 zSJu5dc<)dS{&4>E^pPYzSvW?`%o}YdqvlLT$1u+{JhMrGnr;!%)>e{3&e;s8*W&TDDKDbJ9%*cPk;a z5pVkPn3TtLGRv~#z?D%S>3uG4ByZp2lY%DLdk7Dto+#a8`WyZpp;Ze&g4D$R4NW~@=P;*00HankbMcfNO6IiDyoAN1TCR9^U8ku&sxWuFgZ ztoML1RptpXkGH0vPR+P_H=Mw2!7d891!UsO0yW_e&ur&Al2HjSm{2Mf*gzi&t*>4S z^nZ<@5RcpI<~%yACK;H%USC;Rx$~jWal@Cma;0i1d2q7@`Dv4fDUk9H!U9XB& zMRxrqA6X|k4bSer7?Dv}N7Ih--`G*h@{YVJ zG9g3L9{jpkwvb_&3Xq}yAMxKtp62=X+SY&kA#jI+ca>NAzdlnerXtC%tRUgQ@8=ln zb3P{wa09AXafADHpZT`DK}0a2yLSY~r|>OD)p~$>EQ98jtRGeD>3$ln zrcpL+R^OjRmYnZCYYuM)CCH3tv|^0vB;#!raC2t2QMV3YE;@hdqoyugJ6_h+c0y)d z7s5ceJ^ubFM+zI|nuegnN$#uA%%P-svl4S}n@z z`n$S0$v-E;`trfxY=~;L6fc7v8s~4kwBUs8Z19W3 zh|Uw7K+C3{z9wzK--}pm<#vVM);6c4mvWWmK;=oJK;OruOnUeBCv|3cZ_YkboZs`B z+jP8j{NmIHngmC4C&E^fF7Sw4==dj^Z&ymoCnFZ;Aln71(&|=0i0L0u~d2 z$-!*S3JTn)GzgJ`WolZo0BdSo}0wc&;+8h z&O~F<>%nx-@rkO=s#u|^9Br?g=r$d&FytIg$Y+tXxS`5NyG7LReBt9K@A{5xxyofj zc6AiB2p*t;>g%9VPqrNcL{!&{=jRFDpPST9`AtT(icNxkjXvZzqyo+Jni=e9Q+s~} z@M=!|zh*z7UR!0a9?8j`uHP=dlnj0%??~L`e40bx2vHxZqimujR_LR;iW3gUq zGuo`UTw%C)OEF(BQ17#-J(wNT93J#%8i3-SVif@)FH8S+I$103CH}H?Q7vd97z&`- zfHL7zGfCfrz_aO>mk*PXetQ`0IlZQ-&1n4lLyhlz<8b@vVk9MbERJg}HtJ~>rKGfb zrBS#La{bEQu4D*7;_n-dc&9dV;uqnA za76;t=QAxKm+-DaO9h`gk2T&7Hs%R-*|8Fa3AGM1k~i-R7=b?W)tIgC%>zOXf_D{w zri(9LuS^@iu!yPC>4YL49MO7|l_8+Q>}ItXr8MX{aSBZ58;kT}id@ zvaDU|IM#pJcn06Q)L<&MIO@)T2R=DYuJkiMXZgrwHQUMI%#*MahwLoeg{QSsfA9=9 zU#g0pr|*tu%X)}XjP85WZrAzuxjE>t&1yvg+j6PDa1D^=1dm?MccmzHOdyn2JaW7I6F^Q#sj1Pk61WvDLM?|RkJ~av4g};7I@(D z^msE{Aja44N4$B#!bsUKPRv&G*8U#$3LR7B(7*+}!Bd`NH(kLOzI6!eodaQ;-{Ub< z^;97BQCY)o0G)g$ZifG-W(Q1>j_(N$}~2qD79#@<`1-N3%F)PqcrRlE0Jopt7i zd%5;5Y!1>eHQhxaX4-v4;tv5Cdzmk~VYdHF`5Q+qnA@Cg#}{>Mm{A4*d@}bPe}&jd1OrDdALZX-G&7em7Q5fWZrN-<@kRe_vdju zQm)SU1OJ?uRc2<`3uQ(#?t4#@qFVMM9k@Q*!^5eq3|k`=K~|15vIan16m8 zLN?_pokv87R$Dnb$}@in9kW&R3mQ&w!oz)jGL}i*qtR8W)3`L>YumuCtxd^c<ZNT#E?E2Yy84R*L=W(xj=n5{L5*Jrg;A#ygQCtPdH0)^Pab!=X91SbC>^UW*-B1AO?15`Utgd$1j>{A*5kexzOjb65A-c3$y;ZVL|u$x*T|-b~PPc2)oj%<$1yKYQI^p0oB3 z`%5U;2wP)&>kLHPjBhgETnk*jsBqeOG+l%M#%@L@?az-!_OaltXk0Kn!06smwN(eC zM((Z!#0dbX_AjUwDqKn0oOd?k$=1=4IoeW@&`}|pTIJ=I+3m=;S-Ck!R`aDnrqlBY ztL_lWGwe?pSMC$8uGaJ2>F8rp<7htQ*PUdKmm~{bZ0_ad()NCYJYSr=QZvHO6p{Z6 zR3^eVP*(WX8=+nH=MzL*froQOfqG8haA}y(gpAU`^tEPQ`4m0<%KLXo`f|Y1tF*z& z+3JUvq|rnvBohK9^K@rgnDbgxqYlyNfLl5JEZlo*zV{1~lw-6xv)R#V9k`I1|Al_M zhSxPPhcX?D&-jNR3Zza$vj5%MdQVURFMW8!nl^d+E5j4a!YE|?*Pt%~s(Tl|TSqdU z8EpTPo)j&9wGp@DfG7e)N;-*9mNpZa+qV$*M>gmp zeW`WEVg2urS_jNs0a9-p$p)VNlSN@MLM%o>KrL#%CGND&E(iAp!B~fpB#~AMiq1|; zAw3*kgfE@~yBFZZv!m;RU|gP;^EHSan%?vIeU1QvOLYRxOhJ*umQ%iB6EpQ~ul;kx zgDc_24tiE^=z2T0%j)-V``-35_rBZ3kw*KCNLFIe`8Bp}GXeHXtz+R9^q_v4keSD> zFY&QUkl$6~K)=h%rySU*i11`rGyktDGMecRyk|@^9BW(#$?!-Ik^bkFPxFYC3xiz} zpE(~w+)$?Z__A(sz=@s9M=2i#Yc5em;6_PitqhZj+-qYKD^dR7Rg;zjY@^Kz6IsQO zKd=dMv7wrYdz=LP5@D46YP2D}LsmrZ%5 z3+<^xavzoHyWMBgzdC$=N}VZN`JOLA#F4l3&4Nv@s0sejPosB#pS z`k|-4EITaWp{zV-t6?T-S!U9aAXWG77H(NW#@lXfX+|7g*Ije%W0Uyrc6rZ8OqrS}rosV2IUp2PgD8qHS zUel8_P)gG}k~-zO9=Rfm?_Lg_k(|IS`CE?^vONF@0#(DS@Gc|wg@6>xLkYU~V3_7` zMmtjgka4Vg{sL0;A`i51Lpp3Sn4?l{(vQb&!8D$~L-lyQg1tThzOwQ1mIMEOarQ!K zn~}Mxk>ULXDtN_de7@7wcRRI33|$w4XDLyw{tWX@KAu}>M&6tKkLdcw0yvYt_|@j? zTfNJ|yo-^G4jMY1}Bzw$l+2Uz_6O#e@yt>DV{9&1Wz8boB#C9?uZ9pMVAY z4Tg3;1#5JFKiC^Zv^m!iro6?Tty6APCW>(g&u8)e`}JMYbH8YnnH}nZ@^Mc`Y+fr( z+O2HMGlR}TV^iSs|F8_aE?=2D($b$4RZt<~^Ahac-f>XL4q^5SPHO|)l4f{wg#E0x z<54*j`%a~_v|r)3n>rAQWVcH`HsQE&i-$2(wG``xOxes8buDP~tUn3Vs?F(4>+RL* zq~j<--hcg;hBUj!_x!&q@f>Z; z0x{ZVh6963fDX?DMRWNdyoxueqQs%d2ObIIoe~}h0R#OqCZqhOR$_y zojHL$($fXQ=YO0>9+zXYST`{BiG|yetpqfm8K}XT{r!qTNYl}Zw71jh*2b!s7c|$G z-M-}Q?w$;8&FEqKT=_R}k2|<_VMcW$zbRWcR$FFzU^rM82rl$nP8hOD%g3{7VIF(;O8k5VN$THB`c2^mEbhlr{W72+OG8=6Pp$?~?(@GspBu-M`7UN=tGdh9K#cRWgLq{eGi@sM0;zUnt-kRItjehwMY>qDMNk)F{!c7ys} za_zmEH_lL?b21gRT<&6Z+a%pHn zKZMLB;$3j@`Yy*=EPwkzPhb2y1}xhQ=`f!9Cp0%29-&VcA%a;;4`i>{wpxfM|Lnsr z`%xWxl#CN!FfARnje6zT%mQ56x*yT}Cw*04z<9v2L*9r!Wf0b4J4k)c>5C zG1Ejl+F*pBv%O+K=+mvWM1|O)Q{kdu^r9L3N_wdNu3*J465|C&>BAa}c0pzFr$d8< z>!Ie}+abGqqtxmZtwgjbxlu0jPXNv^GVTirY~6a=j*L+g3@qQ+K$LAL_Wr}k zrHT?@)U}9b{%R>8ub?7E=lRNczx-(}@!k2_sno1HH2^Z<+h03UdWFQB|6ZQ9JKL35s757p;CM;Lop zep3&SRpVp**LzNiP2fvq1N@9BhH7$N4G&y?*i*8qM4)4HcRUD@ayD7L=`(bpy^pNO zb}KWPZL>JFyrVNBd2D&)(s_1S;p$<##!G-C*0U=uMWgcYXFP0(v+{gvL4T&kfUx@k zw5)!PHgRO9U9Dc+-5tykR@qD_-poEW>?jmH8dpAZb#V2(*7D!Gq;x|cre7J$nEgY} z%f_;FFzVUIlfD}lnJSafh>jQW@HJ4m>UmvvXUXrUZ2BH zkV<`IpsJ@pk42}qiDSr)vEPL{4KXo7q*rhsnxI;SOl zY@E7G!s=h+yBk7TMfD5SmKRKqXK9>ET|0k`t?4I~SaX$|qAneH6aLP9;&plp*-r%* zvebR=ULoY-MDhdk$w~k_Mu_T3VaL z1RR)sA^rb#Fg!oIQRGem4#wKzti>Z%ZPHks7XwQwPv@Q_=EeBo)doS&T0cv~oxMc6 zK?=7MjDx;65}1>S<-Gp{N*=iUYPrQpM@?WcuolK`*7Kp!ifV5AYC(E=5 zObMG!Z6mdf0eK$=`o2ry2(&E5WvvC=BIAE1J|k>s1>whOk`~86>ra7sHzQ#h)hl$C zdi8klPS~qEzo36Bx2_7E!DViDtE)>bm9%=lG`!Y8D}cLf9>thBQ>u{DblDv}Tl;ZH zJDpgeHqVp}$BP@K^>L$X?ESr+b)H_`>gr7Ybnug8^L7sLMT-Z*iD7xCl3>0%Iy@j; zN@f^SZRNV)U2_zhPuH+09Z9`@$(}{l-=3=NY*wOL)M2Qj{tkvZ%$9wT)7ykgDbKXY zqZoAA5RkO8eMk?6Yg1G0d+a3XFfC-t)0#Rg&}=J_a_RW}*ceeDw3yLapoYb@rkQ0|e_cl1E)!?fGO zD}qJq>;;re)Q>&h=2P!xk!{ zKi+G@(*rc<$kIo#Pz2DR7SAXwG^`s zOGDfnXnF5$cU+iN_G!PN%(FSe5nk5&hKSvUuxVz-f~57wbxR_4TvL)Ahg}5)$bZ*q z4}^=1J8$9$Ihc~32gy?S)t;nPQL^%e{uuomZyUF)&T@h{tJrgr>C6EC6nj*WM*K%u zAr=ATJ}z9+ z^`_X959H0vKY1RPC=((!R?;68Nd20^gp6M7dz7-(dQqUiRlCeG834L=I-g2Qvw9;8#_vZAz-Wt>2+3V63Oiz%1p{SY zbzz^*eJMHNX^K6C7G-IFz?3;|F`Q)KUMaivD**+;Lwd;YegIn96YIfjq2uba331s+(titWBNM5cBqe6~xk6$@V|$(9L6X2khJR2F|IDk!u^UT z+AJhYN;bfo4}_D3wM<0Q#_rC4Y3|+8!YSl#vdc2DOM;{|K^pQg6eKON9k;<+Fhp$y z)MiQMh+oQMF(S#It$(Z_t@rY2mvP5jtHQ20X(X&zBbv3)ke9OIonK<*Ytx}(9Um}d zGnwE6UgBxTAD)v9z5&|>8)1637$-(IE6ELL8te|B?eTAu$aISPCx!r&$4`3;WSEKt z^U8|fTFg3)sc9H!Wb9?6ZsX*%NXuoOE1pht|2m?~$1guE`7LZZD3)0x246SWo&14h z2VE1{wE6)E9d-;=Qb*#mWUjSRO%&dWEz@ukmv6l-~3p`T@J1}J?yuyv~KeF7coc#q~kLEfWt{;n&}S{^U6 zqTOz9$J@TdzBDFX^();$_mhAnW{$!$u8oHY)7Pvrt^zm97;p%$x`6I#)9+RH;d_hW`Sxu6r%_lHX)J} zv@7Km&54<(OsaPBXRCB6wfW1$Lm>|d)sjs)g3 z-r%Bxu)QRuS7lDHh5N9}mI6(J%SX=@vW*B_$u|>B+^^M!>q=+TMdNXHoC9wJ26NCs zUnb-Gj>+D5t@oI zf1`b>XTk|_`o`aDRB|pDcYHlq626+6J3yCb3<&QUi z{OOvaF)xtd-$Gr2%?Y2|C!wBG;d|+!`1E>=+4qKyDqfv-`5qoXXtvo|D^~>N!VM>Jdjl3gra6>FUfMLqA$&FVU`XXq z4otHP=>vz&t7)%C?fTUePfI#k?24IqmkVfy+^!0K42tkGTNP0x?6TMldEWOJ9}WMh zAkx%D$sz?jC?%{HNCV+3)Xz5DTE3fVF~q0Khi zQUT<;dO+FM_-BdGS-l#;JC(hirx$z!b_ZW>lTioT zqC)g!^yo8%qK3frp|=MEL4g^tF4)bpdy1XRssnws6%i1?ioR2OvZY*4LdC>^P6=zu zX9kJq6xuJPOyxW2Rt%JJn?Y~wVgj?2lmJ`fwd=V1J4gJs6PiSQPL~O<{%6AHv;M=0UzFA@H=^}ZKl#aQaeUEmEjxg$JMLMa zH{^(ZpjAv<4ABiTi|aos6OiS#`#6lP)84(KHlX(BaJ_uyT)Q>`Q_k%aqa55^r1^cB zS>Xpm?*I2#Q7^9)cr63QM~i=qrE~@0#vaHQ*1pp(w^QLIO3{FyPDN`PD`HzGCJuvY zb?Y#0=y%}AgOK3xbd_=EuNS5ZXVu*RPp7*BfYfg*A~mR=}0 zPxpQZKlB0-1tj^O_Bnb7Yk&lOv)q17`47Qf1jyoq zwSm1_x)FM?=TtQO#aFQ8M%^45^k>k&VCJ`zf}6y!uo%qY=jD29YHv^ z8sU(a8&3t6_XNELKdX2sG$4P#aTDszfhkgVC;8on_t#N zPzUxi*8znu0|QWZnqYwI^VZfPdP;o7o+Mz^rhxKKfB4qw&B(}=xGn~CM>~M~xXDBr z+F2QP`6cw$iw3GJ*g;{IA#hwBdE&x{at&{ZX1s$IP{&Dw*X}a5{z;c@OTY#b{LBn> zf+}usLp1)~5I}$<4j=nD{`KK3pn6Td0dn%PpCox2n@D@2Y}WK-x^Xbf`}n<+b(`&M z)`8?Aju_h=F?Q;UkS=UxFuSyo?dTM&yP9NPE67uN`zVEm@dF1^Aqd`qir()c@0x|# zX>8}4$}f-$Le#VEM;iKw@-u9&?P?J00`{#2N)`x?a4@DZBrq~5TEkS@;?ilPkR$$6 zP@ypejOl^>Fqv`*8-Zjmnx)JSVBB=}AsGDFhQ_^)UcANeX>2~@OPz!HCa9Sss*kI6}+$HEsRW!Uh~HLg!uK0sf0S>N*N@4FhDS#nn0LztSn8pBOo z$od;q(6hS%?{q!QYFHQPZZ^oST@DbxdD zvTHb%+zFn0p`F@MCC*5++C%^_%HpOo^Mc#nv|_-oDheE6g-1BRNGx%JT5hg^33R?~ z<>~4H)3}c7J0*hagRTXC1OCA)PFBqk+rZ+syENHx6;g4;@P+j^nU`@Q1qjxZ;^5l& zQJW2j|M#$I=i;3glUT2sH8Q7OLOc+F|KA(Uje&80fm=Os=`rxrJ02D z^W|}4fAiLZyR%yVrdIn4f+N56QnKz`PXUW-@WBqHxWi<`1MI{SH7F6cePRZN%c{El zyifn0^Bhb#QQ8I*=DN!Z#Z&TTK;>A|D{F%Mx;u@WsRBE9%tDsshm{d6P{F<-z0o^6=T+2D?47CQ*KReL zP@ZuD%5oGNNh>hHkb3*|a{$6i{4d;Q2D9qRE>W~NplD4~I%S&!6PJYrQF^2jx3K4<+O~|p6bExCKcxvB~4s=HH%qoy=`!dA3jqPtid>9*h zbMD<HA6o3FAb*uwO4P}&__)yBk0q3ZuWDU2yuxdj9pUq@fpPmKOa015NqjL7I)V?>( z+HC|{l~lo*pCnpx%e|~A!Tc;H?DrCK3?1=1CV*C@WBo&6fr9V=(4H$$u_C@`s$%(b zBYewW)hft8apX#8{iYY}bfq#(z!Jl5|6l_My4&3vc}J6p#I|~n&@HFV4=si6D@`{6 z4Td790}^1(&Ucr`!YN3oH?u}SEJ?C4k=aB6GAYS-bM2p2!L}I|zzFM>!l9zRa|lkeYhuYA_Hi+AXQtj=hefKLLtp&QyO5bo88m^-@Z}30Mi!e4;tl{|6C>wDmT=6x2?{ ztPa9=X6#Ajk6RxuKBSc~4}WcQDUg0$(3o((4HW$fD(c9Gr73NR`sCYHza!vFvKY}~ zqqDGoAxIWmW#e!y1^P13+f(QlaeM|#yM#Yf4%N>MARGO$a zlMVhjh9_Umc+L8Tt_nhmzz*R_Oq|m^i{qcazo}I|QoqPrnez4$gLo|P=#DSNns+v{ z3vDz*D%C%48$U0dYgjmF?qY}G5r!zhGZlHC?2NH_MaFoU#qY|FlciO1w!~({dbWoR-Al46jAD-RjU!$=0((os?3v%M}nCVVVI8TGZZ< zUa8kM`#{m%YQ6Uz&e6PJNToue8wOxM|9C+?Um)rBP_qwL0I`BAfA-*q99U)1wKG|5 z7vTDY4OjMmqFKJ>3nl{ zaZ`SwZ%Ab83>>TRbr3A!rrQiN-B*+b4WDNrg9pavK>`g&D(lqL4U^DxZFZhbo1BNq ztR)ay4)NGZ;8zI3XEzse@dVg&&tort=qU>V1Vl7#v$%ge@ritm*UABTwuDCF*$y-` z@Po~3T)hO_JRVPys?2_nu1B$Nkk(^bK)uSvG4LJ}@veRa=xkd6;3rwAUJUJBw!-L< z%U_WQBOjczluWLwmWypvkF6mXGz=LvYk%R?ss%hit%x$#r=WkUGg!B?S&P|aOIbxP zTq$5KSY??oIqi>F=2F1|*6moD=4Gws$+N^Dqn6HQD+vn^&P)~g#wm)zSb=AN_>ae` zgFJVfD3=>*c#H8Q+zOdr4b*RTyX_&>oeShDj}$E(L$L$RV)EWl-Eav(Q!Tkdl;(@qcalZvNd#|v~}jo z&tB#49A3M)aLos^4_)3VxyKm4+iDudJBpr=H#W#OjLY+4$dj!e2!@j&KmSUo%6!08 zeP7~y5AgOi*4x(SOi({QJn}51aRsTKyu9IEAks(xyfLUEizWw{1#%2O_YXF{ zN_nJmb1I`+4Byz!q|zsfl1scL`F5V1Wh9pmFW@9!UKAAP5v9!n4m<%H{^7U)IP1zU z49}0HPAH-hFrt{;ZV#{Z9;nNzQG6(^11aO68w`m_8$?JPHpWyFQe)2bZC`}%zlj0| zNbr5ME-l2=S)^uO`vRHj+P!+*%H%1KSsUDRsToXxqO2O;Ms2y2X#-Bd;I__<0l&4p zzUckbQSHK2-%unZNtm6cP2{|M6qV4+?vD|b5lpdv`&pW)K)s*O#Yv`>3ur^;KO*|l zTH#mZJZnDO-QT{s=e9+nG}YxFs*pkP5}ye08*I``sf>l>&F19N2~X61u&5=c(( z;)rfxV$SldrSq{a{QHSuotyGD&W=z;UJpN|bEcHR?tJ0Lrm^R15wE&{NN4Nk@h!TZwS)#e%{p+R(yyirILW72)ENz|Is5{dZh%ZT^bobW25-8 z0wDgH@DD@S%Z4|V^s@_~HAhhlujL~#ZW~Z6>t1!zV9k1~9!n;7)kNrXOF3S+%q(di z1N}XOD2`v1x0pOj_Ns{r;;N{1VA^Z@lVk)ZZ>DfbfQ&)=h&TC6HKhTM;lWsDUmue{ z-TO&LP*8K&W6uiwgO2oIS%6=jLf-^6n`O_0opHwzGY6(sliqso3reBJJwDaa!|Yu2!YjtV&6ltDsE?J6`L zG>4EumV+=!gm}kdCX2Glz%Y)(&)!g-6R_@y_vhYEyq{!0d~RhrTrJhOE<=(KS!7!? zcy8^&C%?@}hY)DXjaI~gQu6YVgYwj5wMnm@lpPUF*=z`5!y)C!GYqcK1N zw>prWP{uM{L50mnd2bgpWdnyS8;rQ9p~hFW#0x{)jETDwmB)|LR@#wK!tTwZlw<##ae63a8 zLOb(Hc>4#Z(kuUmYFtRj+2%WDYu!3+k6uFRSUIw*>o{;scT&cE!Sw3o1v&KDaMsyd zJ~Eaj$2ZdhpUviYc(1h)l!Ra|Ahh@1d<}h8@&-q1HXIJCZ&YFva0nXnkZ;pW-VLI> zpUS*aRV6*vvh#L9G13!XjQV8|+*>N)ENIZDMN^6x9!&BUf*|-a^Ez$Rur|WM@tH+@~nO_hdYM!CiBFt*}zO-d#CyXXW zA$A~zM+w`&{`kzng6n`iCHnUWO5>9&rvEq61AnKJ(jz*x+CC9KQ6DVcysL ze1n?zDv;QKPq%;Ibr~VCLA0=bF1f)>H*n9AI?C2WHm(UQ|{CL{UW=`4BxZbZd%UKUnEl--SnO{_E`dX82kxi0KNKB64@m4F3&FPNa;8)*RrxO_;(JhA<9S zK-Q6|oJCndm5h(}COL*Pc1bWA3 z76pLSwM2Y2tJh%zSRm`8)CzN;R)X zRk@KG4T{?1=US$U!Co8AZ>_QUukYEF&tPtzd(|GCkX;MboJIZjX&tQzO!E2k`Gde; zev%|WGjO~w_Dsx}F1S?PEDzq{&ZSJq={^m$kzu0{_E}C?uDp}%YdANM-4SH`l|C_f z7bRR!rK7WGeeMuiUizxVv}2^WPg-Gw*U;H%AWEyK=3)jaJf0l0GhaM`A^Nk#`~}2( zco}6wY|4v4nTp>_4qar&GD*0|3|$b4G+zi5iXVCE45{49UKe%EmmrfzXT6_EY|%?& z`%b=|hlD)95X#8M^R6nz-@+`B2vH?$VP0uS#U0WG$8sRKN1k95>cJWRjOygA0Ku5h zb69B6!dkm=foj1OSDZ5+sW(&uMwIX|g{D3hW})(ap@;?p`=Ro(6vD@ZI|Rb{2a@5# z`l8E72zvZ9=%ttl$9ou=`(X zv)2X%Hgz5RWqi;LtZk89_-Ni;FgVPNdGw+qq?K33XQ-V#nfF7XLG{OX#^fbMOCBf0 z%vz~44xcBVJ5N*{p%vh19A_smdjB25h_w0u)%*wZGo7=S>BD#Jf?9P^DG{bG8V)KJ zirs4SQMwpdbzTo}m@PcHo21aFdY2am+|GFpQ+JqlNyrT~4U2KOcvzvF9!A0iuPXLK z_*fprp@)s;p!5CQVN&y=I8rh;ax5 z-go8qq9H6oI_`Bq$OY3=DXa(oLf0yV>nBwxxGq-&^q%Z4KkUnlvb~aVW^t%y_Omb8 zOzO4EN@0lK)QXvMLZCh|`L&gryA~dZh-ZuV%uI$oZ@g7;6{~K6S>e^tBPifw3L_L3 zdiow1(SMKkc9QN;AEN30mfMZI!$3)H^XAzxeJNOS=e~wr9^Taq71GVe3YB3yfv)$T zHO~*gl{DKLiqlhO6VM*74nG7DROroU=g(?Pf2%g1jX>+Uu%qVXeP2iZpDnQkZ0iO2 z4zW!*&3$s`Fac;F#O6r}xowBMHN899MfaoZCNJID_w9oQ7G}gTKk6v-;=X+AyVN)SY}Pm%hXhxwrQc*`In#I8?wtpm7>(qMVec9ApW#0=#FpNR@kNW_45>MY{6Amr-E*zIe;Yd)Dj{e3nxWmg97iDYXXI; z0v{1ap)K#(aQDOQJzvtJhC+&6>+i%gJMm%G?@7B-Vh!gQq3aJK@6V_&jeOKRG1QNWDxRjwvg~ z;p00BjKu3I3r`xDU)SgG_dHg`Ov<-GR!jZittw7K4$tty8G6fjg5i<~>13R(-CGfg z;lf`(e7-?0zmYJQ;GB%( z&dG#O*Jod&^}>5Qe7Mns@vtIiNAfK^rFfPtL~EK}Ayi&=zoqu;Sx39zW7xbo*mO4| zRHaq3SzB${KHPkDxXuj)!lvB;QFG-22?(3VJ6LL0kNJsub8n=lpg1O?^|rq0{pG+w zx(c!znL7h0h3QTDb_(0K%rzlcW<5GL0o+|!>ZIJRuY$WNF&o&91}YDPaHh?QYpYw| z#uPY3n+=3qVfQJO1z!cFGD3hf z*hJ?>X8BngbJr-&o{|6H>EiA9F+ct7`B11`(5gIuG5x!GQ^qp7xH+~u5Yjqy2KwR@ zS48BSvj@A4q~XXnmO7tZ`~*CIH51DCfD|9sO77$w@N(7o*1l+ZE12h+c;pvj@NBjd#dvarats|ZB3dY-;yUJ?Vlk7V z@eqIBbk*_$8w}^~8(O$3f_OZyKm0Po0@@@Jv7%GPYrc6qB=Yi3>^qP(=M)Q?|1w@W zo7~XFMzoA!VFNsni<9DF3T*Y zOz}b3n+ZNcrm_HUv^?C8%uY|8>p&nvfGX-M1VSi^RM62`)_D{QArIokSK6`$-GjnV zk(Z05&+|3h6xu5*E#NC+_TpgM5l3KTY*H~cOwjrGP`=>EOihSY{^y&4Cl z1@)Yq+E*4se_14hx}K;!ow}N+vu7*bxMPRhNyo%$7$EDEFZ%)GMkQH6Lt0|i>WT`J ztGYi>o$`t|)ZACv5lWUt#NmVZ%Cvvu>~pu`#-{1a9VSc@=aT_9NNSob{d>cEKZjb` z%BUc(gg+L>g$qr3TWebJuDh-6glcjTvI(@s9lP4rytv)KF+GUvx0_XO$;`nw;}+n= zx<}wo8q$8$0O%MnK&3P#_Y7z1Rm^(0;9g=M5LQ|`opfxcSpJnMHB zU?yBcWUDl@Z}`Q#tR>$k45zQ+F-zBv^%dTuOHbb2P&hmN9~S^~Hs%!mUa6R=(scX_ z*mRhZ2qIma{#EKJBq!p<8DB5|%hR_ufn?>nS@pL{_i_r!Ny)&LfjAN3lc}`qEZSM{ zw9^Jm?PNeYtxtTR+E&&Gm;&pTioFh z`ik;UyGrE^$fNx6Tan7(f{bF5)12@+uSB19rZg;>$%jk2W&(>6*z7PJik=^w;I^-I z$JR5MDagu6fGr%({&0cE{U8PIdI&264$o#0TA5Tv?M|KKXABU&J|xN7MA z&Xu42xDu@A!g;`QvTQb+-gY^^tI>KcM6ujAI+uc|vAeBKdN#T6j_2xH){QSpn5j8s zZee|7-U?BfO(btR-rG^1U$6qU|F&*^0se2B2UOy9<K!^_+b1VPJ064r6n#Z1*12b`-0$C)HZn>hU4?_t0O_cE*( zphGpPwiG~Y08KHfefu$Jl#pWr>ywhKZ4j^sgcO`|og`Z92}?#=az)j={2RZGpXdd$ zA?|AJI&SZn_Ayt{;Z2nl(N}V^RHU!yeBMzKt%VuFI~9a%RnrA`sMqt_9duY(&N>k1 zqh0ES3YGYok5(sGZ*SnY7O#={E~$MnGH|vEEUs2DuU^v9M`@TApIEy5f}V5np1Wm} zez-?}9PtDCEXEcsa_}s!Jb>is#=$S2SBON@ zf~VPaBx%_nZ^rs3HeDx>>1&(aLfn4cvTmW2gWKu`@1Sr3B?UoY?$) zhOt|Xi7emC0%C6{RlUE9X*7P0$fNZF{dl7l)<%A{rLaOav=cgU6K%^^e^YERo>N+L zN>fm{JmHWy$y}x4JB{Xg=Vm}9b3oPBbpvoPYXago`C{-Dh&`tw-yVfY-;l_I{2#)QkY34x_bv8t|?h5Zn6>_F)wTAFAA3bOXbG6TnJEquDVeTr}zT&_7uRr7h z_N^-Khj4{k-KxBY;VM30=O@J2xm@S6yQuY!OpPn9Y<|1K?VyMnJiDB1?wOdF!t~xN z|L3dPBZvl;o(4oH{3Nq)xmb`GnCQd4r1Iz*DIq>}+*Q7`V+Bj2Q zVeWK0$RF+^0-I4Cj$daQyRG%Xx{87gh5U#o^A||(iXh$-cw)pk0Cd0p){nCki3E+y zRkTb=B|nkphmaG|y#nR;=jVa8S*qVtku(Dh#8u-p@;2Zt@Dxb_AE$EgdB$ zu0w{xjdonbvPUCG#RP-L@VFGRJD|~p}Hu5_9<-`y2np4`qmhTXV7shRw`C>4F$LRBV#W>TDvbDN#u^dW>dc4aSf!eW|6cxx-)?xKT%B2B)%J7#kH za|?XWlAQeY4LT@59g(=5P@`bn_@Lt_obXx5*c_goXN0G<_hnWyg`0;t#>%uA3KVRN z*Ul@>ZSP87)s-F7s<_@+c#0T|3BK8_+?{SRvlX(brIam3&lPEVtE}b%>yn|=4B-Or z(l#`KQj%6;d;WFoVQ#1s`$(^3#Uup|ql^pEF%5t2P5mF&gb>ii-T1Oa%Ek6Pvg=H; zHeKKNr|y1~y%we>T112A$bfs%hdC@^OcT@q)MSOSAnt8_qVQef*RxLrSn?oR6ToHn zVO8?$n%4;p4R6~bL{7jbK<>mgpShsVu_1B}1%46z@WFxfh1`X*O@ChV%xtrb$iMY2 zbZz}T4;1vBTsT6BA`1ua+q|cpmhE|tl!?vMVv!H~2bNE?FLjDa)yC!phk0D$3%MqK zp4bF$)$o^c*tj5Ywar%0&m2S?H!sWBNWsGgk|iHXWyH(T65&d=;Q3SL<|N?m*&!DWsSEgb`a$$II^=QR%7NWEo)Ho@48A1StFXdu0=&Yp{hr-3mnse!54 zL^GXhrqUsVO3`{mzvJU>QBqr6?vHqgV`aPZ4uJ(hZ?sy*C9%8WL8a79@6x)r_xWQ% z-;aK9(eAp4qm^03(}B$PMjLa7Vf|gND(Ff$Y36{iYp89crK6l9Iwxl!%H`t^iBPs^ z{Us4$HXH)l9(Ls|45|Dt13OvUIcJ94tROB6%kB6D28$&M_l!_LQR$_Zf=&fDbozFha(GKf||3SC}rcT(~`q)TEFbX+# zl>k%y7tWnH;hwW-3E=?|E~tfx?LZddjH4Kh4@)Oh2S9hN;}~b-PgU@-WGLj*Z}{FwjQy%yIL-|;P!V4aMGYoP#zJZI0^YK7;QVeZ#Eeiq zm#E~$R^*z+6-*m9XB=4GY54g7LB+V#o@j9`ZE>ST*lmZ<0lpm5*}~EjC|Jt~iMdle zFtJ-)Z0_(!^;aEF7|X9oEq!eB+h1yp1;3Y?kY1Mz-(LOVRrfD^UTDjCKV!E&{y087 zECAP(oL6wR+SRGDKQL)iF-4l($V}}8>xV=+b8^Pv$g3i8c%_>E5S1@%mPU_oGT)1f zRkGgUTNu#4@&yUGvzN-xCPu7{4DlPWfp!`K@Cu&BH9CMkQ6+e^ei^&PnY_G<;o;GK ziB0M>cy!o|hD+zSAs>VfU}kGSzU6h5!rfyrSyzD-?+#=>Y58iIkl(!P(Y&ZwY418& zwDvtowcnzDwT`-5@rqL+r>9zbx6XqNJ6?I*cGs9ziOpU%CjkjzAGW&eosp(`!K48# z2i*v{?^3^TpN^ofs#VQ>1GPgQ0=kQ*8*m7hTLQ1b7BxxL8}&;&B#)+Fi#c8Ub$1HE z?W~*;!V3_ctg31Ay;x|<#Y_&Uy(XmE;)v)ileM`bGk;D0YS-45^IVxRV*G^m#cECO z^yXvQLlbLD#(?i!j(wlYya8T1WfRcBr=_7vl^D8^l?QVJwl}ewScM-B zOR`@3#lr@$K=8|I4tBHHpmDi0G`3AFVyA8DoO#zbb_^`S%DywAk^Lm%(Di=hlM^wJ z4jkwV9Vcqm|CxM93#0=8EG^g(SDF}7z=t3e6@oh{?e+gRvWA-=ZH+hvtQ(D4eylu!H!^bck;@={K#OUdzM=VNJw+ucGYb|=oz`sK{h0qBpb-cEJ|L2GC zZOL?@(WSt3On#K@z%~UpE~{3x@`9yCBw13m^FVFo`S&_9fqFQ&=AvT!U{4T^xH?7^_u!i zovP>PW695Y;m?Uel3)FkL!hh~`yZAUV@_-bo(m(={Zk0B?awBY;CXoJX@@#fF9Pc& z2l~a_j8Vrc?ITZ_an)^d-k2R|6>NL6_HdG-5$xz_MtzzuCVe?bpFQGBe2Rd>!`%y; zzFXtkr<*|&o-$Vbm>(|GFI7~3(0M;s=Xpw8lHTN-Y;ug>NK;BmiNIX~;qJZA-U9Au z$8lOL(ev+vxOitB2wGgOb{SenmlB?Z=6tDge^ax>P{c2JChKhthIi6dn<7J^WT=;q z+C8g$^z3dvC@)P(A|cG3!(J^^J}`OpN!p!zqL^`Uto638xg+qoWU0~);l;)nXGV`u zy{>J3Uj>a}3*l=~J7KqRAV-lRPg5*-wBFkm>d%bDl810)Ua` zm#n-Di{6`XVyISfKix>;BZy9Ay6fzBBsNDdFh zUjZZ!7B?NG1!ys3^&G$YmDiuDRTi4hAe>7UW**XFldR15loH#f3)`k;xn~0bw2_t@ zX(CCx6-jiPvq1Afg(W#x*Cx37{XIaye8Qr{$UsJBQ$ZLV=HY%@?4fZT>LxuTEW}!q zaY&rVo%Q|GYeHOrK^ap8z|Z|0g>MVk?D*2>V@q-css453kzdA}#(C!IGAW2}(q8>G z(KR-&e+wjHg>7oocf81Q0KjhEk9pKszrw zuS1MQh4if`bLDirXugllPcA6)8`6x1Qt_(m5(B0B4%TRy*3^8A)ni>xpGI|(6%l*< zNm_^J7OX2~Ur)6q%JqBF`#`*6S7qb8)V8*ypR+p>C^<-3R^yGt#QR$Fh?#llK|YQM zw!Tl3HRqIJo&=G2f*rj7A6;)97gf}?4ay(jbj=cMmbZP&3~?_wzjO`+oJ`{9xvs9cx|dT5DbV9I3=lBT&N4CkYr(^a4M~ zQ%MRxrUM>09V7>Aq_3GI^s{Fpc6q@CuSkgriDKJ!SjxLx1?Br%06UpI> z6%|C4S)Xtd<>Bm!uL>Qp$2}(vRoIJhaOLGV4D@#T{xZ7I5P4xvV_+JPz35l7KcnV8{Xtq%9L zL|1l`lL)E?FJvcRR*CnGo61C^gdV53;02)Q?u+cogJKJy%??b$An>Y<`Rsv}vM0PPB$tfYv;2`*!|Z+8V*sxj=09nS@C6E20~-5wz7UM?pfOPI#)|F&s%!pOAJls%g3Tf;ST7HM->8vB!TeEFWve$=>-R zY))Es!p^LI<8oLrq;!xSSzu`PQ1VTQqGhv9OCs#JAyT2NIR4-uKmGXOBX#T7s?5iM zlvT=zQRzzFhnAqn=u>fz){_I|-FtV83ui!`ZM0odoY%f^V-fqQl3rzGi1$2?9b3fm zjv7mg!tbWH+Bc+Hu`~5}$dy4#pJ!-xSDe@3k){64cU+UxVncK?tV~|PoR#b(iBusB zeMF0aT81w)@9eOW{!#AbdA{{T5tKMGq?EnYVU29XQpeeVW~Wt@(bcyAe}vzx>`O(L zxyiWOKcceV{7#Iejr(P`uYDv3bGupB%<&6e=r^C<5vL1+@eIqNttFZ)fz zHs8(C%A08{umP3D-uJKQFBD3CD`uBynK&TP=!vY9=o&V#CCdW|Y>j}gy+_)?)j>3p zKuF+bH#z)ZF0YOzv(+krb;|Z242D5jutf(3QvQ{VxsB(lMZU@{8z} z17(mqqy|9Rf!%@(#anBb9rE+!YUEI{iNG}o2{z<2-|?$|pOtN5t9Ikk+23-(PESyS zV^;|{@eE#NcQs3h=_;P9))^?hUy+%4*c*%fjyZBY)Jl>ybxIYjvm}skqlr4KS_XDInuBMfvTTi8h~B{^yoAB)=&P05haAd{A@y^R<(OD~|p(7WdZuKF|vr1~ShR6Z)$tB;t)@1t*Gc^5Di=&B? zxAAk!Eealem0u8hw6wlt|BEpFDJT?eVY@P}Tkc$W7av7$n|#yRenLichDn(OuOnq< z0Op~>slUoil1PAsj(EHHDGipZ8HX6+A`VmB>0?)l4#O3H+X4jKP%pvIPdNM{>I=I` zwNyLHbP`yD5@OI(y-(eP3;8rhImf+Zv4}N7OHsCR3wC$<(UEzlp+F`1Q|!|p*K}e7 zv;2%3D4kjK8Ij|hBXc6s1C3zie(8<%lvejACSxLw8Zt@~~asG6G80#zP zq#G+9-6^GjBA&w9=kGyoFE~pDo{P#JW#EdlfrKkdXop;wBoQk_l;tNC*4TYqcK1g{ zAfXlr>*m5{W#)&84bF9Bi$=ysd9VQe)Q_5mdvIVNTybE(-DRNyF=xns!Dc+g!f|*} zdcIR)nao^n|*TL9Rd_uXUS(%k# z4l^CMzfpgPxfTq?Lbyl%dCbz~%+0~Ns0rIPrg%6I71pv)6d+>!-o(XynR)(8+HJ5s zq|9Kou|FvBlVZ`3;KoM>y|^LEi_2XcN@ab=i7y|Qo1Asg8)ihxZ_zbe5*{ze{E(ZY zGG|4GjR+d_TQK}&&3sW{Y~o1}%~-n8#O?g2Ii0AkadJLSc6eP$egNCk-}}Z{SrG-n z3u@B$lL%#rF4dnTSQ3zye^_9ZE&lTyeKWR1S+Zi@NzxUZBXCz5v2OIu6O%FdB7aUIAPUP#O zdHeO&iz@0NVh^S=enc*}B!1UAXjrC{KMmrPbvgs`wE*6>wtmx zDlvOMF;-M4l{PVNfBd!O%7r2C)pu1-J0Y+!b=a~qzNAHEvwT;0wE8EeuQ?`@Y!Xg_b9tAzsCtzGtOPqAATTze09X7-aMKwveAst*CJ|8&MGur_ z(!mQGPg!82>P_Z?;U1U(`C0FkQ>F0WSpZ&aJy?TnWB#nsy5^$Al@Ft;y7fs3Dv+Z_yi2IZ|_(l8_|U%jcwjh?h&*7kFYUPV;`U?CU!%`5(j} zKHviz+wumV%mpB|AgG9OO(Nn*SPqUo|NGXGFhrT$TO%Sy%Fa^P7vuG{#iKq&Hx;nR zu%gI-XD#F1JBtbyh9Y6co!s(Ge-vGwa}m>3pFg=}QxH?oJ;%r2;%U`b?oMCpv?V+2(5ZWuG=P@Ad^NEA%I3(kbXOOc}%uaf*Mgq5RM zg5g$$m*bbFyM~4+&w`x6+t|>pcDi$CvSNCr%g}po#x|Dx1M#y~lE&u6Mx+e?lHg3a-4%_Oh*W1^~69iFSLlFD3ip#6BkK!KZPQ!y+ zp`YQsBLhbNA48LWmKTEUyYngu+qX}m*Cco2RiKYYJ!S5@c4un|A;V|j-ZH>o;TqGG zR3y%(FbcMnFk_kz__!$V8jFGA(*c6FtW>wKY6*}F9a*hQ7$?e~*3@bA40Y<{2R(Se z{Zu)C&Gr=AvXU^vEFt@S^T6V1?%DBa*FeJEX>08?1NIP8j;Jq!?}AmqjR5u5GEkC# z_1{{W94_aQ=w?n+nvo~3Og~5sJdxjrU3Ughm0zMxEbS;*KU)1smN3fZHf3e@fTFxi z*s2_f^COrR$)<3{gM(?#7vj$LO!rYb@+p*af`tfdY+c;(SA6=PVkQIVNP>_8nRV_k zFQ(a$m>8b>=ekeopD+l1-{2Z6HC*^|C#TW(_RmW(OH9;UjSu4X$OG-Am9>~$eBI%1 z3P#fZe28gv;Kf!u8(BMt161(TO6Arsr)rWG(~C5z4Je;iuL=|k^c0BSUirr3v)5Ja zM?Kb-9s5P&wW^`Sm!pFcHC>qR8l^I8I%oT)&%etSq0&Qja22}bofxQLl`^A!-(+!V zJw%Ui+I&l;1bN}RKOHpbYfg4Ib2e;Ms^@8cO<7d)uONy__$*$4BvQF8J&v%GCdeIU zJxCPiY-E4wCvBWy6++1!uC4R#`$z5n(E>~}jh32Zzv6!yRI`F}Va zAo1i=T33d*f(BDuaC`((>g6#T?|D$y%!vL>IclGN##&Xa|il=3$y=TqBX$v+5@rcasYul zkWLFk70S(o* zvr>HO%P&g=r0qi@Fd`>*t0mlPIWIB9G*g=?rdvc&T~YCAh}Wmc`R^dlFMmqvfToov_S;-8}w_*L+_ zhtZa5K%xtiz!T*~)OmjAU-H2DVrw%0)9LC(Rw<@YIe#pvyABhSVjYtU4uhY&D(}{0 zcq-e})f-9B8+g#qo`nh>WwrWV-$97qIOgQ#&OEy|+bLgkxTcb2jr=>-5TYr#U$oGc z#J=Kuv@E1;JpOkHPU3&mOSKpWC%7H7vl!lC<>6RUTVt_u8D#nAa&YTSb|01Vg3q18 z`P>a6M^qSHKp4TXaW2&knAZ0+mgaZCF2G*sY?Oo{HYyd8+ik+uVqmeiGk(Hj<$c!G z>onQJ2V{g*H$hCup2*qo*o=wu=n=|e>w6NXM+V>(4t_tAA-)H_qdE=msY0$sZAR?; zXnm4|o>Q*d@eh=3-RYhi|GtKy^ld!Ov-CzB0k31vcARvN(Uj(32IaqGo9F##HU~^w zJJAysKwBCw;bwIj+TS9H(1M?j2}%=-dPmdx`mP;KNX<|uL_vn3gAdEb9Nhl#n1!ZH za8qkrby}Utxmb5GjcmGnPe0dPy7`uArK#geU}N)lTi;w}t?Tq6v5#77TzFBcmtt$4~b5mE&lDqe#0D!}TQ8#9z`b*SGb{?chrp!-iE}rQ+CT z0&@Km-h1e&oB_eEH>I^5Dmw?%-n{E~`M)7vo{OQ>{Is2hf__;@pacS}Mq2C^o7<&9 z0`2Ar1VoyWQ1E;U1y3Sy$Y&F7DjS2KRPD&xNg*aSVBqt+qtq2*wWK$o9d>`E)r5Yw zj?so_?@vF^Mq-*L)0%W639-)2lbHy+?IyqT+o^9)nicDv9Ve`fC46v+_Gg0)Kj9F; z&2+y0yWx@UB|kH-_(GN$W2tg@cpYjdPFp?vMFyYag!cHstV~1sUQCDP+AD`%d;8l~ znsqgw2T>ElE-lo285wdubbdkSyTg@NKA-Hz+A2R8{a$C7LJJ2P3E@b*OuK=QE6{wj zwES(VY&6$p^J4c^V^g*aeZoh-z^;xJyr=bk1hrE{OIvio(XGFSa!vFrwFbCKhZIn85f#QM;L{EqQ;m}~Z7kZO7vUlHlWwzsc_!qJ;RjjTu6h&)uI z*z_?CyfT;MrMZm7BtQ3fPY7AP{7* z6%s;8)I%(8Tx3IKTpsOS@&W3iKWFH>lS;oKP}EQQZJ`Ud`-N){#YV+#5Ip!+Wis1 zG~}w@!BpfKowLKIiLyqU$1?UhYsT=u<$wJ+$fS4mo;cUtz>U9CEDKQm0FYxcNTnfz`}A2Z#@SW;!$8*1 zAFZ#xCb34SxF@QZh7@m!BX0<9NVP-vlf=}WvRdpNoZ=69n-4IquVtoH9^OVb*x&wlpgW5Lm87cM>uXc;njAz+Oa|{ zgb4Dz=8|o$)Drg>f|K^aE02X1|C!cgy1wYLJ{xZ&mf>qg_J@CQqel87v}!NB&sjxR z6u!DOWmJ5~|@ z^Ka;yZmg^x7adGF46>{=IoT;HQqP<)L@2x`Adt%zE2jB1qTMg)b@fD+s*kW^BKHk- zXVaN(UjJ?+jkk@aTQM@}v%l{JPWsICt(6%s&J}O}@1{W<0t@f936BNH4qKT5zXt@6 zkMzH5eq4=3{XqD(v9SPkkUtvB^M_epg^hD#A@$Xfw<} z8oUfwRO~?IE`P6^OLh#>k{)c45YH+ZiC_*$DAtAZDWKv+YOoHrZ=ex)-_FN(rp-g< zY@^kkO_7*z!eQcofPe0eQLl%O}dqw^v~cXHJ&^7FB(VG z5YkRDGrY2YDK;zIehs;35ftN_X7O2n&mE9y-dlL>>LC61q5sXOlI27ws|?v{ z#6o*PQcP;4ZRNb-x&K|NWBtFVJ6lA;<=2apvHQY|4OhfW1$amkL-*0K?ZKY@@xzy9 zL0C9Jel)Y$*uFolBd%CsolpvrNz%^@Q_9`_&(>=3$?>O~o(5y7^`s8jaM({Re?}ff zX|V3i^HrQJes)u^ORmNd?P^1g2xKPB<~sGodk3~kf-FgKb!~lfcM#rw9EpD`Y(XA# zrF*^BYjj2%Yh(ISOWbQs1=IFvRN39}J_fU~R7EWs2aa-ewlBs*3p~*&_tWc_zN~lFbHlC&PMB5_1{WskHF|J3*E?VB zoCCB>7o6gRokCeqfcPjs*hz!;U?(p^6FCKZ7%$_gL?7|RhfaiUtm&apo|h@8#SAax z2jAByI_g8Ez{c+Ph&!E!$I2%Iem*Ei>9cl&;# zr2NqrUgB~NflDn{%pK{ocagIWe;jvTv=lXG zA*Bsw70>sQ16Bp)d8K$C0{~wYhnV{1!|xr12^)s&cK_(A{;aOiRXUbth>>ufp`8FX zSn(ULyH3{<7+$Gc8UJ@|J!MBp8@~kik6jZ$Vl{V|xgNMmA{^0WslFWdSgBFZFJcs)~wRBVjxu>vArEn^u%NQQn0HDh^d-&@D~vbm+WET+i+t)%V_4X?@Z#INh(MWbg{kz$c+L`=xI z+7(Jm^OV+brb8UIE3Bxd;Sv)j-t?oqab@5S4efUYe&@pt0E}7J|BP8~>05Ye0rKN; z-uDSg@^TQRdG+6v2KaQGqoU|^|62P_-r$V)H_e0fHMeB-&4Mk&tF6?dVGbRKA^Iqz zrhg*({rx?i9acDZ(pL)fYZCt|Uj`25xpZ-gz;O@6ZXpTLH(JVE%h&TY%oq*7?S(!N zlFk;p7#|g>;gg77sN7%=pp4(lJv(jsDm@*U(9)7JKdQP=?*EYVVVSUp-BJT*XH%$C z+tJPe9ogpDb)6oZhM_6>vh|wee+AlLDA0;!QS^iIn*2TNOp{!qtdUOJfRhm|-R27` z6nalhQ*e(%IT;t3gFYpZMZf+!;CVH!@n`#^x5t{wq>Fg7znYzu!QcyAhkpzoDc73W zHT`vtN@Dj^&L{%8?a%MEGn{Le)qY^Th$6r&)>a?yw5QN#rAJzHxbM{+6yPJZ^*{U3 z?cgD2bH0Yzm>bJoUt`A#Rh(`75AXl33}v-H@2LRyq~>6z2$2=ulckK?x6}&o3RIN6 zw2-VhaI$S^LNoW`=#2aSg|8peU0giru=f}WXcsy7AlQJnSL#589H2YBP_~^HZ9XGs zYS6hu)kqkJ7t^e~w?3VNKyPU~o{KbhY4->P9-b}!-maaL^)e@0wYH(Dyxw)_YjtQl zc9lU8zA5oC#q_uhyDS>?+Z2czuH_sDvDNtbvhlF>FJ?CsVls0Q>==CyaL7u`e8#`y zF#6B+Ie4)!7e`np0A6p$Q*HTrl^N7F*_HoY^b4-FEhtS2 zCw|vt5Hr8#R zE|HwM2G_eA3HxnurbfFVT~p4cg?6#`u3L9L2xN}eotUP}FI2iQWlmP;cJT)#3fgUz z6%QAG+H7uomP-{7-L1fCP6{?d?T^w&z@mxCuxzL{ahiwqM{oSgY|ThJwY|LeIiexD z1z*88#gKImjVOF1sZ`kaD!gBg`3c%rVm}9 zkyNphRexZHUPA%5MLs;Kfdn2UZ#Et06&TYzu}Sj|<3dsOV?co2%J#tU;DrPF&(`(K z;Xfl1$x#tCgCH3!FCu|{n?BAH@HY92&hjaqA?fc=S24T)NW{Iq6n6$3iVilNvKYGi zeLGOm$6V7fY0%jYY3y4m%w2r=_?&E$kj8Pwk1IPH9U{~8rndnH5Bd7Z)7$TYxu7y{ zv`8WuaOqS<$jtTiR69cnKk+dzttuhw=&$IFjTAfIS;? z^UEj!-^n~NT(gk{UH>g0tb1^VX`$hyq%c0P2PCU+lj?gU%@Wd1@#=o>zeJtnc>U;- zGHb{aBloIyMVTMDy4zDm2MsW^0B&jpm`zvcc5LvWnt?i zY@vMNs`c(cI?|F6&^`&NRXP%`EI|9*A3=!~4weJ~@~TR#%%luXj*Dmrxexzhn7p*S z9U;+7f3xh3nIJES1k>k=hHTep1_m!fPo{TyW&CaWZkV!hFg^v%$F>nSI z>r2@~=ADY|*jUZAp`r=701YRw8cGP$F&157-iVTF4fA+bP|9s;I&7sln$Fk_iA$^c1+&crjifQj9um3-`uZ{lez8Lb6lVdjeDQCL*)*3-i0&9>0Fbr~W;T3TREOT3%)2PDTMQ@Pw(& zK#WHQHo!mR_93P@E5T!C+z{JiVDg-r{GpCbDcH=5=BCa+WgBU$ec#f|@&Z=kQ!#7w zSJXw#`_?Xs3>o3eD>QYO^2y3G{f1h@AS*s?`ZSq~H`1eTY6f(S@R0?g-`Cf0TbnoTz)qWKC5Z z!4J}R7BIi>5;SEyAm7IBoWTn3X6Fefr0n_#alrf%_8!>hufDA!oLL!tX%=>=?K-KI zyCF;DzlUe6URyk2Gv9qSacf4i(Uq{m>Q@(3V^l~xWrkJ#r?*q{K^OR1B_3olb`4Tr zL?0l>0yc8Okqmd6ds}I3`JMK;HrB#=MR&p$GB&k_sAKjROXE_fvqC+aWlnY|6Z0bk7%1;T<2dB`1bdoI=LGC-oZoMZ(=VrA&PpK zWzgTyjFEc#6CvZ^-oMPvlxHJ#{aYGQF!!%L=;G*Ez9&8h=pZqbn4XhTT;Ja@u~G@i zhTHD8Vhd;>=i6;RuKe6k0_*lHu2ST`i-bTXoxfR1zK_e+6XEbWEI zk!Us#*}lI1)?o%Z&DtjcW42?@H{8IY&F=Cxh~|=Ddu#=y;XmA!vZz{rcm-%nntS`r zQ2WJC0>Asjj%GILZYCA;2Y9TD> z{6?0l;r$^uMGbXCES;AJ{2%$k+mwGxYaXblL8%b~0-NZYU%qH(P<7;i(0W_L#9Hq* z<&O*-tt*WxqORQ+@V2hLiCh^Y0whs|@Zl~%bno385WA~l6Xldz z+@T#Ch_(hhW-bA^Qb-UP`cDd6wv39k%xnG!!Wnx!@D4Hdm-*Td2b+YI+4nNwluXD) zY5n4P_>TMde+JpW5!jP{6!Ab>=zKxaCM z+Nt!i+Pb>D&+x~8X@zj|`0DAfz0emg+xKVQ-Q+xDK3D(nN4wbuKF(XedhEG1i3rE zw7H&c(>O{T(2=U||7&>Jva;dNbE&WJbR~XsG*6Cp!rp#tJXqh)ZhT{X-S&$6UF%GR zv~21x`aN3DAqTeo?i_=wx%!&p#dr=c*xN^*0#^d0H|wNLYT_#{))RhvCmza=Zt554 z*$v!;lkcpog|(VQvV6w8%5R$XgMoCML;aEjYX>q$U)~;45R4)~At+~Xy{#wUz54)C zOge@v3++Sioj<;u&kMU2gKPYWk}-;7GzuTNOS8 zt^(b#NWbY>=aYqe-vGV3n&mYC&(<BZVTTwo;1)0Anv|-|1A`q%;t&{zE;1 zXf6bNK2LNql_>gpJpk{>kb3@5Yi*~GbqMp@i3WYu>wT^i=oZ+koFTFwc)9fS}R!tNyaOl|J!;|YBmp`V9VgF^AQ&v?h8?L)9a^xGrkX4faZG!No(+p?i;ucNJ zU&1c_V_=R1*3)lsCo{oaA1bTTK>_m(@EfBHt|_+vqmE_?!H$8jVju~Y4_MzOuABzP zeldjVBfTf-FP|}v2q&h?Fv?Db=P#v0@V`Kam;(__i(MIFX{M$?Y}F!n%M27;K5Ik9W{3&c-hVkP(=Cy>Z)QLlf>s}S&C8hY*Vp((aj>b1 zp0AE@$p0KvgHXQvD6N3g68hMeJEtjFEPM^{*XKuQw3J~B@RqOO?BbOP>V1Q=)4(sMoF_2n!8lHTB^sFGpb z1Y<7BSHS+F0zhGFJzkmUS3YbSi&&CllUetS=2^UBDf(gDY>+LxdQwUU6Z>#+3G z(`2}AW0W?dAP(f?mi^zCx(A+)9&DfwsFQv;(Hf1)&0v9t=r5qIe!s6lF9^MQT^P2I zwBb_Y<88N`UA~HQ;D6i-zYIF;r0)0W4irfV%0jfh@w?RjgL=F}*d)4GjL`ibqW^ye zQ6OLkXENmr`QFLn0mC1C&kP%ZjNRQ$Ihza%Xh1*93$Qn>@{kNcg&s*NC3!}$T`9_p z-1w}EI*<>4bJs3sh3L#vQUxbsECPtFV~H3-&7mpC8tG{Gm|?F$y)z(<wY)yiFJ!~XgUO(qM19oABg2w{E*xKO$HRK?4t&p@9Gpi6 z_Z3Kx)*@GG`3j9tXRFZs@guM(@9%}qGQ%jLslaS2p?*pQRDgf@z!0eXRD-cE9JVt4?rzP z8#yd;Ig&(Xa6|S~5!aVT&@cS)xo>bn#b`3loJX86&wJn_gT<(x(jp1?-WC@zmJhhE zDWlI9u6M!OP;B`+qShJ>X+K$?Vb>~P^FkWZTSK;ZU|81B0y@5`4E{eb{r^!;0ON48 z9X4h)hh)Y^k!&wnVDZou*osH9m0HfBh4q&xddC2XWx6(krI}&l(7#2wK0xZk-RI!X z^~;6*?}8yLaVLHk><-piU~cyPhLZeqXhK?5-TJlAkonWTh-X6efPp|5S0F310<o8!x=E*q|nz55h=l=@Rx`mTiyd~fsxGwcYuuk_K=7sOaatjste z=)K<+KUvVqpo=g+%T4hdR6p8g+URs)p+jHvge&cbDDz6|+H|+<1s8zihMw;13h+Da z#I#RXpA2s~^|j5BeY!5m1zaRm#5sN7V)KN61T%Pdwke3* z2^;tm*W_mmlmTK)QxY$42fPrxCEe#^XIX4o%fVPOMV*!Ip>WBO)gmV>GGHbb<3bRz z5_lmK<~BRm+d7jk?cd<9**tA!t5r`^SG66U9`gJ7wJBq`xs!1_X4IfaFZa# z^6M4l5&+T$cg;0oJWblrVDp}BgBTidtc&G?{bO&<1nJ~E|j z$yCK34Y8N4c7-o<^}4o-;I$8t)kIe}OV}JY32aBcRwom@-1>^L>*0AS=>?-9+GkBU zD<;M6pI?*i%eVYJ?QQwDuXwIOaj9^S(F+T+zvjPCCQr#WTm|3rq&|ctX=qTks4K}IbTRN-IaIe#^xk%FZ7%QBJg} zl_`Jb@idueT-yV|Hqf{~ewh8syK$BhJbkA+R!e#S!b_We=?*pQdr&0$yn1-C2UJBZ z&Hm^&)ZF+;6Qh%skrX|O+ppC^&{K?ThoBRU|0YHu7j9g_-IP_Y;JOe3HkSYOsvrp$ zhz&9fxBvIX0#;6C03UvHBVxc1zfjZm%_c8QhUj8X&1P|%zcf(2QzP=VFo1?wx>8n{ zCB#Q3A3Q=61CE^(JbUZHP{D&(*n^$%)ofQ)J0H}+EqD->T}L`MgCvcUA7r9zkg_0g znrjXRwYQaox071e0xzRGQ{^$!-Le!9wqTwlejg8}P$e55W6 zCS>#1KTP)D-5-&Q(YkTjZa>vPK={wOod2_<*5V2*ozBLFFeDrCfXkI-`h;P<5_X+w z7T?-tWO$t!n{&6%8bop$F~i5$?N7i`{(SxRiUpKyxgZHI`{Olkl}-!9p~>yqzrId) zBTneqX+xTT%G5Dt%4ieu8NSdVHq#`y8T9Y@KjL~@o289T?2Bchd+Nq0oWaKHN`;vw z)4<8!%bH6Yr!@*@!sQPi^3OUU=~do`dkG&8UT?-Q3x(^2*wH_-IDQrS{5z$ z3@3a;L}>KQPx_C)bd(a-oaoLHJR{+J;}TT1qx-(9xOgaO>cQhe zpp@DjrnJq-L&|xORduZ zNxne((Uc8b(#^G(`n}J6z&!L#`?;-HnSEfO%|W#u6uD86?O5(aG+V5={T4@wX9&SJ z9T%lxHfMhIOz}rBdWmR`aJfRZZr>5~g?sa7(R097W=Hp(=DB6o>_K&gN#id(pYA7c#I0RD%8&H&SbBO=n8mrWX^u^o-<= z!{ryg2H5ytA@x0$qi|y?vsY7=Q{H3ya_hxea=@sdP{RX^3Cj!$H9~8h;HXEH=!VMR z;xSdB)nK76A#E12hB2(HruCxM82jR&idI$Cud%gZ#$!9=-KkK#1WXMa5{cR44+9x&#mW&0QN;hXe}l}M78-;p(l_9Jm|!6 zi^~~0?o)~dkjEb)++C?z6q~nH{4mC0=LzceLz{DD0rA<2WUn}fs-w0d(g>0zBc8E+ zgJw$*g4+#Q&rvbkYXxlb#~{hMw!xq&gZ9qJ{@f~&y6fmr-o(<{{~m+(#Vs{%XB~=+ zsBBMi;TCsay^&0gjy*blP1wmKCy00KPUD#)xJ8q-vDyQ|&VU4d;ltajXq1X{ZUP1;IOsaD?T0W@3R?d1sm#t-@ z3O!kLZd>|QVwE+83<>^is$2!RSR7Rx2kgIU4m^r3;85NWGXyw4k^(K4?@7!3UWdID z%FOk8h+fRKMxj6D4Cj?z^L&d3P`_8AYrp!}G@^K%_607o&9{r?xJMw1(9C@WBp)Wv zxR_ZNMX>B$Rz&X8LgxU<{=;*!dB7=w0)oR6hm-24*ey|FqQ93t11!Mj&ih=ze!G6Z zzxG8544>!cb`m(~CJyE#K&jcGgF0QKcSj738omGumQZoI%Mc%8^`VJCY4#yAhLl*b z{R{`2`5ey8$TPY)BBo2FUkkE;iWo`aXW|aw`5N8?^a}-L67fP(Y(p7px+tC?A<6+i z$~7B}e-6mgjg9<;;*!_gL&{IQT4gQ}+p7$;jCh4w5)t3aNx*2nFDCtP1iLISGoodw zB;N$wv)N!~rYt>LnN!Y6-xdsNP~8VljD1cI!Fuvmexc!I%=YN{i}#{M8@FuGifjBsXEcGg_WgzWNH{tIz+vI# z=jwQUN#M>8_;?E*W3Q^pZ^0UwodyF|6_p~YOSRX78U;y=PMw^}uhP41c>`xOH;#pf zI+u08OVBq6wq1}94Ad+!hg?9h}fm!74?K`ul>#`#6|vjU8^yRrgu_LH-UdIApr12=^U4wGK^NG|4_h7%+a&Jfaiz;_QynW8KGL4zFzm_gqUvH<0O$?*#*Lm-cFdIpg%tm2+@lA_e-IM)nk3=AyZ^z}(EkV0fa6I+rvd&MK5Ql0 z>V8>UJ}i+!SWD{0h;CsI3A`S7G<$7-9k9+9v0r0NYdwFFqyh`jlqY8WW2k=|WjBGx zcG+cxEU;p}GH)3MtvI$vgh9XrE_ZD?NDjn+Azqtc9cf@EL6p49jjS0 z_^AN?yb*qlMs9Q&BImu_-dAQAPoT}Xt#NVYy(?YQs4)76h#q~}yS&XrX~3w=xw#>Y z#Nwy(EijUhB;A~;jt@P!89c3VIZo@3nk*V;O1x87q8x^ns*G#(6Q&daCxW~)wwYR< z?twGe_7wTvQSyWkl!`)7N&rD=;cTk>!Z#BfY4kaMQjSEFqSQl+;mg4l?_u=qxcE8? z+RlZ`%o8^-F)oZODWO;4alp1LZWXz|hH8yd$;qT_up=i zsDiTjZ$2WR=^HiRVNi*ov)E0DfD1WSOwPBJ zM2c_>Nrf&{SPA%FYOTN}#D6U%3_i;}!|^zJhAj=1Xa=3{@c_6JfeJBQJnir0G3``5 zrBABL&KYvhm;1Nl9>S;a2mLGVehUJRW6;el6E}oGEXF<)`Qt(wCjV+%58qhUS*(W^#9NHKvj#px^=_iCZ6A3R3pChEC0#u`_}bNHy3keBCyga zRIS%FNe=iLrmI2JcJ9H*KP{ApG#)arLt`LH{v&XWg?s)YX#kcufe#?4lRzZFTfu4& zWp8?sPZWd_7MbIZ-UbT!+NoHZkA5wV4@5VQKTHaxUi21u<{-}9L?19V77U9EG+&b# zyKLOqp$4?7jKW44T#T2SRvEh;%7k4eB0qz(uepq{wCPN@8|g1)>(Fa`dH9;oqIIoG z_0mU{p9C{kCZ$R5`0Av6lm4?BZdbNahsxM>l;9a6_=q%X4$+{p5usHn0?%+=&<}c& zE2#;1KAV_a#MhoPosD6g5L0mlfy^%{y;eUStaRA~RTG~1KUZ*lr?Xrn9u+(S=2nKJ zkUuBGTKEt8+UR3nTpNuqE6&Bm(KfD}pKnTSZ*L2Md1n1&nf>%h5SoxZ3z74aRH1)VNyKr+T4 z6?>S#-ukznu+DLqmpo?vqVYnf)<9!O&i^>SM(TWin?C*iP!Jk{_$Gk))a$miduz49 zvKTL=;&aj;mvKa{&QUIyuNE}lv0m+YrIuS^-! zVh_YgH^XCI--PYV|0W-Q5|$*^^xf&x$CK6Oa))czuBaOEKc_f&f5t%4^R77FLw#$e z&Vgo_vj0(mSI)bLuUeG>BYVXf+(M!Bs9yj!{?v1IDuyZ|`EyPC>m|+OB^6P3SBIP);WV9mK-Rylo zqY>zZQtn$lU?JozS#yB9ibEl2MfwM z;F@S*O6hJ%=|-fbyQE7RB$a$9>6Y$Bx<$G{x}_T_>6UJ!{`0)wz4u?sg)BfhF=uAa zo_*$c@>X`Gnf76z|8xv!=P)g?_x@@JFJrvjtz5O&woCTU#oP}-2g{B>#`HgUfsd#m zo2}<1zuO~}>A|}IZKT5)xoMf8gv)SiXCk;=PVN8R)>XJuTF+6Tm6ssSKv#}Q(+KGj zF4nX-+IYoxwSPl1LPb?`9WQ@O)!=Lbv;vkoI#>4_ zYgX6vaU>n?(Yv*V62XC+ zO!1YFuiWcz%Vc#DY#uS>X)(hSkQkBxgpe;6+*U;Jg1XItR-jiU8?O+cvVE$wTHXG* zBnjL*Nv#OS5x3QVqV(N@y4!t4?}3rf)g_4wx1Fy~duB6;`vJL@9Xc@t;G`*cK`K0ag--?>ePM(pBFCrLZLVGwyU8^yb>|!dNeZK>;p>bd9wpNcdXakYX zB&*^q>w)dXZeUlxtxWa7bd;7d+OI6FSg`#6C=nb14E+zKuOhf{LECT^BTMd|({WT) zvf{pUwT}>}*gLxPqD8wb5TzjgLmcCyHXTp}^z__-P2XU>(M&PE${R1eAM)J5UfkJx zo1CNgo>#yLII6F>m)&w69)8`Kw6`AnO3Wx2UixLqr-w##$XwWzaE4H0|Bamh4-8*W zky0Z4f)W0>Zl26l!Pohfz7yQi^wTeLY|zLAo+R?a{gqJzk+3qOY=aMC(pbnjJ={m= zKc8ooJOlMotkA6HLwP%`$IpXBUj~p0BLA|-YzZcQrGN2nDa#!*g)X{LdJfP3>7XUM zGga@TaS>;68R^LG+rMS53s$r?4>dAQFy52&d+K*r#LF|MW*j2E7bYzF%>z7yZK~OC zBnSpoaU?~>plQEKH6v)PNC-z<-R#lt_Kh}Mf&NlFMm2LQo_t#jK~l!}cR2j5(G5bZT83i+YFKC93TA?KL3t9KWK^p$|r_nvxUph z%iU)E<|2q=hE)Z>y(MMPTB8jiiJzJoS+TL^n;ZnL%YJ-9?e)`{Mu{(96~hjaeGOpI z1QHY=xr5IDKsqAVn=~*mUo=0%Cng9ewj=ZoH?- z`@6b&twiAT=I6I$-U`0v`}*-oCoAzxM#M(@UUrI(usR-^8KcJ~T5M!P~yQDlK zW$S}Gqif{;k@L%XM4LuB7LDO{$m>&@9?&1b@L%1n=>N2-QwqU&uf!_*6!WDu4l0il zghBviTv|`yk7r~kr5kJNku?Wcv$^dFLJ5l}vL|hIYzsRPkl9khE_bai7r0l3@B(o@8qETje z3_To+$n~Fu2{|Yl%)zj<@(5m1IS1nS1f#kZUxo(F-nz6~{;98cJQ~-AO`rg4Z~*?3 zEP@xnJcVDsi74eNz9@fGA%b7|Mt*|(dn*XIXwkV1)_K>pAa3t5H^;M#Uj4%JoYpWu z4G_xNXNOdmB}Lbvp4Ai9x!Ra0^DV-Wha^)5N})1>pmL7cE=Spis@(oJmQsvgH{ZFf z;Ofu-SF8u6y9jh@6o3n|1)i~o5hF;?=Xtw?dXgm+V{$_NHQWl-o09>>c_YXE&xU=Y z)R@IFI@_$iW+}i?oj{S2fc_v87_=c>EOE*OKVKOy@l|sk$v^$cKf{KDg4inL{ac)C#nSjkHXZIq*2b`Op%(9cRlmH1 z9t|D1yNloJds*LpTfknc$anGmbQ`Ko9ejm-$^?FCTX;$&^kTDwXg=oy+#1ucy&~x! zviyHixYSgnU5=J8|3?cTiqI;$Hoxx#o}#U9NG^?BFTxs;|8X850K`q9eo&(AM5@W` z8+0A8KVYRS{T{g^h(JsVH#wX>L&8A6&Qb>|L_Fy=FP#@YK7BB?X}`_vEF{$CqHFiv z^WQKeg6^XK)oDL)L0e)ZiYAVolENES1&K7t2t52fH^dnY?xq8YhcBP_=?s?L%*hR`gan-W|ase$ggcx_v9GJ=QE9J+U99uIFkE5i%6cW`<>D__Zm zgLr4l$L}Owd}XyTN!D@hQWJ%yDbRALE-CozHe57>ICyxJv_t2i-fjU+SvmSD$@5$S zaA|7-J*ywu%A4pcqn%AX0+dv-7<8y8p)}WUL2TknY!xa1X08SOgscXI7-+Cev*rBW zi55zd0t)Gr0oJJyd4Zio#U*zKT?fOgkbtJxZ3ded3IYB??e5)2M^S{(P7#EU&dd_; zzcalseuTe6RS(XWsulex3D!6Sx%KhLgGf11r`)lh4u3rCd9KIq7b*0JKxIiFi+_jdI~wNcY9- zT#Yb4Gw88dm>PU{y0>qgo-QGOUy}L*UOSidR^<8N;si}FGO*s5bhK+C1D+Ko=n;M! z4SuLo)y{%J-9N>tUkRS?C1-Si!9z$IB$0FWwbq=C_2h%ecFjwsye+PGJsvQDtgrvL zXc}Ffykg9Qc3GnIh9hKBbi3Y6TI0!NfQQp0!)WLKDf@3>kQsjQWJ^=o-u(ZfB`60< z$;r0sh#*k$^fN$oV8ANdsdl+FsFt)*7uCE;6*QF1^UvMV7AX= z8nBNVkzb4hQu`+6^dC!`S>dgjL$x#VUzl29Xgr(JTZv)S!H5<7cuMlm8r}*(Ut#PH+4h~s&N{1X>8jzc4;S6VZd6rw~FSXI(HodzhL*O4_ov$If z_r#}!L=9>Fte>mULw0-Z&mm~x4uplABc^^=tl9?>m7z82@xpW}P5I!SdaVGjQ|EW( z)2nA~?~MA}Ka)Q6Pp`tb#X(zmUE=TvDBwXF`Msl`2Nz`8t|jNw*_5@HHqlpd#}x{4 zg`WsP?HA(D?n@!C)gTo9#$jK@{zaNBkXm|2X<^~r@a-e|=b@2FUkC+AHM!soNGH9c#dp?%R z3RBA;bFgH*wZW=u*^~tTT)mZVfi}L9D>WQT*<2tCLT(4H+veYTK93(eZ_p9jnda!1 zcWyfI@L`U=gflg!BmnLIupe-ElyEEbv4x{Lz5Zte%1rnY50@sPhf_oMV}tP6vBdtk zh)S5;_I|(&8fihW>bPJt zqSd4KHyoTTUJmnnk7V}ADHxJ7!IQZ*VXV(@$`|l$T8=C&F>UG;)<66lg(a=_6`?%) zyyxcOB1kSHys!fD^jk&V2~btV8;p<0zD#Jy>Fa!66Kxp8d)O5ED?5R+Bu)pabog;H zf=3jFi$>*YF`G;@fz(}@W!<_6xdx>>R>U}=6S0&*C>=bwPeNFu=`W}lM8D?1T=f|R z&LAQGbUT`84rkKPNL5iamzGLG#!sS4H2N;S-z=XYWE3`aj*^&FDY?)rpP0cikamvp zr5kr=3qlzJ$+CT~yu4F^O*vT!u2UxE8{9-paC}eYLkb)y));{^BL2&r9Xp z8|ooyA|5HSQ^tijSV|-8;~!%3J%f4zc`$o!7_H;&HJAvMcQg4}T0}H{K$_f!W?02a zbw0f5UaS{&h@eCWdAd*re<44w2x1+8&Sr4%IT&i%&z>r>#xAe$cbQCnyy?*}5!W=w z|A7E0ZAHN^#OE{efWzt|1z&(^spm9YZfREN8bu z3SN5S-Dethy&BwOnl>9-ctmTONjhw)!<29~LzR?j@T1G)=Zedk=Jpnp7eaB z(88EYAAA$0Mv5w*isTmG8CJV{w&;s;#1^t`j0ps-t2YG{g-dyl(bob-zK9?Qa6`^_ zIpAi}f15Ixo9yN1TElDppe`lel(4})aFXJ^Y|d+8aV($-_2Wghd351zc&;88dto|n zsA*0vPxzH5%4_cXS5|lPPBWOB9w{;A8Yku;YjJFXSsW8p)(QD(9M~?c^88Q!{HX@+ z?I^_&;~SCHc$V_=ki`$-oA=7i2Ts&}H*8$?PqXmDvJ;N@S;hT;CT5Hus>cHvcd+pwL-x`KwE-scp#PhLJ*q=zu$-_CDvV8b0WeIs*|)f?BL zy!NYNbGEzf@#m)rHR8?=t&;hN7H#^3oNX2d%qY;&Q-TuTicU9$H;Pm972nATgSVL9 z$t(vP{LebEY$fbj*3Si(NJo$c3c;|BYqFl`Kzdajb62{D(TwVaKBo;4CyT17eo2v< zRzmHxue>E?zDUZ^f5QvJIOHY7?r>mZu=w1zSXK<0km+hiDHi#C`%<%R%VbCy=E1SV zh)}-Il#o;;0R->#?W7GmPR>fJVGy(FG%=*Phj@1wcD_V+{abQqi#Me>wS-xQP@yz} zKDCO+1L>SsHF9y3rM2=FOm?i=8{q-&2rgYp;JUN=yQX0H@ai=DKT8@+y_$Sn0{y58 zwvI=eRCN90GY3@b3K9AQlsol5IW{5j4Orlt))wPOQi~10W*tWZh?tW!=_9Rlxn)_v2)XdJuKdC1|Gz$Ab{m!nNbo*#55qJ-xWQOzR6vn$Q7K zmt!V~Lf}tDC5ing7=fPIV1*vkOXuJ2Jn)EL9Q*ScIVAQf}`h(dkCAJhrePc{5uI zec2_u@6AJz8<_(p%Z>>B^HPHI#$98y`rM1F z&>hGeKw;7;0kg=J_!GJo%daFn>t#+-k-lr3`$$aoy4Qc`aFs!BI2KW5wct7@eQ_($ zwfu10eo2$!888TGuK&ILMctne&dPR>olhmIj^t&&E%Q4*>Va?SN?G)jt&^Pg>(Y2+ z%(a|jT@BjG`?#iO0fSmSUfH4&?vg=L$Y#k&$)P%|W zJN%xHOr%PWM4zJoHwaaDzvQ*CplYxP4{2|a&;aFwN{zoIJECvRhwR&^sRgOOXqb-1 zS*(aAihtbADRlMZjYt!H0}E}?hH;iH7EREAKO@7&%n9_kP`a9Nq{e1p(?@itQB1)VA z)em3Vu9aFBO6zu76;(rjQ%~jf`F3@__7glM3gPKgH_GL+yQl@TERXS@$lL8v8sAnx zvX%hL1s0}w)0E9NW+ml|#IP%CL0Umdp0K)pIGMuD#zZ#lTC0gz1VoU({Eg08KyvXA z$ajimhX#}q$8yZqPGD_5Na&!S;C>wraTb@fs`A3Q5vQXc%NjT#!T31Zu<^2-^K{mN z)3?d(m?(EP;$yZ|<~lCM`|1~z`fP%X)$}iKtcpLdCGuf^kLg@DExf(_ZHx6pq5hZA zl4*J(uQ8zkmwy06Ag7UE3)sL~6o%R$AO;OYSv1S~d0arlb)VGc`nt|yhDpXcqr>uA zWsB)Ej%X?@}ja_l=lD)TxxLz)$4l%xaPZ8m72N zU1*JNA$A55CuQ=5kPTmdH0oOKFRS2jn(diJ9bnR(I7e{B*%J~|CFco3J$wJ%iwtzQ zQkfl-=i{JQDix{-JBmzxEp{%uD5HZ!$oHh*L}C3Und?#l0@!hOg44Qq_W0R7wC)#| zo1ar?E0pmnC>F>9jFmB=G$_7a_-W7ipDWX~2WeA}yosTtENP?(pWU`?vHQ#~^WY6J zvI?^+3a~hhPlez(J>KB!{6`BkPncd|JWGjXh57j+JFIag>0BMu)ti2~<;D5rM(Fd) zYi@bNWlXg)hc+Yigp?%8N83f_O(vI1$#?PLLe`Gse*bzY^vv(PPGc*`m<-DICdZT0<&(_J(&|6)NQ%Br`KS%nMNqUD%`cQNbe>m zvfq`=9u&hazwnFh@x)c2X+VT}Xx{ZSNRb;ffb)iL9ZOrdriT6g(ONd3c}c%0pn8KKj|mVDPZFE$fPn z-MVh|t~d+KIamP98C+qo0sg`8CI$X#$0a+ng;V6!&+pAlSh`6%LO5&Gw|%JQOHI}> zJJ_&iSjGdyw@hPIYUUQq67ceMt{AD#*JZ%(x$cai)F=z4W_{HQ&9SM!S!>y2c~ucCD-)GMqC1p1L7QWpkTaz zM((fJ60>o?-Jd8#$qRY>`giW9MK1(L|{=Ew&If#xY5Ux z;G4alD|>JJo(R|I42z4%7BvDCMQjJY)`WY!U`-n0Iy;{I&7?Nrh+Hc%e%uglGyWEtzo~!(y)HDpD|H9-c1J6-4KQuY=&K(J=8P)9)-v&zk z>t#VM*hs?4cXn4Nn&*V^c;`sjx<9FiYxt<_c45qAPv95327_}<=AS?QC@#7;%j)$! zJf^wPi7l2W@>NL6k`(EDPQ{mpa~74>NxJe{;S|SF{yx(!92!pQL74Y;<+_0Sgx{VD z{vH<|G0@IH|6{qSd2WRJ-P!`Qio0H+wN~)LrN`xLN6b>|2R-a63pf}DR*Q*Q>vp;I zq3Bl30Ip8kO3v3NCd4st7*3ZB=5}jwAt>bKWc7G15&^+kO~TFnMH%I@u$gD7-=UpyGyA-mr6wCvTN z^vi=s)+q?9Vhm&oS&zCkU?V!+pBF0-UF;*$aXiHLP84IFkuplZck>QEfjhPX2gm{T zTg!sgd_teHpQ$c8cE8tcn-X)P(jUm&iM#?2S>6WuA)^^_BpejxHRpf1?alAaoB&J{ z+?3C*Zq)w-cTXYE1ZrFj%;0HV$q-y%h~@Q<31L6}_%@vo_B*weOH-Xt@X9_q9{WzwruF{pA9eB`u{+tQ3c6)R49puG(F~Cd z%52F_1%x@yV3E$Wb^QtVo9FccK4JjoyC&^O~J*Jj8Z~y#RZb5$^*sp&rq*( z<%`Y$2guJYF-mGSPlnC1=$t|fHPeauwn_I7b%Hoaas>CNnA%2E)<4Re(iT-;My6}U zmbr#*TIff=U`|O`tMyvZU)b0V(S8mk zZ4UN{r{fTZvJIy8W#<(ezJESyB~El!iL9_-Le1m=SLpKs5eR1<_J0#{O9m6dS7v3v z0TU{aGj8XxY()XP@zq+F>iF6pDGDvl;^W(xpO;qrTE5lTmhwXfNV+21v?TIRA)+(f zVsb}R)3X-&@*tgWC@+J7X!m`eydu+VNe+|id92SC@;1#$`uk5E7wDGu9=VSdCO>H= zJe{@&fAN5k+H;ifR=QbB#6h1@Fa+7vG9s*@%)|l54_vm)358aFbu%rvbOO79V@<@r zSSFp`fVY&JsCfdtNg3rB=Rdck|$xe zSP`54ko3#ku~7FoSVXSf%BbvctaaEimNDk>7R`9nM80`+bVx27Eti@2NaW!p;`}NP zvdTAl$?q>ziYbP5;La))!O<#gtN0dK_gMopL(OJa%;uZfL(7J3>Y39q_Cxt%FiY6> zPWqPSrQ?PU+!AI+Z?Qtd-0u|Op6|-3$2#_AQGm{+qC^KNhFrcDszi=Nfes13PGMIc%z`wKLRW-!tVG!#tbr=I4sIsFlv%ht-C3{hQY> z;wgrjwT9^}SxVj`t*sX+dh-PW?7CvjtkHu`BfHQhF{5>VLe2|l^{|W!vJg5&yu-03 zw)9;$7~!MGISi8b?tUDbf1as%YIX~8;V#or{90#A-@l-cf^=djCFsMw4$#!Of-_+V zZSmxgSqiRAL#u*fqhO`g$L|<61*|PR7Oe#pC)<6;+TqKkDn)>gJ++Kl3_q5HzFQud zKm2NZsJp>(@Rs`(7_KLI=OIElAWI{ahyzZ)tL)QB$8Bm%>VVQ4IiWoB=gGB`BD_fL z)*SGATH9$RInnGP(K5ERDAw3PAr%xsp%cdLw51a^(b@bVH-xtR`%|c{2t=Fgc%yO! zjWqZ_*%)l8a=5_a4t8wRtvwn;=@5!F{4p1Xh#reLS&bn>(#KQzQK7%uYMn zspSEu@!M+hn=+EdCEHD|NqI{3^!s<6*_2~b`gl5v%fa9aW^Jn zWO3w?BFQxttsh^o0!LC#`pJu&yT^fW^LCzRagH^8-QSvBiZ3%GMH<^6QVok0N*-Ab z9j;<*tbM`%i!0(XNs4}h>;GK_Ekc7y_?uIM08tV1bs>G`xjI9Q%aX9|r*E%*+&81J zc4-)qtVSy**w&!PMqUfNz-!hDuUm~1t!&jip)>`{-9anX=Dw}1t{_bj`Ot1)ta(ZY zql#|EPhUvsUgU**OINFtS{Odr|LxV8bcVM&N@J^8-_CMhT3gPnU>-5f$t)F3hOSuC z3(c*BA*BonAkE?P?SG;X>O7HWB&w8!hMG8bUHa~bDPkPrQvsNI^QMV0X)9~$y2f0Y zc7y`~onL=hCdb`zsgs5^2Is8(8qf5C=!I1r9DZJIFF0)c1gPzsU@S{q-*wE zd}&U#gCk3;wRYVcjieI=pW&`D+Z!Y#GNfA62zqzuz7aR#)&;rpi0hH8BM1qd(@u2C z{gcPH)Lt>*Q-TB#fH!Xp|9-1$6*jg(_e)NGulx1orU}k&p}_~jJo~k)=US%zV6rUS zj_DWbTr&d*Tt8dkVg6b{hPz5NEL@)di|2fJ3qSwf-k$IMkiS^hMSlAIUAalFL2j6f zWAVgH_XFXS+PRMmTpKE?)R+Wt&l#nj9K2wEDqoEgpDD_wGF~eC-F&};7wP<5JUD*= z+(sKi<)L@FVldA=3w-Sg<&ap0w;t?Gt2x^EanHzgfR=2jG0h(enT=LYSz6vw*tn-M zVj`Y4N*z_`bmH~e=lt?5*=&1855!>%SUQODa-4^Mk~3|ZWd_@*#triZs@n_LyLaR)ftYFvu_+iVok~c z-WNLCS0U&tlZm#2a?<`e`~du$Md;`UZ z2(enI_{tTZ!yqKSb{O~e;q3TTD%0VM@^DJwt*7lxd}^yW(UZ9CEhlOr@*#9KRI6%I z;~thVO{U

c>gWqNMT7*C9{zRTrmnW?K(0>@F;YFv?KMnG_ef?e&k^4WBSStYcd_ zyYs`4Xp_KOGHftit;H|oM>CMBCQRFVK2J^f8DA~@L*rQMqw?;)P;?c4*{TF0UL|#M z20ZQY7jQYvF3p3-7$h1xS!z|%ZSI9Cv;!O#pPhJMh_>9!>aDVqYv|l7)J;FdlKHa$ zVI_vM0}3j{6(yk&v2B`~x5?eQozzcr;$x)A<)6UG2FrrRKMCI0ID9pK`LxVzVH!2- zI<=CrMW=dN5?_PKvUh*omy~_W$q)IzT7YlF!DtQz^B8U1`#5MV1#P&spF;JK%a%X) z-rThuPMNNq5bonr#9EK3jWFw`e8ES&FU+0iKXt1XUT0<8p({$|<~%ogxjQ~Xg1Z|s z!{V^^`tcX;$MCd|B?@4oy7*VvDvMC_4d_4w0kXNTc%^l!!du$r`Y5t`+~C(oUwN^b zt64X~9YznUV4fSLm&Ze2<9l@wnCaBh6JY02btUYNL&hZXrLf4puCc>;YEg9eQ#3BL zSh|UrE7njU05ADif4+QEI=QWkaC_505dX5D(%nt_CTi;a0v5=LFwc3H8kcuSHzTHT z9nBI7dT}QUh@UXg8oA^92~2e^x>7|VR~&7{%emi4Fv6t&rnYWV8d-R6>hX|4*OXIY z2=(?UunNnq@z+ojZU68GJ=^2rr-G931@I}()@g;Kqq}SQ27`W1%bB{V57o?0iNZYU z>9zQx6xw`PmfBYCJ~qj&lad%oARKY#qP~p$yYFskrs|0Bq+f^IqIFn_&r6Z5_Qvq~ zKX;;AK}N3o$`nHd)id@=ZGP-XccbesqW^5OjkIpFYF*ZA%2uq)m2Sm(ZO8t(wZR`S z>Z|$owD_L=s5+1o(R-QmO%0~@#dWV}4O0*Cq8yNoaZptkE`rA@hcYIPU zqK?v!o@m(r6vn5X?kUviOF8zmeiHpVvT2#9zZuN$r#fH%a&h^tmeCi%3DOc=EZt=y zVf_eDkXk_2{T$t{j+$W33N^&sS%LqgK-#bqx_Nd41^xNFm{O$^#>lkdxgP1AB~z$w z7k^!@Q~KW$MLKN^z>GXeQ`sX*TpuA>IR}F`1Fdh;yOn>BEI*CYkPb)fBcBD03&E*! zqJTlaCYT7#3pZ8ny=Wym+PCOsq8oHN|DEia;;0cikw*l0QM@MwX>| z6<;!{gSPfcK^6_Ib*{V${p&D)2S>d_>!1CP*oAaIQ~8hc=4oAKlQo`fjz^*wA81|EnVWNRl2i7O0a+zuHD-2edCe z|GafWgPp>`SCGM91i9C|=7jsYq;Tr8qU1@Ix*3*t8N_Rz{KIT2EUUSWm$%gVO2eAQ zD>yyU3FnD94M7U$yG8%GXXhxzJKSi8ILB&XlLAiDPmw23x=vuu@w6c55!|HWFVi4& z1i2tvr`m){;muBxd`ay_O#+{#ZxaQ(QI^`9ETw%unAF_Swn+bHrHnuHYk$*-tNE4R zz7!C%o@afW{8ZKdJ2zGY8Ed|y0qJl1k{YwxwIq-mEua9*t6*#b7b>r}_?6=d14b-4 z^xoAjs-<$DJ_>ncjk45W<878+xlx|LVFz83X?`$l-1)8(&$HsMcM5&&=sFIY`iTC})8} zufpo0e3v_4hf^Aby4=_PZX0tZNXRI$(r|c%M*n5+Bu3>@`_124q=`>u;)K^7I}i$t zi)|^GLs7oJ>%bJHt~{Bw?fSU$a2|izOGnzRN^rSX_prpb`TW~zuKqauMGt#FKk{5- zY?9P_uZ1Ze`u1c@=W9Mw?b z2U_f8h&kVG)P%epoBNr?3USR$v$Qb#>=e{7q5GoTf;GUQFP|D44`+`U(M&2CfoV+h z`~zFdwHgO4OUPCJyonp&a}s$u`8bMk)BR4 z*DYs3nV$@d)#z5_-L@oF`LJ(%;8NRU$bnX3J?6~JKcO)Z&LVmxRg97%^H+tOWoDg7 z+U0L*?|$SpnS{dqd3iiyvt~YDEuI z`_$ETor|TN!Aa?=D^^27RmW^tz~6AKhtjLF>@qTAp)AHK#(X^oyT`{MzwEE>oJZ;4 zAi;ssv0_7ry8944(|uNhTL-QR_QhMj3B|yCAQ%h`aCM?we!CZsQU1pJ0S1DoG#M7& zi5T(Jdz?4jz(Iz&Q$&70m2ha+PoO#R7KIL4+YI{g2($Pzfxy&Db~2+E{dh!kD&sqs z#9Px}uJo2Kz4Qu{Q@6(SzikqOY{fk|f2#bKO9!>TAuMyc#|2Z)FY@=QVSq9W9cM18 z31&U+)7&{~=uc-LeYVsUJ2$IDU^dDBk4oHNYDu39t#w2FS8{#-OCI4QI4WnhLWV}V z7!BKg0xxY3X=~glB;QkMzPB&Bl$q@^^(Q2+3>xpG8ZQswruNQ-;pUx(?O(q9emYZL zjbv?NE5&|{!0=8Liw|-wxbomZ_T#;UwP0lRP54f)2tzoGIl1HvT90?+ruXi2*<JNdM~mHF%(+y|3kt*NeT>VeS<@(7efx$$S6X`kKaSHE7vHA9igu!###JfJg@ z-2K}Uv!(#!hnzn!V$r+PI!r|Fuxk8A+j@4yYjB1e_sMm&=)nSfhs798{m*zLU(5K| zRoed)CFKFhOe#^<$AxW1DGe_9Pp;SBwSK-x^TWWe4)#5xi5ZRaX{=2R5P;7ZqoA=#!`ZSLlV#_WZ~b)ewhy@y?^6G$k1(dpMi&S7D{m7f@}<=JMe9Z$!( zLC|0+m@|*sg=@(!I!b!xgqix@HDJzfD{GHHT^pMl^gT9>$`Gm zSB%Nvic6i{of*bOilXAB@0Wm4CM9LB+!EKVcOp!qr}(B#g(Y_!PMRlq%_*}#>vBu9 zKDAQ=3+1Ph%x)1EjlL&CzN{#N35x=O9fvvfSGOjp0LJAuqSC9UKfdeCI+c!0f`vUg z(R07Twx3V_Hd;2XFJ!sQT!{}7;mY9mwqwdLWdT}NW=IuY&Ui#X6L3+tZ9i$VaqDvJ zn3!FLUB@?%S@L4j42r4Q$6S_u`p?&y>+r#=NYBQLSDXCl?&+1_KaDtHELYw z8zQ36X&7xse|GMm@rDwD zPrWDi?(J)p%iq{#Pu))^j}~JxitME9sL;d}!ln^5Rs}?D3eK5J^}Q0fh+&)`!sXQv zRvsa^*GcoATK`7<3(Ad`7pLl)`||0NQ298?SW$BS2p>6{ymBgi+@&``k%jbA$l62J zs}Q@0i&?rKY^hv8e01KwR4Z&sMJoenrV`z$a7`uX#!cyZD#?K2ex|e(Jjv^v#Ff^M z1_)`@{Nn-ZwD$elJ^|dGZoivQS1w<>E~QmBv-_Rxwi44ru1WvAw~hY4sI2lT1HPu< z1Drbr-p1|Iw-ytA%fITV1B2odRQ`ru!@Bvy*+{1+w$#&q_=w0BgTmQAgv&Z2TvPBz zxQp1-MWl217))kJuA_5#2+9J1G0TfL{eg7*A&m9LpDil(&}!wZ zI%FK`K(Va?)GOd`1h{nBu?u&~>+qJnq1)H{;=3;4R%>+Zxy95vu5Ng_8Pm;xPGq;{X{?X}{MzM_-6 zitQkPGd20{ln1u?5I99o&%)a-VE~!oorzmC#O)0^lG&Nrc%z&uZhUN7xeATF6i)83 zi$lPrfwai8g_Rhkg$$uSUiI`$%r~Vuvk+svkM<;Mj2C7hNXDZn=?-qSe`ZO869dc! zQ3243u!al>er%f2C`~!XZA6n;(t5ptl9(6A=>m)m1Bf8(GlWLjo{qCCYp z`3YT^X|yVqL;vp`!@NCUT9dLGnS~-UkB4Kv+w?cWTm3HBD^;E-g_*BM$uyavzg;`g zQw-OiO8C@~;Cx4e+p7Oxjsy5r)I^VqcLQP4&EOTjHg|)3@H!K)H<`}0{G!Ff+{6IL z$v-+yi2LA-1qFO+jNPU>o29ab5VhS|VPyKzw;&^KCp_^bk1JYSbf$*;pSmdQviinef3D(KOfj(g%@yUX*sY{vzCqY#niZJDBp;9a=MaNeERd^f6yXM z%F#zDuZ_*#59LtV{%aS1KVdQbmfLW<#vzDY#m_fIrHw1sLZ4^oxfE|xP-I2=X7A7_J8?9Jnx6LlaEJ?vsnOKDu6pd0Gh=%i=E-JCXr|ivwahFJve0atNkBfs zH?>*RVkud*tSlGho)XVDO8vW)^O093wX=jI?Pxr?zPY^A5=9d%l+8}vX1$khP`J;G zFFmg3*KlRE|^FxFOsZ(#UucJTH5IZd5X0wBtUJlk)i?0D!4D>AD_)x5`+5{xqnE z0d;auIs%4g@<729kQBLy6>Dv8SdGuGp56k=&R)bRX{HC4-CN3D`_rrKp|u^%Ypu^^ zO&P!WLNq6E78muoO%nwgtN2e6rI}J-5PjUO`2p!z`_?$JgUfc*Z4jEaM40nC6-^RH z!dTmf?G*)O5v|b6v%sp6zNM-9oR0m>eOLox-uIZ8m#GGvcEm-;^@({d?kA9{`y)~$ z&~oAHuYAmO`1Ek> z;^BBA?MIVpk{qpdaLI-8K^_zPl)!(8i8Jfo89&H~6?2owATg_|TTgh~hIb>3c72p7 z!1mR>C-)NSnpYKv(4?Kj`45>mqTdDEkEoH}CWO(a7ntKN*frd3AnFn^#m|^yzdZU< z!;rrfANvoQM{gzTKfZo@!F~Ew&0G1Q8Wpa_sk&M$lA~>OJ}M*KWo-0C;@n~p z58=k)q5Ro+Q(4m?%~+b|gxZVm4o77)NF7rW2(3~;Z`iHOz9V)!`!982FR^};h7mfk z@}!M#22o?DuzsGbwZWG*A%iJbj-yDRyNDgu?Z(Z~eAD0Wqwk-hRkLqHNs919r1FP7 z(T(RC|FxnA1-7Y{#oVNc*zazZd4pJu(1|}5ZGX3L#$KM))QE zw^8+<1@}_GMq$yVIm(Jgx4Dziv7W=aC?(>pido6r zbNqmY;9kdCz^_p=Kz;0`sLuh(Rwp@Yts` z)of8;-*W`##S=lXIGZLp(IM!8u_G{eKMi<{)E~KiAI#>{wGT9@*XMchX~x>uZT@bj zr+27kFRXSeP2>%mcpD)WdT`kZgllHo)#26^EbasY-C*Sg1}9odI=@e?Bn}RqA7K$% zi2Qb@5Bk%1DiwH^qBd>wPFZ_XUrJN)gGH8AM!FT2dWKqPA10g-A1^I_F+FPn9EEZ( zQIbVd1c%olvL4&~yyXnNvx|u!x~(5b`s%nI{?AyS^!dq86+vDE6ZgL9y{H|CDK9B& zkZDNoizs^+cr$F<%=+2dNk?peIzk*S?m?d_)d8DCNV0Pnfp7PhI|nAviCYUoXo~RY z6qEp9Ss9M#L%slf&&iwU5EcoUo0xtehY;XM0Ow>J>sHv8P|FU#!uf7@U6!|qp6?IN zB&VwEs=S8o3QPa|h46@E9DNJ-r{H?yQv)Nnv15*+f$$m! zDwI8mg4ofE$}oAlJ(sSXXI>So*j;VUB%9&gvoNOs7)!F3cKCH3kq1Ev^eP<|tJ>nt znd9e)gyB{csq;iPhJj0bXAEkU3 zcUnfjEAN#O!H~b_VzMnsxRFFw((L|p$~c!D;oyamDgt>&TK`Y>YzbK}lbk)lUzHQx z+vIJ%6AxLxMz^l(DxiUr3L>9LMLoXHikkLU|$Oau|_? zTu=doQ?`2zm{>J-dgOuj5=zK5x}_d_+9Iid81vc62T9jY+xMcs@;@SzE_vbv$ILAv z>w=r*@zCV@<^<>XbYhCg$?sndZFmk0w_(=1fW@WTMwajkyA9H3!zvVfP3{z<^x1=J z+FBVhRZ=txI{o`zq1}hC8<)L0!f0aH6`<0l1~W&I!BEZ``yu@OedY(I+(W*CpV0hk z-30($T75y{Ah!QTi1`0T7?JWnni04U!0sQR%FV3wH=3)o^5({D=jkYz{P>Pt#{>_z zxY~IBSq>R^{QEDA))qu+T_(1f6OB$Z$L-RNBD-holIO?RaHu{j0p<6~A8a|QS|Tpb z+K;b};`9qXg3|D+2B<@?I=F6qy%vxO+H*3(j@^K|yGpGP!Uv#@Vi%=NP{NG`j;}a@ zU|$p`+FP9nWD#Qo>>$N0$$*sR6z)$GP#1kz)xWlVv!ytRpygA8-M93$C7fM2WBqsr zcn6&St>oMu+A3VrAWmPe>K~3DzIDEVR43syRS%U^4^1DxC?2CdbBxKQmdO$c%YOr7 z@}u)#;msg(&wU)je*JG*8=(N2v7VI9uD=igFaT9$us{J}YbyPBVSyB1YLMwF-dDc+ z;MzWgWS)CH3=90amxjMd{>V6A+G1@*ApsD$0Rt%Lo}mEP=QOQY7RLYNA8h7hc`kSK z{8)Bj8-scwxr%O~B7sz^D6}e$HKg|5wPQT@>eAlgLE?~G8e5z_?*L-3(7{fdhJZ_! zz-I`K_}{45*JuB?k7+?{DNsxM|M#&A710uuKNG63>nc2)0Z!z)F~HgiLwb<-A55&B zQksxwlub^AasbcCPWK@x*V-ks>8zlX{Bm>kdFmfK<$0MKT&=KP)Ra?_7RE@~fdSVS z=TYU?_{zU(VStgcj0cy2l_436f-crGcMj4avaBtXrpaSY1=adGhqtJDY@pm?NC4eG zG3fpUq>Pv>Jm!AX5@V(jBr?sGKQ({>qne?Ly;mb2OVON1)}5YsC~@w$A(!K@+Bl|qGD{My5Oh8R;YI?u`zkY|(ck(>;j#}|&a zCFDy_9Psg>R5zlj5FiX`X&}sb-O*#%vz3H}oh_-&MW+0p0VdFm<{(OZqYcw%1Sv>0H&BHL3!9ij?ZCnZwP)rm+(in0oP${*MmV&pWJIKDwgtZp*SE*E{) z0=y<(vxac@Gt&ugV(q7MjD6r%dmcSgv+vZ+NH~eisUF53XD)NJHa*uYwB5Md4$u z!i+r}ef4puOEBb~0(T|GeYbZvhEtA9f15TcgiyO&{}ztyZ>az>e5iE1+T}c5HQS-^ zOB>VgcgUCRB|_lC`to;CaOdJVVoegEZZ+%+Fqf6E7c330LE*{aVAoW{ay>?9Zs(d}*=$-Hjmuy}lC>9H@dj;H;vGTBl1 zeUf+x_Je(p_gw|H6#?m5hu<%!HiSf)4;gH2ektuNRokM3eI93?VsLGQ>x-##0K1{| zm)*aGZ>f=`cuu!(O(66Kad_$jBAC$4M`yrmpOMu>06;2VW%U+Q=Ky*HvPPN?^|}^( z%iQ01#e;I=kC_V`(+3a@);FfKlIK5|YleIxsbI!2EmMnTlt@<}1K~{Lndq+=nzQ_QY?GeC* z?h5OC1&T#2y3Yu`q*F11?}vLY5V$zeJWjvyXv0_#T_Jmnd#W`^MX{YI_$fB1tda79 zjL)mshN*ox>sny7+U^Ja;z@4{I6bQ~vqvF!!24lN44UVI2O$6q+Rm&TZPtF_1#aDz z^y;$9hWQrpSkn7tC8z#Bk&8TksLJDeFS|*T)1C_{TRuSsQz7M>h8z9{CAz^av zhJIl^p8sd3kwi^g8ffwKB-=h#x{-xkMaQ>hy3m`5yfv$W?M#{b|D)=wqq>ZiXzA{j zR3xOkTS6KHX=&;1ZV`}Fx={qAySoLXySuv^-uZd&UGKfWT+79R@0>Gd&di=Ydx)ShnJsUPbOu= z(ADs65@Uk%$!GlUZAu)v{zFU$P*$l?NOcjMld&Whsfm>JP;A617fHa==|td0?XQeP zqV?gCts;XqX$C7(MMYhXR~ixJvRt`nHm-uOl_=WZ(sf>)wrJ+(zTU5krx{`KYK5Z3 zNUNEOYZr9XZikaWf;^Agg+#>zsejvxnllhC&BNzejt?^p{#a$h#nEVbU+r^0)L$pX zBd2p;EfGp^be_QSmXzLBzE1rhNc*I7?C~Mj1O(@t{u)JTe11i|LJl?T7B|9HbI0Dp z3%bz|dpiVk-WGKyebe3nLKo69{Io0%GdQ4WKy!68SN}L5|JJ_jqx+&n@ky%fq8B{cP+)7zH9 z)|t;gTcakBv?CVN8cfru^fGA!mPD54{>K(^S0W|~0xs|o7tO;a4rqOb|EDd@THxZ8 zGh5h!nF)6o_qUG+-QT(%UY=FLe|9&BCQi@0HLYyGLUMWftj>+UYyup^u_n(X?tjn8 zZg20Z(;L)i5O-|rK76--bqR<{aT*^~E2{V?>{s;Ul+*IRt4>Gcs4nXLRx$l}^MYG=HBZ2!7bv50&-dmVD5hnM_}oXr43A#M&!%b|8W79Wy}bJF#AYn1mq!q}eBY_1s4 zwWp;-rZ-~Sgdeui5YD>YTcuMlyE-uJsFhYk?i;iDo?Tzo>Bb3M>I%vtm1 zn|i(@t0{g$?^)D&?|X%p5+eC>>$@D=MnUR`--TvgHr;G3EXlKGsP6u`!=Ci0qz+Rj z_7n|@5JGfM+pm4B#q7-StD*Rep1I7{lBmO(b|I{%M#)0%Ne@z%IgQeE+eD0adk3ZI zSFeHtf(2wxF4L(7yKU zqF>i@W|1R1dD${)y#K6|I}v;7^nhB$w?5cLnaZk|gGcr5hL2IJet2T*0j`byzp!;p zymypZE`8IbAIod6?r0bfKgF7Kqxa@%fE zoEHGnL?T;$;Z5j%2KuLZX-ooBVinTQstJOq{;i~1J8?COD#Se9?sApR2+=>^)16`ZxEw9Utb?ys( z=up=oSr!-8)J&9#h+EX4FR62oL;Rc7(qz zN;K6gV&Y#^{Z2mAp+#V1VKcYMqX4H!(eTy`;o($OR;Xj~~MVM6Vpn zx@hV#P}Bz|GF;$DZlR~*i`>TLQc5m>J9+SpNkgMwh=s1_+0fB<;lf?TzFRKCyWr6LVR(F4>DM=wEaR&0Nbp-VeqqDAWok-JFG(PgN9; z|H$or?$PjI@nB!SaG^%IXuOIyE^#RIY0*z4u38eATOsWGllE&gwa#bD%*sfc)W_Id z^3xcS-jr^odX3<~L=C12+Kh_P$lTK-dWKmABUTm|+{~Z#$lP>{&36dE{1CU|n}c-E z^ScN}Yg|g;2c7Je%9wDlNw?9p65!$s{tQi!r}L#NX5j`DGvK0rKSSla32m6>+r~5_ zF#C)0b0CyGJ@Jz>R+W?pCms{-LiisQM5+ZLzxXG1(qdlF5To)=%zZJn(5pAY{W*Vl zwCeOWFyZBqQZg6v{PWCOWd0mmGxJWQpU4@rb}t`c)zV;Q$U=9ir_Hc$7O*k##;7d& zb{a%6IWxt?V8|`fAK+b)IrSW8M!5LRaWP{fK0?FHZP2*N`LZglulbn%{aYgM$-%na z)#>5N%OzFZvP$#dVc`Z$;wYI?Z?x4C&-;DCb6F&AKDQB>=9*H80N>+w+@xlS8QWo9 zG;g1hFUKv`3B|s{m54? zg<@VTP2%S$Vf6_nJG?az`+l1I{EYASWl?S-M6WCUdt#M0VViZ}lv;FzXeIXRokx0SUtYssSn~bD1O?zmC?V1J6W$Yk27h_iPA|_pmX-P#OZ474WZ=(Idg|$w`1IE`J*=@x z!V-!3sLN}7BpY*r*6*9Ok0cd(Qbuz&HMa>VmQ9}1Cck+d+w4+h@pdrDL?ShW7Y&9t z5>CO(+lj`+R zs+NO~iz)NK!*F*}jN2**&;<}&Om`r+Q-OPr4xN!z|3{9!1eUdGC?MXr!>4=q7-8_$ zi=L)mn;L~}fU)|X+=-@Od~rfuI&P9pq^~X-np=$F(1D4#mkX!cwJySX{t@>m%_IBG zQ84C>e#-?}L)ZLJYS74+tuT-A$C~Gh&xH2kdrURAlL?l;6qLGuoCW#LfA}ftcXmSO zec$kS>vSXeKf7C&JnIEQGCianwf}Mb;5si-ye^nmiAbRa z+k7UGg!FFgWAQpPMwc8XOiEG5ecR1~XV5{oiuhMviZ+RGmE^b?>^v6R%U}|PcX~C!Pu|kRL>3q5bkcuQUlUXY* z&wNDwEQR9=N1sEavWDbFE;1fY=m~ji* zh?3v4O8v0_`K~Y#!^==^fOuNfO{DUKq7I{uJ01=0OL~4MWoPMgZ$)v>W9A(y=c?rO z$)XBU(^Ljtuyt!iV}6FiJ5wvpH>YC^O^zQuxQj$j9YK9@z|EjjF{Bayam&jgTJCfy zHr#6anE4i+M^Lz@xVSEtN%{S*-pJj??DG#GOkV9;VXD+qK;dU^ao+Ly2(nNNa;GKk zw_z|o3i0rAnXU)ni%I1KZUi|pPh}#YxZ>Lg&-p$-P;zot_F%ahT}OVP+;{h0@kO-y73LkriZv$rp_Z@ zr&(hkJ?G0exwj;e8y?vZ#Spqu&wuq~IS+ikut-2JWg)uULSGI{08h}#d(^*9dPYyQ z1`ku73=d@KeCq*iMPt@>NPm^Ixr4+mBOWy^SvJu5hK=LsL4&GlSmyIWl-=O%%|Qre zdSIwsc6(LovlfGS^J(Vvd(U58yLCNm<(fQha>ZGflI>3UctFWyCeT>JW z%qhdC*RB*A#DCpQ`?xu6=ST;ZG6JggnH??dcIq1K4=1*5N{er9(7msZZV~^YzjEL0`%$hKvz*sJb|stng-dnmyA;vmji`?790Z$GgCy!`}&O6uDBPuI2bc?FNK`k z*i(A@?_)IYBbtGXCJR}n{62JHP7ht#I}z;=fankFQ%gBL%}(02Lb_y;fO%Cx4@?dwZER>KL36$0>6Ek#}8Ac z$*4!%VfHMrp;T_l8J z2k*HG>-)0=)AA#dw*EFAWlMGLmA&R_jHcOigbd4iLtLsgOYx#*2W%w`Kbe=wnr0LC zCi;WRiqgmP(g9ud<8iaZpKggQ|IE%Wca8B(zvx(OIN&k)zZ-vli>cy2&gnUB3q?4U z?D23VQDwWtD_S^~d{ivTBJr?R9LDP~aoau~-J}sZl`?c=AVuR^^f+Y^=qAL0>BCa# zlF<3zNCf=-_iNG%_Z?o{n{pgKF}e z)f1auhYfQJb@Luju<*_N4$c|Kc#%EyM}TAZ-SIEX<`Zb4JkEClx_6@kJa6ZY_W+sG zm$ul25fXiPS`?E-kG{Da^#kEY`NemNmmpgE9y0&3t*Pt5!C+w(gS1EM=+_!&W3TyU zLcHMmpxj@5bM;^bvCX3Q?(H_E7M6t-S%$D;ujxgbI@qw)b+`7%{}b`-?>ztF$+o3 z*V+og336@*QvUtDJIe&*6mH~d7I54B&i+_<-={nV$vyC-^NIg^Y{lF;ybM*cd2C?q zj|gm_q9Z6GFp5*$*;L8Jy|H&P8&8eW_+JUWba}d*@Z-*w`N(kXHe%NL^_B77vD{6y zJ3fJsL&>mr=fJ$c&ZUQlC@+`!7nUfO_(}pc8{QLiidOG! zLVB%OimTVzh;vXb0DNh@Yr+Ph2be%lGyl2m>p3Rs#dq~nQ!iFQzwf;(zt}Mi4Np#t z^CN2`5sV%do=zU$%k0E-kau)T1OYM$BtVXz?Hq0LQLWE23121*5O(RuA}w`hHZS(9 zBw|XfcJxDh>`JsxzcNid=F&lBHAyIDDuh+FJI>NSTjRmP2*WP{#uS~H-i2Aee^75T znmld-K|Lul{N8fT(s1xv3uWhi~h5X0REmI3tyC5 zf=&79dRqf~kZWD&z-uD~iwcxw*!8Q&#Ha!yk{S`(&FRo!oV6}oa&5lA!byz0mmFW* zcNw6J_nMYHJnYc+MwtcM;o1vp zVIDJVx|33Mmu&}xZZ~{Zf!`qz5uz<(8c`SQpVF}=#%@Gb?nxn z-`DGBSO!wpzJeX=irBpa+Ce{#_}BAZ451glrWbLt{&xsVZ~oJ`UvL}ZUsj=MkZsd$W16H1EU0@i6X5JV z=F9x^AF>TLQ2e^2$I)~$L#OIl&!^MqNhfQ?icUKTBe42SH!g=(&$^d%8D7^idF#a8 zoIQn@f7?x9t8R8{75-$L`ws}5J-cj=*ByR!;(3V7NQua%O+(d&q}h^6-d}SgQ&+RW zperj1k>d$B4F#T0QK^Bb{LVdXM>QO0R9d)pL3dWFbli*t{0TE9Cj3qfj1cl}b8$u8 zI_F(rJyc`q&|aM(A*D+eV-(W~%4dOq7OQ88g}WJ&-@;zR&mND45A~f-1I3=_iFzKd zZGATuMwt+w9`NgvFKz16;M+gY+ZRsScw@FylahI1{3bhp8jn3W_nh|pc&>gpLf@il zqvW-Ejp{~dj5faI8-$_Da`rVk4zYNl3A5*?i1jP>o+bJ|83!zm9MaYg#^XB*Z>>n~ zS!~_Xio;&GUPS)DSBcchcW@aiJunQ=zVTkFVSaZJ%R6;mYPFPfF`KR%DCu=d zE}d+-xPQ{EJ4K!jd5L2DK^~sLjm6wYa9!@=5D><;suu2*%RGYRzSwA2iNBqX!t$K+ zdTs9Wl!h(DfBD^7p$Yw9TFuX3e!6w`3rAEf4gP;nr=@xNZ~?_w-5WUGTA%QE_N><> zWJ?MkX$`Bk6tk|miyFfUcHh#=%N`dzu+d92U5>>)e`;$w9=ug+a8`kmfd z&HBz;@v`}x#S%Ff=bB%2nCuL2wHjY?ugYIJ5gH$ieou?j=)jN0d7#-*29BOK$4UE&^sjdeOY3oa&^CvdoMr2!}xOmh||f9?yd?uVy!15oy+ zg)U&ea~xI87UuJZCmxe-RSZU!YF4cu_iCn}a|^c2p={r{wJrwO_)Un=b@MhhQ}JxZ z+V@bIEe~~hAQZh}GiOP3(sPV%UW8>xmE?-jMjRSm3^=)#q-?&42YV=_3JQPvPD|Qx z{{qBXwfH~0dxKtzS%55pqZQ+7_@|zy^um1V-G+9W*}NLsIxRuYVT|L$Hv%2KpYKt> zL_?)5clxjD;;Xi@IXn_=*Gj%4W+c4bq9!?p2~hy0^(@%$$3(x0_`%Z1w0k>m<_hMX z&RsOJ0`G9I|8@;#+t$t&76^y4&1xsw*m^VdG+SK1vU(8lNvEkl-50q*rk=-~TU%>8 z{9hm#V#e3KuaJ+XskYs06~Zg%p3XRge}$r%1@lOz^i1;jj(5rIrC|K|Qtb6wq7eyq zsS!4U3kfe?gN8`J2tx0ZqZ?Gwbk8%n`(>A(;YKm$4Znip`ZnWiL@P!{S$8+DRNRJT z@D-!;t#sRN`gw;cvbwKL-@!zVa7y+nC2r_2b5pWIOWhAGoZW7Ch5K|bp!E~hxa3_< z^Do@fr)LN#pGdt1PblCZlo649h6H?L*&fV4;lKPziT~j{)vT87L>9_pJz;w zbWY8@!}RRLmEr3~rkGw_zWH0~L!Qr<)%#``i6Cn@a3RymPfhsfGI8lGu@*d#!4XyyabJP8gCvCFoHanh31sK#_llfatF>tNLKmW1faS9=hRZ|y^ z4_I_~qQcp3MtIQ^5dP%_q9&s|ej5>?A*n$d&X9XR&enuREKgLe>SG5{9o$D2`G+kS zhQe}WZ8im?m{aEhI7(dF&!TX5r`gAC>uw_ZoH53pwO(ZA*HJ=j*Wa#3 zYI2;!tfulSohTw}aGwv`EoGCWq$>=b(pAP6hq?gnL6BXCEHkd8) z{^zpWnOI!p8bfJMNuF$w$(YX;y}yCM8Lbq7X|{Ls>Q}fn{QK*jjZJl0u=ABObwbN6 zr|-imYIH~k1}PuLTdUL=Q>F#e+Ot@m(@w76K#H9PHiXPWASKnf{{SaAxz?;9wjDO|x|<;E9v?EE8>mT1bP z8+HNZ3*Wg9$}7h$?Xjkw-$Maeua|wqm+;AiuJ#{tTKXcB60|*LIQ)OvVkbqaM`z1X zI};Zs+l23mo8vd$W6Wv$q2xsKCXH@ZbbkStGqj;<(Hr&d+h-aAvjYsXzbYsHf%1EV z3nZ=OOpvsn!04~VL$~NjLjVNU;a4f9=XVWI2#0T4wOqKrIhgF*bzeJ54z=3T8|l}% z;@{Idve6z&*wZu&FF{R|Esb6zs8pZ1s@CBn z09PXcsP7}XiD~W_xO4%FXdNOYPdZNM)5s}UK=qQ)F!ew&%WkRyoso;{H@~jdydzDC zjUYht_2wyBjEAB1dOu$EDq8EEQv1$V5%*%ouS|rkhuRQa*ESEZnqO24&k9ssyA|o* z3HvT8`>%({wSTaz+8aR@VkU!LW$vvZV=0O}lbQn61;XbiIgk|#FfwFT@R_U$l1VqgoXf!3lRkAowZq;3#mNb6GI+D5MZV9jDhzA z%YSNU%`nZzh<-s*XuDIFY59?bxW2+cVKbl6Rq=Q9bw>v`!oe!q=1LK1L}F)ubWpkD zP|@Z>fB%c^%i$a^L*=jH>^u=pvj*;BkQ|iQb@-+ET~(|Rv2gDO*g~>t1g?Y>?Qc3* zoAb4M{#`AdmiL|4hkN!d$vUl#S5JWtmvZl`e9-%&5`O+X<4xfSE38hOaO3U4Alr^@ z7RqTnoyDXuI^@O$KOly+i96@oHPVu}x;5&SCFeao=YB>0fa)=@)G=2a%&X@?3fqX; zA-`DozdiIRd0G?M`TL>*8>^wI!Y9 za$%@WTuw7@d{rLTItgxit`u*C#)bEL{LwuFU+CC*aUxYQBHQ=ECvnKp+WpBUFoE{#})OQ@M5)A20 zK@ zgC;8GMs7)rlIv=C+*RXAIqmQe^8fZE8SDu&1a>_(0cr~@E zj*fO+1NC|nwQ&=5T>j^FigBv zP;$^Wk}A=B=BEzAk{W5JuHyHoIOoSJ5%X}xpIZ)DFKlnGSqaam0))*A>3)BiZfSgB zB0$z6*C75sZ=jqQqO+b-6ew))cS`A(O!k|Ub1cVE8I4F%*a>4b2&B6j@`@fXJn z{I!V0WSylF3BPTe+(=?RWKs^BHq1PzkWRb%>kV%r98QJ&^r@3lUDSntJ`CwTukms| zBbOvWmf+XyrvAV)al2@Knb2WuaBtNE$tyUvP*KTCG{+Lt{ru zO9COMew+TRw8cBVgBm;^Z}VKE)Vix;KF4|4_Z1HjjBV^LsEf9EUX5jaxPCXE<%HOk zTLnX8$6IMU)i0+0`<PsP{nXmCoxeq2@bzoLrPvO$lDUVyCwJ9}Y|^P&HLggf z=oYrBj#Ok$sW~_Ry@ctt$1u zVOi9Z2ZD#q{7(F+>IbKHfr&3>T0hR3twhHYspw-Q_|eTMj1cX&qmB0k8 zUsRk?faCU?2v3#?8rYL34l*T@1$J9YZHM;E=lrQftvD&S6$#~2I<;ZAE!r0%#{LQ; z#7U#5LQ>ABg74SB47x=3fV9uoR_)~0_m&!u3~4?E42n+l<}zFYN?t5r+*xrN&3qg( zh7L(Yi>|iuT9R+O3o*<-sP;ou{9)dDQM)?r(`&8%!Yo$SQI&w(88@@qk;-^4p0W}2 zMKH9r7^am5rW7+-JauaSO)`wv!NDs%hU4EsZDi{yH6=|9LRPB#8Go!ECnVm@lz1>q zR$XtH06Ksgj~x4l<{uSh`2ft9W9j|^6{4$whmQIJvP>z_aFSKq6>(DDF8?5d2dr&g zxG&-nl|gB%%8Z)W$Y-x~GB%`h;RvX<%p7}c0@b~bU5R{4AMQthmAAszd$!Y%F1Y*> zw(p_Y{W>-T+Bwo*AYZH&D|Uznr`M-EYY|Fjn{$~e5+QJ*&IO^ql1YNQi!mS=3=4J= zEH_}XWV|HiC>6V};Hlasn_}jTEECuXwqt_B=-9rlhPqdWzf1wqZ=NIr)Qj&-0T^2F z*~zjlT$N|5X|JN79iYNt-AVo5%+hT6trW_l&%GbY1%2WoA5!SZq4lo?P6ZK7!{F}P zZh}n!pn#6*>bUpSAI9@s#Q`EpNFGohl<2BnI0=f=^iq)#FS4^#`gDNv*YmU*)Rj&NQR!bwK>~OS=E{ei8+O1fmrrpKnza3Y5bSG+F zU7@8F3qprWnsUPUxI^v;RXp>@}vLVC9>#jW`t~Bxm=V)ZB!WnL479ua#1hUF{v4er=&7ww7ui z!tw}|K%j51`VF-f&U^9rS70ohXa0wd5MJOFG0V)&N08tA;ISKd^$jw2?13`o9~VUB zSV6#$>2B1C*W(V(k{C%_X@LWmC%R#gMV*t57Y^fC7-75#9V0MdDD6mP z1Vs@qK!-X&*#1j^w)7un?^q|{oS~u6C*-=Lilcy(oLh-DD;PQT=P0guxxHSejf5@B zOFfdT03fM{1<$;mi8Qj%HkA84cxa6RSP5@X)2hcuj zwEsri`cD-j@$?RK=CdX|`kwx8lNr(HOlRx>1MJ`Bnx`8-U9fzo zbUz0Bnp9Q^1a8J2;6PJi%>rGV@PoS$2Csj*BOz9sp(`LV{0e%s;3Pyr8$E2GXe_w4 zNb~`>efsF17A$eAK8#v^Ja?x5@9!>~(kyrGkwp^OXT~>vq#?-ZQ-MTsnhf1;jiA`Q z!!cHByLB^>A-zX--1sU*46cbv`NHXiFwbEH$skW6u?ownBR~4T6S;v%LAI8S{(?gK z;{%~0_^*X{(E}Ia22VNP3K;8BuGDm1kYPg}G6S{He`(CoTgq7)s9fWGSMYvVBOkCG z_vW&YR^1E&nAZBAgi9qJ*K97MJ<%TJ-bev5`3n(EerOe{udh$~%}DHjKU6WXAjmVZ znJ<6%_%jYNNAEi&;4b~@$Hs2|_XT=A1E34Kzs?;6NmmAF&nRQa1|YpjX$#!g)Jo$> z&6)(omXFCoA4mCA9apJO4vV;WJ$B-Bv8)S*(r9%4;T0vcUdP3GCDB%RYHBE1GKYMeGcu(QOmW3L46?vpI``+zsaJa!WJ(ch_I*r{1X`fe~P zajW=0P7ow2#Mty=C#%0=@6c;Dz{NP?H%esmQ#BiksfMuDs-|oooK^KryRRC46Ty-< zb79-85z8?sf0deo`&`4_vHoVVnbbr0V2n=kA?Mz)LPuiyo>;9#?k*_cznJy(JR^v=QAXYNRoHLA=K4v}_guE{Mh*zEEGe<}N$a zaoVIHSf*^@LLZ2D<9vC3+7S(O`V_Zm8TmN`B0!2I3$`NoyX(fS%0HGi%oNMzmYOKG zNn9X~*LgpZNU$s%Ws0bt%99I}8&(x_CN~}v=iuuURB9HCfU`&1vbD%d^oUb5P%r%_ zH?~%UU0w{RnoLmE%j{PzS@_Nt6XAN+L*LBpmd=WAT>LzI_9AQ;24#NORlRm#Qy!8K zg#lh8RumhLIPjlp7=DE`u*+$4q{C{F}Q}o!=QKl)omcLJ`npbr7@e3tEnO#miJ=L$4j<^AvZ->t`22SRE}R=@-Q-adcc@ai2tit9pSaWBQ8zKlwE3 zh%VquX^3^lnbXT24l=x7z#{EK;(Pjt3u!Z-d=~svE zlPsW!u|X(ezATYhN)tO8n9xNS%NUFN1%zKN_OP6nH$*+)zWOqEZs+P^glRD)KqGEN z%;h#&)4c{?u^|Dzf=^BEuAKf~f_Aq*w2uUiV*0mv76n|~0%(aLar3RcYi&>EG!4FDnz8Sj*!* zdF&f-Mn`^gUBOHLg_I_!3*k8WkE?Oj)baM3nbgneP~*RW7Y9QYyFd|;*+`DXa!nQz zbP&2Ee_n=U(|+i2!~x&E&grX7;|Q^p*#nwBt^2hb;#U16dAC{+5|aJCQc6}a)e$$j zExDiG`+AP}LuNi3VQ;{@dp(?PTa)^DwJ->-Y2A1;`LZOwi)Jp~^t_u3!SfZJ;aV7ct5P?XRNQ zbmYiynt4j!L2*JNrao*-C-Aj7PVBHaKt^)D&MoQ40RBvt5f z>@^js`mZt3dD=ktQ;M}J0WaFswvkb0qnH9@Yb$9YnhB|Q z>N7#vP~d}J!nlIVlf8+B1WgT*HAAdmr{V`Mu51-6JBHjYsQ!N>MoCAK;0%E88>r+p z1Lk{1Mwq?se}z35{QWwrZ7H)~`Jqq!omlv4``@3=-HJREFw#SXK=yI(-U*5L{3%+!eh65XP5(xaOp6Jh47;uz^t#TuTg8l7!j;eQr za_DokUE0Kq%HpdJ@UKT^#Q8Ek&%b=HarBV{(bA@R4Et-Ce%i7(9$HGM8hl9Fhh9}Z zNQ(xWv`R1T-uc4@pYyDZ*oNp0swo%V?@mR&|CNP5G(7yjWEej@Kt$b0+Gn=ah~6$i zO8rUka7{^jf_w~LMs`IBP8%|zv2u@VUw>vGIF+E{wNiu{JWi| z?0NmTgICQQpuW?|D8(kMvoT3f$4pxPxH&=*tHk)`=`Lw8flBLg3vB@JFWx{9KoH26Tx{iKV+0>H}R$xR@@eMu7_8P?;D8Wfa;KmQZEB4LEm(=`YV3b62*a=b`yT66L=zAh$s3`JkmpKaj)y0uuIp&I*xDBbMjFp>dKh%_^n{9Pf*g~O;NZI(SWh}A)p)RO-C=2d>6LS;I>PvNnV)Nfbm0fHbI@;9 zH8;1L;j&IDQhtN?^9%vdK{h8jlU_&~*)18IQO$!J^f^IP@B68sSfPfc#s$SZM^!)! zj|}!q#e>E}jrI+amY>5)wY*cq8hr6>-)%b4w$~!w21`0$vs@VpK`BepP@A5cNoK$x zJ=-rqQpi%~-FmK)jC(N&p{-+l$lY{DNeJCA=iKNWrE+g({N{X!D7ZbdinGNnvAG+>= zNoTOAnFcR%o?5mFh3zdQ{P=_Q3p)%{`uyWRVu2v-Ell`5b5y?hR27ncP*%M2yr^?S zojB6#R~jm>%KY}B@qDUs0y2WvniW#eqCY!@Kl!Gp+&moY&E;_9U8ATsuT?5M8<5&H zRtyL544}>B1AB98B+Dx~_!Ox^2pd{DKrLI`F|p4q)mt;TkwM;l3=6#)(^Q*aD;C-P zREO5GY*b7x@3ZX#B_j?&U8sQZQE4TV!QtoM>%MheO-(zeV;14y5L8ac_IX$I6OM6Y zSc%it5DoSt{4aImmCr7_I^8Imbgm%MSD}B=BuH7!V|Nnz-2@zchvdq!z!x$&CYLyr z{AcO615wP;<;WU!8$6+KpII^#o|(Vo!vaUmg!$&PE&!Df6 z>TraQBO(-{A!`NW@~xwOe6DCGOxXOTh?3w(J#&~;Z5k7=H3em^k{E4oejq-rhqg+q z+fQr%u%Wd9VBHTH4!p;=ag#$_0b7PP=*l?+t7ocAO- z4vOOJ;_G>f6=5BviFmF>_~a5GaOZZ#oHcih=Uo7o5y}c#lxF+kSmec{Vyw*_5ovAx z!{4FVL+X0E%+Tt$FoZj?5%5q|%&?{hdoq$1%U|)!#ty&(o}>05&6^U?Ab8>WP{5*8 zs8~g2-Kp2@v8qz4EF&iFW2Jv38nsJA$Y5=wKH2o-8%G5ud_89anGvPzh;~exAO7|O z36DD}AQ2~K7Q-@yOX<4{wYA#69`So`DP7N|Eb@(2L`|11zyI0;jtR3INUWF!547~fMD7JA6+n0`go9pSF8ef9RE1vze?PS2BZLUc{vvC z`4TxYWjy^tr6j$zydG;C`ozm+Dm0P0uh~6pLX=s|G%VfwaXOPC}SU z(QxJs8L)dih#+k+-dH%Nd8+`TOg2w39P1c9`;eN2N&V~3slyWWkn@PN3s{Fv9Ayns>MpdUMf_&yD#o_mw4SG41FX^5gFrEVU+acWl&Py|^(xy7{2}5ne3fb=IMz(Xg ze6e`}Q66kA-B_D`n2>hiU2cc+BuBEa3t26sk|Qd(pcc+|sfBTZbeIs|?Jod%Sp9rP zd^PlqBbP2v{q;%;Y^y7NAe!dkg;mqT5bOv&es6DI`oozgM5W#)TMeGpcx|oYbI`52 zRQdB8aM^z{|BHekf=REbn!lDZr74>q26R#b3I(=btl!BF!b+|ik z*G52|g(tKhdU7;w{W+0kgU$FXTb-K4;vN^ow-7f`q4ipRh{)fS$tXU>Rbsd* zzkCSa*SMWpyUfjdaZ==yTh8{rZhKF7=i7+NEfikTsvY&F=EWsOS~0;A`z0Z)Ur|@g zT?LG4ZX7U#moVhu!P&(X5zKo$lrOTEKsl8`D%3OpU-HH5saQ;17S-Dru4! z(#CFZNab{A0`7-1WRYsLEk=$< zLnfW`!P{S2j7XseX@iqsf>s*&X`s4ZML80w471z-n>t|jxv!{? zhMp`3z6esW>D@=oarrPgV zb^M0iLiQ^Jq*$Q_4^9&tb&w(8^XvcmyvyL_RCs5`vnilyIpT^{*#u4sHTL{on{WAu z=C=^P(_OF;Lb0>Xzg3kCM+2-+v6xO380$yeq|79dbhe|H=4?HzH(S0t-uYif`a;{t z&wU^2Hq7joL`T-FWlbH3k+rvvvIxn84>wduHU9I21!=iPE>&T-<&^knR)c<<;Pwcy zz8cUAp~H6~(@Cm|;M`T+*+|Fd(!FuDnX+8&snh3iYv0q8xcSSwBuRn}8**Djp@8>7 zh02h~1$|OYSY#C_QiVkK?JmhTTObmoHcDVK2=F~xIoAF?skUhwSkaSS6hYQ%>>ZyJ zz_3UFuo=%_kdvP=$iDyvyn-6gw6R$mSa}XtOrGe{)8;^UYxK3}OIVwdoL~E~ReCk| z_Bx>5fsvzs$(mki7YnakaX4@e)in2dcYZTSVAIntiwlT!+6-dzwUSZXGXpEyty#a! z{eZB+bD%p{@8h)KrKaD-75SbYMr%vM_l$Ar`N-_SU)JrHC39%2MTDku4p0AQXgen82 zpbMX?qJ^OJ(QlB34K^AMa%%NzK+(Csn8`#6xR+-*0-CKn?H3|SBbF{=sA!G*Mx0Sr z8?6p0FF0ZH`iHZ2vYB{muA8_cX+NYzrfQ~11x_troPS}I3zSvc$R*<9ck*-|`FdGv z7`a3xIB4H0ClVo~l@fdQwrpLw;W0|f3PCGb(j0znNO+ni@~8bYzRyS{?qyEm81c;Q zG@JKDr(#+Oe`)%`D~!qC?43w%N|ADO_+J4M0^TAO;QNB{+}egf**1dpRigpI_Q4CD z*V~|l_}Zgp;}NMG?v4T)&2cky)qF!^R9QUSTZFd=(t&bp1oo z-;RJujKBx{_`}BC^zjI>)2{zDG|#_;=8e!ci^)=NrM&c5<~*^ z7t8F?R99GgA42$Tw}h=+-;GqY>ugEvoo!lT3L~^i-3CBJDdDw=OA2_G!UP)VpTB~n z<90fR{Uyk8o#D1F{@CHor);y++VE=AaQ5c#Tn3%QmW<%OVaadn>7I9JlHakPIaxdI zN*9cmG{fX8l>=bnTmMLVEv_DlQGAmo@`C~zc^w~Y4K25Z^YH@7R4FR?uR8ySy|?g+ zvhDhY>6BJFWGE#?x@#zDkd}}R0g>)hh6V}g4ryrx22fhMkr<>0kRCw#JMnt&Tdwzc zzO}wT;B(E=S;)+J?0xL`?Kt)k{+oSVC2DEmU4;~Ifk^*3lc};98E=G6z|v>0oHY5b~H>eJ*kyBnF`e>}l|Qtj+|Jb+|ozWU}zm9}40Jan@T8@eA> z+BQ?M2PT3a5l0(5`Mk~L{oGo$*m}&*gbbhOqkvN&QU@<^Zv7p$=ocW2mm~rz<0XA3Xzjw<={i+M5*3kyM=N(5+CAl7H#*uRlYGB6-?`Sp}V zx;RFULuExZdf~hKnS!vb3JA*x3*`;!J1Qg0-Xj30yH=Axp8Ehh*^~s-amFg}WN$sk zB{n;?opOW0Ss0SYO@SW9i`UMg=dqh_XObCh&^33)*Q~3L%S@`BlqlLW4bI>4_{&>q zU|<^<2M;Md^+)~5TH>oSWs&M?nzF}y83G&w#pD{#f9X$5xXUSOSm}>^Pk(5)C|0u! zFa*fDFJ@EE$1i$+D<`2ZI$Yw~H+;zHUPe52Wi#)!rI=pFZnB5Tf9B2j17{cjIOB`9 z-tijvCvdH61Pxk}fy(g$&$4}jIAm&&=EPXg`6*a5Nc@K&9`zT;Z-UKLiV0RcI?<&E zT+h?iq5$`iG}HvRkFgSTS!saK8te&}O2F0xZlH^l(t|?jSR$hlpXa(;YI`f1AB7A< zvIAzK7wk{JX|B8&L9rHcPGZTEk#L1ZVBS+l8wy~Ve%nhD-MU>b zw4nu+*hZv{;N;hyuNreRM_jc?un?M>KXi(bX%eJ)?yk%=Utp)&@{FC~?8 zhrjnSt(qPZW*d3(tXw`oA`_;{)8T$GmMy2|-EZddf?ltD zi0%scQ+kL5aQexJIzH}^?H@Y@zU`q1pa*Pmc4l!ZfQ`(5dX5f$u2r#DZTP(tU3WyV zerPe@v7LvF2+js@5$xt>v@d#C*KHjUfKJY%;vo&(CjHJ@AGm9K|20b$z)V79pWDi{ z6Gn=Xlv1J2Qn1tkJZbj9;9T%F*OG3`t25XQPA95wz{bo}Ws732UQ z;kytVkM%=T{+47bCV=FT*UVghzQ_st4~{Jq{!M5;Uy(CeWUu6^@(Y5C;U36puv6hB z8(pho`02D5b*A>EM9i|q$dKoSBe!c#D+Jl2qmoM$mL!`>R@(tPSB9_M_u9;tP!ypJ{ z6c)mwURPUw;^>uoTDcubhh$zm49@num|@cu7TXa91#0|5IQA{!Zq5MWU--7Ozb~Mo zovgQ18@z2oOp$J!mhhe=>Ld68e1nOC=Xv{6kDxb6ZqL`xoy&sb@BKqQhSM$iKdYJg z|B%m(H9&J|6#2;Q_&z=-^=G&yFsu4@T^=f15FMJ8&et4XRNu5rd7tB}NPe&T%gK&QLa#XfAw(MaPQGwA%#&A+FFO+-I`;v2~)POhPu z-!&PD!+!NDD_sl^smpcUfXNx{9XB@`ZIL4V4u+k;-3wFjxk9`qyX73Kyz3?wV@+{` zn%6NB&tl9kvgHqsvgKcYUXEhH^%0!0k2aiOpXV`8?D?7%HM;$W2oRw!0Gg5O7SRJT zzZ}NpaDC1U@moD{;eLMAe~+O7li`_$M75V266t=>P=>$9llS!LF}JAFlPwtNCFyz3 z4kYs>(WIq*&VQIJc0)RSWW#4ahj!pMD>YusG3)App7d-YRz6o@x`d!CX=Q)c{L#hP zlke`doqY_&VqGL+_hk^$q9{%$Imjch>D+=Nn`WR8{15M%;c+p1ePQa1$y2_>hTgg9<)Sf z{12s^&Vx|%7Y2l%+l@4w4weG&^9GNXYxFkw-;*P zYyw9TZhnj^|FJI5Zwte1n5Mz%dm1CeyX8)cwS2<(@IdzkgWT!?{0|QdKG9sSke@h7 z1co%ncsFUaHh?qC%W!@rDR$X1+Jv{*yk!xd*eXRun$Og1(YID@16xcRrz`dHL%Y3p zGJjNZ(mtrzsym$|Vlt*G43c6YtP)`2-d98-X}z@FjqV^Fq|8;iSci|%;~NR{Ryl-` z6ei2t1BosP@I#pb_uRji9NhEq?pzhV<^jOs~QWm{nD- zQ@6){xk(t-;2iD8y>vR$(WAC99Ky2yVShss{aMWOZ)R>@>h&%X)Ma0AaPvGTtS|0$ zh>3$%JwEZq-y#MeO@4dL-|SxS7c7z*6&|4;Ek_B0kVVZ~A5$&}ZVS2I;}`grDb4W$ zeK2rYaUD@$$|UP6xuUIeM@yhB^pizV|K-2Rti~6>wiD8397Qn;IN_9KKt?8ZAEbe z=gk_B&q_!7+$;j4jIQ0;<6eBt*qi&AUm~Q8IEHL@#Sy<6eP6zWzEGsy*XZ?2B{pK3 zo=FqWqMDX67Txzklfc-m~GgrH)Q{^uR1{V#B}tBM}ve z_V&7~HeP8d`^VDiywdLAQafDWT-DdU@kv4FB1#rD?WNUJyXRSz-7pyzQR-h?{D*rK z=Kbm+TuSQ&muqSe_wvCfYIqrMjU;@}5ua1zBa}I@8p)xCFv#?%xywNFOf4G1K>`fy8ZQUl?BH#H>PJXMHe)BNI`39{5 zK4Vk2HA5Yp)}5nK+jhenEfuxDsFIf(qe0IqjI%}VwiD3PDK{NlC2WMJl=L8(71yrR zUY!AASZt^6R)b@$nkB1k$v2cyUV3$tf5F;fr@K;~m$BrdS3O`??<16t?1>S`R%z$pG*Vp|3r_fO(N~qrFml0IGR9(oS0PvB)%lk zKoP*YiL@Q#-%r9->_zLbS0&%1O4*b46>9NF5y8>bfasGX@V>`ao_FcY4+?JtQn24U z>|q1CfT;tFN4O}IVE`gNpe2$4fCSAQhZoZmruRiyrj!gumQB%F^>j-oUQI%`#~Qfp zWQnKvge16cNV|U*(*{3Ew@_7zdseZ0jIKm6BiNX|pv_)rp@SoaOf@yh6mtX|C{$|K z4ncy2g`8sQnIIW;R`1p)i6Obae&>MXA5&vG-uh#f!+5yppXOSC6j8wL#Uw*SP>dJE z2wrjV*<4_zTa2Kc0|_3F*WD6`xOO^cBUYc78{ZbDneg|1f2aWV;L0xdHg`@K)Jq>H zz<5JkxgOF!8pe&llutq9Xhk)hBkvj3NwU+j8r37NW30PJ20&b+1MBfcBv&^wQp~JD z-s0i7XYl`1cfRoa{Z3PcRScOeDKDHl>@`w0--(6911+~z);iRii{Zta!S)qFJ+1Ch zw0kOoBljie7hgVql0!^gTz)T!bzdu^dIbr;BSOY>NpUG|8J&E|zAb7x0r0B;735 zq?Q9mV2Lke2+dnO>52UP8Xti^d(9n^}KSTu*~)&jQiS$bW2Z zgEDdLOj!32%OLRejQWlmhWk9u_RH6k^hpnGoTUc7(POk$b(<}4d#!xVp;2ZeS$$97 z;$P#8v$Bt%ZzZ;KvtDT8mJVt5QBGY*ITiIFGT*A>*UjDJ8|D;%MF?cpM zBOKRp!<5b|#q;Eqit>eeMi2=vFvsZ$m)BXv&tD`2eY9HIwUNxgDW+cQG+z1wz?3*C zI4AJ@!;*(Ue#HuH9sk-OU&pWk)n<*;wfnbALd4UoWCQPm^KY2szYS@Z&+}bP^wma4 zK54Sibpg#$w}Sh}WrBX<-C}h6w5;>Vagr*ebR#>3zQb;d9P?n-5!|nX|5Rb;uivAb z3^KaRrbs5_p5-zCmQ1E6P4o|r`tx0fz!f=tpCgb z(->%3~Qs6PBM4BzB#K|P+B5cB^b0B~)}rc#H8>_e$vwa7DS!+?6Ol@}#h z<@7%pKQG~*4H&Hab*?(!yI*%H`u|bde`p6}DzFOePZ+6wPjbzX)7-O?QJ$)X#W;j> z1h;RUiQ|UW^n*{Jd$(D&@l#;8Gh4qE9*i*o3W9!8%nj09L@1@f3ya<+ol>ae9|JjP(Xvwg! zHrvvgPMmC)W-rLqM{U2Rqo8S-(@uK@wDT_^N6lrEzEpwTyU6>CrDk!6_N^%MH*8P4 zlXjfqqH<>GR%lO2oPf?$`L$$4D5yPx~_bn~R+_y~K>2&O?zKy(7Xx77eWw{D#K` zy)*yIzdynl8X3qu=gJ}|zW*zGhuuMg2MMS*dD61MAaU4;Z@GCu-2K+Xl=JAmIHqA9 zPb@S($XU&N)9v5FL}-Ts^Z-ph=C;KqZ|lZg3@bJJE&Im%S>5r(v2%1@X(+1soKNrY zxAyKta?O@6D=^&WyrKVI2Si>npNkrGe+_AZwtVVi1$m28gN{uI$rzgj3N%g|_S&3m z!c-)%D67UIYB=qA;=!k4XN2(o#Yw)PGsSLE3{PLM4x}GPd|lk7TOc=vTRq@FzpUq} z)!^wj)xrn9GeB-chpT&x@c$x$7zLb3n!S>bXo@(!3dqefq;`6*uSQprV$ zMBs39da-IXzLVvy;TafnyY`#iRa6@{(q0(n8Rxm&-RQuq+Y>v5IW1%folfY-G_@F# zCi5sZQo`MK4GFwnW8hAP&6e*6ADIXryjS-d#YU4v`ul21kz{3cJq!^ba-NyrZW9aW zQ`~SpsA*EZ?2XL2<^y7gEJ;|b`eZ@(HoghPJek6QV@I7&jW>W-AAl%Hc@qdZ zOn3yjOPaOjET$%RFuXH8msseU^z~Ya(Kc3w&$24$MnAM24}}jdo#&Hb1tR@@C1$td zt%7=>QLs)W2}g!*sHl~S&gW~@6~wuc#$`}fs5Ed{7+u63Z)Cebqy!lYxE&Q-c!Yz0dx2=_91||NKxb2DN~-7o zlP!fLpB)ZSH;511z`VQ#_lJqkVpF*?%jxU=N~4>8mkfvR--OO5VvWDGuN2$TrN79h z`n$=%h9@eUHb!Qp^$bW%`))3U_L>0qJ5Nx6*_X~ejH=>rcbm}Xh0j{6No9=*^Nc!% zK?Kg)%fb4?Sxu~$!v+gviSOY5ed*?xSWG+xr4~1b}tl6G1{v%ml!nYXuJl%KrwC*!h zN2m6W&6Gd5?Kq=CciCz9FVTpi+02TmAZN=*CZg4?5Nr# zU8rc&<>d!e?*e!=`CUEy)It2&@-T$O5d7i8PvCfkxEWCYt6wen;K3W1&*|-5^l4ri z91l8aO@#zdw5ozpBRdZ!8n)K8rj7Xg zWa3wvT6|`n9di3e1Hj>T+V9b7GT@Iy@?)L|J zSsF;cZ?;BIe`1BuOuz=hnZ@hcQvhQU) z{<3N2E7ruIm*WP%9W*hVZ_upzomT~=&u}RR>QIp(zGDU0^?`NHuw;|lVTg%oo(b{j z5L57+9QYKguY!zS=xmeN(voG% z^*ufBZ675Q^U3K6?}6BOVjIqa*gX`#HLYEl_-=I(cbjAV>&uv)Y~{7lNGX$HjixdQ zh8BE$G9IiwP3xw}#89e-3YBg?j`NWxlnv;S9v8+QC0f4F`wi<3y2?&#`<>FD`n}`z zUqX?Bj3rI46It?4k~y?Qr|w5w+Jb6CzWh3V)%T^=Sn#Y>_4}q)hsV`P9uZNQL1eqT zoYRd6$->z4h0D2T_r!L!2=1l-HHSesOeU3I+3G$_Y{R33IX^#Ks9ksRY)cDE3Q$w0T^=C%G}9usqS%$mjHDW&Y+tJev6Q zl_q4wvMTIlssD02M~(RR0zCnqUEUOB4ETa24Vs1}Zzp(KW5e!!!we#OJq_rCuwJKRu8fE917wQJdno^u~2EE#s{npS@#@^8wV6PjIV) zp$E%VMDgo%vZ(*^8j>jNt|Iz6SLI}Cp3I+kgk2-rhZcQbs=F1O^}o_LVT!*JVY(M` zcRlXBC@RXX0+Aj^-?xiJ7j^j;t?waz5LhhH6VPocE|)1^>VvkXb!kaYknb8_eiSE? z(ZO)A+tshaae+h?RSiF#E}D7A-H$#q<-?YcWsfAm-YfZ`q~?XI#~O0GJ?@VA zQxwXg%>(F`Y~~?)=y%oUF`7Xre3;SqC~aG!{_}lgsHJi?|Hy`T=y?b&<*s(0D{-2# zGA7X|7(_X5NJ~0}eTOu{wDxUj1P+N9W{9til^D#qQE2#APhtu)SS!^NhiGSFCGs1} zU1QZ0B1uIA1t~}N)sh+-UP#n$C5JS+_jHPh_266P3}arzbnMbCtjnG-#D`0}B607? zep`W9=&4Ci4-njaZbYDxv=U^E$;v=;@_GGg#@bCa(}aR+sSOHXIJRCBWl1Y%Gv@k* z+YCn-KOx1u`_bffD`tO1g)B8I2`?-8r@b2}vrBl0x2gkwtZ19tkzi~Eck+lJ@vMF~3fqYxd3~ZyE5MT~{uOZ^U*G*l zW#N{5K6SI>Z49H6AvP=DYqKQEu-QGRQ5ZHvT|TLsNko>ThFS&?&xSzU7oD+8i~sKA z$OYDuK3v*^{?1EcY%U|0h4>q|Uo$-IL5Jib-wXP?SOKt2IaJuc+(#44dzY(|_jf_O z$?UMqP&wjp$@hsogKe3w#IH|t9AD5Dq5N0;gIXm`&1d7+7xDSZRr%|Z6abVmDP3o9 zbHZ5jy@uG{K-BnX=~%vE7dL&cRIFWcQQLx^?2b+PuR*-?#uuwyGIr&z;NOKs|9KC@ zQ5fg6fiaKo>eXEd`OkX{?~MO0_y6S$92w=w$SIPm@?DYq&x8Il6AAdOm3w!CuK(q| zBmrSyP%CDR`LjEF`@cOmFo@Rr4%Poz?!TDh9f))fA%;Yw6?_+U|1a=rE{sv(G+tQJOXdfb>`VmO>B>pcC`WNF+#fJWu<=!zH zNLUCYWOBF%Uq74;^14*9Ml>oN`!?P=BxR?Qha#^Si9&W`+sE&MIoj1ei_nEGV zi3?;?%tFGH*W1qK6|59%UN8kK+P-fLZ=vx80VH^uMwyWFlIyG^OmCmZ?c5lDWggN{ zJAH_P4cX5mOq%s=*?^jw7IUK6vL&{?;5+&(SQy@*_h-+cwhvXF zJyEeL>SIJGf@V<{dGDxpkTE#0FHW9|Xq=eHU`!b?=J;VG*|-jsoiDJx)gxPf!D(DR zfjF5j70k>d<0#jbIuDJh2oRs$)x5q z>UM^Fy#w-dF42sw^K?U3^rQ5WyMz{i^)CLER$gMYO@O7wScBu#th+Yva}3zkHa z1ZnbgyMk0z<(aB2ezVF%!eQ9nK9*t><#+ay^<6ug@*CX(K8-fllV$P3##15Gt8Vl< zkKj#dQehUO-g+}tB2tvUR6JB4MY|p#T@e0l=3S7d`$L}8qkX4m$06KJ@eE6+P+Cv$r=mb7YyT!zXA!DHmjnn|J_VvLU;&T$~}_F#-|Gn z{atl61iPPl8VY2M%Nols*y9ICU&439P8gqX|5YhK+znnOr)>Q=bTo9|HSwu{!M5=b z+oU63NJrJ)+C1Nr(3wI0`|E*{Z;3v}dLyo8m26esR+LS1Y_y-3Y~UQI0eq?Qao2nD z_jeH}D907^CszVi?7MEj|MHDEwY*bR#WLywU$`G2|L6r*67msinQtx9^1Y9$qz;Ih7oai?7b^K6>I+eWys{i5)I6g|)$0QRq5=f@XCjIU%(b_=jGTHmPKKq@StQ$Z;NSMTbE)>>AT)lvWxKd zPv+7J$GjDN`2Cl{@eudNy8>5hwg&%!ZSrbIgyNKbL3%S3>Iz(KgR92i_5%;f}*EKma;}3gAo*<+1do9MQQ0p z+Beu7zX~$M`Fy{9Ge0qF*aa=CZto~xTIY45Rt;XV0Sf8%>=VUbh1_R(ZF*H5hHF?H zC!bBf+XCYREgFk&heu4#vB4zUmMELlLo1o{(%$29jq8w(>AjnC&ypWbWJC?Q3lh%n z2gQ<(#1rM>hHehLKUM~rZ}H$S)i+#FDF)_*pwlG);I_)4Q?r0O~!H z>XA)xwvp`S5tmrn*sn>v-QZAneCjmpO$unWIG U!8Zw3xbl;R%=OAo)MS9RDtDV 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 MSASM5YZm%{0L;F}N&o-= literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/images/overlappingpodIPs.png b/docs/features/user-defined-networks/images/overlappingpodIPs.png new file mode 100644 index 0000000000000000000000000000000000000000..be29b670924a5df603b9d2480bb75b2525b77688 GIT binary patch literal 166785 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 literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/images/tenant-isolation-lighter.png b/docs/features/user-defined-networks/images/tenant-isolation-lighter.png new file mode 100644 index 0000000000000000000000000000000000000000..11cfb812bb0fb78c40ac058455caf483980b30ea GIT binary patch literal 345958 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

j#CH>ErmL4gMw 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 IY2$$Z15BJ+umAu6 literal 0 HcmV?d00001 diff --git a/docs/features/user-defined-networks/user-defined-networks.md b/docs/features/user-defined-networks/user-defined-networks.md new file mode 100644 index 0000000000..1b0393bd5e --- /dev/null +++ b/docs/features/user-defined-networks/user-defined-networks.md @@ -0,0 +1,692 @@ +# User Defined Networks + +## Introduction + +User Defined Networks (UDNs) in OVN-Kubernetes offer flexible network configurations +for users, going beyond the traditional single default network model for all pods +within a Kubernetes cluster. This feature addresses the diverse and advanced networking +requirements of various applications and use cases. + +## Motivation + +Traditional Kubernetes networking, which typically connects all pods to a default Layer3 network, +lacks the necessary flexibility for many modern use cases and advanced network capabilities. +UDNs provide several key advantages: + +* **Workload/Tenant Isolation**: UDNs enable the grouping of different application +types into isolated networks within the cluster, preventing communication between them. +* **Flexible Network Topologies**: Users can create different types of overlay networks +that suits their use cases and then attach their workloads to these networks which are +then isolated natively. +* **Overlapping Pod IPs**: UDNs allow the creation of multiple networks within a cluster that +can use the same IP address ranges for pods, expanding deployment scenarios. + +See the [enhancement] for more details. + +[enhancement]: https://ovn-kubernetes.io/okeps/okep-5193-user-defined-networks/ + +### User-Stories/Use-Cases + +See the [user-stories] defined in the enhancement. + +[user-stories]: https://ovn-kubernetes.io/okeps/okep-5193-user-defined-networks/#user-storiesuse-cases + +The two main user stories are: + +#### Native Namespace Isolation using Networks + +![namespace-isolation](images/native-namespace-isolation.png) +Here the blue, green, purple and yellow networks within those +namespaces cannot reach other and hence provide native isolation +to the workloads in those networks from workloads in other networks. + +#### Native Tenant Isolation using Networks + +![tenant-isolation](images/tenant-isolation-lighter.png) +Here the tenants BERLIN and MUNICH are isolated from each other. +So the workloads in namespaces belonging to BERLIN across the +four namespaces - purple, yellow, green and blue can talk to each other +but they can't talk to the workloads belogning to MUNICH tenant +across namespaces brown, cyan, orange and violet. + +There are more user stories which will be covered in the sections below +with appropriate diagrams. + +## How to enable this feature on an OVN-Kubernetes cluster? + +This feature is enabled by default on all OVN-Kubernetes clusters. +You don't need to do anything extra to start using this feature. +There is a Feature Config option `--enable-network-segmentation` under +`OVNKubernetesFeatureConfig` config that can be used to disable this +feature. However note that disabling the feature will not remove +existing CRs in the cluster. This feature has to be enabled along with +the flag for multiple-networks `--enable-multi-network` since UDNs +use Network Attachment Definitions as underlying implementation detail +construct and reuse the secondary network controllers. + +## Workflow Description + +A tenant consists of one or more namespaces in a cluster. Network segmentation +can be achieved by attaching 1 or more namespaces as part of same network which +are then not reachable from other namespaces in the cluster that are not part +of that network. + +## Implementation Details + +### User facing API Changes + +The implementation of UDNs introduces two new Custom Resource Definitions (CRDs) +for network creation: + +* Namespace-scoped **UserDefinedNetwork** (UDN): This CRD is for tenant owners, +allowing them to create networks within their namespace. This provides isolation +for their namespaces from other tenants' namespaces. + +* Cluster-scoped **ClusterUserDefinedNetwork** (CUDN): This CRD provides cluster +administrators with the ability to allow multiple namespaces to be part of the +same network that is then isolated from other networks. + +**NOTE**: For a namespace to be considered for UDN creation, it must be +labeled with `k8s.ovn.org/primary-user-defined-network` at the time of its +creation. This label cannot be updated later, and if absent, the namespace +will not be considered for UDN creation. + +See the [api-specification-docs] for information on each of the fields + +[api-specification-docs]: https://ovn-kubernetes.io/api-reference/userdefinednetwork-api-spec/ + +### OVN-Kubernetes Implementation Details + +`UserDefinedNetworks` is an opinionated implementation +of multi-networking in Kubernetes. There are two types of +UserDefinedNetworks: + +* `Primary`: Also known as P-UDN -> Primary UserDefinedNetwork: This means the + network will act as the primary network for the pod and all default traffic + will pass through this network except for Kubelet healthchecks which still uses + the default cluster-wide network as Kubernetes is not multi-networking aware. +* `Secondary`: Also known as S-UDN -> Secondary UserDefinedNetwork: This means the + network will act as only a secondary network for the pod and only pod traffic + that is part of the secondary network may be routed through this interface. These + types of networks have existed for a long time usually created using + `NetworkAttachmentDefinitions` API but are now more standardised using UDN CRs. + +OVN-Kubernetes currently doesn't support north-south traffic for +secondary networks and none of the core Kubernetes features like Services will work there. +Primary networks on the other hand has full support for all features as present +on cluster default network. + +UDNs can have flexible virtual network topologies to suit the use cases +of end users. Currently supported topology types for a given network include: + +**Layer3 Networks** + +`Layer3`: is a topology type wherein the pods or VMs are connected to their +node’s local router and all these routers are then connected to the distributed +switch across nodes. + * Each pod would hence get an IP from the node's subnet segment + * When in doubt which topology to use go with layer3 which is the same topology + as the cluster default network + * Can be of type `primary` or `secondary` + +Let's see how a Layer3 Network looks on the OVN layer. + +![l3-UDN](images/L3DeepDive.png) + +Here we can see a blue and green P-UDN. On node1, pod1 is part of green UDN and +pod2 is part of blue UDN. They each have a udn-0 interface that is attached to +the UDN network and a eth0 interface that is attached to the cluster default +network (grey color) which is only used for kubelet healthchecks. + +**Layer2 Networks** + +`Layer2`: is a topology type wherein the pods or VMs are all connected to the +same layer2 flat switch. + * Usually used when the applications deployed expect a layer2 type network + connection (Perhaps applications want a single broadcast domain, latency sensitive, use proprietary L2 protocols) + * Common in Virtualization world for seamless migration of the VM since + persistent IPs of the VMs can be preserved across nodes in your cluster + during live migration + * Can be of type `primary` or `secondary` + +![l2-UDN](images/L2DeepDive-2segments.png) + +Here we can see a blue and green P-UDN. On node1, pod1 is part of green UDN and +pod2 is part of blue UDN. They each have a udn-0 interface that is attached to +the UDN network and a eth0 interface that is attached to the cluster default +network (grey color) which is only used for kubelet healthchecks. + +**Localnet Networks** + +`Localnet`: is a topology type wherein the pods or VMs attached to a localnet +network on the overlay can egress to the provider’s physical network + * without SNATing to nodeIPs… preserves the podIPs + * podIPs can be on the same subnet as the provider’s VLAN + * VLAN IDs can be used to mark the traffic coming from the localnet for + isolation on provider network + * Can be of type `secondary`, it cannot be a `primary` network of a pod. + * Only `ClusterUserDefinedNetwork` supports `localnet` + +![localnet-UDN](images/localnet-topology.png) + +Here we can see blue and green S-UDN localnet networks. + +The ovnkube-cluster-manager component watches for these CR's and the controller +reacts to it by creating NADs under the hood. The ovnkube-controller watches for +the NADs and creates the required OVN logical constructs in the OVN database. +The ovnkube-node also adds the required gateway plumbing such as openflows and +VRF tables and routes to provide networking to these networks. + +### Creating UserDefinedNetworks + +Now that we understand what a UDN is, let's get handson! + +Let's create two namespaces `blue` and `green`: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: blue + labels: + name: blue + k8s.ovn.org/primary-user-defined-network: "" +--- +apiVersion: v1 +kind: Namespace +metadata: + name: green + labels: + name: green + k8s.ovn.org/primary-user-defined-network: "" +``` + +Sample API yaml for create two `UserDefinedNetworks` of type `Layer3` in these namespaces: + +```yaml +apiVersion: k8s.ovn.org/v1 +kind: UserDefinedNetwork +metadata: + name: blue-network + namespace: blue + labels: + name: blue + purpose: kubecon-eu-2025-demo +spec: + topology: Layer3 + layer3: + role: Primary + subnets: + - cidr: 103.103.0.0/16 + hostSubnet: 24 +--- +apiVersion: k8s.ovn.org/v1 +kind: UserDefinedNetwork +metadata: + name: green-network + namespace: green + labels: + name: green + purpose: kubecon-eu-2025-demo +spec: + topology: Layer3 + layer3: + role: Primary + subnets: + - cidr: 203.203.0.0/16 + hostSubnet: 24 +``` + +### Inspecting a UDN Pod + +Now if you create pods on these two namespaces and try to ping one pod from +the other pod, you will see that connection won't work. + +``` + $ k get pods -n blue -owide + NAME READY STATUS RESTARTS AGE IP NODE + blue 1/1 Running 0 9h 10.244.0.7 ovn-worker + blue1 1/1 Running 0 8h 10.244.1.4 ovn-worker2 + + $ k get pods -n green -owide + NAME READY STATUS RESTARTS AGE IP NODE + green 1/1 Running 0 9h 10.244.0.6 ovn-worker +``` + +NOTE: Doing kubectl get pods and describe pod will all show the default network +podIP which is not to be confused with the UDN podIPs. Remember how we said +Kubernetes is not multi-networking aware? Hence pod.Status.IPs will always +be the IPs that kubelet is aware of for healthchecks to work. + +In order to see the real UDN PodIPs, always do a describe on the pod and see +the following annotations on the pod: +``` +$ k get pod -n green green -oyaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + k8s.ovn.org/pod-networks: '{"default":{"ip_addresses":["10.244.0.6/24"], + "mac_address":"0a:58:0a:f4:00:06","routes":[{"dest":"10.244.0.0/16", + "nextHop":"10.244.0.1"},{"dest":"100.64.0.0/16","nextHop":"10.244.0.1"}], + "ip_address":"10.244.0.6/24","role":"infrastructure-locked"}, + "green/green-network":{"ip_addresses":["203.203.2.5/24"], + "mac_address":"0a:58:c8:0a:02:05","gateway_ips":["203.203.2.1"], + "routes":[{"dest":"203.203.0.0/16","nextHop":"203.203.2.1"}, + {"dest":"10.96.0.0/16","nextHop":"203.203.2.1"},{"dest":"100.65.0.0/16", + "nextHop":"203.203.2.1"}],"ip_address":"203.203.2.5/24","gateway_ip":"203.203.2.1", + "role":"primary"},"green/green-secondary-network":{"ip_addresses":["100.10.1.7/24"], + "mac_address":"0a:58:64:0a:01:07","routes":[{"dest":"100.10.0.0/16", + "nextHop":"100.10.1.1"}],"ip_address":"100.10.1.7/24","role":"secondary"}}' +``` +The above shows the OVN-Kubernetes IPAM Annotation for each type of network: +* `default` which is the cluster-wide `infrastructure-locked` network only used + for Kubelet health checks and pod has IP 10.244.0.6 here +* `primary` which is the primary UDN for the pod through which all traffic + passes through and pod has IP 203.203.2.5. +* `secondary` which is the secondary UDN network for the pod from which pod has IP 100.10.1.7 + +One can also use the multus annotation to figure out the podIPs on each interface: + +``` +$ oc get pod -n green green -oyaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + k8s.v1.cni.cncf.io/network-status: |- + [{ + "name": "ovn-kubernetes", + "interface": "eth0", + "ips": [ + "10.244.0.6" + ], + "mac": "0a:58:0a:f4:00:06", + "dns": {} + },{ + "name": "ovn-kubernetes", + "interface": "ovn-udn1", + "ips": [ + "200.203.2.5" + ], + "mac": "0a:58:c8:0a:02:05", + "default": true, + "dns": {} + },{ + "name": "green/green-secondary-network", + "interface": "net1", + "ips": [ + "100.10.1.7" + ], + "mac": "0a:58:64:0a:01:07", + "dns": {} + }] +``` + +### KubeletHealthChecks for UDN pods + +In each of the above diagrams we saw a grey network still attached to all +pods across all UDNs. This represents the cluster default network which +is `infrastructure-locked` for primary-UDN pods and is only used for healthchecks. + +We add UDN Isolation ACLs and cgroups NFTable rules on these pod ports so that +no traffic except healthcheck traffic from kubelet is allowed to reach these pods. + +Using OVN ACLs, we ensure only traffic from kubelet is allowed on the +default `eth0` interface of the pods: + +``` +_uuid : 1278b0f4-0a14-4637-9d05-83ba9df6ec03 +action : allow +direction : from-lport +external_ids : {direction=Egress, "k8s.ovn.org/id"="default-network-controller:UDNIsolation:AllowHostARPSecondary:Egress", "k8s.ovn.org/name"=AllowHostARPSecondary, "k8s.ovn.org/owner-controller"=default-network-controller, "k8s.ovn.org/owner-type"=UDNIsolation} +label : 0 +log : false +match : "inport == @a8747502060113802905 && (( arp && arp.tpa == 10.244.2.2 ) || ( nd && nd.target == fd00:10:244:3::2 ))" +meter : acl-logging +name : [] +options : {} +priority : 1001 +sample_est : [] +sample_new : [] +severity : [] +tier : 0 + +_uuid : 489ae95b-ae9d-47d0-bf1d-b2477a9ed6a2 +action : allow +direction : to-lport +external_ids : {direction=Ingress, "k8s.ovn.org/id"="default-network-controller:UDNIsolation:AllowHostARPSecondary:Ingress", "k8s.ovn.org/name"=AllowHostARPSecondary, "k8s.ovn.org/owner-controller"=default-network-controller, "k8s.ovn.org/owner-type"=UDNIsolation} +label : 0 +log : false +match : "outport == @a8747502060113802905 && (( arp && arp.spa == 10.244.2.2 ) || ( nd && nd.target == fd00:10:244:3::2 ))" +meter : acl-logging +name : [] +options : {} +priority : 1001 +sample_est : [] +sample_new : [] +severity : [] +tier : 0 + + +_uuid : 980be3e4-75af-45f7-bce3-3bb08ecd8b3a +action : drop +direction : to-lport +external_ids : {direction=Ingress, "k8s.ovn.org/id"="default-network-controller:UDNIsolation:DenySecondary:Ingress", "k8s.ovn.org/name"=DenySecondary, "k8s.ovn.org/owner-controller"=default-network-controller, "k8s.ovn.org/owner-type"=UDNIsolation} +label : 0 +log : false +match : "outport == @a8747502060113802905" +meter : acl-logging +name : [] +options : {} +priority : 1000 +sample_est : [] +sample_new : [] +severity : [] +tier : 0 + +_uuid : cca19dca-1fde-4a14-841d-7e2cce804de4 +action : drop +direction : from-lport +external_ids : {direction=Egress, "k8s.ovn.org/id"="default-network-controller:UDNIsolation:DenySecondary:Egress", "k8s.ovn.org/name"=DenySecondary, "k8s.ovn.org/owner-controller"=default-network-controller, "k8s.ovn.org/owner-type"=UDNIsolation} +label : 0 +log : false +match : "inport == @a8747502060113802905" +meter : acl-logging +name : [] +options : {} +priority : 1000 +sample_est : [] +sample_new : [] +severity : [] +tier : 0 +``` + +![kubelet-healthchecks-part1](images/KubeletHealthchecks-Part1.png) + +As you can see here a default network pod, `pod2` can't reach +the UDN pod `pod1` via its eth0 interface thanks to the ACLs in place. +So no traffic from the UDN pod ever leaves via `eth0`. The only traffic +that is allowed via `eth0` interface is the kubelet probe traffic. + +But given how we have allow ACLs for kubelet traffic, but this matches +on management portIP which is the hostIP, any process on the host can +potentially reach the UDN pods. In order to have more tighter security, +we have cgroups based NFT rules on the host to prevent any non-kubelet +process from being able to reach the default network `eth0` port on +UDN pods. + +![kubelet-healthchecks-part2](images/KubeletHealthchecks-Part2.png) + +These rules look like this: + +``` + chain udn-isolation { + comment "Host isolation for user defined networks" + type filter hook output priority filter; policy accept; + ip daddr . meta l4proto . th dport @udn-open-ports-v4 accept + ip daddr @udn-open-ports-icmp-v4 meta l4proto icmp accept + socket cgroupv2 level 2 475436 ip daddr @udn-pod-default-ips-v4 accept + ip daddr @udn-pod-default-ips-v4 drop + ip6 daddr . meta l4proto . th dport @udn-open-ports-v6 accept + ip6 daddr @udn-open-ports-icmp-v6 meta l4proto ipv6-icmp accept + socket cgroupv2 level 2 475436 ip6 daddr @udn-pod-default-ips-v6 accept + ip6 daddr @udn-pod-default-ips-v6 drop + } + + set udn-open-ports-v4 { + type ipv4_addr . inet_proto . inet_service + comment "default network open ports of pods in user defined networks (IPv4)" + } + + set udn-open-ports-v6 { + type ipv6_addr . inet_proto . inet_service + comment "default network open ports of pods in user defined networks (IPv6)" + } + + set udn-open-ports-icmp-v4 { + type ipv4_addr + comment "default network IPs of pods in user defined networks that allow ICMP (IPv4)" + } + + set udn-open-ports-icmp-v6 { + type ipv6_addr + comment "default network IPs of pods in user defined networks that allow ICMP (IPv6)" + } + + set udn-pod-default-ips-v4 { + type ipv4_addr + comment "default network IPs of pods in user defined networks (IPv4)" + } + + set udn-pod-default-ips-v6 { + type ipv6_addr + comment "default network IPs of pods in user defined networks (IPv6)" + } +``` + +The only exception to this is when users annotate +the UDN pod using the `open-default-ports` annotation: +``` +k8s.ovn.org/open-default-ports: | + - protocol: tcp + port: 80 + - protocol: udp + port: 53 +``` +which means we open up allow ACLs and nftrules to allow traffic +to reach at those ports. + +### Overlapping PodIPs + +Two networks can have the same subnet since they are completely +isolated. We use a `masqueradeIP` SNAT per UDN to avoid conntrack +collisions on the host. So traffic leaving each UDN is SNATed to +a unique IP before being sent to the host. + +![overlapping-podips](images/overlappingpodIPs.png) + +### VM LiveMigration and PersistentIPs over Layer2 UDNs + +Users can use the `layer2` topology when creating virtual machines +on OVN-Kubernetes and can easily live migrate the VMs across nodes +along with preserving their IPs. + +![overlapping-podips](images/Layer2VMMigration.png) + +### Services on UDNs + +Creating a service on UDNs is same as creating them on default +network, no extra plumbing is required. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: service-blue + namespace: blue + labels: + network: blue +spec: + type: LoadBalancer + selector: + network: blue + ports: + - name: web + port: 80 + targetPort: 8080 +``` +``` +$ k get svc -n blue +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service-blue LoadBalancer 10.96.207.175 172.19.0.10 80:31372/TCP 5s +$ k get endpointslice -n blue +NAME ADDRESSTYPE PORTS ENDPOINTS AGE +service-blue-55d6c IPv4 8080 103.103.1.5,103.103.0.5 65s +service-blue-pkll7 IPv4 8080 10.244.0.3,10.244.1.8 66s +``` +One set of endpoints show the UDN ntework IPs of the pods and the other set +shows default network IPs. + +When the service is created inside the blue namespace, the +clusterIPs get automatically isolated from pods in other networks. +However nodeports, loadbalancerIPs and externalIPs can be reached +across UDNs. + +### EndpointSlices mirror controller for User-Defined Networks + +Pods that use a UDN as their primary network will still have the cluster +default network IP in their status. For services this results in the EndpointSlices +providing the IPs of the cluster default network in the Kubernetes API. To enable +services support for primary user-defined networks, the EndpointSlices mirror +controller was introduced to create custom EndpointSlices with user-defined +network IP addresses extracted from OVN-Kubernetes annotations. + +The introduced controller duplicates the default EndpointSlices, creating +new copies that include IP addresses from primary user-defined network. It +bypasses EndpointSlices in namespaces that do not have a user-defined primary +network. The controller lacks specific logic for selecting endpoints, it only +replicates those generated by the default controller and replaces the IP addresses. +For host-networked pods, the controller retains the same IP addresses as the +default controller. Custom EndpointSlices not created by the default controller +are not processed. + +The default EndpointSlices controller creates objects that contain the following labels: + +- `endpointslice.kubernetes.io/managed-by:endpointslice-controller.k8s.io` - Indicates + that the EndpointSlice is managed by the default Kubernetes EndpointSlice controller. +- `kubernetes.io/service-name:` - The service that this EndpointSlice + belongs to, used by the default network service controller. + +The EndpointSlices mirror controller uses a separate set of labels: + +- `endpointslice.kubernetes.io/managed-by:endpointslice-mirror-controller.k8s.ovn.org` - Indicates + that the EndpointSlice is managed by the mirror controller. +- `k8s.ovn.org/service-name:` - The service that this mirrored EndpointSlice + belongs to, used by the user-defined network service controller. Note that the label + key is different from the default EndpointSlice. +- `k8s.ovn.org/source-endpointslice-version:` - The + last reconciled resource version from the default EndpointSlice. + +and annotations (Label values have a length limit of 63 characters): +- `k8s.ovn.org/endpointslice-network:` - The user-defined network + that the IP addresses in the mirrored EndpointSlice belong to. +- `k8s.ovn.org/source-endpointslice:` - The name of the + default EndpointSlice that was the source of the mirrored EndpointSlice. + +Example: + +With the following NetworkAttachmentDefinition: + +```yaml +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: l3-network + namespace: nad-l3 +spec: + config: |2 + { + "cniVersion": "1.0.0", + "name": "l3-network", + "type": "ovn-k8s-cni-overlay", + "topology":"layer3", + "subnets": "10.128.0.0/16/24", + "mtu": 1300, + "netAttachDefName": "nad-l3/l3-network", + "role": "primary" + } +``` + +We can observe the following EndpointSlices created for a one-replica deployment +exposed through a `sample-deployment` service: + + + + + + + + + +
Default EndpointSliceMirrored EndpointSlice
+ +```yaml +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: sample-deployment-rkk4n + generateName: sample-deployment- + generation: 1 + labels: + app: l3pod + endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io + kubernetes.io/service-name: sample-deployment + name: sample-deployment-rkk4n + namespace: nad-l3 + resourceVersion: "31533" +addressType: IPv4 +endpoints: +- addresses: + - 10.244.1.17 + conditions: + ready: true + serving: true + terminating: false + nodeName: ovn-worker + targetRef: + kind: Pod + name: sample-deployment-6b64bd4868-7ftt6 + namespace: nad-l3 + uid: 6eb5d05c-cff4-467d-bc1b-890443750463 +ports: +- name: "" + port: 80 + protocol: TCP +``` + + + +```yaml +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: l3-network-sample-deployment-hgkmw + generateName: l3-network-sample-deployment- + labels: + endpointslice.kubernetes.io/managed-by: endpointslice-mirror-controller.k8s.ovn.org + k8s.ovn.org/service-name: sample-deployment + k8s.ovn.org/source-endpointslice-version: "31533" + annotations: + k8s.ovn.org/endpointslice-network: l3-network + k8s.ovn.org/source-endpointslice: sample-deployment-rkk4n + namespace: nad-l3 + resourceVersion: "31535" +addressType: IPv4 +endpoints: +- addresses: + - 10.128.1.3 + conditions: + ready: true + serving: true + terminating: false + nodeName: ovn-worker + targetRef: + kind: Pod + name: sample-deployment-6b64bd4868-7ftt6 + namespace: nad-l3 + uid: 6eb5d05c-cff4-467d-bc1b-890443750463 +ports: +- name: "" + port: 80 + protocol: TCP + +``` + +
+ +That's how behind the scenes services on UDNs are implemented. + +## References + +* Use the workshop yamls [here](https://github.com/tssurya/kubecon-eu-2025-london-udn-workshop/tree/main/manifests) to play around diff --git a/mkdocs.yml b/mkdocs.yml index f82f75c977..82ce0965e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -110,6 +110,8 @@ nav: - AdminPolicyBasedExternalRoutes: api-reference/admin-epbr-api-spec.md - UserDefinedNetwork: api-reference/userdefinednetwork-api-spec.md - Features: + - Universal Connectivity: + - UserDefinedNetwork: features/user-defined-networks/user-defined-network.md - NetworkSecurityControls: - AdminNetworkPolicy: features/network-security-controls/admin-network-policy.md - NetworkPolicy: features/network-security-controls/network-policy.md From dda44c8399d41d799940f2d32fd083eb0ea988e0 Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Wed, 6 Aug 2025 11:59:23 +0200 Subject: [PATCH 205/278] ovnkube: Do not exit early on ovs CLI initialization errors OVS client setup can fail if something cancels the context before it's creation during startup. If that happens it used to exit early without returning any other errors or doing any cleanup. Signed-off-by: Patryk Diak --- go-controller/cmd/ovnkube/ovnkube.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/go-controller/cmd/ovnkube/ovnkube.go b/go-controller/cmd/ovnkube/ovnkube.go index 8021297d14..83ef37c2d8 100644 --- a/go-controller/cmd/ovnkube/ovnkube.go +++ b/go-controller/cmd/ovnkube/ovnkube.go @@ -445,7 +445,7 @@ func runOvnKube(ctx context.Context, runMode *ovnkubeRunMode, ovnClientset *util wg := &sync.WaitGroup{} var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) - var managerErr, controllerErr, nodeErr error + var managerErr, controllerErr, nodeErr, ovsCLIErr error if runMode.clusterManager { wg.Add(1) @@ -579,21 +579,23 @@ func runOvnKube(ctx context.Context, runMode *ovnkubeRunMode, ovnClientset *util // Note: for ovnkube node mode dpu-host no metrics is required as ovs/ovn is not running on the node. if config.OvnKubeNode.Mode != types.NodeModeDPUHost && config.Metrics.OVNMetricsBindAddress != "" { metricsScrapeInterval := 30 - defer cancel() if ovsClient == nil { ovsClient, err = libovsdb.NewOVSClient(ctx.Done()) if err != nil { - return fmt.Errorf("failed to initialize libovsdb vswitchd client: %w", err) + ovsCLIErr = fmt.Errorf("failed to initialize libovsdb vswitchd client: %w", err) + cancel() } } - if config.Metrics.ExportOVSMetrics { - metrics.RegisterOvsMetricsWithOvnMetrics(ovsClient, metricsScrapeInterval, ctx.Done()) + if ovsClient != nil { + if config.Metrics.ExportOVSMetrics { + metrics.RegisterOvsMetricsWithOvnMetrics(ovsClient, metricsScrapeInterval, ctx.Done()) + } + metrics.RegisterOvnMetrics(ovnClientset.KubeClient, runMode.identity, + ovsClient, metricsScrapeInterval, ctx.Done()) + metrics.StartOVNMetricsServer(config.Metrics.OVNMetricsBindAddress, + config.Metrics.NodeServerCert, config.Metrics.NodeServerPrivKey, ctx.Done(), wg) } - metrics.RegisterOvnMetrics(ovnClientset.KubeClient, runMode.identity, - ovsClient, metricsScrapeInterval, ctx.Done()) - metrics.StartOVNMetricsServer(config.Metrics.OVNMetricsBindAddress, - config.Metrics.NodeServerCert, config.Metrics.NodeServerPrivKey, ctx.Done(), wg) } // run until cancelled @@ -604,7 +606,7 @@ func runOvnKube(ctx context.Context, runMode *ovnkubeRunMode, ovnClientset *util wg.Wait() klog.Infof("Stopped ovnkube") - err = utilerrors.Join(managerErr, controllerErr, nodeErr) + err = utilerrors.Join(managerErr, controllerErr, nodeErr, ovsCLIErr) if err != nil { return fmt.Errorf("failed to run ovnkube: %w", err) } From 0a387dcbed64e1352c1473a2600291c6a7fc8e6f Mon Sep 17 00:00:00 2001 From: Patryk Diak Date: Tue, 5 Aug 2025 18:12:09 +0200 Subject: [PATCH 206/278] Bump OVN to 25.03.0-73.el9fdp for OCP and 25.03.1-36.el9s for OKD A list of relevant bug fixes and new core OVN features picked up by the bump: Bug fixes: ========== - logical-fields: Fix IPv6 dp flow explosion caused by ip6.mcast_rsvd. (#FDP-1557) https://issues.redhat.com/browse/FDP-1557 - controller: Slightly optimize the runtime_data handler for sb_ro. - Revert "northd: Don't skip the unSNAT stage for traffic towards VIPs." - fixes HWOL for node port traffic with NVidia NICs - controller: Install QoS rules even on 'system' ports. (#FDP-1472) https://issues.redhat.com/browse/FDP-1472 - controller: Make sure we run engine_cleanup after thread destroy. - northd: Sample_Collector.set_ids can actually be 32-bit values. New Features: ============= - Added support to choose selection methods - dp_hash or hash (with specified hash fields) for ECMP routes while choosing nexthop. - Added support for Spine-Leaf topology of logical switches by adding a new LSP type 'switch' that can directly connect two logical switches. Supported for both distributed and transit switches. - SSL/TLS: * TLSv1 and TLSv1.1 protocols are deprecated and disabled by default on OpenFlow and database connections. Use --ssl-protocols to turn them back on. Support will be fully removed in the next release. * OpenSSL 1.1.1 or newer is now required for SSL/TLS support. * The protocol list in --ssl-protocols or corresponding database column now supports specifying simple protocol ranges like: - "TLSv1-TLSv1.2" to enable all protocols between TLSv1 and TLSv1.2. - "TLSv1.2+" to enable protocol TLSv1.2 and later. The value must be a list of protocols or exactly one protocol range. * Added explicit support for TLSv1.3. It can now be enabled via --ssl-protocols (TLSv1.3 was supported in earlier versions only when this option was not set). TLS ciphersuites for TLSv1.3 and later can be configured via --ssl-ciphersuites (--ssl-ciphers only applies to TLSv1.2 and earlier). - Add "arp-nd-max-timeout-sec" config option to vswitchd external-ids to configure the interval (in seconds) between ovn-controller originated ARP/ND packets used for tracking ECMP next hop MAC addresses. - Auto flush ECMP symmetric reply connection states when an ECMP route is removed by the CMS. This behavior is controlled by the "ecmp_nexthop_monitor_enable" config option in the NB_Global table. Disabled by default. - Improved handling of IPv6 traffic by enabling address prefix tracking in OVS for both IPv4 and IPv6 addresses, whenever possible, reducing the amount of IPv6 datapath flows. - Add concept of Transit Routers, users are now allowed to specify options:requested-chassis for router ports; if the chassis is remote then the router port will behave as a remote port. - Added a new ACL option "persist-established" that allows for established connections to bypass ACL matching. This way, if an ACL match changes, traffic on the established connection can still pass. - Logical router policies can now be arranged in chains. Using the new "jump" action, combined with new "chain" and "jump_chain" columns, allows for policies to be chained together. - Dynamic Routing support (FRR BGP integration for unicast routing) - Add "options:ct-commit-all" to LR, that enables commit of all traffic to DNAT and SNAT zone when LR is stateful. Co-authored-by: Dumitru Ceara Signed-off-by: Patryk Diak --- Dockerfile.base | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.base b/Dockerfile.base index 071f6e6a01..e6cac3158a 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -17,13 +17,13 @@ RUN dnf --setopt=retries=2 --setopt=timeout=2 install -y --nodocs \ # reduces the number of variables in the system) and receive all the CVE and # bug fixes automatically. ARG ovsver=3.5 -ARG ovnver=24.09.2-69.el9fdp +ARG ovnver=25.03.0-73.el9fdp # NOTE: Ensure that the versions of OVS and OVN are overriden for OKD in each of the subsequent layers. # Centos and RHEL releases for ovn are built out of sync, so please make sure to bump for OKD with # the corresponding Centos version when updating the OCP version. ARG ovsver_okd=3.5 # We are not bumping the OVN version for OKD since the FDP release is not done yet. -ARG ovnver_okd=24.09.1-10.el9s +ARG ovnver_okd=25.03.1-36.el9s RUN INSTALL_PKGS="iptables nftables" && \ source /etc/os-release && \ From 2a0cd67da918e58a95e7ee473d16ef5681b5c123 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Thu, 7 Aug 2025 14:11:14 +0200 Subject: [PATCH 207/278] nit-fix: The filename point to UDN docs is wrong Signed-off-by: Surya Seetharaman --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 82ce0965e0..1dba124302 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -111,7 +111,7 @@ nav: - UserDefinedNetwork: api-reference/userdefinednetwork-api-spec.md - Features: - Universal Connectivity: - - UserDefinedNetwork: features/user-defined-networks/user-defined-network.md + - UserDefinedNetwork: features/user-defined-networks/user-defined-networks.md - NetworkSecurityControls: - AdminNetworkPolicy: features/network-security-controls/admin-network-policy.md - NetworkPolicy: features/network-security-controls/network-policy.md From b2fa79a5f50f5bfb2a448dde714dc0fc34bec568 Mon Sep 17 00:00:00 2001 From: Arti Sood Date: Tue, 5 Aug 2025 22:21:39 -0400 Subject: [PATCH 208/278] Add config file for coderabbit AI bot Signed-off-by: Arti Sood --- .coderabbit.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coderabbit.yml diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 0000000000..1296d048ec --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1,2 @@ +paths_ignore: + - "**/vendor/**" From b1864a4446fdb4938ce4a2c6de597ba7b0a5307f Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Tue, 29 Jul 2025 18:56:00 +0200 Subject: [PATCH 209/278] [kind] Use control-plane node IP instead of DNS name. During our tests we add secondary networks to the cluster and restart ovn-k pods (e.g. on egressIP tests), which means when the pod restarts, it can get a secondary IP for the api-server connection, which is deleted at the end of the test and causes http2: client timeout, which then causes very unobvious test failures. Signed-off-by: Nadia Pinaeva --- contrib/kind-common | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/contrib/kind-common b/contrib/kind-common index 2a564dece0..bf9bc58bc7 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -63,12 +63,20 @@ command_exists() { detect_apiserver_url() { # Detect API_URL used for in-cluster communication # - # Despite OVN run in pod they will only obtain the VIRTUAL apiserver address - # and since OVN has to provide the connectivity to service - # it can not be bootstrapped - # - # This is the address of the node with the control-plane - API_URL=$(kind get kubeconfig --internal --name "${KIND_CLUSTER_NAME}" | grep server | awk '{ print $2 }') + # This will return apiserver address in format https://: + DNS_NAME_URL=$(kind get kubeconfig --internal --name "${KIND_CLUSTER_NAME}" | grep server | awk '{ print $2 }') + # cut https:// from the URL + CP_NODE=${DNS_NAME_URL#*//} + # cut port from the URL + CP_NODE=${CP_NODE%:*} + # find node IP address in the kind network + if [ "$PLATFORM_IPV4_SUPPORT" == false ] && [ "$PLATFORM_IPV6_SUPPORT" == true ]; then + NODE_IP="[$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.kind.GlobalIPv6Address}}' $CP_NODE)]" + else + NODE_IP=$($OCI_BIN inspect -f '{{.NetworkSettings.Networks.kind.IPAddress}}' "$CP_NODE") + fi + # replace node name with node IP address + API_URL=${DNS_NAME_URL/$CP_NODE/$NODE_IP} } docker_disable_ipv6() { From afad0c8ba6a4fd759f29437224fd263048846501 Mon Sep 17 00:00:00 2001 From: Arti Sood Date: Fri, 25 Jul 2025 09:44:44 -0400 Subject: [PATCH 210/278] K8s rebase 1.33.3 > go-controller go get k8s.io/api@v0.33.3 go get k8s.io/client-go@v0.33.3 go get k8s.io/component-helpers@v0.33.3 go get k8s.io/kubernetes@v1.33.3 go get k8s.io/apiextensions-apiserver@v0.33.3 go get sigs.k8s.io/controller-runtime@v0.21.0 go get k8s.io/controller-manager@v0.33.3 go mod tidy go mod vendor make lint Fix typecheck errors by removing "k8s.io/utils/exec".Interface arg to function call NewController() in the files below pkg/node/iptables/iptables_manager.go pkg/node/controllers/egressip/egressip_test.go Fix too many arguments in call to iptables.New in pkg/node/iptables/iptables_manager.go Fix too many arguments in call to utiliptables.New in pkg/node/controllers/egressip/egressip_test.go Update golangci-lint version to 1.68.4 in test.yaml and lint.sh Fix staticcheck error in cmd/ovnkube-trace/ovnkube-trace.go to use endpointslice API instead of endpoints cmd/ovnkube-trace/ovnkube-trace.go:383:98: SA1019: corev1.EndpointSubset is deprecated: This API is deprecated in v1.33+. (staticcheck) Endpoint port processing feedback incorporation (Peri) func extractSubsetInfo(coreclient *corev1client.CoreV1Client, restconfig *rest.Config, subsets []corev1.EndpointSubset, svcInfo *SvcInfo, ovnNamespace, addressFamily string) error { Fix error message string in /go-controller/pkg/util/multi_network_test.go for test failure fmt.Errorf("error parsing Network Attachment Definition ns1/nad1: json: cannot unmarshal string into Go struct field NetConf.NetConf.ipam of type types.IPAM") > KIND K8s version for KIND to v1.33.1 ( v1.33.3 image is not yet available) Fix k8s version for KIND in .github/workflows/test.xml, contrib/kind-common, test/scripts/upgrade-ovn.sh, test/scripts/install-kind.sh, docs/ci/ci.md and docs/installation/launching-ovn-kubernetes-on-kind.md Co-Authored-By: Periyasamy Palanisamy Signed-off-by: Arti Sood --- .github/workflows/test.yml | 4 +- contrib/kind-common | 2 +- docs/ci/ci.md | 2 +- .../launching-ovn-kubernetes-on-kind.md | 6 +- .../cmd/ovnkube-trace/ovnkube-trace.go | 73 +- go-controller/go.mod | 52 +- go-controller/go.sum | 101 +- go-controller/hack/lint.sh | 2 +- .../controllers/egressip/egressip_test.go | 5 +- .../pkg/node/iptables/iptables_manager.go | 4 +- go-controller/pkg/util/multi_network_test.go | 2 +- .../vendor/github.com/blang/semver/v4/LICENSE | 22 + .../vendor/github.com/blang/semver/v4/json.go | 23 + .../github.com/blang/semver/v4/range.go | 416 + .../github.com/blang/semver/v4/semver.go | 476 + .../vendor/github.com/blang/semver/v4/sort.go | 28 + .../vendor/github.com/blang/semver/v4/sql.go | 30 + .../vendor/github.com/golang/protobuf/AUTHORS | 3 - .../github.com/golang/protobuf/CONTRIBUTORS | 3 - .../golang/protobuf/proto/buffer.go | 324 - .../golang/protobuf/proto/defaults.go | 63 - .../golang/protobuf/proto/deprecated.go | 113 - .../golang/protobuf/proto/discard.go | 58 - .../golang/protobuf/proto/extensions.go | 356 - .../golang/protobuf/proto/properties.go | 306 - .../github.com/golang/protobuf/proto/proto.go | 167 - .../golang/protobuf/proto/registry.go | 317 - .../golang/protobuf/proto/text_decode.go | 801 -- .../golang/protobuf/proto/text_encode.go | 560 - .../github.com/golang/protobuf/proto/wire.go | 78 - .../golang/protobuf/proto/wrappers.go | 34 - .../github.com/golang/protobuf/ptypes/any.go | 180 - .../golang/protobuf/ptypes/any/any.pb.go | 62 - .../github.com/golang/protobuf/ptypes/doc.go | 10 - .../golang/protobuf/ptypes/duration.go | 76 - .../protobuf/ptypes/duration/duration.pb.go | 63 - .../golang/protobuf/ptypes/timestamp.go | 112 - .../protobuf/ptypes/timestamp/timestamp.pb.go | 64 - .../gnostic-models/compiler/extensions.go | 8 +- .../gnostic-models/extensions/extension.pb.go | 96 +- .../gnostic-models/extensions/extensions.go | 6 +- .../gnostic-models/openapiv2/OpenAPIv2.pb.go | 1349 +- .../gnostic-models/openapiv3/OpenAPIv3.pb.go | 1763 +-- .../openapiv3/annotations.pb.go | 182 + .../openapiv3/annotations.proto | 56 + .../google/go-cmp/cmp/cmpopts/sort.go | 64 +- .../go-cmp/cmp/internal/function/func.go | 7 + .../github.com/google/go-cmp/cmp/options.go | 10 +- .../github.com/google/gofuzz/.travis.yml | 10 - .../github.com/google/gofuzz/CONTRIBUTING.md | 67 - .../vendor/github.com/google/gofuzz/fuzz.go | 605 - .../github.com/gorilla/websocket/README.md | 17 +- .../github.com/gorilla/websocket/client.go | 245 +- .../gorilla/websocket/compression.go | 6 +- .../github.com/gorilla/websocket/conn.go | 112 +- .../github.com/gorilla/websocket/proxy.go | 53 +- .../github.com/gorilla/websocket/server.go | 122 +- .../gorilla/websocket/tls_handshake.go | 21 - .../gorilla/websocket/tls_handshake_116.go | 21 - .../github.com/gorilla/websocket/util.go | 15 + .../gorilla/websocket/x_net_proxy.go | 473 - .../prometheus/client_golang/NOTICE | 5 - .../internal/github.com/golang/gddo}/LICENSE | 9 +- .../golang/gddo/httputil/header/header.go | 145 + .../golang/gddo/httputil/negotiate.go | 36 + .../client_golang/prometheus/collectorfunc.go | 30 + .../collectors/go_collector_latest.go | 4 +- .../client_golang/prometheus/desc.go | 15 +- .../client_golang/prometheus/go_collector.go | 55 +- .../prometheus/go_collector_latest.go | 19 +- .../client_golang/prometheus/histogram.go | 517 +- .../prometheus/internal/difflib.go | 19 +- .../internal/go_collector_options.go | 2 + .../prometheus/internal/go_runtime_metrics.go | 3 +- .../client_golang/prometheus/metric.go | 26 +- .../prometheus/process_collector.go | 56 +- .../prometheus/process_collector_darwin.go | 130 + .../process_collector_mem_cgo_darwin.c | 84 + .../process_collector_mem_cgo_darwin.go | 51 + .../process_collector_mem_nocgo_darwin.go | 39 + ....go => process_collector_not_supported.go} | 17 +- ....go => process_collector_procfsenabled.go} | 34 +- .../prometheus/process_collector_windows.go | 21 +- .../prometheus/promhttp/delegator.go | 6 + .../client_golang/prometheus/promhttp/http.go | 138 +- .../internal/compression.go} | 19 +- .../client_golang/prometheus/registry.go | 17 +- .../client_golang/prometheus/summary.go | 49 +- .../client_golang/prometheus/vec.go | 2 +- .../prometheus/common/expfmt/decode.go | 14 +- .../prometheus/common/expfmt/encode.go | 28 +- .../prometheus/common/expfmt/expfmt.go | 78 +- .../common/expfmt/openmetrics_create.go | 10 +- .../prometheus/common/expfmt/text_create.go | 4 +- .../prometheus/common/expfmt/text_parse.go | 164 +- .../prometheus/common/model/alert.go | 7 +- .../prometheus/common/model/labels.go | 27 +- .../common/model/labelset_string.go | 2 - .../common/model/labelset_string_go120.go | 39 - .../prometheus/common/model/metric.go | 78 +- .../prometheus/common/model/silence.go | 17 +- .../prometheus/common/model/value_float.go | 3 +- .../common/model/value_histogram.go | 7 +- .../testify/assert/assertion_compare.go | 35 +- .../testify/assert/assertion_format.go | 34 +- .../testify/assert/assertion_forward.go | 68 +- .../testify/assert/assertion_order.go | 10 +- .../stretchr/testify/assert/assertions.go | 157 +- .../testify/assert/yaml/yaml_custom.go | 25 + .../testify/assert/yaml/yaml_default.go | 37 + .../stretchr/testify/assert/yaml/yaml_fail.go | 18 + .../github.com/stretchr/testify/mock/mock.go | 155 +- .../stretchr/testify/require/require.go | 432 +- .../stretchr/testify/require/require.go.tmpl | 2 +- .../testify/require/require_forward.go | 68 +- .../stretchr/testify/require/requirements.go | 2 +- .../vendor/go.opentelemetry.io/otel/LICENSE | 201 + .../otel/attribute/README.md | 3 + .../go.opentelemetry.io/otel/attribute/doc.go | 5 + .../otel/attribute/encoder.go | 135 + .../otel/attribute/filter.go | 49 + .../otel/attribute/iterator.go | 150 + .../go.opentelemetry.io/otel/attribute/key.go | 123 + .../go.opentelemetry.io/otel/attribute/kv.go | 75 + .../go.opentelemetry.io/otel/attribute/set.go | 411 + .../otel/attribute/type_string.go | 31 + .../otel/attribute/value.go | 271 + .../go.opentelemetry.io/otel/codes/README.md | 3 + .../go.opentelemetry.io/otel/codes/codes.go | 106 + .../go.opentelemetry.io/otel/codes/doc.go | 10 + .../otel/internal/attribute/attribute.go | 96 + .../go.opentelemetry.io/otel/internal/gen.go | 18 + .../otel/internal/rawhelpers.go | 48 + .../go.opentelemetry.io/otel/trace/LICENSE | 201 + .../go.opentelemetry.io/otel/trace/README.md | 3 + .../go.opentelemetry.io/otel/trace/config.go | 323 + .../go.opentelemetry.io/otel/trace/context.go | 50 + .../go.opentelemetry.io/otel/trace/doc.go | 119 + .../otel/trace/embedded/README.md | 3 + .../otel/trace/embedded/embedded.go | 45 + .../otel/trace/nonrecording.go | 16 + .../go.opentelemetry.io/otel/trace/noop.go | 85 + .../otel/trace/provider.go | 59 + .../go.opentelemetry.io/otel/trace/span.go | 177 + .../go.opentelemetry.io/otel/trace/trace.go | 323 + .../go.opentelemetry.io/otel/trace/tracer.go | 37 + .../otel/trace/tracestate.go | 330 + .../google.golang.org/grpc/CONTRIBUTING.md | 16 +- .../google.golang.org/grpc/MAINTAINERS.md | 33 +- .../vendor/google.golang.org/grpc/SECURITY.md | 2 +- .../google.golang.org/grpc/backoff/backoff.go | 2 +- .../grpc/balancer/balancer.go | 36 +- .../grpc/balancer/base/balancer.go | 6 +- .../balancer/pickfirst/internal/internal.go | 24 + .../grpc/balancer/pickfirst/pickfirst.go | 18 +- .../pickfirst/pickfirstleaf/pickfirstleaf.go | 625 + .../grpc/balancer_wrapper.go | 78 +- .../grpc_binarylog_v1/binarylog.pb.go | 24 +- .../google.golang.org/grpc/clientconn.go | 134 +- .../vendor/google.golang.org/grpc/codec.go | 69 +- .../grpc/credentials/insecure/insecure.go | 2 +- .../google.golang.org/grpc/credentials/tls.go | 29 +- .../tls/certprovider/pemfile/builder.go | 1 + .../credentials/tls/certprovider/store.go | 2 +- .../google.golang.org/grpc/dialoptions.go | 31 +- .../vendor/google.golang.org/grpc/doc.go | 2 +- .../grpc/encoding/encoding.go | 5 +- .../grpc/encoding/encoding_v2.go | 81 + .../grpc/encoding/proto/proto.go | 44 +- .../grpc/experimental/stats/metricregistry.go | 269 + .../grpc/experimental/stats/metrics.go | 114 + .../grpc/grpclog/component.go | 10 +- .../google.golang.org/grpc/grpclog/grpclog.go | 104 +- .../grpc/grpclog/internal/grpclog.go | 26 + .../grpc/grpclog/internal/logger.go | 87 + .../internal/loggerv2.go} | 178 +- .../google.golang.org/grpc/grpclog/logger.go | 59 +- .../grpc/grpclog/loggerv2.go | 181 +- .../balancer/gracefulswitch/config.go | 2 + .../grpc/internal/binarylog/method_logger.go | 2 +- .../grpc/internal/channelz/channel.go | 15 + .../grpc/internal/channelz/channelmap.go | 9 +- .../grpc/internal/channelz/funcs.go | 2 +- .../grpc/internal/channelz/server.go | 2 + .../grpc/internal/channelz/socket.go | 7 + .../grpc/internal/channelz/subchannel.go | 2 + .../internal/channelz/syscall_nonlinux.go | 4 +- .../grpc/internal/channelz/trace.go | 19 +- .../grpc/internal/envconfig/envconfig.go | 11 +- .../grpc/internal/experimental.go | 8 +- .../{prefixLogger.go => prefix_logger.go} | 40 +- .../internal/grpcsync/callback_serializer.go | 24 +- .../grpc/internal/grpcsync/pubsub.go | 4 +- .../grpc/internal/grpcutil/method.go | 2 +- .../grpc/internal/idle/idle.go | 4 +- .../grpc/internal/internal.go | 30 +- .../internal/resolver/dns/dns_resolver.go | 6 +- .../resolver/passthrough/passthrough.go | 2 +- .../grpc/internal/stats/labels.go | 42 + .../internal/stats/metrics_recorder_list.go | 105 + .../grpc/internal/status/status.go | 39 +- .../grpc/internal/syscall/syscall_nonlinux.go | 6 +- .../grpc/internal/tcp_keepalive_unix.go | 2 +- .../grpc/internal/tcp_keepalive_windows.go | 2 +- .../grpc/internal/transport/controlbuf.go | 256 +- .../grpc/internal/transport/handler_server.go | 47 +- .../grpc/internal/transport/http2_client.go | 135 +- .../grpc/internal/transport/http2_server.go | 49 +- .../grpc/internal/transport/http_util.go | 24 +- .../grpc/internal/transport/proxy.go | 10 +- .../grpc/internal/transport/transport.go | 249 +- .../grpc/keepalive/keepalive.go | 20 +- .../google.golang.org/grpc/mem/buffer_pool.go | 194 + .../grpc/mem/buffer_slice.go | 226 + .../google.golang.org/grpc/mem/buffers.go | 268 + .../grpc/metadata/metadata.go | 7 +- .../google.golang.org/grpc/preloader.go | 28 +- .../google.golang.org/grpc/regenerate.sh | 123 - .../grpc/resolver_wrapper.go | 9 +- .../vendor/google.golang.org/grpc/rpc_util.go | 330 +- .../vendor/google.golang.org/grpc/server.go | 99 +- .../grpc/shared_buffer_pool.go | 154 - .../google.golang.org/grpc/stats/stats.go | 6 - .../vendor/google.golang.org/grpc/stream.go | 213 +- .../grpc/stream_interfaces.go | 86 + .../vendor/google.golang.org/grpc/version.go | 2 +- .../protobuf/encoding/protojson/decode.go | 5 - .../encoding/protojson/well_known_types.go | 6 +- .../protobuf/encoding/prototext/decode.go | 5 - .../editiondefaults/editions_defaults.binpb | Bin 93 -> 138 bytes .../internal/editionssupport/editions.go | 13 - .../protobuf/internal/encoding/tag/tag.go | 8 +- .../protobuf/internal/errors/is_go112.go | 40 - .../protobuf/internal/errors/is_go113.go | 13 - .../protobuf/internal/filedesc/desc.go | 27 +- .../protobuf/internal/filedesc/desc_lazy.go | 9 - .../protobuf/internal/filedesc/editions.go | 8 + .../protobuf/internal/filetype/build.go | 2 +- .../protobuf/internal/flags/flags.go | 2 +- .../internal/genid/go_features_gen.go | 34 + .../protobuf/internal/genid/goname.go | 5 - .../protobuf/internal/genid/name.go | 12 + .../internal/impl/api_export_opaque.go | 128 + .../protobuf/internal/impl/bitmap.go | 34 + .../protobuf/internal/impl/bitmap_race.go | 126 + .../protobuf/internal/impl/checkinit.go | 33 + .../protobuf/internal/impl/codec_field.go | 75 - .../internal/impl/codec_field_opaque.go | 264 + .../protobuf/internal/impl/codec_map.go | 14 +- .../protobuf/internal/impl/codec_map_go111.go | 38 - .../protobuf/internal/impl/codec_map_go112.go | 12 - .../protobuf/internal/impl/codec_message.go | 20 +- .../internal/impl/codec_message_opaque.go | 153 + .../protobuf/internal/impl/convert_map.go | 2 +- .../protobuf/internal/impl/decode.go | 56 +- .../protobuf/internal/impl/encode.go | 78 + .../protobuf/internal/impl/lazy.go | 433 + .../protobuf/internal/impl/legacy_message.go | 5 +- .../protobuf/internal/impl/merge.go | 27 + .../protobuf/internal/impl/message.go | 31 +- .../protobuf/internal/impl/message_opaque.go | 627 + .../internal/impl/message_opaque_gen.go | 132 + .../protobuf/internal/impl/message_reflect.go | 10 +- .../internal/impl/message_reflect_field.go | 160 +- .../impl/message_reflect_field_gen.go | 273 + .../protobuf/internal/impl/pointer_unsafe.go | 12 +- .../internal/impl/pointer_unsafe_opaque.go | 42 + .../protobuf/internal/impl/presence.go | 142 + .../protobuf/internal/impl/validate.go | 40 +- .../protobuf/internal/impl/weak.go | 74 - .../internal/protolazy/bufferreader.go | 364 + .../protobuf/internal/protolazy/lazy.go | 359 + .../internal/protolazy/pointer_unsafe.go | 17 + .../protobuf/internal/version/version.go | 4 +- .../protobuf/proto/decode.go | 21 +- .../protobuf/proto/encode.go | 3 +- .../google.golang.org/protobuf/proto/size.go | 8 + .../protobuf/proto/wrapperopaque.go | 80 + .../protobuf/reflect/protodesc/desc.go | 286 - .../protobuf/reflect/protodesc/desc_init.go | 289 - .../reflect/protodesc/desc_resolve.go | 291 - .../reflect/protodesc/desc_validate.go | 371 - .../protobuf/reflect/protodesc/editions.go | 145 - .../protobuf/reflect/protodesc/proto.go | 274 - .../protobuf/reflect/protoreflect/type.go | 12 +- .../protobuf/reflect/protoreflect/value.go | 2 +- .../protobuf/runtime/protoiface/methods.go | 16 + .../protobuf/runtime/protoimpl/impl.go | 4 + .../types/descriptorpb/descriptor.pb.go | 1785 ++- .../types/gofeaturespb/go_features.pb.go | 165 - .../protobuf/types/known/anypb/any.pb.go | 21 +- .../types/known/durationpb/duration.pb.go | 21 +- .../types/known/timestamppb/timestamp.pb.go | 21 +- .../vendor/k8s.io/api/admission/v1/doc.go | 2 +- .../k8s.io/api/admission/v1beta1/doc.go | 2 +- .../api/admissionregistration/v1/doc.go | 2 +- .../api/admissionregistration/v1alpha1/doc.go | 2 +- .../v1alpha1/generated.proto | 13 +- .../admissionregistration/v1alpha1/types.go | 23 +- .../v1alpha1/types_swagger_doc_generated.go | 8 +- .../api/admissionregistration/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/apidiscovery/v2/doc.go | 2 +- .../k8s.io/api/apidiscovery/v2beta1/doc.go | 2 +- .../api/apiserverinternal/v1alpha1/doc.go | 2 +- .../vendor/k8s.io/api/apps/v1/doc.go | 2 +- .../vendor/k8s.io/api/apps/v1/generated.pb.go | 336 +- .../vendor/k8s.io/api/apps/v1/generated.proto | 41 +- .../vendor/k8s.io/api/apps/v1/types.go | 41 +- .../apps/v1/types_swagger_doc_generated.go | 24 +- .../api/apps/v1/zz_generated.deepcopy.go | 10 + .../vendor/k8s.io/api/apps/v1beta1/doc.go | 2 +- .../k8s.io/api/apps/v1beta1/generated.pb.go | 286 +- .../k8s.io/api/apps/v1beta1/generated.proto | 22 +- .../vendor/k8s.io/api/apps/v1beta1/types.go | 22 +- .../v1beta1/types_swagger_doc_generated.go | 15 +- .../api/apps/v1beta1/zz_generated.deepcopy.go | 5 + .../vendor/k8s.io/api/apps/v1beta2/doc.go | 2 +- .../k8s.io/api/apps/v1beta2/generated.pb.go | 352 +- .../k8s.io/api/apps/v1beta2/generated.proto | 41 +- .../vendor/k8s.io/api/apps/v1beta2/types.go | 41 +- .../v1beta2/types_swagger_doc_generated.go | 24 +- .../api/apps/v1beta2/zz_generated.deepcopy.go | 10 + .../k8s.io/api/authentication/v1/doc.go | 2 +- .../k8s.io/api/authentication/v1alpha1/doc.go | 2 +- .../k8s.io/api/authentication/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/authorization/v1/doc.go | 2 +- .../k8s.io/api/authorization/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/autoscaling/v1/doc.go | 2 +- .../vendor/k8s.io/api/autoscaling/v2/doc.go | 2 +- .../k8s.io/api/autoscaling/v2/generated.pb.go | 272 +- .../k8s.io/api/autoscaling/v2/generated.proto | 30 +- .../vendor/k8s.io/api/autoscaling/v2/types.go | 30 +- .../v2/types_swagger_doc_generated.go | 5 +- .../autoscaling/v2/zz_generated.deepcopy.go | 5 + .../k8s.io/api/autoscaling/v2beta1/doc.go | 2 +- .../k8s.io/api/autoscaling/v2beta2/doc.go | 2 +- .../vendor/k8s.io/api/batch/v1/doc.go | 2 +- .../k8s.io/api/batch/v1/generated.proto | 10 - .../vendor/k8s.io/api/batch/v1/types.go | 15 - .../batch/v1/types_swagger_doc_generated.go | 10 +- .../vendor/k8s.io/api/batch/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/certificates/v1/doc.go | 2 +- .../k8s.io/api/certificates/v1alpha1/doc.go | 2 +- .../k8s.io/api/certificates/v1beta1/doc.go | 2 +- .../api/certificates/v1beta1/generated.pb.go | 761 +- .../api/certificates/v1beta1/generated.proto | 73 + .../api/certificates/v1beta1/register.go | 2 + .../k8s.io/api/certificates/v1beta1/types.go | 85 + .../v1beta1/types_swagger_doc_generated.go | 30 + .../v1beta1/zz_generated.deepcopy.go | 76 + .../zz_generated.prerelease-lifecycle.go | 36 + .../vendor/k8s.io/api/coordination/v1/doc.go | 2 +- .../k8s.io/api/coordination/v1alpha2/doc.go | 2 +- .../api/coordination/v1alpha2/generated.proto | 2 - .../k8s.io/api/coordination/v1alpha2/types.go | 2 - .../v1alpha2/types_swagger_doc_generated.go | 2 +- .../k8s.io/api/coordination/v1beta1/doc.go | 2 +- .../api/coordination/v1beta1/generated.pb.go | 915 +- .../api/coordination/v1beta1/generated.proto | 69 + .../api/coordination/v1beta1/register.go | 2 + .../k8s.io/api/coordination/v1beta1/types.go | 73 + .../v1beta1/types_swagger_doc_generated.go | 34 + .../v1beta1/zz_generated.deepcopy.go | 84 + .../zz_generated.prerelease-lifecycle.go | 36 + .../vendor/k8s.io/api/core/v1/doc.go | 2 +- .../vendor/k8s.io/api/core/v1/generated.pb.go | 2767 ++-- .../vendor/k8s.io/api/core/v1/generated.proto | 77 +- .../vendor/k8s.io/api/core/v1/lifecycle.go | 24 + .../vendor/k8s.io/api/core/v1/types.go | 201 +- .../core/v1/types_swagger_doc_generated.go | 50 +- .../api/core/v1/zz_generated.deepcopy.go | 38 +- .../vendor/k8s.io/api/discovery/v1/doc.go | 2 +- .../k8s.io/api/discovery/v1/generated.pb.go | 336 +- .../k8s.io/api/discovery/v1/generated.proto | 78 +- .../vendor/k8s.io/api/discovery/v1/types.go | 78 +- .../v1/types_swagger_doc_generated.go | 28 +- .../api/discovery/v1/zz_generated.deepcopy.go | 21 + .../k8s.io/api/discovery/v1beta1/doc.go | 2 +- .../api/discovery/v1beta1/generated.pb.go | 329 +- .../api/discovery/v1beta1/generated.proto | 13 + .../k8s.io/api/discovery/v1beta1/types.go | 13 + .../v1beta1/types_swagger_doc_generated.go | 10 + .../v1beta1/zz_generated.deepcopy.go | 21 + .../vendor/k8s.io/api/events/v1/doc.go | 2 +- .../vendor/k8s.io/api/events/v1beta1/doc.go | 2 +- .../k8s.io/api/extensions/v1beta1/doc.go | 2 +- .../api/extensions/v1beta1/generated.pb.go | 418 +- .../api/extensions/v1beta1/generated.proto | 40 +- .../k8s.io/api/extensions/v1beta1/types.go | 40 +- .../v1beta1/types_swagger_doc_generated.go | 24 +- .../v1beta1/zz_generated.deepcopy.go | 10 + .../vendor/k8s.io/api/flowcontrol/v1/doc.go | 2 +- .../k8s.io/api/flowcontrol/v1beta1/doc.go | 2 +- .../k8s.io/api/flowcontrol/v1beta2/doc.go | 2 +- .../k8s.io/api/flowcontrol/v1beta3/doc.go | 2 +- .../k8s.io/api/imagepolicy/v1alpha1/doc.go | 2 +- .../vendor/k8s.io/api/networking/v1/doc.go | 2 +- .../k8s.io/api/networking/v1/generated.pb.go | 3729 ++++-- .../k8s.io/api/networking/v1/generated.proto | 109 + .../k8s.io/api/networking/v1/register.go | 4 + .../vendor/k8s.io/api/networking/v1/types.go | 130 + .../v1/types_swagger_doc_generated.go | 80 + .../api/networking/v1/well_known_labels.go | 33 + .../networking/v1/zz_generated.deepcopy.go | 202 + .../v1/zz_generated.prerelease-lifecycle.go | 24 + .../k8s.io/api/networking/v1alpha1/doc.go | 2 +- .../k8s.io/api/networking/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/node/v1/doc.go | 2 +- .../vendor/k8s.io/api/node/v1alpha1/doc.go | 2 +- .../vendor/k8s.io/api/node/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/policy/v1/doc.go | 2 +- .../k8s.io/api/policy/v1/generated.proto | 3 - .../vendor/k8s.io/api/policy/v1/types.go | 3 - .../policy/v1/types_swagger_doc_generated.go | 2 +- .../vendor/k8s.io/api/policy/v1beta1/doc.go | 2 +- .../k8s.io/api/policy/v1beta1/generated.proto | 3 - .../vendor/k8s.io/api/policy/v1beta1/types.go | 3 - .../v1beta1/types_swagger_doc_generated.go | 2 +- .../vendor/k8s.io/api/rbac/v1/doc.go | 2 +- .../vendor/k8s.io/api/rbac/v1alpha1/doc.go | 2 +- .../vendor/k8s.io/api/rbac/v1beta1/doc.go | 2 +- .../k8s.io/api/resource/v1alpha3/doc.go | 2 +- .../api/resource/v1alpha3/generated.pb.go | 6403 ++++++--- .../api/resource/v1alpha3/generated.proto | 514 +- .../k8s.io/api/resource/v1alpha3/register.go | 2 + .../k8s.io/api/resource/v1alpha3/types.go | 609 +- .../v1alpha3/types_swagger_doc_generated.go | 165 +- .../v1alpha3/zz_generated.deepcopy.go | 327 +- .../zz_generated.prerelease-lifecycle.go | 36 + .../api/resource/v1beta1/devicetaint.go | 35 + .../vendor/k8s.io/api/resource/v1beta1/doc.go | 2 +- .../api/resource/v1beta1/generated.pb.go | 3890 ++++-- .../api/resource/v1beta1/generated.proto | 428 +- .../k8s.io/api/resource/v1beta1/types.go | 506 +- .../v1beta1/types_swagger_doc_generated.go | 124 +- .../resource/v1beta1/zz_generated.deepcopy.go | 202 +- .../api/resource/v1beta2/devicetaint.go | 35 + .../vendor/k8s.io/api/resource/v1beta2/doc.go | 24 + .../api/resource/v1beta2/generated.pb.go | 11047 ++++++++++++++++ .../api/resource/v1beta2/generated.proto | 1278 ++ .../k8s.io/api/resource/v1beta2/register.go | 60 + .../k8s.io/api/resource/v1beta2/types.go | 1552 +++ .../v1beta2/types_swagger_doc_generated.go | 464 + .../resource/v1beta2/zz_generated.deepcopy.go | 1092 ++ .../zz_generated.prerelease-lifecycle.go | 166 + .../vendor/k8s.io/api/scheduling/v1/doc.go | 2 +- .../k8s.io/api/scheduling/v1alpha1/doc.go | 2 +- .../k8s.io/api/scheduling/v1beta1/doc.go | 2 +- .../vendor/k8s.io/api/storage/v1/doc.go | 2 +- .../k8s.io/api/storage/v1/generated.pb.go | 271 +- .../k8s.io/api/storage/v1/generated.proto | 22 + .../vendor/k8s.io/api/storage/v1/types.go | 22 + .../storage/v1/types_swagger_doc_generated.go | 26 +- .../api/storage/v1/zz_generated.deepcopy.go | 10 + .../vendor/k8s.io/api/storage/v1alpha1/doc.go | 2 +- .../api/storage/v1alpha1/generated.pb.go | 160 +- .../api/storage/v1alpha1/generated.proto | 8 + .../k8s.io/api/storage/v1alpha1/types.go | 8 + .../v1alpha1/types_swagger_doc_generated.go | 7 +- .../storage/v1alpha1/zz_generated.deepcopy.go | 5 + .../vendor/k8s.io/api/storage/v1beta1/doc.go | 2 +- .../api/storage/v1beta1/generated.pb.go | 280 +- .../api/storage/v1beta1/generated.proto | 22 + .../k8s.io/api/storage/v1beta1/types.go | 22 + .../v1beta1/types_swagger_doc_generated.go | 26 +- .../storage/v1beta1/zz_generated.deepcopy.go | 10 + .../api/storagemigration/v1alpha1/doc.go | 2 +- .../pkg/apis/apiextensions/doc.go | 2 +- .../pkg/apis/apiextensions/v1/doc.go | 2 +- .../pkg/features/OWNERS | 4 + .../pkg/features/kube_features.go | 66 + .../k8s.io/apimachinery/pkg/api/errors/doc.go | 2 +- .../k8s.io/apimachinery/pkg/api/meta/doc.go | 2 +- .../k8s.io/apimachinery/pkg/api/meta/help.go | 3 + .../pkg/api/operation/operation.go | 56 + .../apimachinery/pkg/api/validation/doc.go | 2 +- .../pkg/api/validation/generic.go | 2 +- .../pkg/apis/meta/internalversion/doc.go | 2 +- .../apis/meta/internalversion/scheme/doc.go | 2 +- .../pkg/apis/meta/internalversion/types.go | 2 - .../apimachinery/pkg/apis/meta/v1/doc.go | 2 +- .../pkg/apis/meta/v1/micro_time_fuzz.go | 13 +- .../pkg/apis/meta/v1/time_fuzz.go | 13 +- .../pkg/apis/meta/v1/unstructured/helpers.go | 31 +- .../apis/meta/v1/unstructured/unstructured.go | 4 +- .../pkg/apis/meta/v1/validation/validation.go | 2 +- .../apimachinery/pkg/apis/meta/v1beta1/doc.go | 2 +- .../k8s.io/apimachinery/pkg/conversion/doc.go | 2 +- .../pkg/conversion/queryparams/doc.go | 2 +- .../k8s.io/apimachinery/pkg/fields/doc.go | 2 +- .../k8s.io/apimachinery/pkg/labels/doc.go | 2 +- .../k8s.io/apimachinery/pkg/runtime/doc.go | 2 +- .../apimachinery/pkg/runtime/interfaces.go | 1 + .../k8s.io/apimachinery/pkg/runtime/scheme.go | 39 + .../serializer/cbor/internal/modes/custom.go | 4 +- .../pkg/runtime/serializer/codec_factory.go | 23 +- .../runtime/serializer/json/collections.go | 230 + .../pkg/runtime/serializer/json/json.go | 16 +- .../serializer/protobuf/collections.go | 174 + .../pkg/runtime/serializer/protobuf/doc.go | 2 +- .../runtime/serializer/protobuf/protobuf.go | 87 +- .../apimachinery/pkg/runtime/types_proto.go | 127 +- .../k8s.io/apimachinery/pkg/types/doc.go | 2 +- .../k8s.io/apimachinery/pkg/util/diff/diff.go | 2 +- .../apimachinery/pkg/util/errors/doc.go | 2 +- .../apimachinery/pkg/util/framer/framer.go | 6 +- .../apimachinery/pkg/util/httpstream/doc.go | 2 +- .../pkg/util/httpstream/wsstream/doc.go | 2 +- .../pkg/util/intstr/instr_fuzz.go | 14 +- .../k8s.io/apimachinery/pkg/util/proxy/doc.go | 2 +- .../apimachinery/pkg/util/runtime/runtime.go | 46 +- .../k8s.io/apimachinery/pkg/util/sets/doc.go | 2 +- .../util/validation/field/error_matcher.go | 212 + .../pkg/util/validation/field/errors.go | 132 +- .../apimachinery/pkg/util/validation/ip.go | 278 + .../pkg/util/validation/validation.go | 40 - .../apimachinery/pkg/util/version/doc.go | 2 +- .../apimachinery/pkg/util/version/version.go | 18 +- .../apimachinery/pkg/util/wait/backoff.go | 50 +- .../k8s.io/apimachinery/pkg/util/wait/doc.go | 2 +- .../k8s.io/apimachinery/pkg/util/wait/loop.go | 4 +- .../k8s.io/apimachinery/pkg/util/wait/wait.go | 9 +- .../apimachinery/pkg/util/yaml/decoder.go | 170 +- .../pkg/util/yaml/stream_reader.go | 130 + .../k8s.io/apimachinery/pkg/version/doc.go | 4 +- .../k8s.io/apimachinery/pkg/version/types.go | 28 +- .../k8s.io/apimachinery/pkg/watch/doc.go | 2 +- .../apimachinery/pkg/watch/streamwatcher.go | 15 +- .../k8s.io/apimachinery/pkg/watch/watch.go | 35 +- .../gofuzz => k8s.io/apiserver}/LICENSE | 0 .../k8s.io/apiserver/pkg/features/OWNERS | 4 + .../apiserver/pkg/features/kube_features.go | 421 + .../pkg/util/feature/feature_gate.go | 33 + .../client-go/applyconfigurations/OWNERS | 1 + .../apps/v1/deploymentstatus.go | 9 + .../apps/v1/replicasetstatus.go | 9 + .../apps/v1beta1/deploymentstatus.go | 9 + .../apps/v1beta2/deploymentstatus.go | 9 + .../apps/v1beta2/replicasetstatus.go | 9 + .../autoscaling/v2/hpascalingrules.go | 10 + .../v1beta1/clustertrustbundle.go | 253 + .../v1beta1/clustertrustbundlespec.go | 48 + .../coordination/v1beta1/leasecandidate.go | 255 + .../v1beta1/leasecandidatespec.go | 89 + .../core/v1/containerstatus.go | 9 + .../applyconfigurations/core/v1/lifecycle.go | 17 +- .../core/v1/nodeswapstatus.go | 39 + .../core/v1/nodesysteminfo.go | 29 +- .../core/v1/podcondition.go | 9 + .../applyconfigurations/core/v1/podstatus.go | 9 + .../discovery/v1/endpointhints.go | 14 + .../discovery/v1/fornode.go | 39 + .../discovery/v1beta1/endpointhints.go | 14 + .../discovery/v1beta1/fornode.go | 39 + .../client-go/applyconfigurations/doc.go | 2 +- .../extensions/v1beta1/deploymentstatus.go | 9 + .../extensions/v1beta1/replicasetstatus.go | 9 + .../applyconfigurations/internal/internal.go | 1344 +- .../networking/v1/ipaddress.go | 253 + .../networking/v1/ipaddressspec.go | 39 + .../networking/v1/parentreference.go | 66 + .../networking/v1/servicecidr.go | 262 + .../networking/v1/servicecidrspec.go | 41 + .../networking/v1/servicecidrstatus.go | 48 + .../resource/v1alpha3/basicdevice.go | 60 +- .../resource/v1alpha3/counter.go | 43 + .../resource/v1alpha3/counterset.go | 54 + .../v1alpha3/devicecounterconsumption.go | 54 + .../resource/v1alpha3/devicerequest.go | 28 + .../v1alpha3/devicerequestallocationresult.go | 24 +- .../resource/v1alpha3/devicesubrequest.go | 98 + .../resource/v1alpha3/devicetaint.go | 71 + .../resource/v1alpha3/devicetaintrule.go | 253 + .../resource/v1alpha3/devicetaintrulespec.go | 48 + .../resource/v1alpha3/devicetaintselector.go | 80 + .../resource/v1alpha3/devicetoleration.go | 79 + .../resource/v1alpha3/resourceslicespec.go | 35 +- .../resource/v1beta1/basicdevice.go | 60 +- .../resource/v1beta1/counter.go | 43 + .../resource/v1beta1/counterset.go | 54 + .../v1beta1/devicecounterconsumption.go | 54 + .../resource/v1beta1/devicerequest.go | 28 + .../v1beta1/devicerequestallocationresult.go | 24 +- .../resource/v1beta1/devicesubrequest.go | 98 + .../resource/v1beta1/devicetaint.go | 71 + .../resource/v1beta1/devicetoleration.go | 79 + .../resource/v1beta1/resourceslicespec.go | 35 +- .../resource/v1beta2/allocateddevicestatus.go | 94 + .../resource/v1beta2/allocationresult.go | 52 + .../resource/v1beta2/celdeviceselector.go | 39 + .../resource/v1beta2/counter.go | 43 + .../resource/v1beta2/counterset.go | 54 + .../resource/v1beta2/device.go | 129 + .../v1beta2/deviceallocationconfiguration.go | 63 + .../v1beta2/deviceallocationresult.go | 58 + .../resource/v1beta2/deviceattribute.go | 66 + .../resource/v1beta2/devicecapacity.go | 43 + .../resource/v1beta2/deviceclaim.go | 72 + .../v1beta2/deviceclaimconfiguration.go | 50 + .../resource/v1beta2/deviceclass.go | 253 + .../v1beta2/deviceclassconfiguration.go | 39 + .../resource/v1beta2/deviceclassspec.go | 58 + .../resource/v1beta2/deviceconfiguration.go | 39 + .../resource/v1beta2/deviceconstraint.go | 54 + .../v1beta2/devicecounterconsumption.go | 54 + .../resource/v1beta2/devicerequest.go | 62 + .../v1beta2/devicerequestallocationresult.go | 89 + .../resource/v1beta2/deviceselector.go | 39 + .../resource/v1beta2/devicesubrequest.go | 98 + .../resource/v1beta2/devicetaint.go | 71 + .../resource/v1beta2/devicetoleration.go | 79 + .../resource/v1beta2/exactdevicerequest.go | 98 + .../resource/v1beta2/networkdevicedata.go | 59 + .../v1beta2/opaquedeviceconfiguration.go | 52 + .../resource/v1beta2/resourceclaim.go | 264 + .../v1beta2/resourceclaimconsumerreference.go | 70 + .../resource/v1beta2/resourceclaimspec.go | 39 + .../resource/v1beta2/resourceclaimstatus.go | 67 + .../resource/v1beta2/resourceclaimtemplate.go | 255 + .../v1beta2/resourceclaimtemplatespec.go | 194 + .../resource/v1beta2/resourcepool.go | 57 + .../resource/v1beta2/resourceslice.go | 253 + .../resource/v1beta2/resourceslicespec.go | 116 + .../storage/v1/csidriverspec.go | 25 +- .../storage/v1/volumeerror.go | 13 +- .../storage/v1alpha1/volumeerror.go | 13 +- .../storage/v1beta1/csidriverspec.go | 25 +- .../storage/v1beta1/volumeerror.go | 13 +- .../client-go/applyconfigurations/utils.go | 132 + .../discovery/aggregated_discovery.go | 2 +- .../client-go/discovery/discovery_client.go | 3 +- .../vendor/k8s.io/client-go/discovery/doc.go | 2 +- .../client-go/features/known_features.go | 7 + .../vendor/k8s.io/client-go/gentype/fake.go | 1 + .../v1/mutatingwebhookconfiguration.go | 16 +- .../v1/validatingadmissionpolicy.go | 16 +- .../v1/validatingadmissionpolicybinding.go | 16 +- .../v1/validatingwebhookconfiguration.go | 16 +- .../v1alpha1/mutatingadmissionpolicy.go | 16 +- .../mutatingadmissionpolicybinding.go | 16 +- .../v1alpha1/validatingadmissionpolicy.go | 16 +- .../validatingadmissionpolicybinding.go | 16 +- .../v1beta1/mutatingwebhookconfiguration.go | 16 +- .../v1beta1/validatingadmissionpolicy.go | 16 +- .../validatingadmissionpolicybinding.go | 16 +- .../v1beta1/validatingwebhookconfiguration.go | 16 +- .../v1alpha1/storageversion.go | 16 +- .../informers/apps/v1/controllerrevision.go | 16 +- .../client-go/informers/apps/v1/daemonset.go | 16 +- .../client-go/informers/apps/v1/deployment.go | 16 +- .../client-go/informers/apps/v1/replicaset.go | 16 +- .../informers/apps/v1/statefulset.go | 16 +- .../apps/v1beta1/controllerrevision.go | 16 +- .../informers/apps/v1beta1/deployment.go | 16 +- .../informers/apps/v1beta1/statefulset.go | 16 +- .../apps/v1beta2/controllerrevision.go | 16 +- .../informers/apps/v1beta2/daemonset.go | 16 +- .../informers/apps/v1beta2/deployment.go | 16 +- .../informers/apps/v1beta2/replicaset.go | 16 +- .../informers/apps/v1beta2/statefulset.go | 16 +- .../autoscaling/v1/horizontalpodautoscaler.go | 16 +- .../autoscaling/v2/horizontalpodautoscaler.go | 16 +- .../v2beta1/horizontalpodautoscaler.go | 16 +- .../v2beta2/horizontalpodautoscaler.go | 16 +- .../client-go/informers/batch/v1/cronjob.go | 16 +- .../client-go/informers/batch/v1/job.go | 16 +- .../informers/batch/v1beta1/cronjob.go | 16 +- .../v1/certificatesigningrequest.go | 16 +- .../v1alpha1/clustertrustbundle.go | 16 +- .../v1beta1/certificatesigningrequest.go | 16 +- .../v1beta1/clustertrustbundle.go | 101 + .../certificates/v1beta1/interface.go | 7 + .../informers/coordination/v1/lease.go | 16 +- .../coordination/v1alpha2/leasecandidate.go | 16 +- .../coordination/v1beta1/interface.go | 7 + .../informers/coordination/v1beta1/lease.go | 16 +- .../coordination/v1beta1/leasecandidate.go | 102 + .../informers/core/v1/componentstatus.go | 16 +- .../client-go/informers/core/v1/configmap.go | 16 +- .../client-go/informers/core/v1/endpoints.go | 16 +- .../client-go/informers/core/v1/event.go | 16 +- .../client-go/informers/core/v1/limitrange.go | 16 +- .../client-go/informers/core/v1/namespace.go | 16 +- .../client-go/informers/core/v1/node.go | 16 +- .../informers/core/v1/persistentvolume.go | 16 +- .../core/v1/persistentvolumeclaim.go | 16 +- .../k8s.io/client-go/informers/core/v1/pod.go | 16 +- .../informers/core/v1/podtemplate.go | 16 +- .../core/v1/replicationcontroller.go | 16 +- .../informers/core/v1/resourcequota.go | 16 +- .../client-go/informers/core/v1/secret.go | 16 +- .../client-go/informers/core/v1/service.go | 16 +- .../informers/core/v1/serviceaccount.go | 16 +- .../informers/discovery/v1/endpointslice.go | 16 +- .../discovery/v1beta1/endpointslice.go | 16 +- .../vendor/k8s.io/client-go/informers/doc.go | 2 +- .../client-go/informers/events/v1/event.go | 16 +- .../informers/events/v1beta1/event.go | 16 +- .../informers/extensions/v1beta1/daemonset.go | 16 +- .../extensions/v1beta1/deployment.go | 16 +- .../informers/extensions/v1beta1/ingress.go | 16 +- .../extensions/v1beta1/networkpolicy.go | 16 +- .../extensions/v1beta1/replicaset.go | 16 +- .../informers/flowcontrol/v1/flowschema.go | 16 +- .../v1/prioritylevelconfiguration.go | 16 +- .../flowcontrol/v1beta1/flowschema.go | 16 +- .../v1beta1/prioritylevelconfiguration.go | 16 +- .../flowcontrol/v1beta2/flowschema.go | 16 +- .../v1beta2/prioritylevelconfiguration.go | 16 +- .../flowcontrol/v1beta3/flowschema.go | 16 +- .../v1beta3/prioritylevelconfiguration.go | 16 +- .../k8s.io/client-go/informers/generic.go | 21 + .../informers/networking/v1/ingress.go | 16 +- .../informers/networking/v1/ingressclass.go | 16 +- .../informers/networking/v1/interface.go | 14 + .../informers/networking/v1/ipaddress.go | 101 + .../informers/networking/v1/networkpolicy.go | 16 +- .../informers/networking/v1/servicecidr.go | 101 + .../networking/v1alpha1/ipaddress.go | 16 +- .../networking/v1alpha1/servicecidr.go | 16 +- .../informers/networking/v1beta1/ingress.go | 16 +- .../networking/v1beta1/ingressclass.go | 16 +- .../informers/networking/v1beta1/ipaddress.go | 16 +- .../networking/v1beta1/servicecidr.go | 16 +- .../informers/node/v1/runtimeclass.go | 16 +- .../informers/node/v1alpha1/runtimeclass.go | 16 +- .../informers/node/v1beta1/runtimeclass.go | 16 +- .../policy/v1/poddisruptionbudget.go | 16 +- .../policy/v1beta1/poddisruptionbudget.go | 16 +- .../informers/rbac/v1/clusterrole.go | 16 +- .../informers/rbac/v1/clusterrolebinding.go | 16 +- .../client-go/informers/rbac/v1/role.go | 16 +- .../informers/rbac/v1/rolebinding.go | 16 +- .../informers/rbac/v1alpha1/clusterrole.go | 16 +- .../rbac/v1alpha1/clusterrolebinding.go | 16 +- .../client-go/informers/rbac/v1alpha1/role.go | 16 +- .../informers/rbac/v1alpha1/rolebinding.go | 16 +- .../informers/rbac/v1beta1/clusterrole.go | 16 +- .../rbac/v1beta1/clusterrolebinding.go | 16 +- .../client-go/informers/rbac/v1beta1/role.go | 16 +- .../informers/rbac/v1beta1/rolebinding.go | 16 +- .../client-go/informers/resource/interface.go | 8 + .../resource/v1alpha3/deviceclass.go | 16 +- .../resource/v1alpha3/devicetaintrule.go | 101 + .../informers/resource/v1alpha3/interface.go | 7 + .../resource/v1alpha3/resourceclaim.go | 16 +- .../v1alpha3/resourceclaimtemplate.go | 16 +- .../resource/v1alpha3/resourceslice.go | 16 +- .../informers/resource/v1beta1/deviceclass.go | 16 +- .../resource/v1beta1/resourceclaim.go | 16 +- .../resource/v1beta1/resourceclaimtemplate.go | 16 +- .../resource/v1beta1/resourceslice.go | 16 +- .../informers/resource/v1beta2/deviceclass.go | 101 + .../informers/resource/v1beta2/interface.go | 66 + .../resource/v1beta2/resourceclaim.go | 102 + .../resource/v1beta2/resourceclaimtemplate.go | 102 + .../resource/v1beta2/resourceslice.go | 101 + .../informers/scheduling/v1/priorityclass.go | 16 +- .../scheduling/v1alpha1/priorityclass.go | 16 +- .../scheduling/v1beta1/priorityclass.go | 16 +- .../informers/storage/v1/csidriver.go | 16 +- .../client-go/informers/storage/v1/csinode.go | 16 +- .../storage/v1/csistoragecapacity.go | 16 +- .../informers/storage/v1/storageclass.go | 16 +- .../informers/storage/v1/volumeattachment.go | 16 +- .../storage/v1alpha1/csistoragecapacity.go | 16 +- .../storage/v1alpha1/volumeattachment.go | 16 +- .../storage/v1alpha1/volumeattributesclass.go | 16 +- .../informers/storage/v1beta1/csidriver.go | 16 +- .../informers/storage/v1beta1/csinode.go | 16 +- .../storage/v1beta1/csistoragecapacity.go | 16 +- .../informers/storage/v1beta1/storageclass.go | 16 +- .../storage/v1beta1/volumeattachment.go | 16 +- .../storage/v1beta1/volumeattributesclass.go | 16 +- .../v1alpha1/storageversionmigration.go | 16 +- .../k8s.io/client-go/kubernetes/clientset.go | 13 + .../vendor/k8s.io/client-go/kubernetes/doc.go | 2 +- .../kubernetes/fake/clientset_generated.go | 20 +- .../client-go/kubernetes/fake/register.go | 2 + .../k8s.io/client-go/kubernetes/import.go | 2 +- .../client-go/kubernetes/scheme/register.go | 2 + .../v1/admissionregistration_client.go | 12 +- .../v1alpha1/admissionregistration_client.go | 12 +- .../v1beta1/admissionregistration_client.go | 12 +- .../v1alpha1/apiserverinternal_client.go | 12 +- .../kubernetes/typed/apps/v1/apps_client.go | 12 +- .../typed/apps/v1beta1/apps_client.go | 12 +- .../typed/apps/v1beta2/apps_client.go | 12 +- .../v1/authentication_client.go | 12 +- .../v1alpha1/authentication_client.go | 12 +- .../v1beta1/authentication_client.go | 12 +- .../authorization/v1/authorization_client.go | 12 +- .../v1beta1/authorization_client.go | 12 +- .../autoscaling/v1/autoscaling_client.go | 12 +- .../autoscaling/v2/autoscaling_client.go | 12 +- .../autoscaling/v2beta1/autoscaling_client.go | 12 +- .../autoscaling/v2beta2/autoscaling_client.go | 12 +- .../kubernetes/typed/batch/v1/batch_client.go | 12 +- .../typed/batch/v1beta1/batch_client.go | 12 +- .../certificates/v1/certificates_client.go | 12 +- .../v1alpha1/certificates_client.go | 12 +- .../v1beta1/certificates_client.go | 17 +- .../v1beta1/clustertrustbundle.go | 73 + .../v1beta1/fake/fake_certificates_client.go | 4 + .../v1beta1/fake/fake_clustertrustbundle.go | 53 + .../v1beta1/generated_expansion.go | 2 + .../coordination/v1/coordination_client.go | 12 +- .../v1alpha2/coordination_client.go | 12 +- .../v1beta1/coordination_client.go | 17 +- .../v1beta1/fake/fake_coordination_client.go | 4 + .../v1beta1/fake/fake_leasecandidate.go | 53 + .../v1beta1/generated_expansion.go | 2 + .../coordination/v1beta1/leasecandidate.go | 71 + .../kubernetes/typed/core/v1/core_client.go | 12 +- .../typed/core/v1/event_expansion.go | 65 +- .../core/v1/fake/fake_event_expansion.go | 29 + .../typed/discovery/v1/discovery_client.go | 12 +- .../discovery/v1beta1/discovery_client.go | 12 +- .../typed/events/v1/events_client.go | 12 +- .../typed/events/v1beta1/event_expansion.go | 6 +- .../typed/events/v1beta1/events_client.go | 12 +- .../extensions/v1beta1/extensions_client.go | 12 +- .../flowcontrol/v1/flowcontrol_client.go | 12 +- .../flowcontrol/v1beta1/flowcontrol_client.go | 12 +- .../flowcontrol/v1beta2/flowcontrol_client.go | 12 +- .../flowcontrol/v1beta3/flowcontrol_client.go | 12 +- .../networking/v1/fake/fake_ipaddress.go | 49 + .../v1/fake/fake_networking_client.go | 8 + .../networking/v1/fake/fake_servicecidr.go | 49 + .../networking/v1/generated_expansion.go | 4 + .../typed/networking/v1/ipaddress.go | 71 + .../typed/networking/v1/networking_client.go | 22 +- .../typed/networking/v1/servicecidr.go | 75 + .../networking/v1alpha1/networking_client.go | 12 +- .../networking/v1beta1/networking_client.go | 12 +- .../kubernetes/typed/node/v1/node_client.go | 12 +- .../typed/node/v1alpha1/node_client.go | 12 +- .../typed/node/v1beta1/node_client.go | 12 +- .../typed/policy/v1/policy_client.go | 12 +- .../typed/policy/v1beta1/policy_client.go | 12 +- .../kubernetes/typed/rbac/v1/rbac_client.go | 12 +- .../typed/rbac/v1alpha1/rbac_client.go | 12 +- .../typed/rbac/v1beta1/rbac_client.go | 12 +- .../resource/v1alpha3/devicetaintrule.go | 71 + .../v1alpha3/fake/fake_devicetaintrule.go | 53 + .../v1alpha3/fake/fake_resource_client.go | 4 + .../resource/v1alpha3/generated_expansion.go | 2 + .../resource/v1alpha3/resource_client.go | 17 +- .../typed/resource/v1beta1/resource_client.go | 12 +- .../typed/resource/v1beta2/deviceclass.go | 71 + .../kubernetes/typed/resource/v1beta2/doc.go | 20 + .../typed/resource/v1beta2/fake}/doc.go | 8 +- .../resource/v1beta2/fake/fake_deviceclass.go | 51 + .../v1beta2/fake/fake_resource_client.go | 52 + .../v1beta2/fake/fake_resourceclaim.go | 53 + .../fake/fake_resourceclaimtemplate.go | 53 + .../v1beta2/fake/fake_resourceslice.go | 53 + .../resource/v1beta2/generated_expansion.go | 27 + .../typed/resource/v1beta2/resource_client.go | 116 + .../typed/resource/v1beta2/resourceclaim.go | 75 + .../resource/v1beta2/resourceclaimtemplate.go | 71 + .../typed/resource/v1beta2/resourceslice.go | 71 + .../typed/scheduling/v1/scheduling_client.go | 12 +- .../scheduling/v1alpha1/scheduling_client.go | 12 +- .../scheduling/v1beta1/scheduling_client.go | 12 +- .../typed/storage/v1/storage_client.go | 12 +- .../typed/storage/v1alpha1/storage_client.go | 12 +- .../typed/storage/v1beta1/storage_client.go | 12 +- .../v1alpha1/storagemigration_client.go | 12 +- .../v1beta1/clustertrustbundle.go | 48 + .../v1beta1/expansion_generated.go | 4 + .../v1beta1/expansion_generated.go | 8 + .../coordination/v1beta1/leasecandidate.go | 70 + .../vendor/k8s.io/client-go/listers/doc.go | 2 +- .../networking/v1/expansion_generated.go | 8 + .../listers/networking/v1/ipaddress.go | 48 + .../listers/networking/v1/servicecidr.go | 48 + .../resource/v1alpha3/devicetaintrule.go | 48 + .../resource/v1alpha3/expansion_generated.go | 4 + .../listers/resource/v1beta2/deviceclass.go | 48 + .../resource/v1beta2/expansion_generated.go | 43 + .../listers/resource/v1beta2/resourceclaim.go | 70 + .../resource/v1beta2/resourceclaimtemplate.go | 70 + .../listers/resource/v1beta2/resourceslice.go | 48 + .../pkg/apis/clientauthentication/doc.go | 2 +- .../pkg/apis/clientauthentication/v1/doc.go | 2 +- .../apis/clientauthentication/v1beta1/doc.go | 2 +- .../k8s.io/client-go/pkg/version/doc.go | 2 +- .../k8s.io/client-go/rest/.mockery.yaml | 10 + .../vendor/k8s.io/client-go/rest/client.go | 6 +- .../vendor/k8s.io/client-go/rest/config.go | 85 +- .../vendor/k8s.io/client-go/rest/plugin.go | 7 +- .../vendor/k8s.io/client-go/rest/request.go | 138 +- .../k8s.io/client-go/rest/urlbackoff.go | 101 +- .../vendor/k8s.io/client-go/rest/warnings.go | 57 +- .../k8s.io/client-go/rest/with_retry.go | 12 +- .../client-go/tools/cache/controller.go | 84 +- .../client-go/tools/cache/delta_fifo.go | 125 +- .../k8s.io/client-go/tools/cache/doc.go | 2 +- .../k8s.io/client-go/tools/cache/fifo.go | 97 +- .../k8s.io/client-go/tools/cache/listers.go | 7 +- .../k8s.io/client-go/tools/cache/listwatch.go | 174 +- .../client-go/tools/cache/mutation_cache.go | 8 +- .../tools/cache/mutation_detector.go | 1 + .../k8s.io/client-go/tools/cache/reflector.go | 238 +- .../client-go/tools/cache/shared_informer.go | 194 +- .../client-go/tools/cache/the_real_fifo.go | 407 + .../client-go/tools/clientcmd/api/doc.go | 2 +- .../client-go/tools/clientcmd/api/v1/doc.go | 2 +- .../k8s.io/client-go/tools/clientcmd/doc.go | 2 +- .../tools/leaderelection/leasecandidate.go | 18 +- .../k8s.io/client-go/tools/record/doc.go | 2 +- .../k8s.io/client-go/tools/record/event.go | 2 +- .../client-go/tools/record/util/util.go | 17 + .../client-go/tools/remotecommand/doc.go | 2 +- .../tools/remotecommand/errorstream.go | 2 +- .../tools/remotecommand/websocket.go | 19 +- .../client-go/tools/watch/informerwatcher.go | 18 +- .../client-go/tools/watch/retrywatcher.go | 103 +- .../k8s.io/client-go/tools/watch/until.go | 6 +- .../k8s.io/client-go/transport/cache.go | 8 +- .../client-go/transport/cert_rotation.go | 17 +- .../client-go/transport/round_trippers.go | 192 +- .../client-go/transport/token_source.go | 5 +- .../k8s.io/client-go/transport/transport.go | 2 +- .../vendor/k8s.io/client-go/util/cert/cert.go | 48 +- .../util/certificate/certificate_manager.go | 176 +- .../util/certificate/certificate_store.go | 23 +- .../client-go/util/certificate/csr/csr.go | 45 +- .../data_consistency_detector.go | 2 +- .../client-go/util/flowcontrol/backoff.go | 5 +- .../util/workqueue/delaying_queue.go | 19 +- .../k8s.io/client-go/util/workqueue/doc.go | 2 +- .../client-go/util/workqueue/parallelizer.go | 2 +- .../k8s.io/component-base/featuregate/OWNERS | 16 + .../featuregate/feature_gate.go | 769 ++ .../k8s.io/component-base/metrics/OWNERS | 11 + .../k8s.io/component-base/metrics/buckets.go | 53 + .../component-base/metrics/collector.go | 190 + .../k8s.io/component-base/metrics/counter.go | 306 + .../k8s.io/component-base/metrics/desc.go | 225 + .../k8s.io/component-base/metrics/gauge.go | 298 + .../component-base/metrics/histogram.go | 299 + .../k8s.io/component-base/metrics/http.go | 87 + .../k8s.io/component-base/metrics/labels.go | 22 + .../metrics/legacyregistry/registry.go | 92 + .../k8s.io/component-base/metrics/metric.go | 240 + .../k8s.io/component-base/metrics/options.go | 136 + .../k8s.io/component-base/metrics/opts.go | 390 + .../metrics/processstarttime.go | 51 + .../metrics/processstarttime_others.go | 39 + .../metrics/processstarttime_windows.go | 34 + .../metrics/prometheus/feature/metrics.go | 53 + .../prometheusextension/timing_histogram.go | 189 + .../timing_histogram_vec.go | 111 + .../prometheusextension/weighted_histogram.go | 203 + .../weighted_histogram_vec.go | 106 + .../k8s.io/component-base/metrics/registry.go | 394 + .../k8s.io/component-base/metrics/summary.go | 247 + .../metrics/timing_histogram.go | 291 + .../k8s.io/component-base/metrics/value.go | 70 + .../k8s.io/component-base/metrics/version.go | 37 + .../component-base/metrics/version_parser.go | 50 + .../k8s.io/component-base/metrics/wrappers.go | 167 + .../k8s.io/component-base/version/base.go | 2 +- .../k8s.io/component-base/version/version.go | 159 +- .../component-base/zpages/features/doc.go | 22 + .../zpages/features/kube_features.go | 52 + .../vendor/k8s.io/controller-manager/LICENSE | 201 + .../controller-manager/pkg/features/OWNERS | 4 + .../pkg/features/kube_features.go | 51 + .../kube-openapi/pkg/handler3/handler.go | 2 +- .../k8s.io/kube-openapi/pkg/spec3/fuzz.go | 146 +- .../k8s.io/kubernetes/pkg/api/v1/pod/util.go | 30 + .../k8s.io/kubernetes/pkg/apis/core/doc.go | 2 +- .../k8s.io/kubernetes/pkg/apis/core/types.go | 184 +- .../pkg/apis/core/zz_generated.deepcopy.go | 43 +- .../k8s.io/kubernetes/pkg/features/OWNERS | 4 + .../kubernetes/pkg/features/client_adapter.go | 76 + .../kubernetes/pkg/features/kube_features.go | 1894 +++ .../vendor/k8s.io/kubernetes/pkg/probe/doc.go | 2 +- .../kubernetes/pkg/util/iptables/doc.go | 2 +- .../kubernetes/pkg/util/iptables/iptables.go | 50 +- .../k8s.io/utils/clock/testing/fake_clock.go | 361 - .../clock/testing/simple_interval_clock.go | 44 - go-controller/vendor/modules.txt | 135 +- .../controller-runtime/.golangci.yml | 289 +- .../sigs.k8s.io/controller-runtime/Makefile | 6 +- .../controller-runtime/OWNERS_ALIASES | 2 + .../sigs.k8s.io/controller-runtime/README.md | 4 +- .../sigs.k8s.io/controller-runtime/alias.go | 6 + .../sigs.k8s.io/controller-runtime/doc.go | 2 +- .../pkg/builder/controller.go | 8 +- .../controller-runtime/pkg/builder/webhook.go | 61 +- .../controller-runtime/pkg/cache/cache.go | 8 +- .../pkg/cache/internal/cache_reader.go | 8 +- .../pkg/cache/internal/informers.go | 58 +- .../pkg/cache/multi_namespace_cache.go | 46 +- .../pkg/client/apiutil/restmapper.go | 20 +- .../controller-runtime/pkg/client/client.go | 9 +- .../pkg/client/config/config.go | 16 +- .../pkg/client/fake/client.go | 33 +- .../pkg/config/controller.go | 13 +- .../pkg/controller/controller.go | 86 +- .../controllerutil/controllerutil.go | 61 +- .../controller-runtime/pkg/controller/name.go | 2 +- .../pkg/controller/priorityqueue/metrics.go | 54 +- .../controller/priorityqueue/priorityqueue.go | 12 +- .../controller-runtime/pkg/event/event.go | 3 + .../controller-runtime/pkg/handler/enqueue.go | 19 +- .../pkg/handler/enqueue_mapped.go | 44 +- .../pkg/handler/enqueue_owner.go | 2 +- .../pkg/handler/eventhandler.go | 130 +- .../pkg/internal/controller/controller.go | 162 +- .../internal/controller/metrics/metrics.go | 9 +- .../pkg/internal/metrics/workqueue.go | 61 +- .../pkg/internal/source/event_handler.go | 18 +- .../pkg/internal/source/kind.go | 14 +- .../pkg/log/warning_handler.go | 27 +- .../controller-runtime/pkg/manager/manager.go | 4 + .../pkg/reconcile/reconcile.go | 12 +- .../controller-runtime/pkg/source/source.go | 8 +- .../pkg/webhook/admission/multi.go | 6 + .../pkg/webhook/internal/metrics/metrics.go | 8 +- .../sigs.k8s.io/randfill/CONTRIBUTING.md | 43 + .../vendor/sigs.k8s.io/randfill/LICENSE | 202 + .../vendor/sigs.k8s.io/randfill/NOTICE | 24 + .../vendor/sigs.k8s.io/randfill/OWNERS | 8 + .../sigs.k8s.io/randfill/OWNERS_ALIASES | 14 + .../gofuzz => sigs.k8s.io/randfill}/README.md | 45 +- .../sigs.k8s.io/randfill/SECURITY_CONTACTS | 16 + .../randfill}/bytesource/bytesource.go | 0 .../sigs.k8s.io/randfill/code-of-conduct.md | 3 + .../vendor/sigs.k8s.io/randfill/randfill.go | 682 + .../structured-merge-diff/v4/merge/update.go | 50 +- .../structured-merge-diff/v4/typed/typed.go | 47 +- .../structured-merge-diff/v4/value/scalar.go | 2 +- test/scripts/install-kind.sh | 2 +- test/scripts/upgrade-ovn.sh | 2 +- 1039 files changed, 80119 insertions(+), 22573 deletions(-) create mode 100644 go-controller/vendor/github.com/blang/semver/v4/LICENSE create mode 100644 go-controller/vendor/github.com/blang/semver/v4/json.go create mode 100644 go-controller/vendor/github.com/blang/semver/v4/range.go create mode 100644 go-controller/vendor/github.com/blang/semver/v4/semver.go create mode 100644 go-controller/vendor/github.com/blang/semver/v4/sort.go create mode 100644 go-controller/vendor/github.com/blang/semver/v4/sql.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/AUTHORS delete mode 100644 go-controller/vendor/github.com/golang/protobuf/CONTRIBUTORS delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/buffer.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/defaults.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/deprecated.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/discard.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/extensions.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/properties.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/proto.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/registry.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/text_decode.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/text_encode.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/wire.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/proto/wrappers.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/any.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/doc.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/duration.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/duration/duration.pb.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp.go delete mode 100644 go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go create mode 100644 go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.pb.go create mode 100644 go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto delete mode 100644 go-controller/vendor/github.com/google/gofuzz/.travis.yml delete mode 100644 go-controller/vendor/github.com/google/gofuzz/CONTRIBUTING.md delete mode 100644 go-controller/vendor/github.com/google/gofuzz/fuzz.go delete mode 100644 go-controller/vendor/github.com/gorilla/websocket/tls_handshake.go delete mode 100644 go-controller/vendor/github.com/gorilla/websocket/tls_handshake_116.go delete mode 100644 go-controller/vendor/github.com/gorilla/websocket/x_net_proxy.go rename go-controller/vendor/github.com/{golang/protobuf => prometheus/client_golang/internal/github.com/golang/gddo}/LICENSE (83%) create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectorfunc.go create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.c create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.go create mode 100644 go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_nocgo_darwin.go rename go-controller/vendor/github.com/prometheus/client_golang/prometheus/{process_collector_wasip1.go => process_collector_not_supported.go} (55%) rename go-controller/vendor/github.com/prometheus/client_golang/prometheus/{process_collector_other.go => process_collector_procfsenabled.go} (63%) rename go-controller/vendor/github.com/prometheus/client_golang/prometheus/{process_collector_js.go => promhttp/internal/compression.go} (70%) delete mode 100644 go-controller/vendor/github.com/prometheus/common/model/labelset_string_go120.go create mode 100644 go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go create mode 100644 go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go create mode 100644 go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/LICENSE create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/README.md create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/doc.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/encoder.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/filter.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/iterator.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/key.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/kv.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/set.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/type_string.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/attribute/value.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/codes/README.md create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/codes/codes.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/codes/doc.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/internal/attribute/attribute.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/internal/gen.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/LICENSE create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/README.md create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/config.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/context.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/doc.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/README.md create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/embedded.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/nonrecording.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/noop.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/provider.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/span.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/trace.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/tracer.go create mode 100644 go-controller/vendor/go.opentelemetry.io/otel/trace/tracestate.go create mode 100644 go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go create mode 100644 go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go create mode 100644 go-controller/vendor/google.golang.org/grpc/encoding/encoding_v2.go create mode 100644 go-controller/vendor/google.golang.org/grpc/experimental/stats/metricregistry.go create mode 100644 go-controller/vendor/google.golang.org/grpc/experimental/stats/metrics.go create mode 100644 go-controller/vendor/google.golang.org/grpc/grpclog/internal/grpclog.go create mode 100644 go-controller/vendor/google.golang.org/grpc/grpclog/internal/logger.go rename go-controller/vendor/google.golang.org/grpc/{internal/grpclog/grpclog.go => grpclog/internal/loggerv2.go} (52%) rename go-controller/vendor/google.golang.org/grpc/internal/grpclog/{prefixLogger.go => prefix_logger.go} (63%) create mode 100644 go-controller/vendor/google.golang.org/grpc/internal/stats/labels.go create mode 100644 go-controller/vendor/google.golang.org/grpc/internal/stats/metrics_recorder_list.go create mode 100644 go-controller/vendor/google.golang.org/grpc/mem/buffer_pool.go create mode 100644 go-controller/vendor/google.golang.org/grpc/mem/buffer_slice.go create mode 100644 go-controller/vendor/google.golang.org/grpc/mem/buffers.go delete mode 100644 go-controller/vendor/google.golang.org/grpc/regenerate.sh delete mode 100644 go-controller/vendor/google.golang.org/grpc/shared_buffer_pool.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go112.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go113.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/genid/name.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/api_export_opaque.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap_race.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field_opaque.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go111.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go112.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message_opaque.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/lazy.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque_gen.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field_gen.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe_opaque.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/presence.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/impl/weak.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/protolazy/bufferreader.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/protolazy/lazy.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/internal/protolazy/pointer_unsafe.go create mode 100644 go-controller/vendor/google.golang.org/protobuf/proto/wrapperopaque.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_resolve.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_validate.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/proto.go delete mode 100644 go-controller/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go create mode 100644 go-controller/vendor/k8s.io/api/networking/v1/well_known_labels.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta1/devicetaint.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/devicetaint.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/doc.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/generated.pb.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/generated.proto create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/register.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/types.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/types_swagger_doc_generated.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/zz_generated.deepcopy.go create mode 100644 go-controller/vendor/k8s.io/api/resource/v1beta2/zz_generated.prerelease-lifecycle.go create mode 100644 go-controller/vendor/k8s.io/apiextensions-apiserver/pkg/features/OWNERS create mode 100644 go-controller/vendor/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/api/operation/operation.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/collections.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/util/validation/ip.go create mode 100644 go-controller/vendor/k8s.io/apimachinery/pkg/util/yaml/stream_reader.go rename go-controller/vendor/{github.com/google/gofuzz => k8s.io/apiserver}/LICENSE (100%) create mode 100644 go-controller/vendor/k8s.io/apiserver/pkg/features/OWNERS create mode 100644 go-controller/vendor/k8s.io/apiserver/pkg/features/kube_features.go create mode 100644 go-controller/vendor/k8s.io/apiserver/pkg/util/feature/feature_gate.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/certificates/v1beta1/clustertrustbundle.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/certificates/v1beta1/clustertrustbundlespec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/coordination/v1beta1/leasecandidate.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/coordination/v1beta1/leasecandidatespec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/core/v1/nodeswapstatus.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/discovery/v1/fornode.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/discovery/v1beta1/fornode.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/ipaddress.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/ipaddressspec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/parentreference.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/servicecidr.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/servicecidrspec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/networking/v1/servicecidrstatus.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/counter.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/counterset.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicecounterconsumption.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicesubrequest.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaint.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaintrule.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaintrulespec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetaintselector.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicetoleration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/counter.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/counterset.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicecounterconsumption.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetaint.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicetoleration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocateddevicestatus.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/allocationresult.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/celdeviceselector.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/counter.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/counterset.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/device.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceallocationconfiguration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceallocationresult.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceattribute.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicecapacity.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclaimconfiguration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclass.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassconfiguration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceclassspec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconfiguration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceconstraint.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicecounterconsumption.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequest.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicerequestallocationresult.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/deviceselector.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicesubrequest.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetaint.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/devicetoleration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/exactdevicerequest.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/networkdevicedata.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/opaquedeviceconfiguration.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaimconsumerreference.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaimspec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaimstatus.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaimtemplate.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceclaimtemplatespec.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourcepool.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceslice.go create mode 100644 go-controller/vendor/k8s.io/client-go/applyconfigurations/resource/v1beta2/resourceslicespec.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/certificates/v1beta1/clustertrustbundle.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/coordination/v1beta1/leasecandidate.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/networking/v1/ipaddress.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/networking/v1/servicecidr.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1alpha3/devicetaintrule.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1beta2/deviceclass.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1beta2/interface.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1beta2/resourceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1beta2/resourceclaimtemplate.go create mode 100644 go-controller/vendor/k8s.io/client-go/informers/resource/v1beta2/resourceslice.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/clustertrustbundle.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake/fake_clustertrustbundle.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/coordination/v1beta1/fake/fake_leasecandidate.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/coordination/v1beta1/leasecandidate.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/networking/v1/fake/fake_ipaddress.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/networking/v1/fake/fake_servicecidr.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/networking/v1/ipaddress.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/networking/v1/servicecidr.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/devicetaintrule.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1alpha3/fake/fake_devicetaintrule.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/deviceclass.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/doc.go rename go-controller/vendor/{github.com/google/gofuzz => k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake}/doc.go (77%) create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_deviceclass.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resource_client.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resourceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resourceclaimtemplate.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/fake/fake_resourceslice.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/generated_expansion.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resource_client.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resourceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resourceclaimtemplate.go create mode 100644 go-controller/vendor/k8s.io/client-go/kubernetes/typed/resource/v1beta2/resourceslice.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/certificates/v1beta1/clustertrustbundle.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/coordination/v1beta1/leasecandidate.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/networking/v1/ipaddress.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/networking/v1/servicecidr.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1alpha3/devicetaintrule.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1beta2/deviceclass.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1beta2/expansion_generated.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1beta2/resourceclaim.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1beta2/resourceclaimtemplate.go create mode 100644 go-controller/vendor/k8s.io/client-go/listers/resource/v1beta2/resourceslice.go create mode 100644 go-controller/vendor/k8s.io/client-go/rest/.mockery.yaml create mode 100644 go-controller/vendor/k8s.io/client-go/tools/cache/the_real_fifo.go create mode 100644 go-controller/vendor/k8s.io/component-base/featuregate/OWNERS create mode 100644 go-controller/vendor/k8s.io/component-base/featuregate/feature_gate.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/OWNERS create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/buckets.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/collector.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/counter.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/desc.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/gauge.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/histogram.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/http.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/labels.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/legacyregistry/registry.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/metric.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/options.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/opts.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/processstarttime.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/processstarttime_others.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/processstarttime_windows.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/prometheus/feature/metrics.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/prometheusextension/timing_histogram.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/prometheusextension/timing_histogram_vec.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/prometheusextension/weighted_histogram.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/prometheusextension/weighted_histogram_vec.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/registry.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/summary.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/timing_histogram.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/value.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/version.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/version_parser.go create mode 100644 go-controller/vendor/k8s.io/component-base/metrics/wrappers.go create mode 100644 go-controller/vendor/k8s.io/component-base/zpages/features/doc.go create mode 100644 go-controller/vendor/k8s.io/component-base/zpages/features/kube_features.go create mode 100644 go-controller/vendor/k8s.io/controller-manager/LICENSE create mode 100644 go-controller/vendor/k8s.io/controller-manager/pkg/features/OWNERS create mode 100644 go-controller/vendor/k8s.io/controller-manager/pkg/features/kube_features.go create mode 100644 go-controller/vendor/k8s.io/kubernetes/pkg/features/OWNERS create mode 100644 go-controller/vendor/k8s.io/kubernetes/pkg/features/client_adapter.go create mode 100644 go-controller/vendor/k8s.io/kubernetes/pkg/features/kube_features.go delete mode 100644 go-controller/vendor/k8s.io/utils/clock/testing/fake_clock.go delete mode 100644 go-controller/vendor/k8s.io/utils/clock/testing/simple_interval_clock.go create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/CONTRIBUTING.md create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/LICENSE create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/NOTICE create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/OWNERS create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/OWNERS_ALIASES rename go-controller/vendor/{github.com/google/gofuzz => sigs.k8s.io/randfill}/README.md (53%) create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/SECURITY_CONTACTS rename go-controller/vendor/{github.com/google/gofuzz => sigs.k8s.io/randfill}/bytesource/bytesource.go (100%) create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/code-of-conduct.md create mode 100644 go-controller/vendor/sigs.k8s.io/randfill/randfill.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd0a35e4c3..6d56ef607d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ concurrency: cancel-in-progress: true env: - K8S_VERSION: v1.32.3 + K8S_VERSION: v1.33.1 KIND_CLUSTER_NAME: ovn KIND_INSTALL_INGRESS: true KIND_ALLOW_SYSTEM_WRITES: true @@ -61,7 +61,7 @@ jobs: - name: Verify uses: golangci/golangci-lint-action@v6 with: - version: v1.60.3 + version: v1.64.8 working-directory: go-controller args: --modules-download-mode=vendor --timeout=15m0s --verbose diff --git a/contrib/kind-common b/contrib/kind-common index 2a564dece0..16a8c1a894 100644 --- a/contrib/kind-common +++ b/contrib/kind-common @@ -34,7 +34,7 @@ if_error_exit() { set_common_default_params() { KIND_IMAGE=${KIND_IMAGE:-kindest/node} - K8S_VERSION=${K8S_VERSION:-v1.32.3} + K8S_VERSION=${K8S_VERSION:-v1.33.1} } run_kubectl() { diff --git a/docs/ci/ci.md b/docs/ci/ci.md index b0f98c0762..1d17671925 100644 --- a/docs/ci/ci.md +++ b/docs/ci/ci.md @@ -120,7 +120,7 @@ and set the environmental variable `K8S_VERSION` to the same value. Also make su your go directory with `export GOPATH=(...)`. ``` -K8S_VERSION=v1.32.3 +K8S_VERSION=v1.33.1 git clone --single-branch --branch $K8S_VERSION https://github.com/kubernetes/kubernetes.git $GOPATH/src/k8s.io/kubernetes/ pushd $GOPATH/src/k8s.io/kubernetes/ make WHAT="test/e2e/e2e.test vendor/github.com/onsi/ginkgo/ginkgo cmd/kubectl" diff --git a/docs/installation/launching-ovn-kubernetes-on-kind.md b/docs/installation/launching-ovn-kubernetes-on-kind.md index 1a6ae11a15..183f738884 100644 --- a/docs/installation/launching-ovn-kubernetes-on-kind.md +++ b/docs/installation/launching-ovn-kubernetes-on-kind.md @@ -375,7 +375,7 @@ sudo ln -s /usr/bin/kubectl-v1.17.3 /usr/bin/kubectl Download and install latest version of `kubectl`: ``` -$ K8S_VERSION=v1.32.3 +$ K8S_VERSION=v1.33.1 $ curl -LO https://storage.googleapis.com/kubernetes-release/release/$K8S_VERSION/bin/linux/amd64/kubectl $ chmod +x kubectl $ sudo mv kubectl /usr/bin/kubectl-$K8S_VERSION @@ -431,7 +431,7 @@ $ cd ../dist/images/ $ make fedora-image $ cd ../../contrib/ -$ PLATFORM_IPV4_SUPPORT=true PLATFORM_IPV6_SUPPORT=true K8S_VERSION=v1.32.3 ./kind.sh +$ PLATFORM_IPV4_SUPPORT=true PLATFORM_IPV6_SUPPORT=true K8S_VERSION=v1.33.1 ./kind.sh ``` Once `kind.sh` completes, setup kube config file: @@ -457,7 +457,7 @@ one (or both of) the following variables: ``` $ cd ../../contrib/ -$ KIND_IMAGE=example.com/kindest/node K8S_VERSION=v1.32.3 ./kind.sh +$ KIND_IMAGE=example.com/kindest/node K8S_VERSION=v1.33.1 ./kind.sh ``` ### Using kind local registry to deploy non ovn-k containers diff --git a/go-controller/cmd/ovnkube-trace/ovnkube-trace.go b/go-controller/cmd/ovnkube-trace/ovnkube-trace.go index 2460ff3d36..96cea038d2 100644 --- a/go-controller/cmd/ovnkube-trace/ovnkube-trace.go +++ b/go-controller/cmd/ovnkube-trace/ovnkube-trace.go @@ -15,9 +15,11 @@ import ( "strings" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + discoveryv1client "k8s.io/client-go/kubernetes/typed/discovery/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/remotecommand" @@ -44,7 +46,7 @@ const ( const ( // nbdb and sbdb local socket file path and protocol string which are // used by ovnkube-trace for establishing the connection when node - // runs on its own zone in an interconnect environment. + // runs on its own zone in an interconnect environment nbdbServerSock = "unix:/var/run/ovn/ovnnb_db.sock" sbdbServerSock = "unix:/var/run/ovn/ovnsb_db.sock" sockProtocol = "unix" @@ -364,13 +366,20 @@ func getSvcInfo(coreclient *corev1client.CoreV1Client, restconfig *rest.Config, ClusterIP: clusterIPStr, } - ep, err := coreclient.Endpoints(namespace).Get(context.TODO(), svcName, metav1.GetOptions{}) + discoveryClient, err := discoveryv1client.NewForConfig(restconfig) if err != nil { - return nil, fmt.Errorf("endpoints for service %s in namespace %s not found, err: %v", svcName, namespace, err) + return nil, fmt.Errorf("failed to create discovery client: %w", err) } - klog.V(5).Infof("==> Got Endpoint %v for service %s in namespace %s\n", ep, svcName, namespace) - err = extractSubsetInfo(coreclient, restconfig, ep.Subsets, svcInfo, ovnNamespace, addressFamily) + es, err := discoveryClient.EndpointSlices(namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("kubernetes.io/service-name=%s", svcName), + }) + if err != nil { + return nil, fmt.Errorf("EndpointSlices for service %s in namespace %s not found, err: %w", svcName, namespace, err) + } + klog.V(5).Infof("==> Got EndpointSlices %v for service %s in namespace %s\n", es, svcName, namespace) + + err = extractEndpointSliceInfo(coreclient, restconfig, es.Items, svcInfo, ovnNamespace, addressFamily) if err != nil { return nil, err } @@ -378,58 +387,69 @@ func getSvcInfo(coreclient *corev1client.CoreV1Client, restconfig *rest.Config, return svcInfo, err } -// extractSubsetInfo copies information from the endpoint subsets into the SvcInfo object. +// extractEndpointSliceInfo copies information from the endpoint slices into the SvcInfo object. // Modifies the svcInfo object the pointer of which is passed to it. -func extractSubsetInfo(coreclient *corev1client.CoreV1Client, restconfig *rest.Config, subsets []corev1.EndpointSubset, svcInfo *SvcInfo, ovnNamespace, addressFamily string) error { - for _, subset := range subsets { - klog.V(5).Infof("==> Trying to extract information for service %s in namespace %s from subset %v", - svcInfo.SvcName, svcInfo.SvcNamespace, subset) +// slice is *discoveryv1.EndpointSlice slices is +func extractEndpointSliceInfo(coreclient *corev1client.CoreV1Client, restconfig *rest.Config, slices []discoveryv1.EndpointSlice, svcInfo *SvcInfo, ovnNamespace, addressFamily string) error { + + for _, slice := range slices { + klog.V(5).Infof("==> Trying to extract information for service %s in namespace %s from slice %v", + svcInfo.SvcName, svcInfo.SvcNamespace, slice) - // Find a port for the subset. + // Find a port for the endpoint slice. var podPort string - for _, port := range subset.Ports { - podPort = strconv.Itoa(int(port.Port)) + for _, epPort := range slice.Ports { + if epPort.Port == nil { + klog.V(5).Infof("==> Endpoint Port is nil, skipping.") + continue // with the next port + } + podPort = strconv.Itoa(int(*epPort.Port)) if podPort == "" { - klog.V(5).Infof("==> Could not parse port %v, skipping.", port) + klog.V(5).Infof("==> Could not parse port %v, skipping.", epPort) continue // with the next port } } - // Continue with the next subset if podPort is empty. + // Continue with the next slice if podPort is empty. if podPort == "" { - continue // with the next subset + continue // with the next slice } - // Parse pod information from the subset addresses. One of the subsets must have a valid address which does not belong + // Parse pod information from the endpoint slice addresses. One of the slices must have a valid address which does not belong // to a host networked pod. - for _, epAddress := range subset.Addresses { + for _, endpoint := range slice.Endpoints { + epAddress := endpoint.Addresses + if len(epAddress) == 0 { + klog.V(5).Infof("Address is empty for endpoint. Skipping.") + continue + } // This is nil for host networked services. - if epAddress.TargetRef == nil { + if endpoint.TargetRef == nil { klog.V(5).Infof("Address %v belongs to a host networked pod. Skipping.", epAddress) continue // with the next address } - if epAddress.TargetRef.Name == "" || epAddress.TargetRef.Namespace == "" || epAddress.IP == "" { - klog.V(5).Infof("Address %v contains invalid data. podName %s, podNamespace %s, podIP %s. Skipping.", - epAddress, epAddress.TargetRef.Name, epAddress.TargetRef.Namespace, epAddress.IP) + if endpoint.TargetRef.Name == "" || endpoint.TargetRef.Namespace == "" { + klog.V(5).Infof("Address %v contains invalid data. podName %s, podNamespace %s. Skipping.", + epAddress, endpoint.TargetRef.Name, endpoint.TargetRef.Namespace) continue // with the next address } // Get info needed for the src Pod - svcPodInfo, err := getPodInfo(coreclient, restconfig, epAddress.TargetRef.Name, ovnNamespace, epAddress.TargetRef.Namespace, addressFamily) + svcPodInfo, err := getPodInfo(coreclient, restconfig, endpoint.TargetRef.Name, ovnNamespace, endpoint.TargetRef.Namespace, addressFamily) if err != nil { - klog.Exitf("Failed to get information from pod %s: %v", epAddress.TargetRef.Name, err) + klog.Exitf("Failed to get information from pod %s: %v", endpoint.TargetRef.Name, err) } klog.V(5).Infof("svcPodInfo is %s\n", svcPodInfo) // At this point, we should have found valid pod information + a port, so set them and return nil. svcInfo.PodInfo = svcPodInfo svcInfo.PodPort = podPort - klog.V(5).Infof("==> Got address and port information for service endpoint. podName: %s, podNamespace: %s, podIP: %s, podPort: %s, podNodeName: %s", + klog.V(5).Infof("==> Got address and port information for service endpoint slice. podName: %s, podNamespace: %s, podIP: %s, podPort: %s, podNodeName: %s", svcInfo.PodInfo.PodName, svcInfo.PodInfo.PodNamespace, svcInfo.PodInfo.IP, svcInfo.PodPort, svcInfo.PodInfo.NodeName) return nil } } - return fmt.Errorf("could not extract pod and port information from endpoints for service %s in namespace %s", svcInfo.SvcName, svcInfo.SvcNamespace) + return fmt.Errorf("could not extract pod and port information from endpointslice for service %s in namespace %s", svcInfo.SvcName, svcInfo.SvcNamespace) } // getPodInfo returns a pointer to a fully populated PodInfo struct, or error on failure. @@ -1222,7 +1242,6 @@ func main() { } klog.V(5).Infof("OVN Kubernetes namespace is %s", ovnNamespace) - if *dumpVRFTableIDs { nodesVRFTableIDs, err := findUserDefinedNetworkVRFTableIDs(coreclient, restconfig, ovnNamespace) if err != nil { diff --git a/go-controller/go.mod b/go-controller/go.mod index 0a4e54196b..0fb223c7e7 100644 --- a/go-controller/go.mod +++ b/go-controller/go.mod @@ -1,8 +1,8 @@ module github.com/ovn-org/ovn-kubernetes/go-controller -go 1.23.0 +go 1.24.0 -toolchain go1.23.6 +toolchain go1.24.5 require ( github.com/Microsoft/hcsshim v0.9.6 @@ -18,7 +18,7 @@ require ( github.com/go-logr/logr v1.4.2 github.com/go-logr/stdr v1.2.2 github.com/godbus/dbus/v5 v5.1.0 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/gopacket v1.1.19 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 @@ -39,42 +39,43 @@ require ( github.com/openshift/api v0.0.0-20231120222239-b86761094ee3 github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a github.com/ovn-kubernetes/libovsdb v0.8.0 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_model v0.6.1 github.com/safchain/ethtool v0.3.1-0.20231027162144-83e5e0097c91 github.com/spf13/afero v1.9.5 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.2 github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.38.0 golang.org/x/sync v0.12.0 golang.org/x/sys v0.31.0 - golang.org/x/time v0.7.0 - google.golang.org/grpc v1.65.0 + golang.org/x/time v0.9.0 + google.golang.org/grpc v1.68.1 google.golang.org/grpc/security/advancedtls v0.0.0-20240425232638-1e8b9b7fc655 - google.golang.org/protobuf v1.35.1 + google.golang.org/protobuf v1.36.5 gopkg.in/fsnotify/fsnotify.v1 v1.4.7 gopkg.in/gcfg.v1 v1.2.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 - k8s.io/api v0.32.5 - k8s.io/apimachinery v0.32.5 - k8s.io/client-go v0.32.5 - k8s.io/component-helpers v0.32.3 + k8s.io/api v0.33.3 + k8s.io/apimachinery v0.33.3 + k8s.io/client-go v0.33.3 + k8s.io/component-helpers v0.33.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubernetes v1.32.6 + k8s.io/kubernetes v1.33.3 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 kubevirt.io/api v1.0.0-alpha.0 - sigs.k8s.io/controller-runtime v0.20.3 + sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/knftables v0.0.18 sigs.k8s.io/network-policy-api v0.1.5 - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 sigs.k8s.io/yaml v1.4.0 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/hub v1.0.1 // indirect github.com/cenkalti/rpc2 v0.0.0-20210604223624-c1acbc6ec984 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -90,12 +91,10 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -114,7 +113,7 @@ require ( github.com/pborman/uuid v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -124,23 +123,28 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.3 // indirect - k8s.io/component-base v0.32.3 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/apiextensions-apiserver v0.33.3 // indirect + k8s.io/apiserver v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect + k8s.io/controller-manager v0.33.3 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect kubevirt.io/containerized-data-importer-api v1.55.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect ) replace ( diff --git a/go-controller/go.sum b/go-controller/go.sum index d0bde9c817..f5a40c16ac 100644 --- a/go-controller/go.sum +++ b/go-controller/go.sum @@ -98,6 +98,8 @@ github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e/go.mod h1:f7v github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= @@ -378,8 +380,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -394,8 +396,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -439,8 +441,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -506,6 +508,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -521,6 +525,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -661,8 +667,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -676,8 +682,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -693,8 +699,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -753,8 +759,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -813,6 +819,10 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -1071,8 +1081,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1208,8 +1218,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1230,8 +1240,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/examples v0.0.0-20201112215255-90f1b3ee835b h1:NuxyvVZoDfHZwYW9LD4GJiF5/nhiSyP4/InTrvw9Ibk= google.golang.org/grpc/examples v0.0.0-20201112215255-90f1b3ee835b/go.mod h1:IBqQ7wSUJ2Ep09a8rMWFsg4fmI2r38zwsq8a0GgxXpM= google.golang.org/grpc/security/advancedtls v0.0.0-20240425232638-1e8b9b7fc655 h1:m116OZfEvs1iB0qlYNH3M9C+t8eQj3rT+2hzn88UWnU= @@ -1249,8 +1259,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1316,35 +1326,39 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.7/go.mod h1:7hejA1BgBEiSsWljUyRkIjj+AISXO16IwsaDgFjJsQE= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.32.5 h1:uqjjsYo1kTJr5NIcoIaP9F+TgXgADH7nKQx91FDAhtk= -k8s.io/api v0.32.5/go.mod h1:bXXFU3fGCZ/eFMZvfHZC69PeGbXEL4zzjuPVzOxHF64= -k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= -k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= +k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.7/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.32.5 h1:6We3aJ6crC0ap8EhsEXcgX3LpI6SEjubpiOMXLROwPM= -k8s.io/apimachinery v0.32.5/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.7/go.mod h1:pGU/tWSzzvsYT7M3npHhoZ3Jh9qJTTIvFvDtWuW31dw= -k8s.io/client-go v0.32.5 h1:huFmQMzgWu0z4kbWsuZci+Gt4Fo72I4CcrvhToZ/Qp0= -k8s.io/client-go v0.32.5/go.mod h1:Qchw6f9WIVrur7DKojAHpRgGLcANT0RLIvF39Jz58xA= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/code-generator v0.22.7/go.mod h1:iOZwYADSgFPNGWfqHFfg1V0TNJnl1t0WyZluQp4baqU= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= -k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= -k8s.io/component-helpers v0.32.3 h1:9veHpOGTPLluqU4hAu5IPOwkOIZiGAJUhHndfVc5FT4= -k8s.io/component-helpers v0.32.3/go.mod h1:utTBXk8lhkJewBKNuNf32Xl3KT/0VV19DmiXU/SV4Ao= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= +k8s.io/component-helpers v0.33.3 h1:fjWVORSQfI0WKzPeIFSju/gMD9sybwXBJ7oPbqQu6eM= +k8s.io/component-helpers v0.33.3/go.mod h1:7iwv+Y9Guw6X4RrnNQOyQlXcvJrVjPveHVqUA5dm31c= +k8s.io/controller-manager v0.33.3 h1:OItg5te3ixRw9MFko5KW2ed4ogBbwnJfrS4mCXixbsg= +k8s.io/controller-manager v0.33.3/go.mod h1:sH/I5CXliIc+3bnEjdalgSTJ/3fJhIHrDA3sOwTNgxM= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -1365,10 +1379,10 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/kubernetes v1.32.6 h1:tp1gRjOqZjaoFBek5PN6eSmODdS1QRrH5UKiFP8ZByg= -k8s.io/kubernetes v1.32.6/go.mod h1:REY0Gok66BTTrbGyZaFMNKO9JhxvgBDW9B7aksWRFoY= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kubernetes v1.33.3 h1:dBx5Z2ZhR8kNzAwCoCz4j1niUbUrNUDVxeSj4/Ienu0= +k8s.io/kubernetes v1.33.3/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -1385,8 +1399,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= -sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= @@ -1394,11 +1408,14 @@ sigs.k8s.io/knftables v0.0.18 h1:6Duvmu0s/HwGifKrtl6G3AyAPYlWiZqTgS8bkVMiyaE= sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= sigs.k8s.io/network-policy-api v0.1.5 h1:xyS7VAaM9EfyB428oFk7WjWaCK6B129i+ILUF4C8l6E= sigs.k8s.io/network-policy-api v0.1.5/go.mod h1:D7Nkr43VLNd7iYryemnj8qf0N/WjBzTZDxYA+g4u1/Y= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/go-controller/hack/lint.sh b/go-controller/hack/lint.sh index 57f4695827..92863e6b5c 100755 --- a/go-controller/hack/lint.sh +++ b/go-controller/hack/lint.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -VERSION=v1.60.3 +VERSION=v1.64.8 extra_flags="" if [ "$#" -ne 1 ]; then if [ "$#" -eq 2 ] && [ "$2" == "fix" ]; then diff --git a/go-controller/pkg/node/controllers/egressip/egressip_test.go b/go-controller/pkg/node/controllers/egressip/egressip_test.go index b8a0a6d6a1..7de412fdc2 100644 --- a/go-controller/pkg/node/controllers/egressip/egressip_test.go +++ b/go-controller/pkg/node/controllers/egressip/egressip_test.go @@ -29,7 +29,6 @@ import ( "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/cache" utiliptables "k8s.io/kubernetes/pkg/util/iptables" - kexec "k8s.io/utils/exec" utilnet "k8s.io/utils/net" ovnconfig "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config" @@ -170,8 +169,8 @@ func setupFakeNode(nodeInitialConfig nodeConfig) (ns.NetNS, error) { } } // adding IPTable rules - ipTableV4Client := utiliptables.New(kexec.New(), utiliptables.ProtocolIPv4) - ipTableV6Client := utiliptables.New(kexec.New(), utiliptables.ProtocolIPv6) + ipTableV4Client := utiliptables.New(utiliptables.ProtocolIPv4) + ipTableV6Client := utiliptables.New(utiliptables.ProtocolIPv6) var ipTableClient utiliptables.Interface for _, iptableRule := range nodeInitialConfig.iptableRules { if len(iptableRule.Args) != 0 { diff --git a/go-controller/pkg/node/iptables/iptables_manager.go b/go-controller/pkg/node/iptables/iptables_manager.go index b84516b9a1..1d8f31f1b0 100644 --- a/go-controller/pkg/node/iptables/iptables_manager.go +++ b/go-controller/pkg/node/iptables/iptables_manager.go @@ -67,8 +67,8 @@ func NewController() *Controller { return &Controller{ store: make(map[rulesIndex]rules, 0), mu: &sync.Mutex{}, - iptV4: iptables.New(kexec.New(), iptables.ProtocolIPv4), - iptV6: iptables.New(kexec.New(), iptables.ProtocolIPv6), + iptV4: iptables.New(iptables.ProtocolIPv4), + iptV6: iptables.New(iptables.ProtocolIPv6), } } diff --git a/go-controller/pkg/util/multi_network_test.go b/go-controller/pkg/util/multi_network_test.go index 6b2220ef25..ab6eacf5d8 100644 --- a/go-controller/pkg/util/multi_network_test.go +++ b/go-controller/pkg/util/multi_network_test.go @@ -194,7 +194,7 @@ func TestParseNetconf(t *testing.T) { "ipam": "this is wrong" } `, - expectedError: fmt.Errorf("error parsing Network Attachment Definition ns1/nad1: json: cannot unmarshal string into Go struct field NetConf.ipam of type types.IPAM"), + expectedError: fmt.Errorf("error parsing Network Attachment Definition ns1/nad1: json: cannot unmarshal string into Go struct field NetConf.NetConf.ipam of type types.IPAM"), }, { desc: "attachment definition with IPAM key defined", diff --git a/go-controller/vendor/github.com/blang/semver/v4/LICENSE b/go-controller/vendor/github.com/blang/semver/v4/LICENSE new file mode 100644 index 0000000000..5ba5c86fcb --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/go-controller/vendor/github.com/blang/semver/v4/json.go b/go-controller/vendor/github.com/blang/semver/v4/json.go new file mode 100644 index 0000000000..a74bf7c449 --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/go-controller/vendor/github.com/blang/semver/v4/range.go b/go-controller/vendor/github.com/blang/semver/v4/range.go new file mode 100644 index 0000000000..95f7139b97 --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Contains(ap, "x") { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/go-controller/vendor/github.com/blang/semver/v4/semver.go b/go-controller/vendor/github.com/blang/semver/v4/semver.go new file mode 100644 index 0000000000..307de610f9 --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/semver.go @@ -0,0 +1,476 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precedence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// FinalizeVersion discards prerelease and build number and only returns +// major, minor and patch number. +func (v Version) FinalizeVersion() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// IncrementPatch increments the patch version +func (v *Version) IncrementPatch() error { + v.Patch++ + return nil +} + +// IncrementMinor increments the minor version +func (v *Version) IncrementMinor() error { + v.Minor++ + v.Patch = 0 + return nil +} + +// IncrementMajor increments the major version +func (v *Version) IncrementMajor() error { + v.Major++ + v.Minor = 0 + v.Patch = 0 + return nil +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (*Version, error) { + v, err := Parse(s) + vp := &v + return vp, err +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions +// with only major and minor components specified, and removes leading 0s. +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + // Remove leading zeros. + for i, p := range parts { + if len(p) > 1 { + p = strings.TrimLeft(p, "0") + if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { + p = "0" + p + } + parts[i] = p + } + } + // Fill up shortened versions. + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + } + s = strings.Join(parts, ".") + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} + +// FinalizeVersion returns the major, minor and patch number only and discards +// prerelease and build number. +func FinalizeVersion(s string) (string, error) { + v, err := Parse(s) + if err != nil { + return "", err + } + v.Pre = nil + v.Build = nil + + finalVer := v.String() + return finalVer, nil +} diff --git a/go-controller/vendor/github.com/blang/semver/v4/sort.go b/go-controller/vendor/github.com/blang/semver/v4/sort.go new file mode 100644 index 0000000000..e18f880826 --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/go-controller/vendor/github.com/blang/semver/v4/sql.go b/go-controller/vendor/github.com/blang/semver/v4/sql.go new file mode 100644 index 0000000000..db958134f3 --- /dev/null +++ b/go-controller/vendor/github.com/blang/semver/v4/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("version.Scan: cannot convert %T to string", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/go-controller/vendor/github.com/golang/protobuf/AUTHORS b/go-controller/vendor/github.com/golang/protobuf/AUTHORS deleted file mode 100644 index 15167cd746..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code refers to The Go Authors for copyright purposes. -# The master list of authors is in the main Go distribution, -# visible at http://tip.golang.org/AUTHORS. diff --git a/go-controller/vendor/github.com/golang/protobuf/CONTRIBUTORS b/go-controller/vendor/github.com/golang/protobuf/CONTRIBUTORS deleted file mode 100644 index 1c4577e968..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/CONTRIBUTORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code was written by the Go contributors. -# The master list of contributors is in the main Go distribution, -# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/buffer.go b/go-controller/vendor/github.com/golang/protobuf/proto/buffer.go deleted file mode 100644 index e810e6fea1..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/buffer.go +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "errors" - "fmt" - - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/encoding/protowire" - "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - WireVarint = 0 - WireFixed32 = 5 - WireFixed64 = 1 - WireBytes = 2 - WireStartGroup = 3 - WireEndGroup = 4 -) - -// EncodeVarint returns the varint encoded bytes of v. -func EncodeVarint(v uint64) []byte { - return protowire.AppendVarint(nil, v) -} - -// SizeVarint returns the length of the varint encoded bytes of v. -// This is equal to len(EncodeVarint(v)). -func SizeVarint(v uint64) int { - return protowire.SizeVarint(v) -} - -// DecodeVarint parses a varint encoded integer from b, -// returning the integer value and the length of the varint. -// It returns (0, 0) if there is a parse error. -func DecodeVarint(b []byte) (uint64, int) { - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return 0, 0 - } - return v, n -} - -// Buffer is a buffer for encoding and decoding the protobuf wire format. -// It may be reused between invocations to reduce memory usage. -type Buffer struct { - buf []byte - idx int - deterministic bool -} - -// NewBuffer allocates a new Buffer initialized with buf, -// where the contents of buf are considered the unread portion of the buffer. -func NewBuffer(buf []byte) *Buffer { - return &Buffer{buf: buf} -} - -// SetDeterministic specifies whether to use deterministic serialization. -// -// Deterministic serialization guarantees that for a given binary, equal -// messages will always be serialized to the same bytes. This implies: -// -// - Repeated serialization of a message will return the same bytes. -// - Different processes of the same binary (which may be executing on -// different machines) will serialize equal messages to the same bytes. -// -// Note that the deterministic serialization is NOT canonical across -// languages. It is not guaranteed to remain stable over time. It is unstable -// across different builds with schema changes due to unknown fields. -// Users who need canonical serialization (e.g., persistent storage in a -// canonical form, fingerprinting, etc.) should define their own -// canonicalization specification and implement their own serializer rather -// than relying on this API. -// -// If deterministic serialization is requested, map entries will be sorted -// by keys in lexographical order. This is an implementation detail and -// subject to change. -func (b *Buffer) SetDeterministic(deterministic bool) { - b.deterministic = deterministic -} - -// SetBuf sets buf as the internal buffer, -// where the contents of buf are considered the unread portion of the buffer. -func (b *Buffer) SetBuf(buf []byte) { - b.buf = buf - b.idx = 0 -} - -// Reset clears the internal buffer of all written and unread data. -func (b *Buffer) Reset() { - b.buf = b.buf[:0] - b.idx = 0 -} - -// Bytes returns the internal buffer. -func (b *Buffer) Bytes() []byte { - return b.buf -} - -// Unread returns the unread portion of the buffer. -func (b *Buffer) Unread() []byte { - return b.buf[b.idx:] -} - -// Marshal appends the wire-format encoding of m to the buffer. -func (b *Buffer) Marshal(m Message) error { - var err error - b.buf, err = marshalAppend(b.buf, m, b.deterministic) - return err -} - -// Unmarshal parses the wire-format message in the buffer and -// places the decoded results in m. -// It does not reset m before unmarshaling. -func (b *Buffer) Unmarshal(m Message) error { - err := UnmarshalMerge(b.Unread(), m) - b.idx = len(b.buf) - return err -} - -type unknownFields struct{ XXX_unrecognized protoimpl.UnknownFields } - -func (m *unknownFields) String() string { panic("not implemented") } -func (m *unknownFields) Reset() { panic("not implemented") } -func (m *unknownFields) ProtoMessage() { panic("not implemented") } - -// DebugPrint dumps the encoded bytes of b with a header and footer including s -// to stdout. This is only intended for debugging. -func (*Buffer) DebugPrint(s string, b []byte) { - m := MessageReflect(new(unknownFields)) - m.SetUnknown(b) - b, _ = prototext.MarshalOptions{AllowPartial: true, Indent: "\t"}.Marshal(m.Interface()) - fmt.Printf("==== %s ====\n%s==== %s ====\n", s, b, s) -} - -// EncodeVarint appends an unsigned varint encoding to the buffer. -func (b *Buffer) EncodeVarint(v uint64) error { - b.buf = protowire.AppendVarint(b.buf, v) - return nil -} - -// EncodeZigzag32 appends a 32-bit zig-zag varint encoding to the buffer. -func (b *Buffer) EncodeZigzag32(v uint64) error { - return b.EncodeVarint(uint64((uint32(v) << 1) ^ uint32((int32(v) >> 31)))) -} - -// EncodeZigzag64 appends a 64-bit zig-zag varint encoding to the buffer. -func (b *Buffer) EncodeZigzag64(v uint64) error { - return b.EncodeVarint(uint64((uint64(v) << 1) ^ uint64((int64(v) >> 63)))) -} - -// EncodeFixed32 appends a 32-bit little-endian integer to the buffer. -func (b *Buffer) EncodeFixed32(v uint64) error { - b.buf = protowire.AppendFixed32(b.buf, uint32(v)) - return nil -} - -// EncodeFixed64 appends a 64-bit little-endian integer to the buffer. -func (b *Buffer) EncodeFixed64(v uint64) error { - b.buf = protowire.AppendFixed64(b.buf, uint64(v)) - return nil -} - -// EncodeRawBytes appends a length-prefixed raw bytes to the buffer. -func (b *Buffer) EncodeRawBytes(v []byte) error { - b.buf = protowire.AppendBytes(b.buf, v) - return nil -} - -// EncodeStringBytes appends a length-prefixed raw bytes to the buffer. -// It does not validate whether v contains valid UTF-8. -func (b *Buffer) EncodeStringBytes(v string) error { - b.buf = protowire.AppendString(b.buf, v) - return nil -} - -// EncodeMessage appends a length-prefixed encoded message to the buffer. -func (b *Buffer) EncodeMessage(m Message) error { - var err error - b.buf = protowire.AppendVarint(b.buf, uint64(Size(m))) - b.buf, err = marshalAppend(b.buf, m, b.deterministic) - return err -} - -// DecodeVarint consumes an encoded unsigned varint from the buffer. -func (b *Buffer) DecodeVarint() (uint64, error) { - v, n := protowire.ConsumeVarint(b.buf[b.idx:]) - if n < 0 { - return 0, protowire.ParseError(n) - } - b.idx += n - return uint64(v), nil -} - -// DecodeZigzag32 consumes an encoded 32-bit zig-zag varint from the buffer. -func (b *Buffer) DecodeZigzag32() (uint64, error) { - v, err := b.DecodeVarint() - if err != nil { - return 0, err - } - return uint64((uint32(v) >> 1) ^ uint32((int32(v&1)<<31)>>31)), nil -} - -// DecodeZigzag64 consumes an encoded 64-bit zig-zag varint from the buffer. -func (b *Buffer) DecodeZigzag64() (uint64, error) { - v, err := b.DecodeVarint() - if err != nil { - return 0, err - } - return uint64((uint64(v) >> 1) ^ uint64((int64(v&1)<<63)>>63)), nil -} - -// DecodeFixed32 consumes a 32-bit little-endian integer from the buffer. -func (b *Buffer) DecodeFixed32() (uint64, error) { - v, n := protowire.ConsumeFixed32(b.buf[b.idx:]) - if n < 0 { - return 0, protowire.ParseError(n) - } - b.idx += n - return uint64(v), nil -} - -// DecodeFixed64 consumes a 64-bit little-endian integer from the buffer. -func (b *Buffer) DecodeFixed64() (uint64, error) { - v, n := protowire.ConsumeFixed64(b.buf[b.idx:]) - if n < 0 { - return 0, protowire.ParseError(n) - } - b.idx += n - return uint64(v), nil -} - -// DecodeRawBytes consumes a length-prefixed raw bytes from the buffer. -// If alloc is specified, it returns a copy the raw bytes -// rather than a sub-slice of the buffer. -func (b *Buffer) DecodeRawBytes(alloc bool) ([]byte, error) { - v, n := protowire.ConsumeBytes(b.buf[b.idx:]) - if n < 0 { - return nil, protowire.ParseError(n) - } - b.idx += n - if alloc { - v = append([]byte(nil), v...) - } - return v, nil -} - -// DecodeStringBytes consumes a length-prefixed raw bytes from the buffer. -// It does not validate whether the raw bytes contain valid UTF-8. -func (b *Buffer) DecodeStringBytes() (string, error) { - v, n := protowire.ConsumeString(b.buf[b.idx:]) - if n < 0 { - return "", protowire.ParseError(n) - } - b.idx += n - return v, nil -} - -// DecodeMessage consumes a length-prefixed message from the buffer. -// It does not reset m before unmarshaling. -func (b *Buffer) DecodeMessage(m Message) error { - v, err := b.DecodeRawBytes(false) - if err != nil { - return err - } - return UnmarshalMerge(v, m) -} - -// DecodeGroup consumes a message group from the buffer. -// It assumes that the start group marker has already been consumed and -// consumes all bytes until (and including the end group marker). -// It does not reset m before unmarshaling. -func (b *Buffer) DecodeGroup(m Message) error { - v, n, err := consumeGroup(b.buf[b.idx:]) - if err != nil { - return err - } - b.idx += n - return UnmarshalMerge(v, m) -} - -// consumeGroup parses b until it finds an end group marker, returning -// the raw bytes of the message (excluding the end group marker) and the -// the total length of the message (including the end group marker). -func consumeGroup(b []byte) ([]byte, int, error) { - b0 := b - depth := 1 // assume this follows a start group marker - for { - _, wtyp, tagLen := protowire.ConsumeTag(b) - if tagLen < 0 { - return nil, 0, protowire.ParseError(tagLen) - } - b = b[tagLen:] - - var valLen int - switch wtyp { - case protowire.VarintType: - _, valLen = protowire.ConsumeVarint(b) - case protowire.Fixed32Type: - _, valLen = protowire.ConsumeFixed32(b) - case protowire.Fixed64Type: - _, valLen = protowire.ConsumeFixed64(b) - case protowire.BytesType: - _, valLen = protowire.ConsumeBytes(b) - case protowire.StartGroupType: - depth++ - case protowire.EndGroupType: - depth-- - default: - return nil, 0, errors.New("proto: cannot parse reserved wire type") - } - if valLen < 0 { - return nil, 0, protowire.ParseError(valLen) - } - b = b[valLen:] - - if depth == 0 { - return b0[:len(b0)-len(b)-tagLen], len(b0) - len(b), nil - } - } -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/defaults.go b/go-controller/vendor/github.com/golang/protobuf/proto/defaults.go deleted file mode 100644 index d399bf069c..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/defaults.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "google.golang.org/protobuf/reflect/protoreflect" -) - -// SetDefaults sets unpopulated scalar fields to their default values. -// Fields within a oneof are not set even if they have a default value. -// SetDefaults is recursively called upon any populated message fields. -func SetDefaults(m Message) { - if m != nil { - setDefaults(MessageReflect(m)) - } -} - -func setDefaults(m protoreflect.Message) { - fds := m.Descriptor().Fields() - for i := 0; i < fds.Len(); i++ { - fd := fds.Get(i) - if !m.Has(fd) { - if fd.HasDefault() && fd.ContainingOneof() == nil { - v := fd.Default() - if fd.Kind() == protoreflect.BytesKind { - v = protoreflect.ValueOf(append([]byte(nil), v.Bytes()...)) // copy the default bytes - } - m.Set(fd, v) - } - continue - } - } - - m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { - switch { - // Handle singular message. - case fd.Cardinality() != protoreflect.Repeated: - if fd.Message() != nil { - setDefaults(m.Get(fd).Message()) - } - // Handle list of messages. - case fd.IsList(): - if fd.Message() != nil { - ls := m.Get(fd).List() - for i := 0; i < ls.Len(); i++ { - setDefaults(ls.Get(i).Message()) - } - } - // Handle map of messages. - case fd.IsMap(): - if fd.MapValue().Message() != nil { - ms := m.Get(fd).Map() - ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool { - setDefaults(v.Message()) - return true - }) - } - } - return true - }) -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/deprecated.go b/go-controller/vendor/github.com/golang/protobuf/proto/deprecated.go deleted file mode 100644 index e8db57e097..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/deprecated.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "encoding/json" - "errors" - "fmt" - "strconv" - - protoV2 "google.golang.org/protobuf/proto" -) - -var ( - // Deprecated: No longer returned. - ErrNil = errors.New("proto: Marshal called with nil") - - // Deprecated: No longer returned. - ErrTooLarge = errors.New("proto: message encodes to over 2 GB") - - // Deprecated: No longer returned. - ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof") -) - -// Deprecated: Do not use. -type Stats struct{ Emalloc, Dmalloc, Encode, Decode, Chit, Cmiss, Size uint64 } - -// Deprecated: Do not use. -func GetStats() Stats { return Stats{} } - -// Deprecated: Do not use. -func MarshalMessageSet(interface{}) ([]byte, error) { - return nil, errors.New("proto: not implemented") -} - -// Deprecated: Do not use. -func UnmarshalMessageSet([]byte, interface{}) error { - return errors.New("proto: not implemented") -} - -// Deprecated: Do not use. -func MarshalMessageSetJSON(interface{}) ([]byte, error) { - return nil, errors.New("proto: not implemented") -} - -// Deprecated: Do not use. -func UnmarshalMessageSetJSON([]byte, interface{}) error { - return errors.New("proto: not implemented") -} - -// Deprecated: Do not use. -func RegisterMessageSetType(Message, int32, string) {} - -// Deprecated: Do not use. -func EnumName(m map[int32]string, v int32) string { - s, ok := m[v] - if ok { - return s - } - return strconv.Itoa(int(v)) -} - -// Deprecated: Do not use. -func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) { - if data[0] == '"' { - // New style: enums are strings. - var repr string - if err := json.Unmarshal(data, &repr); err != nil { - return -1, err - } - val, ok := m[repr] - if !ok { - return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr) - } - return val, nil - } - // Old style: enums are ints. - var val int32 - if err := json.Unmarshal(data, &val); err != nil { - return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName) - } - return val, nil -} - -// Deprecated: Do not use; this type existed for intenal-use only. -type InternalMessageInfo struct{} - -// Deprecated: Do not use; this method existed for intenal-use only. -func (*InternalMessageInfo) DiscardUnknown(m Message) { - DiscardUnknown(m) -} - -// Deprecated: Do not use; this method existed for intenal-use only. -func (*InternalMessageInfo) Marshal(b []byte, m Message, deterministic bool) ([]byte, error) { - return protoV2.MarshalOptions{Deterministic: deterministic}.MarshalAppend(b, MessageV2(m)) -} - -// Deprecated: Do not use; this method existed for intenal-use only. -func (*InternalMessageInfo) Merge(dst, src Message) { - protoV2.Merge(MessageV2(dst), MessageV2(src)) -} - -// Deprecated: Do not use; this method existed for intenal-use only. -func (*InternalMessageInfo) Size(m Message) int { - return protoV2.Size(MessageV2(m)) -} - -// Deprecated: Do not use; this method existed for intenal-use only. -func (*InternalMessageInfo) Unmarshal(m Message, b []byte) error { - return protoV2.UnmarshalOptions{Merge: true}.Unmarshal(b, MessageV2(m)) -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/discard.go b/go-controller/vendor/github.com/golang/protobuf/proto/discard.go deleted file mode 100644 index 2187e877fa..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/discard.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "google.golang.org/protobuf/reflect/protoreflect" -) - -// DiscardUnknown recursively discards all unknown fields from this message -// and all embedded messages. -// -// When unmarshaling a message with unrecognized fields, the tags and values -// of such fields are preserved in the Message. This allows a later call to -// marshal to be able to produce a message that continues to have those -// unrecognized fields. To avoid this, DiscardUnknown is used to -// explicitly clear the unknown fields after unmarshaling. -func DiscardUnknown(m Message) { - if m != nil { - discardUnknown(MessageReflect(m)) - } -} - -func discardUnknown(m protoreflect.Message) { - m.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool { - switch { - // Handle singular message. - case fd.Cardinality() != protoreflect.Repeated: - if fd.Message() != nil { - discardUnknown(m.Get(fd).Message()) - } - // Handle list of messages. - case fd.IsList(): - if fd.Message() != nil { - ls := m.Get(fd).List() - for i := 0; i < ls.Len(); i++ { - discardUnknown(ls.Get(i).Message()) - } - } - // Handle map of messages. - case fd.IsMap(): - if fd.MapValue().Message() != nil { - ms := m.Get(fd).Map() - ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool { - discardUnknown(v.Message()) - return true - }) - } - } - return true - }) - - // Discard unknown fields. - if len(m.GetUnknown()) > 0 { - m.SetUnknown(nil) - } -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/extensions.go b/go-controller/vendor/github.com/golang/protobuf/proto/extensions.go deleted file mode 100644 index 42fc120c97..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/extensions.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "errors" - "fmt" - "reflect" - - "google.golang.org/protobuf/encoding/protowire" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/runtime/protoiface" - "google.golang.org/protobuf/runtime/protoimpl" -) - -type ( - // ExtensionDesc represents an extension descriptor and - // is used to interact with an extension field in a message. - // - // Variables of this type are generated in code by protoc-gen-go. - ExtensionDesc = protoimpl.ExtensionInfo - - // ExtensionRange represents a range of message extensions. - // Used in code generated by protoc-gen-go. - ExtensionRange = protoiface.ExtensionRangeV1 - - // Deprecated: Do not use; this is an internal type. - Extension = protoimpl.ExtensionFieldV1 - - // Deprecated: Do not use; this is an internal type. - XXX_InternalExtensions = protoimpl.ExtensionFields -) - -// ErrMissingExtension reports whether the extension was not present. -var ErrMissingExtension = errors.New("proto: missing extension") - -var errNotExtendable = errors.New("proto: not an extendable proto.Message") - -// HasExtension reports whether the extension field is present in m -// either as an explicitly populated field or as an unknown field. -func HasExtension(m Message, xt *ExtensionDesc) (has bool) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return false - } - - // Check whether any populated known field matches the field number. - xtd := xt.TypeDescriptor() - if isValidExtension(mr.Descriptor(), xtd) { - has = mr.Has(xtd) - } else { - mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool { - has = int32(fd.Number()) == xt.Field - return !has - }) - } - - // Check whether any unknown field matches the field number. - for b := mr.GetUnknown(); !has && len(b) > 0; { - num, _, n := protowire.ConsumeField(b) - has = int32(num) == xt.Field - b = b[n:] - } - return has -} - -// ClearExtension removes the extension field from m -// either as an explicitly populated field or as an unknown field. -func ClearExtension(m Message, xt *ExtensionDesc) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return - } - - xtd := xt.TypeDescriptor() - if isValidExtension(mr.Descriptor(), xtd) { - mr.Clear(xtd) - } else { - mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool { - if int32(fd.Number()) == xt.Field { - mr.Clear(fd) - return false - } - return true - }) - } - clearUnknown(mr, fieldNum(xt.Field)) -} - -// ClearAllExtensions clears all extensions from m. -// This includes populated fields and unknown fields in the extension range. -func ClearAllExtensions(m Message) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return - } - - mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool { - if fd.IsExtension() { - mr.Clear(fd) - } - return true - }) - clearUnknown(mr, mr.Descriptor().ExtensionRanges()) -} - -// GetExtension retrieves a proto2 extended field from m. -// -// If the descriptor is type complete (i.e., ExtensionDesc.ExtensionType is non-nil), -// then GetExtension parses the encoded field and returns a Go value of the specified type. -// If the field is not present, then the default value is returned (if one is specified), -// otherwise ErrMissingExtension is reported. -// -// If the descriptor is type incomplete (i.e., ExtensionDesc.ExtensionType is nil), -// then GetExtension returns the raw encoded bytes for the extension field. -func GetExtension(m Message, xt *ExtensionDesc) (interface{}, error) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 { - return nil, errNotExtendable - } - - // Retrieve the unknown fields for this extension field. - var bo protoreflect.RawFields - for bi := mr.GetUnknown(); len(bi) > 0; { - num, _, n := protowire.ConsumeField(bi) - if int32(num) == xt.Field { - bo = append(bo, bi[:n]...) - } - bi = bi[n:] - } - - // For type incomplete descriptors, only retrieve the unknown fields. - if xt.ExtensionType == nil { - return []byte(bo), nil - } - - // If the extension field only exists as unknown fields, unmarshal it. - // This is rarely done since proto.Unmarshal eagerly unmarshals extensions. - xtd := xt.TypeDescriptor() - if !isValidExtension(mr.Descriptor(), xtd) { - return nil, fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m) - } - if !mr.Has(xtd) && len(bo) > 0 { - m2 := mr.New() - if err := (proto.UnmarshalOptions{ - Resolver: extensionResolver{xt}, - }.Unmarshal(bo, m2.Interface())); err != nil { - return nil, err - } - if m2.Has(xtd) { - mr.Set(xtd, m2.Get(xtd)) - clearUnknown(mr, fieldNum(xt.Field)) - } - } - - // Check whether the message has the extension field set or a default. - var pv protoreflect.Value - switch { - case mr.Has(xtd): - pv = mr.Get(xtd) - case xtd.HasDefault(): - pv = xtd.Default() - default: - return nil, ErrMissingExtension - } - - v := xt.InterfaceOf(pv) - rv := reflect.ValueOf(v) - if isScalarKind(rv.Kind()) { - rv2 := reflect.New(rv.Type()) - rv2.Elem().Set(rv) - v = rv2.Interface() - } - return v, nil -} - -// extensionResolver is a custom extension resolver that stores a single -// extension type that takes precedence over the global registry. -type extensionResolver struct{ xt protoreflect.ExtensionType } - -func (r extensionResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) { - if xtd := r.xt.TypeDescriptor(); xtd.FullName() == field { - return r.xt, nil - } - return protoregistry.GlobalTypes.FindExtensionByName(field) -} - -func (r extensionResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { - if xtd := r.xt.TypeDescriptor(); xtd.ContainingMessage().FullName() == message && xtd.Number() == field { - return r.xt, nil - } - return protoregistry.GlobalTypes.FindExtensionByNumber(message, field) -} - -// GetExtensions returns a list of the extensions values present in m, -// corresponding with the provided list of extension descriptors, xts. -// If an extension is missing in m, the corresponding value is nil. -func GetExtensions(m Message, xts []*ExtensionDesc) ([]interface{}, error) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return nil, errNotExtendable - } - - vs := make([]interface{}, len(xts)) - for i, xt := range xts { - v, err := GetExtension(m, xt) - if err != nil { - if err == ErrMissingExtension { - continue - } - return vs, err - } - vs[i] = v - } - return vs, nil -} - -// SetExtension sets an extension field in m to the provided value. -func SetExtension(m Message, xt *ExtensionDesc, v interface{}) error { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 { - return errNotExtendable - } - - rv := reflect.ValueOf(v) - if reflect.TypeOf(v) != reflect.TypeOf(xt.ExtensionType) { - return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", v, xt.ExtensionType) - } - if rv.Kind() == reflect.Ptr { - if rv.IsNil() { - return fmt.Errorf("proto: SetExtension called with nil value of type %T", v) - } - if isScalarKind(rv.Elem().Kind()) { - v = rv.Elem().Interface() - } - } - - xtd := xt.TypeDescriptor() - if !isValidExtension(mr.Descriptor(), xtd) { - return fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m) - } - mr.Set(xtd, xt.ValueOf(v)) - clearUnknown(mr, fieldNum(xt.Field)) - return nil -} - -// SetRawExtension inserts b into the unknown fields of m. -// -// Deprecated: Use Message.ProtoReflect.SetUnknown instead. -func SetRawExtension(m Message, fnum int32, b []byte) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return - } - - // Verify that the raw field is valid. - for b0 := b; len(b0) > 0; { - num, _, n := protowire.ConsumeField(b0) - if int32(num) != fnum { - panic(fmt.Sprintf("mismatching field number: got %d, want %d", num, fnum)) - } - b0 = b0[n:] - } - - ClearExtension(m, &ExtensionDesc{Field: fnum}) - mr.SetUnknown(append(mr.GetUnknown(), b...)) -} - -// ExtensionDescs returns a list of extension descriptors found in m, -// containing descriptors for both populated extension fields in m and -// also unknown fields of m that are in the extension range. -// For the later case, an type incomplete descriptor is provided where only -// the ExtensionDesc.Field field is populated. -// The order of the extension descriptors is undefined. -func ExtensionDescs(m Message) ([]*ExtensionDesc, error) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 { - return nil, errNotExtendable - } - - // Collect a set of known extension descriptors. - extDescs := make(map[protoreflect.FieldNumber]*ExtensionDesc) - mr.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { - if fd.IsExtension() { - xt := fd.(protoreflect.ExtensionTypeDescriptor) - if xd, ok := xt.Type().(*ExtensionDesc); ok { - extDescs[fd.Number()] = xd - } - } - return true - }) - - // Collect a set of unknown extension descriptors. - extRanges := mr.Descriptor().ExtensionRanges() - for b := mr.GetUnknown(); len(b) > 0; { - num, _, n := protowire.ConsumeField(b) - if extRanges.Has(num) && extDescs[num] == nil { - extDescs[num] = nil - } - b = b[n:] - } - - // Transpose the set of descriptors into a list. - var xts []*ExtensionDesc - for num, xt := range extDescs { - if xt == nil { - xt = &ExtensionDesc{Field: int32(num)} - } - xts = append(xts, xt) - } - return xts, nil -} - -// isValidExtension reports whether xtd is a valid extension descriptor for md. -func isValidExtension(md protoreflect.MessageDescriptor, xtd protoreflect.ExtensionTypeDescriptor) bool { - return xtd.ContainingMessage() == md && md.ExtensionRanges().Has(xtd.Number()) -} - -// isScalarKind reports whether k is a protobuf scalar kind (except bytes). -// This function exists for historical reasons since the representation of -// scalars differs between v1 and v2, where v1 uses *T and v2 uses T. -func isScalarKind(k reflect.Kind) bool { - switch k { - case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String: - return true - default: - return false - } -} - -// clearUnknown removes unknown fields from m where remover.Has reports true. -func clearUnknown(m protoreflect.Message, remover interface { - Has(protoreflect.FieldNumber) bool -}) { - var bo protoreflect.RawFields - for bi := m.GetUnknown(); len(bi) > 0; { - num, _, n := protowire.ConsumeField(bi) - if !remover.Has(num) { - bo = append(bo, bi[:n]...) - } - bi = bi[n:] - } - if bi := m.GetUnknown(); len(bi) != len(bo) { - m.SetUnknown(bo) - } -} - -type fieldNum protoreflect.FieldNumber - -func (n1 fieldNum) Has(n2 protoreflect.FieldNumber) bool { - return protoreflect.FieldNumber(n1) == n2 -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/properties.go b/go-controller/vendor/github.com/golang/protobuf/proto/properties.go deleted file mode 100644 index dcdc2202fa..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/properties.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "sync" - - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/runtime/protoimpl" -) - -// StructProperties represents protocol buffer type information for a -// generated protobuf message in the open-struct API. -// -// Deprecated: Do not use. -type StructProperties struct { - // Prop are the properties for each field. - // - // Fields belonging to a oneof are stored in OneofTypes instead, with a - // single Properties representing the parent oneof held here. - // - // The order of Prop matches the order of fields in the Go struct. - // Struct fields that are not related to protobufs have a "XXX_" prefix - // in the Properties.Name and must be ignored by the user. - Prop []*Properties - - // OneofTypes contains information about the oneof fields in this message. - // It is keyed by the protobuf field name. - OneofTypes map[string]*OneofProperties -} - -// Properties represents the type information for a protobuf message field. -// -// Deprecated: Do not use. -type Properties struct { - // Name is a placeholder name with little meaningful semantic value. - // If the name has an "XXX_" prefix, the entire Properties must be ignored. - Name string - // OrigName is the protobuf field name or oneof name. - OrigName string - // JSONName is the JSON name for the protobuf field. - JSONName string - // Enum is a placeholder name for enums. - // For historical reasons, this is neither the Go name for the enum, - // nor the protobuf name for the enum. - Enum string // Deprecated: Do not use. - // Weak contains the full name of the weakly referenced message. - Weak string - // Wire is a string representation of the wire type. - Wire string - // WireType is the protobuf wire type for the field. - WireType int - // Tag is the protobuf field number. - Tag int - // Required reports whether this is a required field. - Required bool - // Optional reports whether this is a optional field. - Optional bool - // Repeated reports whether this is a repeated field. - Repeated bool - // Packed reports whether this is a packed repeated field of scalars. - Packed bool - // Proto3 reports whether this field operates under the proto3 syntax. - Proto3 bool - // Oneof reports whether this field belongs within a oneof. - Oneof bool - - // Default is the default value in string form. - Default string - // HasDefault reports whether the field has a default value. - HasDefault bool - - // MapKeyProp is the properties for the key field for a map field. - MapKeyProp *Properties - // MapValProp is the properties for the value field for a map field. - MapValProp *Properties -} - -// OneofProperties represents the type information for a protobuf oneof. -// -// Deprecated: Do not use. -type OneofProperties struct { - // Type is a pointer to the generated wrapper type for the field value. - // This is nil for messages that are not in the open-struct API. - Type reflect.Type - // Field is the index into StructProperties.Prop for the containing oneof. - Field int - // Prop is the properties for the field. - Prop *Properties -} - -// String formats the properties in the protobuf struct field tag style. -func (p *Properties) String() string { - s := p.Wire - s += "," + strconv.Itoa(p.Tag) - if p.Required { - s += ",req" - } - if p.Optional { - s += ",opt" - } - if p.Repeated { - s += ",rep" - } - if p.Packed { - s += ",packed" - } - s += ",name=" + p.OrigName - if p.JSONName != "" { - s += ",json=" + p.JSONName - } - if len(p.Enum) > 0 { - s += ",enum=" + p.Enum - } - if len(p.Weak) > 0 { - s += ",weak=" + p.Weak - } - if p.Proto3 { - s += ",proto3" - } - if p.Oneof { - s += ",oneof" - } - if p.HasDefault { - s += ",def=" + p.Default - } - return s -} - -// Parse populates p by parsing a string in the protobuf struct field tag style. -func (p *Properties) Parse(tag string) { - // For example: "bytes,49,opt,name=foo,def=hello!" - for len(tag) > 0 { - i := strings.IndexByte(tag, ',') - if i < 0 { - i = len(tag) - } - switch s := tag[:i]; { - case strings.HasPrefix(s, "name="): - p.OrigName = s[len("name="):] - case strings.HasPrefix(s, "json="): - p.JSONName = s[len("json="):] - case strings.HasPrefix(s, "enum="): - p.Enum = s[len("enum="):] - case strings.HasPrefix(s, "weak="): - p.Weak = s[len("weak="):] - case strings.Trim(s, "0123456789") == "": - n, _ := strconv.ParseUint(s, 10, 32) - p.Tag = int(n) - case s == "opt": - p.Optional = true - case s == "req": - p.Required = true - case s == "rep": - p.Repeated = true - case s == "varint" || s == "zigzag32" || s == "zigzag64": - p.Wire = s - p.WireType = WireVarint - case s == "fixed32": - p.Wire = s - p.WireType = WireFixed32 - case s == "fixed64": - p.Wire = s - p.WireType = WireFixed64 - case s == "bytes": - p.Wire = s - p.WireType = WireBytes - case s == "group": - p.Wire = s - p.WireType = WireStartGroup - case s == "packed": - p.Packed = true - case s == "proto3": - p.Proto3 = true - case s == "oneof": - p.Oneof = true - case strings.HasPrefix(s, "def="): - // The default tag is special in that everything afterwards is the - // default regardless of the presence of commas. - p.HasDefault = true - p.Default, i = tag[len("def="):], len(tag) - } - tag = strings.TrimPrefix(tag[i:], ",") - } -} - -// Init populates the properties from a protocol buffer struct tag. -// -// Deprecated: Do not use. -func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) { - p.Name = name - p.OrigName = name - if tag == "" { - return - } - p.Parse(tag) - - if typ != nil && typ.Kind() == reflect.Map { - p.MapKeyProp = new(Properties) - p.MapKeyProp.Init(nil, "Key", f.Tag.Get("protobuf_key"), nil) - p.MapValProp = new(Properties) - p.MapValProp.Init(nil, "Value", f.Tag.Get("protobuf_val"), nil) - } -} - -var propertiesCache sync.Map // map[reflect.Type]*StructProperties - -// GetProperties returns the list of properties for the type represented by t, -// which must be a generated protocol buffer message in the open-struct API, -// where protobuf message fields are represented by exported Go struct fields. -// -// Deprecated: Use protobuf reflection instead. -func GetProperties(t reflect.Type) *StructProperties { - if p, ok := propertiesCache.Load(t); ok { - return p.(*StructProperties) - } - p, _ := propertiesCache.LoadOrStore(t, newProperties(t)) - return p.(*StructProperties) -} - -func newProperties(t reflect.Type) *StructProperties { - if t.Kind() != reflect.Struct { - panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t)) - } - - var hasOneof bool - prop := new(StructProperties) - - // Construct a list of properties for each field in the struct. - for i := 0; i < t.NumField(); i++ { - p := new(Properties) - f := t.Field(i) - tagField := f.Tag.Get("protobuf") - p.Init(f.Type, f.Name, tagField, &f) - - tagOneof := f.Tag.Get("protobuf_oneof") - if tagOneof != "" { - hasOneof = true - p.OrigName = tagOneof - } - - // Rename unrelated struct fields with the "XXX_" prefix since so much - // user code simply checks for this to exclude special fields. - if tagField == "" && tagOneof == "" && !strings.HasPrefix(p.Name, "XXX_") { - p.Name = "XXX_" + p.Name - p.OrigName = "XXX_" + p.OrigName - } else if p.Weak != "" { - p.Name = p.OrigName // avoid possible "XXX_" prefix on weak field - } - - prop.Prop = append(prop.Prop, p) - } - - // Construct a mapping of oneof field names to properties. - if hasOneof { - var oneofWrappers []interface{} - if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofFuncs"); ok { - oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3].Interface().([]interface{}) - } - if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofWrappers"); ok { - oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0].Interface().([]interface{}) - } - if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(protoreflect.ProtoMessage); ok { - if m, ok := m.ProtoReflect().(interface{ ProtoMessageInfo() *protoimpl.MessageInfo }); ok { - oneofWrappers = m.ProtoMessageInfo().OneofWrappers - } - } - - prop.OneofTypes = make(map[string]*OneofProperties) - for _, wrapper := range oneofWrappers { - p := &OneofProperties{ - Type: reflect.ValueOf(wrapper).Type(), // *T - Prop: new(Properties), - } - f := p.Type.Elem().Field(0) - p.Prop.Name = f.Name - p.Prop.Parse(f.Tag.Get("protobuf")) - - // Determine the struct field that contains this oneof. - // Each wrapper is assignable to exactly one parent field. - var foundOneof bool - for i := 0; i < t.NumField() && !foundOneof; i++ { - if p.Type.AssignableTo(t.Field(i).Type) { - p.Field = i - foundOneof = true - } - } - if !foundOneof { - panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t)) - } - prop.OneofTypes[p.Prop.OrigName] = p - } - } - - return prop -} - -func (sp *StructProperties) Len() int { return len(sp.Prop) } -func (sp *StructProperties) Less(i, j int) bool { return false } -func (sp *StructProperties) Swap(i, j int) { return } diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/proto.go b/go-controller/vendor/github.com/golang/protobuf/proto/proto.go deleted file mode 100644 index 5aee89c323..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/proto.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package proto provides functionality for handling protocol buffer messages. -// In particular, it provides marshaling and unmarshaling between a protobuf -// message and the binary wire format. -// -// See https://developers.google.com/protocol-buffers/docs/gotutorial for -// more information. -// -// Deprecated: Use the "google.golang.org/protobuf/proto" package instead. -package proto - -import ( - protoV2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/runtime/protoiface" - "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - ProtoPackageIsVersion1 = true - ProtoPackageIsVersion2 = true - ProtoPackageIsVersion3 = true - ProtoPackageIsVersion4 = true -) - -// GeneratedEnum is any enum type generated by protoc-gen-go -// which is a named int32 kind. -// This type exists for documentation purposes. -type GeneratedEnum interface{} - -// GeneratedMessage is any message type generated by protoc-gen-go -// which is a pointer to a named struct kind. -// This type exists for documentation purposes. -type GeneratedMessage interface{} - -// Message is a protocol buffer message. -// -// This is the v1 version of the message interface and is marginally better -// than an empty interface as it lacks any method to programatically interact -// with the contents of the message. -// -// A v2 message is declared in "google.golang.org/protobuf/proto".Message and -// exposes protobuf reflection as a first-class feature of the interface. -// -// To convert a v1 message to a v2 message, use the MessageV2 function. -// To convert a v2 message to a v1 message, use the MessageV1 function. -type Message = protoiface.MessageV1 - -// MessageV1 converts either a v1 or v2 message to a v1 message. -// It returns nil if m is nil. -func MessageV1(m GeneratedMessage) protoiface.MessageV1 { - return protoimpl.X.ProtoMessageV1Of(m) -} - -// MessageV2 converts either a v1 or v2 message to a v2 message. -// It returns nil if m is nil. -func MessageV2(m GeneratedMessage) protoV2.Message { - return protoimpl.X.ProtoMessageV2Of(m) -} - -// MessageReflect returns a reflective view for a message. -// It returns nil if m is nil. -func MessageReflect(m Message) protoreflect.Message { - return protoimpl.X.MessageOf(m) -} - -// Marshaler is implemented by messages that can marshal themselves. -// This interface is used by the following functions: Size, Marshal, -// Buffer.Marshal, and Buffer.EncodeMessage. -// -// Deprecated: Do not implement. -type Marshaler interface { - // Marshal formats the encoded bytes of the message. - // It should be deterministic and emit valid protobuf wire data. - // The caller takes ownership of the returned buffer. - Marshal() ([]byte, error) -} - -// Unmarshaler is implemented by messages that can unmarshal themselves. -// This interface is used by the following functions: Unmarshal, UnmarshalMerge, -// Buffer.Unmarshal, Buffer.DecodeMessage, and Buffer.DecodeGroup. -// -// Deprecated: Do not implement. -type Unmarshaler interface { - // Unmarshal parses the encoded bytes of the protobuf wire input. - // The provided buffer is only valid for during method call. - // It should not reset the receiver message. - Unmarshal([]byte) error -} - -// Merger is implemented by messages that can merge themselves. -// This interface is used by the following functions: Clone and Merge. -// -// Deprecated: Do not implement. -type Merger interface { - // Merge merges the contents of src into the receiver message. - // It clones all data structures in src such that it aliases no mutable - // memory referenced by src. - Merge(src Message) -} - -// RequiredNotSetError is an error type returned when -// marshaling or unmarshaling a message with missing required fields. -type RequiredNotSetError struct { - err error -} - -func (e *RequiredNotSetError) Error() string { - if e.err != nil { - return e.err.Error() - } - return "proto: required field not set" -} -func (e *RequiredNotSetError) RequiredNotSet() bool { - return true -} - -func checkRequiredNotSet(m protoV2.Message) error { - if err := protoV2.CheckInitialized(m); err != nil { - return &RequiredNotSetError{err: err} - } - return nil -} - -// Clone returns a deep copy of src. -func Clone(src Message) Message { - return MessageV1(protoV2.Clone(MessageV2(src))) -} - -// Merge merges src into dst, which must be messages of the same type. -// -// Populated scalar fields in src are copied to dst, while populated -// singular messages in src are merged into dst by recursively calling Merge. -// The elements of every list field in src is appended to the corresponded -// list fields in dst. The entries of every map field in src is copied into -// the corresponding map field in dst, possibly replacing existing entries. -// The unknown fields of src are appended to the unknown fields of dst. -func Merge(dst, src Message) { - protoV2.Merge(MessageV2(dst), MessageV2(src)) -} - -// Equal reports whether two messages are equal. -// If two messages marshal to the same bytes under deterministic serialization, -// then Equal is guaranteed to report true. -// -// Two messages are equal if they are the same protobuf message type, -// have the same set of populated known and extension field values, -// and the same set of unknown fields values. -// -// Scalar values are compared with the equivalent of the == operator in Go, -// except bytes values which are compared using bytes.Equal and -// floating point values which specially treat NaNs as equal. -// Message values are compared by recursively calling Equal. -// Lists are equal if each element value is also equal. -// Maps are equal if they have the same set of keys, where the pair of values -// for each key is also equal. -func Equal(x, y Message) bool { - return protoV2.Equal(MessageV2(x), MessageV2(y)) -} - -func isMessageSet(md protoreflect.MessageDescriptor) bool { - ms, ok := md.(interface{ IsMessageSet() bool }) - return ok && ms.IsMessageSet() -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/registry.go b/go-controller/vendor/github.com/golang/protobuf/proto/registry.go deleted file mode 100644 index 066b4323b4..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/registry.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "bytes" - "compress/gzip" - "fmt" - "io/ioutil" - "reflect" - "strings" - "sync" - - "google.golang.org/protobuf/reflect/protodesc" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/runtime/protoimpl" -) - -// filePath is the path to the proto source file. -type filePath = string // e.g., "google/protobuf/descriptor.proto" - -// fileDescGZIP is the compressed contents of the encoded FileDescriptorProto. -type fileDescGZIP = []byte - -var fileCache sync.Map // map[filePath]fileDescGZIP - -// RegisterFile is called from generated code to register the compressed -// FileDescriptorProto with the file path for a proto source file. -// -// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead. -func RegisterFile(s filePath, d fileDescGZIP) { - // Decompress the descriptor. - zr, err := gzip.NewReader(bytes.NewReader(d)) - if err != nil { - panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err)) - } - b, err := ioutil.ReadAll(zr) - if err != nil { - panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err)) - } - - // Construct a protoreflect.FileDescriptor from the raw descriptor. - // Note that DescBuilder.Build automatically registers the constructed - // file descriptor with the v2 registry. - protoimpl.DescBuilder{RawDescriptor: b}.Build() - - // Locally cache the raw descriptor form for the file. - fileCache.Store(s, d) -} - -// FileDescriptor returns the compressed FileDescriptorProto given the file path -// for a proto source file. It returns nil if not found. -// -// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead. -func FileDescriptor(s filePath) fileDescGZIP { - if v, ok := fileCache.Load(s); ok { - return v.(fileDescGZIP) - } - - // Find the descriptor in the v2 registry. - var b []byte - if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil { - b, _ = Marshal(protodesc.ToFileDescriptorProto(fd)) - } - - // Locally cache the raw descriptor form for the file. - if len(b) > 0 { - v, _ := fileCache.LoadOrStore(s, protoimpl.X.CompressGZIP(b)) - return v.(fileDescGZIP) - } - return nil -} - -// enumName is the name of an enum. For historical reasons, the enum name is -// neither the full Go name nor the full protobuf name of the enum. -// The name is the dot-separated combination of just the proto package that the -// enum is declared within followed by the Go type name of the generated enum. -type enumName = string // e.g., "my.proto.package.GoMessage_GoEnum" - -// enumsByName maps enum values by name to their numeric counterpart. -type enumsByName = map[string]int32 - -// enumsByNumber maps enum values by number to their name counterpart. -type enumsByNumber = map[int32]string - -var enumCache sync.Map // map[enumName]enumsByName -var numFilesCache sync.Map // map[protoreflect.FullName]int - -// RegisterEnum is called from the generated code to register the mapping of -// enum value names to enum numbers for the enum identified by s. -// -// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead. -func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) { - if _, ok := enumCache.Load(s); ok { - panic("proto: duplicate enum registered: " + s) - } - enumCache.Store(s, m) - - // This does not forward registration to the v2 registry since this API - // lacks sufficient information to construct a complete v2 enum descriptor. -} - -// EnumValueMap returns the mapping from enum value names to enum numbers for -// the enum of the given name. It returns nil if not found. -// -// Deprecated: Use protoregistry.GlobalTypes.FindEnumByName instead. -func EnumValueMap(s enumName) enumsByName { - if v, ok := enumCache.Load(s); ok { - return v.(enumsByName) - } - - // Check whether the cache is stale. If the number of files in the current - // package differs, then it means that some enums may have been recently - // registered upstream that we do not know about. - var protoPkg protoreflect.FullName - if i := strings.LastIndexByte(s, '.'); i >= 0 { - protoPkg = protoreflect.FullName(s[:i]) - } - v, _ := numFilesCache.Load(protoPkg) - numFiles, _ := v.(int) - if protoregistry.GlobalFiles.NumFilesByPackage(protoPkg) == numFiles { - return nil // cache is up-to-date; was not found earlier - } - - // Update the enum cache for all enums declared in the given proto package. - numFiles = 0 - protoregistry.GlobalFiles.RangeFilesByPackage(protoPkg, func(fd protoreflect.FileDescriptor) bool { - walkEnums(fd, func(ed protoreflect.EnumDescriptor) { - name := protoimpl.X.LegacyEnumName(ed) - if _, ok := enumCache.Load(name); !ok { - m := make(enumsByName) - evs := ed.Values() - for i := evs.Len() - 1; i >= 0; i-- { - ev := evs.Get(i) - m[string(ev.Name())] = int32(ev.Number()) - } - enumCache.LoadOrStore(name, m) - } - }) - numFiles++ - return true - }) - numFilesCache.Store(protoPkg, numFiles) - - // Check cache again for enum map. - if v, ok := enumCache.Load(s); ok { - return v.(enumsByName) - } - return nil -} - -// walkEnums recursively walks all enums declared in d. -func walkEnums(d interface { - Enums() protoreflect.EnumDescriptors - Messages() protoreflect.MessageDescriptors -}, f func(protoreflect.EnumDescriptor)) { - eds := d.Enums() - for i := eds.Len() - 1; i >= 0; i-- { - f(eds.Get(i)) - } - mds := d.Messages() - for i := mds.Len() - 1; i >= 0; i-- { - walkEnums(mds.Get(i), f) - } -} - -// messageName is the full name of protobuf message. -type messageName = string - -var messageTypeCache sync.Map // map[messageName]reflect.Type - -// RegisterType is called from generated code to register the message Go type -// for a message of the given name. -// -// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead. -func RegisterType(m Message, s messageName) { - mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s)) - if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil { - panic(err) - } - messageTypeCache.Store(s, reflect.TypeOf(m)) -} - -// RegisterMapType is called from generated code to register the Go map type -// for a protobuf message representing a map entry. -// -// Deprecated: Do not use. -func RegisterMapType(m interface{}, s messageName) { - t := reflect.TypeOf(m) - if t.Kind() != reflect.Map { - panic(fmt.Sprintf("invalid map kind: %v", t)) - } - if _, ok := messageTypeCache.Load(s); ok { - panic(fmt.Errorf("proto: duplicate proto message registered: %s", s)) - } - messageTypeCache.Store(s, t) -} - -// MessageType returns the message type for a named message. -// It returns nil if not found. -// -// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead. -func MessageType(s messageName) reflect.Type { - if v, ok := messageTypeCache.Load(s); ok { - return v.(reflect.Type) - } - - // Derive the message type from the v2 registry. - var t reflect.Type - if mt, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(s)); mt != nil { - t = messageGoType(mt) - } - - // If we could not get a concrete type, it is possible that it is a - // pseudo-message for a map entry. - if t == nil { - d, _ := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(s)) - if md, _ := d.(protoreflect.MessageDescriptor); md != nil && md.IsMapEntry() { - kt := goTypeForField(md.Fields().ByNumber(1)) - vt := goTypeForField(md.Fields().ByNumber(2)) - t = reflect.MapOf(kt, vt) - } - } - - // Locally cache the message type for the given name. - if t != nil { - v, _ := messageTypeCache.LoadOrStore(s, t) - return v.(reflect.Type) - } - return nil -} - -func goTypeForField(fd protoreflect.FieldDescriptor) reflect.Type { - switch k := fd.Kind(); k { - case protoreflect.EnumKind: - if et, _ := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()); et != nil { - return enumGoType(et) - } - return reflect.TypeOf(protoreflect.EnumNumber(0)) - case protoreflect.MessageKind, protoreflect.GroupKind: - if mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()); mt != nil { - return messageGoType(mt) - } - return reflect.TypeOf((*protoreflect.Message)(nil)).Elem() - default: - return reflect.TypeOf(fd.Default().Interface()) - } -} - -func enumGoType(et protoreflect.EnumType) reflect.Type { - return reflect.TypeOf(et.New(0)) -} - -func messageGoType(mt protoreflect.MessageType) reflect.Type { - return reflect.TypeOf(MessageV1(mt.Zero().Interface())) -} - -// MessageName returns the full protobuf name for the given message type. -// -// Deprecated: Use protoreflect.MessageDescriptor.FullName instead. -func MessageName(m Message) messageName { - if m == nil { - return "" - } - if m, ok := m.(interface{ XXX_MessageName() messageName }); ok { - return m.XXX_MessageName() - } - return messageName(protoimpl.X.MessageDescriptorOf(m).FullName()) -} - -// RegisterExtension is called from the generated code to register -// the extension descriptor. -// -// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead. -func RegisterExtension(d *ExtensionDesc) { - if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil { - panic(err) - } -} - -type extensionsByNumber = map[int32]*ExtensionDesc - -var extensionCache sync.Map // map[messageName]extensionsByNumber - -// RegisteredExtensions returns a map of the registered extensions for the -// provided protobuf message, indexed by the extension field number. -// -// Deprecated: Use protoregistry.GlobalTypes.RangeExtensionsByMessage instead. -func RegisteredExtensions(m Message) extensionsByNumber { - // Check whether the cache is stale. If the number of extensions for - // the given message differs, then it means that some extensions were - // recently registered upstream that we do not know about. - s := MessageName(m) - v, _ := extensionCache.Load(s) - xs, _ := v.(extensionsByNumber) - if protoregistry.GlobalTypes.NumExtensionsByMessage(protoreflect.FullName(s)) == len(xs) { - return xs // cache is up-to-date - } - - // Cache is stale, re-compute the extensions map. - xs = make(extensionsByNumber) - protoregistry.GlobalTypes.RangeExtensionsByMessage(protoreflect.FullName(s), func(xt protoreflect.ExtensionType) bool { - if xd, ok := xt.(*ExtensionDesc); ok { - xs[int32(xt.TypeDescriptor().Number())] = xd - } else { - // TODO: This implies that the protoreflect.ExtensionType is a - // custom type not generated by protoc-gen-go. We could try and - // convert the type to an ExtensionDesc. - } - return true - }) - extensionCache.Store(s, xs) - return xs -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/text_decode.go b/go-controller/vendor/github.com/golang/protobuf/proto/text_decode.go deleted file mode 100644 index 47eb3e4450..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/text_decode.go +++ /dev/null @@ -1,801 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "encoding" - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "unicode/utf8" - - "google.golang.org/protobuf/encoding/prototext" - protoV2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" -) - -const wrapTextUnmarshalV2 = false - -// ParseError is returned by UnmarshalText. -type ParseError struct { - Message string - - // Deprecated: Do not use. - Line, Offset int -} - -func (e *ParseError) Error() string { - if wrapTextUnmarshalV2 { - return e.Message - } - if e.Line == 1 { - return fmt.Sprintf("line 1.%d: %v", e.Offset, e.Message) - } - return fmt.Sprintf("line %d: %v", e.Line, e.Message) -} - -// UnmarshalText parses a proto text formatted string into m. -func UnmarshalText(s string, m Message) error { - if u, ok := m.(encoding.TextUnmarshaler); ok { - return u.UnmarshalText([]byte(s)) - } - - m.Reset() - mi := MessageV2(m) - - if wrapTextUnmarshalV2 { - err := prototext.UnmarshalOptions{ - AllowPartial: true, - }.Unmarshal([]byte(s), mi) - if err != nil { - return &ParseError{Message: err.Error()} - } - return checkRequiredNotSet(mi) - } else { - if err := newTextParser(s).unmarshalMessage(mi.ProtoReflect(), ""); err != nil { - return err - } - return checkRequiredNotSet(mi) - } -} - -type textParser struct { - s string // remaining input - done bool // whether the parsing is finished (success or error) - backed bool // whether back() was called - offset, line int - cur token -} - -type token struct { - value string - err *ParseError - line int // line number - offset int // byte number from start of input, not start of line - unquoted string // the unquoted version of value, if it was a quoted string -} - -func newTextParser(s string) *textParser { - p := new(textParser) - p.s = s - p.line = 1 - p.cur.line = 1 - return p -} - -func (p *textParser) unmarshalMessage(m protoreflect.Message, terminator string) (err error) { - md := m.Descriptor() - fds := md.Fields() - - // A struct is a sequence of "name: value", terminated by one of - // '>' or '}', or the end of the input. A name may also be - // "[extension]" or "[type/url]". - // - // The whole struct can also be an expanded Any message, like: - // [type/url] < ... struct contents ... > - seen := make(map[protoreflect.FieldNumber]bool) - for { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value == terminator { - break - } - if tok.value == "[" { - if err := p.unmarshalExtensionOrAny(m, seen); err != nil { - return err - } - continue - } - - // This is a normal, non-extension field. - name := protoreflect.Name(tok.value) - fd := fds.ByName(name) - switch { - case fd == nil: - gd := fds.ByName(protoreflect.Name(strings.ToLower(string(name)))) - if gd != nil && gd.Kind() == protoreflect.GroupKind && gd.Message().Name() == name { - fd = gd - } - case fd.Kind() == protoreflect.GroupKind && fd.Message().Name() != name: - fd = nil - case fd.IsWeak() && fd.Message().IsPlaceholder(): - fd = nil - } - if fd == nil { - typeName := string(md.FullName()) - if m, ok := m.Interface().(Message); ok { - t := reflect.TypeOf(m) - if t.Kind() == reflect.Ptr { - typeName = t.Elem().String() - } - } - return p.errorf("unknown field name %q in %v", name, typeName) - } - if od := fd.ContainingOneof(); od != nil && m.WhichOneof(od) != nil { - return p.errorf("field '%s' would overwrite already parsed oneof '%s'", name, od.Name()) - } - if fd.Cardinality() != protoreflect.Repeated && seen[fd.Number()] { - return p.errorf("non-repeated field %q was repeated", fd.Name()) - } - seen[fd.Number()] = true - - // Consume any colon. - if err := p.checkForColon(fd); err != nil { - return err - } - - // Parse into the field. - v := m.Get(fd) - if !m.Has(fd) && (fd.IsList() || fd.IsMap() || fd.Message() != nil) { - v = m.Mutable(fd) - } - if v, err = p.unmarshalValue(v, fd); err != nil { - return err - } - m.Set(fd, v) - - if err := p.consumeOptionalSeparator(); err != nil { - return err - } - } - return nil -} - -func (p *textParser) unmarshalExtensionOrAny(m protoreflect.Message, seen map[protoreflect.FieldNumber]bool) error { - name, err := p.consumeExtensionOrAnyName() - if err != nil { - return err - } - - // If it contains a slash, it's an Any type URL. - if slashIdx := strings.LastIndex(name, "/"); slashIdx >= 0 { - tok := p.next() - if tok.err != nil { - return tok.err - } - // consume an optional colon - if tok.value == ":" { - tok = p.next() - if tok.err != nil { - return tok.err - } - } - - var terminator string - switch tok.value { - case "<": - terminator = ">" - case "{": - terminator = "}" - default: - return p.errorf("expected '{' or '<', found %q", tok.value) - } - - mt, err := protoregistry.GlobalTypes.FindMessageByURL(name) - if err != nil { - return p.errorf("unrecognized message %q in google.protobuf.Any", name[slashIdx+len("/"):]) - } - m2 := mt.New() - if err := p.unmarshalMessage(m2, terminator); err != nil { - return err - } - b, err := protoV2.Marshal(m2.Interface()) - if err != nil { - return p.errorf("failed to marshal message of type %q: %v", name[slashIdx+len("/"):], err) - } - - urlFD := m.Descriptor().Fields().ByName("type_url") - valFD := m.Descriptor().Fields().ByName("value") - if seen[urlFD.Number()] { - return p.errorf("Any message unpacked multiple times, or %q already set", urlFD.Name()) - } - if seen[valFD.Number()] { - return p.errorf("Any message unpacked multiple times, or %q already set", valFD.Name()) - } - m.Set(urlFD, protoreflect.ValueOfString(name)) - m.Set(valFD, protoreflect.ValueOfBytes(b)) - seen[urlFD.Number()] = true - seen[valFD.Number()] = true - return nil - } - - xname := protoreflect.FullName(name) - xt, _ := protoregistry.GlobalTypes.FindExtensionByName(xname) - if xt == nil && isMessageSet(m.Descriptor()) { - xt, _ = protoregistry.GlobalTypes.FindExtensionByName(xname.Append("message_set_extension")) - } - if xt == nil { - return p.errorf("unrecognized extension %q", name) - } - fd := xt.TypeDescriptor() - if fd.ContainingMessage().FullName() != m.Descriptor().FullName() { - return p.errorf("extension field %q does not extend message %q", name, m.Descriptor().FullName()) - } - - if err := p.checkForColon(fd); err != nil { - return err - } - - v := m.Get(fd) - if !m.Has(fd) && (fd.IsList() || fd.IsMap() || fd.Message() != nil) { - v = m.Mutable(fd) - } - v, err = p.unmarshalValue(v, fd) - if err != nil { - return err - } - m.Set(fd, v) - return p.consumeOptionalSeparator() -} - -func (p *textParser) unmarshalValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) { - tok := p.next() - if tok.err != nil { - return v, tok.err - } - if tok.value == "" { - return v, p.errorf("unexpected EOF") - } - - switch { - case fd.IsList(): - lv := v.List() - var err error - if tok.value == "[" { - // Repeated field with list notation, like [1,2,3]. - for { - vv := lv.NewElement() - vv, err = p.unmarshalSingularValue(vv, fd) - if err != nil { - return v, err - } - lv.Append(vv) - - tok := p.next() - if tok.err != nil { - return v, tok.err - } - if tok.value == "]" { - break - } - if tok.value != "," { - return v, p.errorf("Expected ']' or ',' found %q", tok.value) - } - } - return v, nil - } - - // One value of the repeated field. - p.back() - vv := lv.NewElement() - vv, err = p.unmarshalSingularValue(vv, fd) - if err != nil { - return v, err - } - lv.Append(vv) - return v, nil - case fd.IsMap(): - // The map entry should be this sequence of tokens: - // < key : KEY value : VALUE > - // However, implementations may omit key or value, and technically - // we should support them in any order. - var terminator string - switch tok.value { - case "<": - terminator = ">" - case "{": - terminator = "}" - default: - return v, p.errorf("expected '{' or '<', found %q", tok.value) - } - - keyFD := fd.MapKey() - valFD := fd.MapValue() - - mv := v.Map() - kv := keyFD.Default() - vv := mv.NewValue() - for { - tok := p.next() - if tok.err != nil { - return v, tok.err - } - if tok.value == terminator { - break - } - var err error - switch tok.value { - case "key": - if err := p.consumeToken(":"); err != nil { - return v, err - } - if kv, err = p.unmarshalSingularValue(kv, keyFD); err != nil { - return v, err - } - if err := p.consumeOptionalSeparator(); err != nil { - return v, err - } - case "value": - if err := p.checkForColon(valFD); err != nil { - return v, err - } - if vv, err = p.unmarshalSingularValue(vv, valFD); err != nil { - return v, err - } - if err := p.consumeOptionalSeparator(); err != nil { - return v, err - } - default: - p.back() - return v, p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value) - } - } - mv.Set(kv.MapKey(), vv) - return v, nil - default: - p.back() - return p.unmarshalSingularValue(v, fd) - } -} - -func (p *textParser) unmarshalSingularValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) { - tok := p.next() - if tok.err != nil { - return v, tok.err - } - if tok.value == "" { - return v, p.errorf("unexpected EOF") - } - - switch fd.Kind() { - case protoreflect.BoolKind: - switch tok.value { - case "true", "1", "t", "True": - return protoreflect.ValueOfBool(true), nil - case "false", "0", "f", "False": - return protoreflect.ValueOfBool(false), nil - } - case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: - if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil { - return protoreflect.ValueOfInt32(int32(x)), nil - } - - // The C++ parser accepts large positive hex numbers that uses - // two's complement arithmetic to represent negative numbers. - // This feature is here for backwards compatibility with C++. - if strings.HasPrefix(tok.value, "0x") { - if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { - return protoreflect.ValueOfInt32(int32(-(int64(^x) + 1))), nil - } - } - case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: - if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil { - return protoreflect.ValueOfInt64(int64(x)), nil - } - - // The C++ parser accepts large positive hex numbers that uses - // two's complement arithmetic to represent negative numbers. - // This feature is here for backwards compatibility with C++. - if strings.HasPrefix(tok.value, "0x") { - if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil { - return protoreflect.ValueOfInt64(int64(-(int64(^x) + 1))), nil - } - } - case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: - if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { - return protoreflect.ValueOfUint32(uint32(x)), nil - } - case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: - if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil { - return protoreflect.ValueOfUint64(uint64(x)), nil - } - case protoreflect.FloatKind: - // Ignore 'f' for compatibility with output generated by C++, - // but don't remove 'f' when the value is "-inf" or "inf". - v := tok.value - if strings.HasSuffix(v, "f") && v != "-inf" && v != "inf" { - v = v[:len(v)-len("f")] - } - if x, err := strconv.ParseFloat(v, 32); err == nil { - return protoreflect.ValueOfFloat32(float32(x)), nil - } - case protoreflect.DoubleKind: - // Ignore 'f' for compatibility with output generated by C++, - // but don't remove 'f' when the value is "-inf" or "inf". - v := tok.value - if strings.HasSuffix(v, "f") && v != "-inf" && v != "inf" { - v = v[:len(v)-len("f")] - } - if x, err := strconv.ParseFloat(v, 64); err == nil { - return protoreflect.ValueOfFloat64(float64(x)), nil - } - case protoreflect.StringKind: - if isQuote(tok.value[0]) { - return protoreflect.ValueOfString(tok.unquoted), nil - } - case protoreflect.BytesKind: - if isQuote(tok.value[0]) { - return protoreflect.ValueOfBytes([]byte(tok.unquoted)), nil - } - case protoreflect.EnumKind: - if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil { - return protoreflect.ValueOfEnum(protoreflect.EnumNumber(x)), nil - } - vd := fd.Enum().Values().ByName(protoreflect.Name(tok.value)) - if vd != nil { - return protoreflect.ValueOfEnum(vd.Number()), nil - } - case protoreflect.MessageKind, protoreflect.GroupKind: - var terminator string - switch tok.value { - case "{": - terminator = "}" - case "<": - terminator = ">" - default: - return v, p.errorf("expected '{' or '<', found %q", tok.value) - } - err := p.unmarshalMessage(v.Message(), terminator) - return v, err - default: - panic(fmt.Sprintf("invalid kind %v", fd.Kind())) - } - return v, p.errorf("invalid %v: %v", fd.Kind(), tok.value) -} - -// Consume a ':' from the input stream (if the next token is a colon), -// returning an error if a colon is needed but not present. -func (p *textParser) checkForColon(fd protoreflect.FieldDescriptor) *ParseError { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != ":" { - if fd.Message() == nil { - return p.errorf("expected ':', found %q", tok.value) - } - p.back() - } - return nil -} - -// consumeExtensionOrAnyName consumes an extension name or an Any type URL and -// the following ']'. It returns the name or URL consumed. -func (p *textParser) consumeExtensionOrAnyName() (string, error) { - tok := p.next() - if tok.err != nil { - return "", tok.err - } - - // If extension name or type url is quoted, it's a single token. - if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] { - name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0])) - if err != nil { - return "", err - } - return name, p.consumeToken("]") - } - - // Consume everything up to "]" - var parts []string - for tok.value != "]" { - parts = append(parts, tok.value) - tok = p.next() - if tok.err != nil { - return "", p.errorf("unrecognized type_url or extension name: %s", tok.err) - } - if p.done && tok.value != "]" { - return "", p.errorf("unclosed type_url or extension name") - } - } - return strings.Join(parts, ""), nil -} - -// consumeOptionalSeparator consumes an optional semicolon or comma. -// It is used in unmarshalMessage to provide backward compatibility. -func (p *textParser) consumeOptionalSeparator() error { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != ";" && tok.value != "," { - p.back() - } - return nil -} - -func (p *textParser) errorf(format string, a ...interface{}) *ParseError { - pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset} - p.cur.err = pe - p.done = true - return pe -} - -func (p *textParser) skipWhitespace() { - i := 0 - for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') { - if p.s[i] == '#' { - // comment; skip to end of line or input - for i < len(p.s) && p.s[i] != '\n' { - i++ - } - if i == len(p.s) { - break - } - } - if p.s[i] == '\n' { - p.line++ - } - i++ - } - p.offset += i - p.s = p.s[i:len(p.s)] - if len(p.s) == 0 { - p.done = true - } -} - -func (p *textParser) advance() { - // Skip whitespace - p.skipWhitespace() - if p.done { - return - } - - // Start of non-whitespace - p.cur.err = nil - p.cur.offset, p.cur.line = p.offset, p.line - p.cur.unquoted = "" - switch p.s[0] { - case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/': - // Single symbol - p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)] - case '"', '\'': - // Quoted string - i := 1 - for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' { - if p.s[i] == '\\' && i+1 < len(p.s) { - // skip escaped char - i++ - } - i++ - } - if i >= len(p.s) || p.s[i] != p.s[0] { - p.errorf("unmatched quote") - return - } - unq, err := unquoteC(p.s[1:i], rune(p.s[0])) - if err != nil { - p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err) - return - } - p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)] - p.cur.unquoted = unq - default: - i := 0 - for i < len(p.s) && isIdentOrNumberChar(p.s[i]) { - i++ - } - if i == 0 { - p.errorf("unexpected byte %#x", p.s[0]) - return - } - p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)] - } - p.offset += len(p.cur.value) -} - -// Back off the parser by one token. Can only be done between calls to next(). -// It makes the next advance() a no-op. -func (p *textParser) back() { p.backed = true } - -// Advances the parser and returns the new current token. -func (p *textParser) next() *token { - if p.backed || p.done { - p.backed = false - return &p.cur - } - p.advance() - if p.done { - p.cur.value = "" - } else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) { - // Look for multiple quoted strings separated by whitespace, - // and concatenate them. - cat := p.cur - for { - p.skipWhitespace() - if p.done || !isQuote(p.s[0]) { - break - } - p.advance() - if p.cur.err != nil { - return &p.cur - } - cat.value += " " + p.cur.value - cat.unquoted += p.cur.unquoted - } - p.done = false // parser may have seen EOF, but we want to return cat - p.cur = cat - } - return &p.cur -} - -func (p *textParser) consumeToken(s string) error { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != s { - p.back() - return p.errorf("expected %q, found %q", s, tok.value) - } - return nil -} - -var errBadUTF8 = errors.New("proto: bad UTF-8") - -func unquoteC(s string, quote rune) (string, error) { - // This is based on C++'s tokenizer.cc. - // Despite its name, this is *not* parsing C syntax. - // For instance, "\0" is an invalid quoted string. - - // Avoid allocation in trivial cases. - simple := true - for _, r := range s { - if r == '\\' || r == quote { - simple = false - break - } - } - if simple { - return s, nil - } - - buf := make([]byte, 0, 3*len(s)/2) - for len(s) > 0 { - r, n := utf8.DecodeRuneInString(s) - if r == utf8.RuneError && n == 1 { - return "", errBadUTF8 - } - s = s[n:] - if r != '\\' { - if r < utf8.RuneSelf { - buf = append(buf, byte(r)) - } else { - buf = append(buf, string(r)...) - } - continue - } - - ch, tail, err := unescape(s) - if err != nil { - return "", err - } - buf = append(buf, ch...) - s = tail - } - return string(buf), nil -} - -func unescape(s string) (ch string, tail string, err error) { - r, n := utf8.DecodeRuneInString(s) - if r == utf8.RuneError && n == 1 { - return "", "", errBadUTF8 - } - s = s[n:] - switch r { - case 'a': - return "\a", s, nil - case 'b': - return "\b", s, nil - case 'f': - return "\f", s, nil - case 'n': - return "\n", s, nil - case 'r': - return "\r", s, nil - case 't': - return "\t", s, nil - case 'v': - return "\v", s, nil - case '?': - return "?", s, nil // trigraph workaround - case '\'', '"', '\\': - return string(r), s, nil - case '0', '1', '2', '3', '4', '5', '6', '7': - if len(s) < 2 { - return "", "", fmt.Errorf(`\%c requires 2 following digits`, r) - } - ss := string(r) + s[:2] - s = s[2:] - i, err := strconv.ParseUint(ss, 8, 8) - if err != nil { - return "", "", fmt.Errorf(`\%s contains non-octal digits`, ss) - } - return string([]byte{byte(i)}), s, nil - case 'x', 'X', 'u', 'U': - var n int - switch r { - case 'x', 'X': - n = 2 - case 'u': - n = 4 - case 'U': - n = 8 - } - if len(s) < n { - return "", "", fmt.Errorf(`\%c requires %d following digits`, r, n) - } - ss := s[:n] - s = s[n:] - i, err := strconv.ParseUint(ss, 16, 64) - if err != nil { - return "", "", fmt.Errorf(`\%c%s contains non-hexadecimal digits`, r, ss) - } - if r == 'x' || r == 'X' { - return string([]byte{byte(i)}), s, nil - } - if i > utf8.MaxRune { - return "", "", fmt.Errorf(`\%c%s is not a valid Unicode code point`, r, ss) - } - return string(rune(i)), s, nil - } - return "", "", fmt.Errorf(`unknown escape \%c`, r) -} - -func isIdentOrNumberChar(c byte) bool { - switch { - case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z': - return true - case '0' <= c && c <= '9': - return true - } - switch c { - case '-', '+', '.', '_': - return true - } - return false -} - -func isWhitespace(c byte) bool { - switch c { - case ' ', '\t', '\n', '\r': - return true - } - return false -} - -func isQuote(c byte) bool { - switch c { - case '"', '\'': - return true - } - return false -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/text_encode.go b/go-controller/vendor/github.com/golang/protobuf/proto/text_encode.go deleted file mode 100644 index a31134eeb3..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/text_encode.go +++ /dev/null @@ -1,560 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - "bytes" - "encoding" - "fmt" - "io" - "math" - "sort" - "strings" - - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/encoding/protowire" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" -) - -const wrapTextMarshalV2 = false - -// TextMarshaler is a configurable text format marshaler. -type TextMarshaler struct { - Compact bool // use compact text format (one line) - ExpandAny bool // expand google.protobuf.Any messages of known types -} - -// Marshal writes the proto text format of m to w. -func (tm *TextMarshaler) Marshal(w io.Writer, m Message) error { - b, err := tm.marshal(m) - if len(b) > 0 { - if _, err := w.Write(b); err != nil { - return err - } - } - return err -} - -// Text returns a proto text formatted string of m. -func (tm *TextMarshaler) Text(m Message) string { - b, _ := tm.marshal(m) - return string(b) -} - -func (tm *TextMarshaler) marshal(m Message) ([]byte, error) { - mr := MessageReflect(m) - if mr == nil || !mr.IsValid() { - return []byte(""), nil - } - - if wrapTextMarshalV2 { - if m, ok := m.(encoding.TextMarshaler); ok { - return m.MarshalText() - } - - opts := prototext.MarshalOptions{ - AllowPartial: true, - EmitUnknown: true, - } - if !tm.Compact { - opts.Indent = " " - } - if !tm.ExpandAny { - opts.Resolver = (*protoregistry.Types)(nil) - } - return opts.Marshal(mr.Interface()) - } else { - w := &textWriter{ - compact: tm.Compact, - expandAny: tm.ExpandAny, - complete: true, - } - - if m, ok := m.(encoding.TextMarshaler); ok { - b, err := m.MarshalText() - if err != nil { - return nil, err - } - w.Write(b) - return w.buf, nil - } - - err := w.writeMessage(mr) - return w.buf, err - } -} - -var ( - defaultTextMarshaler = TextMarshaler{} - compactTextMarshaler = TextMarshaler{Compact: true} -) - -// MarshalText writes the proto text format of m to w. -func MarshalText(w io.Writer, m Message) error { return defaultTextMarshaler.Marshal(w, m) } - -// MarshalTextString returns a proto text formatted string of m. -func MarshalTextString(m Message) string { return defaultTextMarshaler.Text(m) } - -// CompactText writes the compact proto text format of m to w. -func CompactText(w io.Writer, m Message) error { return compactTextMarshaler.Marshal(w, m) } - -// CompactTextString returns a compact proto text formatted string of m. -func CompactTextString(m Message) string { return compactTextMarshaler.Text(m) } - -var ( - newline = []byte("\n") - endBraceNewline = []byte("}\n") - posInf = []byte("inf") - negInf = []byte("-inf") - nan = []byte("nan") -) - -// textWriter is an io.Writer that tracks its indentation level. -type textWriter struct { - compact bool // same as TextMarshaler.Compact - expandAny bool // same as TextMarshaler.ExpandAny - complete bool // whether the current position is a complete line - indent int // indentation level; never negative - buf []byte -} - -func (w *textWriter) Write(p []byte) (n int, _ error) { - newlines := bytes.Count(p, newline) - if newlines == 0 { - if !w.compact && w.complete { - w.writeIndent() - } - w.buf = append(w.buf, p...) - w.complete = false - return len(p), nil - } - - frags := bytes.SplitN(p, newline, newlines+1) - if w.compact { - for i, frag := range frags { - if i > 0 { - w.buf = append(w.buf, ' ') - n++ - } - w.buf = append(w.buf, frag...) - n += len(frag) - } - return n, nil - } - - for i, frag := range frags { - if w.complete { - w.writeIndent() - } - w.buf = append(w.buf, frag...) - n += len(frag) - if i+1 < len(frags) { - w.buf = append(w.buf, '\n') - n++ - } - } - w.complete = len(frags[len(frags)-1]) == 0 - return n, nil -} - -func (w *textWriter) WriteByte(c byte) error { - if w.compact && c == '\n' { - c = ' ' - } - if !w.compact && w.complete { - w.writeIndent() - } - w.buf = append(w.buf, c) - w.complete = c == '\n' - return nil -} - -func (w *textWriter) writeName(fd protoreflect.FieldDescriptor) { - if !w.compact && w.complete { - w.writeIndent() - } - w.complete = false - - if fd.Kind() != protoreflect.GroupKind { - w.buf = append(w.buf, fd.Name()...) - w.WriteByte(':') - } else { - // Use message type name for group field name. - w.buf = append(w.buf, fd.Message().Name()...) - } - - if !w.compact { - w.WriteByte(' ') - } -} - -func requiresQuotes(u string) bool { - // When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted. - for _, ch := range u { - switch { - case ch == '.' || ch == '/' || ch == '_': - continue - case '0' <= ch && ch <= '9': - continue - case 'A' <= ch && ch <= 'Z': - continue - case 'a' <= ch && ch <= 'z': - continue - default: - return true - } - } - return false -} - -// writeProto3Any writes an expanded google.protobuf.Any message. -// -// It returns (false, nil) if sv value can't be unmarshaled (e.g. because -// required messages are not linked in). -// -// It returns (true, error) when sv was written in expanded format or an error -// was encountered. -func (w *textWriter) writeProto3Any(m protoreflect.Message) (bool, error) { - md := m.Descriptor() - fdURL := md.Fields().ByName("type_url") - fdVal := md.Fields().ByName("value") - - url := m.Get(fdURL).String() - mt, err := protoregistry.GlobalTypes.FindMessageByURL(url) - if err != nil { - return false, nil - } - - b := m.Get(fdVal).Bytes() - m2 := mt.New() - if err := proto.Unmarshal(b, m2.Interface()); err != nil { - return false, nil - } - w.Write([]byte("[")) - if requiresQuotes(url) { - w.writeQuotedString(url) - } else { - w.Write([]byte(url)) - } - if w.compact { - w.Write([]byte("]:<")) - } else { - w.Write([]byte("]: <\n")) - w.indent++ - } - if err := w.writeMessage(m2); err != nil { - return true, err - } - if w.compact { - w.Write([]byte("> ")) - } else { - w.indent-- - w.Write([]byte(">\n")) - } - return true, nil -} - -func (w *textWriter) writeMessage(m protoreflect.Message) error { - md := m.Descriptor() - if w.expandAny && md.FullName() == "google.protobuf.Any" { - if canExpand, err := w.writeProto3Any(m); canExpand { - return err - } - } - - fds := md.Fields() - for i := 0; i < fds.Len(); { - fd := fds.Get(i) - if od := fd.ContainingOneof(); od != nil { - fd = m.WhichOneof(od) - i += od.Fields().Len() - } else { - i++ - } - if fd == nil || !m.Has(fd) { - continue - } - - switch { - case fd.IsList(): - lv := m.Get(fd).List() - for j := 0; j < lv.Len(); j++ { - w.writeName(fd) - v := lv.Get(j) - if err := w.writeSingularValue(v, fd); err != nil { - return err - } - w.WriteByte('\n') - } - case fd.IsMap(): - kfd := fd.MapKey() - vfd := fd.MapValue() - mv := m.Get(fd).Map() - - type entry struct{ key, val protoreflect.Value } - var entries []entry - mv.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { - entries = append(entries, entry{k.Value(), v}) - return true - }) - sort.Slice(entries, func(i, j int) bool { - switch kfd.Kind() { - case protoreflect.BoolKind: - return !entries[i].key.Bool() && entries[j].key.Bool() - case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: - return entries[i].key.Int() < entries[j].key.Int() - case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, protoreflect.Fixed64Kind: - return entries[i].key.Uint() < entries[j].key.Uint() - case protoreflect.StringKind: - return entries[i].key.String() < entries[j].key.String() - default: - panic("invalid kind") - } - }) - for _, entry := range entries { - w.writeName(fd) - w.WriteByte('<') - if !w.compact { - w.WriteByte('\n') - } - w.indent++ - w.writeName(kfd) - if err := w.writeSingularValue(entry.key, kfd); err != nil { - return err - } - w.WriteByte('\n') - w.writeName(vfd) - if err := w.writeSingularValue(entry.val, vfd); err != nil { - return err - } - w.WriteByte('\n') - w.indent-- - w.WriteByte('>') - w.WriteByte('\n') - } - default: - w.writeName(fd) - if err := w.writeSingularValue(m.Get(fd), fd); err != nil { - return err - } - w.WriteByte('\n') - } - } - - if b := m.GetUnknown(); len(b) > 0 { - w.writeUnknownFields(b) - } - return w.writeExtensions(m) -} - -func (w *textWriter) writeSingularValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) error { - switch fd.Kind() { - case protoreflect.FloatKind, protoreflect.DoubleKind: - switch vf := v.Float(); { - case math.IsInf(vf, +1): - w.Write(posInf) - case math.IsInf(vf, -1): - w.Write(negInf) - case math.IsNaN(vf): - w.Write(nan) - default: - fmt.Fprint(w, v.Interface()) - } - case protoreflect.StringKind: - // NOTE: This does not validate UTF-8 for historical reasons. - w.writeQuotedString(string(v.String())) - case protoreflect.BytesKind: - w.writeQuotedString(string(v.Bytes())) - case protoreflect.MessageKind, protoreflect.GroupKind: - var bra, ket byte = '<', '>' - if fd.Kind() == protoreflect.GroupKind { - bra, ket = '{', '}' - } - w.WriteByte(bra) - if !w.compact { - w.WriteByte('\n') - } - w.indent++ - m := v.Message() - if m2, ok := m.Interface().(encoding.TextMarshaler); ok { - b, err := m2.MarshalText() - if err != nil { - return err - } - w.Write(b) - } else { - w.writeMessage(m) - } - w.indent-- - w.WriteByte(ket) - case protoreflect.EnumKind: - if ev := fd.Enum().Values().ByNumber(v.Enum()); ev != nil { - fmt.Fprint(w, ev.Name()) - } else { - fmt.Fprint(w, v.Enum()) - } - default: - fmt.Fprint(w, v.Interface()) - } - return nil -} - -// writeQuotedString writes a quoted string in the protocol buffer text format. -func (w *textWriter) writeQuotedString(s string) { - w.WriteByte('"') - for i := 0; i < len(s); i++ { - switch c := s[i]; c { - case '\n': - w.buf = append(w.buf, `\n`...) - case '\r': - w.buf = append(w.buf, `\r`...) - case '\t': - w.buf = append(w.buf, `\t`...) - case '"': - w.buf = append(w.buf, `\"`...) - case '\\': - w.buf = append(w.buf, `\\`...) - default: - if isPrint := c >= 0x20 && c < 0x7f; isPrint { - w.buf = append(w.buf, c) - } else { - w.buf = append(w.buf, fmt.Sprintf(`\%03o`, c)...) - } - } - } - w.WriteByte('"') -} - -func (w *textWriter) writeUnknownFields(b []byte) { - if !w.compact { - fmt.Fprintf(w, "/* %d unknown bytes */\n", len(b)) - } - - for len(b) > 0 { - num, wtyp, n := protowire.ConsumeTag(b) - if n < 0 { - return - } - b = b[n:] - - if wtyp == protowire.EndGroupType { - w.indent-- - w.Write(endBraceNewline) - continue - } - fmt.Fprint(w, num) - if wtyp != protowire.StartGroupType { - w.WriteByte(':') - } - if !w.compact || wtyp == protowire.StartGroupType { - w.WriteByte(' ') - } - switch wtyp { - case protowire.VarintType: - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return - } - b = b[n:] - fmt.Fprint(w, v) - case protowire.Fixed32Type: - v, n := protowire.ConsumeFixed32(b) - if n < 0 { - return - } - b = b[n:] - fmt.Fprint(w, v) - case protowire.Fixed64Type: - v, n := protowire.ConsumeFixed64(b) - if n < 0 { - return - } - b = b[n:] - fmt.Fprint(w, v) - case protowire.BytesType: - v, n := protowire.ConsumeBytes(b) - if n < 0 { - return - } - b = b[n:] - fmt.Fprintf(w, "%q", v) - case protowire.StartGroupType: - w.WriteByte('{') - w.indent++ - default: - fmt.Fprintf(w, "/* unknown wire type %d */", wtyp) - } - w.WriteByte('\n') - } -} - -// writeExtensions writes all the extensions in m. -func (w *textWriter) writeExtensions(m protoreflect.Message) error { - md := m.Descriptor() - if md.ExtensionRanges().Len() == 0 { - return nil - } - - type ext struct { - desc protoreflect.FieldDescriptor - val protoreflect.Value - } - var exts []ext - m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { - if fd.IsExtension() { - exts = append(exts, ext{fd, v}) - } - return true - }) - sort.Slice(exts, func(i, j int) bool { - return exts[i].desc.Number() < exts[j].desc.Number() - }) - - for _, ext := range exts { - // For message set, use the name of the message as the extension name. - name := string(ext.desc.FullName()) - if isMessageSet(ext.desc.ContainingMessage()) { - name = strings.TrimSuffix(name, ".message_set_extension") - } - - if !ext.desc.IsList() { - if err := w.writeSingularExtension(name, ext.val, ext.desc); err != nil { - return err - } - } else { - lv := ext.val.List() - for i := 0; i < lv.Len(); i++ { - if err := w.writeSingularExtension(name, lv.Get(i), ext.desc); err != nil { - return err - } - } - } - } - return nil -} - -func (w *textWriter) writeSingularExtension(name string, v protoreflect.Value, fd protoreflect.FieldDescriptor) error { - fmt.Fprintf(w, "[%s]:", name) - if !w.compact { - w.WriteByte(' ') - } - if err := w.writeSingularValue(v, fd); err != nil { - return err - } - w.WriteByte('\n') - return nil -} - -func (w *textWriter) writeIndent() { - if !w.complete { - return - } - for i := 0; i < w.indent*2; i++ { - w.buf = append(w.buf, ' ') - } - w.complete = false -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/wire.go b/go-controller/vendor/github.com/golang/protobuf/proto/wire.go deleted file mode 100644 index d7c28da5a7..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/wire.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -import ( - protoV2 "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/runtime/protoiface" -) - -// Size returns the size in bytes of the wire-format encoding of m. -func Size(m Message) int { - if m == nil { - return 0 - } - mi := MessageV2(m) - return protoV2.Size(mi) -} - -// Marshal returns the wire-format encoding of m. -func Marshal(m Message) ([]byte, error) { - b, err := marshalAppend(nil, m, false) - if b == nil { - b = zeroBytes - } - return b, err -} - -var zeroBytes = make([]byte, 0, 0) - -func marshalAppend(buf []byte, m Message, deterministic bool) ([]byte, error) { - if m == nil { - return nil, ErrNil - } - mi := MessageV2(m) - nbuf, err := protoV2.MarshalOptions{ - Deterministic: deterministic, - AllowPartial: true, - }.MarshalAppend(buf, mi) - if err != nil { - return buf, err - } - if len(buf) == len(nbuf) { - if !mi.ProtoReflect().IsValid() { - return buf, ErrNil - } - } - return nbuf, checkRequiredNotSet(mi) -} - -// Unmarshal parses a wire-format message in b and places the decoded results in m. -// -// Unmarshal resets m before starting to unmarshal, so any existing data in m is always -// removed. Use UnmarshalMerge to preserve and append to existing data. -func Unmarshal(b []byte, m Message) error { - m.Reset() - return UnmarshalMerge(b, m) -} - -// UnmarshalMerge parses a wire-format message in b and places the decoded results in m. -func UnmarshalMerge(b []byte, m Message) error { - mi := MessageV2(m) - out, err := protoV2.UnmarshalOptions{ - AllowPartial: true, - Merge: true, - }.UnmarshalState(protoiface.UnmarshalInput{ - Buf: b, - Message: mi.ProtoReflect(), - }) - if err != nil { - return err - } - if out.Flags&protoiface.UnmarshalInitialized > 0 { - return nil - } - return checkRequiredNotSet(mi) -} diff --git a/go-controller/vendor/github.com/golang/protobuf/proto/wrappers.go b/go-controller/vendor/github.com/golang/protobuf/proto/wrappers.go deleted file mode 100644 index 398e348599..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/proto/wrappers.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proto - -// Bool stores v in a new bool value and returns a pointer to it. -func Bool(v bool) *bool { return &v } - -// Int stores v in a new int32 value and returns a pointer to it. -// -// Deprecated: Use Int32 instead. -func Int(v int) *int32 { return Int32(int32(v)) } - -// Int32 stores v in a new int32 value and returns a pointer to it. -func Int32(v int32) *int32 { return &v } - -// Int64 stores v in a new int64 value and returns a pointer to it. -func Int64(v int64) *int64 { return &v } - -// Uint32 stores v in a new uint32 value and returns a pointer to it. -func Uint32(v uint32) *uint32 { return &v } - -// Uint64 stores v in a new uint64 value and returns a pointer to it. -func Uint64(v uint64) *uint64 { return &v } - -// Float32 stores v in a new float32 value and returns a pointer to it. -func Float32(v float32) *float32 { return &v } - -// Float64 stores v in a new float64 value and returns a pointer to it. -func Float64(v float64) *float64 { return &v } - -// String stores v in a new string value and returns a pointer to it. -func String(v string) *string { return &v } diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/any.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/any.go deleted file mode 100644 index fdff3fdb4c..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/any.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ptypes - -import ( - "fmt" - "strings" - - "github.com/golang/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - - anypb "github.com/golang/protobuf/ptypes/any" -) - -const urlPrefix = "type.googleapis.com/" - -// AnyMessageName returns the message name contained in an anypb.Any message. -// Most type assertions should use the Is function instead. -// -// Deprecated: Call the any.MessageName method instead. -func AnyMessageName(any *anypb.Any) (string, error) { - name, err := anyMessageName(any) - return string(name), err -} -func anyMessageName(any *anypb.Any) (protoreflect.FullName, error) { - if any == nil { - return "", fmt.Errorf("message is nil") - } - name := protoreflect.FullName(any.TypeUrl) - if i := strings.LastIndex(any.TypeUrl, "/"); i >= 0 { - name = name[i+len("/"):] - } - if !name.IsValid() { - return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl) - } - return name, nil -} - -// MarshalAny marshals the given message m into an anypb.Any message. -// -// Deprecated: Call the anypb.New function instead. -func MarshalAny(m proto.Message) (*anypb.Any, error) { - switch dm := m.(type) { - case DynamicAny: - m = dm.Message - case *DynamicAny: - if dm == nil { - return nil, proto.ErrNil - } - m = dm.Message - } - b, err := proto.Marshal(m) - if err != nil { - return nil, err - } - return &anypb.Any{TypeUrl: urlPrefix + proto.MessageName(m), Value: b}, nil -} - -// Empty returns a new message of the type specified in an anypb.Any message. -// It returns protoregistry.NotFound if the corresponding message type could not -// be resolved in the global registry. -// -// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead -// to resolve the message name and create a new instance of it. -func Empty(any *anypb.Any) (proto.Message, error) { - name, err := anyMessageName(any) - if err != nil { - return nil, err - } - mt, err := protoregistry.GlobalTypes.FindMessageByName(name) - if err != nil { - return nil, err - } - return proto.MessageV1(mt.New().Interface()), nil -} - -// UnmarshalAny unmarshals the encoded value contained in the anypb.Any message -// into the provided message m. It returns an error if the target message -// does not match the type in the Any message or if an unmarshal error occurs. -// -// The target message m may be a *DynamicAny message. If the underlying message -// type could not be resolved, then this returns protoregistry.NotFound. -// -// Deprecated: Call the any.UnmarshalTo method instead. -func UnmarshalAny(any *anypb.Any, m proto.Message) error { - if dm, ok := m.(*DynamicAny); ok { - if dm.Message == nil { - var err error - dm.Message, err = Empty(any) - if err != nil { - return err - } - } - m = dm.Message - } - - anyName, err := AnyMessageName(any) - if err != nil { - return err - } - msgName := proto.MessageName(m) - if anyName != msgName { - return fmt.Errorf("mismatched message type: got %q want %q", anyName, msgName) - } - return proto.Unmarshal(any.Value, m) -} - -// Is reports whether the Any message contains a message of the specified type. -// -// Deprecated: Call the any.MessageIs method instead. -func Is(any *anypb.Any, m proto.Message) bool { - if any == nil || m == nil { - return false - } - name := proto.MessageName(m) - if !strings.HasSuffix(any.TypeUrl, name) { - return false - } - return len(any.TypeUrl) == len(name) || any.TypeUrl[len(any.TypeUrl)-len(name)-1] == '/' -} - -// DynamicAny is a value that can be passed to UnmarshalAny to automatically -// allocate a proto.Message for the type specified in an anypb.Any message. -// The allocated message is stored in the embedded proto.Message. -// -// Example: -// -// var x ptypes.DynamicAny -// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... } -// fmt.Printf("unmarshaled message: %v", x.Message) -// -// Deprecated: Use the any.UnmarshalNew method instead to unmarshal -// the any message contents into a new instance of the underlying message. -type DynamicAny struct{ proto.Message } - -func (m DynamicAny) String() string { - if m.Message == nil { - return "" - } - return m.Message.String() -} -func (m DynamicAny) Reset() { - if m.Message == nil { - return - } - m.Message.Reset() -} -func (m DynamicAny) ProtoMessage() { - return -} -func (m DynamicAny) ProtoReflect() protoreflect.Message { - if m.Message == nil { - return nil - } - return dynamicAny{proto.MessageReflect(m.Message)} -} - -type dynamicAny struct{ protoreflect.Message } - -func (m dynamicAny) Type() protoreflect.MessageType { - return dynamicAnyType{m.Message.Type()} -} -func (m dynamicAny) New() protoreflect.Message { - return dynamicAnyType{m.Message.Type()}.New() -} -func (m dynamicAny) Interface() protoreflect.ProtoMessage { - return DynamicAny{proto.MessageV1(m.Message.Interface())} -} - -type dynamicAnyType struct{ protoreflect.MessageType } - -func (t dynamicAnyType) New() protoreflect.Message { - return dynamicAny{t.MessageType.New()} -} -func (t dynamicAnyType) Zero() protoreflect.Message { - return dynamicAny{t.MessageType.Zero()} -} diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go deleted file mode 100644 index 0ef27d33de..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: github.com/golang/protobuf/ptypes/any/any.proto - -package any - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - anypb "google.golang.org/protobuf/types/known/anypb" - reflect "reflect" -) - -// Symbols defined in public import of google/protobuf/any.proto. - -type Any = anypb.Any - -var File_github_com_golang_protobuf_ptypes_any_any_proto protoreflect.FileDescriptor - -var file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = []byte{ - 0x0a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, - 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x2b, 0x5a, 0x29, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, - 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x3b, 0x61, 0x6e, 0x79, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = []interface{}{} -var file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_github_com_golang_protobuf_ptypes_any_any_proto_init() } -func file_github_com_golang_protobuf_ptypes_any_any_proto_init() { - if File_github_com_golang_protobuf_ptypes_any_any_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc, - NumEnums: 0, - NumMessages: 0, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes, - DependencyIndexes: file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs, - }.Build() - File_github_com_golang_protobuf_ptypes_any_any_proto = out.File - file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = nil - file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = nil - file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = nil -} diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/doc.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/doc.go deleted file mode 100644 index d3c33259d2..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package ptypes provides functionality for interacting with well-known types. -// -// Deprecated: Well-known types have specialized functionality directly -// injected into the generated packages for each message type. -// See the deprecation notice for each function for the suggested alternative. -package ptypes diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/duration.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/duration.go deleted file mode 100644 index b2b55dd851..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/duration.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ptypes - -import ( - "errors" - "fmt" - "time" - - durationpb "github.com/golang/protobuf/ptypes/duration" -) - -// Range of google.protobuf.Duration as specified in duration.proto. -// This is about 10,000 years in seconds. -const ( - maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60) - minSeconds = -maxSeconds -) - -// Duration converts a durationpb.Duration to a time.Duration. -// Duration returns an error if dur is invalid or overflows a time.Duration. -// -// Deprecated: Call the dur.AsDuration and dur.CheckValid methods instead. -func Duration(dur *durationpb.Duration) (time.Duration, error) { - if err := validateDuration(dur); err != nil { - return 0, err - } - d := time.Duration(dur.Seconds) * time.Second - if int64(d/time.Second) != dur.Seconds { - return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur) - } - if dur.Nanos != 0 { - d += time.Duration(dur.Nanos) * time.Nanosecond - if (d < 0) != (dur.Nanos < 0) { - return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur) - } - } - return d, nil -} - -// DurationProto converts a time.Duration to a durationpb.Duration. -// -// Deprecated: Call the durationpb.New function instead. -func DurationProto(d time.Duration) *durationpb.Duration { - nanos := d.Nanoseconds() - secs := nanos / 1e9 - nanos -= secs * 1e9 - return &durationpb.Duration{ - Seconds: int64(secs), - Nanos: int32(nanos), - } -} - -// validateDuration determines whether the durationpb.Duration is valid -// according to the definition in google/protobuf/duration.proto. -// A valid durpb.Duration may still be too large to fit into a time.Duration -// Note that the range of durationpb.Duration is about 10,000 years, -// while the range of time.Duration is about 290 years. -func validateDuration(dur *durationpb.Duration) error { - if dur == nil { - return errors.New("duration: nil Duration") - } - if dur.Seconds < minSeconds || dur.Seconds > maxSeconds { - return fmt.Errorf("duration: %v: seconds out of range", dur) - } - if dur.Nanos <= -1e9 || dur.Nanos >= 1e9 { - return fmt.Errorf("duration: %v: nanos out of range", dur) - } - // Seconds and Nanos must have the same sign, unless d.Nanos is zero. - if (dur.Seconds < 0 && dur.Nanos > 0) || (dur.Seconds > 0 && dur.Nanos < 0) { - return fmt.Errorf("duration: %v: seconds and nanos have different signs", dur) - } - return nil -} diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/duration/duration.pb.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/duration/duration.pb.go deleted file mode 100644 index d0079ee3ef..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/duration/duration.pb.go +++ /dev/null @@ -1,63 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: github.com/golang/protobuf/ptypes/duration/duration.proto - -package duration - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" - reflect "reflect" -) - -// Symbols defined in public import of google/protobuf/duration.proto. - -type Duration = durationpb.Duration - -var File_github_com_golang_protobuf_ptypes_duration_duration_proto protoreflect.FileDescriptor - -var file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = []byte{ - 0x0a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, - 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x35, 0x5a, 0x33, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = []interface{}{} -var file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() } -func file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() { - if File_github_com_golang_protobuf_ptypes_duration_duration_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc, - NumEnums: 0, - NumMessages: 0, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes, - DependencyIndexes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs, - }.Build() - File_github_com_golang_protobuf_ptypes_duration_duration_proto = out.File - file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = nil - file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = nil - file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = nil -} diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp.go deleted file mode 100644 index 8368a3f70d..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ptypes - -import ( - "errors" - "fmt" - "time" - - timestamppb "github.com/golang/protobuf/ptypes/timestamp" -) - -// Range of google.protobuf.Duration as specified in timestamp.proto. -const ( - // Seconds field of the earliest valid Timestamp. - // This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix(). - minValidSeconds = -62135596800 - // Seconds field just after the latest valid Timestamp. - // This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix(). - maxValidSeconds = 253402300800 -) - -// Timestamp converts a timestamppb.Timestamp to a time.Time. -// It returns an error if the argument is invalid. -// -// Unlike most Go functions, if Timestamp returns an error, the first return -// value is not the zero time.Time. Instead, it is the value obtained from the -// time.Unix function when passed the contents of the Timestamp, in the UTC -// locale. This may or may not be a meaningful time; many invalid Timestamps -// do map to valid time.Times. -// -// A nil Timestamp returns an error. The first return value in that case is -// undefined. -// -// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead. -func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) { - // Don't return the zero value on error, because corresponds to a valid - // timestamp. Instead return whatever time.Unix gives us. - var t time.Time - if ts == nil { - t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp - } else { - t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC() - } - return t, validateTimestamp(ts) -} - -// TimestampNow returns a google.protobuf.Timestamp for the current time. -// -// Deprecated: Call the timestamppb.Now function instead. -func TimestampNow() *timestamppb.Timestamp { - ts, err := TimestampProto(time.Now()) - if err != nil { - panic("ptypes: time.Now() out of Timestamp range") - } - return ts -} - -// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto. -// It returns an error if the resulting Timestamp is invalid. -// -// Deprecated: Call the timestamppb.New function instead. -func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) { - ts := ×tamppb.Timestamp{ - Seconds: t.Unix(), - Nanos: int32(t.Nanosecond()), - } - if err := validateTimestamp(ts); err != nil { - return nil, err - } - return ts, nil -} - -// TimestampString returns the RFC 3339 string for valid Timestamps. -// For invalid Timestamps, it returns an error message in parentheses. -// -// Deprecated: Call the ts.AsTime method instead, -// followed by a call to the Format method on the time.Time value. -func TimestampString(ts *timestamppb.Timestamp) string { - t, err := Timestamp(ts) - if err != nil { - return fmt.Sprintf("(%v)", err) - } - return t.Format(time.RFC3339Nano) -} - -// validateTimestamp determines whether a Timestamp is valid. -// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01) -// and has a Nanos field in the range [0, 1e9). -// -// If the Timestamp is valid, validateTimestamp returns nil. -// Otherwise, it returns an error that describes the problem. -// -// Every valid Timestamp can be represented by a time.Time, -// but the converse is not true. -func validateTimestamp(ts *timestamppb.Timestamp) error { - if ts == nil { - return errors.New("timestamp: nil Timestamp") - } - if ts.Seconds < minValidSeconds { - return fmt.Errorf("timestamp: %v before 0001-01-01", ts) - } - if ts.Seconds >= maxValidSeconds { - return fmt.Errorf("timestamp: %v after 10000-01-01", ts) - } - if ts.Nanos < 0 || ts.Nanos >= 1e9 { - return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts) - } - return nil -} diff --git a/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go b/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go deleted file mode 100644 index a76f807600..0000000000 --- a/go-controller/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go +++ /dev/null @@ -1,64 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: github.com/golang/protobuf/ptypes/timestamp/timestamp.proto - -package timestamp - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" -) - -// Symbols defined in public import of google/protobuf/timestamp.proto. - -type Timestamp = timestamppb.Timestamp - -var File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto protoreflect.FileDescriptor - -var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc = []byte{ - 0x0a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, - 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x37, - 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, - 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x3b, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes = []interface{}{} -var file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_init() } -func file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_init() { - if File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc, - NumEnums: 0, - NumMessages: 0, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes, - DependencyIndexes: file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs, - }.Build() - File_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto = out.File - file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_rawDesc = nil - file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_goTypes = nil - file_github_com_golang_protobuf_ptypes_timestamp_timestamp_proto_depIdxs = nil -} diff --git a/go-controller/vendor/github.com/google/gnostic-models/compiler/extensions.go b/go-controller/vendor/github.com/google/gnostic-models/compiler/extensions.go index 250c81e8c8..16ae66faa3 100644 --- a/go-controller/vendor/github.com/google/gnostic-models/compiler/extensions.go +++ b/go-controller/vendor/github.com/google/gnostic-models/compiler/extensions.go @@ -20,8 +20,8 @@ import ( "os/exec" "strings" - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/any" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" yaml "gopkg.in/yaml.v3" extensions "github.com/google/gnostic-models/extensions" @@ -33,7 +33,7 @@ type ExtensionHandler struct { } // CallExtension calls a binary extension handler. -func CallExtension(context *Context, in *yaml.Node, extensionName string) (handled bool, response *any.Any, err error) { +func CallExtension(context *Context, in *yaml.Node, extensionName string) (handled bool, response *anypb.Any, err error) { if context == nil || context.ExtensionHandlers == nil { return false, nil, nil } @@ -50,7 +50,7 @@ func CallExtension(context *Context, in *yaml.Node, extensionName string) (handl return handled, response, err } -func (extensionHandlers *ExtensionHandler) handle(in *yaml.Node, extensionName string) (*any.Any, error) { +func (extensionHandlers *ExtensionHandler) handle(in *yaml.Node, extensionName string) (*anypb.Any, error) { if extensionHandlers.Name != "" { yamlData, _ := yaml.Marshal(in) request := &extensions.ExtensionHandlerRequest{ diff --git a/go-controller/vendor/github.com/google/gnostic-models/extensions/extension.pb.go b/go-controller/vendor/github.com/google/gnostic-models/extensions/extension.pb.go index a71df8abec..16c40d985f 100644 --- a/go-controller/vendor/github.com/google/gnostic-models/extensions/extension.pb.go +++ b/go-controller/vendor/github.com/google/gnostic-models/extensions/extension.pb.go @@ -14,8 +14,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.3 +// protoc-gen-go v1.35.1 +// protoc v4.23.4 // source: extensions/extension.proto package gnostic_extension_v1 @@ -51,11 +51,9 @@ type Version struct { func (x *Version) Reset() { *x = Version{} - if protoimpl.UnsafeEnabled { - mi := &file_extensions_extension_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_extensions_extension_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Version) String() string { @@ -66,7 +64,7 @@ func (*Version) ProtoMessage() {} func (x *Version) ProtoReflect() protoreflect.Message { mi := &file_extensions_extension_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -123,11 +121,9 @@ type ExtensionHandlerRequest struct { func (x *ExtensionHandlerRequest) Reset() { *x = ExtensionHandlerRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_extensions_extension_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_extensions_extension_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExtensionHandlerRequest) String() string { @@ -138,7 +134,7 @@ func (*ExtensionHandlerRequest) ProtoMessage() {} func (x *ExtensionHandlerRequest) ProtoReflect() protoreflect.Message { mi := &file_extensions_extension_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -191,11 +187,9 @@ type ExtensionHandlerResponse struct { func (x *ExtensionHandlerResponse) Reset() { *x = ExtensionHandlerResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_extensions_extension_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_extensions_extension_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExtensionHandlerResponse) String() string { @@ -206,7 +200,7 @@ func (*ExtensionHandlerResponse) ProtoMessage() {} func (x *ExtensionHandlerResponse) ProtoReflect() protoreflect.Message { mi := &file_extensions_extension_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -257,11 +251,9 @@ type Wrapper struct { func (x *Wrapper) Reset() { *x = Wrapper{} - if protoimpl.UnsafeEnabled { - mi := &file_extensions_extension_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_extensions_extension_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Wrapper) String() string { @@ -272,7 +264,7 @@ func (*Wrapper) ProtoMessage() {} func (x *Wrapper) ProtoReflect() protoreflect.Message { mi := &file_extensions_extension_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -367,7 +359,7 @@ func file_extensions_extension_proto_rawDescGZIP() []byte { } var file_extensions_extension_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_extensions_extension_proto_goTypes = []interface{}{ +var file_extensions_extension_proto_goTypes = []any{ (*Version)(nil), // 0: gnostic.extension.v1.Version (*ExtensionHandlerRequest)(nil), // 1: gnostic.extension.v1.ExtensionHandlerRequest (*ExtensionHandlerResponse)(nil), // 2: gnostic.extension.v1.ExtensionHandlerResponse @@ -390,56 +382,6 @@ func file_extensions_extension_proto_init() { if File_extensions_extension_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_extensions_extension_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Version); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_extensions_extension_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExtensionHandlerRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_extensions_extension_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExtensionHandlerResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_extensions_extension_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Wrapper); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/go-controller/vendor/github.com/google/gnostic-models/extensions/extensions.go b/go-controller/vendor/github.com/google/gnostic-models/extensions/extensions.go index ec8afd0092..0768163e5a 100644 --- a/go-controller/vendor/github.com/google/gnostic-models/extensions/extensions.go +++ b/go-controller/vendor/github.com/google/gnostic-models/extensions/extensions.go @@ -19,8 +19,8 @@ import ( "log" "os" - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" ) type extensionHandler func(name string, yamlInput string) (bool, proto.Message, error) @@ -54,7 +54,7 @@ func Main(handler extensionHandler) { response.Errors = append(response.Errors, err.Error()) } else if handled { response.Handled = true - response.Value, err = ptypes.MarshalAny(output) + response.Value, err = anypb.New(output) if err != nil { response.Errors = append(response.Errors, err.Error()) } diff --git a/go-controller/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.pb.go b/go-controller/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.pb.go index 65c4c913ce..3b930b3de2 100644 --- a/go-controller/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.pb.go +++ b/go-controller/vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.3 +// protoc-gen-go v1.35.1 +// protoc v4.23.4 // source: openapiv2/OpenAPIv2.proto package openapi_v2 @@ -43,6 +43,7 @@ type AdditionalPropertiesItem struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *AdditionalPropertiesItem_Schema // *AdditionalPropertiesItem_Boolean Oneof isAdditionalPropertiesItem_Oneof `protobuf_oneof:"oneof"` @@ -50,11 +51,9 @@ type AdditionalPropertiesItem struct { func (x *AdditionalPropertiesItem) Reset() { *x = AdditionalPropertiesItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AdditionalPropertiesItem) String() string { @@ -65,7 +64,7 @@ func (*AdditionalPropertiesItem) ProtoMessage() {} func (x *AdditionalPropertiesItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -128,11 +127,9 @@ type Any struct { func (x *Any) Reset() { *x = Any{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Any) String() string { @@ -143,7 +140,7 @@ func (*Any) ProtoMessage() {} func (x *Any) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -186,11 +183,9 @@ type ApiKeySecurity struct { func (x *ApiKeySecurity) Reset() { *x = ApiKeySecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ApiKeySecurity) String() string { @@ -201,7 +196,7 @@ func (*ApiKeySecurity) ProtoMessage() {} func (x *ApiKeySecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -263,11 +258,9 @@ type BasicAuthenticationSecurity struct { func (x *BasicAuthenticationSecurity) Reset() { *x = BasicAuthenticationSecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BasicAuthenticationSecurity) String() string { @@ -278,7 +271,7 @@ func (*BasicAuthenticationSecurity) ProtoMessage() {} func (x *BasicAuthenticationSecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -333,11 +326,9 @@ type BodyParameter struct { func (x *BodyParameter) Reset() { *x = BodyParameter{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BodyParameter) String() string { @@ -348,7 +339,7 @@ func (*BodyParameter) ProtoMessage() {} func (x *BodyParameter) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -422,11 +413,9 @@ type Contact struct { func (x *Contact) Reset() { *x = Contact{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Contact) String() string { @@ -437,7 +426,7 @@ func (*Contact) ProtoMessage() {} func (x *Contact) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -490,11 +479,9 @@ type Default struct { func (x *Default) Reset() { *x = Default{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Default) String() string { @@ -505,7 +492,7 @@ func (*Default) ProtoMessage() {} func (x *Default) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -538,11 +525,9 @@ type Definitions struct { func (x *Definitions) Reset() { *x = Definitions{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Definitions) String() string { @@ -553,7 +538,7 @@ func (*Definitions) ProtoMessage() {} func (x *Definitions) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -606,11 +591,9 @@ type Document struct { func (x *Document) Reset() { *x = Document{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Document) String() string { @@ -621,7 +604,7 @@ func (*Document) ProtoMessage() {} func (x *Document) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -758,11 +741,9 @@ type Examples struct { func (x *Examples) Reset() { *x = Examples{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Examples) String() string { @@ -773,7 +754,7 @@ func (*Examples) ProtoMessage() {} func (x *Examples) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -808,11 +789,9 @@ type ExternalDocs struct { func (x *ExternalDocs) Reset() { *x = ExternalDocs{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExternalDocs) String() string { @@ -823,7 +802,7 @@ func (*ExternalDocs) ProtoMessage() {} func (x *ExternalDocs) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -879,11 +858,9 @@ type FileSchema struct { func (x *FileSchema) Reset() { *x = FileSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FileSchema) String() string { @@ -894,7 +871,7 @@ func (*FileSchema) ProtoMessage() {} func (x *FileSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1016,11 +993,9 @@ type FormDataParameterSubSchema struct { func (x *FormDataParameterSubSchema) Reset() { *x = FormDataParameterSubSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FormDataParameterSubSchema) String() string { @@ -1031,7 +1006,7 @@ func (*FormDataParameterSubSchema) ProtoMessage() {} func (x *FormDataParameterSubSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1235,11 +1210,9 @@ type Header struct { func (x *Header) Reset() { *x = Header{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Header) String() string { @@ -1250,7 +1223,7 @@ func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1433,11 +1406,9 @@ type HeaderParameterSubSchema struct { func (x *HeaderParameterSubSchema) Reset() { *x = HeaderParameterSubSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HeaderParameterSubSchema) String() string { @@ -1448,7 +1419,7 @@ func (*HeaderParameterSubSchema) ProtoMessage() {} func (x *HeaderParameterSubSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1627,11 +1598,9 @@ type Headers struct { func (x *Headers) Reset() { *x = Headers{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Headers) String() string { @@ -1642,7 +1611,7 @@ func (*Headers) ProtoMessage() {} func (x *Headers) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1685,11 +1654,9 @@ type Info struct { func (x *Info) Reset() { *x = Info{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Info) String() string { @@ -1700,7 +1667,7 @@ func (*Info) ProtoMessage() {} func (x *Info) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1774,11 +1741,9 @@ type ItemsItem struct { func (x *ItemsItem) Reset() { *x = ItemsItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ItemsItem) String() string { @@ -1789,7 +1754,7 @@ func (*ItemsItem) ProtoMessage() {} func (x *ItemsItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1822,11 +1787,9 @@ type JsonReference struct { func (x *JsonReference) Reset() { *x = JsonReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JsonReference) String() string { @@ -1837,7 +1800,7 @@ func (*JsonReference) ProtoMessage() {} func (x *JsonReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1880,11 +1843,9 @@ type License struct { func (x *License) Reset() { *x = License{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *License) String() string { @@ -1895,7 +1856,7 @@ func (*License) ProtoMessage() {} func (x *License) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1945,11 +1906,9 @@ type NamedAny struct { func (x *NamedAny) Reset() { *x = NamedAny{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedAny) String() string { @@ -1960,7 +1919,7 @@ func (*NamedAny) ProtoMessage() {} func (x *NamedAny) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2003,11 +1962,9 @@ type NamedHeader struct { func (x *NamedHeader) Reset() { *x = NamedHeader{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedHeader) String() string { @@ -2018,7 +1975,7 @@ func (*NamedHeader) ProtoMessage() {} func (x *NamedHeader) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2061,11 +2018,9 @@ type NamedParameter struct { func (x *NamedParameter) Reset() { *x = NamedParameter{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedParameter) String() string { @@ -2076,7 +2031,7 @@ func (*NamedParameter) ProtoMessage() {} func (x *NamedParameter) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2119,11 +2074,9 @@ type NamedPathItem struct { func (x *NamedPathItem) Reset() { *x = NamedPathItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedPathItem) String() string { @@ -2134,7 +2087,7 @@ func (*NamedPathItem) ProtoMessage() {} func (x *NamedPathItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2177,11 +2130,9 @@ type NamedResponse struct { func (x *NamedResponse) Reset() { *x = NamedResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedResponse) String() string { @@ -2192,7 +2143,7 @@ func (*NamedResponse) ProtoMessage() {} func (x *NamedResponse) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2235,11 +2186,9 @@ type NamedResponseValue struct { func (x *NamedResponseValue) Reset() { *x = NamedResponseValue{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedResponseValue) String() string { @@ -2250,7 +2199,7 @@ func (*NamedResponseValue) ProtoMessage() {} func (x *NamedResponseValue) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2293,11 +2242,9 @@ type NamedSchema struct { func (x *NamedSchema) Reset() { *x = NamedSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedSchema) String() string { @@ -2308,7 +2255,7 @@ func (*NamedSchema) ProtoMessage() {} func (x *NamedSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2351,11 +2298,9 @@ type NamedSecurityDefinitionsItem struct { func (x *NamedSecurityDefinitionsItem) Reset() { *x = NamedSecurityDefinitionsItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedSecurityDefinitionsItem) String() string { @@ -2366,7 +2311,7 @@ func (*NamedSecurityDefinitionsItem) ProtoMessage() {} func (x *NamedSecurityDefinitionsItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2409,11 +2354,9 @@ type NamedString struct { func (x *NamedString) Reset() { *x = NamedString{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedString) String() string { @@ -2424,7 +2367,7 @@ func (*NamedString) ProtoMessage() {} func (x *NamedString) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2467,11 +2410,9 @@ type NamedStringArray struct { func (x *NamedStringArray) Reset() { *x = NamedStringArray{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedStringArray) String() string { @@ -2482,7 +2423,7 @@ func (*NamedStringArray) ProtoMessage() {} func (x *NamedStringArray) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2517,6 +2458,7 @@ type NonBodyParameter struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *NonBodyParameter_HeaderParameterSubSchema // *NonBodyParameter_FormDataParameterSubSchema // *NonBodyParameter_QueryParameterSubSchema @@ -2526,11 +2468,9 @@ type NonBodyParameter struct { func (x *NonBodyParameter) Reset() { *x = NonBodyParameter{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NonBodyParameter) String() string { @@ -2541,7 +2481,7 @@ func (*NonBodyParameter) ProtoMessage() {} func (x *NonBodyParameter) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2635,11 +2575,9 @@ type Oauth2AccessCodeSecurity struct { func (x *Oauth2AccessCodeSecurity) Reset() { *x = Oauth2AccessCodeSecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Oauth2AccessCodeSecurity) String() string { @@ -2650,7 +2588,7 @@ func (*Oauth2AccessCodeSecurity) ProtoMessage() {} func (x *Oauth2AccessCodeSecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2729,11 +2667,9 @@ type Oauth2ApplicationSecurity struct { func (x *Oauth2ApplicationSecurity) Reset() { *x = Oauth2ApplicationSecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Oauth2ApplicationSecurity) String() string { @@ -2744,7 +2680,7 @@ func (*Oauth2ApplicationSecurity) ProtoMessage() {} func (x *Oauth2ApplicationSecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2816,11 +2752,9 @@ type Oauth2ImplicitSecurity struct { func (x *Oauth2ImplicitSecurity) Reset() { *x = Oauth2ImplicitSecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Oauth2ImplicitSecurity) String() string { @@ -2831,7 +2765,7 @@ func (*Oauth2ImplicitSecurity) ProtoMessage() {} func (x *Oauth2ImplicitSecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2903,11 +2837,9 @@ type Oauth2PasswordSecurity struct { func (x *Oauth2PasswordSecurity) Reset() { *x = Oauth2PasswordSecurity{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Oauth2PasswordSecurity) String() string { @@ -2918,7 +2850,7 @@ func (*Oauth2PasswordSecurity) ProtoMessage() {} func (x *Oauth2PasswordSecurity) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2985,11 +2917,9 @@ type Oauth2Scopes struct { func (x *Oauth2Scopes) Reset() { *x = Oauth2Scopes{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Oauth2Scopes) String() string { @@ -3000,7 +2930,7 @@ func (*Oauth2Scopes) ProtoMessage() {} func (x *Oauth2Scopes) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3051,11 +2981,9 @@ type Operation struct { func (x *Operation) Reset() { *x = Operation{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Operation) String() string { @@ -3066,7 +2994,7 @@ func (*Operation) ProtoMessage() {} func (x *Operation) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3178,6 +3106,7 @@ type Parameter struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *Parameter_BodyParameter // *Parameter_NonBodyParameter Oneof isParameter_Oneof `protobuf_oneof:"oneof"` @@ -3185,11 +3114,9 @@ type Parameter struct { func (x *Parameter) Reset() { *x = Parameter{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Parameter) String() string { @@ -3200,7 +3127,7 @@ func (*Parameter) ProtoMessage() {} func (x *Parameter) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[37] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3263,11 +3190,9 @@ type ParameterDefinitions struct { func (x *ParameterDefinitions) Reset() { *x = ParameterDefinitions{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ParameterDefinitions) String() string { @@ -3278,7 +3203,7 @@ func (*ParameterDefinitions) ProtoMessage() {} func (x *ParameterDefinitions) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[38] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3306,6 +3231,7 @@ type ParametersItem struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *ParametersItem_Parameter // *ParametersItem_JsonReference Oneof isParametersItem_Oneof `protobuf_oneof:"oneof"` @@ -3313,11 +3239,9 @@ type ParametersItem struct { func (x *ParametersItem) Reset() { *x = ParametersItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ParametersItem) String() string { @@ -3328,7 +3252,7 @@ func (*ParametersItem) ProtoMessage() {} func (x *ParametersItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[39] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3400,11 +3324,9 @@ type PathItem struct { func (x *PathItem) Reset() { *x = PathItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PathItem) String() string { @@ -3415,7 +3337,7 @@ func (*PathItem) ProtoMessage() {} func (x *PathItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[40] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3535,11 +3457,9 @@ type PathParameterSubSchema struct { func (x *PathParameterSubSchema) Reset() { *x = PathParameterSubSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PathParameterSubSchema) String() string { @@ -3550,7 +3470,7 @@ func (*PathParameterSubSchema) ProtoMessage() {} func (x *PathParameterSubSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[41] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3731,11 +3651,9 @@ type Paths struct { func (x *Paths) Reset() { *x = Paths{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Paths) String() string { @@ -3746,7 +3664,7 @@ func (*Paths) ProtoMessage() {} func (x *Paths) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[42] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3802,11 +3720,9 @@ type PrimitivesItems struct { func (x *PrimitivesItems) Reset() { *x = PrimitivesItems{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PrimitivesItems) String() string { @@ -3817,7 +3733,7 @@ func (*PrimitivesItems) ProtoMessage() {} func (x *PrimitivesItems) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[43] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3968,11 +3884,9 @@ type Properties struct { func (x *Properties) Reset() { *x = Properties{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Properties) String() string { @@ -3983,7 +3897,7 @@ func (*Properties) ProtoMessage() {} func (x *Properties) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[44] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4042,11 +3956,9 @@ type QueryParameterSubSchema struct { func (x *QueryParameterSubSchema) Reset() { *x = QueryParameterSubSchema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *QueryParameterSubSchema) String() string { @@ -4057,7 +3969,7 @@ func (*QueryParameterSubSchema) ProtoMessage() {} func (x *QueryParameterSubSchema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[45] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4247,11 +4159,9 @@ type Response struct { func (x *Response) Reset() { *x = Response{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Response) String() string { @@ -4262,7 +4172,7 @@ func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[46] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4323,11 +4233,9 @@ type ResponseDefinitions struct { func (x *ResponseDefinitions) Reset() { *x = ResponseDefinitions{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[47] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResponseDefinitions) String() string { @@ -4338,7 +4246,7 @@ func (*ResponseDefinitions) ProtoMessage() {} func (x *ResponseDefinitions) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[47] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4366,6 +4274,7 @@ type ResponseValue struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *ResponseValue_Response // *ResponseValue_JsonReference Oneof isResponseValue_Oneof `protobuf_oneof:"oneof"` @@ -4373,11 +4282,9 @@ type ResponseValue struct { func (x *ResponseValue) Reset() { *x = ResponseValue{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[48] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResponseValue) String() string { @@ -4388,7 +4295,7 @@ func (*ResponseValue) ProtoMessage() {} func (x *ResponseValue) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[48] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4452,11 +4359,9 @@ type Responses struct { func (x *Responses) Reset() { *x = Responses{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[49] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Responses) String() string { @@ -4467,7 +4372,7 @@ func (*Responses) ProtoMessage() {} func (x *Responses) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[49] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4537,11 +4442,9 @@ type Schema struct { func (x *Schema) Reset() { *x = Schema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[50] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Schema) String() string { @@ -4552,7 +4455,7 @@ func (*Schema) ProtoMessage() {} func (x *Schema) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[50] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4790,6 +4693,7 @@ type SchemaItem struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *SchemaItem_Schema // *SchemaItem_FileSchema Oneof isSchemaItem_Oneof `protobuf_oneof:"oneof"` @@ -4797,11 +4701,9 @@ type SchemaItem struct { func (x *SchemaItem) Reset() { *x = SchemaItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[51] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SchemaItem) String() string { @@ -4812,7 +4714,7 @@ func (*SchemaItem) ProtoMessage() {} func (x *SchemaItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[51] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4874,11 +4776,9 @@ type SecurityDefinitions struct { func (x *SecurityDefinitions) Reset() { *x = SecurityDefinitions{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[52] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecurityDefinitions) String() string { @@ -4889,7 +4789,7 @@ func (*SecurityDefinitions) ProtoMessage() {} func (x *SecurityDefinitions) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[52] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4917,6 +4817,7 @@ type SecurityDefinitionsItem struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *SecurityDefinitionsItem_BasicAuthenticationSecurity // *SecurityDefinitionsItem_ApiKeySecurity // *SecurityDefinitionsItem_Oauth2ImplicitSecurity @@ -4928,11 +4829,9 @@ type SecurityDefinitionsItem struct { func (x *SecurityDefinitionsItem) Reset() { *x = SecurityDefinitionsItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[53] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecurityDefinitionsItem) String() string { @@ -4943,7 +4842,7 @@ func (*SecurityDefinitionsItem) ProtoMessage() {} func (x *SecurityDefinitionsItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[53] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5057,11 +4956,9 @@ type SecurityRequirement struct { func (x *SecurityRequirement) Reset() { *x = SecurityRequirement{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[54] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecurityRequirement) String() string { @@ -5072,7 +4969,7 @@ func (*SecurityRequirement) ProtoMessage() {} func (x *SecurityRequirement) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[54] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5104,11 +5001,9 @@ type StringArray struct { func (x *StringArray) Reset() { *x = StringArray{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[55] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *StringArray) String() string { @@ -5119,7 +5014,7 @@ func (*StringArray) ProtoMessage() {} func (x *StringArray) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[55] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5154,11 +5049,9 @@ type Tag struct { func (x *Tag) Reset() { *x = Tag{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[56] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Tag) String() string { @@ -5169,7 +5062,7 @@ func (*Tag) ProtoMessage() {} func (x *Tag) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[56] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5222,11 +5115,9 @@ type TypeItem struct { func (x *TypeItem) Reset() { *x = TypeItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[57] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TypeItem) String() string { @@ -5237,7 +5128,7 @@ func (*TypeItem) ProtoMessage() {} func (x *TypeItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[57] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5270,11 +5161,9 @@ type VendorExtension struct { func (x *VendorExtension) Reset() { *x = VendorExtension{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[58] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *VendorExtension) String() string { @@ -5285,7 +5174,7 @@ func (*VendorExtension) ProtoMessage() {} func (x *VendorExtension) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[58] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5322,11 +5211,9 @@ type Xml struct { func (x *Xml) Reset() { *x = Xml{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[59] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Xml) String() string { @@ -5337,7 +5224,7 @@ func (*Xml) ProtoMessage() {} func (x *Xml) ProtoReflect() protoreflect.Message { mi := &file_openapiv2_OpenAPIv2_proto_msgTypes[59] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6356,7 +6243,7 @@ func file_openapiv2_OpenAPIv2_proto_rawDescGZIP() []byte { } var file_openapiv2_OpenAPIv2_proto_msgTypes = make([]protoimpl.MessageInfo, 60) -var file_openapiv2_OpenAPIv2_proto_goTypes = []interface{}{ +var file_openapiv2_OpenAPIv2_proto_goTypes = []any{ (*AdditionalPropertiesItem)(nil), // 0: openapi.v2.AdditionalPropertiesItem (*Any)(nil), // 1: openapi.v2.Any (*ApiKeySecurity)(nil), // 2: openapi.v2.ApiKeySecurity @@ -6565,755 +6452,33 @@ func file_openapiv2_OpenAPIv2_proto_init() { if File_openapiv2_OpenAPIv2_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_openapiv2_OpenAPIv2_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AdditionalPropertiesItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Any); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApiKeySecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BasicAuthenticationSecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BodyParameter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Contact); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Default); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Definitions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Document); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Examples); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExternalDocs); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FileSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FormDataParameterSubSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Header); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HeaderParameterSubSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Headers); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ItemsItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JsonReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*License); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedAny); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedHeader); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedParameter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedPathItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedResponseValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedSecurityDefinitionsItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedString); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedStringArray); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NonBodyParameter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Oauth2AccessCodeSecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Oauth2ApplicationSecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Oauth2ImplicitSecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Oauth2PasswordSecurity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Oauth2Scopes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Operation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parameter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParameterDefinitions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParametersItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PathItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PathParameterSubSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Paths); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PrimitivesItems); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Properties); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryParameterSubSchema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Response); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResponseDefinitions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResponseValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Responses); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Schema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SchemaItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecurityDefinitions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecurityDefinitionsItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecurityRequirement); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StringArray); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Tag); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TypeItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VendorExtension); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Xml); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_openapiv2_OpenAPIv2_proto_msgTypes[0].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[0].OneofWrappers = []any{ (*AdditionalPropertiesItem_Schema)(nil), (*AdditionalPropertiesItem_Boolean)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[30].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[30].OneofWrappers = []any{ (*NonBodyParameter_HeaderParameterSubSchema)(nil), (*NonBodyParameter_FormDataParameterSubSchema)(nil), (*NonBodyParameter_QueryParameterSubSchema)(nil), (*NonBodyParameter_PathParameterSubSchema)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[37].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[37].OneofWrappers = []any{ (*Parameter_BodyParameter)(nil), (*Parameter_NonBodyParameter)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[39].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[39].OneofWrappers = []any{ (*ParametersItem_Parameter)(nil), (*ParametersItem_JsonReference)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[48].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[48].OneofWrappers = []any{ (*ResponseValue_Response)(nil), (*ResponseValue_JsonReference)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[51].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[51].OneofWrappers = []any{ (*SchemaItem_Schema)(nil), (*SchemaItem_FileSchema)(nil), } - file_openapiv2_OpenAPIv2_proto_msgTypes[53].OneofWrappers = []interface{}{ + file_openapiv2_OpenAPIv2_proto_msgTypes[53].OneofWrappers = []any{ (*SecurityDefinitionsItem_BasicAuthenticationSecurity)(nil), (*SecurityDefinitionsItem_ApiKeySecurity)(nil), (*SecurityDefinitionsItem_Oauth2ImplicitSecurity)(nil), diff --git a/go-controller/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.pb.go b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.pb.go index 945b8d11ff..b9df95a379 100644 --- a/go-controller/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.pb.go +++ b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.3 +// protoc-gen-go v1.35.1 +// protoc v4.23.4 // source: openapiv3/OpenAPIv3.proto package openapi_v3 @@ -43,6 +43,7 @@ type AdditionalPropertiesItem struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *AdditionalPropertiesItem_SchemaOrReference // *AdditionalPropertiesItem_Boolean Oneof isAdditionalPropertiesItem_Oneof `protobuf_oneof:"oneof"` @@ -50,11 +51,9 @@ type AdditionalPropertiesItem struct { func (x *AdditionalPropertiesItem) Reset() { *x = AdditionalPropertiesItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AdditionalPropertiesItem) String() string { @@ -65,7 +64,7 @@ func (*AdditionalPropertiesItem) ProtoMessage() {} func (x *AdditionalPropertiesItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -128,11 +127,9 @@ type Any struct { func (x *Any) Reset() { *x = Any{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Any) String() string { @@ -143,7 +140,7 @@ func (*Any) ProtoMessage() {} func (x *Any) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -178,6 +175,7 @@ type AnyOrExpression struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *AnyOrExpression_Any // *AnyOrExpression_Expression Oneof isAnyOrExpression_Oneof `protobuf_oneof:"oneof"` @@ -185,11 +183,9 @@ type AnyOrExpression struct { func (x *AnyOrExpression) Reset() { *x = AnyOrExpression{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AnyOrExpression) String() string { @@ -200,7 +196,7 @@ func (*AnyOrExpression) ProtoMessage() {} func (x *AnyOrExpression) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -264,11 +260,9 @@ type Callback struct { func (x *Callback) Reset() { *x = Callback{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Callback) String() string { @@ -279,7 +273,7 @@ func (*Callback) ProtoMessage() {} func (x *Callback) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -314,6 +308,7 @@ type CallbackOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *CallbackOrReference_Callback // *CallbackOrReference_Reference Oneof isCallbackOrReference_Oneof `protobuf_oneof:"oneof"` @@ -321,11 +316,9 @@ type CallbackOrReference struct { func (x *CallbackOrReference) Reset() { *x = CallbackOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CallbackOrReference) String() string { @@ -336,7 +329,7 @@ func (*CallbackOrReference) ProtoMessage() {} func (x *CallbackOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -398,11 +391,9 @@ type CallbacksOrReferences struct { func (x *CallbacksOrReferences) Reset() { *x = CallbacksOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CallbacksOrReferences) String() string { @@ -413,7 +404,7 @@ func (*CallbacksOrReferences) ProtoMessage() {} func (x *CallbacksOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -455,11 +446,9 @@ type Components struct { func (x *Components) Reset() { *x = Components{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Components) String() string { @@ -470,7 +459,7 @@ func (*Components) ProtoMessage() {} func (x *Components) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -569,11 +558,9 @@ type Contact struct { func (x *Contact) Reset() { *x = Contact{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Contact) String() string { @@ -584,7 +571,7 @@ func (*Contact) ProtoMessage() {} func (x *Contact) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -633,6 +620,7 @@ type DefaultType struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *DefaultType_Number // *DefaultType_Boolean // *DefaultType_String_ @@ -641,11 +629,9 @@ type DefaultType struct { func (x *DefaultType) Reset() { *x = DefaultType{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DefaultType) String() string { @@ -656,7 +642,7 @@ func (*DefaultType) ProtoMessage() {} func (x *DefaultType) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -734,11 +720,9 @@ type Discriminator struct { func (x *Discriminator) Reset() { *x = Discriminator{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Discriminator) String() string { @@ -749,7 +733,7 @@ func (*Discriminator) ProtoMessage() {} func (x *Discriminator) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -803,11 +787,9 @@ type Document struct { func (x *Document) Reset() { *x = Document{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Document) String() string { @@ -818,7 +800,7 @@ func (*Document) ProtoMessage() {} func (x *Document) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -912,11 +894,9 @@ type Encoding struct { func (x *Encoding) Reset() { *x = Encoding{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Encoding) String() string { @@ -927,7 +907,7 @@ func (*Encoding) ProtoMessage() {} func (x *Encoding) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -994,11 +974,9 @@ type Encodings struct { func (x *Encodings) Reset() { *x = Encodings{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Encodings) String() string { @@ -1009,7 +987,7 @@ func (*Encodings) ProtoMessage() {} func (x *Encodings) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1045,11 +1023,9 @@ type Example struct { func (x *Example) Reset() { *x = Example{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Example) String() string { @@ -1060,7 +1036,7 @@ func (*Example) ProtoMessage() {} func (x *Example) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1116,6 +1092,7 @@ type ExampleOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *ExampleOrReference_Example // *ExampleOrReference_Reference Oneof isExampleOrReference_Oneof `protobuf_oneof:"oneof"` @@ -1123,11 +1100,9 @@ type ExampleOrReference struct { func (x *ExampleOrReference) Reset() { *x = ExampleOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExampleOrReference) String() string { @@ -1138,7 +1113,7 @@ func (*ExampleOrReference) ProtoMessage() {} func (x *ExampleOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1200,11 +1175,9 @@ type ExamplesOrReferences struct { func (x *ExamplesOrReferences) Reset() { *x = ExamplesOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExamplesOrReferences) String() string { @@ -1215,7 +1188,7 @@ func (*ExamplesOrReferences) ProtoMessage() {} func (x *ExamplesOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1247,11 +1220,9 @@ type Expression struct { func (x *Expression) Reset() { *x = Expression{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Expression) String() string { @@ -1262,7 +1233,7 @@ func (*Expression) ProtoMessage() {} func (x *Expression) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1297,11 +1268,9 @@ type ExternalDocs struct { func (x *ExternalDocs) Reset() { *x = ExternalDocs{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExternalDocs) String() string { @@ -1312,7 +1281,7 @@ func (*ExternalDocs) ProtoMessage() {} func (x *ExternalDocs) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1370,11 +1339,9 @@ type Header struct { func (x *Header) Reset() { *x = Header{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Header) String() string { @@ -1385,7 +1352,7 @@ func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1490,6 +1457,7 @@ type HeaderOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *HeaderOrReference_Header // *HeaderOrReference_Reference Oneof isHeaderOrReference_Oneof `protobuf_oneof:"oneof"` @@ -1497,11 +1465,9 @@ type HeaderOrReference struct { func (x *HeaderOrReference) Reset() { *x = HeaderOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HeaderOrReference) String() string { @@ -1512,7 +1478,7 @@ func (*HeaderOrReference) ProtoMessage() {} func (x *HeaderOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1574,11 +1540,9 @@ type HeadersOrReferences struct { func (x *HeadersOrReferences) Reset() { *x = HeadersOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HeadersOrReferences) String() string { @@ -1589,7 +1553,7 @@ func (*HeadersOrReferences) ProtoMessage() {} func (x *HeadersOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1629,11 +1593,9 @@ type Info struct { func (x *Info) Reset() { *x = Info{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Info) String() string { @@ -1644,7 +1606,7 @@ func (*Info) ProtoMessage() {} func (x *Info) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1725,11 +1687,9 @@ type ItemsItem struct { func (x *ItemsItem) Reset() { *x = ItemsItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ItemsItem) String() string { @@ -1740,7 +1700,7 @@ func (*ItemsItem) ProtoMessage() {} func (x *ItemsItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1775,11 +1735,9 @@ type License struct { func (x *License) Reset() { *x = License{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *License) String() string { @@ -1790,7 +1748,7 @@ func (*License) ProtoMessage() {} func (x *License) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1843,11 +1801,9 @@ type Link struct { func (x *Link) Reset() { *x = Link{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Link) String() string { @@ -1858,7 +1814,7 @@ func (*Link) ProtoMessage() {} func (x *Link) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1928,6 +1884,7 @@ type LinkOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *LinkOrReference_Link // *LinkOrReference_Reference Oneof isLinkOrReference_Oneof `protobuf_oneof:"oneof"` @@ -1935,11 +1892,9 @@ type LinkOrReference struct { func (x *LinkOrReference) Reset() { *x = LinkOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LinkOrReference) String() string { @@ -1950,7 +1905,7 @@ func (*LinkOrReference) ProtoMessage() {} func (x *LinkOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2012,11 +1967,9 @@ type LinksOrReferences struct { func (x *LinksOrReferences) Reset() { *x = LinksOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LinksOrReferences) String() string { @@ -2027,7 +1980,7 @@ func (*LinksOrReferences) ProtoMessage() {} func (x *LinksOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2064,11 +2017,9 @@ type MediaType struct { func (x *MediaType) Reset() { *x = MediaType{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MediaType) String() string { @@ -2079,7 +2030,7 @@ func (*MediaType) ProtoMessage() {} func (x *MediaType) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2139,11 +2090,9 @@ type MediaTypes struct { func (x *MediaTypes) Reset() { *x = MediaTypes{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MediaTypes) String() string { @@ -2154,7 +2103,7 @@ func (*MediaTypes) ProtoMessage() {} func (x *MediaTypes) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2190,11 +2139,9 @@ type NamedAny struct { func (x *NamedAny) Reset() { *x = NamedAny{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedAny) String() string { @@ -2205,7 +2152,7 @@ func (*NamedAny) ProtoMessage() {} func (x *NamedAny) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2248,11 +2195,9 @@ type NamedCallbackOrReference struct { func (x *NamedCallbackOrReference) Reset() { *x = NamedCallbackOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedCallbackOrReference) String() string { @@ -2263,7 +2208,7 @@ func (*NamedCallbackOrReference) ProtoMessage() {} func (x *NamedCallbackOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2306,11 +2251,9 @@ type NamedEncoding struct { func (x *NamedEncoding) Reset() { *x = NamedEncoding{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedEncoding) String() string { @@ -2321,7 +2264,7 @@ func (*NamedEncoding) ProtoMessage() {} func (x *NamedEncoding) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2364,11 +2307,9 @@ type NamedExampleOrReference struct { func (x *NamedExampleOrReference) Reset() { *x = NamedExampleOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedExampleOrReference) String() string { @@ -2379,7 +2320,7 @@ func (*NamedExampleOrReference) ProtoMessage() {} func (x *NamedExampleOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2422,11 +2363,9 @@ type NamedHeaderOrReference struct { func (x *NamedHeaderOrReference) Reset() { *x = NamedHeaderOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedHeaderOrReference) String() string { @@ -2437,7 +2376,7 @@ func (*NamedHeaderOrReference) ProtoMessage() {} func (x *NamedHeaderOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2480,11 +2419,9 @@ type NamedLinkOrReference struct { func (x *NamedLinkOrReference) Reset() { *x = NamedLinkOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedLinkOrReference) String() string { @@ -2495,7 +2432,7 @@ func (*NamedLinkOrReference) ProtoMessage() {} func (x *NamedLinkOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2538,11 +2475,9 @@ type NamedMediaType struct { func (x *NamedMediaType) Reset() { *x = NamedMediaType{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedMediaType) String() string { @@ -2553,7 +2488,7 @@ func (*NamedMediaType) ProtoMessage() {} func (x *NamedMediaType) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2596,11 +2531,9 @@ type NamedParameterOrReference struct { func (x *NamedParameterOrReference) Reset() { *x = NamedParameterOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedParameterOrReference) String() string { @@ -2611,7 +2544,7 @@ func (*NamedParameterOrReference) ProtoMessage() {} func (x *NamedParameterOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2654,11 +2587,9 @@ type NamedPathItem struct { func (x *NamedPathItem) Reset() { *x = NamedPathItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedPathItem) String() string { @@ -2669,7 +2600,7 @@ func (*NamedPathItem) ProtoMessage() {} func (x *NamedPathItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[37] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2712,11 +2643,9 @@ type NamedRequestBodyOrReference struct { func (x *NamedRequestBodyOrReference) Reset() { *x = NamedRequestBodyOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedRequestBodyOrReference) String() string { @@ -2727,7 +2656,7 @@ func (*NamedRequestBodyOrReference) ProtoMessage() {} func (x *NamedRequestBodyOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[38] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2770,11 +2699,9 @@ type NamedResponseOrReference struct { func (x *NamedResponseOrReference) Reset() { *x = NamedResponseOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedResponseOrReference) String() string { @@ -2785,7 +2712,7 @@ func (*NamedResponseOrReference) ProtoMessage() {} func (x *NamedResponseOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[39] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2828,11 +2755,9 @@ type NamedSchemaOrReference struct { func (x *NamedSchemaOrReference) Reset() { *x = NamedSchemaOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedSchemaOrReference) String() string { @@ -2843,7 +2768,7 @@ func (*NamedSchemaOrReference) ProtoMessage() {} func (x *NamedSchemaOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[40] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2886,11 +2811,9 @@ type NamedSecuritySchemeOrReference struct { func (x *NamedSecuritySchemeOrReference) Reset() { *x = NamedSecuritySchemeOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedSecuritySchemeOrReference) String() string { @@ -2901,7 +2824,7 @@ func (*NamedSecuritySchemeOrReference) ProtoMessage() {} func (x *NamedSecuritySchemeOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[41] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2944,11 +2867,9 @@ type NamedServerVariable struct { func (x *NamedServerVariable) Reset() { *x = NamedServerVariable{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedServerVariable) String() string { @@ -2959,7 +2880,7 @@ func (*NamedServerVariable) ProtoMessage() {} func (x *NamedServerVariable) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[42] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3002,11 +2923,9 @@ type NamedString struct { func (x *NamedString) Reset() { *x = NamedString{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedString) String() string { @@ -3017,7 +2936,7 @@ func (*NamedString) ProtoMessage() {} func (x *NamedString) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[43] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3060,11 +2979,9 @@ type NamedStringArray struct { func (x *NamedStringArray) Reset() { *x = NamedStringArray{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NamedStringArray) String() string { @@ -3075,7 +2992,7 @@ func (*NamedStringArray) ProtoMessage() {} func (x *NamedStringArray) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[44] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3119,11 +3036,9 @@ type OauthFlow struct { func (x *OauthFlow) Reset() { *x = OauthFlow{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OauthFlow) String() string { @@ -3134,7 +3049,7 @@ func (*OauthFlow) ProtoMessage() {} func (x *OauthFlow) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[45] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3199,11 +3114,9 @@ type OauthFlows struct { func (x *OauthFlows) Reset() { *x = OauthFlows{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OauthFlows) String() string { @@ -3214,7 +3127,7 @@ func (*OauthFlows) ProtoMessage() {} func (x *OauthFlows) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[46] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3274,11 +3187,9 @@ type Object struct { func (x *Object) Reset() { *x = Object{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[47] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Object) String() string { @@ -3289,7 +3200,7 @@ func (*Object) ProtoMessage() {} func (x *Object) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[47] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3334,11 +3245,9 @@ type Operation struct { func (x *Operation) Reset() { *x = Operation{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[48] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Operation) String() string { @@ -3349,7 +3258,7 @@ func (*Operation) ProtoMessage() {} func (x *Operation) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[48] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3479,11 +3388,9 @@ type Parameter struct { func (x *Parameter) Reset() { *x = Parameter{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[49] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Parameter) String() string { @@ -3494,7 +3401,7 @@ func (*Parameter) ProtoMessage() {} func (x *Parameter) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[49] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3613,6 +3520,7 @@ type ParameterOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *ParameterOrReference_Parameter // *ParameterOrReference_Reference Oneof isParameterOrReference_Oneof `protobuf_oneof:"oneof"` @@ -3620,11 +3528,9 @@ type ParameterOrReference struct { func (x *ParameterOrReference) Reset() { *x = ParameterOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[50] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ParameterOrReference) String() string { @@ -3635,7 +3541,7 @@ func (*ParameterOrReference) ProtoMessage() {} func (x *ParameterOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[50] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3697,11 +3603,9 @@ type ParametersOrReferences struct { func (x *ParametersOrReferences) Reset() { *x = ParametersOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[51] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ParametersOrReferences) String() string { @@ -3712,7 +3616,7 @@ func (*ParametersOrReferences) ProtoMessage() {} func (x *ParametersOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[51] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3758,11 +3662,9 @@ type PathItem struct { func (x *PathItem) Reset() { *x = PathItem{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[52] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PathItem) String() string { @@ -3773,7 +3675,7 @@ func (*PathItem) ProtoMessage() {} func (x *PathItem) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[52] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3898,11 +3800,9 @@ type Paths struct { func (x *Paths) Reset() { *x = Paths{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[53] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Paths) String() string { @@ -3913,7 +3813,7 @@ func (*Paths) ProtoMessage() {} func (x *Paths) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[53] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3952,11 +3852,9 @@ type Properties struct { func (x *Properties) Reset() { *x = Properties{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[54] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Properties) String() string { @@ -3967,7 +3865,7 @@ func (*Properties) ProtoMessage() {} func (x *Properties) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[54] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4002,11 +3900,9 @@ type Reference struct { func (x *Reference) Reset() { *x = Reference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[55] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Reference) String() string { @@ -4017,7 +3913,7 @@ func (*Reference) ProtoMessage() {} func (x *Reference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[55] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4063,11 +3959,9 @@ type RequestBodiesOrReferences struct { func (x *RequestBodiesOrReferences) Reset() { *x = RequestBodiesOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[56] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestBodiesOrReferences) String() string { @@ -4078,7 +3972,7 @@ func (*RequestBodiesOrReferences) ProtoMessage() {} func (x *RequestBodiesOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[56] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4114,11 +4008,9 @@ type RequestBody struct { func (x *RequestBody) Reset() { *x = RequestBody{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[57] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestBody) String() string { @@ -4129,7 +4021,7 @@ func (*RequestBody) ProtoMessage() {} func (x *RequestBody) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[57] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4178,6 +4070,7 @@ type RequestBodyOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *RequestBodyOrReference_RequestBody // *RequestBodyOrReference_Reference Oneof isRequestBodyOrReference_Oneof `protobuf_oneof:"oneof"` @@ -4185,11 +4078,9 @@ type RequestBodyOrReference struct { func (x *RequestBodyOrReference) Reset() { *x = RequestBodyOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[58] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestBodyOrReference) String() string { @@ -4200,7 +4091,7 @@ func (*RequestBodyOrReference) ProtoMessage() {} func (x *RequestBodyOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[58] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4267,11 +4158,9 @@ type Response struct { func (x *Response) Reset() { *x = Response{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[59] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Response) String() string { @@ -4282,7 +4171,7 @@ func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[59] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4338,6 +4227,7 @@ type ResponseOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *ResponseOrReference_Response // *ResponseOrReference_Reference Oneof isResponseOrReference_Oneof `protobuf_oneof:"oneof"` @@ -4345,11 +4235,9 @@ type ResponseOrReference struct { func (x *ResponseOrReference) Reset() { *x = ResponseOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[60] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResponseOrReference) String() string { @@ -4360,7 +4248,7 @@ func (*ResponseOrReference) ProtoMessage() {} func (x *ResponseOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[60] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4425,11 +4313,9 @@ type Responses struct { func (x *Responses) Reset() { *x = Responses{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[61] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Responses) String() string { @@ -4440,7 +4326,7 @@ func (*Responses) ProtoMessage() {} func (x *Responses) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[61] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4486,11 +4372,9 @@ type ResponsesOrReferences struct { func (x *ResponsesOrReferences) Reset() { *x = ResponsesOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[62] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResponsesOrReferences) String() string { @@ -4501,7 +4385,7 @@ func (*ResponsesOrReferences) ProtoMessage() {} func (x *ResponsesOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[62] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4569,11 +4453,9 @@ type Schema struct { func (x *Schema) Reset() { *x = Schema{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[63] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Schema) String() string { @@ -4584,7 +4466,7 @@ func (*Schema) ProtoMessage() {} func (x *Schema) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[63] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4857,6 +4739,7 @@ type SchemaOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *SchemaOrReference_Schema // *SchemaOrReference_Reference Oneof isSchemaOrReference_Oneof `protobuf_oneof:"oneof"` @@ -4864,11 +4747,9 @@ type SchemaOrReference struct { func (x *SchemaOrReference) Reset() { *x = SchemaOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[64] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SchemaOrReference) String() string { @@ -4879,7 +4760,7 @@ func (*SchemaOrReference) ProtoMessage() {} func (x *SchemaOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[64] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4941,11 +4822,9 @@ type SchemasOrReferences struct { func (x *SchemasOrReferences) Reset() { *x = SchemasOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[65] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SchemasOrReferences) String() string { @@ -4956,7 +4835,7 @@ func (*SchemasOrReferences) ProtoMessage() {} func (x *SchemasOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[65] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4989,11 +4868,9 @@ type SecurityRequirement struct { func (x *SecurityRequirement) Reset() { *x = SecurityRequirement{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[66] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecurityRequirement) String() string { @@ -5004,7 +4881,7 @@ func (*SecurityRequirement) ProtoMessage() {} func (x *SecurityRequirement) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[66] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5045,11 +4922,9 @@ type SecurityScheme struct { func (x *SecurityScheme) Reset() { *x = SecurityScheme{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[67] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecurityScheme) String() string { @@ -5060,7 +4935,7 @@ func (*SecurityScheme) ProtoMessage() {} func (x *SecurityScheme) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[67] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5144,6 +5019,7 @@ type SecuritySchemeOrReference struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *SecuritySchemeOrReference_SecurityScheme // *SecuritySchemeOrReference_Reference Oneof isSecuritySchemeOrReference_Oneof `protobuf_oneof:"oneof"` @@ -5151,11 +5027,9 @@ type SecuritySchemeOrReference struct { func (x *SecuritySchemeOrReference) Reset() { *x = SecuritySchemeOrReference{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[68] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecuritySchemeOrReference) String() string { @@ -5166,7 +5040,7 @@ func (*SecuritySchemeOrReference) ProtoMessage() {} func (x *SecuritySchemeOrReference) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[68] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5228,11 +5102,9 @@ type SecuritySchemesOrReferences struct { func (x *SecuritySchemesOrReferences) Reset() { *x = SecuritySchemesOrReferences{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[69] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SecuritySchemesOrReferences) String() string { @@ -5243,7 +5115,7 @@ func (*SecuritySchemesOrReferences) ProtoMessage() {} func (x *SecuritySchemesOrReferences) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[69] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5279,11 +5151,9 @@ type Server struct { func (x *Server) Reset() { *x = Server{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[70] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Server) String() string { @@ -5294,7 +5164,7 @@ func (*Server) ProtoMessage() {} func (x *Server) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[70] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5351,11 +5221,9 @@ type ServerVariable struct { func (x *ServerVariable) Reset() { *x = ServerVariable{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[71] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerVariable) String() string { @@ -5366,7 +5234,7 @@ func (*ServerVariable) ProtoMessage() {} func (x *ServerVariable) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[71] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5419,11 +5287,9 @@ type ServerVariables struct { func (x *ServerVariables) Reset() { *x = ServerVariables{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[72] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerVariables) String() string { @@ -5434,7 +5300,7 @@ func (*ServerVariables) ProtoMessage() {} func (x *ServerVariables) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[72] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5463,6 +5329,7 @@ type SpecificationExtension struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Oneof: + // // *SpecificationExtension_Number // *SpecificationExtension_Boolean // *SpecificationExtension_String_ @@ -5471,11 +5338,9 @@ type SpecificationExtension struct { func (x *SpecificationExtension) Reset() { *x = SpecificationExtension{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[73] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SpecificationExtension) String() string { @@ -5486,7 +5351,7 @@ func (*SpecificationExtension) ProtoMessage() {} func (x *SpecificationExtension) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[73] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5561,11 +5426,9 @@ type StringArray struct { func (x *StringArray) Reset() { *x = StringArray{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[74] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *StringArray) String() string { @@ -5576,7 +5439,7 @@ func (*StringArray) ProtoMessage() {} func (x *StringArray) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[74] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5608,11 +5471,9 @@ type Strings struct { func (x *Strings) Reset() { *x = Strings{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[75] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Strings) String() string { @@ -5623,7 +5484,7 @@ func (*Strings) ProtoMessage() {} func (x *Strings) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[75] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5659,11 +5520,9 @@ type Tag struct { func (x *Tag) Reset() { *x = Tag{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[76] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Tag) String() string { @@ -5674,7 +5533,7 @@ func (*Tag) ProtoMessage() {} func (x *Tag) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[76] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5733,11 +5592,9 @@ type Xml struct { func (x *Xml) Reset() { *x = Xml{} - if protoimpl.UnsafeEnabled { - mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[77] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Xml) String() string { @@ -5748,7 +5605,7 @@ func (*Xml) ProtoMessage() {} func (x *Xml) ProtoReflect() protoreflect.Message { mi := &file_openapiv3_OpenAPIv3_proto_msgTypes[77] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6781,7 +6638,7 @@ func file_openapiv3_OpenAPIv3_proto_rawDescGZIP() []byte { } var file_openapiv3_OpenAPIv3_proto_msgTypes = make([]protoimpl.MessageInfo, 78) -var file_openapiv3_OpenAPIv3_proto_goTypes = []interface{}{ +var file_openapiv3_OpenAPIv3_proto_goTypes = []any{ (*AdditionalPropertiesItem)(nil), // 0: openapi.v3.AdditionalPropertiesItem (*Any)(nil), // 1: openapi.v3.Any (*AnyOrExpression)(nil), // 2: openapi.v3.AnyOrExpression @@ -7040,994 +6897,56 @@ func file_openapiv3_OpenAPIv3_proto_init() { if File_openapiv3_OpenAPIv3_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_openapiv3_OpenAPIv3_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AdditionalPropertiesItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Any); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AnyOrExpression); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Callback); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CallbackOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CallbacksOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Components); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Contact); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DefaultType); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Discriminator); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Document); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Encoding); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Encodings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Example); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExampleOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExamplesOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Expression); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExternalDocs); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Header); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HeaderOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HeadersOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Info); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ItemsItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*License); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Link); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LinkOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LinksOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MediaType); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MediaTypes); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedAny); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedCallbackOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedEncoding); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedExampleOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedHeaderOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedLinkOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedMediaType); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedParameterOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedPathItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedRequestBodyOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedResponseOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedSchemaOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedSecuritySchemeOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedServerVariable); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedString); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NamedStringArray); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OauthFlow); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OauthFlows); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Object); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Operation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parameter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParameterOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParametersOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PathItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Paths); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Properties); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Reference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RequestBodiesOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RequestBody); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RequestBodyOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Response); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResponseOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Responses); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResponsesOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Schema); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SchemaOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SchemasOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecurityRequirement); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecurityScheme); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecuritySchemeOrReference); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecuritySchemesOrReferences); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Server); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerVariable); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerVariables); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SpecificationExtension); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StringArray); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Strings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Tag); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Xml); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_openapiv3_OpenAPIv3_proto_msgTypes[0].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[0].OneofWrappers = []any{ (*AdditionalPropertiesItem_SchemaOrReference)(nil), (*AdditionalPropertiesItem_Boolean)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[2].OneofWrappers = []any{ (*AnyOrExpression_Any)(nil), (*AnyOrExpression_Expression)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[4].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[4].OneofWrappers = []any{ (*CallbackOrReference_Callback)(nil), (*CallbackOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[8].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[8].OneofWrappers = []any{ (*DefaultType_Number)(nil), (*DefaultType_Boolean)(nil), (*DefaultType_String_)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[14].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[14].OneofWrappers = []any{ (*ExampleOrReference_Example)(nil), (*ExampleOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[19].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[19].OneofWrappers = []any{ (*HeaderOrReference_Header)(nil), (*HeaderOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[25].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[25].OneofWrappers = []any{ (*LinkOrReference_Link)(nil), (*LinkOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[50].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[50].OneofWrappers = []any{ (*ParameterOrReference_Parameter)(nil), (*ParameterOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[58].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[58].OneofWrappers = []any{ (*RequestBodyOrReference_RequestBody)(nil), (*RequestBodyOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[60].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[60].OneofWrappers = []any{ (*ResponseOrReference_Response)(nil), (*ResponseOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[64].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[64].OneofWrappers = []any{ (*SchemaOrReference_Schema)(nil), (*SchemaOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[68].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[68].OneofWrappers = []any{ (*SecuritySchemeOrReference_SecurityScheme)(nil), (*SecuritySchemeOrReference_Reference)(nil), } - file_openapiv3_OpenAPIv3_proto_msgTypes[73].OneofWrappers = []interface{}{ + file_openapiv3_OpenAPIv3_proto_msgTypes[73].OneofWrappers = []any{ (*SpecificationExtension_Number)(nil), (*SpecificationExtension_Boolean)(nil), (*SpecificationExtension_String_)(nil), diff --git a/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.pb.go b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.pb.go new file mode 100644 index 0000000000..f9f1bd2654 --- /dev/null +++ b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.pb.go @@ -0,0 +1,182 @@ +// Copyright 2022 Google LLC. 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v4.23.4 +// source: openapiv3/annotations.proto + +package openapi_v3 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var file_openapiv3_annotations_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.FileOptions)(nil), + ExtensionType: (*Document)(nil), + Field: 1143, + Name: "openapi.v3.document", + Tag: "bytes,1143,opt,name=document", + Filename: "openapiv3/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*Operation)(nil), + Field: 1143, + Name: "openapi.v3.operation", + Tag: "bytes,1143,opt,name=operation", + Filename: "openapiv3/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.MessageOptions)(nil), + ExtensionType: (*Schema)(nil), + Field: 1143, + Name: "openapi.v3.schema", + Tag: "bytes,1143,opt,name=schema", + Filename: "openapiv3/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*Schema)(nil), + Field: 1143, + Name: "openapi.v3.property", + Tag: "bytes,1143,opt,name=property", + Filename: "openapiv3/annotations.proto", + }, +} + +// Extension fields to descriptorpb.FileOptions. +var ( + // optional openapi.v3.Document document = 1143; + E_Document = &file_openapiv3_annotations_proto_extTypes[0] +) + +// Extension fields to descriptorpb.MethodOptions. +var ( + // optional openapi.v3.Operation operation = 1143; + E_Operation = &file_openapiv3_annotations_proto_extTypes[1] +) + +// Extension fields to descriptorpb.MessageOptions. +var ( + // optional openapi.v3.Schema schema = 1143; + E_Schema = &file_openapiv3_annotations_proto_extTypes[2] +) + +// Extension fields to descriptorpb.FieldOptions. +var ( + // optional openapi.v3.Schema property = 1143; + E_Property = &file_openapiv3_annotations_proto_extTypes[3] +) + +var File_openapiv3_annotations_proto protoreflect.FileDescriptor + +var file_openapiv3_annotations_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6f, + 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x6f, 0x70, 0x65, + 0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x2f, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x50, 0x49, 0x76, 0x33, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x4f, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x54, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x4c, 0x0a, + 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x3a, 0x4e, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xf7, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x42, 0x42, 0x0a, 0x0e, 0x6f, + 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x33, 0x42, 0x10, 0x41, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x16, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x33, 0x3b, 0x6f, + 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x33, 0xa2, 0x02, 0x03, 0x4f, 0x41, 0x53, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_openapiv3_annotations_proto_goTypes = []any{ + (*descriptorpb.FileOptions)(nil), // 0: google.protobuf.FileOptions + (*descriptorpb.MethodOptions)(nil), // 1: google.protobuf.MethodOptions + (*descriptorpb.MessageOptions)(nil), // 2: google.protobuf.MessageOptions + (*descriptorpb.FieldOptions)(nil), // 3: google.protobuf.FieldOptions + (*Document)(nil), // 4: openapi.v3.Document + (*Operation)(nil), // 5: openapi.v3.Operation + (*Schema)(nil), // 6: openapi.v3.Schema +} +var file_openapiv3_annotations_proto_depIdxs = []int32{ + 0, // 0: openapi.v3.document:extendee -> google.protobuf.FileOptions + 1, // 1: openapi.v3.operation:extendee -> google.protobuf.MethodOptions + 2, // 2: openapi.v3.schema:extendee -> google.protobuf.MessageOptions + 3, // 3: openapi.v3.property:extendee -> google.protobuf.FieldOptions + 4, // 4: openapi.v3.document:type_name -> openapi.v3.Document + 5, // 5: openapi.v3.operation:type_name -> openapi.v3.Operation + 6, // 6: openapi.v3.schema:type_name -> openapi.v3.Schema + 6, // 7: openapi.v3.property:type_name -> openapi.v3.Schema + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 4, // [4:8] is the sub-list for extension type_name + 0, // [0:4] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_openapiv3_annotations_proto_init() } +func file_openapiv3_annotations_proto_init() { + if File_openapiv3_annotations_proto != nil { + return + } + file_openapiv3_OpenAPIv3_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_openapiv3_annotations_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 4, + NumServices: 0, + }, + GoTypes: file_openapiv3_annotations_proto_goTypes, + DependencyIndexes: file_openapiv3_annotations_proto_depIdxs, + ExtensionInfos: file_openapiv3_annotations_proto_extTypes, + }.Build() + File_openapiv3_annotations_proto = out.File + file_openapiv3_annotations_proto_rawDesc = nil + file_openapiv3_annotations_proto_goTypes = nil + file_openapiv3_annotations_proto_depIdxs = nil +} diff --git a/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto new file mode 100644 index 0000000000..09ee0aac51 --- /dev/null +++ b/go-controller/vendor/github.com/google/gnostic-models/openapiv3/annotations.proto @@ -0,0 +1,56 @@ +// Copyright 2022 Google LLC. 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. + +syntax = "proto3"; + +package openapi.v3; + +import "google/protobuf/descriptor.proto"; +import "openapiv3/OpenAPIv3.proto"; + +// The Go package name. +option go_package = "./openapiv3;openapi_v3"; +// This option lets the proto compiler generate Java code inside the package +// name (see below) instead of inside an outer class. It creates a simpler +// developer experience by reducing one-level of name nesting and be +// consistent with most programming languages that don't support outer classes. +option java_multiple_files = true; +// The Java outer classname should be the filename in UpperCamelCase. This +// class is only used to hold proto descriptor, so developers don't need to +// work with it directly. +option java_outer_classname = "AnnotationsProto"; +// The Java package name must be proto package name with proper prefix. +option java_package = "org.openapi_v3"; +// A reasonable prefix for the Objective-C symbols generated from the package. +// It should at a minimum be 3 characters long, all uppercase, and convention +// is to use an abbreviation of the package name. Something short, but +// hopefully unique enough to not conflict with things that may come along in +// the future. 'GPB' is reserved for the protocol buffer implementation itself. +option objc_class_prefix = "OAS"; + +extend google.protobuf.FileOptions { + Document document = 1143; +} + +extend google.protobuf.MethodOptions { + Operation operation = 1143; +} + +extend google.protobuf.MessageOptions { + Schema schema = 1143; +} + +extend google.protobuf.FieldOptions { + Schema property = 1143; +} diff --git a/go-controller/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/go-controller/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go index c6d09dae40..720f3cdf57 100644 --- a/go-controller/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go +++ b/go-controller/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go @@ -14,22 +14,29 @@ import ( ) // SortSlices returns a [cmp.Transformer] option that sorts all []V. -// The less function must be of the form "func(T, T) bool" which is used to -// sort any slice with element type V that is assignable to T. +// The lessOrCompareFunc function must be either +// a less function of the form "func(T, T) bool" or +// a compare function of the format "func(T, T) int" +// which is used to sort any slice with element type V that is assignable to T. // -// The less function must be: +// A less function must be: // - Deterministic: less(x, y) == less(x, y) // - Irreflexive: !less(x, x) // - Transitive: if !less(x, y) and !less(y, z), then !less(x, z) // -// The less function does not have to be "total". That is, if !less(x, y) and -// !less(y, x) for two elements x and y, their relative order is maintained. +// A compare function must be: +// - Deterministic: compare(x, y) == compare(x, y) +// - Irreflexive: compare(x, x) == 0 +// - Transitive: if !less(x, y) and !less(y, z), then !less(x, z) +// +// The function does not have to be "total". That is, if x != y, but +// less or compare report inequality, their relative order is maintained. // // SortSlices can be used in conjunction with [EquateEmpty]. -func SortSlices(lessFunc interface{}) cmp.Option { - vf := reflect.ValueOf(lessFunc) - if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { - panic(fmt.Sprintf("invalid less function: %T", lessFunc)) +func SortSlices(lessOrCompareFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessOrCompareFunc) + if (!function.IsType(vf.Type(), function.Less) && !function.IsType(vf.Type(), function.Compare)) || vf.IsNil() { + panic(fmt.Sprintf("invalid less or compare function: %T", lessOrCompareFunc)) } ss := sliceSorter{vf.Type().In(0), vf} return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort)) @@ -79,28 +86,40 @@ func (ss sliceSorter) checkSort(v reflect.Value) { } func (ss sliceSorter) less(v reflect.Value, i, j int) bool { vx, vy := v.Index(i), v.Index(j) - return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool() + vo := ss.fnc.Call([]reflect.Value{vx, vy})[0] + if vo.Kind() == reflect.Bool { + return vo.Bool() + } else { + return vo.Int() < 0 + } } -// SortMaps returns a [cmp.Transformer] option that flattens map[K]V types to be a -// sorted []struct{K, V}. The less function must be of the form -// "func(T, T) bool" which is used to sort any map with key K that is -// assignable to T. +// SortMaps returns a [cmp.Transformer] option that flattens map[K]V types to be +// a sorted []struct{K, V}. The lessOrCompareFunc function must be either +// a less function of the form "func(T, T) bool" or +// a compare function of the format "func(T, T) int" +// which is used to sort any map with key K that is assignable to T. // // Flattening the map into a slice has the property that [cmp.Equal] is able to // use [cmp.Comparer] options on K or the K.Equal method if it exists. // -// The less function must be: +// A less function must be: // - Deterministic: less(x, y) == less(x, y) // - Irreflexive: !less(x, x) // - Transitive: if !less(x, y) and !less(y, z), then !less(x, z) // - Total: if x != y, then either less(x, y) or less(y, x) // +// A compare function must be: +// - Deterministic: compare(x, y) == compare(x, y) +// - Irreflexive: compare(x, x) == 0 +// - Transitive: if compare(x, y) < 0 and compare(y, z) < 0, then compare(x, z) < 0 +// - Total: if x != y, then compare(x, y) != 0 +// // SortMaps can be used in conjunction with [EquateEmpty]. -func SortMaps(lessFunc interface{}) cmp.Option { - vf := reflect.ValueOf(lessFunc) - if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { - panic(fmt.Sprintf("invalid less function: %T", lessFunc)) +func SortMaps(lessOrCompareFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessOrCompareFunc) + if (!function.IsType(vf.Type(), function.Less) && !function.IsType(vf.Type(), function.Compare)) || vf.IsNil() { + panic(fmt.Sprintf("invalid less or compare function: %T", lessOrCompareFunc)) } ms := mapSorter{vf.Type().In(0), vf} return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort)) @@ -143,5 +162,10 @@ func (ms mapSorter) checkSort(v reflect.Value) { } func (ms mapSorter) less(v reflect.Value, i, j int) bool { vx, vy := v.Index(i).Field(0), v.Index(j).Field(0) - return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() + vo := ms.fnc.Call([]reflect.Value{vx, vy})[0] + if vo.Kind() == reflect.Bool { + return vo.Bool() + } else { + return vo.Int() < 0 + } } diff --git a/go-controller/vendor/github.com/google/go-cmp/cmp/internal/function/func.go b/go-controller/vendor/github.com/google/go-cmp/cmp/internal/function/func.go index d127d43623..def01a6be3 100644 --- a/go-controller/vendor/github.com/google/go-cmp/cmp/internal/function/func.go +++ b/go-controller/vendor/github.com/google/go-cmp/cmp/internal/function/func.go @@ -19,6 +19,7 @@ const ( tbFunc // func(T) bool ttbFunc // func(T, T) bool + ttiFunc // func(T, T) int trbFunc // func(T, R) bool tibFunc // func(T, I) bool trFunc // func(T) R @@ -28,11 +29,13 @@ const ( Transformer = trFunc // func(T) R ValueFilter = ttbFunc // func(T, T) bool Less = ttbFunc // func(T, T) bool + Compare = ttiFunc // func(T, T) int ValuePredicate = tbFunc // func(T) bool KeyValuePredicate = trbFunc // func(T, R) bool ) var boolType = reflect.TypeOf(true) +var intType = reflect.TypeOf(0) // IsType reports whether the reflect.Type is of the specified function type. func IsType(t reflect.Type, ft funcType) bool { @@ -49,6 +52,10 @@ func IsType(t reflect.Type, ft funcType) bool { if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType { return true } + case ttiFunc: // func(T, T) int + if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == intType { + return true + } case trbFunc: // func(T, R) bool if ni == 2 && no == 1 && t.Out(0) == boolType { return true diff --git a/go-controller/vendor/github.com/google/go-cmp/cmp/options.go b/go-controller/vendor/github.com/google/go-cmp/cmp/options.go index 754496f3b3..ba3fce81ff 100644 --- a/go-controller/vendor/github.com/google/go-cmp/cmp/options.go +++ b/go-controller/vendor/github.com/google/go-cmp/cmp/options.go @@ -232,7 +232,15 @@ func (validator) apply(s *state, vx, vy reflect.Value) { if t := s.curPath.Index(-2).Type(); t.Name() != "" { // Named type with unexported fields. name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType - if _, ok := reflect.New(t).Interface().(error); ok { + isProtoMessage := func(t reflect.Type) bool { + m, ok := reflect.PointerTo(t).MethodByName("ProtoReflect") + return ok && m.Type.NumIn() == 1 && m.Type.NumOut() == 1 && + m.Type.Out(0).PkgPath() == "google.golang.org/protobuf/reflect/protoreflect" && + m.Type.Out(0).Name() == "Message" + } + if isProtoMessage(t) { + help = `consider using "google.golang.org/protobuf/testing/protocmp".Transform to compare proto.Message types` + } else if _, ok := reflect.New(t).Interface().(error); ok { help = "consider using cmpopts.EquateErrors to compare error values" } else if t.Comparable() { help = "consider using cmpopts.EquateComparable to compare comparable Go types" diff --git a/go-controller/vendor/github.com/google/gofuzz/.travis.yml b/go-controller/vendor/github.com/google/gofuzz/.travis.yml deleted file mode 100644 index 061d72ae07..0000000000 --- a/go-controller/vendor/github.com/google/gofuzz/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go - -go: - - 1.11.x - - 1.12.x - - 1.13.x - - master - -script: - - go test -cover diff --git a/go-controller/vendor/github.com/google/gofuzz/CONTRIBUTING.md b/go-controller/vendor/github.com/google/gofuzz/CONTRIBUTING.md deleted file mode 100644 index 97c1b34fd5..0000000000 --- a/go-controller/vendor/github.com/google/gofuzz/CONTRIBUTING.md +++ /dev/null @@ -1,67 +0,0 @@ -# How to contribute # - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - - -## Contributor License Agreement ## - -Contributions to any Google project must be accompanied by a Contributor -License Agreement. This is not a copyright **assignment**, it simply gives -Google permission to use and redistribute your contributions as part of the -project. - - * If you are an individual writing original source code and you're sure you - own the intellectual property, then you'll need to sign an [individual - CLA][]. - - * If you work for a company that wants to allow you to contribute your work, - then you'll need to sign a [corporate CLA][]. - -You generally only need to submit a CLA once, so if you've already submitted -one (even if it was for a different project), you probably don't need to do it -again. - -[individual CLA]: https://developers.google.com/open-source/cla/individual -[corporate CLA]: https://developers.google.com/open-source/cla/corporate - - -## Submitting a patch ## - - 1. It's generally best to start by opening a new issue describing the bug or - feature you're intending to fix. Even if you think it's relatively minor, - it's helpful to know what people are working on. Mention in the initial - issue that you are planning to work on that bug or feature so that it can - be assigned to you. - - 1. Follow the normal process of [forking][] the project, and setup a new - branch to work in. It's important that each group of changes be done in - separate branches in order to ensure that a pull request only includes the - commits related to that bug or feature. - - 1. Go makes it very simple to ensure properly formatted code, so always run - `go fmt` on your code before committing it. You should also run - [golint][] over your code. As noted in the [golint readme][], it's not - strictly necessary that your code be completely "lint-free", but this will - help you find common style issues. - - 1. Any significant changes should almost always be accompanied by tests. The - project already has good test coverage, so look at some of the existing - tests if you're unsure how to go about it. [gocov][] and [gocov-html][] - are invaluable tools for seeing which parts of your code aren't being - exercised by your tests. - - 1. Do your best to have [well-formed commit messages][] for each change. - This provides consistency throughout the project, and ensures that commit - messages are able to be formatted properly by various git tools. - - 1. Finally, push the commits to your fork and submit a [pull request][]. - -[forking]: https://help.github.com/articles/fork-a-repo -[golint]: https://github.com/golang/lint -[golint readme]: https://github.com/golang/lint/blob/master/README -[gocov]: https://github.com/axw/gocov -[gocov-html]: https://github.com/matm/gocov-html -[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html -[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits -[pull request]: https://help.github.com/articles/creating-a-pull-request diff --git a/go-controller/vendor/github.com/google/gofuzz/fuzz.go b/go-controller/vendor/github.com/google/gofuzz/fuzz.go deleted file mode 100644 index 761520a8ce..0000000000 --- a/go-controller/vendor/github.com/google/gofuzz/fuzz.go +++ /dev/null @@ -1,605 +0,0 @@ -/* -Copyright 2014 Google 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 fuzz - -import ( - "fmt" - "math/rand" - "reflect" - "regexp" - "time" - - "github.com/google/gofuzz/bytesource" - "strings" -) - -// fuzzFuncMap is a map from a type to a fuzzFunc that handles that type. -type fuzzFuncMap map[reflect.Type]reflect.Value - -// Fuzzer knows how to fill any object with random fields. -type Fuzzer struct { - fuzzFuncs fuzzFuncMap - defaultFuzzFuncs fuzzFuncMap - r *rand.Rand - nilChance float64 - minElements int - maxElements int - maxDepth int - skipFieldPatterns []*regexp.Regexp -} - -// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs, -// RandSource, NilChance, or NumElements in any order. -func New() *Fuzzer { - return NewWithSeed(time.Now().UnixNano()) -} - -func NewWithSeed(seed int64) *Fuzzer { - f := &Fuzzer{ - defaultFuzzFuncs: fuzzFuncMap{ - reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime), - }, - - fuzzFuncs: fuzzFuncMap{}, - r: rand.New(rand.NewSource(seed)), - nilChance: .2, - minElements: 1, - maxElements: 10, - maxDepth: 100, - } - return f -} - -// NewFromGoFuzz is a helper function that enables using gofuzz (this -// project) with go-fuzz (https://github.com/dvyukov/go-fuzz) for continuous -// fuzzing. Essentially, it enables translating the fuzzing bytes from -// go-fuzz to any Go object using this library. -// -// This implementation promises a constant translation from a given slice of -// bytes to the fuzzed objects. This promise will remain over future -// versions of Go and of this library. -// -// Note: the returned Fuzzer should not be shared between multiple goroutines, -// as its deterministic output will no longer be available. -// -// Example: use go-fuzz to test the function `MyFunc(int)` in the package -// `mypackage`. Add the file: "mypacakge_fuzz.go" with the content: -// -// // +build gofuzz -// package mypacakge -// import fuzz "github.com/google/gofuzz" -// func Fuzz(data []byte) int { -// var i int -// fuzz.NewFromGoFuzz(data).Fuzz(&i) -// MyFunc(i) -// return 0 -// } -func NewFromGoFuzz(data []byte) *Fuzzer { - return New().RandSource(bytesource.New(data)) -} - -// Funcs adds each entry in fuzzFuncs as a custom fuzzing function. -// -// Each entry in fuzzFuncs must be a function taking two parameters. -// The first parameter must be a pointer or map. It is the variable that -// function will fill with random data. The second parameter must be a -// fuzz.Continue, which will provide a source of randomness and a way -// to automatically continue fuzzing smaller pieces of the first parameter. -// -// These functions are called sensibly, e.g., if you wanted custom string -// fuzzing, the function `func(s *string, c fuzz.Continue)` would get -// called and passed the address of strings. Maps and pointers will always -// be made/new'd for you, ignoring the NilChange option. For slices, it -// doesn't make much sense to pre-create them--Fuzzer doesn't know how -// long you want your slice--so take a pointer to a slice, and make it -// yourself. (If you don't want your map/pointer type pre-made, take a -// pointer to it, and make it yourself.) See the examples for a range of -// custom functions. -func (f *Fuzzer) Funcs(fuzzFuncs ...interface{}) *Fuzzer { - for i := range fuzzFuncs { - v := reflect.ValueOf(fuzzFuncs[i]) - if v.Kind() != reflect.Func { - panic("Need only funcs!") - } - t := v.Type() - if t.NumIn() != 2 || t.NumOut() != 0 { - panic("Need 2 in and 0 out params!") - } - argT := t.In(0) - switch argT.Kind() { - case reflect.Ptr, reflect.Map: - default: - panic("fuzzFunc must take pointer or map type") - } - if t.In(1) != reflect.TypeOf(Continue{}) { - panic("fuzzFunc's second parameter must be type fuzz.Continue") - } - f.fuzzFuncs[argT] = v - } - return f -} - -// RandSource causes f to get values from the given source of randomness. -// Use if you want deterministic fuzzing. -func (f *Fuzzer) RandSource(s rand.Source) *Fuzzer { - f.r = rand.New(s) - return f -} - -// NilChance sets the probability of creating a nil pointer, map, or slice to -// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive. -func (f *Fuzzer) NilChance(p float64) *Fuzzer { - if p < 0 || p > 1 { - panic("p should be between 0 and 1, inclusive.") - } - f.nilChance = p - return f -} - -// NumElements sets the minimum and maximum number of elements that will be -// added to a non-nil map or slice. -func (f *Fuzzer) NumElements(atLeast, atMost int) *Fuzzer { - if atLeast > atMost { - panic("atLeast must be <= atMost") - } - if atLeast < 0 { - panic("atLeast must be >= 0") - } - f.minElements = atLeast - f.maxElements = atMost - return f -} - -func (f *Fuzzer) genElementCount() int { - if f.minElements == f.maxElements { - return f.minElements - } - return f.minElements + f.r.Intn(f.maxElements-f.minElements+1) -} - -func (f *Fuzzer) genShouldFill() bool { - return f.r.Float64() >= f.nilChance -} - -// MaxDepth sets the maximum number of recursive fuzz calls that will be made -// before stopping. This includes struct members, pointers, and map and slice -// elements. -func (f *Fuzzer) MaxDepth(d int) *Fuzzer { - f.maxDepth = d - return f -} - -// Skip fields which match the supplied pattern. Call this multiple times if needed -// This is useful to skip XXX_ fields generated by protobuf -func (f *Fuzzer) SkipFieldsWithPattern(pattern *regexp.Regexp) *Fuzzer { - f.skipFieldPatterns = append(f.skipFieldPatterns, pattern) - return f -} - -// Fuzz recursively fills all of obj's fields with something random. First -// this tries to find a custom fuzz function (see Funcs). If there is no -// custom function this tests whether the object implements fuzz.Interface and, -// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if -// there is a default fuzz function provided by this package. If all of that -// fails, this will generate random values for all primitive fields and then -// recurse for all non-primitives. -// -// This is safe for cyclic or tree-like structs, up to a limit. Use the -// MaxDepth method to adjust how deep you need it to recurse. -// -// obj must be a pointer. Only exported (public) fields can be set (thanks, -// golang :/ ) Intended for tests, so will panic on bad input or unimplemented -// fields. -func (f *Fuzzer) Fuzz(obj interface{}) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - panic("needed ptr!") - } - v = v.Elem() - f.fuzzWithContext(v, 0) -} - -// FuzzNoCustom is just like Fuzz, except that any custom fuzz function for -// obj's type will not be called and obj will not be tested for fuzz.Interface -// conformance. This applies only to obj and not other instances of obj's -// type. -// Not safe for cyclic or tree-like structs! -// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ ) -// Intended for tests, so will panic on bad input or unimplemented fields. -func (f *Fuzzer) FuzzNoCustom(obj interface{}) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - panic("needed ptr!") - } - v = v.Elem() - f.fuzzWithContext(v, flagNoCustomFuzz) -} - -const ( - // Do not try to find a custom fuzz function. Does not apply recursively. - flagNoCustomFuzz uint64 = 1 << iota -) - -func (f *Fuzzer) fuzzWithContext(v reflect.Value, flags uint64) { - fc := &fuzzerContext{fuzzer: f} - fc.doFuzz(v, flags) -} - -// fuzzerContext carries context about a single fuzzing run, which lets Fuzzer -// be thread-safe. -type fuzzerContext struct { - fuzzer *Fuzzer - curDepth int -} - -func (fc *fuzzerContext) doFuzz(v reflect.Value, flags uint64) { - if fc.curDepth >= fc.fuzzer.maxDepth { - return - } - fc.curDepth++ - defer func() { fc.curDepth-- }() - - if !v.CanSet() { - return - } - - if flags&flagNoCustomFuzz == 0 { - // Check for both pointer and non-pointer custom functions. - if v.CanAddr() && fc.tryCustom(v.Addr()) { - return - } - if fc.tryCustom(v) { - return - } - } - - if fn, ok := fillFuncMap[v.Kind()]; ok { - fn(v, fc.fuzzer.r) - return - } - - switch v.Kind() { - case reflect.Map: - if fc.fuzzer.genShouldFill() { - v.Set(reflect.MakeMap(v.Type())) - n := fc.fuzzer.genElementCount() - for i := 0; i < n; i++ { - key := reflect.New(v.Type().Key()).Elem() - fc.doFuzz(key, 0) - val := reflect.New(v.Type().Elem()).Elem() - fc.doFuzz(val, 0) - v.SetMapIndex(key, val) - } - return - } - v.Set(reflect.Zero(v.Type())) - case reflect.Ptr: - if fc.fuzzer.genShouldFill() { - v.Set(reflect.New(v.Type().Elem())) - fc.doFuzz(v.Elem(), 0) - return - } - v.Set(reflect.Zero(v.Type())) - case reflect.Slice: - if fc.fuzzer.genShouldFill() { - n := fc.fuzzer.genElementCount() - v.Set(reflect.MakeSlice(v.Type(), n, n)) - for i := 0; i < n; i++ { - fc.doFuzz(v.Index(i), 0) - } - return - } - v.Set(reflect.Zero(v.Type())) - case reflect.Array: - if fc.fuzzer.genShouldFill() { - n := v.Len() - for i := 0; i < n; i++ { - fc.doFuzz(v.Index(i), 0) - } - return - } - v.Set(reflect.Zero(v.Type())) - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - skipField := false - fieldName := v.Type().Field(i).Name - for _, pattern := range fc.fuzzer.skipFieldPatterns { - if pattern.MatchString(fieldName) { - skipField = true - break - } - } - if !skipField { - fc.doFuzz(v.Field(i), 0) - } - } - case reflect.Chan: - fallthrough - case reflect.Func: - fallthrough - case reflect.Interface: - fallthrough - default: - panic(fmt.Sprintf("Can't handle %#v", v.Interface())) - } -} - -// tryCustom searches for custom handlers, and returns true iff it finds a match -// and successfully randomizes v. -func (fc *fuzzerContext) tryCustom(v reflect.Value) bool { - // First: see if we have a fuzz function for it. - doCustom, ok := fc.fuzzer.fuzzFuncs[v.Type()] - if !ok { - // Second: see if it can fuzz itself. - if v.CanInterface() { - intf := v.Interface() - if fuzzable, ok := intf.(Interface); ok { - fuzzable.Fuzz(Continue{fc: fc, Rand: fc.fuzzer.r}) - return true - } - } - // Finally: see if there is a default fuzz function. - doCustom, ok = fc.fuzzer.defaultFuzzFuncs[v.Type()] - if !ok { - return false - } - } - - switch v.Kind() { - case reflect.Ptr: - if v.IsNil() { - if !v.CanSet() { - return false - } - v.Set(reflect.New(v.Type().Elem())) - } - case reflect.Map: - if v.IsNil() { - if !v.CanSet() { - return false - } - v.Set(reflect.MakeMap(v.Type())) - } - default: - return false - } - - doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{ - fc: fc, - Rand: fc.fuzzer.r, - })}) - return true -} - -// Interface represents an object that knows how to fuzz itself. Any time we -// find a type that implements this interface we will delegate the act of -// fuzzing itself. -type Interface interface { - Fuzz(c Continue) -} - -// Continue can be passed to custom fuzzing functions to allow them to use -// the correct source of randomness and to continue fuzzing their members. -type Continue struct { - fc *fuzzerContext - - // For convenience, Continue implements rand.Rand via embedding. - // Use this for generating any randomness if you want your fuzzing - // to be repeatable for a given seed. - *rand.Rand -} - -// Fuzz continues fuzzing obj. obj must be a pointer. -func (c Continue) Fuzz(obj interface{}) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - panic("needed ptr!") - } - v = v.Elem() - c.fc.doFuzz(v, 0) -} - -// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for -// obj's type will not be called and obj will not be tested for fuzz.Interface -// conformance. This applies only to obj and not other instances of obj's -// type. -func (c Continue) FuzzNoCustom(obj interface{}) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - panic("needed ptr!") - } - v = v.Elem() - c.fc.doFuzz(v, flagNoCustomFuzz) -} - -// RandString makes a random string up to 20 characters long. The returned string -// may include a variety of (valid) UTF-8 encodings. -func (c Continue) RandString() string { - return randString(c.Rand) -} - -// RandUint64 makes random 64 bit numbers. -// Weirdly, rand doesn't have a function that gives you 64 random bits. -func (c Continue) RandUint64() uint64 { - return randUint64(c.Rand) -} - -// RandBool returns true or false randomly. -func (c Continue) RandBool() bool { - return randBool(c.Rand) -} - -func fuzzInt(v reflect.Value, r *rand.Rand) { - v.SetInt(int64(randUint64(r))) -} - -func fuzzUint(v reflect.Value, r *rand.Rand) { - v.SetUint(randUint64(r)) -} - -func fuzzTime(t *time.Time, c Continue) { - var sec, nsec int64 - // Allow for about 1000 years of random time values, which keeps things - // like JSON parsing reasonably happy. - sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60) - c.Fuzz(&nsec) - *t = time.Unix(sec, nsec) -} - -var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){ - reflect.Bool: func(v reflect.Value, r *rand.Rand) { - v.SetBool(randBool(r)) - }, - reflect.Int: fuzzInt, - reflect.Int8: fuzzInt, - reflect.Int16: fuzzInt, - reflect.Int32: fuzzInt, - reflect.Int64: fuzzInt, - reflect.Uint: fuzzUint, - reflect.Uint8: fuzzUint, - reflect.Uint16: fuzzUint, - reflect.Uint32: fuzzUint, - reflect.Uint64: fuzzUint, - reflect.Uintptr: fuzzUint, - reflect.Float32: func(v reflect.Value, r *rand.Rand) { - v.SetFloat(float64(r.Float32())) - }, - reflect.Float64: func(v reflect.Value, r *rand.Rand) { - v.SetFloat(r.Float64()) - }, - reflect.Complex64: func(v reflect.Value, r *rand.Rand) { - v.SetComplex(complex128(complex(r.Float32(), r.Float32()))) - }, - reflect.Complex128: func(v reflect.Value, r *rand.Rand) { - v.SetComplex(complex(r.Float64(), r.Float64())) - }, - reflect.String: func(v reflect.Value, r *rand.Rand) { - v.SetString(randString(r)) - }, - reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) { - panic("unimplemented") - }, -} - -// randBool returns true or false randomly. -func randBool(r *rand.Rand) bool { - return r.Int31()&(1<<30) == 0 -} - -type int63nPicker interface { - Int63n(int64) int64 -} - -// UnicodeRange describes a sequential range of unicode characters. -// Last must be numerically greater than First. -type UnicodeRange struct { - First, Last rune -} - -// UnicodeRanges describes an arbitrary number of sequential ranges of unicode characters. -// To be useful, each range must have at least one character (First <= Last) and -// there must be at least one range. -type UnicodeRanges []UnicodeRange - -// choose returns a random unicode character from the given range, using the -// given randomness source. -func (ur UnicodeRange) choose(r int63nPicker) rune { - count := int64(ur.Last - ur.First + 1) - return ur.First + rune(r.Int63n(count)) -} - -// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings. -// Each character is selected from the range ur. If there are no characters -// in the range (cr.Last < cr.First), this will panic. -func (ur UnicodeRange) CustomStringFuzzFunc() func(s *string, c Continue) { - ur.check() - return func(s *string, c Continue) { - *s = ur.randString(c.Rand) - } -} - -// check is a function that used to check whether the first of ur(UnicodeRange) -// is greater than the last one. -func (ur UnicodeRange) check() { - if ur.Last < ur.First { - panic("The last encoding must be greater than the first one.") - } -} - -// randString of UnicodeRange makes a random string up to 20 characters long. -// Each character is selected form ur(UnicodeRange). -func (ur UnicodeRange) randString(r *rand.Rand) string { - n := r.Intn(20) - sb := strings.Builder{} - sb.Grow(n) - for i := 0; i < n; i++ { - sb.WriteRune(ur.choose(r)) - } - return sb.String() -} - -// defaultUnicodeRanges sets a default unicode range when user do not set -// CustomStringFuzzFunc() but wants fuzz string. -var defaultUnicodeRanges = UnicodeRanges{ - {' ', '~'}, // ASCII characters - {'\u00a0', '\u02af'}, // Multi-byte encoded characters - {'\u4e00', '\u9fff'}, // Common CJK (even longer encodings) -} - -// CustomStringFuzzFunc constructs a FuzzFunc which produces random strings. -// Each character is selected from one of the ranges of ur(UnicodeRanges). -// Each range has an equal probability of being chosen. If there are no ranges, -// or a selected range has no characters (.Last < .First), this will panic. -// Do not modify any of the ranges in ur after calling this function. -func (ur UnicodeRanges) CustomStringFuzzFunc() func(s *string, c Continue) { - // Check unicode ranges slice is empty. - if len(ur) == 0 { - panic("UnicodeRanges is empty.") - } - // if not empty, each range should be checked. - for i := range ur { - ur[i].check() - } - return func(s *string, c Continue) { - *s = ur.randString(c.Rand) - } -} - -// randString of UnicodeRanges makes a random string up to 20 characters long. -// Each character is selected form one of the ranges of ur(UnicodeRanges), -// and each range has an equal probability of being chosen. -func (ur UnicodeRanges) randString(r *rand.Rand) string { - n := r.Intn(20) - sb := strings.Builder{} - sb.Grow(n) - for i := 0; i < n; i++ { - sb.WriteRune(ur[r.Intn(len(ur))].choose(r)) - } - return sb.String() -} - -// randString makes a random string up to 20 characters long. The returned string -// may include a variety of (valid) UTF-8 encodings. -func randString(r *rand.Rand) string { - return defaultUnicodeRanges.randString(r) -} - -// randUint64 makes random 64 bit numbers. -// Weirdly, rand doesn't have a function that gives you 64 random bits. -func randUint64(r *rand.Rand) uint64 { - return uint64(r.Uint32())<<32 | uint64(r.Uint32()) -} diff --git a/go-controller/vendor/github.com/gorilla/websocket/README.md b/go-controller/vendor/github.com/gorilla/websocket/README.md index 2517a28715..ff8bfab0b2 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/README.md +++ b/go-controller/vendor/github.com/gorilla/websocket/README.md @@ -7,19 +7,13 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. ---- - -āš ļø **[The Gorilla WebSocket Package is looking for a new maintainer](https://github.com/gorilla/websocket/issues/370)** - ---- - ### Documentation * [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) -* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) -* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) -* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) -* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) +* [Chat example](https://github.com/gorilla/websocket/tree/main/examples/chat) +* [Command example](https://github.com/gorilla/websocket/tree/main/examples/command) +* [Client and server example](https://github.com/gorilla/websocket/tree/main/examples/echo) +* [File watch example](https://github.com/gorilla/websocket/tree/main/examples/filewatch) ### Status @@ -35,5 +29,4 @@ package API is stable. The Gorilla WebSocket package passes the server tests in the [Autobahn Test Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn -subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). - +subdirectory](https://github.com/gorilla/websocket/tree/main/examples/autobahn). diff --git a/go-controller/vendor/github.com/gorilla/websocket/client.go b/go-controller/vendor/github.com/gorilla/websocket/client.go index 2efd83555d..00917ea341 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/client.go +++ b/go-controller/vendor/github.com/gorilla/websocket/client.go @@ -9,8 +9,8 @@ import ( "context" "crypto/tls" "errors" + "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptrace" @@ -51,18 +51,34 @@ func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufS // // It is safe to call Dialer's methods concurrently. type Dialer struct { + // The following custom dial functions can be set to establish + // connections to either the backend server or the proxy (if it + // exists). The scheme of the dialed entity (either backend or + // proxy) determines which custom dial function is selected: + // either NetDialTLSContext for HTTPS or NetDialContext/NetDial + // for HTTP. Since the "Proxy" function can determine the scheme + // dynamically, it can make sense to set multiple custom dial + // functions simultaneously. + // // NetDial specifies the dial function for creating TCP connections. If - // NetDial is nil, net.Dial is used. + // NetDial is nil, net.Dialer DialContext is used. + // If "Proxy" field is also set, this function dials the proxy--not + // the backend server. NetDial func(network, addr string) (net.Conn, error) // NetDialContext specifies the dial function for creating TCP connections. If // NetDialContext is nil, NetDial is used. + // If "Proxy" field is also set, this function dials the proxy--not + // the backend server. NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) // NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If // NetDialTLSContext is nil, NetDialContext is used. // If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and // TLSClientConfig is ignored. + // If "Proxy" field is also set, this function dials the proxy (and performs + // the TLS handshake with the proxy, ignoring TLSClientConfig). In this TLS proxy + // dialing case the TLSClientConfig could still be necessary for TLS to the backend server. NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) // Proxy specifies a function to return a proxy for a given @@ -73,7 +89,7 @@ type Dialer struct { // TLSClientConfig specifies the TLS configuration to use with tls.Client. // If nil, the default configuration is used. - // If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake + // If NetDialTLSContext is set, Dial assumes the TLS handshake // is done there and TLSClientConfig is ignored. TLSClientConfig *tls.Config @@ -244,71 +260,16 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h defer cancel() } - // Get network dial function. - var netDial func(network, add string) (net.Conn, error) - - switch u.Scheme { - case "http": - if d.NetDialContext != nil { - netDial = func(network, addr string) (net.Conn, error) { - return d.NetDialContext(ctx, network, addr) - } - } else if d.NetDial != nil { - netDial = d.NetDial - } - case "https": - if d.NetDialTLSContext != nil { - netDial = func(network, addr string) (net.Conn, error) { - return d.NetDialTLSContext(ctx, network, addr) - } - } else if d.NetDialContext != nil { - netDial = func(network, addr string) (net.Conn, error) { - return d.NetDialContext(ctx, network, addr) - } - } else if d.NetDial != nil { - netDial = d.NetDial - } - default: - return nil, nil, errMalformedURL - } - - if netDial == nil { - netDialer := &net.Dialer{} - netDial = func(network, addr string) (net.Conn, error) { - return netDialer.DialContext(ctx, network, addr) - } - } - - // If needed, wrap the dial function to set the connection deadline. - if deadline, ok := ctx.Deadline(); ok { - forwardDial := netDial - netDial = func(network, addr string) (net.Conn, error) { - c, err := forwardDial(network, addr) - if err != nil { - return nil, err - } - err = c.SetDeadline(deadline) - if err != nil { - c.Close() - return nil, err - } - return c, nil - } - } - - // If needed, wrap the dial function to connect through a proxy. + var proxyURL *url.URL if d.Proxy != nil { - proxyURL, err := d.Proxy(req) + proxyURL, err = d.Proxy(req) if err != nil { return nil, nil, err } - if proxyURL != nil { - dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) - if err != nil { - return nil, nil, err - } - netDial = dialer.Dial - } + } + netDial, err := d.netDialFn(ctx, proxyURL, u) + if err != nil { + return nil, nil, err } hostPort, hostNoPort := hostPortNoPort(u) @@ -317,24 +278,30 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h trace.GetConn(hostPort) } - netConn, err := netDial("tcp", hostPort) + netConn, err := netDial(ctx, "tcp", hostPort) + if err != nil { + return nil, nil, err + } if trace != nil && trace.GotConn != nil { trace.GotConn(httptrace.GotConnInfo{ Conn: netConn, }) } - if err != nil { - return nil, nil, err - } + // Close the network connection when returning an error. The variable + // netConn is set to nil before the success return at the end of the + // function. defer func() { if netConn != nil { - netConn.Close() + // It's safe to ignore the error from Close() because this code is + // only executed when returning a more important error to the + // application. + _ = netConn.Close() } }() - if u.Scheme == "https" && d.NetDialTLSContext == nil { - // If NetDialTLSContext is set, assume that the TLS handshake has already been done + // Do TLS handshake over established connection if a proxy exists. + if proxyURL != nil && u.Scheme == "https" { cfg := cloneTLSConfig(d.TLSClientConfig) if cfg.ServerName == "" { @@ -370,6 +337,17 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h resp, err := http.ReadResponse(conn.br, req) if err != nil { + if d.TLSClientConfig != nil { + for _, proto := range d.TLSClientConfig.NextProtos { + if proto != "http/1.1" { + return nil, nil, fmt.Errorf( + "websocket: protocol %q was given but is not supported;"+ + "sharing tls.Config with net/http Transport can cause this error: %w", + proto, err, + ) + } + } + } return nil, nil, err } @@ -388,7 +366,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h // debugging. buf := make([]byte, 1024) n, _ := io.ReadFull(resp.Body, buf) - resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) + resp.Body = io.NopCloser(bytes.NewReader(buf[:n])) return nil, resp, ErrBadHandshake } @@ -406,17 +384,134 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h break } - resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) + resp.Body = io.NopCloser(bytes.NewReader([]byte{})) conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") - netConn.SetDeadline(time.Time{}) - netConn = nil // to avoid close in defer. + if err := netConn.SetDeadline(time.Time{}); err != nil { + return nil, resp, err + } + + // Success! Set netConn to nil to stop the deferred function above from + // closing the network connection. + netConn = nil + return conn, resp, nil } +// Returns the dial function to establish the connection to either the backend +// server or the proxy (if it exists). If the dialed entity is HTTPS, then the +// returned dial function *also* performs the TLS handshake to the dialed entity. +// NOTE: If a proxy exists, it is possible for a second TLS handshake to be +// necessary over the established connection. +func (d *Dialer) netDialFn(ctx context.Context, proxyURL *url.URL, backendURL *url.URL) (netDialerFunc, error) { + var netDial netDialerFunc + if proxyURL != nil { + netDial = d.netDialFromURL(proxyURL) + } else { + netDial = d.netDialFromURL(backendURL) + } + // If needed, wrap the dial function to set the connection deadline. + if deadline, ok := ctx.Deadline(); ok { + netDial = netDialWithDeadline(netDial, deadline) + } + // Proxy dialing is wrapped to implement CONNECT method and possibly proxy auth. + if proxyURL != nil { + return proxyFromURL(proxyURL, netDial) + } + return netDial, nil +} + +// Returns function to create the connection depending on the Dialer's +// custom dialing functions and the passed URL of entity connecting to. +func (d *Dialer) netDialFromURL(u *url.URL) netDialerFunc { + var netDial netDialerFunc + switch { + case d.NetDialContext != nil: + netDial = d.NetDialContext + case d.NetDial != nil: + netDial = func(ctx context.Context, net, addr string) (net.Conn, error) { + return d.NetDial(net, addr) + } + default: + netDial = (&net.Dialer{}).DialContext + } + // If dialed entity is HTTPS, then either use custom TLS dialing function (if exists) + // or wrap the previously computed "netDial" to use TLS config for handshake. + if u.Scheme == "https" { + if d.NetDialTLSContext != nil { + netDial = d.NetDialTLSContext + } else { + netDial = netDialWithTLSHandshake(netDial, d.TLSClientConfig, u) + } + } + return netDial +} + +// Returns wrapped "netDial" function, performing TLS handshake after connecting. +func netDialWithTLSHandshake(netDial netDialerFunc, tlsConfig *tls.Config, u *url.URL) netDialerFunc { + return func(ctx context.Context, unused, addr string) (net.Conn, error) { + hostPort, hostNoPort := hostPortNoPort(u) + trace := httptrace.ContextClientTrace(ctx) + if trace != nil && trace.GetConn != nil { + trace.GetConn(hostPort) + } + // Creates TCP connection to addr using passed "netDial" function. + conn, err := netDial(ctx, "tcp", addr) + if err != nil { + return nil, err + } + cfg := cloneTLSConfig(tlsConfig) + if cfg.ServerName == "" { + cfg.ServerName = hostNoPort + } + tlsConn := tls.Client(conn, cfg) + // Do the TLS handshake using TLSConfig over the wrapped connection. + if trace != nil && trace.TLSHandshakeStart != nil { + trace.TLSHandshakeStart() + } + err = doHandshake(ctx, tlsConn, cfg) + if trace != nil && trace.TLSHandshakeDone != nil { + trace.TLSHandshakeDone(tlsConn.ConnectionState(), err) + } + if err != nil { + tlsConn.Close() + return nil, err + } + return tlsConn, nil + } +} + +// Returns wrapped "netDial" function, setting passed deadline. +func netDialWithDeadline(netDial netDialerFunc, deadline time.Time) netDialerFunc { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + c, err := netDial(ctx, network, addr) + if err != nil { + return nil, err + } + err = c.SetDeadline(deadline) + if err != nil { + c.Close() + return nil, err + } + return c, nil + } +} + func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { return &tls.Config{} } return cfg.Clone() } + +func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error { + if err := tlsConn.HandshakeContext(ctx); err != nil { + return err + } + if !cfg.InsecureSkipVerify { + if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { + return err + } + } + return nil +} diff --git a/go-controller/vendor/github.com/gorilla/websocket/compression.go b/go-controller/vendor/github.com/gorilla/websocket/compression.go index 813ffb1e84..fe1079edbc 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/compression.go +++ b/go-controller/vendor/github.com/gorilla/websocket/compression.go @@ -33,7 +33,11 @@ func decompressNoContextTakeover(r io.Reader) io.ReadCloser { "\x01\x00\x00\xff\xff" fr, _ := flateReaderPool.Get().(io.ReadCloser) - fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) + mr := io.MultiReader(r, strings.NewReader(tail)) + if err := fr.(flate.Resetter).Reset(mr, nil); err != nil { + // Reset never fails, but handle error in case that changes. + fr = flate.NewReader(mr) + } return &flateReadWrapper{fr} } diff --git a/go-controller/vendor/github.com/gorilla/websocket/conn.go b/go-controller/vendor/github.com/gorilla/websocket/conn.go index 331eebc850..9562ffd497 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/conn.go +++ b/go-controller/vendor/github.com/gorilla/websocket/conn.go @@ -6,11 +6,10 @@ package websocket import ( "bufio" + "crypto/rand" "encoding/binary" "errors" "io" - "io/ioutil" - "math/rand" "net" "strconv" "strings" @@ -181,16 +180,16 @@ var ( errInvalidControlFrame = errors.New("websocket: invalid control frame") ) -func newMaskKey() [4]byte { - n := rand.Uint32() - return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} -} +// maskRand is an io.Reader for generating mask bytes. The reader is initialized +// to crypto/rand Reader. Tests swap the reader to a math/rand reader for +// reproducible results. +var maskRand = rand.Reader -func hideTempErr(err error) error { - if e, ok := err.(net.Error); ok && e.Temporary() { - err = &netError{msg: e.Error(), timeout: e.Timeout()} - } - return err +// newMaskKey returns a new 32 bit value for masking client frames. +func newMaskKey() [4]byte { + var k [4]byte + _, _ = io.ReadFull(maskRand, k[:]) + return k } func isControl(frameType int) bool { @@ -358,7 +357,6 @@ func (c *Conn) RemoteAddr() net.Addr { // Write methods func (c *Conn) writeFatal(err error) error { - err = hideTempErr(err) c.writeErrMu.Lock() if c.writeErr == nil { c.writeErr = err @@ -372,7 +370,9 @@ func (c *Conn) read(n int) ([]byte, error) { if err == io.EOF { err = errUnexpectedEOF } - c.br.Discard(len(p)) + // Discard is guaranteed to succeed because the number of bytes to discard + // is less than or equal to the number of bytes buffered. + _, _ = c.br.Discard(len(p)) return p, err } @@ -387,7 +387,9 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error return err } - c.conn.SetWriteDeadline(deadline) + if err := c.conn.SetWriteDeadline(deadline); err != nil { + return c.writeFatal(err) + } if len(buf1) == 0 { _, err = c.conn.Write(buf0) } else { @@ -397,7 +399,7 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error return c.writeFatal(err) } if frameType == CloseMessage { - c.writeFatal(ErrCloseSent) + _ = c.writeFatal(ErrCloseSent) } return nil } @@ -436,21 +438,27 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er maskBytes(key, 0, buf[6:]) } - d := 1000 * time.Hour - if !deadline.IsZero() { - d = deadline.Sub(time.Now()) + if deadline.IsZero() { + // No timeout for zero time. + <-c.mu + } else { + d := time.Until(deadline) if d < 0 { return errWriteTimeout } + select { + case <-c.mu: + default: + timer := time.NewTimer(d) + select { + case <-c.mu: + timer.Stop() + case <-timer.C: + return errWriteTimeout + } + } } - timer := time.NewTimer(d) - select { - case <-c.mu: - timer.Stop() - case <-timer.C: - return errWriteTimeout - } defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() @@ -460,13 +468,14 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er return err } - c.conn.SetWriteDeadline(deadline) - _, err = c.conn.Write(buf) - if err != nil { + if err := c.conn.SetWriteDeadline(deadline); err != nil { + return c.writeFatal(err) + } + if _, err = c.conn.Write(buf); err != nil { return c.writeFatal(err) } if messageType == CloseMessage { - c.writeFatal(ErrCloseSent) + _ = c.writeFatal(ErrCloseSent) } return err } @@ -630,7 +639,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { } if final { - w.endMessage(errWriteClosed) + _ = w.endMessage(errWriteClosed) return nil } @@ -795,7 +804,7 @@ func (c *Conn) advanceFrame() (int, error) { // 1. Skip remainder of previous frame. if c.readRemaining > 0 { - if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { + if _, err := io.CopyN(io.Discard, c.br, c.readRemaining); err != nil { return noFrame, err } } @@ -817,7 +826,7 @@ func (c *Conn) advanceFrame() (int, error) { rsv2 := p[0]&rsv2Bit != 0 rsv3 := p[0]&rsv3Bit != 0 mask := p[1]&maskBit != 0 - c.setReadRemaining(int64(p[1] & 0x7f)) + _ = c.setReadRemaining(int64(p[1] & 0x7f)) // will not fail because argument is >= 0 c.readDecompress = false if rsv1 { @@ -922,7 +931,8 @@ func (c *Conn) advanceFrame() (int, error) { } if c.readLimit > 0 && c.readLength > c.readLimit { - c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) + // Make a best effort to send a close message describing the problem. + _ = c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) return noFrame, ErrReadLimit } @@ -934,7 +944,7 @@ func (c *Conn) advanceFrame() (int, error) { var payload []byte if c.readRemaining > 0 { payload, err = c.read(int(c.readRemaining)) - c.setReadRemaining(0) + _ = c.setReadRemaining(0) // will not fail because argument is >= 0 if err != nil { return noFrame, err } @@ -981,7 +991,8 @@ func (c *Conn) handleProtocolError(message string) error { if len(data) > maxControlFramePayloadSize { data = data[:maxControlFramePayloadSize] } - c.WriteControl(CloseMessage, data, time.Now().Add(writeWait)) + // Make a best effor to send a close message describing the problem. + _ = c.WriteControl(CloseMessage, data, time.Now().Add(writeWait)) return errors.New("websocket: " + message) } @@ -1008,7 +1019,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { for c.readErr == nil { frameType, err := c.advanceFrame() if err != nil { - c.readErr = hideTempErr(err) + c.readErr = err break } @@ -1048,13 +1059,13 @@ func (r *messageReader) Read(b []byte) (int, error) { b = b[:c.readRemaining] } n, err := c.br.Read(b) - c.readErr = hideTempErr(err) + c.readErr = err if c.isServer { c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) } rem := c.readRemaining rem -= int64(n) - c.setReadRemaining(rem) + _ = c.setReadRemaining(rem) // rem is guaranteed to be >= 0 if c.readRemaining > 0 && c.readErr == io.EOF { c.readErr = errUnexpectedEOF } @@ -1069,7 +1080,7 @@ func (r *messageReader) Read(b []byte) (int, error) { frameType, err := c.advanceFrame() switch { case err != nil: - c.readErr = hideTempErr(err) + c.readErr = err case frameType == TextMessage || frameType == BinaryMessage: c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") } @@ -1094,7 +1105,7 @@ func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { if err != nil { return messageType, nil, err } - p, err = ioutil.ReadAll(r) + p, err = io.ReadAll(r) return messageType, p, err } @@ -1136,7 +1147,8 @@ func (c *Conn) SetCloseHandler(h func(code int, text string) error) { if h == nil { h = func(code int, text string) error { message := FormatCloseMessage(code, "") - c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) + // Make a best effor to send the close message. + _ = c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) return nil } } @@ -1158,13 +1170,9 @@ func (c *Conn) PingHandler() func(appData string) error { func (c *Conn) SetPingHandler(h func(appData string) error) { if h == nil { h = func(message string) error { - err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) - if err == ErrCloseSent { - return nil - } else if e, ok := err.(net.Error); ok && e.Temporary() { - return nil - } - return err + // Make a best effort to send the pong message. + _ = c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) + return nil } } c.handlePing = h @@ -1189,8 +1197,16 @@ func (c *Conn) SetPongHandler(h func(appData string) error) { c.handlePong = h } +// NetConn returns the underlying connection that is wrapped by c. +// Note that writing to or reading from this connection directly will corrupt the +// WebSocket connection. +func (c *Conn) NetConn() net.Conn { + return c.conn +} + // UnderlyingConn returns the internal net.Conn. This can be used to further // modifications to connection specific flags. +// Deprecated: Use the NetConn method. func (c *Conn) UnderlyingConn() net.Conn { return c.conn } diff --git a/go-controller/vendor/github.com/gorilla/websocket/proxy.go b/go-controller/vendor/github.com/gorilla/websocket/proxy.go index e0f466b72f..d716a05884 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/proxy.go +++ b/go-controller/vendor/github.com/gorilla/websocket/proxy.go @@ -6,34 +6,52 @@ package websocket import ( "bufio" + "bytes" + "context" "encoding/base64" "errors" "net" "net/http" "net/url" "strings" + + "golang.org/x/net/proxy" ) -type netDialerFunc func(network, addr string) (net.Conn, error) +type netDialerFunc func(ctx context.Context, network, addr string) (net.Conn, error) func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { - return fn(network, addr) + return fn(context.Background(), network, addr) } -func init() { - proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { - return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil - }) +func (fn netDialerFunc) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + return fn(ctx, network, addr) +} + +func proxyFromURL(proxyURL *url.URL, forwardDial netDialerFunc) (netDialerFunc, error) { + if proxyURL.Scheme == "http" || proxyURL.Scheme == "https" { + return (&httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDial}).DialContext, nil + } + dialer, err := proxy.FromURL(proxyURL, forwardDial) + if err != nil { + return nil, err + } + if d, ok := dialer.(proxy.ContextDialer); ok { + return d.DialContext, nil + } + return func(ctx context.Context, net, addr string) (net.Conn, error) { + return dialer.Dial(net, addr) + }, nil } type httpProxyDialer struct { proxyURL *url.URL - forwardDial func(network, addr string) (net.Conn, error) + forwardDial netDialerFunc } -func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { +func (hpd *httpProxyDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { hostPort, _ := hostPortNoPort(hpd.proxyURL) - conn, err := hpd.forwardDial(network, hostPort) + conn, err := hpd.forwardDial(ctx, network, hostPort) if err != nil { return nil, err } @@ -46,7 +64,6 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) connectHeader.Set("Proxy-Authorization", "Basic "+credential) } } - connectReq := &http.Request{ Method: http.MethodConnect, URL: &url.URL{Opaque: addr}, @@ -59,7 +76,7 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) return nil, err } - // Read response. It's OK to use and discard buffered reader here becaue + // Read response. It's OK to use and discard buffered reader here because // the remote server does not speak until spoken to. br := bufio.NewReader(conn) resp, err := http.ReadResponse(br, connectReq) @@ -68,8 +85,18 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) return nil, err } - if resp.StatusCode != 200 { - conn.Close() + // Close the response body to silence false positives from linters. Reset + // the buffered reader first to ensure that Close() does not read from + // conn. + // Note: Applications must call resp.Body.Close() on a response returned + // http.ReadResponse to inspect trailers or read another response from the + // buffered reader. The call to resp.Body.Close() does not release + // resources. + br.Reset(bytes.NewReader(nil)) + _ = resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + _ = conn.Close() f := strings.SplitN(resp.Status, " ", 2) return nil, errors.New(f[1]) } diff --git a/go-controller/vendor/github.com/gorilla/websocket/server.go b/go-controller/vendor/github.com/gorilla/websocket/server.go index 24d53b38ab..02ea01fdcd 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/server.go +++ b/go-controller/vendor/github.com/gorilla/websocket/server.go @@ -6,8 +6,7 @@ package websocket import ( "bufio" - "errors" - "io" + "net" "net/http" "net/url" "strings" @@ -101,8 +100,8 @@ func checkSameOrigin(r *http.Request) bool { func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { if u.Subprotocols != nil { clientProtocols := Subprotocols(r) - for _, serverProtocol := range u.Subprotocols { - for _, clientProtocol := range clientProtocols { + for _, clientProtocol := range clientProtocols { + for _, serverProtocol := range u.Subprotocols { if clientProtocol == serverProtocol { return clientProtocol } @@ -130,7 +129,8 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { - return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") + w.Header().Set("Upgrade", "websocket") + return u.returnError(w, r, http.StatusUpgradeRequired, badHandshake+"'websocket' token not found in 'Upgrade' header") } if r.Method != http.MethodGet { @@ -154,8 +154,8 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } challengeKey := r.Header.Get("Sec-Websocket-Key") - if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") + if !isValidChallengeKey(challengeKey) { + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header must be Base64 encoded value of 16-byte in length") } subprotocol := u.selectSubprotocol(r, responseHeader) @@ -172,28 +172,37 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } } - h, ok := w.(http.Hijacker) - if !ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") - } - var brw *bufio.ReadWriter - netConn, brw, err := h.Hijack() + netConn, brw, err := http.NewResponseController(w).Hijack() if err != nil { - return u.returnError(w, r, http.StatusInternalServerError, err.Error()) + return u.returnError(w, r, http.StatusInternalServerError, + "websocket: hijack: "+err.Error()) } - if brw.Reader.Buffered() > 0 { - netConn.Close() - return nil, errors.New("websocket: client sent data before handshake is complete") - } + // Close the network connection when returning an error. The variable + // netConn is set to nil before the success return at the end of the + // function. + defer func() { + if netConn != nil { + // It's safe to ignore the error from Close() because this code is + // only executed when returning a more important error to the + // application. + _ = netConn.Close() + } + }() var br *bufio.Reader - if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { - // Reuse hijacked buffered reader as connection reader. + if u.ReadBufferSize == 0 && brw.Reader.Size() > 256 { + // Use hijacked buffered reader as the connection reader. br = brw.Reader + } else if brw.Reader.Buffered() > 0 { + // Wrap the network connection to read buffered data in brw.Reader + // before reading from the network connection. This should be rare + // because a client must not send message data before receiving the + // handshake response. + netConn = &brNetConn{br: brw.Reader, Conn: netConn} } - buf := bufioWriterBuffer(netConn, brw.Writer) + buf := brw.Writer.AvailableBuffer() var writeBuf []byte if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { @@ -247,20 +256,30 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } p = append(p, "\r\n"...) - // Clear deadlines set by HTTP server. - netConn.SetDeadline(time.Time{}) - if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) + if err := netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)); err != nil { + return nil, err + } + } else { + // Clear deadlines set by HTTP server. + if err := netConn.SetDeadline(time.Time{}); err != nil { + return nil, err + } } + if _, err = netConn.Write(p); err != nil { - netConn.Close() return nil, err } if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Time{}) + if err := netConn.SetWriteDeadline(time.Time{}); err != nil { + return nil, err + } } + // Success! Set netConn to nil to stop the deferred function above from + // closing the network connection. + netConn = nil + return c, nil } @@ -327,39 +346,28 @@ func IsWebSocketUpgrade(r *http.Request) bool { tokenListContainsValue(r.Header, "Upgrade", "websocket") } -// bufioReaderSize size returns the size of a bufio.Reader. -func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { - // This code assumes that peek on a reset reader returns - // bufio.Reader.buf[:0]. - // TODO: Use bufio.Reader.Size() after Go 1.10 - br.Reset(originalReader) - if p, err := br.Peek(0); err == nil { - return cap(p) - } - return 0 +type brNetConn struct { + br *bufio.Reader + net.Conn } -// writeHook is an io.Writer that records the last slice passed to it vio -// io.Writer.Write. -type writeHook struct { - p []byte +func (b *brNetConn) Read(p []byte) (n int, err error) { + if b.br != nil { + // Limit read to buferred data. + if n := b.br.Buffered(); len(p) > n { + p = p[:n] + } + n, err = b.br.Read(p) + if b.br.Buffered() == 0 { + b.br = nil + } + return n, err + } + return b.Conn.Read(p) } -func (wh *writeHook) Write(p []byte) (int, error) { - wh.p = p - return len(p), nil +// NetConn returns the underlying connection that is wrapped by b. +func (b *brNetConn) NetConn() net.Conn { + return b.Conn } -// bufioWriterBuffer grabs the buffer from a bufio.Writer. -func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { - // This code assumes that bufio.Writer.buf[:1] is passed to the - // bufio.Writer's underlying writer. - var wh writeHook - bw.Reset(&wh) - bw.WriteByte(0) - bw.Flush() - - bw.Reset(originalWriter) - - return wh.p[:cap(wh.p)] -} diff --git a/go-controller/vendor/github.com/gorilla/websocket/tls_handshake.go b/go-controller/vendor/github.com/gorilla/websocket/tls_handshake.go deleted file mode 100644 index a62b68ccb1..0000000000 --- a/go-controller/vendor/github.com/gorilla/websocket/tls_handshake.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build go1.17 -// +build go1.17 - -package websocket - -import ( - "context" - "crypto/tls" -) - -func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error { - if err := tlsConn.HandshakeContext(ctx); err != nil { - return err - } - if !cfg.InsecureSkipVerify { - if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { - return err - } - } - return nil -} diff --git a/go-controller/vendor/github.com/gorilla/websocket/tls_handshake_116.go b/go-controller/vendor/github.com/gorilla/websocket/tls_handshake_116.go deleted file mode 100644 index e1b2b44f6e..0000000000 --- a/go-controller/vendor/github.com/gorilla/websocket/tls_handshake_116.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !go1.17 -// +build !go1.17 - -package websocket - -import ( - "context" - "crypto/tls" -) - -func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error { - if err := tlsConn.Handshake(); err != nil { - return err - } - if !cfg.InsecureSkipVerify { - if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { - return err - } - } - return nil -} diff --git a/go-controller/vendor/github.com/gorilla/websocket/util.go b/go-controller/vendor/github.com/gorilla/websocket/util.go index 7bf2f66c67..31a5dee646 100644 --- a/go-controller/vendor/github.com/gorilla/websocket/util.go +++ b/go-controller/vendor/github.com/gorilla/websocket/util.go @@ -281,3 +281,18 @@ headers: } return result } + +// isValidChallengeKey checks if the argument meets RFC6455 specification. +func isValidChallengeKey(s string) bool { + // From RFC6455: + // + // A |Sec-WebSocket-Key| header field with a base64-encoded (see + // Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in + // length. + + if s == "" { + return false + } + decoded, err := base64.StdEncoding.DecodeString(s) + return err == nil && len(decoded) == 16 +} diff --git a/go-controller/vendor/github.com/gorilla/websocket/x_net_proxy.go b/go-controller/vendor/github.com/gorilla/websocket/x_net_proxy.go deleted file mode 100644 index 2e668f6b88..0000000000 --- a/go-controller/vendor/github.com/gorilla/websocket/x_net_proxy.go +++ /dev/null @@ -1,473 +0,0 @@ -// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. -//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy - -// Package proxy provides support for a variety of protocols to proxy network -// data. -// - -package websocket - -import ( - "errors" - "io" - "net" - "net/url" - "os" - "strconv" - "strings" - "sync" -) - -type proxy_direct struct{} - -// Direct is a direct proxy: one that makes network connections directly. -var proxy_Direct = proxy_direct{} - -func (proxy_direct) Dial(network, addr string) (net.Conn, error) { - return net.Dial(network, addr) -} - -// A PerHost directs connections to a default Dialer unless the host name -// requested matches one of a number of exceptions. -type proxy_PerHost struct { - def, bypass proxy_Dialer - - bypassNetworks []*net.IPNet - bypassIPs []net.IP - bypassZones []string - bypassHosts []string -} - -// NewPerHost returns a PerHost Dialer that directs connections to either -// defaultDialer or bypass, depending on whether the connection matches one of -// the configured rules. -func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost { - return &proxy_PerHost{ - def: defaultDialer, - bypass: bypass, - } -} - -// Dial connects to the address addr on the given network through either -// defaultDialer or bypass. -func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) { - host, _, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - - return p.dialerForRequest(host).Dial(network, addr) -} - -func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer { - if ip := net.ParseIP(host); ip != nil { - for _, net := range p.bypassNetworks { - if net.Contains(ip) { - return p.bypass - } - } - for _, bypassIP := range p.bypassIPs { - if bypassIP.Equal(ip) { - return p.bypass - } - } - return p.def - } - - for _, zone := range p.bypassZones { - if strings.HasSuffix(host, zone) { - return p.bypass - } - if host == zone[1:] { - // For a zone ".example.com", we match "example.com" - // too. - return p.bypass - } - } - for _, bypassHost := range p.bypassHosts { - if bypassHost == host { - return p.bypass - } - } - return p.def -} - -// AddFromString parses a string that contains comma-separated values -// specifying hosts that should use the bypass proxy. Each value is either an -// IP address, a CIDR range, a zone (*.example.com) or a host name -// (localhost). A best effort is made to parse the string and errors are -// ignored. -func (p *proxy_PerHost) AddFromString(s string) { - hosts := strings.Split(s, ",") - for _, host := range hosts { - host = strings.TrimSpace(host) - if len(host) == 0 { - continue - } - if strings.Contains(host, "/") { - // We assume that it's a CIDR address like 127.0.0.0/8 - if _, net, err := net.ParseCIDR(host); err == nil { - p.AddNetwork(net) - } - continue - } - if ip := net.ParseIP(host); ip != nil { - p.AddIP(ip) - continue - } - if strings.HasPrefix(host, "*.") { - p.AddZone(host[1:]) - continue - } - p.AddHost(host) - } -} - -// AddIP specifies an IP address that will use the bypass proxy. Note that -// this will only take effect if a literal IP address is dialed. A connection -// to a named host will never match an IP. -func (p *proxy_PerHost) AddIP(ip net.IP) { - p.bypassIPs = append(p.bypassIPs, ip) -} - -// AddNetwork specifies an IP range that will use the bypass proxy. Note that -// this will only take effect if a literal IP address is dialed. A connection -// to a named host will never match. -func (p *proxy_PerHost) AddNetwork(net *net.IPNet) { - p.bypassNetworks = append(p.bypassNetworks, net) -} - -// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of -// "example.com" matches "example.com" and all of its subdomains. -func (p *proxy_PerHost) AddZone(zone string) { - if strings.HasSuffix(zone, ".") { - zone = zone[:len(zone)-1] - } - if !strings.HasPrefix(zone, ".") { - zone = "." + zone - } - p.bypassZones = append(p.bypassZones, zone) -} - -// AddHost specifies a host name that will use the bypass proxy. -func (p *proxy_PerHost) AddHost(host string) { - if strings.HasSuffix(host, ".") { - host = host[:len(host)-1] - } - p.bypassHosts = append(p.bypassHosts, host) -} - -// A Dialer is a means to establish a connection. -type proxy_Dialer interface { - // Dial connects to the given address via the proxy. - Dial(network, addr string) (c net.Conn, err error) -} - -// Auth contains authentication parameters that specific Dialers may require. -type proxy_Auth struct { - User, Password string -} - -// FromEnvironment returns the dialer specified by the proxy related variables in -// the environment. -func proxy_FromEnvironment() proxy_Dialer { - allProxy := proxy_allProxyEnv.Get() - if len(allProxy) == 0 { - return proxy_Direct - } - - proxyURL, err := url.Parse(allProxy) - if err != nil { - return proxy_Direct - } - proxy, err := proxy_FromURL(proxyURL, proxy_Direct) - if err != nil { - return proxy_Direct - } - - noProxy := proxy_noProxyEnv.Get() - if len(noProxy) == 0 { - return proxy - } - - perHost := proxy_NewPerHost(proxy, proxy_Direct) - perHost.AddFromString(noProxy) - return perHost -} - -// proxySchemes is a map from URL schemes to a function that creates a Dialer -// from a URL with such a scheme. -var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error) - -// RegisterDialerType takes a URL scheme and a function to generate Dialers from -// a URL with that scheme and a forwarding Dialer. Registered schemes are used -// by FromURL. -func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) { - if proxy_proxySchemes == nil { - proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) - } - proxy_proxySchemes[scheme] = f -} - -// FromURL returns a Dialer given a URL specification and an underlying -// Dialer for it to make network requests. -func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) { - var auth *proxy_Auth - if u.User != nil { - auth = new(proxy_Auth) - auth.User = u.User.Username() - if p, ok := u.User.Password(); ok { - auth.Password = p - } - } - - switch u.Scheme { - case "socks5": - return proxy_SOCKS5("tcp", u.Host, auth, forward) - } - - // If the scheme doesn't match any of the built-in schemes, see if it - // was registered by another package. - if proxy_proxySchemes != nil { - if f, ok := proxy_proxySchemes[u.Scheme]; ok { - return f(u, forward) - } - } - - return nil, errors.New("proxy: unknown scheme: " + u.Scheme) -} - -var ( - proxy_allProxyEnv = &proxy_envOnce{ - names: []string{"ALL_PROXY", "all_proxy"}, - } - proxy_noProxyEnv = &proxy_envOnce{ - names: []string{"NO_PROXY", "no_proxy"}, - } -) - -// envOnce looks up an environment variable (optionally by multiple -// names) once. It mitigates expensive lookups on some platforms -// (e.g. Windows). -// (Borrowed from net/http/transport.go) -type proxy_envOnce struct { - names []string - once sync.Once - val string -} - -func (e *proxy_envOnce) Get() string { - e.once.Do(e.init) - return e.val -} - -func (e *proxy_envOnce) init() { - for _, n := range e.names { - e.val = os.Getenv(n) - if e.val != "" { - return - } - } -} - -// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address -// with an optional username and password. See RFC 1928 and RFC 1929. -func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) { - s := &proxy_socks5{ - network: network, - addr: addr, - forward: forward, - } - if auth != nil { - s.user = auth.User - s.password = auth.Password - } - - return s, nil -} - -type proxy_socks5 struct { - user, password string - network, addr string - forward proxy_Dialer -} - -const proxy_socks5Version = 5 - -const ( - proxy_socks5AuthNone = 0 - proxy_socks5AuthPassword = 2 -) - -const proxy_socks5Connect = 1 - -const ( - proxy_socks5IP4 = 1 - proxy_socks5Domain = 3 - proxy_socks5IP6 = 4 -) - -var proxy_socks5Errors = []string{ - "", - "general failure", - "connection forbidden", - "network unreachable", - "host unreachable", - "connection refused", - "TTL expired", - "command not supported", - "address type not supported", -} - -// Dial connects to the address addr on the given network via the SOCKS5 proxy. -func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) { - switch network { - case "tcp", "tcp6", "tcp4": - default: - return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) - } - - conn, err := s.forward.Dial(s.network, s.addr) - if err != nil { - return nil, err - } - if err := s.connect(conn, addr); err != nil { - conn.Close() - return nil, err - } - return conn, nil -} - -// connect takes an existing connection to a socks5 proxy server, -// and commands the server to extend that connection to target, -// which must be a canonical address with a host and port. -func (s *proxy_socks5) connect(conn net.Conn, target string) error { - host, portStr, err := net.SplitHostPort(target) - if err != nil { - return err - } - - port, err := strconv.Atoi(portStr) - if err != nil { - return errors.New("proxy: failed to parse port number: " + portStr) - } - if port < 1 || port > 0xffff { - return errors.New("proxy: port number out of range: " + portStr) - } - - // the size here is just an estimate - buf := make([]byte, 0, 6+len(host)) - - buf = append(buf, proxy_socks5Version) - if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { - buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword) - } else { - buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone) - } - - if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - if buf[0] != 5 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) - } - if buf[1] == 0xff { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") - } - - // See RFC 1929 - if buf[1] == proxy_socks5AuthPassword { - buf = buf[:0] - buf = append(buf, 1 /* password protocol version */) - buf = append(buf, uint8(len(s.user))) - buf = append(buf, s.user...) - buf = append(buf, uint8(len(s.password))) - buf = append(buf, s.password...) - - if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - if buf[1] != 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") - } - } - - buf = buf[:0] - buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */) - - if ip := net.ParseIP(host); ip != nil { - if ip4 := ip.To4(); ip4 != nil { - buf = append(buf, proxy_socks5IP4) - ip = ip4 - } else { - buf = append(buf, proxy_socks5IP6) - } - buf = append(buf, ip...) - } else { - if len(host) > 255 { - return errors.New("proxy: destination host name too long: " + host) - } - buf = append(buf, proxy_socks5Domain) - buf = append(buf, byte(len(host))) - buf = append(buf, host...) - } - buf = append(buf, byte(port>>8), byte(port)) - - if _, err := conn.Write(buf); err != nil { - return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - if _, err := io.ReadFull(conn, buf[:4]); err != nil { - return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - failure := "unknown error" - if int(buf[1]) < len(proxy_socks5Errors) { - failure = proxy_socks5Errors[buf[1]] - } - - if len(failure) > 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) - } - - bytesToDiscard := 0 - switch buf[3] { - case proxy_socks5IP4: - bytesToDiscard = net.IPv4len - case proxy_socks5IP6: - bytesToDiscard = net.IPv6len - case proxy_socks5Domain: - _, err := io.ReadFull(conn, buf[:1]) - if err != nil { - return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - bytesToDiscard = int(buf[0]) - default: - return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) - } - - if cap(buf) < bytesToDiscard { - buf = make([]byte, bytesToDiscard) - } else { - buf = buf[:bytesToDiscard] - } - if _, err := io.ReadFull(conn, buf); err != nil { - return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - // Also need to discard the port number - if _, err := io.ReadFull(conn, buf[:2]); err != nil { - return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) - } - - return nil -} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/NOTICE b/go-controller/vendor/github.com/prometheus/client_golang/NOTICE index dd878a30ee..b9cc55abbb 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/NOTICE +++ b/go-controller/vendor/github.com/prometheus/client_golang/NOTICE @@ -16,8 +16,3 @@ Go support for Protocol Buffers - Google's data interchange format http://github.com/golang/protobuf/ Copyright 2010 The Go Authors See source code for license details. - -Support for streaming Protocol Buffer messages for the Go language (golang). -https://github.com/matttproud/golang_protobuf_extensions -Copyright 2013 Matt T. Proud -Licensed under the Apache License, Version 2.0 diff --git a/go-controller/vendor/github.com/golang/protobuf/LICENSE b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE similarity index 83% rename from go-controller/vendor/github.com/golang/protobuf/LICENSE rename to go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE index 0f646931a4..65d761bc9f 100644 --- a/go-controller/vendor/github.com/golang/protobuf/LICENSE +++ b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE @@ -1,16 +1,16 @@ -Copyright 2010 The Go Authors. All rights reserved. +Copyright (c) 2013 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -25,4 +25,3 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go new file mode 100644 index 0000000000..8547c8dfd1 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go @@ -0,0 +1,145 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +// Package header provides functions for parsing HTTP headers. +package header + +import ( + "net/http" + "strings" +) + +// Octet types from RFC 2616. +var octetTypes [256]octetType + +type octetType byte + +const ( + isToken octetType = 1 << iota + isSpace +) + +func init() { + // OCTET = + // CHAR = + // CTL = + // CR = + // LF = + // SP = + // HT = + // <"> = + // CRLF = CR LF + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> + // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT + // token = 1* + // qdtext = > + + for c := 0; c < 256; c++ { + var t octetType + isCtl := c <= 31 || c == 127 + isChar := 0 <= c && c <= 127 + isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) + if strings.ContainsRune(" \t\r\n", rune(c)) { + t |= isSpace + } + if isChar && !isCtl && !isSeparator { + t |= isToken + } + octetTypes[c] = t + } +} + +// AcceptSpec describes an Accept* header. +type AcceptSpec struct { + Value string + Q float64 +} + +// ParseAccept parses Accept* headers. +func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { +loop: + for _, s := range header[key] { + for { + var spec AcceptSpec + spec.Value, s = expectTokenSlash(s) + if spec.Value == "" { + continue loop + } + spec.Q = 1.0 + s = skipSpace(s) + if strings.HasPrefix(s, ";") { + s = skipSpace(s[1:]) + if !strings.HasPrefix(s, "q=") { + continue loop + } + spec.Q, s = expectQuality(s[2:]) + if spec.Q < 0.0 { + continue loop + } + } + specs = append(specs, spec) + s = skipSpace(s) + if !strings.HasPrefix(s, ",") { + continue loop + } + s = skipSpace(s[1:]) + } + } + return +} + +func skipSpace(s string) (rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isSpace == 0 { + break + } + } + return s[i:] +} + +func expectTokenSlash(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + b := s[i] + if (octetTypes[b]&isToken == 0) && b != '/' { + break + } + } + return s[:i], s[i:] +} + +func expectQuality(s string) (q float64, rest string) { + switch { + case len(s) == 0: + return -1, "" + case s[0] == '0': + q = 0 + case s[0] == '1': + q = 1 + default: + return -1, "" + } + s = s[1:] + if !strings.HasPrefix(s, ".") { + return q, s + } + s = s[1:] + i := 0 + n := 0 + d := 1 + for ; i < len(s); i++ { + b := s[i] + if b < '0' || b > '9' { + break + } + n = n*10 + int(b) - '0' + d *= 10 + } + return q + float64(n)/float64(d), s[i:] +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go new file mode 100644 index 0000000000..2e45780b74 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go @@ -0,0 +1,36 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "net/http" + + "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header" +) + +// NegotiateContentEncoding returns the best offered content encoding for the +// request's Accept-Encoding header. If two offers match with equal weight and +// then the offer earlier in the list is preferred. If no offers are +// acceptable, then "" is returned. +func NegotiateContentEncoding(r *http.Request, offers []string) string { + bestOffer := "identity" + bestQ := -1.0 + specs := header.ParseAccept(r.Header, "Accept-Encoding") + for _, offer := range offers { + for _, spec := range specs { + if spec.Q > bestQ && + (spec.Value == "*" || spec.Value == offer) { + bestQ = spec.Q + bestOffer = offer + } + } + } + if bestQ == 0 { + bestOffer = "" + } + return bestOffer +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectorfunc.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectorfunc.go new file mode 100644 index 0000000000..9a71a15db1 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectorfunc.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Prometheus Authors +// 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 prometheus + +// CollectorFunc is a convenient way to implement a Prometheus Collector +// without interface boilerplate. +// This implementation is based on DescribeByCollect method. +// familiarize yourself to it before using. +type CollectorFunc func(chan<- Metric) + +// Collect calls the defined CollectorFunc function with the provided Metrics channel +func (f CollectorFunc) Collect(ch chan<- Metric) { + f(ch) +} + +// Describe sends the descriptor information using DescribeByCollect +func (f CollectorFunc) Describe(ch chan<- *Desc) { + DescribeByCollect(f, ch) +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go index bcfa4fa10e..cc4ef1077e 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/collectors/go_collector_latest.go @@ -37,6 +37,9 @@ var ( // MetricsScheduler allows only scheduler metrics to be collected from Go runtime. // e.g. go_sched_goroutines_goroutines MetricsScheduler = GoRuntimeMetricsRule{regexp.MustCompile(`^/sched/.*`)} + // MetricsDebug allows only debug metrics to be collected from Go runtime. + // e.g. go_godebug_non_default_behavior_gocachetest_events_total + MetricsDebug = GoRuntimeMetricsRule{regexp.MustCompile(`^/godebug/.*`)} ) // WithGoCollectorMemStatsMetricsDisabled disables metrics that is gathered in runtime.MemStats structure such as: @@ -44,7 +47,6 @@ var ( // go_memstats_alloc_bytes // go_memstats_alloc_bytes_total // go_memstats_sys_bytes -// go_memstats_lookups_total // go_memstats_mallocs_total // go_memstats_frees_total // go_memstats_heap_alloc_bytes diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/desc.go index 68ffe3c248..ad347113c0 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/desc.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/desc.go @@ -189,12 +189,15 @@ func (d *Desc) String() string { fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()), ) } - vlStrings := make([]string, 0, len(d.variableLabels.names)) - for _, vl := range d.variableLabels.names { - if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil { - vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl)) - } else { - vlStrings = append(vlStrings, vl) + vlStrings := []string{} + if d.variableLabels != nil { + vlStrings = make([]string, 0, len(d.variableLabels.names)) + for _, vl := range d.variableLabels.names { + if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil { + vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl)) + } else { + vlStrings = append(vlStrings, vl) + } } } return fmt.Sprintf( diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go index ad9a71a5e0..520cbd7d41 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go @@ -22,13 +22,13 @@ import ( // goRuntimeMemStats provides the metrics initially provided by runtime.ReadMemStats. // From Go 1.17 those similar (and better) statistics are provided by runtime/metrics, so // while eval closure works on runtime.MemStats, the struct from Go 1.17+ is -// populated using runtime/metrics. +// populated using runtime/metrics. Those are the defaults we can't alter. func goRuntimeMemStats() memStatsMetrics { return memStatsMetrics{ { desc: NewDesc( memstatNamespace("alloc_bytes"), - "Number of bytes allocated and still in use.", + "Number of bytes allocated in heap and currently in use. Equals to /memory/classes/heap/objects:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Alloc) }, @@ -36,7 +36,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("alloc_bytes_total"), - "Total number of bytes allocated, even if freed.", + "Total number of bytes allocated in heap until now, even if released already. Equals to /gc/heap/allocs:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.TotalAlloc) }, @@ -44,23 +44,16 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("sys_bytes"), - "Number of bytes obtained from system.", + "Number of bytes obtained from system. Equals to /memory/classes/total:byte.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) }, valType: GaugeValue, - }, { - desc: NewDesc( - memstatNamespace("lookups_total"), - "Total number of pointer lookups.", - nil, nil, - ), - eval: func(ms *runtime.MemStats) float64 { return float64(ms.Lookups) }, - valType: CounterValue, }, { desc: NewDesc( memstatNamespace("mallocs_total"), - "Total number of mallocs.", + // TODO(bwplotka): We could add go_memstats_heap_objects, probably useful for discovery. Let's gather more feedback, kind of a waste of bytes for everybody for compatibility reasons to keep both, and we can't really rename/remove useful metric. + "Total number of heap objects allocated, both live and gc-ed. Semantically a counter version for go_memstats_heap_objects gauge. Equals to /gc/heap/allocs:objects + /gc/heap/tiny/allocs:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Mallocs) }, @@ -68,7 +61,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("frees_total"), - "Total number of frees.", + "Total number of heap objects frees. Equals to /gc/heap/frees:objects + /gc/heap/tiny/allocs:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Frees) }, @@ -76,7 +69,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_alloc_bytes"), - "Number of heap bytes allocated and still in use.", + "Number of heap bytes allocated and currently in use, same as go_memstats_alloc_bytes. Equals to /memory/classes/heap/objects:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapAlloc) }, @@ -84,7 +77,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_sys_bytes"), - "Number of heap bytes obtained from system.", + "Number of heap bytes obtained from system. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes + /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapSys) }, @@ -92,7 +85,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_idle_bytes"), - "Number of heap bytes waiting to be used.", + "Number of heap bytes waiting to be used. Equals to /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapIdle) }, @@ -100,7 +93,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_inuse_bytes"), - "Number of heap bytes that are in use.", + "Number of heap bytes that are in use. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapInuse) }, @@ -108,7 +101,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_released_bytes"), - "Number of heap bytes released to OS.", + "Number of heap bytes released to OS. Equals to /memory/classes/heap/released:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) }, @@ -116,7 +109,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_objects"), - "Number of allocated objects.", + "Number of currently allocated objects. Equals to /gc/heap/objects:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapObjects) }, @@ -124,7 +117,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("stack_inuse_bytes"), - "Number of bytes in use by the stack allocator.", + "Number of bytes obtained from system for stack allocator in non-CGO environments. Equals to /memory/classes/heap/stacks:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackInuse) }, @@ -132,7 +125,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("stack_sys_bytes"), - "Number of bytes obtained from system for stack allocator.", + "Number of bytes obtained from system for stack allocator. Equals to /memory/classes/heap/stacks:bytes + /memory/classes/os-stacks:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackSys) }, @@ -140,7 +133,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mspan_inuse_bytes"), - "Number of bytes in use by mspan structures.", + "Number of bytes in use by mspan structures. Equals to /memory/classes/metadata/mspan/inuse:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanInuse) }, @@ -148,7 +141,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mspan_sys_bytes"), - "Number of bytes used for mspan structures obtained from system.", + "Number of bytes used for mspan structures obtained from system. Equals to /memory/classes/metadata/mspan/inuse:bytes + /memory/classes/metadata/mspan/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanSys) }, @@ -156,7 +149,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mcache_inuse_bytes"), - "Number of bytes in use by mcache structures.", + "Number of bytes in use by mcache structures. Equals to /memory/classes/metadata/mcache/inuse:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheInuse) }, @@ -164,7 +157,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mcache_sys_bytes"), - "Number of bytes used for mcache structures obtained from system.", + "Number of bytes used for mcache structures obtained from system. Equals to /memory/classes/metadata/mcache/inuse:bytes + /memory/classes/metadata/mcache/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheSys) }, @@ -172,7 +165,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("buck_hash_sys_bytes"), - "Number of bytes used by the profiling bucket hash table.", + "Number of bytes used by the profiling bucket hash table. Equals to /memory/classes/profiling/buckets:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.BuckHashSys) }, @@ -180,7 +173,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("gc_sys_bytes"), - "Number of bytes used for garbage collection system metadata.", + "Number of bytes used for garbage collection system metadata. Equals to /memory/classes/metadata/other:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.GCSys) }, @@ -188,7 +181,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("other_sys_bytes"), - "Number of bytes used for other system allocations.", + "Number of bytes used for other system allocations. Equals to /memory/classes/other:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.OtherSys) }, @@ -196,7 +189,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("next_gc_bytes"), - "Number of heap bytes when next garbage collection will take place.", + "Number of heap bytes when next garbage collection will take place. Equals to /gc/heap/goal:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) }, @@ -225,7 +218,7 @@ func newBaseGoCollector() baseGoCollector { nil, nil), gcDesc: NewDesc( "go_gc_duration_seconds", - "A summary of the pause duration of garbage collection cycles.", + "A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.", nil, nil), gcLastTimeDesc: NewDesc( "go_memstats_last_gc_time_seconds", diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go index 2d8d9f64f4..6b8684731c 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go @@ -17,6 +17,7 @@ package prometheus import ( + "fmt" "math" "runtime" "runtime/metrics" @@ -153,7 +154,8 @@ func defaultGoCollectorOptions() internal.GoCollectorOptions { "/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes, }, RuntimeMetricRules: []internal.GoCollectorRule{ - //{Matcher: regexp.MustCompile("")}, + // Recommended metrics we want by default from runtime/metrics. + {Matcher: internal.GoCollectorDefaultRuntimeMetrics}, }, } } @@ -203,6 +205,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { // to fail here. This condition is tested in TestExpectedRuntimeMetrics. continue } + help := attachOriginalName(d.Description.Description, d.Name) sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name}) sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1] @@ -214,7 +217,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { m = newBatchHistogram( NewDesc( BuildFQName(namespace, subsystem, name), - d.Description.Description, + help, nil, nil, ), @@ -226,7 +229,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Namespace: namespace, Subsystem: subsystem, Name: name, - Help: d.Description.Description, + Help: help, }, ) } else { @@ -234,7 +237,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Namespace: namespace, Subsystem: subsystem, Name: name, - Help: d.Description.Description, + Help: help, }) } metricSet = append(metricSet, m) @@ -284,6 +287,10 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { } } +func attachOriginalName(desc, origName string) string { + return fmt.Sprintf("%s Sourced from %s.", desc, origName) +} + // Describe returns all descriptions of the collector. func (c *goCollector) Describe(ch chan<- *Desc) { c.base.Describe(ch) @@ -376,13 +383,13 @@ func unwrapScalarRMValue(v metrics.Value) float64 { // // This should never happen because we always populate our metric // set from the runtime/metrics package. - panic("unexpected unsupported metric") + panic("unexpected bad kind metric") default: // Unsupported metric kind. // // This should never happen because we check for this during initialization // and flag and filter metrics whose kinds we don't understand. - panic("unexpected unsupported metric kind") + panic(fmt.Sprintf("unexpected unsupported metric: %v", v.Kind())) } } diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/histogram.go index b5c8bcb395..c453b754a7 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/histogram.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/histogram.go @@ -14,6 +14,7 @@ package prometheus import ( + "errors" "fmt" "math" "runtime" @@ -28,6 +29,11 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +const ( + nativeHistogramSchemaMaximum = 8 + nativeHistogramSchemaMinimum = -4 +) + // nativeHistogramBounds for the frac of observed values. Only relevant for // schema > 0. The position in the slice is the schema. (0 is never used, just // here for convenience of using the schema directly as the index.) @@ -330,11 +336,11 @@ func ExponentialBuckets(start, factor float64, count int) []float64 { // used for the Buckets field of HistogramOpts. // // The function panics if 'count' is 0 or negative, if 'min' is 0 or negative. -func ExponentialBucketsRange(min, max float64, count int) []float64 { +func ExponentialBucketsRange(minBucket, maxBucket float64, count int) []float64 { if count < 1 { panic("ExponentialBucketsRange count needs a positive count") } - if min <= 0 { + if minBucket <= 0 { panic("ExponentialBucketsRange min needs to be greater than 0") } @@ -342,12 +348,12 @@ func ExponentialBucketsRange(min, max float64, count int) []float64 { // max = min*growthFactor^(bucketCount-1) // We know max/min and highest bucket. Solve for growthFactor. - growthFactor := math.Pow(max/min, 1.0/float64(count-1)) + growthFactor := math.Pow(maxBucket/minBucket, 1.0/float64(count-1)) // Now that we know growthFactor, solve for each bucket. buckets := make([]float64, count) for i := 1; i <= count; i++ { - buckets[i-1] = min * math.Pow(growthFactor, float64(i-1)) + buckets[i-1] = minBucket * math.Pow(growthFactor, float64(i-1)) } return buckets } @@ -440,7 +446,7 @@ type HistogramOpts struct { // constant (or any negative float value). NativeHistogramZeroThreshold float64 - // The remaining fields define a strategy to limit the number of + // The next three fields define a strategy to limit the number of // populated sparse buckets. If NativeHistogramMaxBucketNumber is left // at zero, the number of buckets is not limited. (Note that this might // lead to unbounded memory consumption if the values observed by the @@ -473,6 +479,22 @@ type HistogramOpts struct { NativeHistogramMinResetDuration time.Duration NativeHistogramMaxZeroThreshold float64 + // NativeHistogramMaxExemplars limits the number of exemplars + // that are kept in memory for each native histogram. If you leave it at + // zero, a default value of 10 is used. If no exemplars should be kept specifically + // for native histograms, set it to a negative value. (Scrapers can + // still use the exemplars exposed for classic buckets, which are managed + // independently.) + NativeHistogramMaxExemplars int + // NativeHistogramExemplarTTL is only checked once + // NativeHistogramMaxExemplars is exceeded. In that case, the + // oldest exemplar is removed if it is older than NativeHistogramExemplarTTL. + // Otherwise, the older exemplar in the pair of exemplars that are closest + // together (on an exponential scale) is removed. + // If NativeHistogramExemplarTTL is left at its zero value, a default value of + // 5m is used. To always delete the oldest exemplar, set it to a negative value. + NativeHistogramExemplarTTL time.Duration + // now is for testing purposes, by default it's time.Now. now func() time.Time @@ -532,6 +554,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr if opts.afterFunc == nil { opts.afterFunc = time.AfterFunc } + h := &histogram{ desc: desc, upperBounds: opts.Buckets, @@ -556,6 +579,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr h.nativeHistogramZeroThreshold = DefNativeHistogramZeroThreshold } // Leave h.nativeHistogramZeroThreshold at 0 otherwise. h.nativeHistogramSchema = pickSchema(opts.NativeHistogramBucketFactor) + h.nativeExemplars = makeNativeExemplars(opts.NativeHistogramExemplarTTL, opts.NativeHistogramMaxExemplars) } for i, upperBound := range h.upperBounds { if i < len(h.upperBounds)-1 { @@ -725,7 +749,8 @@ type histogram struct { // resetScheduled is protected by mtx. It is true if a reset is // scheduled for a later time (when nativeHistogramMinResetDuration has // passed). - resetScheduled bool + resetScheduled bool + nativeExemplars nativeExemplars // now is for testing purposes, by default it's time.Now. now func() time.Time @@ -742,6 +767,9 @@ func (h *histogram) Observe(v float64) { h.observe(v, h.findBucket(v)) } +// ObserveWithExemplar should not be called in a high-frequency setting +// for a native histogram with configured exemplars. For this case, +// the implementation isn't lock-free and might suffer from lock contention. func (h *histogram) ObserveWithExemplar(v float64, e Labels) { i := h.findBucket(v) h.observe(v, i) @@ -821,6 +849,13 @@ func (h *histogram) Write(out *dto.Metric) error { Length: proto.Uint32(0), }} } + + if h.nativeExemplars.isEnabled() { + h.nativeExemplars.Lock() + his.Exemplars = append(his.Exemplars, h.nativeExemplars.exemplars...) + h.nativeExemplars.Unlock() + } + } addAndResetCounts(hotCounts, coldCounts) return nil @@ -829,15 +864,35 @@ func (h *histogram) Write(out *dto.Metric) error { // findBucket returns the index of the bucket for the provided value, or // len(h.upperBounds) for the +Inf bucket. func (h *histogram) findBucket(v float64) int { - // TODO(beorn7): For small numbers of buckets (<30), a linear search is - // slightly faster than the binary search. If we really care, we could - // switch from one search strategy to the other depending on the number - // of buckets. - // - // Microbenchmarks (BenchmarkHistogramNoLabels): - // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op - // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op - // 300 buckets: 154 ns/op linear - binary 61.6 ns/op + n := len(h.upperBounds) + if n == 0 { + return 0 + } + + // Early exit: if v is less than or equal to the first upper bound, return 0 + if v <= h.upperBounds[0] { + return 0 + } + + // Early exit: if v is greater than the last upper bound, return len(h.upperBounds) + if v > h.upperBounds[n-1] { + return n + } + + // For small arrays, use simple linear search + // "magic number" 35 is result of tests on couple different (AWS and baremetal) servers + // see more details here: https://github.com/prometheus/client_golang/pull/1662 + if n < 35 { + for i, bound := range h.upperBounds { + if v <= bound { + return i + } + } + // If v is greater than all upper bounds, return len(h.upperBounds) + return n + } + + // For larger arrays, use stdlib's binary search return sort.SearchFloat64s(h.upperBounds, v) } @@ -1091,8 +1146,10 @@ func (h *histogram) resetCounts(counts *histogramCounts) { deleteSyncMap(&counts.nativeHistogramBucketsPositive) } -// updateExemplar replaces the exemplar for the provided bucket. With empty -// labels, it's a no-op. It panics if any of the labels is invalid. +// updateExemplar replaces the exemplar for the provided classic bucket. +// With empty labels, it's a no-op. It panics if any of the labels is invalid. +// If histogram is native, the exemplar will be cached into nativeExemplars, +// which has a limit, and will remove one exemplar when limit is reached. func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { if l == nil { return @@ -1102,6 +1159,10 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { panic(err) } h.exemplars[bucket].Store(e) + doSparse := h.nativeHistogramSchema > math.MinInt32 && !math.IsNaN(v) + if doSparse { + h.nativeExemplars.addExemplar(e) + } } // HistogramVec is a Collector that bundles a set of Histograms that all share the @@ -1336,6 +1397,48 @@ func MustNewConstHistogram( return m } +// NewConstHistogramWithCreatedTimestamp does the same thing as NewConstHistogram but sets the created timestamp. +func NewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + ct time.Time, + labelValues ...string, +) (Metric, error) { + if desc.err != nil { + return nil, desc.err + } + if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil { + return nil, err + } + return &constHistogram{ + desc: desc, + count: count, + sum: sum, + buckets: buckets, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstHistogramWithCreatedTimestamp is a version of NewConstHistogramWithCreatedTimestamp that panics where +// NewConstHistogramWithCreatedTimestamp would have returned an error. +func MustNewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstHistogramWithCreatedTimestamp(desc, count, sum, buckets, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} + type buckSort []*dto.Bucket func (s buckSort) Len() int { @@ -1363,9 +1466,9 @@ func pickSchema(bucketFactor float64) int32 { floor := math.Floor(math.Log2(math.Log2(bucketFactor))) switch { case floor <= -8: - return 8 + return nativeHistogramSchemaMaximum case floor >= 4: - return -4 + return nativeHistogramSchemaMinimum default: return -int32(floor) } @@ -1575,3 +1678,379 @@ func addAndResetCounts(hot, cold *histogramCounts) { atomic.AddUint64(&hot.nativeHistogramZeroBucket, atomic.LoadUint64(&cold.nativeHistogramZeroBucket)) atomic.StoreUint64(&cold.nativeHistogramZeroBucket, 0) } + +type nativeExemplars struct { + sync.Mutex + + // Time-to-live for exemplars, it is set to -1 if exemplars are disabled, that is NativeHistogramMaxExemplars is below 0. + // The ttl is used on insertion to remove an exemplar that is older than ttl, if present. + ttl time.Duration + + exemplars []*dto.Exemplar +} + +func (n *nativeExemplars) isEnabled() bool { + return n.ttl != -1 +} + +func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars { + if ttl == 0 { + ttl = 5 * time.Minute + } + + if maxCount == 0 { + maxCount = 10 + } + + if maxCount < 0 { + maxCount = 0 + ttl = -1 + } + + return nativeExemplars{ + ttl: ttl, + exemplars: make([]*dto.Exemplar, 0, maxCount), + } +} + +func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { + if !n.isEnabled() { + return + } + + n.Lock() + defer n.Unlock() + + // When the number of exemplars has not yet exceeded or + // is equal to cap(n.exemplars), then + // insert the new exemplar directly. + if len(n.exemplars) < cap(n.exemplars) { + var nIdx int + for nIdx = 0; nIdx < len(n.exemplars); nIdx++ { + if *e.Value < *n.exemplars[nIdx].Value { + break + } + } + n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...) + return + } + + if len(n.exemplars) == 1 { + // When the number of exemplars is 1, then + // replace the existing exemplar with the new exemplar. + n.exemplars[0] = e + return + } + // From this point on, the number of exemplars is greater than 1. + + // When the number of exemplars exceeds the limit, remove one exemplar. + var ( + ot = time.Time{} // Oldest timestamp seen. Initial value doesn't matter as we replace it due to otIdx == -1 in the loop. + otIdx = -1 // Index of the exemplar with the oldest timestamp. + + md = -1.0 // Logarithm of the delta of the closest pair of exemplars. + + // The insertion point of the new exemplar in the exemplars slice after insertion. + // This is calculated purely based on the order of the exemplars by value. + // nIdx == len(n.exemplars) means the new exemplar is to be inserted after the end. + nIdx = -1 + + // rIdx is ultimately the index for the exemplar that we are replacing with the new exemplar. + // The aim is to keep a good spread of exemplars by value and not let them bunch up too much. + // It is calculated in 3 steps: + // 1. First we set rIdx to the index of the older exemplar within the closest pair by value. + // That is the following will be true (on log scale): + // either the exemplar pair on index (rIdx-1, rIdx) or (rIdx, rIdx+1) will have + // the closest values to each other from all pairs. + // For example, suppose the values are distributed like this: + // |-----------x-------------x----------------x----x-----| + // ^--rIdx as this is older. + // Or like this: + // |-----------x-------------x----------------x----x-----| + // ^--rIdx as this is older. + // 2. If there is an exemplar that expired, then we simple reset rIdx to that index. + // 3. We check if by inserting the new exemplar we would create a closer pair at + // (nIdx-1, nIdx) or (nIdx, nIdx+1) and set rIdx to nIdx-1 or nIdx accordingly to + // keep the spread of exemplars by value; otherwise we keep rIdx as it is. + rIdx = -1 + cLog float64 // Logarithm of the current exemplar. + pLog float64 // Logarithm of the previous exemplar. + ) + + for i, exemplar := range n.exemplars { + // Find the exemplar with the oldest timestamp. + if otIdx == -1 || exemplar.Timestamp.AsTime().Before(ot) { + ot = exemplar.Timestamp.AsTime() + otIdx = i + } + + // Find the index at which to insert new the exemplar. + if nIdx == -1 && *e.Value <= *exemplar.Value { + nIdx = i + } + + // Find the two closest exemplars and pick the one the with older timestamp. + pLog = cLog + cLog = math.Log(exemplar.GetValue()) + if i == 0 { + continue + } + diff := math.Abs(cLog - pLog) + if md == -1 || diff < md { + // The closest exemplar pair is at index: i-1, i. + // Choose the exemplar with the older timestamp for replacement. + md = diff + if n.exemplars[i].Timestamp.AsTime().Before(n.exemplars[i-1].Timestamp.AsTime()) { + rIdx = i + } else { + rIdx = i - 1 + } + } + + } + + // If all existing exemplar are smaller than new exemplar, + // then the exemplar should be inserted at the end. + if nIdx == -1 { + nIdx = len(n.exemplars) + } + // Here, we have the following relationships: + // n.exemplars[nIdx-1].Value < e.Value (if nIdx > 0) + // e.Value <= n.exemplars[nIdx].Value (if nIdx < len(n.exemplars)) + + if otIdx != -1 && e.Timestamp.AsTime().Sub(ot) > n.ttl { + // If the oldest exemplar has expired, then replace it with the new exemplar. + rIdx = otIdx + } else { + // In the previous for loop, when calculating the closest pair of exemplars, + // we did not take into account the newly inserted exemplar. + // So we need to calculate with the newly inserted exemplar again. + elog := math.Log(e.GetValue()) + if nIdx > 0 { + diff := math.Abs(elog - math.Log(n.exemplars[nIdx-1].GetValue())) + if diff < md { + // The value we are about to insert is closer to the previous exemplar at the insertion point than what we calculated before in rIdx. + // v--rIdx + // |-----------x-n-----------x----------------x----x-----| + // nIdx-1--^ ^--new exemplar value + // Do not make the spread worse, replace nIdx-1 and not rIdx. + md = diff + rIdx = nIdx - 1 + } + } + if nIdx < len(n.exemplars) { + diff := math.Abs(math.Log(n.exemplars[nIdx].GetValue()) - elog) + if diff < md { + // The value we are about to insert is closer to the next exemplar at the insertion point than what we calculated before in rIdx. + // v--rIdx + // |-----------x-----------n-x----------------x----x-----| + // new exemplar value--^ ^--nIdx + // Do not make the spread worse, replace nIdx-1 and not rIdx. + rIdx = nIdx + } + } + } + + // Adjust the slice according to rIdx and nIdx. + switch { + case rIdx == nIdx: + n.exemplars[nIdx] = e + case rIdx < nIdx: + n.exemplars = append(n.exemplars[:rIdx], append(n.exemplars[rIdx+1:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...)...) + case rIdx > nIdx: + n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...) + } +} + +type constNativeHistogram struct { + desc *Desc + dto.Histogram + labelPairs []*dto.LabelPair +} + +func validateCount(sum float64, count uint64, negativeBuckets, positiveBuckets map[int]int64, zeroBucket uint64) error { + var bucketPopulationSum int64 + for _, v := range positiveBuckets { + bucketPopulationSum += v + } + for _, v := range negativeBuckets { + bucketPopulationSum += v + } + bucketPopulationSum += int64(zeroBucket) + + // If the sum of observations is NaN, the number of observations must be greater or equal to the sum of all bucket counts. + // Otherwise, the number of observations must be equal to the sum of all bucket counts . + + if math.IsNaN(sum) && bucketPopulationSum > int64(count) || + !math.IsNaN(sum) && bucketPopulationSum != int64(count) { + return errors.New("the sum of all bucket populations exceeds the count of observations") + } + return nil +} + +// NewConstNativeHistogram returns a metric representing a Prometheus native histogram with +// fixed values for the count, sum, and positive/negative/zero bucket counts. As those parameters +// cannot be changed, the returned value does not implement the Histogram +// interface (but only the Metric interface). Users of this package will not +// have much use for it in regular operations. However, when implementing custom +// OpenTelemetry Collectors, it is useful as a throw-away metric that is generated on the fly +// to send it to Prometheus in the Collect method. +// +// zeroBucket counts all (positive and negative) +// observations in the zero bucket (with an absolute value less or equal +// the current threshold). +// positiveBuckets and negativeBuckets are separate maps for negative and positive +// observations. The map's value is an int64, counting observations in +// that bucket. The map's key is the +// index of the bucket according to the used +// Schema. Index 0 is for an upper bound of 1 in positive buckets and for a lower bound of -1 in negative buckets. +// NewConstNativeHistogram returns an error if +// - the length of labelValues is not consistent with the variable labels in Desc or if Desc is invalid. +// - the schema passed is not between 8 and -4 +// - the sum of counts in all buckets including the zero bucket does not equal the count if sum is not NaN (or exceeds the count if sum is NaN) +// +// See https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#exponential-histograms for more details about the conversion from OTel to Prometheus. +func NewConstNativeHistogram( + desc *Desc, + count uint64, + sum float64, + positiveBuckets, negativeBuckets map[int]int64, + zeroBucket uint64, + schema int32, + zeroThreshold float64, + createdTimestamp time.Time, + labelValues ...string, +) (Metric, error) { + if desc.err != nil { + return nil, desc.err + } + if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil { + return nil, err + } + if schema > nativeHistogramSchemaMaximum || schema < nativeHistogramSchemaMinimum { + return nil, errors.New("invalid native histogram schema") + } + if err := validateCount(sum, count, negativeBuckets, positiveBuckets, zeroBucket); err != nil { + return nil, err + } + + NegativeSpan, NegativeDelta := makeBucketsFromMap(negativeBuckets) + PositiveSpan, PositiveDelta := makeBucketsFromMap(positiveBuckets) + ret := &constNativeHistogram{ + desc: desc, + Histogram: dto.Histogram{ + CreatedTimestamp: timestamppb.New(createdTimestamp), + Schema: &schema, + ZeroThreshold: &zeroThreshold, + SampleCount: &count, + SampleSum: &sum, + + NegativeSpan: NegativeSpan, + NegativeDelta: NegativeDelta, + + PositiveSpan: PositiveSpan, + PositiveDelta: PositiveDelta, + + ZeroCount: proto.Uint64(zeroBucket), + }, + labelPairs: MakeLabelPairs(desc, labelValues), + } + if *ret.ZeroThreshold == 0 && *ret.ZeroCount == 0 && len(ret.PositiveSpan) == 0 && len(ret.NegativeSpan) == 0 { + ret.PositiveSpan = []*dto.BucketSpan{{ + Offset: proto.Int32(0), + Length: proto.Uint32(0), + }} + } + return ret, nil +} + +// MustNewConstNativeHistogram is a version of NewConstNativeHistogram that panics where +// NewConstNativeHistogram would have returned an error. +func MustNewConstNativeHistogram( + desc *Desc, + count uint64, + sum float64, + positiveBuckets, negativeBuckets map[int]int64, + zeroBucket uint64, + nativeHistogramSchema int32, + nativeHistogramZeroThreshold float64, + createdTimestamp time.Time, + labelValues ...string, +) Metric { + nativehistogram, err := NewConstNativeHistogram(desc, + count, + sum, + positiveBuckets, + negativeBuckets, + zeroBucket, + nativeHistogramSchema, + nativeHistogramZeroThreshold, + createdTimestamp, + labelValues...) + if err != nil { + panic(err) + } + return nativehistogram +} + +func (h *constNativeHistogram) Desc() *Desc { + return h.desc +} + +func (h *constNativeHistogram) Write(out *dto.Metric) error { + out.Histogram = &h.Histogram + out.Label = h.labelPairs + return nil +} + +func makeBucketsFromMap(buckets map[int]int64) ([]*dto.BucketSpan, []int64) { + if len(buckets) == 0 { + return nil, nil + } + var ii []int + for k := range buckets { + ii = append(ii, k) + } + sort.Ints(ii) + + var ( + spans []*dto.BucketSpan + deltas []int64 + prevCount int64 + nextI int + ) + + appendDelta := func(count int64) { + *spans[len(spans)-1].Length++ + deltas = append(deltas, count-prevCount) + prevCount = count + } + + for n, i := range ii { + count := buckets[i] + // Multiple spans with only small gaps in between are probably + // encoded more efficiently as one larger span with a few empty + // buckets. Needs some research to find the sweet spot. For now, + // we assume that gaps of one or two buckets should not create + // a new span. + iDelta := int32(i - nextI) + if n == 0 || iDelta > 2 { + // We have to create a new span, either because we are + // at the very beginning, or because we have found a gap + // of more than two buckets. + spans = append(spans, &dto.BucketSpan{ + Offset: proto.Int32(iDelta), + Length: proto.Uint32(0), + }) + } else { + // We have found a small gap (or no gap at all). + // Insert empty buckets as needed. + for j := int32(0); j < iDelta; j++ { + appendDelta(0) + } + } + appendDelta(count) + nextI = i + 1 + } + return spans, deltas +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go index a595a20362..8b016355ad 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go @@ -22,17 +22,18 @@ import ( "bytes" "fmt" "io" + "strconv" "strings" ) -func min(a, b int) int { +func minInt(a, b int) int { if a < b { return a } return b } -func max(a, b int) int { +func maxInt(a, b int) int { if a > b { return a } @@ -427,12 +428,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { if codes[0].Tag == 'e' { c := codes[0] i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 - codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2} + codes[0] = OpCode{c.Tag, maxInt(i1, i2-n), i2, maxInt(j1, j2-n), j2} } if codes[len(codes)-1].Tag == 'e' { c := codes[len(codes)-1] i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 - codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)} + codes[len(codes)-1] = OpCode{c.Tag, i1, minInt(i2, i1+n), j1, minInt(j2, j1+n)} } nn := n + n groups := [][]OpCode{} @@ -443,12 +444,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { // there is a large range with no changes. if c.Tag == 'e' && i2-i1 > nn { group = append(group, OpCode{ - c.Tag, i1, min(i2, i1+n), - j1, min(j2, j1+n), + c.Tag, i1, minInt(i2, i1+n), + j1, minInt(j2, j1+n), }) groups = append(groups, group) group = []OpCode{} - i1, j1 = max(i1, i2-n), max(j1, j2-n) + i1, j1 = maxInt(i1, i2-n), maxInt(j1, j2-n) } group = append(group, OpCode{c.Tag, i1, i2, j1, j2}) } @@ -515,7 +516,7 @@ func (m *SequenceMatcher) QuickRatio() float64 { // is faster to compute than either .Ratio() or .QuickRatio(). func (m *SequenceMatcher) RealQuickRatio() float64 { la, lb := len(m.a), len(m.b) - return calculateRatio(min(la, lb), la+lb) + return calculateRatio(minInt(la, lb), la+lb) } // Convert range to the "ed" format @@ -524,7 +525,7 @@ func formatRangeUnified(start, stop int) string { beginning := start + 1 // lines start numbering with one length := stop - start if length == 1 { - return fmt.Sprintf("%d", beginning) + return strconv.Itoa(beginning) } if length == 0 { beginning-- // empty ranges begin at line just before the range diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go index 723b45d644..a4fa6eabd7 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go @@ -30,3 +30,5 @@ type GoCollectorOptions struct { RuntimeMetricSumForHist map[string]string RuntimeMetricRules []GoCollectorRule } + +var GoCollectorDefaultRuntimeMetrics = regexp.MustCompile(`/gc/gogc:percent|/gc/gomemlimit:bytes|/sched/gomaxprocs:threads`) diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go index 97d17d6cb6..f7f97ef926 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go @@ -66,7 +66,8 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool) name += "_total" } - valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name)) + // Our current conversion moves to legacy naming, so use legacy validation. + valid := model.IsValidLegacyMetricName(namespace + "_" + subsystem + "_" + name) switch d.Kind { case metrics.KindUint64: case metrics.KindFloat64: diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/metric.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/metric.go index f018e57237..592eec3e24 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/metric.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/metric.go @@ -108,15 +108,23 @@ func BuildFQName(namespace, subsystem, name string) string { if name == "" { return "" } - switch { - case namespace != "" && subsystem != "": - return strings.Join([]string{namespace, subsystem, name}, "_") - case namespace != "": - return strings.Join([]string{namespace, name}, "_") - case subsystem != "": - return strings.Join([]string{subsystem, name}, "_") + + sb := strings.Builder{} + sb.Grow(len(namespace) + len(subsystem) + len(name) + 2) + + if namespace != "" { + sb.WriteString(namespace) + sb.WriteString("_") } - return name + + if subsystem != "" { + sb.WriteString(subsystem) + sb.WriteString("_") + } + + sb.WriteString(name) + + return sb.String() } type invalidMetric struct { @@ -234,7 +242,7 @@ func NewMetricWithExemplars(m Metric, exemplars ...Exemplar) (Metric, error) { ) for i, e := range exemplars { ts := e.Timestamp - if ts == (time.Time{}) { + if ts.IsZero() { ts = now } exs[i], err = newExemplar(e.Value, ts, e.Labels) diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go index 8548dd18ed..e7bce8b58e 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go @@ -22,14 +22,16 @@ import ( ) type processCollector struct { - collectFn func(chan<- Metric) - pidFn func() (int, error) - reportErrors bool - cpuTotal *Desc - openFDs, maxFDs *Desc - vsize, maxVsize *Desc - rss *Desc - startTime *Desc + collectFn func(chan<- Metric) + describeFn func(chan<- *Desc) + pidFn func() (int, error) + reportErrors bool + cpuTotal *Desc + openFDs, maxFDs *Desc + vsize, maxVsize *Desc + rss *Desc + startTime *Desc + inBytes, outBytes *Desc } // ProcessCollectorOpts defines the behavior of a process metrics collector @@ -100,6 +102,16 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector { "Start time of the process since unix epoch in seconds.", nil, nil, ), + inBytes: NewDesc( + ns+"process_network_receive_bytes_total", + "Number of bytes received by the process over the network.", + nil, nil, + ), + outBytes: NewDesc( + ns+"process_network_transmit_bytes_total", + "Number of bytes sent by the process over the network.", + nil, nil, + ), } if opts.PidFn == nil { @@ -111,24 +123,23 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector { // Set up process metric collection if supported by the runtime. if canCollectProcess() { c.collectFn = c.processCollect + c.describeFn = c.describe } else { - c.collectFn = func(ch chan<- Metric) { - c.reportError(ch, nil, errors.New("process metrics not supported on this platform")) - } + c.collectFn = c.errorCollectFn + c.describeFn = c.errorDescribeFn } return c } -// Describe returns all descriptions of the collector. -func (c *processCollector) Describe(ch chan<- *Desc) { - ch <- c.cpuTotal - ch <- c.openFDs - ch <- c.maxFDs - ch <- c.vsize - ch <- c.maxVsize - ch <- c.rss - ch <- c.startTime +func (c *processCollector) errorCollectFn(ch chan<- Metric) { + c.reportError(ch, nil, errors.New("process metrics not supported on this platform")) +} + +func (c *processCollector) errorDescribeFn(ch chan<- *Desc) { + if c.reportErrors { + ch <- NewInvalidDesc(errors.New("process metrics not supported on this platform")) + } } // Collect returns the current state of all metrics of the collector. @@ -136,6 +147,11 @@ func (c *processCollector) Collect(ch chan<- Metric) { c.collectFn(ch) } +// Describe returns all descriptions of the collector. +func (c *processCollector) Describe(ch chan<- *Desc) { + c.describeFn(ch) +} + func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) { if !c.reportErrors { return diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go new file mode 100644 index 0000000000..0a61b98461 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_darwin.go @@ -0,0 +1,130 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build darwin && !ios + +package prometheus + +import ( + "errors" + "fmt" + "os" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// notImplementedErr is returned by stub functions that replace cgo functions, when cgo +// isn't available. +var notImplementedErr = errors.New("not implemented") + +type memoryInfo struct { + vsize uint64 // Virtual memory size in bytes + rss uint64 // Resident memory size in bytes +} + +func canCollectProcess() bool { + return true +} + +func getSoftLimit(which int) (uint64, error) { + rlimit := syscall.Rlimit{} + + if err := syscall.Getrlimit(which, &rlimit); err != nil { + return 0, err + } + + return rlimit.Cur, nil +} + +func getOpenFileCount() (float64, error) { + // Alternately, the undocumented proc_pidinfo(PROC_PIDLISTFDS) can be used to + // return a list of open fds, but that requires a way to call C APIs. The + // benefits, however, include fewer system calls and not failing when at the + // open file soft limit. + + if dir, err := os.Open("/dev/fd"); err != nil { + return 0.0, err + } else { + defer dir.Close() + + // Avoid ReadDir(), as it calls stat(2) on each descriptor. Not only is + // that info not used, but KQUEUE descriptors fail stat(2), which causes + // the whole method to fail. + if names, err := dir.Readdirnames(0); err != nil { + return 0.0, err + } else { + // Subtract 1 to ignore the open /dev/fd descriptor above. + return float64(len(names) - 1), nil + } + } +} + +func (c *processCollector) processCollect(ch chan<- Metric) { + if procs, err := unix.SysctlKinfoProcSlice("kern.proc.pid", os.Getpid()); err == nil { + if len(procs) == 1 { + startTime := float64(procs[0].Proc.P_starttime.Nano() / 1e9) + ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) + } else { + err = fmt.Errorf("sysctl() returned %d proc structs (expected 1)", len(procs)) + c.reportError(ch, c.startTime, err) + } + } else { + c.reportError(ch, c.startTime, err) + } + + // The proc structure returned by kern.proc.pid above has an Rusage member, + // but it is not filled in, so it needs to be fetched by getrusage(2). For + // that call, the UTime, STime, and Maxrss members are filled out, but not + // Ixrss, Idrss, or Isrss for the memory usage. Memory stats will require + // access to the C API to call task_info(TASK_BASIC_INFO). + rusage := unix.Rusage{} + + if err := unix.Getrusage(syscall.RUSAGE_SELF, &rusage); err == nil { + cpuTime := time.Duration(rusage.Stime.Nano() + rusage.Utime.Nano()).Seconds() + ch <- MustNewConstMetric(c.cpuTotal, CounterValue, cpuTime) + } else { + c.reportError(ch, c.cpuTotal, err) + } + + if memInfo, err := getMemory(); err == nil { + ch <- MustNewConstMetric(c.rss, GaugeValue, float64(memInfo.rss)) + ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(memInfo.vsize)) + } else if !errors.Is(err, notImplementedErr) { + // Don't report an error when support is not compiled in. + c.reportError(ch, c.rss, err) + c.reportError(ch, c.vsize, err) + } + + if fds, err := getOpenFileCount(); err == nil { + ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds) + } else { + c.reportError(ch, c.openFDs, err) + } + + if openFiles, err := getSoftLimit(syscall.RLIMIT_NOFILE); err == nil { + ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(openFiles)) + } else { + c.reportError(ch, c.maxFDs, err) + } + + if addressSpace, err := getSoftLimit(syscall.RLIMIT_AS); err == nil { + ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(addressSpace)) + } else { + c.reportError(ch, c.maxVsize, err) + } + + // TODO: socket(PF_SYSTEM) to fetch "com.apple.network.statistics" might + // be able to get the per-process network send/receive counts. +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.c b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.c new file mode 100644 index 0000000000..d00a24315d --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.c @@ -0,0 +1,84 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build darwin && !ios && cgo + +#include +#include +#include + +// The compiler warns that mach/shared_memory_server.h is deprecated, and to use +// mach/shared_region.h instead. But that doesn't define +// SHARED_DATA_REGION_SIZE or SHARED_TEXT_REGION_SIZE, so redefine them here and +// avoid a warning message when running tests. +#define GLOBAL_SHARED_TEXT_SEGMENT 0x90000000U +#define SHARED_DATA_REGION_SIZE 0x10000000 +#define SHARED_TEXT_REGION_SIZE 0x10000000 + + +int get_memory_info(unsigned long long *rss, unsigned long long *vsize) +{ + // This is lightly adapted from how ps(1) obtains its memory info. + // https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109 + + kern_return_t error; + task_t task = MACH_PORT_NULL; + mach_task_basic_info_data_t info; + mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT; + + error = task_info( + mach_task_self(), + MACH_TASK_BASIC_INFO, + (task_info_t) &info, + &info_count ); + + if( error != KERN_SUCCESS ) + { + return error; + } + + *rss = info.resident_size; + *vsize = info.virtual_size; + + { + vm_region_basic_info_data_64_t b_info; + mach_vm_address_t address = GLOBAL_SHARED_TEXT_SEGMENT; + mach_vm_size_t size; + mach_port_t object_name; + + /* + * try to determine if this task has the split libraries + * mapped in... if so, adjust its virtual size down by + * the 2 segments that are used for split libraries + */ + info_count = VM_REGION_BASIC_INFO_COUNT_64; + + error = mach_vm_region( + mach_task_self(), + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t) &b_info, + &info_count, + &object_name); + + if (error == KERN_SUCCESS) { + if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) && + *vsize > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) { + *vsize -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE); + } + } + } + + return 0; +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.go new file mode 100644 index 0000000000..9ac53f9992 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_cgo_darwin.go @@ -0,0 +1,51 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build darwin && !ios && cgo + +package prometheus + +/* +int get_memory_info(unsigned long long *rss, unsigned long long *vs); +*/ +import "C" +import "fmt" + +func getMemory() (*memoryInfo, error) { + var rss, vsize C.ulonglong + + if err := C.get_memory_info(&rss, &vsize); err != 0 { + return nil, fmt.Errorf("task_info() failed with 0x%x", int(err)) + } + + return &memoryInfo{vsize: uint64(vsize), rss: uint64(rss)}, nil +} + +// describe returns all descriptions of the collector for Darwin. +// Ensure that this list of descriptors is kept in sync with the metrics collected +// in the processCollect method. Any changes to the metrics in processCollect +// (such as adding or removing metrics) should be reflected in this list of descriptors. +func (c *processCollector) describe(ch chan<- *Desc) { + ch <- c.cpuTotal + ch <- c.openFDs + ch <- c.maxFDs + ch <- c.maxVsize + ch <- c.startTime + ch <- c.rss + ch <- c.vsize + + /* the process could be collected but not implemented yet + ch <- c.inBytes + ch <- c.outBytes + */ +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_nocgo_darwin.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_nocgo_darwin.go new file mode 100644 index 0000000000..8ddb0995d6 --- /dev/null +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_mem_nocgo_darwin.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build darwin && !ios && !cgo + +package prometheus + +func getMemory() (*memoryInfo, error) { + return nil, notImplementedErr +} + +// describe returns all descriptions of the collector for Darwin. +// Ensure that this list of descriptors is kept in sync with the metrics collected +// in the processCollect method. Any changes to the metrics in processCollect +// (such as adding or removing metrics) should be reflected in this list of descriptors. +func (c *processCollector) describe(ch chan<- *Desc) { + ch <- c.cpuTotal + ch <- c.openFDs + ch <- c.maxFDs + ch <- c.maxVsize + ch <- c.startTime + + /* the process could be collected but not implemented yet + ch <- c.rss + ch <- c.vsize + ch <- c.inBytes + ch <- c.outBytes + */ +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_wasip1.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_not_supported.go similarity index 55% rename from go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_wasip1.go rename to go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_not_supported.go index d8d9a6d7a2..7732b7f376 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_wasip1.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_not_supported.go @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build wasip1 -// +build wasip1 +//go:build wasip1 || js || ios +// +build wasip1 js ios package prometheus @@ -20,7 +20,14 @@ func canCollectProcess() bool { return false } -func (*processCollector) processCollect(chan<- Metric) { - // noop on this platform - return +func (c *processCollector) processCollect(ch chan<- Metric) { + c.errorCollectFn(ch) +} + +// describe returns all descriptions of the collector for wasip1 and js. +// Ensure that this list of descriptors is kept in sync with the metrics collected +// in the processCollect method. Any changes to the metrics in processCollect +// (such as adding or removing metrics) should be reflected in this list of descriptors. +func (c *processCollector) describe(ch chan<- *Desc) { + c.errorDescribeFn(ch) } diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_procfsenabled.go similarity index 63% rename from go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go rename to go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_procfsenabled.go index 8c1136ceea..9f4b130bef 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_procfsenabled.go @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !windows && !js && !wasip1 -// +build !windows,!js,!wasip1 +//go:build !windows && !js && !wasip1 && !darwin +// +build !windows,!js,!wasip1,!darwin package prometheus @@ -63,4 +63,34 @@ func (c *processCollector) processCollect(ch chan<- Metric) { } else { c.reportError(ch, nil, err) } + + if netstat, err := p.Netstat(); err == nil { + var inOctets, outOctets float64 + if netstat.IpExt.InOctets != nil { + inOctets = *netstat.IpExt.InOctets + } + if netstat.IpExt.OutOctets != nil { + outOctets = *netstat.IpExt.OutOctets + } + ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets) + ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets) + } else { + c.reportError(ch, nil, err) + } +} + +// describe returns all descriptions of the collector for others than windows, js, wasip1 and darwin. +// Ensure that this list of descriptors is kept in sync with the metrics collected +// in the processCollect method. Any changes to the metrics in processCollect +// (such as adding or removing metrics) should be reflected in this list of descriptors. +func (c *processCollector) describe(ch chan<- *Desc) { + ch <- c.cpuTotal + ch <- c.openFDs + ch <- c.maxFDs + ch <- c.vsize + ch <- c.maxVsize + ch <- c.rss + ch <- c.startTime + ch <- c.inBytes + ch <- c.outBytes } diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go index f973398df2..fa474289ef 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go @@ -79,14 +79,10 @@ func getProcessHandleCount(handle windows.Handle) (uint32, error) { } func (c *processCollector) processCollect(ch chan<- Metric) { - h, err := windows.GetCurrentProcess() - if err != nil { - c.reportError(ch, nil, err) - return - } + h := windows.CurrentProcess() var startTime, exitTime, kernelTime, userTime windows.Filetime - err = windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) + err := windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) if err != nil { c.reportError(ch, nil, err) return @@ -111,6 +107,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) { ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process. } +// describe returns all descriptions of the collector for windows. +// Ensure that this list of descriptors is kept in sync with the metrics collected +// in the processCollect method. Any changes to the metrics in processCollect +// (such as adding or removing metrics) should be reflected in this list of descriptors. +func (c *processCollector) describe(ch chan<- *Desc) { + ch <- c.cpuTotal + ch <- c.openFDs + ch <- c.maxFDs + ch <- c.vsize + ch <- c.rss + ch <- c.startTime +} + func fileTimeToSeconds(ft windows.Filetime) float64 { return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7 } diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go index 9819917b83..315eab5f17 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go @@ -76,6 +76,12 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) { return n, err } +// Unwrap lets http.ResponseController get the underlying http.ResponseWriter, +// by implementing the [rwUnwrapper](https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/net/http/responsecontroller.go;l=42-44) interface. +func (r *responseWriterDelegator) Unwrap() http.ResponseWriter { + return r.ResponseWriter +} + type ( closeNotifierDelegator struct{ *responseWriterDelegator } flusherDelegator struct{ *responseWriterDelegator } diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go index 09b8d2fbea..763d99e362 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -38,13 +38,14 @@ import ( "io" "net/http" "strconv" - "strings" "sync" "time" "github.com/prometheus/common/expfmt" + "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp/internal" ) const ( @@ -54,6 +55,24 @@ const ( processStartTimeHeader = "Process-Start-Time-Unix" ) +// Compression represents the content encodings handlers support for the HTTP +// responses. +type Compression string + +const ( + Identity Compression = "identity" + Gzip Compression = "gzip" + Zstd Compression = "zstd" +) + +func defaultCompressionFormats() []Compression { + if internal.NewZstdWriter != nil { + return []Compression{Identity, Gzip, Zstd} + } else { + return []Compression{Identity, Gzip} + } +} + var gzipPool = sync.Pool{ New: func() interface{} { return gzip.NewWriter(nil) @@ -122,6 +141,18 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO } } + // Select compression formats to offer based on default or user choice. + var compressions []string + if !opts.DisableCompression { + offers := defaultCompressionFormats() + if len(opts.OfferedCompressions) > 0 { + offers = opts.OfferedCompressions + } + for _, comp := range offers { + compressions = append(compressions, string(comp)) + } + } + h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { if !opts.ProcessStartTime.IsZero() { rsp.Header().Set(processStartTimeHeader, strconv.FormatInt(opts.ProcessStartTime.Unix(), 10)) @@ -165,22 +196,30 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO } else { contentType = expfmt.Negotiate(req.Header) } - header := rsp.Header() - header.Set(contentTypeHeader, string(contentType)) + rsp.Header().Set(contentTypeHeader, string(contentType)) - w := io.Writer(rsp) - if !opts.DisableCompression && gzipAccepted(req.Header) { - header.Set(contentEncodingHeader, "gzip") - gz := gzipPool.Get().(*gzip.Writer) - defer gzipPool.Put(gz) + w, encodingHeader, closeWriter, err := negotiateEncodingWriter(req, rsp, compressions) + if err != nil { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error getting writer", err) + } + w = io.Writer(rsp) + encodingHeader = string(Identity) + } - gz.Reset(w) - defer gz.Close() + defer closeWriter() - w = gz + // Set Content-Encoding only when data is compressed + if encodingHeader != string(Identity) { + rsp.Header().Set(contentEncodingHeader, encodingHeader) } - enc := expfmt.NewEncoder(w, contentType) + var enc expfmt.Encoder + if opts.EnableOpenMetricsTextCreatedSamples { + enc = expfmt.NewEncoder(w, contentType, expfmt.WithCreatedLines()) + } else { + enc = expfmt.NewEncoder(w, contentType) + } // handleError handles the error according to opts.ErrorHandling // and returns true if we have to abort after the handling. @@ -343,9 +382,19 @@ type HandlerOpts struct { // no effect on the HTTP status code because ErrorHandling is set to // ContinueOnError. Registry prometheus.Registerer - // If DisableCompression is true, the handler will never compress the - // response, even if requested by the client. + // DisableCompression disables the response encoding (compression) and + // encoding negotiation. If true, the handler will + // never compress the response, even if requested + // by the client and the OfferedCompressions field is set. DisableCompression bool + // OfferedCompressions is a set of encodings (compressions) handler will + // try to offer when negotiating with the client. This defaults to identity, gzip + // and zstd. + // NOTE: If handler can't agree with the client on the encodings or + // unsupported or empty encodings are set in OfferedCompressions, + // handler always fallbacks to no compression (identity), for + // compatibility reasons. In such cases ErrorLog will be used if set. + OfferedCompressions []Compression // The number of concurrent HTTP requests is limited to // MaxRequestsInFlight. Additional requests are responded to with 503 // Service Unavailable and a suitable message in the body. If @@ -371,6 +420,21 @@ type HandlerOpts struct { // (which changes the identity of the resulting series on the Prometheus // server). EnableOpenMetrics bool + // EnableOpenMetricsTextCreatedSamples specifies if this handler should add, extra, synthetic + // Created Timestamps for counters, histograms and summaries, which for the current + // version of OpenMetrics are defined as extra series with the same name and "_created" + // suffix. See also the OpenMetrics specification for more details + // https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1 + // + // Created timestamps are used to improve the accuracy of reset detection, + // but the way it's designed in OpenMetrics 1.0 it also dramatically increases cardinality + // if the scraper does not handle those metrics correctly (converting to created timestamp + // instead of leaving those series as-is). New OpenMetrics versions might improve + // this situation. + // + // Prometheus introduced the feature flag 'created-timestamp-zero-ingestion' + // in version 2.50.0 to handle this situation. + EnableOpenMetricsTextCreatedSamples bool // ProcessStartTime allows setting process start timevalue that will be exposed // with "Process-Start-Time-Unix" response header along with the metrics // payload. This allow callers to have efficient transformations to cumulative @@ -381,19 +445,6 @@ type HandlerOpts struct { ProcessStartTime time.Time } -// gzipAccepted returns whether the client will accept gzip-encoded content. -func gzipAccepted(header http.Header) bool { - a := header.Get(acceptEncodingHeader) - parts := strings.Split(a, ",") - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "gzip" || strings.HasPrefix(part, "gzip;") { - return true - } - } - return false -} - // httpError removes any content-encoding header and then calls http.Error with // the provided error and http.StatusInternalServerError. Error contents is // supposed to be uncompressed plain text. Same as with a plain http.Error, this @@ -406,3 +457,36 @@ func httpError(rsp http.ResponseWriter, err error) { http.StatusInternalServerError, ) } + +// negotiateEncodingWriter reads the Accept-Encoding header from a request and +// selects the right compression based on an allow-list of supported +// compressions. It returns a writer implementing the compression and an the +// correct value that the caller can set in the response header. +func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []string) (_ io.Writer, encodingHeaderValue string, closeWriter func(), _ error) { + if len(compressions) == 0 { + return rw, string(Identity), func() {}, nil + } + + // TODO(mrueg): Replace internal/github.com/gddo once https://github.com/golang/go/issues/19307 is implemented. + selected := httputil.NegotiateContentEncoding(r, compressions) + + switch selected { + case "zstd": + if internal.NewZstdWriter == nil { + // The content encoding was not implemented yet. + return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats()) + } + writer, closeWriter, err := internal.NewZstdWriter(rw) + return writer, selected, closeWriter, err + case "gzip": + gz := gzipPool.Get().(*gzip.Writer) + gz.Reset(rw) + return gz, selected, func() { _ = gz.Close(); gzipPool.Put(gz) }, nil + case "identity": + // This means the content is not compressed. + return rw, selected, func() {}, nil + default: + // The content encoding was not implemented yet. + return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats()) + } +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/internal/compression.go similarity index 70% rename from go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go rename to go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/internal/compression.go index b1e363d6cf..c5039590f7 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/promhttp/internal/compression.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Prometheus Authors +// Copyright 2025 The Prometheus Authors // 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 @@ -11,16 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build js -// +build js +package internal -package prometheus +import ( + "io" +) -func canCollectProcess() bool { - return false -} - -func (c *processCollector) processCollect(ch chan<- Metric) { - // noop on this platform - return -} +// NewZstdWriter enables zstd write support if non-nil. +var NewZstdWriter func(rw io.Writer) (_ io.Writer, closeWriter func(), _ error) diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/registry.go index 5e2ced25a0..c6fd2f58b7 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/registry.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/registry.go @@ -314,16 +314,17 @@ func (r *Registry) Register(c Collector) error { if dimHash != desc.dimHash { return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc) } - } else { - // ...then check the new descriptors already seen. - if dimHash, exists := newDimHashesByName[desc.fqName]; exists { - if dimHash != desc.dimHash { - return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc) - } - } else { - newDimHashesByName[desc.fqName] = desc.dimHash + continue + } + + // ...then check the new descriptors already seen. + if dimHash, exists := newDimHashesByName[desc.fqName]; exists { + if dimHash != desc.dimHash { + return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc) } + continue } + newDimHashesByName[desc.fqName] = desc.dimHash } // A Collector yielding no Desc at all is considered unchecked. if len(newDescIDs) == 0 { diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/summary.go index 1462704446..ac5203c6fa 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/summary.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/summary.go @@ -243,6 +243,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { s := &summary{ desc: desc, + now: opts.now, objectives: opts.Objectives, sortedObjectives: make([]float64, 0, len(opts.Objectives)), @@ -280,6 +281,8 @@ type summary struct { desc *Desc + now func() time.Time + objectives map[float64]float64 sortedObjectives []float64 @@ -307,7 +310,7 @@ func (s *summary) Observe(v float64) { s.bufMtx.Lock() defer s.bufMtx.Unlock() - now := time.Now() + now := s.now() if now.After(s.hotBufExpTime) { s.asyncFlush(now) } @@ -326,7 +329,7 @@ func (s *summary) Write(out *dto.Metric) error { s.bufMtx.Lock() s.mtx.Lock() // Swap bufs even if hotBuf is empty to set new hotBufExpTime. - s.swapBufs(time.Now()) + s.swapBufs(s.now()) s.bufMtx.Unlock() s.flushColdBuf() @@ -783,3 +786,45 @@ func MustNewConstSummary( } return m } + +// NewConstSummaryWithCreatedTimestamp does the same thing as NewConstSummary but sets the created timestamp. +func NewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]float64, + ct time.Time, + labelValues ...string, +) (Metric, error) { + if desc.err != nil { + return nil, desc.err + } + if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil { + return nil, err + } + return &constSummary{ + desc: desc, + count: count, + sum: sum, + quantiles: quantiles, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstSummaryWithCreatedTimestamp is a version of NewConstSummaryWithCreatedTimestamp that panics where +// NewConstSummaryWithCreatedTimestamp would have returned an error. +func MustNewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]float64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstSummaryWithCreatedTimestamp(desc, count, sum, quantiles, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} diff --git a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/vec.go index 955cfd59f8..2c808eece0 100644 --- a/go-controller/vendor/github.com/prometheus/client_golang/prometheus/vec.go +++ b/go-controller/vendor/github.com/prometheus/client_golang/prometheus/vec.go @@ -507,7 +507,7 @@ func (m *metricMap) getOrCreateMetricWithLabelValues( return metric } -// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value +// getOrCreateMetricWithLabels retrieves the metric by hash and label value // or creates it and returns the new one. // // This function holds the mutex. diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/decode.go b/go-controller/vendor/github.com/prometheus/common/expfmt/decode.go index 25cfaa2164..1448439b7f 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/decode.go @@ -45,7 +45,7 @@ func ResponseFormat(h http.Header) Format { mediatype, params, err := mime.ParseMediaType(ct) if err != nil { - return fmtUnknown + return FmtUnknown } const textType = "text/plain" @@ -53,21 +53,21 @@ func ResponseFormat(h http.Header) Format { switch mediatype { case ProtoType: if p, ok := params["proto"]; ok && p != ProtoProtocol { - return fmtUnknown + return FmtUnknown } if e, ok := params["encoding"]; ok && e != "delimited" { - return fmtUnknown + return FmtUnknown } - return fmtProtoDelim + return FmtProtoDelim case textType: if v, ok := params["version"]; ok && v != TextVersion { - return fmtUnknown + return FmtUnknown } - return fmtText + return FmtText } - return fmtUnknown + return FmtUnknown } // NewDecoder returns a new decoder based on the given input format. diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/encode.go b/go-controller/vendor/github.com/prometheus/common/expfmt/encode.go index ff5ef7a9d9..d7f3d76f55 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/encode.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/encode.go @@ -68,7 +68,7 @@ func Negotiate(h http.Header) Format { if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" { switch Format(escapeParam) { case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: - escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam)) + escapingScheme = Format("; escaping=" + escapeParam) default: // If the escaping parameter is unknown, ignore it. } @@ -77,18 +77,18 @@ func Negotiate(h http.Header) Format { if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { switch ac.Params["encoding"] { case "delimited": - return fmtProtoDelim + escapingScheme + return FmtProtoDelim + escapingScheme case "text": - return fmtProtoText + escapingScheme + return FmtProtoText + escapingScheme case "compact-text": - return fmtProtoCompact + escapingScheme + return FmtProtoCompact + escapingScheme } } if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { - return fmtText + escapingScheme + return FmtText + escapingScheme } } - return fmtText + escapingScheme + return FmtText + escapingScheme } // NegotiateIncludingOpenMetrics works like Negotiate but includes @@ -101,7 +101,7 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format { if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" { switch Format(escapeParam) { case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: - escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam)) + escapingScheme = Format("; escaping=" + escapeParam) default: // If the escaping parameter is unknown, ignore it. } @@ -110,26 +110,26 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format { if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { switch ac.Params["encoding"] { case "delimited": - return fmtProtoDelim + escapingScheme + return FmtProtoDelim + escapingScheme case "text": - return fmtProtoText + escapingScheme + return FmtProtoText + escapingScheme case "compact-text": - return fmtProtoCompact + escapingScheme + return FmtProtoCompact + escapingScheme } } if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { - return fmtText + escapingScheme + return FmtText + escapingScheme } if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") { switch ver { case OpenMetricsVersion_1_0_0: - return fmtOpenMetrics_1_0_0 + escapingScheme + return FmtOpenMetrics_1_0_0 + escapingScheme default: - return fmtOpenMetrics_0_0_1 + escapingScheme + return FmtOpenMetrics_0_0_1 + escapingScheme } } } - return fmtText + escapingScheme + return FmtText + escapingScheme } // NewEncoder returns a new encoder based on content type negotiation. All diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/expfmt.go b/go-controller/vendor/github.com/prometheus/common/expfmt/expfmt.go index 051b38cd17..b26886560d 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -15,7 +15,7 @@ package expfmt import ( - "fmt" + "errors" "strings" "github.com/prometheus/common/model" @@ -32,24 +32,31 @@ type Format string // it on the wire, new content-type strings will have to be agreed upon and // added here. const ( - TextVersion = "0.0.4" - ProtoType = `application/vnd.google.protobuf` - ProtoProtocol = `io.prometheus.client.MetricFamily` - protoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" + TextVersion = "0.0.4" + ProtoType = `application/vnd.google.protobuf` + ProtoProtocol = `io.prometheus.client.MetricFamily` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. + ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" OpenMetricsType = `application/openmetrics-text` OpenMetricsVersion_0_0_1 = "0.0.1" OpenMetricsVersion_1_0_0 = "1.0.0" - // The Content-Type values for the different wire protocols. Note that these - // values are now unexported. If code was relying on comparisons to these - // constants, instead use FormatType(). - fmtUnknown Format = `` - fmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` - fmtProtoDelim Format = protoFmt + ` encoding=delimited` - fmtProtoText Format = protoFmt + ` encoding=text` - fmtProtoCompact Format = protoFmt + ` encoding=compact-text` - fmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` - fmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` + // The Content-Type values for the different wire protocols. Do not do direct + // comparisons to these constants, instead use the comparison functions. + // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead. + FmtUnknown Format = `` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead. + FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead. + FmtProtoDelim Format = ProtoFmt + ` encoding=delimited` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead. + FmtProtoText Format = ProtoFmt + ` encoding=text` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. + FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` ) const ( @@ -79,17 +86,17 @@ const ( func NewFormat(t FormatType) Format { switch t { case TypeProtoCompact: - return fmtProtoCompact + return FmtProtoCompact case TypeProtoDelim: - return fmtProtoDelim + return FmtProtoDelim case TypeProtoText: - return fmtProtoText + return FmtProtoText case TypeTextPlain: - return fmtText + return FmtText case TypeOpenMetrics: - return fmtOpenMetrics_1_0_0 + return FmtOpenMetrics_1_0_0 default: - return fmtUnknown + return FmtUnknown } } @@ -97,12 +104,35 @@ func NewFormat(t FormatType) Format { // specified version number. func NewOpenMetricsFormat(version string) (Format, error) { if version == OpenMetricsVersion_0_0_1 { - return fmtOpenMetrics_0_0_1, nil + return FmtOpenMetrics_0_0_1, nil } if version == OpenMetricsVersion_1_0_0 { - return fmtOpenMetrics_1_0_0, nil + return FmtOpenMetrics_1_0_0, nil } - return fmtUnknown, fmt.Errorf("unknown open metrics version string") + return FmtUnknown, errors.New("unknown open metrics version string") +} + +// WithEscapingScheme returns a copy of Format with the specified escaping +// scheme appended to the end. If an escaping scheme already exists it is +// removed. +func (f Format) WithEscapingScheme(s model.EscapingScheme) Format { + var terms []string + for _, p := range strings.Split(string(f), ";") { + toks := strings.Split(p, "=") + if len(toks) != 2 { + trimmed := strings.TrimSpace(p) + if len(trimmed) > 0 { + terms = append(terms, trimmed) + } + continue + } + key := strings.TrimSpace(toks[0]) + if key != model.EscapingKey { + terms = append(terms, strings.TrimSpace(p)) + } + } + terms = append(terms, model.EscapingKey+"="+s.String()) + return Format(strings.Join(terms, "; ")) } // FormatType deduces an overall FormatType for the given format. diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/go-controller/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go index 353c5e93f9..a21ed4ec1f 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go @@ -38,7 +38,7 @@ type EncoderOption func(*encoderOption) // WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder // to include _created lines (See -// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1). +// https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1). // Created timestamps can improve the accuracy of series reset detection, but // come with a bandwidth cost. // @@ -102,7 +102,7 @@ func WithUnit() EncoderOption { // // - According to the OM specs, the `# UNIT` line is optional, but if populated, // the unit has to be present in the metric name as its suffix: -// (see https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit). +// (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit). // However, in order to accommodate any potential scenario where such a change in the // metric name is not desirable, the users are here given the choice of either explicitly // opt in, in case they wish for the unit to be included in the output AND in the metric name @@ -152,8 +152,8 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") { compliantName = name[:len(name)-6] } - if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, fmt.Sprintf("_%s", *in.Unit)) { - compliantName = compliantName + fmt.Sprintf("_%s", *in.Unit) + if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, "_"+*in.Unit) { + compliantName = compliantName + "_" + *in.Unit } // Comments, first HELP, then TYPE. @@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces, quoted. - if !model.IsValidLegacyMetricName(model.LabelValue(name)) { + if !model.IsValidLegacyMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/text_create.go b/go-controller/vendor/github.com/prometheus/common/expfmt/text_create.go index f9b8265a9e..4b86434b33 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/text_create.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/text_create.go @@ -354,7 +354,7 @@ func writeNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces. - if !model.IsValidLegacyMetricName(model.LabelValue(name)) { + if !model.IsValidLegacyMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ @@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) { // writeName writes a string as-is if it complies with the legacy naming // scheme, or escapes it in double quotes if not. func writeName(w enhancedWriter, name string) (int, error) { - if model.IsValidLegacyMetricName(model.LabelValue(name)) { + if model.IsValidLegacyMetricName(name) { return w.WriteString(name) } var written int diff --git a/go-controller/vendor/github.com/prometheus/common/expfmt/text_parse.go b/go-controller/vendor/github.com/prometheus/common/expfmt/text_parse.go index 26490211af..b4607fe4d2 100644 --- a/go-controller/vendor/github.com/prometheus/common/expfmt/text_parse.go +++ b/go-controller/vendor/github.com/prometheus/common/expfmt/text_parse.go @@ -22,9 +22,9 @@ import ( "math" "strconv" "strings" + "unicode/utf8" dto "github.com/prometheus/client_model/go" - "google.golang.org/protobuf/proto" "github.com/prometheus/common/model" @@ -60,6 +60,7 @@ type TextParser struct { currentMF *dto.MetricFamily currentMetric *dto.Metric currentLabelPair *dto.LabelPair + currentLabelPairs []*dto.LabelPair // Temporarily stores label pairs while parsing a metric line. // The remaining member variables are only used for summaries/histograms. currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le' @@ -74,6 +75,9 @@ type TextParser struct { // count and sum of that summary/histogram. currentIsSummaryCount, currentIsSummarySum bool currentIsHistogramCount, currentIsHistogramSum bool + // These indicate if the metric name from the current line being parsed is inside + // braces and if that metric name was found respectively. + currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool } // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange @@ -137,12 +141,15 @@ func (p *TextParser) reset(in io.Reader) { } p.currentQuantile = math.NaN() p.currentBucket = math.NaN() + p.currentMF = nil } // startOfLine represents the state where the next byte read from p.buf is the // start of a line (or whitespace leading up to it). func (p *TextParser) startOfLine() stateFn { p.lineCount++ + p.currentMetricIsInsideBraces = false + p.currentMetricInsideBracesIsPresent = false if p.skipBlankTab(); p.err != nil { // This is the only place that we expect to see io.EOF, // which is not an error but the signal that we are done. @@ -158,6 +165,9 @@ func (p *TextParser) startOfLine() stateFn { return p.startComment case '\n': return p.startOfLine // Empty line, start the next one. + case '{': + p.currentMetricIsInsideBraces = true + return p.readingLabels } return p.readingMetricName } @@ -275,6 +285,8 @@ func (p *TextParser) startLabelName() stateFn { return nil // Unexpected end of input. } if p.currentByte == '}' { + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } @@ -287,6 +299,45 @@ func (p *TextParser) startLabelName() stateFn { p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName())) return nil } + if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { + return nil // Unexpected end of input. + } + if p.currentByte != '=' { + if p.currentMetricIsInsideBraces { + if p.currentMetricInsideBracesIsPresent { + p.parseError(fmt.Sprintf("multiple metric names for metric %q", p.currentMF.GetName())) + return nil + } + switch p.currentByte { + case ',': + p.setOrCreateCurrentMF() + if p.currentMF.Type == nil { + p.currentMF.Type = dto.MetricType_UNTYPED.Enum() + } + p.currentMetric = &dto.Metric{} + p.currentMetricInsideBracesIsPresent = true + return p.startLabelName + case '}': + p.setOrCreateCurrentMF() + if p.currentMF.Type == nil { + p.currentMF.Type = dto.MetricType_UNTYPED.Enum() + } + p.currentMetric = &dto.Metric{} + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil + if p.skipBlankTab(); p.err != nil { + return nil // Unexpected end of input. + } + return p.readingValue + default: + p.parseError(fmt.Sprintf("unexpected end of metric name %q", p.currentByte)) + return nil + } + } + p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) + p.currentLabelPairs = nil + return nil + } p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())} if p.currentLabelPair.GetName() == string(model.MetricNameLabel) { p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel)) @@ -296,23 +347,17 @@ func (p *TextParser) startLabelName() stateFn { // labels to 'real' labels. if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) { - p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair) - } - if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { - return nil // Unexpected end of input. - } - if p.currentByte != '=' { - p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) - return nil + p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) } // Check for duplicate label names. labels := make(map[string]struct{}) - for _, l := range p.currentMetric.Label { + for _, l := range p.currentLabelPairs { lName := l.GetName() if _, exists := labels[lName]; !exists { labels[lName] = struct{}{} } else { p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName())) + p.currentLabelPairs = nil return nil } } @@ -345,6 +390,7 @@ func (p *TextParser) startLabelValue() stateFn { if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue())) + p.currentLabelPairs = nil return nil } } else { @@ -371,12 +417,19 @@ func (p *TextParser) startLabelValue() stateFn { return p.startLabelName case '}': + if p.currentMF == nil { + p.parseError("invalid metric name") + return nil + } + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } return p.readingValue default: p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue())) + p.currentLabelPairs = nil return nil } } @@ -585,6 +638,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return @@ -610,13 +665,45 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { // but not into p.currentToken. func (p *TextParser) readTokenAsMetricName() { p.currentToken.Reset() + // A UTF-8 metric name must be quoted and may have escaped characters. + quoted := false + escaped := false if !isValidMetricNameStart(p.currentByte) { return } - for { - p.currentToken.WriteByte(p.currentByte) + for p.err == nil { + if escaped { + switch p.currentByte { + case '\\': + p.currentToken.WriteByte(p.currentByte) + case 'n': + p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') + default: + p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + return + } + escaped = false + } else { + switch p.currentByte { + case '"': + quoted = !quoted + if !quoted { + p.currentByte, p.err = p.buf.ReadByte() + return + } + case '\n': + p.parseError(fmt.Sprintf("metric name %q contains unescaped new-line", p.currentToken.String())) + return + case '\\': + escaped = true + default: + p.currentToken.WriteByte(p.currentByte) + } + } p.currentByte, p.err = p.buf.ReadByte() - if p.err != nil || !isValidMetricNameContinuation(p.currentByte) { + if !isValidMetricNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == ' ') { return } } @@ -628,13 +715,45 @@ func (p *TextParser) readTokenAsMetricName() { // but not into p.currentToken. func (p *TextParser) readTokenAsLabelName() { p.currentToken.Reset() + // A UTF-8 label name must be quoted and may have escaped characters. + quoted := false + escaped := false if !isValidLabelNameStart(p.currentByte) { return } - for { - p.currentToken.WriteByte(p.currentByte) + for p.err == nil { + if escaped { + switch p.currentByte { + case '\\': + p.currentToken.WriteByte(p.currentByte) + case 'n': + p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') + default: + p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + return + } + escaped = false + } else { + switch p.currentByte { + case '"': + quoted = !quoted + if !quoted { + p.currentByte, p.err = p.buf.ReadByte() + return + } + case '\n': + p.parseError(fmt.Sprintf("label name %q contains unescaped new-line", p.currentToken.String())) + return + case '\\': + escaped = true + default: + p.currentToken.WriteByte(p.currentByte) + } + } p.currentByte, p.err = p.buf.ReadByte() - if p.err != nil || !isValidLabelNameContinuation(p.currentByte) { + if !isValidLabelNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == '=') { return } } @@ -660,6 +779,7 @@ func (p *TextParser) readTokenAsLabelValue() { p.currentToken.WriteByte('\n') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + p.currentLabelPairs = nil return } escaped = false @@ -718,19 +838,19 @@ func (p *TextParser) setOrCreateCurrentMF() { } func isValidLabelNameStart(b byte) bool { - return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '"' } -func isValidLabelNameContinuation(b byte) bool { - return isValidLabelNameStart(b) || (b >= '0' && b <= '9') +func isValidLabelNameContinuation(b byte, quoted bool) bool { + return isValidLabelNameStart(b) || (b >= '0' && b <= '9') || (quoted && utf8.ValidString(string(b))) } func isValidMetricNameStart(b byte) bool { return isValidLabelNameStart(b) || b == ':' } -func isValidMetricNameContinuation(b byte) bool { - return isValidLabelNameContinuation(b) || b == ':' +func isValidMetricNameContinuation(b byte, quoted bool) bool { + return isValidLabelNameContinuation(b, quoted) || b == ':' } func isBlankOrTab(b byte) bool { @@ -775,7 +895,7 @@ func histogramMetricName(name string) string { func parseFloat(s string) (float64, error) { if strings.ContainsAny(s, "pP_") { - return 0, fmt.Errorf("unsupported character in float") + return 0, errors.New("unsupported character in float") } return strconv.ParseFloat(s, 64) } diff --git a/go-controller/vendor/github.com/prometheus/common/model/alert.go b/go-controller/vendor/github.com/prometheus/common/model/alert.go index 80d1fe944e..bd3a39e3e1 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/alert.go +++ b/go-controller/vendor/github.com/prometheus/common/model/alert.go @@ -14,6 +14,7 @@ package model import ( + "errors" "fmt" "time" ) @@ -89,16 +90,16 @@ func (a *Alert) StatusAt(ts time.Time) AlertStatus { // Validate checks whether the alert data is inconsistent. func (a *Alert) Validate() error { if a.StartsAt.IsZero() { - return fmt.Errorf("start time missing") + return errors.New("start time missing") } if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) { - return fmt.Errorf("start time must be before end time") + return errors.New("start time must be before end time") } if err := a.Labels.Validate(); err != nil { return fmt.Errorf("invalid label set: %w", err) } if len(a.Labels) == 0 { - return fmt.Errorf("at least one label pair required") + return errors.New("at least one label pair required") } if err := a.Annotations.Validate(); err != nil { return fmt.Errorf("invalid annotations: %w", err) diff --git a/go-controller/vendor/github.com/prometheus/common/model/labels.go b/go-controller/vendor/github.com/prometheus/common/model/labels.go index 3317ce22ff..73b7aa3e60 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/labels.go +++ b/go-controller/vendor/github.com/prometheus/common/model/labels.go @@ -97,26 +97,35 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") // therewith. type LabelName string -// IsValid returns true iff name matches the pattern of LabelNameRE for legacy -// names, and iff it's valid UTF-8 if NameValidationScheme is set to -// UTF8Validation. For the legacy matching, it does not use LabelNameRE for the -// check but a much faster hardcoded implementation. +// IsValid returns true iff the name matches the pattern of LabelNameRE when +// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if +// NameValidationScheme is set to UTF8Validation. func (ln LabelName) IsValid() bool { if len(ln) == 0 { return false } switch NameValidationScheme { case LegacyValidation: - for i, b := range ln { - if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { - return false - } - } + return ln.IsValidLegacy() case UTF8Validation: return utf8.ValidString(string(ln)) default: panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme)) } +} + +// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for +// legacy names. It does not use LabelNameRE for the check but a much faster +// hardcoded implementation. +func (ln LabelName) IsValidLegacy() bool { + if len(ln) == 0 { + return false + } + for i, b := range ln { + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { + return false + } + } return true } diff --git a/go-controller/vendor/github.com/prometheus/common/model/labelset_string.go b/go-controller/vendor/github.com/prometheus/common/model/labelset_string.go index 481c47b46e..abb2c90018 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/labelset_string.go +++ b/go-controller/vendor/github.com/prometheus/common/model/labelset_string.go @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build go1.21 - package model import ( diff --git a/go-controller/vendor/github.com/prometheus/common/model/labelset_string_go120.go b/go-controller/vendor/github.com/prometheus/common/model/labelset_string_go120.go deleted file mode 100644 index c4212685e7..0000000000 --- a/go-controller/vendor/github.com/prometheus/common/model/labelset_string_go120.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Prometheus Authors -// 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. - -//go:build !go1.21 - -package model - -import ( - "fmt" - "sort" - "strings" -) - -// String was optimized using functions not available for go 1.20 -// or lower. We keep the old implementation for compatibility with client_golang. -// Once client golang drops support for go 1.20 (scheduled for August 2024), this -// file can be removed. -func (l LabelSet) String() string { - labelNames := make([]string, 0, len(l)) - for name := range l { - labelNames = append(labelNames, string(name)) - } - sort.Strings(labelNames) - lstrs := make([]string, 0, len(l)) - for _, name := range labelNames { - lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)])) - } - return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) -} diff --git a/go-controller/vendor/github.com/prometheus/common/model/metric.go b/go-controller/vendor/github.com/prometheus/common/model/metric.go index eb865e5a59..5766107cf9 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/metric.go +++ b/go-controller/vendor/github.com/prometheus/common/model/metric.go @@ -14,9 +14,11 @@ package model import ( + "errors" "fmt" "regexp" "sort" + "strconv" "strings" "unicode/utf8" @@ -26,18 +28,21 @@ import ( var ( // NameValidationScheme determines the method of name validation to be used by - // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 mode - // in isolation from other components that don't support UTF-8 may result in - // bugs or other undefined behavior. This value is intended to be set by - // UTF-8-aware binaries as part of their startup. To avoid need for locking, - // this value should be set once, ideally in an init(), before multiple - // goroutines are started. - NameValidationScheme = LegacyValidation - - // NameEscapingScheme defines the default way that names will be - // escaped when presented to systems that do not support UTF-8 names. If the - // Content-Type "escaping" term is specified, that will override this value. - NameEscapingScheme = ValueEncodingEscaping + // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 + // mode in isolation from other components that don't support UTF-8 may result + // in bugs or other undefined behavior. This value can be set to + // LegacyValidation during startup if a binary is not UTF-8-aware binaries. To + // avoid need for locking, this value should be set once, ideally in an + // init(), before multiple goroutines are started. + NameValidationScheme = UTF8Validation + + // NameEscapingScheme defines the default way that names will be escaped when + // presented to systems that do not support UTF-8 names. If the Content-Type + // "escaping" term is specified, that will override this value. + // NameEscapingScheme should not be set to the NoEscaping value. That string + // is used in content negotiation to indicate that a system supports UTF-8 and + // has that feature enabled. + NameEscapingScheme = UnderscoreEscaping ) // ValidationScheme is a Go enum for determining how metric and label names will @@ -161,7 +166,7 @@ func (m Metric) FastFingerprint() Fingerprint { func IsValidMetricName(n LabelValue) bool { switch NameValidationScheme { case LegacyValidation: - return IsValidLegacyMetricName(n) + return IsValidLegacyMetricName(string(n)) case UTF8Validation: if len(n) == 0 { return false @@ -176,7 +181,7 @@ func IsValidMetricName(n LabelValue) bool { // legacy validation scheme regardless of the value of NameValidationScheme. // This function, however, does not use MetricNameRE for the check but a much // faster hardcoded implementation. -func IsValidLegacyMetricName(n LabelValue) bool { +func IsValidLegacyMetricName(n string) bool { if len(n) == 0 { return false } @@ -208,7 +213,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF } // If the name is nil, copy as-is, don't try to escape. - if v.Name == nil || IsValidLegacyMetricName(LabelValue(v.GetName())) { + if v.Name == nil || IsValidLegacyMetricName(v.GetName()) { out.Name = v.Name } else { out.Name = proto.String(EscapeName(v.GetName(), scheme)) @@ -230,7 +235,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF for _, l := range m.Label { if l.GetName() == MetricNameLabel { - if l.Value == nil || IsValidLegacyMetricName(LabelValue(l.GetValue())) { + if l.Value == nil || IsValidLegacyMetricName(l.GetValue()) { escaped.Label = append(escaped.Label, l) continue } @@ -240,7 +245,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF }) continue } - if l.Name == nil || IsValidLegacyMetricName(LabelValue(l.GetName())) { + if l.Name == nil || IsValidLegacyMetricName(l.GetName()) { escaped.Label = append(escaped.Label, l) continue } @@ -256,20 +261,16 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF func metricNeedsEscaping(m *dto.Metric) bool { for _, l := range m.Label { - if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(LabelValue(l.GetValue())) { + if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(l.GetValue()) { return true } - if !IsValidLegacyMetricName(LabelValue(l.GetName())) { + if !IsValidLegacyMetricName(l.GetName()) { return true } } return false } -const ( - lowerhex = "0123456789abcdef" -) - // EscapeName escapes the incoming name according to the provided escaping // scheme. Depending on the rules of escaping, this may cause no change in the // string that is returned. (Especially NoEscaping, which by definition is a @@ -283,7 +284,7 @@ func EscapeName(name string, scheme EscapingScheme) string { case NoEscaping: return name case UnderscoreEscaping: - if IsValidLegacyMetricName(LabelValue(name)) { + if IsValidLegacyMetricName(name) { return name } for i, b := range name { @@ -304,31 +305,25 @@ func EscapeName(name string, scheme EscapingScheme) string { } else if isValidLegacyRune(b, i) { escaped.WriteRune(b) } else { - escaped.WriteRune('_') + escaped.WriteString("__") } } return escaped.String() case ValueEncodingEscaping: - if IsValidLegacyMetricName(LabelValue(name)) { + if IsValidLegacyMetricName(name) { return name } escaped.WriteString("U__") for i, b := range name { - if isValidLegacyRune(b, i) { + if b == '_' { + escaped.WriteString("__") + } else if isValidLegacyRune(b, i) { escaped.WriteRune(b) } else if !utf8.ValidRune(b) { escaped.WriteString("_FFFD_") - } else if b < 0x100 { - escaped.WriteRune('_') - for s := 4; s >= 0; s -= 4 { - escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) - } - escaped.WriteRune('_') - } else if b < 0x10000 { + } else { escaped.WriteRune('_') - for s := 12; s >= 0; s -= 4 { - escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) - } + escaped.WriteString(strconv.FormatInt(int64(b), 16)) escaped.WriteRune('_') } } @@ -386,8 +381,9 @@ func UnescapeName(name string, scheme EscapingScheme) string { // We think we are in a UTF-8 code, process it. var utf8Val uint for j := 0; i < len(escapedName); j++ { - // This is too many characters for a utf8 value. - if j > 4 { + // This is too many characters for a utf8 value based on the MaxRune + // value of '\U0010FFFF'. + if j >= 6 { return name } // Found a closing underscore, convert to a rune, check validity, and append. @@ -440,7 +436,7 @@ func (e EscapingScheme) String() string { func ToEscapingScheme(s string) (EscapingScheme, error) { if s == "" { - return NoEscaping, fmt.Errorf("got empty string instead of escaping scheme") + return NoEscaping, errors.New("got empty string instead of escaping scheme") } switch s { case AllowUTF8: @@ -452,6 +448,6 @@ func ToEscapingScheme(s string) (EscapingScheme, error) { case EscapeValues: return ValueEncodingEscaping, nil default: - return NoEscaping, fmt.Errorf("unknown format scheme " + s) + return NoEscaping, fmt.Errorf("unknown format scheme %s", s) } } diff --git a/go-controller/vendor/github.com/prometheus/common/model/silence.go b/go-controller/vendor/github.com/prometheus/common/model/silence.go index 910b0b71fc..8f91a9702e 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/silence.go +++ b/go-controller/vendor/github.com/prometheus/common/model/silence.go @@ -15,6 +15,7 @@ package model import ( "encoding/json" + "errors" "fmt" "regexp" "time" @@ -34,7 +35,7 @@ func (m *Matcher) UnmarshalJSON(b []byte) error { } if len(m.Name) == 0 { - return fmt.Errorf("label name in matcher must not be empty") + return errors.New("label name in matcher must not be empty") } if m.IsRegex { if _, err := regexp.Compile(m.Value); err != nil { @@ -77,7 +78,7 @@ type Silence struct { // Validate returns true iff all fields of the silence have valid values. func (s *Silence) Validate() error { if len(s.Matchers) == 0 { - return fmt.Errorf("at least one matcher required") + return errors.New("at least one matcher required") } for _, m := range s.Matchers { if err := m.Validate(); err != nil { @@ -85,22 +86,22 @@ func (s *Silence) Validate() error { } } if s.StartsAt.IsZero() { - return fmt.Errorf("start time missing") + return errors.New("start time missing") } if s.EndsAt.IsZero() { - return fmt.Errorf("end time missing") + return errors.New("end time missing") } if s.EndsAt.Before(s.StartsAt) { - return fmt.Errorf("start time must be before end time") + return errors.New("start time must be before end time") } if s.CreatedBy == "" { - return fmt.Errorf("creator information missing") + return errors.New("creator information missing") } if s.Comment == "" { - return fmt.Errorf("comment missing") + return errors.New("comment missing") } if s.CreatedAt.IsZero() { - return fmt.Errorf("creation timestamp missing") + return errors.New("creation timestamp missing") } return nil } diff --git a/go-controller/vendor/github.com/prometheus/common/model/value_float.go b/go-controller/vendor/github.com/prometheus/common/model/value_float.go index ae35cc2ab4..6bfc757d18 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/value_float.go +++ b/go-controller/vendor/github.com/prometheus/common/model/value_float.go @@ -15,6 +15,7 @@ package model import ( "encoding/json" + "errors" "fmt" "math" "strconv" @@ -39,7 +40,7 @@ func (v SampleValue) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements json.Unmarshaler. func (v *SampleValue) UnmarshalJSON(b []byte) error { if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("sample value must be a quoted string") + return errors.New("sample value must be a quoted string") } f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64) if err != nil { diff --git a/go-controller/vendor/github.com/prometheus/common/model/value_histogram.go b/go-controller/vendor/github.com/prometheus/common/model/value_histogram.go index 54bb038cff..895e6a3e83 100644 --- a/go-controller/vendor/github.com/prometheus/common/model/value_histogram.go +++ b/go-controller/vendor/github.com/prometheus/common/model/value_histogram.go @@ -15,6 +15,7 @@ package model import ( "encoding/json" + "errors" "fmt" "strconv" "strings" @@ -32,7 +33,7 @@ func (v FloatString) MarshalJSON() ([]byte, error) { func (v *FloatString) UnmarshalJSON(b []byte) error { if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("float value must be a quoted string") + return errors.New("float value must be a quoted string") } f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64) if err != nil { @@ -141,7 +142,7 @@ type SampleHistogramPair struct { func (s SampleHistogramPair) MarshalJSON() ([]byte, error) { if s.Histogram == nil { - return nil, fmt.Errorf("histogram is nil") + return nil, errors.New("histogram is nil") } t, err := json.Marshal(s.Timestamp) if err != nil { @@ -164,7 +165,7 @@ func (s *SampleHistogramPair) UnmarshalJSON(buf []byte) error { return fmt.Errorf("wrong number of fields: %d != %d", gotLen, wantLen) } if s.Histogram == nil { - return fmt.Errorf("histogram is null") + return errors.New("histogram is null") } return nil } diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 4d4b4aad6f..7e19eba090 100644 --- a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -7,10 +7,13 @@ import ( "time" ) -type CompareType int +// Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it. +type CompareType = compareResult + +type compareResult int const ( - compareLess CompareType = iota - 1 + compareLess compareResult = iota - 1 compareEqual compareGreater ) @@ -39,7 +42,7 @@ var ( bytesType = reflect.TypeOf([]byte{}) ) -func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { +func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) { obj1Value := reflect.ValueOf(obj1) obj2Value := reflect.ValueOf(obj2) @@ -325,7 +328,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time) } - return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64) + if timeObj1.Before(timeObj2) { + return compareLess, true + } + if timeObj1.Equal(timeObj2) { + return compareEqual, true + } + return compareGreater, true } case reflect.Slice: { @@ -345,7 +354,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte) } - return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true + return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true } case reflect.Uintptr: { @@ -381,7 +390,7 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // GreaterOrEqual asserts that the first element is greater than or equal to the second @@ -394,7 +403,7 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // Less asserts that the first element is less than the second @@ -406,7 +415,7 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // LessOrEqual asserts that the first element is less than or equal to the second @@ -419,7 +428,7 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } // Positive asserts that the specified element is positive @@ -431,7 +440,7 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, "\"%v\" is not positive", msgAndArgs...) } // Negative asserts that the specified element is negative @@ -443,10 +452,10 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, "\"%v\" is not negative", msgAndArgs...) } -func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { +func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } @@ -469,7 +478,7 @@ func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedCompare return true } -func containsValue(values []CompareType, value CompareType) bool { +func containsValue(values []compareResult, value compareResult) bool { for _, v := range values { if v == value { return true diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_format.go b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_format.go index 3ddab109ad..1906341657 100644 --- a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -104,8 +104,8 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -186,7 +186,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -568,6 +568,23 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) } +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// assert.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -604,7 +621,16 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...) +} + +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_forward.go index a84e09bd40..21629087ba 100644 --- a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -186,8 +186,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface return EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { @@ -197,8 +197,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn return EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -336,7 +336,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -361,7 +361,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor // a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1128,6 +1128,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin return NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -1200,7 +1234,25 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAsf(a.t, err, target, msg, args...) +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -1209,7 +1261,7 @@ func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface return NotErrorIs(a.t, err, target, msgAndArgs...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_order.go b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_order.go index 00df62a059..1d2f71824a 100644 --- a/go-controller/vendor/github.com/stretchr/testify/assert/assertion_order.go +++ b/go-controller/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -6,7 +6,7 @@ import ( ) // isOrdered checks that collection contains orderable elements. -func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { +func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool { objKind := reflect.TypeOf(object).Kind() if objKind != reflect.Slice && objKind != reflect.Array { return false @@ -50,7 +50,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT // assert.IsIncreasing(t, []float{1, 2}) // assert.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // IsNonIncreasing asserts that the collection is not increasing @@ -59,7 +59,7 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonIncreasing(t, []float{2, 1}) // assert.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // IsDecreasing asserts that the collection is decreasing @@ -68,7 +68,7 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // assert.IsDecreasing(t, []float{2, 1}) // assert.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // IsNonDecreasing asserts that the collection is not decreasing @@ -77,5 +77,5 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonDecreasing(t, []float{1, 2}) // assert.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/assertions.go b/go-controller/vendor/github.com/stretchr/testify/assert/assertions.go index 0b7570f21c..4e91332bb5 100644 --- a/go-controller/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/go-controller/vendor/github.com/stretchr/testify/assert/assertions.go @@ -19,7 +19,9 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" - "gopkg.in/yaml.v3" + + // Wrapper around gopkg.in/yaml.v3 + "github.com/stretchr/testify/assert/yaml" ) //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" @@ -45,6 +47,10 @@ type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool // for table driven tests. type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool +// PanicAssertionFunc is a common function prototype when validating a panic value. Can be useful +// for table driven tests. +type PanicAssertionFunc = func(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool + // Comparison is a custom function that returns true on success and false on failure type Comparison func() (success bool) @@ -496,7 +502,13 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b h.Helper() } - if !samePointers(expected, actual) { + same, ok := samePointers(expected, actual) + if !ok { + return Fail(t, "Both arguments must be pointers", msgAndArgs...) + } + + if !same { + // both are pointers but not the same type & pointing to the same address return Fail(t, fmt.Sprintf("Not same: \n"+ "expected: %p %#v\n"+ "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) @@ -516,7 +528,13 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} h.Helper() } - if samePointers(expected, actual) { + same, ok := samePointers(expected, actual) + if !ok { + //fails when the arguments are not pointers + return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) + } + + if same { return Fail(t, fmt.Sprintf( "Expected and actual point to the same object: %p %#v", expected, expected), msgAndArgs...) @@ -524,21 +542,23 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} return true } -// samePointers compares two generic interface objects and returns whether -// they point to the same object -func samePointers(first, second interface{}) bool { +// samePointers checks if two generic interface objects are pointers of the same +// type pointing to the same object. It returns two values: same indicating if +// they are the same type and point to the same object, and ok indicating that +// both inputs are pointers. +func samePointers(first, second interface{}) (same bool, ok bool) { firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false + return false, false //not both are pointers } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) if firstType != secondType { - return false + return false, true // both are pointers, but of different types } // compare pointer addresses - return first == second + return first == second, true } // formatUnequalValues takes two values of arbitrary types and returns string @@ -572,8 +592,8 @@ func truncatingFormat(data interface{}) string { return value } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { @@ -615,21 +635,6 @@ func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs .. return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) } - if aType.Kind() == reflect.Ptr { - aType = aType.Elem() - } - if bType.Kind() == reflect.Ptr { - bType = bType.Elem() - } - - if aType.Kind() != reflect.Struct { - return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...) - } - - if bType.Kind() != reflect.Struct { - return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...) - } - expected = copyExportedFields(expected) actual = copyExportedFields(actual) @@ -1170,6 +1175,39 @@ func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) stri return msg.String() } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// assert.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return Fail(t, "listA and listB contain the same elements", msgAndArgs) + } + + if !isList(t, listA, msgAndArgs...) { + return Fail(t, "listA is not a list type", msgAndArgs...) + } + if !isList(t, listB, msgAndArgs...) { + return Fail(t, "listB is not a list type", msgAndArgs...) + } + + extraA, extraB := diffLists(listA, listB) + if len(extraA) == 0 && len(extraB) == 0 { + return Fail(t, "listA and listB contain the same elements", msgAndArgs) + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -1488,6 +1526,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd if err != nil { return Fail(t, err.Error(), msgAndArgs...) } + if math.IsNaN(actualEpsilon) { + return Fail(t, "relative error is NaN", msgAndArgs...) + } if actualEpsilon > epsilon { return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...) @@ -1611,7 +1652,6 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // matchRegexp return true if a specified regexp matches a string. func matchRegexp(rx interface{}, str interface{}) bool { - var r *regexp.Regexp if rr, ok := rx.(*regexp.Regexp); ok { r = rr @@ -1619,7 +1659,14 @@ func matchRegexp(rx interface{}, str interface{}) bool { r = regexp.MustCompile(fmt.Sprint(rx)) } - return (r.FindStringIndex(fmt.Sprint(str)) != nil) + switch v := str.(type) { + case []byte: + return r.Match(v) + case string: + return r.MatchString(v) + default: + return r.MatchString(fmt.Sprint(v)) + } } @@ -1872,7 +1919,7 @@ var spewConfigStringerEnabled = spew.ConfigState{ MaxDepth: 10, } -type tHelper interface { +type tHelper = interface { Helper() } @@ -1911,6 +1958,9 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t // CollectT implements the TestingT interface and collects all errors. type CollectT struct { + // A slice of errors. Non-nil slice denotes a failure. + // If it's non-nil but len(c.errors) == 0, this is also a failure + // obtained by direct c.FailNow() call. errors []error } @@ -1919,9 +1969,10 @@ func (c *CollectT) Errorf(format string, args ...interface{}) { c.errors = append(c.errors, fmt.Errorf(format, args...)) } -// FailNow panics. -func (*CollectT) FailNow() { - panic("Assertion failed") +// FailNow stops execution by calling runtime.Goexit. +func (c *CollectT) FailNow() { + c.fail() + runtime.Goexit() } // Deprecated: That was a method for internal usage that should not have been published. Now just panics. @@ -1934,6 +1985,16 @@ func (*CollectT) Copy(TestingT) { panic("Copy() is deprecated") } +func (c *CollectT) fail() { + if !c.failed() { + c.errors = []error{} // Make it non-nil to mark a failure. + } +} + +func (c *CollectT) failed() bool { + return c.errors != nil +} + // EventuallyWithT asserts that given condition will be met in waitFor time, // periodically checking target function each tick. In contrast to Eventually, // it supplies a CollectT to the condition function, so that the condition @@ -1951,14 +2012,14 @@ func (*CollectT) Copy(TestingT) { // assert.EventuallyWithT(t, func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } var lastFinishedTickErrs []error - ch := make(chan []error, 1) + ch := make(chan *CollectT, 1) timer := time.NewTimer(waitFor) defer timer.Stop() @@ -1978,16 +2039,16 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time go func() { collect := new(CollectT) defer func() { - ch <- collect.errors + ch <- collect }() condition(collect) }() - case errs := <-ch: - if len(errs) == 0 { + case collect := <-ch: + if !collect.failed() { return true } // Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached. - lastFinishedTickErrs = errs + lastFinishedTickErrs = collect.errors tick = ticker.C } } @@ -2049,7 +2110,7 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { ), msgAndArgs...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -2090,6 +2151,24 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ ), msgAndArgs...) } +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !errors.As(err, target) { + return true + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ + "found: %q\n"+ + "in chain: %s", target, chain, + ), msgAndArgs...) +} + func buildErrorChainString(err error) string { if err == nil { return "" diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go new file mode 100644 index 0000000000..baa0cc7d7f --- /dev/null +++ b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go @@ -0,0 +1,25 @@ +//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default +// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default + +// Package yaml is an implementation of YAML functions that calls a pluggable implementation. +// +// This implementation is selected with the testify_yaml_custom build tag. +// +// go test -tags testify_yaml_custom +// +// This implementation can be used at build time to replace the default implementation +// to avoid linking with [gopkg.in/yaml.v3]. +// +// In your test package: +// +// import assertYaml "github.com/stretchr/testify/assert/yaml" +// +// func init() { +// assertYaml.Unmarshal = func (in []byte, out interface{}) error { +// // ... +// return nil +// } +// } +package yaml + +var Unmarshal func(in []byte, out interface{}) error diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go new file mode 100644 index 0000000000..b83c6cf64c --- /dev/null +++ b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go @@ -0,0 +1,37 @@ +//go:build !testify_yaml_fail && !testify_yaml_custom +// +build !testify_yaml_fail,!testify_yaml_custom + +// Package yaml is just an indirection to handle YAML deserialization. +// +// This package is just an indirection that allows the builder to override the +// indirection with an alternative implementation of this package that uses +// another implementation of YAML deserialization. This allows to not either not +// use YAML deserialization at all, or to use another implementation than +// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]). +// +// Alternative implementations are selected using build tags: +// +// - testify_yaml_fail: [Unmarshal] always fails with an error +// - testify_yaml_custom: [Unmarshal] is a variable. Caller must initialize it +// before calling any of [github.com/stretchr/testify/assert.YAMLEq] or +// [github.com/stretchr/testify/assert.YAMLEqf]. +// +// Usage: +// +// go test -tags testify_yaml_fail +// +// You can check with "go list" which implementation is linked: +// +// go list -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// go list -tags testify_yaml_fail -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// go list -tags testify_yaml_custom -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// +// [PR #1120]: https://github.com/stretchr/testify/pull/1120 +package yaml + +import goyaml "gopkg.in/yaml.v3" + +// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal]. +func Unmarshal(in []byte, out interface{}) error { + return goyaml.Unmarshal(in, out) +} diff --git a/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go new file mode 100644 index 0000000000..e78f7dfe69 --- /dev/null +++ b/go-controller/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go @@ -0,0 +1,18 @@ +//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default +// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default + +// Package yaml is an implementation of YAML functions that always fail. +// +// This implementation can be used at build time to replace the default implementation +// to avoid linking with [gopkg.in/yaml.v3]: +// +// go test -tags testify_yaml_fail +package yaml + +import "errors" + +var errNotImplemented = errors.New("YAML functions are not available (see https://pkg.go.dev/github.com/stretchr/testify/assert/yaml)") + +func Unmarshal([]byte, interface{}) error { + return errNotImplemented +} diff --git a/go-controller/vendor/github.com/stretchr/testify/mock/mock.go b/go-controller/vendor/github.com/stretchr/testify/mock/mock.go index 213bde2ea6..eb5682df97 100644 --- a/go-controller/vendor/github.com/stretchr/testify/mock/mock.go +++ b/go-controller/vendor/github.com/stretchr/testify/mock/mock.go @@ -80,12 +80,12 @@ type Call struct { requires []*Call } -func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call { +func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments Arguments, returnArguments Arguments) *Call { return &Call{ Parent: parent, Method: methodName, Arguments: methodArguments, - ReturnArguments: make([]interface{}, 0), + ReturnArguments: returnArguments, callerInfo: callerInfo, Repeatability: 0, WaitFor: nil, @@ -256,7 +256,7 @@ func (c *Call) Unset() *Call { // calls have been called as expected. The referenced calls may be from the // same mock instance and/or other mock instances. // -// Mock.On("Do").Return(nil).Notbefore( +// Mock.On("Do").Return(nil).NotBefore( // Mock.On("Init").Return(nil) // ) func (c *Call) NotBefore(calls ...*Call) *Call { @@ -273,6 +273,20 @@ func (c *Call) NotBefore(calls ...*Call) *Call { return c } +// InOrder defines the order in which the calls should be made +// +// For example: +// +// InOrder( +// Mock.On("init").Return(nil), +// Mock.On("Do").Return(nil), +// ) +func InOrder(calls ...*Call) { + for i := 1; i < len(calls); i++ { + calls[i].NotBefore(calls[i-1]) + } +} + // Mock is the workhorse used to track activity on another object. // For an example of its usage, refer to the "Example Usage" section at the top // of this document. @@ -351,7 +365,8 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { m.mutex.Lock() defer m.mutex.Unlock() - c := newCall(m, methodName, assert.CallerInfo(), arguments...) + + c := newCall(m, methodName, assert.CallerInfo(), arguments, make([]interface{}, 0)) m.ExpectedCalls = append(m.ExpectedCalls, c) return c } @@ -491,11 +506,12 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen m.mutex.Unlock() if closestCall != nil { - m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s", + m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s\nat: %s\n", callString(methodName, arguments, true), callString(methodName, closestCall.Arguments, true), diffArguments(closestCall.Arguments, arguments), strings.TrimSpace(mismatch), + assert.CallerInfo(), ) } else { m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) @@ -529,7 +545,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen call.totalCalls++ // add the call - m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...)) + m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments, call.ReturnArguments)) m.mutex.Unlock() // block if specified @@ -764,9 +780,17 @@ const ( ) // AnythingOfTypeArgument contains the type of an argument -// for use when type checking. Used in Diff and Assert. +// for use when type checking. Used in [Arguments.Diff] and [Arguments.Assert]. // -// Deprecated: this is an implementation detail that must not be used. Use [AnythingOfType] instead. +// Deprecated: this is an implementation detail that must not be used. Use the [AnythingOfType] constructor instead, example: +// +// m.On("Do", mock.AnythingOfType("string")) +// +// All explicit type declarations can be replaced with interface{} as is expected by [Mock.On], example: +// +// func anyString interface{} { +// return mock.AnythingOfType("string") +// } type AnythingOfTypeArgument = anythingOfTypeArgument // anythingOfTypeArgument is a string that contains the type of an argument @@ -780,53 +804,54 @@ type anythingOfTypeArgument string // // For example: // -// Assert(t, AnythingOfType("string"), AnythingOfType("int")) +// args.Assert(t, AnythingOfType("string"), AnythingOfType("int")) func AnythingOfType(t string) AnythingOfTypeArgument { return anythingOfTypeArgument(t) } // IsTypeArgument is a struct that contains the type of an argument -// for use when type checking. This is an alternative to AnythingOfType. -// Used in Diff and Assert. +// for use when type checking. This is an alternative to [AnythingOfType]. +// Used in [Arguments.Diff] and [Arguments.Assert]. type IsTypeArgument struct { t reflect.Type } // IsType returns an IsTypeArgument object containing the type to check for. // You can provide a zero-value of the type to check. This is an -// alternative to AnythingOfType. Used in Diff and Assert. +// alternative to [AnythingOfType]. Used in [Arguments.Diff] and [Arguments.Assert]. // // For example: -// Assert(t, IsType(""), IsType(0)) +// +// args.Assert(t, IsType(""), IsType(0)) func IsType(t interface{}) *IsTypeArgument { return &IsTypeArgument{t: reflect.TypeOf(t)} } -// FunctionalOptionsArgument is a struct that contains the type and value of an functional option argument -// for use when type checking. +// FunctionalOptionsArgument contains a list of functional options arguments +// expected for use when matching a list of arguments. type FunctionalOptionsArgument struct { - value interface{} + values []interface{} } // String returns the string representation of FunctionalOptionsArgument func (f *FunctionalOptionsArgument) String() string { var name string - tValue := reflect.ValueOf(f.value) - if tValue.Len() > 0 { - name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String() + if len(f.values) > 0 { + name = "[]" + reflect.TypeOf(f.values[0]).String() } - return strings.Replace(fmt.Sprintf("%#v", f.value), "[]interface {}", name, 1) + return strings.Replace(fmt.Sprintf("%#v", f.values), "[]interface {}", name, 1) } -// FunctionalOptions returns an FunctionalOptionsArgument object containing the functional option type -// and the values to check of +// FunctionalOptions returns an [FunctionalOptionsArgument] object containing +// the expected functional-options to check for. // // For example: -// Assert(t, FunctionalOptions("[]foo.FunctionalOption", foo.Opt1(), foo.Opt2())) -func FunctionalOptions(value ...interface{}) *FunctionalOptionsArgument { +// +// args.Assert(t, FunctionalOptions(foo.Opt1("strValue"), foo.Opt2(613))) +func FunctionalOptions(values ...interface{}) *FunctionalOptionsArgument { return &FunctionalOptionsArgument{ - value: value, + values: values, } } @@ -873,10 +898,11 @@ func (f argumentMatcher) String() string { // and false otherwise. // // Example: -// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) // -// |fn|, must be a function accepting a single argument (of the expected type) -// which returns a bool. If |fn| doesn't match the required signature, +// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) +// +// fn must be a function accepting a single argument (of the expected type) +// which returns a bool. If fn doesn't match the required signature, // MatchedBy() panics. func MatchedBy(fn interface{}) argumentMatcher { fnType := reflect.TypeOf(fn) @@ -979,20 +1005,17 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected.t.Name(), actualT.Name(), actualFmt) } case *FunctionalOptionsArgument: - t := expected.value - var name string - tValue := reflect.ValueOf(t) - if tValue.Len() > 0 { - name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String() + if len(expected.values) > 0 { + name = "[]" + reflect.TypeOf(expected.values[0]).String() } - tName := reflect.TypeOf(t).Name() - if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 { + const tName = "[]interface{}" + if name != reflect.TypeOf(actual).String() && len(expected.values) != 0 { differences++ output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt) } else { - if ef, af := assertOpts(t, actual); ef == "" && af == "" { + if ef, af := assertOpts(expected.values, actual); ef == "" && af == "" { // match output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName) } else { @@ -1092,7 +1115,7 @@ func (args Arguments) Error(index int) error { return nil } if s, ok = obj.(error); !ok { - panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, obj)) } return s } @@ -1181,32 +1204,38 @@ type tHelper interface { func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) { expectedOpts := reflect.ValueOf(expected) actualOpts := reflect.ValueOf(actual) + + var expectedFuncs []*runtime.Func var expectedNames []string for i := 0; i < expectedOpts.Len(); i++ { - expectedNames = append(expectedNames, funcName(expectedOpts.Index(i).Interface())) + f := runtimeFunc(expectedOpts.Index(i).Interface()) + expectedFuncs = append(expectedFuncs, f) + expectedNames = append(expectedNames, funcName(f)) } + var actualFuncs []*runtime.Func var actualNames []string for i := 0; i < actualOpts.Len(); i++ { - actualNames = append(actualNames, funcName(actualOpts.Index(i).Interface())) + f := runtimeFunc(actualOpts.Index(i).Interface()) + actualFuncs = append(actualFuncs, f) + actualNames = append(actualNames, funcName(f)) } - if !assert.ObjectsAreEqual(expectedNames, actualNames) { + + if expectedOpts.Len() != actualOpts.Len() { expectedFmt = fmt.Sprintf("%v", expectedNames) actualFmt = fmt.Sprintf("%v", actualNames) return } for i := 0; i < expectedOpts.Len(); i++ { - expectedOpt := expectedOpts.Index(i).Interface() - actualOpt := actualOpts.Index(i).Interface() - - expectedFunc := expectedNames[i] - actualFunc := actualNames[i] - if expectedFunc != actualFunc { - expectedFmt = expectedFunc - actualFmt = actualFunc + if !isFuncSame(expectedFuncs[i], actualFuncs[i]) { + expectedFmt = expectedNames[i] + actualFmt = actualNames[i] return } + expectedOpt := expectedOpts.Index(i).Interface() + actualOpt := actualOpts.Index(i).Interface() + ot := reflect.TypeOf(expectedOpt) var expectedValues []reflect.Value var actualValues []reflect.Value @@ -1224,9 +1253,9 @@ func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) { reflect.ValueOf(actualOpt).Call(actualValues) for i := 0; i < ot.NumIn(); i++ { - if !assert.ObjectsAreEqual(expectedValues[i].Interface(), actualValues[i].Interface()) { - expectedFmt = fmt.Sprintf("%s %+v", expectedNames[i], expectedValues[i].Interface()) - actualFmt = fmt.Sprintf("%s %+v", expectedNames[i], actualValues[i].Interface()) + if expectedArg, actualArg := expectedValues[i].Interface(), actualValues[i].Interface(); !assert.ObjectsAreEqual(expectedArg, actualArg) { + expectedFmt = fmt.Sprintf("%s(%T) -> %#v", expectedNames[i], expectedArg, expectedArg) + actualFmt = fmt.Sprintf("%s(%T) -> %#v", expectedNames[i], actualArg, actualArg) return } } @@ -1235,7 +1264,25 @@ func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) { return "", "" } -func funcName(opt interface{}) string { - n := runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name() - return strings.TrimSuffix(path.Base(n), path.Ext(n)) +func runtimeFunc(opt interface{}) *runtime.Func { + return runtime.FuncForPC(reflect.ValueOf(opt).Pointer()) +} + +func funcName(f *runtime.Func) string { + name := f.Name() + trimmed := strings.TrimSuffix(path.Base(name), path.Ext(name)) + splitted := strings.Split(trimmed, ".") + + if len(splitted) == 0 { + return trimmed + } + + return splitted[len(splitted)-1] +} + +func isFuncSame(f1, f2 *runtime.Func) bool { + f1File, f1Loc := f1.FileLine(f1.Entry()) + f2File, f2Loc := f2.FileLine(f2.Entry()) + + return f1File == f2File && f1Loc == f2Loc } diff --git a/go-controller/vendor/github.com/stretchr/testify/require/require.go b/go-controller/vendor/github.com/stretchr/testify/require/require.go index 506a82f807..d8921950d7 100644 --- a/go-controller/vendor/github.com/stretchr/testify/require/require.go +++ b/go-controller/vendor/github.com/stretchr/testify/require/require.go @@ -34,9 +34,9 @@ func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interfac // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World") -// assert.Contains(t, ["Hello", "World"], "World") -// assert.Contains(t, {"Hello": "World"}, "Hello") +// require.Contains(t, "Hello World", "World") +// require.Contains(t, ["Hello", "World"], "World") +// require.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -50,9 +50,9 @@ func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...int // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") -// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") -// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +// require.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// require.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// require.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -91,7 +91,7 @@ func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. // -// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +// require.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -106,7 +106,7 @@ func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. // -// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +// require.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -120,7 +120,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Empty(t, obj) +// require.Empty(t, obj) func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -134,7 +134,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Emptyf(t, obj, "error message %s", "formatted") +// require.Emptyf(t, obj, "error message %s", "formatted") func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -147,7 +147,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123) +// require.Equal(t, 123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -166,7 +166,7 @@ func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...i // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString) +// require.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -181,7 +181,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +// require.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -200,8 +200,8 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args // Exported int // notExported int // } -// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true -// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +// require.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// require.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -220,8 +220,8 @@ func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, m // Exported int // notExported int // } -// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true -// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +// require.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// require.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -232,10 +232,10 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, t.FailNow() } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // -// assert.EqualValues(t, uint32(123), int32(123)) +// require.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -246,10 +246,10 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg t.FailNow() } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // -// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +// require.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -262,7 +262,7 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // Equalf asserts that two objects are equal. // -// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// require.Equalf(t, 123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -280,8 +280,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // Error asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Error(t, err) { -// assert.Equal(t, expectedError, err) +// if require.Error(t, err) { +// require.Equal(t, expectedError, err) // } func Error(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -321,7 +321,7 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int // and that the error contains the specified substring. // // actualObj, err := SomeFunction() -// assert.ErrorContains(t, err, expectedErrorSubString) +// require.ErrorContains(t, err, expectedErrorSubString) func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -336,7 +336,7 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // and that the error contains the specified substring. // // actualObj, err := SomeFunction() -// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +// require.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -374,8 +374,8 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface // Errorf asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Errorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) +// if require.Errorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedErrorf, err) // } func Errorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -390,7 +390,7 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +// require.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -415,10 +415,10 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t // time.Sleep(8*time.Second) // externalValue = true // }() -// assert.EventuallyWithT(t, func(c *assert.CollectT) { +// require.EventuallyWithT(t, func(c *require.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick -// assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -443,10 +443,10 @@ func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitF // time.Sleep(8*time.Second) // externalValue = true // }() -// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// require.EventuallyWithTf(t, func(c *require.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick -// assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -460,7 +460,7 @@ func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), wait // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// require.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -473,7 +473,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // Exactly asserts that two objects are equal in value and type. // -// assert.Exactly(t, int32(123), int64(123)) +// require.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -486,7 +486,7 @@ func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // Exactlyf asserts that two objects are equal in value and type. // -// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +// require.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -543,7 +543,7 @@ func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { // False asserts that the specified value is false. // -// assert.False(t, myBool) +// require.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -556,7 +556,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) { // Falsef asserts that the specified value is false. // -// assert.Falsef(t, myBool, "error message %s", "formatted") +// require.Falsef(t, myBool, "error message %s", "formatted") func Falsef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -593,9 +593,9 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { // Greater asserts that the first element is greater than the second // -// assert.Greater(t, 2, 1) -// assert.Greater(t, float64(2), float64(1)) -// assert.Greater(t, "b", "a") +// require.Greater(t, 2, 1) +// require.Greater(t, float64(2), float64(1)) +// require.Greater(t, "b", "a") func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -608,10 +608,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqual(t, 2, 1) -// assert.GreaterOrEqual(t, 2, 2) -// assert.GreaterOrEqual(t, "b", "a") -// assert.GreaterOrEqual(t, "b", "b") +// require.GreaterOrEqual(t, 2, 1) +// require.GreaterOrEqual(t, 2, 2) +// require.GreaterOrEqual(t, "b", "a") +// require.GreaterOrEqual(t, "b", "b") func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -624,10 +624,10 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +// require.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// require.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -640,9 +640,9 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg // Greaterf asserts that the first element is greater than the second // -// assert.Greaterf(t, 2, 1, "error message %s", "formatted") -// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") -// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +// require.Greaterf(t, 2, 1, "error message %s", "formatted") +// require.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// require.Greaterf(t, "b", "a", "error message %s", "formatted") func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -656,7 +656,7 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// require.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -672,7 +672,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url s // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// require.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -688,7 +688,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// require.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -704,7 +704,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, ur // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// require.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -719,7 +719,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // HTTPError asserts that a specified handler returns an error status code. // -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -734,7 +734,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPErrorf asserts that a specified handler returns an error status code. // -// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -749,7 +749,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -764,7 +764,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url strin // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -779,7 +779,7 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// require.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { @@ -794,7 +794,7 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url str // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// require.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { @@ -809,7 +809,7 @@ func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url st // HTTPSuccess asserts that a specified handler returns a success status code. // -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// require.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -824,7 +824,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string // HTTPSuccessf asserts that a specified handler returns a success status code. // -// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// require.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -839,7 +839,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +// require.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -852,7 +852,7 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg // Implementsf asserts that an object is implemented by the specified interface. // -// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// require.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -865,7 +865,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms // InDelta asserts that the two numerals are within delta of each other. // -// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +// require.InDelta(t, math.Pi, 22/7.0, 0.01) func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -922,7 +922,7 @@ func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta f // InDeltaf asserts that the two numerals are within delta of each other. // -// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// require.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -979,9 +979,9 @@ func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon fl // IsDecreasing asserts that the collection is decreasing // -// assert.IsDecreasing(t, []int{2, 1, 0}) -// assert.IsDecreasing(t, []float{2, 1}) -// assert.IsDecreasing(t, []string{"b", "a"}) +// require.IsDecreasing(t, []int{2, 1, 0}) +// require.IsDecreasing(t, []float{2, 1}) +// require.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -994,9 +994,9 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsDecreasingf asserts that the collection is decreasing // -// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// require.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// require.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1009,9 +1009,9 @@ func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface // IsIncreasing asserts that the collection is increasing // -// assert.IsIncreasing(t, []int{1, 2, 3}) -// assert.IsIncreasing(t, []float{1, 2}) -// assert.IsIncreasing(t, []string{"a", "b"}) +// require.IsIncreasing(t, []int{1, 2, 3}) +// require.IsIncreasing(t, []float{1, 2}) +// require.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1024,9 +1024,9 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsIncreasingf asserts that the collection is increasing // -// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// require.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// require.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1039,9 +1039,9 @@ func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface // IsNonDecreasing asserts that the collection is not decreasing // -// assert.IsNonDecreasing(t, []int{1, 1, 2}) -// assert.IsNonDecreasing(t, []float{1, 2}) -// assert.IsNonDecreasing(t, []string{"a", "b"}) +// require.IsNonDecreasing(t, []int{1, 1, 2}) +// require.IsNonDecreasing(t, []float{1, 2}) +// require.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1054,9 +1054,9 @@ func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonDecreasingf asserts that the collection is not decreasing // -// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1069,9 +1069,9 @@ func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interf // IsNonIncreasing asserts that the collection is not increasing // -// assert.IsNonIncreasing(t, []int{2, 1, 1}) -// assert.IsNonIncreasing(t, []float{2, 1}) -// assert.IsNonIncreasing(t, []string{"b", "a"}) +// require.IsNonIncreasing(t, []int{2, 1, 1}) +// require.IsNonIncreasing(t, []float{2, 1}) +// require.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1084,9 +1084,9 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonIncreasingf asserts that the collection is not increasing // -// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1121,7 +1121,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin // JSONEq asserts that two JSON strings are equivalent. // -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// require.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1134,7 +1134,7 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ // JSONEqf asserts that two JSON strings are equivalent. // -// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// require.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1148,7 +1148,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3) +// require.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1162,7 +1162,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +// require.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1175,9 +1175,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf // Less asserts that the first element is less than the second // -// assert.Less(t, 1, 2) -// assert.Less(t, float64(1), float64(2)) -// assert.Less(t, "a", "b") +// require.Less(t, 1, 2) +// require.Less(t, float64(1), float64(2)) +// require.Less(t, "a", "b") func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1190,10 +1190,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) // LessOrEqual asserts that the first element is less than or equal to the second // -// assert.LessOrEqual(t, 1, 2) -// assert.LessOrEqual(t, 2, 2) -// assert.LessOrEqual(t, "a", "b") -// assert.LessOrEqual(t, "b", "b") +// require.LessOrEqual(t, 1, 2) +// require.LessOrEqual(t, 2, 2) +// require.LessOrEqual(t, "a", "b") +// require.LessOrEqual(t, "b", "b") func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1206,10 +1206,10 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter // LessOrEqualf asserts that the first element is less than or equal to the second // -// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") -// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +// require.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// require.LessOrEqualf(t, "b", "b", "error message %s", "formatted") func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1222,9 +1222,9 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . // Lessf asserts that the first element is less than the second // -// assert.Lessf(t, 1, 2, "error message %s", "formatted") -// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") -// assert.Lessf(t, "a", "b", "error message %s", "formatted") +// require.Lessf(t, 1, 2, "error message %s", "formatted") +// require.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// require.Lessf(t, "a", "b", "error message %s", "formatted") func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1237,8 +1237,8 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter // Negative asserts that the specified element is negative // -// assert.Negative(t, -1) -// assert.Negative(t, -1.23) +// require.Negative(t, -1) +// require.Negative(t, -1.23) func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1251,8 +1251,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Negativef asserts that the specified element is negative // -// assert.Negativef(t, -1, "error message %s", "formatted") -// assert.Negativef(t, -1.23, "error message %s", "formatted") +// require.Negativef(t, -1, "error message %s", "formatted") +// require.Negativef(t, -1.23, "error message %s", "formatted") func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1266,7 +1266,7 @@ func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +// require.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1280,7 +1280,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// require.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1293,7 +1293,7 @@ func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time. // Nil asserts that the specified object is nil. // -// assert.Nil(t, err) +// require.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1306,7 +1306,7 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Nilf asserts that the specified object is nil. // -// assert.Nilf(t, err, "error message %s", "formatted") +// require.Nilf(t, err, "error message %s", "formatted") func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1344,8 +1344,8 @@ func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { // NoError asserts that a function returned no error (i.e. `nil`). // // actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, expectedObj, actualObj) +// if require.NoError(t, err) { +// require.Equal(t, expectedObj, actualObj) // } func NoError(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1360,8 +1360,8 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) { // NoErrorf asserts that a function returned no error (i.e. `nil`). // // actualObj, err := SomeFunction() -// if assert.NoErrorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) +// if require.NoErrorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedObj, actualObj) // } func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1400,9 +1400,9 @@ func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth") -// assert.NotContains(t, ["Hello", "World"], "Earth") -// assert.NotContains(t, {"Hello": "World"}, "Earth") +// require.NotContains(t, "Hello World", "Earth") +// require.NotContains(t, ["Hello", "World"], "Earth") +// require.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1416,9 +1416,9 @@ func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ... // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +// require.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// require.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// require.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1429,11 +1429,51 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a t.FailNow() } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// require.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// require.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) +// if require.NotEmpty(t, obj) { +// require.Equal(t, "two", obj[1]) // } func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1448,8 +1488,8 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) +// if require.NotEmptyf(t, obj, "error message %s", "formatted") { +// require.Equal(t, "two", obj[1]) // } func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1463,7 +1503,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2) +// require.NotEqual(t, obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1479,7 +1519,7 @@ func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs . // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValues(t, obj1, obj2) +// require.NotEqualValues(t, obj1, obj2) func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1492,7 +1532,7 @@ func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAnd // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +// require.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1505,7 +1545,7 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s // NotEqualf asserts that the specified values are NOT equal. // -// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// require.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1519,7 +1559,31 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1531,7 +1595,7 @@ func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) t.FailNow() } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1545,7 +1609,7 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf // NotImplements asserts that an object does not implement the specified interface. // -// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject)) +// require.NotImplements(t, (*MyInterface)(nil), new(MyObject)) func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1558,7 +1622,7 @@ func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, // NotImplementsf asserts that an object does not implement the specified interface. // -// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// require.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1571,7 +1635,7 @@ func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err) +// require.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1584,7 +1648,7 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotNilf asserts that the specified object is not nil. // -// assert.NotNilf(t, err, "error message %s", "formatted") +// require.NotNilf(t, err, "error message %s", "formatted") func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1597,7 +1661,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanics(t, func(){ RemainCalm() }) +// require.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1610,7 +1674,7 @@ func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +// require.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1623,8 +1687,8 @@ func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interfac // NotRegexp asserts that a specified regexp does not match a string. // -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") +// require.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// require.NotRegexp(t, "^start", "it's not starting") func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1637,8 +1701,8 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf // NotRegexpf asserts that a specified regexp does not match a string. // -// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// require.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// require.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1651,7 +1715,7 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // NotSame asserts that two pointers do not reference the same object. // -// assert.NotSame(t, ptr1, ptr2) +// require.NotSame(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1667,7 +1731,7 @@ func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // NotSamef asserts that two pointers do not reference the same object. // -// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// require.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1685,8 +1749,8 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, // contain all elements given in the specified subset list(array, slice...) or // map. // -// assert.NotSubset(t, [1, 3, 4], [1, 2]) -// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +// require.NotSubset(t, [1, 3, 4], [1, 2]) +// require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1701,8 +1765,8 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i // contain all elements given in the specified subset list(array, slice...) or // map. // -// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") -// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") +// require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1737,7 +1801,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ GoCrazy() }) +// require.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1752,7 +1816,7 @@ func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +// require.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1767,7 +1831,7 @@ func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAn // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// require.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1781,7 +1845,7 @@ func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +// require.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1795,7 +1859,7 @@ func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, m // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// require.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1808,7 +1872,7 @@ func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +// require.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1821,8 +1885,8 @@ func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{} // Positive asserts that the specified element is positive // -// assert.Positive(t, 1) -// assert.Positive(t, 1.23) +// require.Positive(t, 1) +// require.Positive(t, 1.23) func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1835,8 +1899,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Positivef asserts that the specified element is positive // -// assert.Positivef(t, 1, "error message %s", "formatted") -// assert.Positivef(t, 1.23, "error message %s", "formatted") +// require.Positivef(t, 1, "error message %s", "formatted") +// require.Positivef(t, 1.23, "error message %s", "formatted") func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1849,8 +1913,8 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") +// require.Regexp(t, regexp.MustCompile("start"), "it's starting") +// require.Regexp(t, "start...$", "it's not starting") func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1863,8 +1927,8 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // Regexpf asserts that a specified regexp matches a string. // -// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// require.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// require.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1877,7 +1941,7 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // Same asserts that two pointers reference the same object. // -// assert.Same(t, ptr1, ptr2) +// require.Same(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1893,7 +1957,7 @@ func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...in // Samef asserts that two pointers reference the same object. // -// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// require.Samef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1910,8 +1974,8 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg // Subset asserts that the specified list(array, slice...) or map contains all // elements given in the specified subset list(array, slice...) or map. // -// assert.Subset(t, [1, 2, 3], [1, 2]) -// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +// require.Subset(t, [1, 2, 3], [1, 2]) +// require.Subset(t, {"x": 1, "y": 2}, {"x": 1}) func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1925,8 +1989,8 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte // Subsetf asserts that the specified list(array, slice...) or map contains all // elements given in the specified subset list(array, slice...) or map. // -// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") -// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") +// require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1939,7 +2003,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args // True asserts that the specified value is true. // -// assert.True(t, myBool) +// require.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1952,7 +2016,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) { // Truef asserts that the specified value is true. // -// assert.Truef(t, myBool, "error message %s", "formatted") +// require.Truef(t, myBool, "error message %s", "formatted") func Truef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1965,7 +2029,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +// require.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1978,7 +2042,7 @@ func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time // WithinDurationf asserts that the two times are within duration delta of each other. // -// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// require.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1991,7 +2055,7 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim // WithinRange asserts that a time is within a time range (inclusive). // -// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// require.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -2004,7 +2068,7 @@ func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, m // WithinRangef asserts that a time is within a time range (inclusive). // -// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// require.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/go-controller/vendor/github.com/stretchr/testify/require/require.go.tmpl b/go-controller/vendor/github.com/stretchr/testify/require/require.go.tmpl index 55e42ddebd..8b32836850 100644 --- a/go-controller/vendor/github.com/stretchr/testify/require/require.go.tmpl +++ b/go-controller/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -1,4 +1,4 @@ -{{.Comment}} +{{ replace .Comment "assert." "require."}} func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { if h, ok := t.(tHelper); ok { h.Helper() } if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } diff --git a/go-controller/vendor/github.com/stretchr/testify/require/require_forward.go b/go-controller/vendor/github.com/stretchr/testify/require/require_forward.go index eee8310a5f..1bd87304f4 100644 --- a/go-controller/vendor/github.com/stretchr/testify/require/require_forward.go +++ b/go-controller/vendor/github.com/stretchr/testify/require/require_forward.go @@ -187,8 +187,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { @@ -198,8 +198,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { @@ -337,7 +337,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -362,7 +362,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), w // a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1129,6 +1129,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -1201,7 +1235,25 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAsf(a.t, err, target, msg, args...) +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { @@ -1210,7 +1262,7 @@ func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface NotErrorIs(a.t, err, target, msgAndArgs...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { diff --git a/go-controller/vendor/github.com/stretchr/testify/require/requirements.go b/go-controller/vendor/github.com/stretchr/testify/require/requirements.go index 91772dfeb9..6b7ce929eb 100644 --- a/go-controller/vendor/github.com/stretchr/testify/require/requirements.go +++ b/go-controller/vendor/github.com/stretchr/testify/require/requirements.go @@ -6,7 +6,7 @@ type TestingT interface { FailNow() } -type tHelper interface { +type tHelper = interface { Helper() } diff --git a/go-controller/vendor/go.opentelemetry.io/otel/LICENSE b/go-controller/vendor/go.opentelemetry.io/otel/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/README.md b/go-controller/vendor/go.opentelemetry.io/otel/attribute/README.md new file mode 100644 index 0000000000..5b3da8f14c --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/README.md @@ -0,0 +1,3 @@ +# Attribute + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/attribute)](https://pkg.go.dev/go.opentelemetry.io/otel/attribute) diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/doc.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/doc.go new file mode 100644 index 0000000000..eef51ebc2a --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/doc.go @@ -0,0 +1,5 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package attribute provides key and value attributes. +package attribute // import "go.opentelemetry.io/otel/attribute" diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/encoder.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/encoder.go new file mode 100644 index 0000000000..318e42fcab --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/encoder.go @@ -0,0 +1,135 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +import ( + "bytes" + "sync" + "sync/atomic" +) + +type ( + // Encoder is a mechanism for serializing an attribute set into a specific + // string representation that supports caching, to avoid repeated + // serialization. An example could be an exporter encoding the attribute + // set into a wire representation. + Encoder interface { + // Encode returns the serialized encoding of the attribute set using + // its Iterator. This result may be cached by a attribute.Set. + Encode(iterator Iterator) string + + // ID returns a value that is unique for each class of attribute + // encoder. Attribute encoders allocate these using `NewEncoderID`. + ID() EncoderID + } + + // EncoderID is used to identify distinct Encoder + // implementations, for caching encoded results. + EncoderID struct { + value uint64 + } + + // defaultAttrEncoder uses a sync.Pool of buffers to reduce the number of + // allocations used in encoding attributes. This implementation encodes a + // comma-separated list of key=value, with '/'-escaping of '=', ',', and + // '\'. + defaultAttrEncoder struct { + // pool is a pool of attribute set builders. The buffers in this pool + // grow to a size that most attribute encodings will not allocate new + // memory. + pool sync.Pool // *bytes.Buffer + } +) + +// escapeChar is used to ensure uniqueness of the attribute encoding where +// keys or values contain either '=' or ','. Since there is no parser needed +// for this encoding and its only requirement is to be unique, this choice is +// arbitrary. Users will see these in some exporters (e.g., stdout), so the +// backslash ('\') is used as a conventional choice. +const escapeChar = '\\' + +var ( + _ Encoder = &defaultAttrEncoder{} + + // encoderIDCounter is for generating IDs for other attribute encoders. + encoderIDCounter uint64 + + defaultEncoderOnce sync.Once + defaultEncoderID = NewEncoderID() + defaultEncoderInstance *defaultAttrEncoder +) + +// NewEncoderID returns a unique attribute encoder ID. It should be called +// once per each type of attribute encoder. Preferably in init() or in var +// definition. +func NewEncoderID() EncoderID { + return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)} +} + +// DefaultEncoder returns an attribute encoder that encodes attributes in such +// a way that each escaped attribute's key is followed by an equal sign and +// then by an escaped attribute's value. All key-value pairs are separated by +// a comma. +// +// Escaping is done by prepending a backslash before either a backslash, equal +// sign or a comma. +func DefaultEncoder() Encoder { + defaultEncoderOnce.Do(func() { + defaultEncoderInstance = &defaultAttrEncoder{ + pool: sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, + }, + } + }) + return defaultEncoderInstance +} + +// Encode is a part of an implementation of the AttributeEncoder interface. +func (d *defaultAttrEncoder) Encode(iter Iterator) string { + buf := d.pool.Get().(*bytes.Buffer) + defer d.pool.Put(buf) + buf.Reset() + + for iter.Next() { + i, keyValue := iter.IndexedAttribute() + if i > 0 { + _, _ = buf.WriteRune(',') + } + copyAndEscape(buf, string(keyValue.Key)) + + _, _ = buf.WriteRune('=') + + if keyValue.Value.Type() == STRING { + copyAndEscape(buf, keyValue.Value.AsString()) + } else { + _, _ = buf.WriteString(keyValue.Value.Emit()) + } + } + return buf.String() +} + +// ID is a part of an implementation of the AttributeEncoder interface. +func (*defaultAttrEncoder) ID() EncoderID { + return defaultEncoderID +} + +// copyAndEscape escapes `=`, `,` and its own escape character (`\`), +// making the default encoding unique. +func copyAndEscape(buf *bytes.Buffer, val string) { + for _, ch := range val { + switch ch { + case '=', ',', escapeChar: + _, _ = buf.WriteRune(escapeChar) + } + _, _ = buf.WriteRune(ch) + } +} + +// Valid returns true if this encoder ID was allocated by +// `NewEncoderID`. Invalid encoder IDs will not be cached. +func (id EncoderID) Valid() bool { + return id.value != 0 +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/filter.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/filter.go new file mode 100644 index 0000000000..be9cd922d8 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/filter.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +// Filter supports removing certain attributes from attribute sets. When +// the filter returns true, the attribute will be kept in the filtered +// attribute set. When the filter returns false, the attribute is excluded +// from the filtered attribute set, and the attribute instead appears in +// the removed list of excluded attributes. +type Filter func(KeyValue) bool + +// NewAllowKeysFilter returns a Filter that only allows attributes with one of +// the provided keys. +// +// If keys is empty a deny-all filter is returned. +func NewAllowKeysFilter(keys ...Key) Filter { + if len(keys) <= 0 { + return func(kv KeyValue) bool { return false } + } + + allowed := make(map[Key]struct{}) + for _, k := range keys { + allowed[k] = struct{}{} + } + return func(kv KeyValue) bool { + _, ok := allowed[kv.Key] + return ok + } +} + +// NewDenyKeysFilter returns a Filter that only allows attributes +// that do not have one of the provided keys. +// +// If keys is empty an allow-all filter is returned. +func NewDenyKeysFilter(keys ...Key) Filter { + if len(keys) <= 0 { + return func(kv KeyValue) bool { return true } + } + + forbid := make(map[Key]struct{}) + for _, k := range keys { + forbid[k] = struct{}{} + } + return func(kv KeyValue) bool { + _, ok := forbid[kv.Key] + return !ok + } +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/iterator.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/iterator.go new file mode 100644 index 0000000000..f2ba89ce4b --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/iterator.go @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +// Iterator allows iterating over the set of attributes in order, sorted by +// key. +type Iterator struct { + storage *Set + idx int +} + +// MergeIterator supports iterating over two sets of attributes while +// eliminating duplicate values from the combined set. The first iterator +// value takes precedence. +type MergeIterator struct { + one oneIterator + two oneIterator + current KeyValue +} + +type oneIterator struct { + iter Iterator + done bool + attr KeyValue +} + +// Next moves the iterator to the next position. Returns false if there are no +// more attributes. +func (i *Iterator) Next() bool { + i.idx++ + return i.idx < i.Len() +} + +// Label returns current KeyValue. Must be called only after Next returns +// true. +// +// Deprecated: Use Attribute instead. +func (i *Iterator) Label() KeyValue { + return i.Attribute() +} + +// Attribute returns the current KeyValue of the Iterator. It must be called +// only after Next returns true. +func (i *Iterator) Attribute() KeyValue { + kv, _ := i.storage.Get(i.idx) + return kv +} + +// IndexedLabel returns current index and attribute. Must be called only +// after Next returns true. +// +// Deprecated: Use IndexedAttribute instead. +func (i *Iterator) IndexedLabel() (int, KeyValue) { + return i.idx, i.Attribute() +} + +// IndexedAttribute returns current index and attribute. Must be called only +// after Next returns true. +func (i *Iterator) IndexedAttribute() (int, KeyValue) { + return i.idx, i.Attribute() +} + +// Len returns a number of attributes in the iterated set. +func (i *Iterator) Len() int { + return i.storage.Len() +} + +// ToSlice is a convenience function that creates a slice of attributes from +// the passed iterator. The iterator is set up to start from the beginning +// before creating the slice. +func (i *Iterator) ToSlice() []KeyValue { + l := i.Len() + if l == 0 { + return nil + } + i.idx = -1 + slice := make([]KeyValue, 0, l) + for i.Next() { + slice = append(slice, i.Attribute()) + } + return slice +} + +// NewMergeIterator returns a MergeIterator for merging two attribute sets. +// Duplicates are resolved by taking the value from the first set. +func NewMergeIterator(s1, s2 *Set) MergeIterator { + mi := MergeIterator{ + one: makeOne(s1.Iter()), + two: makeOne(s2.Iter()), + } + return mi +} + +func makeOne(iter Iterator) oneIterator { + oi := oneIterator{ + iter: iter, + } + oi.advance() + return oi +} + +func (oi *oneIterator) advance() { + if oi.done = !oi.iter.Next(); !oi.done { + oi.attr = oi.iter.Attribute() + } +} + +// Next returns true if there is another attribute available. +func (m *MergeIterator) Next() bool { + if m.one.done && m.two.done { + return false + } + if m.one.done { + m.current = m.two.attr + m.two.advance() + return true + } + if m.two.done { + m.current = m.one.attr + m.one.advance() + return true + } + if m.one.attr.Key == m.two.attr.Key { + m.current = m.one.attr // first iterator attribute value wins + m.one.advance() + m.two.advance() + return true + } + if m.one.attr.Key < m.two.attr.Key { + m.current = m.one.attr + m.one.advance() + return true + } + m.current = m.two.attr + m.two.advance() + return true +} + +// Label returns the current value after Next() returns true. +// +// Deprecated: Use Attribute instead. +func (m *MergeIterator) Label() KeyValue { + return m.current +} + +// Attribute returns the current value after Next() returns true. +func (m *MergeIterator) Attribute() KeyValue { + return m.current +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/key.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/key.go new file mode 100644 index 0000000000..d9a22c6502 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/key.go @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +// Key represents the key part in key-value pairs. It's a string. The +// allowed character set in the key depends on the use of the key. +type Key string + +// Bool creates a KeyValue instance with a BOOL Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Bool(name, value). +func (k Key) Bool(v bool) KeyValue { + return KeyValue{ + Key: k, + Value: BoolValue(v), + } +} + +// BoolSlice creates a KeyValue instance with a BOOLSLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- BoolSlice(name, value). +func (k Key) BoolSlice(v []bool) KeyValue { + return KeyValue{ + Key: k, + Value: BoolSliceValue(v), + } +} + +// Int creates a KeyValue instance with an INT64 Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Int(name, value). +func (k Key) Int(v int) KeyValue { + return KeyValue{ + Key: k, + Value: IntValue(v), + } +} + +// IntSlice creates a KeyValue instance with an INT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- IntSlice(name, value). +func (k Key) IntSlice(v []int) KeyValue { + return KeyValue{ + Key: k, + Value: IntSliceValue(v), + } +} + +// Int64 creates a KeyValue instance with an INT64 Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Int64(name, value). +func (k Key) Int64(v int64) KeyValue { + return KeyValue{ + Key: k, + Value: Int64Value(v), + } +} + +// Int64Slice creates a KeyValue instance with an INT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Int64Slice(name, value). +func (k Key) Int64Slice(v []int64) KeyValue { + return KeyValue{ + Key: k, + Value: Int64SliceValue(v), + } +} + +// Float64 creates a KeyValue instance with a FLOAT64 Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Float64(name, value). +func (k Key) Float64(v float64) KeyValue { + return KeyValue{ + Key: k, + Value: Float64Value(v), + } +} + +// Float64Slice creates a KeyValue instance with a FLOAT64SLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- Float64(name, value). +func (k Key) Float64Slice(v []float64) KeyValue { + return KeyValue{ + Key: k, + Value: Float64SliceValue(v), + } +} + +// String creates a KeyValue instance with a STRING Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- String(name, value). +func (k Key) String(v string) KeyValue { + return KeyValue{ + Key: k, + Value: StringValue(v), + } +} + +// StringSlice creates a KeyValue instance with a STRINGSLICE Value. +// +// If creating both a key and value at the same time, use the provided +// convenience function instead -- StringSlice(name, value). +func (k Key) StringSlice(v []string) KeyValue { + return KeyValue{ + Key: k, + Value: StringSliceValue(v), + } +} + +// Defined returns true for non-empty keys. +func (k Key) Defined() bool { + return len(k) != 0 +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/kv.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/kv.go new file mode 100644 index 0000000000..3028f9a40f --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/kv.go @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +import ( + "fmt" +) + +// KeyValue holds a key and value pair. +type KeyValue struct { + Key Key + Value Value +} + +// Valid returns if kv is a valid OpenTelemetry attribute. +func (kv KeyValue) Valid() bool { + return kv.Key.Defined() && kv.Value.Type() != INVALID +} + +// Bool creates a KeyValue with a BOOL Value type. +func Bool(k string, v bool) KeyValue { + return Key(k).Bool(v) +} + +// BoolSlice creates a KeyValue with a BOOLSLICE Value type. +func BoolSlice(k string, v []bool) KeyValue { + return Key(k).BoolSlice(v) +} + +// Int creates a KeyValue with an INT64 Value type. +func Int(k string, v int) KeyValue { + return Key(k).Int(v) +} + +// IntSlice creates a KeyValue with an INT64SLICE Value type. +func IntSlice(k string, v []int) KeyValue { + return Key(k).IntSlice(v) +} + +// Int64 creates a KeyValue with an INT64 Value type. +func Int64(k string, v int64) KeyValue { + return Key(k).Int64(v) +} + +// Int64Slice creates a KeyValue with an INT64SLICE Value type. +func Int64Slice(k string, v []int64) KeyValue { + return Key(k).Int64Slice(v) +} + +// Float64 creates a KeyValue with a FLOAT64 Value type. +func Float64(k string, v float64) KeyValue { + return Key(k).Float64(v) +} + +// Float64Slice creates a KeyValue with a FLOAT64SLICE Value type. +func Float64Slice(k string, v []float64) KeyValue { + return Key(k).Float64Slice(v) +} + +// String creates a KeyValue with a STRING Value type. +func String(k, v string) KeyValue { + return Key(k).String(v) +} + +// StringSlice creates a KeyValue with a STRINGSLICE Value type. +func StringSlice(k string, v []string) KeyValue { + return Key(k).StringSlice(v) +} + +// Stringer creates a new key-value pair with a passed name and a string +// value generated by the passed Stringer interface. +func Stringer(k string, v fmt.Stringer) KeyValue { + return Key(k).String(v.String()) +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/set.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/set.go new file mode 100644 index 0000000000..6cbefceadf --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/set.go @@ -0,0 +1,411 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +import ( + "cmp" + "encoding/json" + "reflect" + "slices" + "sort" +) + +type ( + // Set is the representation for a distinct attribute set. It manages an + // immutable set of attributes, with an internal cache for storing + // attribute encodings. + // + // This type will remain comparable for backwards compatibility. The + // equivalence of Sets across versions is not guaranteed to be stable. + // Prior versions may find two Sets to be equal or not when compared + // directly (i.e. ==), but subsequent versions may not. Users should use + // the Equals method to ensure stable equivalence checking. + // + // Users should also use the Distinct returned from Equivalent as a map key + // instead of a Set directly. In addition to that type providing guarantees + // on stable equivalence, it may also provide performance improvements. + Set struct { + equivalent Distinct + } + + // Distinct is a unique identifier of a Set. + // + // Distinct is designed to be ensures equivalence stability: comparisons + // will return the save value across versions. For this reason, Distinct + // should always be used as a map key instead of a Set. + Distinct struct { + iface interface{} + } + + // Sortable implements sort.Interface, used for sorting KeyValue. + // + // Deprecated: This type is no longer used. It was added as a performance + // optimization for Go < 1.21 that is no longer needed (Go < 1.21 is no + // longer supported by the module). + Sortable []KeyValue +) + +var ( + // keyValueType is used in computeDistinctReflect. + keyValueType = reflect.TypeOf(KeyValue{}) + + // emptySet is returned for empty attribute sets. + emptySet = &Set{ + equivalent: Distinct{ + iface: [0]KeyValue{}, + }, + } +) + +// EmptySet returns a reference to a Set with no elements. +// +// This is a convenience provided for optimized calling utility. +func EmptySet() *Set { + return emptySet +} + +// reflectValue abbreviates reflect.ValueOf(d). +func (d Distinct) reflectValue() reflect.Value { + return reflect.ValueOf(d.iface) +} + +// Valid returns true if this value refers to a valid Set. +func (d Distinct) Valid() bool { + return d.iface != nil +} + +// Len returns the number of attributes in this set. +func (l *Set) Len() int { + if l == nil || !l.equivalent.Valid() { + return 0 + } + return l.equivalent.reflectValue().Len() +} + +// Get returns the KeyValue at ordered position idx in this set. +func (l *Set) Get(idx int) (KeyValue, bool) { + if l == nil || !l.equivalent.Valid() { + return KeyValue{}, false + } + value := l.equivalent.reflectValue() + + if idx >= 0 && idx < value.Len() { + // Note: The Go compiler successfully avoids an allocation for + // the interface{} conversion here: + return value.Index(idx).Interface().(KeyValue), true + } + + return KeyValue{}, false +} + +// Value returns the value of a specified key in this set. +func (l *Set) Value(k Key) (Value, bool) { + if l == nil || !l.equivalent.Valid() { + return Value{}, false + } + rValue := l.equivalent.reflectValue() + vlen := rValue.Len() + + idx := sort.Search(vlen, func(idx int) bool { + return rValue.Index(idx).Interface().(KeyValue).Key >= k + }) + if idx >= vlen { + return Value{}, false + } + keyValue := rValue.Index(idx).Interface().(KeyValue) + if k == keyValue.Key { + return keyValue.Value, true + } + return Value{}, false +} + +// HasValue tests whether a key is defined in this set. +func (l *Set) HasValue(k Key) bool { + if l == nil { + return false + } + _, ok := l.Value(k) + return ok +} + +// Iter returns an iterator for visiting the attributes in this set. +func (l *Set) Iter() Iterator { + return Iterator{ + storage: l, + idx: -1, + } +} + +// ToSlice returns the set of attributes belonging to this set, sorted, where +// keys appear no more than once. +func (l *Set) ToSlice() []KeyValue { + iter := l.Iter() + return iter.ToSlice() +} + +// Equivalent returns a value that may be used as a map key. The Distinct type +// guarantees that the result will equal the equivalent. Distinct value of any +// attribute set with the same elements as this, where sets are made unique by +// choosing the last value in the input for any given key. +func (l *Set) Equivalent() Distinct { + if l == nil || !l.equivalent.Valid() { + return emptySet.equivalent + } + return l.equivalent +} + +// Equals returns true if the argument set is equivalent to this set. +func (l *Set) Equals(o *Set) bool { + return l.Equivalent() == o.Equivalent() +} + +// Encoded returns the encoded form of this set, according to encoder. +func (l *Set) Encoded(encoder Encoder) string { + if l == nil || encoder == nil { + return "" + } + + return encoder.Encode(l.Iter()) +} + +func empty() Set { + return Set{ + equivalent: emptySet.equivalent, + } +} + +// NewSet returns a new Set. See the documentation for +// NewSetWithSortableFiltered for more details. +// +// Except for empty sets, this method adds an additional allocation compared +// with calls that include a Sortable. +func NewSet(kvs ...KeyValue) Set { + s, _ := NewSetWithFiltered(kvs, nil) + return s +} + +// NewSetWithSortable returns a new Set. See the documentation for +// NewSetWithSortableFiltered for more details. +// +// This call includes a Sortable option as a memory optimization. +// +// Deprecated: Use [NewSet] instead. +func NewSetWithSortable(kvs []KeyValue, _ *Sortable) Set { + s, _ := NewSetWithFiltered(kvs, nil) + return s +} + +// NewSetWithFiltered returns a new Set. See the documentation for +// NewSetWithSortableFiltered for more details. +// +// This call includes a Filter to include/exclude attribute keys from the +// return value. Excluded keys are returned as a slice of attribute values. +func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) { + // Check for empty set. + if len(kvs) == 0 { + return empty(), nil + } + + // Stable sort so the following de-duplication can implement + // last-value-wins semantics. + slices.SortStableFunc(kvs, func(a, b KeyValue) int { + return cmp.Compare(a.Key, b.Key) + }) + + position := len(kvs) - 1 + offset := position - 1 + + // The requirements stated above require that the stable + // result be placed in the end of the input slice, while + // overwritten values are swapped to the beginning. + // + // De-duplicate with last-value-wins semantics. Preserve + // duplicate values at the beginning of the input slice. + for ; offset >= 0; offset-- { + if kvs[offset].Key == kvs[position].Key { + continue + } + position-- + kvs[offset], kvs[position] = kvs[position], kvs[offset] + } + kvs = kvs[position:] + + if filter != nil { + if div := filteredToFront(kvs, filter); div != 0 { + return Set{equivalent: computeDistinct(kvs[div:])}, kvs[:div] + } + } + return Set{equivalent: computeDistinct(kvs)}, nil +} + +// NewSetWithSortableFiltered returns a new Set. +// +// Duplicate keys are eliminated by taking the last value. This +// re-orders the input slice so that unique last-values are contiguous +// at the end of the slice. +// +// This ensures the following: +// +// - Last-value-wins semantics +// - Caller sees the reordering, but doesn't lose values +// - Repeated call preserve last-value wins. +// +// Note that methods are defined on Set, although this returns Set. Callers +// can avoid memory allocations by: +// +// - allocating a Sortable for use as a temporary in this method +// - allocating a Set for storing the return value of this constructor. +// +// The result maintains a cache of encoded attributes, by attribute.EncoderID. +// This value should not be copied after its first use. +// +// The second []KeyValue return value is a list of attributes that were +// excluded by the Filter (if non-nil). +// +// Deprecated: Use [NewSetWithFiltered] instead. +func NewSetWithSortableFiltered(kvs []KeyValue, _ *Sortable, filter Filter) (Set, []KeyValue) { + return NewSetWithFiltered(kvs, filter) +} + +// filteredToFront filters slice in-place using keep function. All KeyValues that need to +// be removed are moved to the front. All KeyValues that need to be kept are +// moved (in-order) to the back. The index for the first KeyValue to be kept is +// returned. +func filteredToFront(slice []KeyValue, keep Filter) int { + n := len(slice) + j := n + for i := n - 1; i >= 0; i-- { + if keep(slice[i]) { + j-- + slice[i], slice[j] = slice[j], slice[i] + } + } + return j +} + +// Filter returns a filtered copy of this Set. See the documentation for +// NewSetWithSortableFiltered for more details. +func (l *Set) Filter(re Filter) (Set, []KeyValue) { + if re == nil { + return *l, nil + } + + // Iterate in reverse to the first attribute that will be filtered out. + n := l.Len() + first := n - 1 + for ; first >= 0; first-- { + kv, _ := l.Get(first) + if !re(kv) { + break + } + } + + // No attributes will be dropped, return the immutable Set l and nil. + if first < 0 { + return *l, nil + } + + // Copy now that we know we need to return a modified set. + // + // Do not do this in-place on the underlying storage of *Set l. Sets are + // immutable and filtering should not change this. + slice := l.ToSlice() + + // Don't re-iterate the slice if only slice[0] is filtered. + if first == 0 { + // It is safe to assume len(slice) >= 1 given we found at least one + // attribute above that needs to be filtered out. + return Set{equivalent: computeDistinct(slice[1:])}, slice[:1] + } + + // Move the filtered slice[first] to the front (preserving order). + kv := slice[first] + copy(slice[1:first+1], slice[:first]) + slice[0] = kv + + // Do not re-evaluate re(slice[first+1:]). + div := filteredToFront(slice[1:first+1], re) + 1 + return Set{equivalent: computeDistinct(slice[div:])}, slice[:div] +} + +// computeDistinct returns a Distinct using either the fixed- or +// reflect-oriented code path, depending on the size of the input. The input +// slice is assumed to already be sorted and de-duplicated. +func computeDistinct(kvs []KeyValue) Distinct { + iface := computeDistinctFixed(kvs) + if iface == nil { + iface = computeDistinctReflect(kvs) + } + return Distinct{ + iface: iface, + } +} + +// computeDistinctFixed computes a Distinct for small slices. It returns nil +// if the input is too large for this code path. +func computeDistinctFixed(kvs []KeyValue) interface{} { + switch len(kvs) { + case 1: + return [1]KeyValue(kvs) + case 2: + return [2]KeyValue(kvs) + case 3: + return [3]KeyValue(kvs) + case 4: + return [4]KeyValue(kvs) + case 5: + return [5]KeyValue(kvs) + case 6: + return [6]KeyValue(kvs) + case 7: + return [7]KeyValue(kvs) + case 8: + return [8]KeyValue(kvs) + case 9: + return [9]KeyValue(kvs) + case 10: + return [10]KeyValue(kvs) + default: + return nil + } +} + +// computeDistinctReflect computes a Distinct using reflection, works for any +// size input. +func computeDistinctReflect(kvs []KeyValue) interface{} { + at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem() + for i, keyValue := range kvs { + *(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue + } + return at.Interface() +} + +// MarshalJSON returns the JSON encoding of the Set. +func (l *Set) MarshalJSON() ([]byte, error) { + return json.Marshal(l.equivalent.iface) +} + +// MarshalLog is the marshaling function used by the logging system to represent this Set. +func (l Set) MarshalLog() interface{} { + kvs := make(map[string]string) + for _, kv := range l.ToSlice() { + kvs[string(kv.Key)] = kv.Value.Emit() + } + return kvs +} + +// Len implements sort.Interface. +func (l *Sortable) Len() int { + return len(*l) +} + +// Swap implements sort.Interface. +func (l *Sortable) Swap(i, j int) { + (*l)[i], (*l)[j] = (*l)[j], (*l)[i] +} + +// Less implements sort.Interface. +func (l *Sortable) Less(i, j int) bool { + return (*l)[i].Key < (*l)[j].Key +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/type_string.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/type_string.go new file mode 100644 index 0000000000..e584b24776 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/type_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type=Type"; DO NOT EDIT. + +package attribute + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[INVALID-0] + _ = x[BOOL-1] + _ = x[INT64-2] + _ = x[FLOAT64-3] + _ = x[STRING-4] + _ = x[BOOLSLICE-5] + _ = x[INT64SLICE-6] + _ = x[FLOAT64SLICE-7] + _ = x[STRINGSLICE-8] +} + +const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE" + +var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 38, 48, 60, 71} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/attribute/value.go b/go-controller/vendor/go.opentelemetry.io/otel/attribute/value.go new file mode 100644 index 0000000000..9ea0ecbbd2 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/attribute/value.go @@ -0,0 +1,271 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package attribute // import "go.opentelemetry.io/otel/attribute" + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + + "go.opentelemetry.io/otel/internal" + "go.opentelemetry.io/otel/internal/attribute" +) + +//go:generate stringer -type=Type + +// Type describes the type of the data Value holds. +type Type int // nolint: revive // redefines builtin Type. + +// Value represents the value part in key-value pairs. +type Value struct { + vtype Type + numeric uint64 + stringly string + slice interface{} +} + +const ( + // INVALID is used for a Value with no value set. + INVALID Type = iota + // BOOL is a boolean Type Value. + BOOL + // INT64 is a 64-bit signed integral Type Value. + INT64 + // FLOAT64 is a 64-bit floating point Type Value. + FLOAT64 + // STRING is a string Type Value. + STRING + // BOOLSLICE is a slice of booleans Type Value. + BOOLSLICE + // INT64SLICE is a slice of 64-bit signed integral numbers Type Value. + INT64SLICE + // FLOAT64SLICE is a slice of 64-bit floating point numbers Type Value. + FLOAT64SLICE + // STRINGSLICE is a slice of strings Type Value. + STRINGSLICE +) + +// BoolValue creates a BOOL Value. +func BoolValue(v bool) Value { + return Value{ + vtype: BOOL, + numeric: internal.BoolToRaw(v), + } +} + +// BoolSliceValue creates a BOOLSLICE Value. +func BoolSliceValue(v []bool) Value { + return Value{vtype: BOOLSLICE, slice: attribute.BoolSliceValue(v)} +} + +// IntValue creates an INT64 Value. +func IntValue(v int) Value { + return Int64Value(int64(v)) +} + +// IntSliceValue creates an INTSLICE Value. +func IntSliceValue(v []int) Value { + var int64Val int64 + cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(int64Val))) + for i, val := range v { + cp.Elem().Index(i).SetInt(int64(val)) + } + return Value{ + vtype: INT64SLICE, + slice: cp.Elem().Interface(), + } +} + +// Int64Value creates an INT64 Value. +func Int64Value(v int64) Value { + return Value{ + vtype: INT64, + numeric: internal.Int64ToRaw(v), + } +} + +// Int64SliceValue creates an INT64SLICE Value. +func Int64SliceValue(v []int64) Value { + return Value{vtype: INT64SLICE, slice: attribute.Int64SliceValue(v)} +} + +// Float64Value creates a FLOAT64 Value. +func Float64Value(v float64) Value { + return Value{ + vtype: FLOAT64, + numeric: internal.Float64ToRaw(v), + } +} + +// Float64SliceValue creates a FLOAT64SLICE Value. +func Float64SliceValue(v []float64) Value { + return Value{vtype: FLOAT64SLICE, slice: attribute.Float64SliceValue(v)} +} + +// StringValue creates a STRING Value. +func StringValue(v string) Value { + return Value{ + vtype: STRING, + stringly: v, + } +} + +// StringSliceValue creates a STRINGSLICE Value. +func StringSliceValue(v []string) Value { + return Value{vtype: STRINGSLICE, slice: attribute.StringSliceValue(v)} +} + +// Type returns a type of the Value. +func (v Value) Type() Type { + return v.vtype +} + +// AsBool returns the bool value. Make sure that the Value's type is +// BOOL. +func (v Value) AsBool() bool { + return internal.RawToBool(v.numeric) +} + +// AsBoolSlice returns the []bool value. Make sure that the Value's type is +// BOOLSLICE. +func (v Value) AsBoolSlice() []bool { + if v.vtype != BOOLSLICE { + return nil + } + return v.asBoolSlice() +} + +func (v Value) asBoolSlice() []bool { + return attribute.AsBoolSlice(v.slice) +} + +// AsInt64 returns the int64 value. Make sure that the Value's type is +// INT64. +func (v Value) AsInt64() int64 { + return internal.RawToInt64(v.numeric) +} + +// AsInt64Slice returns the []int64 value. Make sure that the Value's type is +// INT64SLICE. +func (v Value) AsInt64Slice() []int64 { + if v.vtype != INT64SLICE { + return nil + } + return v.asInt64Slice() +} + +func (v Value) asInt64Slice() []int64 { + return attribute.AsInt64Slice(v.slice) +} + +// AsFloat64 returns the float64 value. Make sure that the Value's +// type is FLOAT64. +func (v Value) AsFloat64() float64 { + return internal.RawToFloat64(v.numeric) +} + +// AsFloat64Slice returns the []float64 value. Make sure that the Value's type is +// FLOAT64SLICE. +func (v Value) AsFloat64Slice() []float64 { + if v.vtype != FLOAT64SLICE { + return nil + } + return v.asFloat64Slice() +} + +func (v Value) asFloat64Slice() []float64 { + return attribute.AsFloat64Slice(v.slice) +} + +// AsString returns the string value. Make sure that the Value's type +// is STRING. +func (v Value) AsString() string { + return v.stringly +} + +// AsStringSlice returns the []string value. Make sure that the Value's type is +// STRINGSLICE. +func (v Value) AsStringSlice() []string { + if v.vtype != STRINGSLICE { + return nil + } + return v.asStringSlice() +} + +func (v Value) asStringSlice() []string { + return attribute.AsStringSlice(v.slice) +} + +type unknownValueType struct{} + +// AsInterface returns Value's data as interface{}. +func (v Value) AsInterface() interface{} { + switch v.Type() { + case BOOL: + return v.AsBool() + case BOOLSLICE: + return v.asBoolSlice() + case INT64: + return v.AsInt64() + case INT64SLICE: + return v.asInt64Slice() + case FLOAT64: + return v.AsFloat64() + case FLOAT64SLICE: + return v.asFloat64Slice() + case STRING: + return v.stringly + case STRINGSLICE: + return v.asStringSlice() + } + return unknownValueType{} +} + +// Emit returns a string representation of Value's data. +func (v Value) Emit() string { + switch v.Type() { + case BOOLSLICE: + return fmt.Sprint(v.asBoolSlice()) + case BOOL: + return strconv.FormatBool(v.AsBool()) + case INT64SLICE: + j, err := json.Marshal(v.asInt64Slice()) + if err != nil { + return fmt.Sprintf("invalid: %v", v.asInt64Slice()) + } + return string(j) + case INT64: + return strconv.FormatInt(v.AsInt64(), 10) + case FLOAT64SLICE: + j, err := json.Marshal(v.asFloat64Slice()) + if err != nil { + return fmt.Sprintf("invalid: %v", v.asFloat64Slice()) + } + return string(j) + case FLOAT64: + return fmt.Sprint(v.AsFloat64()) + case STRINGSLICE: + j, err := json.Marshal(v.asStringSlice()) + if err != nil { + return fmt.Sprintf("invalid: %v", v.asStringSlice()) + } + return string(j) + case STRING: + return v.stringly + default: + return "unknown" + } +} + +// MarshalJSON returns the JSON encoding of the Value. +func (v Value) MarshalJSON() ([]byte, error) { + var jsonVal struct { + Type string + Value interface{} + } + jsonVal.Type = v.Type().String() + jsonVal.Value = v.AsInterface() + return json.Marshal(jsonVal) +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/codes/README.md b/go-controller/vendor/go.opentelemetry.io/otel/codes/README.md new file mode 100644 index 0000000000..24c52b387d --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/codes/README.md @@ -0,0 +1,3 @@ +# Codes + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/codes)](https://pkg.go.dev/go.opentelemetry.io/otel/codes) diff --git a/go-controller/vendor/go.opentelemetry.io/otel/codes/codes.go b/go-controller/vendor/go.opentelemetry.io/otel/codes/codes.go new file mode 100644 index 0000000000..49a35b1225 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/codes/codes.go @@ -0,0 +1,106 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package codes // import "go.opentelemetry.io/otel/codes" + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" +) + +const ( + // Unset is the default status code. + Unset Code = 0 + + // Error indicates the operation contains an error. + // + // NOTE: The error code in OTLP is 2. + // The value of this enum is only relevant to the internals + // of the Go SDK. + Error Code = 1 + + // Ok indicates operation has been validated by an Application developers + // or Operator to have completed successfully, or contain no error. + // + // NOTE: The Ok code in OTLP is 1. + // The value of this enum is only relevant to the internals + // of the Go SDK. + Ok Code = 2 + + maxCode = 3 +) + +// Code is an 32-bit representation of a status state. +type Code uint32 + +var codeToStr = map[Code]string{ + Unset: "Unset", + Error: "Error", + Ok: "Ok", +} + +var strToCode = map[string]Code{ + `"Unset"`: Unset, + `"Error"`: Error, + `"Ok"`: Ok, +} + +// String returns the Code as a string. +func (c Code) String() string { + return codeToStr[c] +} + +// UnmarshalJSON unmarshals b into the Code. +// +// This is based on the functionality in the gRPC codes package: +// https://github.com/grpc/grpc-go/blob/bb64fee312b46ebee26be43364a7a966033521b1/codes/codes.go#L218-L244 +func (c *Code) UnmarshalJSON(b []byte) error { + // From json.Unmarshaler: By convention, to approximate the behavior of + // Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as + // a no-op. + if string(b) == "null" { + return nil + } + if c == nil { + return errors.New("nil receiver passed to UnmarshalJSON") + } + + var x interface{} + if err := json.Unmarshal(b, &x); err != nil { + return err + } + switch x.(type) { + case string: + if jc, ok := strToCode[string(b)]; ok { + *c = jc + return nil + } + return fmt.Errorf("invalid code: %q", string(b)) + case float64: + if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil { + if ci >= maxCode { + return fmt.Errorf("invalid code: %q", ci) + } + + *c = Code(ci) // nolint: gosec // Bit size of 32 check above. + return nil + } + return fmt.Errorf("invalid code: %q", string(b)) + default: + return fmt.Errorf("invalid code: %q", string(b)) + } +} + +// MarshalJSON returns c as the JSON encoding of c. +func (c *Code) MarshalJSON() ([]byte, error) { + if c == nil { + return []byte("null"), nil + } + str, ok := codeToStr[*c] + if !ok { + return nil, fmt.Errorf("invalid code: %d", *c) + } + return []byte(fmt.Sprintf("%q", str)), nil +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/codes/doc.go b/go-controller/vendor/go.opentelemetry.io/otel/codes/doc.go new file mode 100644 index 0000000000..ee8db448b8 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/codes/doc.go @@ -0,0 +1,10 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package codes defines the canonical error codes used by OpenTelemetry. + +It conforms to [the OpenTelemetry +specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/api.md#set-status). +*/ +package codes // import "go.opentelemetry.io/otel/codes" diff --git a/go-controller/vendor/go.opentelemetry.io/otel/internal/attribute/attribute.go b/go-controller/vendor/go.opentelemetry.io/otel/internal/attribute/attribute.go new file mode 100644 index 0000000000..691d96c755 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/internal/attribute/attribute.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package attribute provide several helper functions for some commonly used +logic of processing attributes. +*/ +package attribute // import "go.opentelemetry.io/otel/internal/attribute" + +import ( + "reflect" +) + +// BoolSliceValue converts a bool slice into an array with same elements as slice. +func BoolSliceValue(v []bool) interface{} { + var zero bool + cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem() + reflect.Copy(cp, reflect.ValueOf(v)) + return cp.Interface() +} + +// Int64SliceValue converts an int64 slice into an array with same elements as slice. +func Int64SliceValue(v []int64) interface{} { + var zero int64 + cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem() + reflect.Copy(cp, reflect.ValueOf(v)) + return cp.Interface() +} + +// Float64SliceValue converts a float64 slice into an array with same elements as slice. +func Float64SliceValue(v []float64) interface{} { + var zero float64 + cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem() + reflect.Copy(cp, reflect.ValueOf(v)) + return cp.Interface() +} + +// StringSliceValue converts a string slice into an array with same elements as slice. +func StringSliceValue(v []string) interface{} { + var zero string + cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem() + reflect.Copy(cp, reflect.ValueOf(v)) + return cp.Interface() +} + +// AsBoolSlice converts a bool array into a slice into with same elements as array. +func AsBoolSlice(v interface{}) []bool { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Array { + return nil + } + cpy := make([]bool, rv.Len()) + if len(cpy) > 0 { + _ = reflect.Copy(reflect.ValueOf(cpy), rv) + } + return cpy +} + +// AsInt64Slice converts an int64 array into a slice into with same elements as array. +func AsInt64Slice(v interface{}) []int64 { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Array { + return nil + } + cpy := make([]int64, rv.Len()) + if len(cpy) > 0 { + _ = reflect.Copy(reflect.ValueOf(cpy), rv) + } + return cpy +} + +// AsFloat64Slice converts a float64 array into a slice into with same elements as array. +func AsFloat64Slice(v interface{}) []float64 { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Array { + return nil + } + cpy := make([]float64, rv.Len()) + if len(cpy) > 0 { + _ = reflect.Copy(reflect.ValueOf(cpy), rv) + } + return cpy +} + +// AsStringSlice converts a string array into a slice into with same elements as array. +func AsStringSlice(v interface{}) []string { + rv := reflect.ValueOf(v) + if rv.Type().Kind() != reflect.Array { + return nil + } + cpy := make([]string, rv.Len()) + if len(cpy) > 0 { + _ = reflect.Copy(reflect.ValueOf(cpy), rv) + } + return cpy +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/internal/gen.go b/go-controller/vendor/go.opentelemetry.io/otel/internal/gen.go new file mode 100644 index 0000000000..4259f0320d --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/internal/gen.go @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "go.opentelemetry.io/otel/internal" + +//go:generate gotmpl --body=./shared/matchers/expectation.go.tmpl "--data={}" --out=matchers/expectation.go +//go:generate gotmpl --body=./shared/matchers/expecter.go.tmpl "--data={}" --out=matchers/expecter.go +//go:generate gotmpl --body=./shared/matchers/temporal_matcher.go.tmpl "--data={}" --out=matchers/temporal_matcher.go + +//go:generate gotmpl --body=./shared/internaltest/alignment.go.tmpl "--data={}" --out=internaltest/alignment.go +//go:generate gotmpl --body=./shared/internaltest/env.go.tmpl "--data={}" --out=internaltest/env.go +//go:generate gotmpl --body=./shared/internaltest/env_test.go.tmpl "--data={}" --out=internaltest/env_test.go +//go:generate gotmpl --body=./shared/internaltest/errors.go.tmpl "--data={}" --out=internaltest/errors.go +//go:generate gotmpl --body=./shared/internaltest/harness.go.tmpl "--data={\"matchersImportPath\": \"go.opentelemetry.io/otel/internal/matchers\"}" --out=internaltest/harness.go +//go:generate gotmpl --body=./shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=internaltest/text_map_carrier.go +//go:generate gotmpl --body=./shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=internaltest/text_map_carrier_test.go +//go:generate gotmpl --body=./shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=internaltest/text_map_propagator.go +//go:generate gotmpl --body=./shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=internaltest/text_map_propagator_test.go diff --git a/go-controller/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go b/go-controller/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go new file mode 100644 index 0000000000..b2fe3e41d3 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "go.opentelemetry.io/otel/internal" + +import ( + "math" + "unsafe" +) + +func BoolToRaw(b bool) uint64 { // nolint:revive // b is not a control flag. + if b { + return 1 + } + return 0 +} + +func RawToBool(r uint64) bool { + return r != 0 +} + +func Int64ToRaw(i int64) uint64 { + // Assumes original was a valid int64 (overflow not checked). + return uint64(i) // nolint: gosec +} + +func RawToInt64(r uint64) int64 { + // Assumes original was a valid int64 (overflow not checked). + return int64(r) // nolint: gosec +} + +func Float64ToRaw(f float64) uint64 { + return math.Float64bits(f) +} + +func RawToFloat64(r uint64) float64 { + return math.Float64frombits(r) +} + +func RawPtrToFloat64Ptr(r *uint64) *float64 { + // Assumes original was a valid *float64 (overflow not checked). + return (*float64)(unsafe.Pointer(r)) // nolint: gosec +} + +func RawPtrToInt64Ptr(r *uint64) *int64 { + // Assumes original was a valid *int64 (overflow not checked). + return (*int64)(unsafe.Pointer(r)) // nolint: gosec +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/LICENSE b/go-controller/vendor/go.opentelemetry.io/otel/trace/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/README.md b/go-controller/vendor/go.opentelemetry.io/otel/trace/README.md new file mode 100644 index 0000000000..58ccaba69b --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/README.md @@ -0,0 +1,3 @@ +# Trace API + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/trace)](https://pkg.go.dev/go.opentelemetry.io/otel/trace) diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/config.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/config.go new file mode 100644 index 0000000000..9c0b720a4d --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/config.go @@ -0,0 +1,323 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "time" + + "go.opentelemetry.io/otel/attribute" +) + +// TracerConfig is a group of options for a Tracer. +type TracerConfig struct { + instrumentationVersion string + // Schema URL of the telemetry emitted by the Tracer. + schemaURL string + attrs attribute.Set +} + +// InstrumentationVersion returns the version of the library providing instrumentation. +func (t *TracerConfig) InstrumentationVersion() string { + return t.instrumentationVersion +} + +// InstrumentationAttributes returns the attributes associated with the library +// providing instrumentation. +func (t *TracerConfig) InstrumentationAttributes() attribute.Set { + return t.attrs +} + +// SchemaURL returns the Schema URL of the telemetry emitted by the Tracer. +func (t *TracerConfig) SchemaURL() string { + return t.schemaURL +} + +// NewTracerConfig applies all the options to a returned TracerConfig. +func NewTracerConfig(options ...TracerOption) TracerConfig { + var config TracerConfig + for _, option := range options { + config = option.apply(config) + } + return config +} + +// TracerOption applies an option to a TracerConfig. +type TracerOption interface { + apply(TracerConfig) TracerConfig +} + +type tracerOptionFunc func(TracerConfig) TracerConfig + +func (fn tracerOptionFunc) apply(cfg TracerConfig) TracerConfig { + return fn(cfg) +} + +// SpanConfig is a group of options for a Span. +type SpanConfig struct { + attributes []attribute.KeyValue + timestamp time.Time + links []Link + newRoot bool + spanKind SpanKind + stackTrace bool +} + +// Attributes describe the associated qualities of a Span. +func (cfg *SpanConfig) Attributes() []attribute.KeyValue { + return cfg.attributes +} + +// Timestamp is a time in a Span life-cycle. +func (cfg *SpanConfig) Timestamp() time.Time { + return cfg.timestamp +} + +// StackTrace checks whether stack trace capturing is enabled. +func (cfg *SpanConfig) StackTrace() bool { + return cfg.stackTrace +} + +// Links are the associations a Span has with other Spans. +func (cfg *SpanConfig) Links() []Link { + return cfg.links +} + +// NewRoot identifies a Span as the root Span for a new trace. This is +// commonly used when an existing trace crosses trust boundaries and the +// remote parent span context should be ignored for security. +func (cfg *SpanConfig) NewRoot() bool { + return cfg.newRoot +} + +// SpanKind is the role a Span has in a trace. +func (cfg *SpanConfig) SpanKind() SpanKind { + return cfg.spanKind +} + +// NewSpanStartConfig applies all the options to a returned SpanConfig. +// No validation is performed on the returned SpanConfig (e.g. no uniqueness +// checking or bounding of data), it is left to the SDK to perform this +// action. +func NewSpanStartConfig(options ...SpanStartOption) SpanConfig { + var c SpanConfig + for _, option := range options { + c = option.applySpanStart(c) + } + return c +} + +// NewSpanEndConfig applies all the options to a returned SpanConfig. +// No validation is performed on the returned SpanConfig (e.g. no uniqueness +// checking or bounding of data), it is left to the SDK to perform this +// action. +func NewSpanEndConfig(options ...SpanEndOption) SpanConfig { + var c SpanConfig + for _, option := range options { + c = option.applySpanEnd(c) + } + return c +} + +// SpanStartOption applies an option to a SpanConfig. These options are applicable +// only when the span is created. +type SpanStartOption interface { + applySpanStart(SpanConfig) SpanConfig +} + +type spanOptionFunc func(SpanConfig) SpanConfig + +func (fn spanOptionFunc) applySpanStart(cfg SpanConfig) SpanConfig { + return fn(cfg) +} + +// SpanEndOption applies an option to a SpanConfig. These options are +// applicable only when the span is ended. +type SpanEndOption interface { + applySpanEnd(SpanConfig) SpanConfig +} + +// EventConfig is a group of options for an Event. +type EventConfig struct { + attributes []attribute.KeyValue + timestamp time.Time + stackTrace bool +} + +// Attributes describe the associated qualities of an Event. +func (cfg *EventConfig) Attributes() []attribute.KeyValue { + return cfg.attributes +} + +// Timestamp is a time in an Event life-cycle. +func (cfg *EventConfig) Timestamp() time.Time { + return cfg.timestamp +} + +// StackTrace checks whether stack trace capturing is enabled. +func (cfg *EventConfig) StackTrace() bool { + return cfg.stackTrace +} + +// NewEventConfig applies all the EventOptions to a returned EventConfig. If no +// timestamp option is passed, the returned EventConfig will have a Timestamp +// set to the call time, otherwise no validation is performed on the returned +// EventConfig. +func NewEventConfig(options ...EventOption) EventConfig { + var c EventConfig + for _, option := range options { + c = option.applyEvent(c) + } + if c.timestamp.IsZero() { + c.timestamp = time.Now() + } + return c +} + +// EventOption applies span event options to an EventConfig. +type EventOption interface { + applyEvent(EventConfig) EventConfig +} + +// SpanOption are options that can be used at both the beginning and end of a span. +type SpanOption interface { + SpanStartOption + SpanEndOption +} + +// SpanStartEventOption are options that can be used at the start of a span, or with an event. +type SpanStartEventOption interface { + SpanStartOption + EventOption +} + +// SpanEndEventOption are options that can be used at the end of a span, or with an event. +type SpanEndEventOption interface { + SpanEndOption + EventOption +} + +type attributeOption []attribute.KeyValue + +func (o attributeOption) applySpan(c SpanConfig) SpanConfig { + c.attributes = append(c.attributes, []attribute.KeyValue(o)...) + return c +} +func (o attributeOption) applySpanStart(c SpanConfig) SpanConfig { return o.applySpan(c) } +func (o attributeOption) applyEvent(c EventConfig) EventConfig { + c.attributes = append(c.attributes, []attribute.KeyValue(o)...) + return c +} + +var _ SpanStartEventOption = attributeOption{} + +// WithAttributes adds the attributes related to a span life-cycle event. +// These attributes are used to describe the work a Span represents when this +// option is provided to a Span's start event. Otherwise, these +// attributes provide additional information about the event being recorded +// (e.g. error, state change, processing progress, system event). +// +// If multiple of these options are passed the attributes of each successive +// option will extend the attributes instead of overwriting. There is no +// guarantee of uniqueness in the resulting attributes. +func WithAttributes(attributes ...attribute.KeyValue) SpanStartEventOption { + return attributeOption(attributes) +} + +// SpanEventOption are options that can be used with an event or a span. +type SpanEventOption interface { + SpanOption + EventOption +} + +type timestampOption time.Time + +func (o timestampOption) applySpan(c SpanConfig) SpanConfig { + c.timestamp = time.Time(o) + return c +} +func (o timestampOption) applySpanStart(c SpanConfig) SpanConfig { return o.applySpan(c) } +func (o timestampOption) applySpanEnd(c SpanConfig) SpanConfig { return o.applySpan(c) } +func (o timestampOption) applyEvent(c EventConfig) EventConfig { + c.timestamp = time.Time(o) + return c +} + +var _ SpanEventOption = timestampOption{} + +// WithTimestamp sets the time of a Span or Event life-cycle moment (e.g. +// started, stopped, errored). +func WithTimestamp(t time.Time) SpanEventOption { + return timestampOption(t) +} + +type stackTraceOption bool + +func (o stackTraceOption) applyEvent(c EventConfig) EventConfig { + c.stackTrace = bool(o) + return c +} + +func (o stackTraceOption) applySpan(c SpanConfig) SpanConfig { + c.stackTrace = bool(o) + return c +} +func (o stackTraceOption) applySpanEnd(c SpanConfig) SpanConfig { return o.applySpan(c) } + +// WithStackTrace sets the flag to capture the error with stack trace (e.g. true, false). +func WithStackTrace(b bool) SpanEndEventOption { + return stackTraceOption(b) +} + +// WithLinks adds links to a Span. The links are added to the existing Span +// links, i.e. this does not overwrite. Links with invalid span context are ignored. +func WithLinks(links ...Link) SpanStartOption { + return spanOptionFunc(func(cfg SpanConfig) SpanConfig { + cfg.links = append(cfg.links, links...) + return cfg + }) +} + +// WithNewRoot specifies that the Span should be treated as a root Span. Any +// existing parent span context will be ignored when defining the Span's trace +// identifiers. +func WithNewRoot() SpanStartOption { + return spanOptionFunc(func(cfg SpanConfig) SpanConfig { + cfg.newRoot = true + return cfg + }) +} + +// WithSpanKind sets the SpanKind of a Span. +func WithSpanKind(kind SpanKind) SpanStartOption { + return spanOptionFunc(func(cfg SpanConfig) SpanConfig { + cfg.spanKind = kind + return cfg + }) +} + +// WithInstrumentationVersion sets the instrumentation version. +func WithInstrumentationVersion(version string) TracerOption { + return tracerOptionFunc(func(cfg TracerConfig) TracerConfig { + cfg.instrumentationVersion = version + return cfg + }) +} + +// WithInstrumentationAttributes sets the instrumentation attributes. +// +// The passed attributes will be de-duplicated. +func WithInstrumentationAttributes(attr ...attribute.KeyValue) TracerOption { + return tracerOptionFunc(func(config TracerConfig) TracerConfig { + config.attrs = attribute.NewSet(attr...) + return config + }) +} + +// WithSchemaURL sets the schema URL for the Tracer. +func WithSchemaURL(schemaURL string) TracerOption { + return tracerOptionFunc(func(cfg TracerConfig) TracerConfig { + cfg.schemaURL = schemaURL + return cfg + }) +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/context.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/context.go new file mode 100644 index 0000000000..8c45a7107f --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/context.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import "context" + +type traceContextKeyType int + +const currentSpanKey traceContextKeyType = iota + +// ContextWithSpan returns a copy of parent with span set as the current Span. +func ContextWithSpan(parent context.Context, span Span) context.Context { + return context.WithValue(parent, currentSpanKey, span) +} + +// ContextWithSpanContext returns a copy of parent with sc as the current +// Span. The Span implementation that wraps sc is non-recording and performs +// no operations other than to return sc as the SpanContext from the +// SpanContext method. +func ContextWithSpanContext(parent context.Context, sc SpanContext) context.Context { + return ContextWithSpan(parent, nonRecordingSpan{sc: sc}) +} + +// ContextWithRemoteSpanContext returns a copy of parent with rsc set explicitly +// as a remote SpanContext and as the current Span. The Span implementation +// that wraps rsc is non-recording and performs no operations other than to +// return rsc as the SpanContext from the SpanContext method. +func ContextWithRemoteSpanContext(parent context.Context, rsc SpanContext) context.Context { + return ContextWithSpanContext(parent, rsc.WithRemote(true)) +} + +// SpanFromContext returns the current Span from ctx. +// +// If no Span is currently set in ctx an implementation of a Span that +// performs no operations is returned. +func SpanFromContext(ctx context.Context) Span { + if ctx == nil { + return noopSpanInstance + } + if span, ok := ctx.Value(currentSpanKey).(Span); ok { + return span + } + return noopSpanInstance +} + +// SpanContextFromContext returns the current Span's SpanContext. +func SpanContextFromContext(ctx context.Context) SpanContext { + return SpanFromContext(ctx).SpanContext() +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/doc.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/doc.go new file mode 100644 index 0000000000..cdbf41d6d7 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/doc.go @@ -0,0 +1,119 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package trace provides an implementation of the tracing part of the +OpenTelemetry API. + +To participate in distributed traces a Span needs to be created for the +operation being performed as part of a traced workflow. In its simplest form: + + var tracer trace.Tracer + + func init() { + tracer = otel.Tracer("instrumentation/package/name") + } + + func operation(ctx context.Context) { + var span trace.Span + ctx, span = tracer.Start(ctx, "operation") + defer span.End() + // ... + } + +A Tracer is unique to the instrumentation and is used to create Spans. +Instrumentation should be designed to accept a TracerProvider from which it +can create its own unique Tracer. Alternatively, the registered global +TracerProvider from the go.opentelemetry.io/otel package can be used as +a default. + + const ( + name = "instrumentation/package/name" + version = "0.1.0" + ) + + type Instrumentation struct { + tracer trace.Tracer + } + + func NewInstrumentation(tp trace.TracerProvider) *Instrumentation { + if tp == nil { + tp = otel.TracerProvider() + } + return &Instrumentation{ + tracer: tp.Tracer(name, trace.WithInstrumentationVersion(version)), + } + } + + func operation(ctx context.Context, inst *Instrumentation) { + var span trace.Span + ctx, span = inst.tracer.Start(ctx, "operation") + defer span.End() + // ... + } + +# API Implementations + +This package does not conform to the standard Go versioning policy; all of its +interfaces may have methods added to them without a package major version bump. +This non-standard API evolution could surprise an uninformed implementation +author. They could unknowingly build their implementation in a way that would +result in a runtime panic for their users that update to the new API. + +The API is designed to help inform an instrumentation author about this +non-standard API evolution. It requires them to choose a default behavior for +unimplemented interface methods. There are three behavior choices they can +make: + + - Compilation failure + - Panic + - Default to another implementation + +All interfaces in this API embed a corresponding interface from +[go.opentelemetry.io/otel/trace/embedded]. If an author wants the default +behavior of their implementations to be a compilation failure, signaling to +their users they need to update to the latest version of that implementation, +they need to embed the corresponding interface from +[go.opentelemetry.io/otel/trace/embedded] in their implementation. For +example, + + import "go.opentelemetry.io/otel/trace/embedded" + + type TracerProvider struct { + embedded.TracerProvider + // ... + } + +If an author wants the default behavior of their implementations to panic, they +can embed the API interface directly. + + import "go.opentelemetry.io/otel/trace" + + type TracerProvider struct { + trace.TracerProvider + // ... + } + +This option is not recommended. It will lead to publishing packages that +contain runtime panics when users update to newer versions of +[go.opentelemetry.io/otel/trace], which may be done with a transitive +dependency. + +Finally, an author can embed another implementation in theirs. The embedded +implementation will be used for methods not defined by the author. For example, +an author who wants to default to silently dropping the call can use +[go.opentelemetry.io/otel/trace/noop]: + + import "go.opentelemetry.io/otel/trace/noop" + + type TracerProvider struct { + noop.TracerProvider + // ... + } + +It is strongly recommended that authors only embed +[go.opentelemetry.io/otel/trace/noop] if they choose this default behavior. +That implementation is the only one OpenTelemetry authors can guarantee will +fully implement all the API interfaces when a user updates their API. +*/ +package trace // import "go.opentelemetry.io/otel/trace" diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/README.md b/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/README.md new file mode 100644 index 0000000000..7754a239ee --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/README.md @@ -0,0 +1,3 @@ +# Trace Embedded + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/trace/embedded)](https://pkg.go.dev/go.opentelemetry.io/otel/trace/embedded) diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/embedded.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/embedded.go new file mode 100644 index 0000000000..3e359a00bf --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/embedded/embedded.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package embedded provides interfaces embedded within the [OpenTelemetry +// trace API]. +// +// Implementers of the [OpenTelemetry trace API] can embed the relevant type +// from this package into their implementation directly. Doing so will result +// in a compilation error for users when the [OpenTelemetry trace API] is +// extended (which is something that can happen without a major version bump of +// the API package). +// +// [OpenTelemetry trace API]: https://pkg.go.dev/go.opentelemetry.io/otel/trace +package embedded // import "go.opentelemetry.io/otel/trace/embedded" + +// TracerProvider is embedded in +// [go.opentelemetry.io/otel/trace.TracerProvider]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/trace.TracerProvider] if you want users to +// experience a compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/trace.TracerProvider] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +type TracerProvider interface{ tracerProvider() } + +// Tracer is embedded in [go.opentelemetry.io/otel/trace.Tracer]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/trace.Tracer] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/trace.Tracer] interface +// is extended (which is something that can happen without a major version bump +// of the API package). +type Tracer interface{ tracer() } + +// Span is embedded in [go.opentelemetry.io/otel/trace.Span]. +// +// Embed this interface in your implementation of the +// [go.opentelemetry.io/otel/trace.Span] if you want users to experience a +// compilation error, signaling they need to update to your latest +// implementation, when the [go.opentelemetry.io/otel/trace.Span] interface is +// extended (which is something that can happen without a major version bump of +// the API package). +type Span interface{ span() } diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/nonrecording.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/nonrecording.go new file mode 100644 index 0000000000..c00221e7be --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/nonrecording.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +// nonRecordingSpan is a minimal implementation of a Span that wraps a +// SpanContext. It performs no operations other than to return the wrapped +// SpanContext. +type nonRecordingSpan struct { + noopSpan + + sc SpanContext +} + +// SpanContext returns the wrapped SpanContext. +func (s nonRecordingSpan) SpanContext() SpanContext { return s.sc } diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/noop.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/noop.go new file mode 100644 index 0000000000..ca20e9997a --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/noop.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace/embedded" +) + +// NewNoopTracerProvider returns an implementation of TracerProvider that +// performs no operations. The Tracer and Spans created from the returned +// TracerProvider also perform no operations. +// +// Deprecated: Use [go.opentelemetry.io/otel/trace/noop.NewTracerProvider] +// instead. +func NewNoopTracerProvider() TracerProvider { + return noopTracerProvider{} +} + +type noopTracerProvider struct{ embedded.TracerProvider } + +var _ TracerProvider = noopTracerProvider{} + +// Tracer returns noop implementation of Tracer. +func (p noopTracerProvider) Tracer(string, ...TracerOption) Tracer { + return noopTracer{} +} + +// noopTracer is an implementation of Tracer that performs no operations. +type noopTracer struct{ embedded.Tracer } + +var _ Tracer = noopTracer{} + +// Start carries forward a non-recording Span, if one is present in the context, otherwise it +// creates a no-op Span. +func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption) (context.Context, Span) { + span := SpanFromContext(ctx) + if _, ok := span.(nonRecordingSpan); !ok { + // span is likely already a noopSpan, but let's be sure + span = noopSpanInstance + } + return ContextWithSpan(ctx, span), span +} + +// noopSpan is an implementation of Span that performs no operations. +type noopSpan struct{ embedded.Span } + +var noopSpanInstance Span = noopSpan{} + +// SpanContext returns an empty span context. +func (noopSpan) SpanContext() SpanContext { return SpanContext{} } + +// IsRecording always returns false. +func (noopSpan) IsRecording() bool { return false } + +// SetStatus does nothing. +func (noopSpan) SetStatus(codes.Code, string) {} + +// SetError does nothing. +func (noopSpan) SetError(bool) {} + +// SetAttributes does nothing. +func (noopSpan) SetAttributes(...attribute.KeyValue) {} + +// End does nothing. +func (noopSpan) End(...SpanEndOption) {} + +// RecordError does nothing. +func (noopSpan) RecordError(error, ...EventOption) {} + +// AddEvent does nothing. +func (noopSpan) AddEvent(string, ...EventOption) {} + +// AddLink does nothing. +func (noopSpan) AddLink(Link) {} + +// SetName does nothing. +func (noopSpan) SetName(string) {} + +// TracerProvider returns a no-op TracerProvider. +func (noopSpan) TracerProvider() TracerProvider { return noopTracerProvider{} } diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/provider.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/provider.go new file mode 100644 index 0000000000..ef85cb70c6 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/provider.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import "go.opentelemetry.io/otel/trace/embedded" + +// TracerProvider provides Tracers that are used by instrumentation code to +// trace computational workflows. +// +// A TracerProvider is the collection destination of all Spans from Tracers it +// provides, it represents a unique telemetry collection pipeline. How that +// pipeline is defined, meaning how those Spans are collected, processed, and +// where they are exported, depends on its implementation. Instrumentation +// authors do not need to define this implementation, rather just use the +// provided Tracers to instrument code. +// +// Commonly, instrumentation code will accept a TracerProvider implementation +// at runtime from its users or it can simply use the globally registered one +// (see https://pkg.go.dev/go.opentelemetry.io/otel#GetTracerProvider). +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type TracerProvider interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.TracerProvider + + // Tracer returns a unique Tracer scoped to be used by instrumentation code + // to trace computational workflows. The scope and identity of that + // instrumentation code is uniquely defined by the name and options passed. + // + // The passed name needs to uniquely identify instrumentation code. + // Therefore, it is recommended that name is the Go package name of the + // library providing instrumentation (note: not the code being + // instrumented). Instrumentation libraries can have multiple versions, + // therefore, the WithInstrumentationVersion option should be used to + // distinguish these different codebases. Additionally, instrumentation + // libraries may sometimes use traces to communicate different domains of + // workflow data (i.e. using spans to communicate workflow events only). If + // this is the case, the WithScopeAttributes option should be used to + // uniquely identify Tracers that handle the different domains of workflow + // data. + // + // If the same name and options are passed multiple times, the same Tracer + // will be returned (it is up to the implementation if this will be the + // same underlying instance of that Tracer or not). It is not necessary to + // call this multiple times with the same name and options to get an + // up-to-date Tracer. All implementations will ensure any TracerProvider + // configuration changes are propagated to all provided Tracers. + // + // If name is empty, then an implementation defined default name will be + // used instead. + // + // This method is safe to call concurrently. + Tracer(name string, options ...TracerOption) Tracer +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/span.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/span.go new file mode 100644 index 0000000000..d3aa476ee1 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/span.go @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace/embedded" +) + +// Span is the individual component of a trace. It represents a single named +// and timed operation of a workflow that is traced. A Tracer is used to +// create a Span and it is then up to the operation the Span represents to +// properly end the Span when the operation itself ends. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Span interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Span + + // End completes the Span. The Span is considered complete and ready to be + // delivered through the rest of the telemetry pipeline after this method + // is called. Therefore, updates to the Span are not allowed after this + // method has been called. + End(options ...SpanEndOption) + + // AddEvent adds an event with the provided name and options. + AddEvent(name string, options ...EventOption) + + // AddLink adds a link. + // Adding links at span creation using WithLinks is preferred to calling AddLink + // later, for contexts that are available during span creation, because head + // sampling decisions can only consider information present during span creation. + AddLink(link Link) + + // IsRecording returns the recording state of the Span. It will return + // true if the Span is active and events can be recorded. + IsRecording() bool + + // RecordError will record err as an exception span event for this span. An + // additional call to SetStatus is required if the Status of the Span should + // be set to Error, as this method does not change the Span status. If this + // span is not being recorded or err is nil then this method does nothing. + RecordError(err error, options ...EventOption) + + // SpanContext returns the SpanContext of the Span. The returned SpanContext + // is usable even after the End method has been called for the Span. + SpanContext() SpanContext + + // SetStatus sets the status of the Span in the form of a code and a + // description, provided the status hasn't already been set to a higher + // value before (OK > Error > Unset). The description is only included in a + // status when the code is for an error. + SetStatus(code codes.Code, description string) + + // SetName sets the Span name. + SetName(name string) + + // SetAttributes sets kv as attributes of the Span. If a key from kv + // already exists for an attribute of the Span it will be overwritten with + // the value contained in kv. + SetAttributes(kv ...attribute.KeyValue) + + // TracerProvider returns a TracerProvider that can be used to generate + // additional Spans on the same telemetry pipeline as the current Span. + TracerProvider() TracerProvider +} + +// Link is the relationship between two Spans. The relationship can be within +// the same Trace or across different Traces. +// +// For example, a Link is used in the following situations: +// +// 1. Batch Processing: A batch of operations may contain operations +// associated with one or more traces/spans. Since there can only be one +// parent SpanContext, a Link is used to keep reference to the +// SpanContext of all operations in the batch. +// 2. Public Endpoint: A SpanContext for an in incoming client request on a +// public endpoint should be considered untrusted. In such a case, a new +// trace with its own identity and sampling decision needs to be created, +// but this new trace needs to be related to the original trace in some +// form. A Link is used to keep reference to the original SpanContext and +// track the relationship. +type Link struct { + // SpanContext of the linked Span. + SpanContext SpanContext + + // Attributes describe the aspects of the link. + Attributes []attribute.KeyValue +} + +// LinkFromContext returns a link encapsulating the SpanContext in the provided +// ctx. +func LinkFromContext(ctx context.Context, attrs ...attribute.KeyValue) Link { + return Link{ + SpanContext: SpanContextFromContext(ctx), + Attributes: attrs, + } +} + +// SpanKind is the role a Span plays in a Trace. +type SpanKind int + +// As a convenience, these match the proto definition, see +// https://github.com/open-telemetry/opentelemetry-proto/blob/30d237e1ff3ab7aa50e0922b5bebdd93505090af/opentelemetry/proto/trace/v1/trace.proto#L101-L129 +// +// The unspecified value is not a valid `SpanKind`. Use `ValidateSpanKind()` +// to coerce a span kind to a valid value. +const ( + // SpanKindUnspecified is an unspecified SpanKind and is not a valid + // SpanKind. SpanKindUnspecified should be replaced with SpanKindInternal + // if it is received. + SpanKindUnspecified SpanKind = 0 + // SpanKindInternal is a SpanKind for a Span that represents an internal + // operation within an application. + SpanKindInternal SpanKind = 1 + // SpanKindServer is a SpanKind for a Span that represents the operation + // of handling a request from a client. + SpanKindServer SpanKind = 2 + // SpanKindClient is a SpanKind for a Span that represents the operation + // of client making a request to a server. + SpanKindClient SpanKind = 3 + // SpanKindProducer is a SpanKind for a Span that represents the operation + // of a producer sending a message to a message broker. Unlike + // SpanKindClient and SpanKindServer, there is often no direct + // relationship between this kind of Span and a SpanKindConsumer kind. A + // SpanKindProducer Span will end once the message is accepted by the + // message broker which might not overlap with the processing of that + // message. + SpanKindProducer SpanKind = 4 + // SpanKindConsumer is a SpanKind for a Span that represents the operation + // of a consumer receiving a message from a message broker. Like + // SpanKindProducer Spans, there is often no direct relationship between + // this Span and the Span that produced the message. + SpanKindConsumer SpanKind = 5 +) + +// ValidateSpanKind returns a valid span kind value. This will coerce +// invalid values into the default value, SpanKindInternal. +func ValidateSpanKind(spanKind SpanKind) SpanKind { + switch spanKind { + case SpanKindInternal, + SpanKindServer, + SpanKindClient, + SpanKindProducer, + SpanKindConsumer: + // valid + return spanKind + default: + return SpanKindInternal + } +} + +// String returns the specified name of the SpanKind in lower-case. +func (sk SpanKind) String() string { + switch sk { + case SpanKindInternal: + return "internal" + case SpanKindServer: + return "server" + case SpanKindClient: + return "client" + case SpanKindProducer: + return "producer" + case SpanKindConsumer: + return "consumer" + default: + return "unspecified" + } +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/trace.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/trace.go new file mode 100644 index 0000000000..d49adf671b --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/trace.go @@ -0,0 +1,323 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "bytes" + "encoding/hex" + "encoding/json" +) + +const ( + // FlagsSampled is a bitmask with the sampled bit set. A SpanContext + // with the sampling bit set means the span is sampled. + FlagsSampled = TraceFlags(0x01) + + errInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase" + + errInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32" + errNilTraceID errorConst = "trace-id can't be all zero" + + errInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16" + errNilSpanID errorConst = "span-id can't be all zero" +) + +type errorConst string + +func (e errorConst) Error() string { + return string(e) +} + +// TraceID is a unique identity of a trace. +// nolint:revive // revive complains about stutter of `trace.TraceID`. +type TraceID [16]byte + +var ( + nilTraceID TraceID + _ json.Marshaler = nilTraceID +) + +// IsValid checks whether the trace TraceID is valid. A valid trace ID does +// not consist of zeros only. +func (t TraceID) IsValid() bool { + return !bytes.Equal(t[:], nilTraceID[:]) +} + +// MarshalJSON implements a custom marshal function to encode TraceID +// as a hex string. +func (t TraceID) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +// String returns the hex string representation form of a TraceID. +func (t TraceID) String() string { + return hex.EncodeToString(t[:]) +} + +// SpanID is a unique identity of a span in a trace. +type SpanID [8]byte + +var ( + nilSpanID SpanID + _ json.Marshaler = nilSpanID +) + +// IsValid checks whether the SpanID is valid. A valid SpanID does not consist +// of zeros only. +func (s SpanID) IsValid() bool { + return !bytes.Equal(s[:], nilSpanID[:]) +} + +// MarshalJSON implements a custom marshal function to encode SpanID +// as a hex string. +func (s SpanID) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +// String returns the hex string representation form of a SpanID. +func (s SpanID) String() string { + return hex.EncodeToString(s[:]) +} + +// TraceIDFromHex returns a TraceID from a hex string if it is compliant with +// the W3C trace-context specification. See more at +// https://www.w3.org/TR/trace-context/#trace-id +// nolint:revive // revive complains about stutter of `trace.TraceIDFromHex`. +func TraceIDFromHex(h string) (TraceID, error) { + t := TraceID{} + if len(h) != 32 { + return t, errInvalidTraceIDLength + } + + if err := decodeHex(h, t[:]); err != nil { + return t, err + } + + if !t.IsValid() { + return t, errNilTraceID + } + return t, nil +} + +// SpanIDFromHex returns a SpanID from a hex string if it is compliant +// with the w3c trace-context specification. +// See more at https://www.w3.org/TR/trace-context/#parent-id +func SpanIDFromHex(h string) (SpanID, error) { + s := SpanID{} + if len(h) != 16 { + return s, errInvalidSpanIDLength + } + + if err := decodeHex(h, s[:]); err != nil { + return s, err + } + + if !s.IsValid() { + return s, errNilSpanID + } + return s, nil +} + +func decodeHex(h string, b []byte) error { + for _, r := range h { + switch { + case 'a' <= r && r <= 'f': + continue + case '0' <= r && r <= '9': + continue + default: + return errInvalidHexID + } + } + + decoded, err := hex.DecodeString(h) + if err != nil { + return err + } + + copy(b, decoded) + return nil +} + +// TraceFlags contains flags that can be set on a SpanContext. +type TraceFlags byte //nolint:revive // revive complains about stutter of `trace.TraceFlags`. + +// IsSampled returns if the sampling bit is set in the TraceFlags. +func (tf TraceFlags) IsSampled() bool { + return tf&FlagsSampled == FlagsSampled +} + +// WithSampled sets the sampling bit in a new copy of the TraceFlags. +func (tf TraceFlags) WithSampled(sampled bool) TraceFlags { // nolint:revive // sampled is not a control flag. + if sampled { + return tf | FlagsSampled + } + + return tf &^ FlagsSampled +} + +// MarshalJSON implements a custom marshal function to encode TraceFlags +// as a hex string. +func (tf TraceFlags) MarshalJSON() ([]byte, error) { + return json.Marshal(tf.String()) +} + +// String returns the hex string representation form of TraceFlags. +func (tf TraceFlags) String() string { + return hex.EncodeToString([]byte{byte(tf)}[:]) +} + +// SpanContextConfig contains mutable fields usable for constructing +// an immutable SpanContext. +type SpanContextConfig struct { + TraceID TraceID + SpanID SpanID + TraceFlags TraceFlags + TraceState TraceState + Remote bool +} + +// NewSpanContext constructs a SpanContext using values from the provided +// SpanContextConfig. +func NewSpanContext(config SpanContextConfig) SpanContext { + return SpanContext{ + traceID: config.TraceID, + spanID: config.SpanID, + traceFlags: config.TraceFlags, + traceState: config.TraceState, + remote: config.Remote, + } +} + +// SpanContext contains identifying trace information about a Span. +type SpanContext struct { + traceID TraceID + spanID SpanID + traceFlags TraceFlags + traceState TraceState + remote bool +} + +var _ json.Marshaler = SpanContext{} + +// IsValid returns if the SpanContext is valid. A valid span context has a +// valid TraceID and SpanID. +func (sc SpanContext) IsValid() bool { + return sc.HasTraceID() && sc.HasSpanID() +} + +// IsRemote indicates whether the SpanContext represents a remotely-created Span. +func (sc SpanContext) IsRemote() bool { + return sc.remote +} + +// WithRemote returns a copy of sc with the Remote property set to remote. +func (sc SpanContext) WithRemote(remote bool) SpanContext { + return SpanContext{ + traceID: sc.traceID, + spanID: sc.spanID, + traceFlags: sc.traceFlags, + traceState: sc.traceState, + remote: remote, + } +} + +// TraceID returns the TraceID from the SpanContext. +func (sc SpanContext) TraceID() TraceID { + return sc.traceID +} + +// HasTraceID checks if the SpanContext has a valid TraceID. +func (sc SpanContext) HasTraceID() bool { + return sc.traceID.IsValid() +} + +// WithTraceID returns a new SpanContext with the TraceID replaced. +func (sc SpanContext) WithTraceID(traceID TraceID) SpanContext { + return SpanContext{ + traceID: traceID, + spanID: sc.spanID, + traceFlags: sc.traceFlags, + traceState: sc.traceState, + remote: sc.remote, + } +} + +// SpanID returns the SpanID from the SpanContext. +func (sc SpanContext) SpanID() SpanID { + return sc.spanID +} + +// HasSpanID checks if the SpanContext has a valid SpanID. +func (sc SpanContext) HasSpanID() bool { + return sc.spanID.IsValid() +} + +// WithSpanID returns a new SpanContext with the SpanID replaced. +func (sc SpanContext) WithSpanID(spanID SpanID) SpanContext { + return SpanContext{ + traceID: sc.traceID, + spanID: spanID, + traceFlags: sc.traceFlags, + traceState: sc.traceState, + remote: sc.remote, + } +} + +// TraceFlags returns the flags from the SpanContext. +func (sc SpanContext) TraceFlags() TraceFlags { + return sc.traceFlags +} + +// IsSampled returns if the sampling bit is set in the SpanContext's TraceFlags. +func (sc SpanContext) IsSampled() bool { + return sc.traceFlags.IsSampled() +} + +// WithTraceFlags returns a new SpanContext with the TraceFlags replaced. +func (sc SpanContext) WithTraceFlags(flags TraceFlags) SpanContext { + return SpanContext{ + traceID: sc.traceID, + spanID: sc.spanID, + traceFlags: flags, + traceState: sc.traceState, + remote: sc.remote, + } +} + +// TraceState returns the TraceState from the SpanContext. +func (sc SpanContext) TraceState() TraceState { + return sc.traceState +} + +// WithTraceState returns a new SpanContext with the TraceState replaced. +func (sc SpanContext) WithTraceState(state TraceState) SpanContext { + return SpanContext{ + traceID: sc.traceID, + spanID: sc.spanID, + traceFlags: sc.traceFlags, + traceState: state, + remote: sc.remote, + } +} + +// Equal is a predicate that determines whether two SpanContext values are equal. +func (sc SpanContext) Equal(other SpanContext) bool { + return sc.traceID == other.traceID && + sc.spanID == other.spanID && + sc.traceFlags == other.traceFlags && + sc.traceState.String() == other.traceState.String() && + sc.remote == other.remote +} + +// MarshalJSON implements a custom marshal function to encode a SpanContext. +func (sc SpanContext) MarshalJSON() ([]byte, error) { + return json.Marshal(SpanContextConfig{ + TraceID: sc.traceID, + SpanID: sc.spanID, + TraceFlags: sc.traceFlags, + TraceState: sc.traceState, + Remote: sc.remote, + }) +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/tracer.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/tracer.go new file mode 100644 index 0000000000..77952d2a0b --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/tracer.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "context" + + "go.opentelemetry.io/otel/trace/embedded" +) + +// Tracer is the creator of Spans. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Tracer interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Tracer + + // Start creates a span and a context.Context containing the newly-created span. + // + // If the context.Context provided in `ctx` contains a Span then the newly-created + // Span will be a child of that span, otherwise it will be a root span. This behavior + // can be overridden by providing `WithNewRoot()` as a SpanOption, causing the + // newly-created Span to be a root span even if `ctx` contains a Span. + // + // When creating a Span it is recommended to provide all known span attributes using + // the `WithAttributes()` SpanOption as samplers will only have access to the + // attributes provided when a Span is created. + // + // Any Span that is created MUST also be ended. This is the responsibility of the user. + // Implementations of this API may leak memory or other resources if Spans are not ended. + Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span) +} diff --git a/go-controller/vendor/go.opentelemetry.io/otel/trace/tracestate.go b/go-controller/vendor/go.opentelemetry.io/otel/trace/tracestate.go new file mode 100644 index 0000000000..dc5e34cad0 --- /dev/null +++ b/go-controller/vendor/go.opentelemetry.io/otel/trace/tracestate.go @@ -0,0 +1,330 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trace // import "go.opentelemetry.io/otel/trace" + +import ( + "encoding/json" + "fmt" + "strings" +) + +const ( + maxListMembers = 32 + + listDelimiters = "," + memberDelimiter = "=" + + errInvalidKey errorConst = "invalid tracestate key" + errInvalidValue errorConst = "invalid tracestate value" + errInvalidMember errorConst = "invalid tracestate list-member" + errMemberNumber errorConst = "too many list-members in tracestate" + errDuplicate errorConst = "duplicate list-member in tracestate" +) + +type member struct { + Key string + Value string +} + +// according to (chr = %x20 / (nblk-char = %x21-2B / %x2D-3C / %x3E-7E) ) +// means (chr = %x20-2B / %x2D-3C / %x3E-7E) . +func checkValueChar(v byte) bool { + return v >= '\x20' && v <= '\x7e' && v != '\x2c' && v != '\x3d' +} + +// according to (nblk-chr = %x21-2B / %x2D-3C / %x3E-7E) . +func checkValueLast(v byte) bool { + return v >= '\x21' && v <= '\x7e' && v != '\x2c' && v != '\x3d' +} + +// based on the W3C Trace Context specification +// +// value = (0*255(chr)) nblk-chr +// nblk-chr = %x21-2B / %x2D-3C / %x3E-7E +// chr = %x20 / nblk-chr +// +// see https://www.w3.org/TR/trace-context-1/#value +func checkValue(val string) bool { + n := len(val) + if n == 0 || n > 256 { + return false + } + for i := 0; i < n-1; i++ { + if !checkValueChar(val[i]) { + return false + } + } + return checkValueLast(val[n-1]) +} + +func checkKeyRemain(key string) bool { + // ( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) + for _, v := range key { + if isAlphaNum(byte(v)) { + continue + } + switch v { + case '_', '-', '*', '/': + continue + } + return false + } + return true +} + +// according to +// +// simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )) +// system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )) +// +// param n is remain part length, should be 255 in simple-key or 13 in system-id. +func checkKeyPart(key string, n int) bool { + if len(key) == 0 { + return false + } + first := key[0] // key's first char + ret := len(key[1:]) <= n + ret = ret && first >= 'a' && first <= 'z' + return ret && checkKeyRemain(key[1:]) +} + +func isAlphaNum(c byte) bool { + if c >= 'a' && c <= 'z' { + return true + } + return c >= '0' && c <= '9' +} + +// according to +// +// tenant-id = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) +// +// param n is remain part length, should be 240 exactly. +func checkKeyTenant(key string, n int) bool { + if len(key) == 0 { + return false + } + return isAlphaNum(key[0]) && len(key[1:]) <= n && checkKeyRemain(key[1:]) +} + +// based on the W3C Trace Context specification +// +// key = simple-key / multi-tenant-key +// simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )) +// multi-tenant-key = tenant-id "@" system-id +// tenant-id = ( lcalpha / DIGIT ) (0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )) +// system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )) +// lcalpha = %x61-7A ; a-z +// +// see https://www.w3.org/TR/trace-context-1/#tracestate-header. +func checkKey(key string) bool { + tenant, system, ok := strings.Cut(key, "@") + if !ok { + return checkKeyPart(key, 255) + } + return checkKeyTenant(tenant, 240) && checkKeyPart(system, 13) +} + +func newMember(key, value string) (member, error) { + if !checkKey(key) { + return member{}, errInvalidKey + } + if !checkValue(value) { + return member{}, errInvalidValue + } + return member{Key: key, Value: value}, nil +} + +func parseMember(m string) (member, error) { + key, val, ok := strings.Cut(m, memberDelimiter) + if !ok { + return member{}, fmt.Errorf("%w: %s", errInvalidMember, m) + } + key = strings.TrimLeft(key, " \t") + val = strings.TrimRight(val, " \t") + result, e := newMember(key, val) + if e != nil { + return member{}, fmt.Errorf("%w: %s", errInvalidMember, m) + } + return result, nil +} + +// String encodes member into a string compliant with the W3C Trace Context +// specification. +func (m member) String() string { + return m.Key + "=" + m.Value +} + +// TraceState provides additional vendor-specific trace identification +// information across different distributed tracing systems. It represents an +// immutable list consisting of key/value pairs, each pair is referred to as a +// list-member. +// +// TraceState conforms to the W3C Trace Context specification +// (https://www.w3.org/TR/trace-context-1). All operations that create or copy +// a TraceState do so by validating all input and will only produce TraceState +// that conform to the specification. Specifically, this means that all +// list-member's key/value pairs are valid, no duplicate list-members exist, +// and the maximum number of list-members (32) is not exceeded. +type TraceState struct { //nolint:revive // revive complains about stutter of `trace.TraceState` + // list is the members in order. + list []member +} + +var _ json.Marshaler = TraceState{} + +// ParseTraceState attempts to decode a TraceState from the passed +// string. It returns an error if the input is invalid according to the W3C +// Trace Context specification. +func ParseTraceState(ts string) (TraceState, error) { + if ts == "" { + return TraceState{}, nil + } + + wrapErr := func(err error) error { + return fmt.Errorf("failed to parse tracestate: %w", err) + } + + var members []member + found := make(map[string]struct{}) + for ts != "" { + var memberStr string + memberStr, ts, _ = strings.Cut(ts, listDelimiters) + if len(memberStr) == 0 { + continue + } + + m, err := parseMember(memberStr) + if err != nil { + return TraceState{}, wrapErr(err) + } + + if _, ok := found[m.Key]; ok { + return TraceState{}, wrapErr(errDuplicate) + } + found[m.Key] = struct{}{} + + members = append(members, m) + if n := len(members); n > maxListMembers { + return TraceState{}, wrapErr(errMemberNumber) + } + } + + return TraceState{list: members}, nil +} + +// MarshalJSON marshals the TraceState into JSON. +func (ts TraceState) MarshalJSON() ([]byte, error) { + return json.Marshal(ts.String()) +} + +// String encodes the TraceState into a string compliant with the W3C +// Trace Context specification. The returned string will be invalid if the +// TraceState contains any invalid members. +func (ts TraceState) String() string { + if len(ts.list) == 0 { + return "" + } + var n int + n += len(ts.list) // member delimiters: '=' + n += len(ts.list) - 1 // list delimiters: ',' + for _, mem := range ts.list { + n += len(mem.Key) + n += len(mem.Value) + } + + var sb strings.Builder + sb.Grow(n) + _, _ = sb.WriteString(ts.list[0].Key) + _ = sb.WriteByte('=') + _, _ = sb.WriteString(ts.list[0].Value) + for i := 1; i < len(ts.list); i++ { + _ = sb.WriteByte(listDelimiters[0]) + _, _ = sb.WriteString(ts.list[i].Key) + _ = sb.WriteByte('=') + _, _ = sb.WriteString(ts.list[i].Value) + } + return sb.String() +} + +// Get returns the value paired with key from the corresponding TraceState +// list-member if it exists, otherwise an empty string is returned. +func (ts TraceState) Get(key string) string { + for _, member := range ts.list { + if member.Key == key { + return member.Value + } + } + + return "" +} + +// Walk walks all key value pairs in the TraceState by calling f +// Iteration stops if f returns false. +func (ts TraceState) Walk(f func(key, value string) bool) { + for _, m := range ts.list { + if !f(m.Key, m.Value) { + break + } + } +} + +// Insert adds a new list-member defined by the key/value pair to the +// TraceState. If a list-member already exists for the given key, that +// list-member's value is updated. The new or updated list-member is always +// moved to the beginning of the TraceState as specified by the W3C Trace +// Context specification. +// +// If key or value are invalid according to the W3C Trace Context +// specification an error is returned with the original TraceState. +// +// If adding a new list-member means the TraceState would have more members +// then is allowed, the new list-member will be inserted and the right-most +// list-member will be dropped in the returned TraceState. +func (ts TraceState) Insert(key, value string) (TraceState, error) { + m, err := newMember(key, value) + if err != nil { + return ts, err + } + n := len(ts.list) + found := n + for i := range ts.list { + if ts.list[i].Key == key { + found = i + } + } + cTS := TraceState{} + if found == n && n < maxListMembers { + cTS.list = make([]member, n+1) + } else { + cTS.list = make([]member, n) + } + cTS.list[0] = m + // When the number of members exceeds capacity, drop the "right-most". + copy(cTS.list[1:], ts.list[0:found]) + if found < n { + copy(cTS.list[1+found:], ts.list[found+1:]) + } + return cTS, nil +} + +// Delete returns a copy of the TraceState with the list-member identified by +// key removed. +func (ts TraceState) Delete(key string) TraceState { + members := make([]member, ts.Len()) + copy(members, ts.list) + for i, member := range ts.list { + if member.Key == key { + members = append(members[:i], members[i+1:]...) + // TraceState should contain no duplicate members. + break + } + } + return TraceState{list: members} +} + +// Len returns the number of list-members in the TraceState. +func (ts TraceState) Len() int { + return len(ts.list) +} diff --git a/go-controller/vendor/google.golang.org/grpc/CONTRIBUTING.md b/go-controller/vendor/google.golang.org/grpc/CONTRIBUTING.md index 0854d298e4..d9bfa6e1e7 100644 --- a/go-controller/vendor/google.golang.org/grpc/CONTRIBUTING.md +++ b/go-controller/vendor/google.golang.org/grpc/CONTRIBUTING.md @@ -4,7 +4,7 @@ We definitely welcome your patches and contributions to gRPC! Please read the gR organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md) and [contribution guidelines](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) before proceeding. -If you are new to github, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/) +If you are new to GitHub, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/) ## Legal requirements @@ -25,8 +25,8 @@ How to get your contributions merged smoothly and quickly. is a great place to start. These issues are well-documented and usually can be resolved with a single pull request. -- If you are adding a new file, make sure it has the copyright message template - at the top as a comment. You can copy over the message from an existing file +- If you are adding a new file, make sure it has the copyright message template + at the top as a comment. You can copy over the message from an existing file and update the year. - The grpc package should only depend on standard Go packages and a small number @@ -39,12 +39,12 @@ How to get your contributions merged smoothly and quickly. proposal](https://github.com/grpc/proposal). - Provide a good **PR description** as a record of **what** change is being made - and **why** it was made. Link to a github issue if it exists. + and **why** it was made. Link to a GitHub issue if it exists. -- If you want to fix formatting or style, consider whether your changes are an - obvious improvement or might be considered a personal preference. If a style - change is based on preference, it likely will not be accepted. If it corrects - widely agreed-upon anti-patterns, then please do create a PR and explain the +- If you want to fix formatting or style, consider whether your changes are an + obvious improvement or might be considered a personal preference. If a style + change is based on preference, it likely will not be accepted. If it corrects + widely agreed-upon anti-patterns, then please do create a PR and explain the benefits of the change. - Unless your PR is trivial, you should expect there will be reviewer comments diff --git a/go-controller/vendor/google.golang.org/grpc/MAINTAINERS.md b/go-controller/vendor/google.golang.org/grpc/MAINTAINERS.md index 6a8a07781a..5d4096d46a 100644 --- a/go-controller/vendor/google.golang.org/grpc/MAINTAINERS.md +++ b/go-controller/vendor/google.golang.org/grpc/MAINTAINERS.md @@ -9,21 +9,28 @@ for general contribution guidelines. ## Maintainers (in alphabetical order) +- [aranjans](https://github.com/aranjans), Google LLC +- [arjan-bal](https://github.com/arjan-bal), Google LLC +- [arvindbr8](https://github.com/arvindbr8), Google LLC - [atollena](https://github.com/atollena), Datadog, Inc. -- [cesarghali](https://github.com/cesarghali), Google LLC - [dfawley](https://github.com/dfawley), Google LLC - [easwars](https://github.com/easwars), Google LLC -- [menghanl](https://github.com/menghanl), Google LLC -- [srini100](https://github.com/srini100), Google LLC +- [erm-g](https://github.com/erm-g), Google LLC +- [gtcooke94](https://github.com/gtcooke94), Google LLC +- [purnesh42h](https://github.com/purnesh42h), Google LLC +- [zasweq](https://github.com/zasweq), Google LLC ## Emeritus Maintainers (in alphabetical order) -- [adelez](https://github.com/adelez), Google LLC -- [canguler](https://github.com/canguler), Google LLC -- [iamqizhao](https://github.com/iamqizhao), Google LLC -- [jadekler](https://github.com/jadekler), Google LLC -- [jtattermusch](https://github.com/jtattermusch), Google LLC -- [lyuxuan](https://github.com/lyuxuan), Google LLC -- [makmukhi](https://github.com/makmukhi), Google LLC -- [matt-kwong](https://github.com/matt-kwong), Google LLC -- [nicolasnoble](https://github.com/nicolasnoble), Google LLC -- [yongni](https://github.com/yongni), Google LLC +- [adelez](https://github.com/adelez) +- [canguler](https://github.com/canguler) +- [cesarghali](https://github.com/cesarghali) +- [iamqizhao](https://github.com/iamqizhao) +- [jeanbza](https://github.com/jeanbza) +- [jtattermusch](https://github.com/jtattermusch) +- [lyuxuan](https://github.com/lyuxuan) +- [makmukhi](https://github.com/makmukhi) +- [matt-kwong](https://github.com/matt-kwong) +- [menghanl](https://github.com/menghanl) +- [nicolasnoble](https://github.com/nicolasnoble) +- [srini100](https://github.com/srini100) +- [yongni](https://github.com/yongni) diff --git a/go-controller/vendor/google.golang.org/grpc/SECURITY.md b/go-controller/vendor/google.golang.org/grpc/SECURITY.md index be6e108705..abab279379 100644 --- a/go-controller/vendor/google.golang.org/grpc/SECURITY.md +++ b/go-controller/vendor/google.golang.org/grpc/SECURITY.md @@ -1,3 +1,3 @@ # Security Policy -For information on gRPC Security Policy and reporting potentional security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md). +For information on gRPC Security Policy and reporting potential security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md). diff --git a/go-controller/vendor/google.golang.org/grpc/backoff/backoff.go b/go-controller/vendor/google.golang.org/grpc/backoff/backoff.go index 0787d0b50c..d7b40b7cb6 100644 --- a/go-controller/vendor/google.golang.org/grpc/backoff/backoff.go +++ b/go-controller/vendor/google.golang.org/grpc/backoff/backoff.go @@ -39,7 +39,7 @@ type Config struct { MaxDelay time.Duration } -// DefaultConfig is a backoff configuration with the default values specfied +// DefaultConfig is a backoff configuration with the default values specified // at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. // // This should be useful for callers who want to configure backoff with diff --git a/go-controller/vendor/google.golang.org/grpc/balancer/balancer.go b/go-controller/vendor/google.golang.org/grpc/balancer/balancer.go index f391744f72..3a2092f105 100644 --- a/go-controller/vendor/google.golang.org/grpc/balancer/balancer.go +++ b/go-controller/vendor/google.golang.org/grpc/balancer/balancer.go @@ -30,6 +30,7 @@ import ( "google.golang.org/grpc/channelz" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials" + estats "google.golang.org/grpc/experimental/stats" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" "google.golang.org/grpc/metadata" @@ -72,8 +73,21 @@ func unregisterForTesting(name string) { delete(m, name) } +// connectedAddress returns the connected address for a SubConnState. The +// address is only valid if the state is READY. +func connectedAddress(scs SubConnState) resolver.Address { + return scs.connectedAddress +} + +// setConnectedAddress sets the connected address for a SubConnState. +func setConnectedAddress(scs *SubConnState, addr resolver.Address) { + scs.connectedAddress = addr +} + func init() { internal.BalancerUnregister = unregisterForTesting + internal.ConnectedAddress = connectedAddress + internal.SetConnectedAddress = setConnectedAddress } // Get returns the resolver builder registered with the given name. @@ -116,7 +130,7 @@ type SubConn interface { // UpdateAddresses updates the addresses used in this SubConn. // gRPC checks if currently-connected address is still in the new list. // If it's in the list, the connection will be kept. - // If it's not in the list, the connection will gracefully closed, and + // If it's not in the list, the connection will gracefully close, and // a new connection will be created. // // This will trigger a state transition for the SubConn. @@ -128,8 +142,11 @@ type SubConn interface { Connect() // GetOrBuildProducer returns a reference to the existing Producer for this // ProducerBuilder in this SubConn, or, if one does not currently exist, - // creates a new one and returns it. Returns a close function which must - // be called when the Producer is no longer needed. + // creates a new one and returns it. Returns a close function which may be + // called when the Producer is no longer needed. Otherwise the producer + // will automatically be closed upon connection loss or subchannel close. + // Should only be called on a SubConn in state Ready. Otherwise the + // producer will be unable to create streams. GetOrBuildProducer(ProducerBuilder) (p Producer, close func()) // Shutdown shuts down the SubConn gracefully. Any started RPCs will be // allowed to complete. No future calls should be made on the SubConn. @@ -243,6 +260,10 @@ type BuildOptions struct { // same resolver.Target as passed to the resolver. See the documentation for // the resolver.Target type for details about what it contains. Target resolver.Target + // MetricsRecorder is the metrics recorder that balancers can use to record + // metrics. Balancer implementations which do not register metrics on + // metrics registry and record on them can ignore this field. + MetricsRecorder estats.MetricsRecorder } // Builder creates a balancer. @@ -410,6 +431,9 @@ type SubConnState struct { // ConnectionError is set if the ConnectivityState is TransientFailure, // describing the reason the SubConn failed. Otherwise, it is nil. ConnectionError error + // connectedAddr contains the connected address when ConnectivityState is + // Ready. Otherwise, it is indeterminate. + connectedAddress resolver.Address } // ClientConnState describes the state of a ClientConn relevant to the @@ -431,8 +455,10 @@ type ProducerBuilder interface { // Build creates a Producer. The first parameter is always a // grpc.ClientConnInterface (a type to allow creating RPCs/streams on the // associated SubConn), but is declared as `any` to avoid a dependency - // cycle. Should also return a close function that will be called when all - // references to the Producer have been given up. + // cycle. Build also returns a close function that will be called when all + // references to the Producer have been given up for a SubConn, or when a + // connectivity state change occurs on the SubConn. The close function + // should always block until all asynchronous cleanup work is completed. Build(grpcClientConnInterface any) (p Producer, close func()) } diff --git a/go-controller/vendor/google.golang.org/grpc/balancer/base/balancer.go b/go-controller/vendor/google.golang.org/grpc/balancer/base/balancer.go index a7f1eeec8e..d5ed172ae6 100644 --- a/go-controller/vendor/google.golang.org/grpc/balancer/base/balancer.go +++ b/go-controller/vendor/google.golang.org/grpc/balancer/base/balancer.go @@ -36,7 +36,7 @@ type baseBuilder struct { config Config } -func (bb *baseBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { +func (bb *baseBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer { bal := &baseBalancer{ cc: cc, pickerBuilder: bb.pickerBuilder, @@ -133,7 +133,7 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error { } } // If resolver state contains no addresses, return an error so ClientConn - // will trigger re-resolve. Also records this as an resolver error, so when + // will trigger re-resolve. Also records this as a resolver error, so when // the overall state turns transient failure, the error message will have // the zero address information. if len(s.ResolverState.Addresses) == 0 { @@ -259,6 +259,6 @@ type errPicker struct { err error // Pick() always returns this err. } -func (p *errPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { +func (p *errPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { return balancer.PickResult{}, p.err } diff --git a/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go new file mode 100644 index 0000000000..c519789458 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go @@ -0,0 +1,24 @@ +/* + * Copyright 2024 gRPC authors. + * + * 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 internal contains code internal to the pickfirst package. +package internal + +import "math/rand" + +// RandShuffle pseudo-randomizes the order of addresses. +var RandShuffle = rand.Shuffle diff --git a/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirst.go b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirst.go index 07527603f1..e069346a75 100644 --- a/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirst.go +++ b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirst.go @@ -26,18 +26,23 @@ import ( "math/rand" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/pickfirst/internal" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/internal" + "google.golang.org/grpc/internal/envconfig" internalgrpclog "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" + + _ "google.golang.org/grpc/balancer/pickfirst/pickfirstleaf" // For automatically registering the new pickfirst if required. ) func init() { + if envconfig.NewPickFirstEnabled { + return + } balancer.Register(pickfirstBuilder{}) - internal.ShuffleAddressListForTesting = func(n int, swap func(i, j int)) { rand.Shuffle(n, swap) } } var logger = grpclog.Component("pick-first-lb") @@ -50,7 +55,7 @@ const ( type pickfirstBuilder struct{} -func (pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { +func (pickfirstBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer { b := &pickfirstBalancer{cc: cc} b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b)) return b @@ -103,10 +108,13 @@ func (b *pickfirstBalancer) ResolverError(err error) { }) } +// Shuffler is an interface for shuffling an address list. type Shuffler interface { ShuffleAddressListForTesting(n int, swap func(i, j int)) } +// ShuffleAddressListForTesting pseudo-randomizes the order of addresses. n +// is the number of elements. swap swaps the elements with indexes i and j. func ShuffleAddressListForTesting(n int, swap func(i, j int)) { rand.Shuffle(n, swap) } func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error { @@ -140,7 +148,7 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState // within each endpoint. - A61 if cfg.ShuffleAddressList { endpoints = append([]resolver.Endpoint{}, endpoints...) - internal.ShuffleAddressListForTesting.(func(int, func(int, int)))(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) + internal.RandShuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) } // "Flatten the list by concatenating the ordered list of addresses for each @@ -155,7 +163,7 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState // Endpoints not set, process addresses until we migrate resolver // emissions fully to Endpoints. The top channel does wrap emitted // addresses with endpoints, however some balancers such as weighted - // target do not forwarrd the corresponding correct endpoints down/split + // target do not forward the corresponding correct endpoints down/split // endpoints properly. Once all balancers correctly forward endpoints // down, can delete this else conditional. addrs = state.ResolverState.Addresses diff --git a/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go new file mode 100644 index 0000000000..985b6edc7f --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go @@ -0,0 +1,625 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 pickfirstleaf contains the pick_first load balancing policy which +// will be the universal leaf policy after dualstack changes are implemented. +// +// # Experimental +// +// Notice: This package is EXPERIMENTAL and may be changed or removed in a +// later release. +package pickfirstleaf + +import ( + "encoding/json" + "errors" + "fmt" + "sync" + + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/pickfirst/internal" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/internal/envconfig" + internalgrpclog "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/serviceconfig" +) + +func init() { + if envconfig.NewPickFirstEnabled { + // Register as the default pick_first balancer. + Name = "pick_first" + } + balancer.Register(pickfirstBuilder{}) +} + +var ( + logger = grpclog.Component("pick-first-leaf-lb") + // Name is the name of the pick_first_leaf balancer. + // It is changed to "pick_first" in init() if this balancer is to be + // registered as the default pickfirst. + Name = "pick_first_leaf" +) + +// TODO: change to pick-first when this becomes the default pick_first policy. +const logPrefix = "[pick-first-leaf-lb %p] " + +type pickfirstBuilder struct{} + +func (pickfirstBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer { + b := &pickfirstBalancer{ + cc: cc, + addressList: addressList{}, + subConns: resolver.NewAddressMap(), + state: connectivity.Connecting, + mu: sync.Mutex{}, + } + b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b)) + return b +} + +func (b pickfirstBuilder) Name() string { + return Name +} + +func (pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { + var cfg pfConfig + if err := json.Unmarshal(js, &cfg); err != nil { + return nil, fmt.Errorf("pickfirst: unable to unmarshal LB policy config: %s, error: %v", string(js), err) + } + return cfg, nil +} + +type pfConfig struct { + serviceconfig.LoadBalancingConfig `json:"-"` + + // If set to true, instructs the LB policy to shuffle the order of the list + // of endpoints received from the name resolver before attempting to + // connect to them. + ShuffleAddressList bool `json:"shuffleAddressList"` +} + +// scData keeps track of the current state of the subConn. +// It is not safe for concurrent access. +type scData struct { + // The following fields are initialized at build time and read-only after + // that. + subConn balancer.SubConn + addr resolver.Address + + state connectivity.State + lastErr error +} + +func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) { + sd := &scData{ + state: connectivity.Idle, + addr: addr, + } + sc, err := b.cc.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{ + StateListener: func(state balancer.SubConnState) { + b.updateSubConnState(sd, state) + }, + }) + if err != nil { + return nil, err + } + sd.subConn = sc + return sd, nil +} + +type pickfirstBalancer struct { + // The following fields are initialized at build time and read-only after + // that and therefore do not need to be guarded by a mutex. + logger *internalgrpclog.PrefixLogger + cc balancer.ClientConn + + // The mutex is used to ensure synchronization of updates triggered + // from the idle picker and the already serialized resolver, + // SubConn state updates. + mu sync.Mutex + state connectivity.State + // scData for active subonns mapped by address. + subConns *resolver.AddressMap + addressList addressList + firstPass bool + numTF int +} + +// ResolverError is called by the ClientConn when the name resolver produces +// an error or when pickfirst determined the resolver update to be invalid. +func (b *pickfirstBalancer) ResolverError(err error) { + b.mu.Lock() + defer b.mu.Unlock() + b.resolverErrorLocked(err) +} + +func (b *pickfirstBalancer) resolverErrorLocked(err error) { + if b.logger.V(2) { + b.logger.Infof("Received error from the name resolver: %v", err) + } + + // The picker will not change since the balancer does not currently + // report an error. If the balancer hasn't received a single good resolver + // update yet, transition to TRANSIENT_FAILURE. + if b.state != connectivity.TransientFailure && b.addressList.size() > 0 { + if b.logger.V(2) { + b.logger.Infof("Ignoring resolver error because balancer is using a previous good update.") + } + return + } + + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.TransientFailure, + Picker: &picker{err: fmt.Errorf("name resolver error: %v", err)}, + }) +} + +func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error { + b.mu.Lock() + defer b.mu.Unlock() + if len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 { + // Cleanup state pertaining to the previous resolver state. + // Treat an empty address list like an error by calling b.ResolverError. + b.state = connectivity.TransientFailure + b.closeSubConnsLocked() + b.addressList.updateAddrs(nil) + b.resolverErrorLocked(errors.New("produced zero addresses")) + return balancer.ErrBadResolverState + } + cfg, ok := state.BalancerConfig.(pfConfig) + if state.BalancerConfig != nil && !ok { + return fmt.Errorf("pickfirst: received illegal BalancerConfig (type %T): %v: %w", state.BalancerConfig, state.BalancerConfig, balancer.ErrBadResolverState) + } + + if b.logger.V(2) { + b.logger.Infof("Received new config %s, resolver state %s", pretty.ToJSON(cfg), pretty.ToJSON(state.ResolverState)) + } + + var newAddrs []resolver.Address + if endpoints := state.ResolverState.Endpoints; len(endpoints) != 0 { + // Perform the optional shuffling described in gRFC A62. The shuffling + // will change the order of endpoints but not touch the order of the + // addresses within each endpoint. - A61 + if cfg.ShuffleAddressList { + endpoints = append([]resolver.Endpoint{}, endpoints...) + internal.RandShuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) + } + + // "Flatten the list by concatenating the ordered list of addresses for + // each of the endpoints, in order." - A61 + for _, endpoint := range endpoints { + // "In the flattened list, interleave addresses from the two address + // families, as per RFC-8305 section 4." - A61 + // TODO: support the above language. + newAddrs = append(newAddrs, endpoint.Addresses...) + } + } else { + // Endpoints not set, process addresses until we migrate resolver + // emissions fully to Endpoints. The top channel does wrap emitted + // addresses with endpoints, however some balancers such as weighted + // target do not forward the corresponding correct endpoints down/split + // endpoints properly. Once all balancers correctly forward endpoints + // down, can delete this else conditional. + newAddrs = state.ResolverState.Addresses + if cfg.ShuffleAddressList { + newAddrs = append([]resolver.Address{}, newAddrs...) + internal.RandShuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) + } + } + + // If an address appears in multiple endpoints or in the same endpoint + // multiple times, we keep it only once. We will create only one SubConn + // for the address because an AddressMap is used to store SubConns. + // Not de-duplicating would result in attempting to connect to the same + // SubConn multiple times in the same pass. We don't want this. + newAddrs = deDupAddresses(newAddrs) + + // Since we have a new set of addresses, we are again at first pass. + b.firstPass = true + + // If the previous ready SubConn exists in new address list, + // keep this connection and don't create new SubConns. + prevAddr := b.addressList.currentAddress() + prevAddrsCount := b.addressList.size() + b.addressList.updateAddrs(newAddrs) + if b.state == connectivity.Ready && b.addressList.seekTo(prevAddr) { + return nil + } + + b.reconcileSubConnsLocked(newAddrs) + // If it's the first resolver update or the balancer was already READY + // (but the new address list does not contain the ready SubConn) or + // CONNECTING, enter CONNECTING. + // We may be in TRANSIENT_FAILURE due to a previous empty address list, + // we should still enter CONNECTING because the sticky TF behaviour + // mentioned in A62 applies only when the TRANSIENT_FAILURE is reported + // due to connectivity failures. + if b.state == connectivity.Ready || b.state == connectivity.Connecting || prevAddrsCount == 0 { + // Start connection attempt at first address. + b.state = connectivity.Connecting + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.Connecting, + Picker: &picker{err: balancer.ErrNoSubConnAvailable}, + }) + b.requestConnectionLocked() + } else if b.state == connectivity.TransientFailure { + // If we're in TRANSIENT_FAILURE, we stay in TRANSIENT_FAILURE until + // we're READY. See A62. + b.requestConnectionLocked() + } + return nil +} + +// UpdateSubConnState is unused as a StateListener is always registered when +// creating SubConns. +func (b *pickfirstBalancer) UpdateSubConnState(subConn balancer.SubConn, state balancer.SubConnState) { + b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", subConn, state) +} + +func (b *pickfirstBalancer) Close() { + b.mu.Lock() + defer b.mu.Unlock() + b.closeSubConnsLocked() + b.state = connectivity.Shutdown +} + +// ExitIdle moves the balancer out of idle state. It can be called concurrently +// by the idlePicker and clientConn so access to variables should be +// synchronized. +func (b *pickfirstBalancer) ExitIdle() { + b.mu.Lock() + defer b.mu.Unlock() + if b.state == connectivity.Idle && b.addressList.currentAddress() == b.addressList.first() { + b.firstPass = true + b.requestConnectionLocked() + } +} + +func (b *pickfirstBalancer) closeSubConnsLocked() { + for _, sd := range b.subConns.Values() { + sd.(*scData).subConn.Shutdown() + } + b.subConns = resolver.NewAddressMap() +} + +// deDupAddresses ensures that each address appears only once in the slice. +func deDupAddresses(addrs []resolver.Address) []resolver.Address { + seenAddrs := resolver.NewAddressMap() + retAddrs := []resolver.Address{} + + for _, addr := range addrs { + if _, ok := seenAddrs.Get(addr); ok { + continue + } + retAddrs = append(retAddrs, addr) + } + return retAddrs +} + +// reconcileSubConnsLocked updates the active subchannels based on a new address +// list from the resolver. It does this by: +// - closing subchannels: any existing subchannels associated with addresses +// that are no longer in the updated list are shut down. +// - removing subchannels: entries for these closed subchannels are removed +// from the subchannel map. +// +// This ensures that the subchannel map accurately reflects the current set of +// addresses received from the name resolver. +func (b *pickfirstBalancer) reconcileSubConnsLocked(newAddrs []resolver.Address) { + newAddrsMap := resolver.NewAddressMap() + for _, addr := range newAddrs { + newAddrsMap.Set(addr, true) + } + + for _, oldAddr := range b.subConns.Keys() { + if _, ok := newAddrsMap.Get(oldAddr); ok { + continue + } + val, _ := b.subConns.Get(oldAddr) + val.(*scData).subConn.Shutdown() + b.subConns.Delete(oldAddr) + } +} + +// shutdownRemainingLocked shuts down remaining subConns. Called when a subConn +// becomes ready, which means that all other subConn must be shutdown. +func (b *pickfirstBalancer) shutdownRemainingLocked(selected *scData) { + for _, v := range b.subConns.Values() { + sd := v.(*scData) + if sd.subConn != selected.subConn { + sd.subConn.Shutdown() + } + } + b.subConns = resolver.NewAddressMap() + b.subConns.Set(selected.addr, selected) +} + +// requestConnectionLocked starts connecting on the subchannel corresponding to +// the current address. If no subchannel exists, one is created. If the current +// subchannel is in TransientFailure, a connection to the next address is +// attempted until a subchannel is found. +func (b *pickfirstBalancer) requestConnectionLocked() { + if !b.addressList.isValid() { + return + } + var lastErr error + for valid := true; valid; valid = b.addressList.increment() { + curAddr := b.addressList.currentAddress() + sd, ok := b.subConns.Get(curAddr) + if !ok { + var err error + // We want to assign the new scData to sd from the outer scope, + // hence we can't use := below. + sd, err = b.newSCData(curAddr) + if err != nil { + // This should never happen, unless the clientConn is being shut + // down. + if b.logger.V(2) { + b.logger.Infof("Failed to create a subConn for address %v: %v", curAddr.String(), err) + } + // Do nothing, the LB policy will be closed soon. + return + } + b.subConns.Set(curAddr, sd) + } + + scd := sd.(*scData) + switch scd.state { + case connectivity.Idle: + scd.subConn.Connect() + case connectivity.TransientFailure: + // Try the next address. + lastErr = scd.lastErr + continue + case connectivity.Ready: + // Should never happen. + b.logger.Errorf("Requesting a connection even though we have a READY SubConn") + case connectivity.Shutdown: + // Should never happen. + b.logger.Errorf("SubConn with state SHUTDOWN present in SubConns map") + case connectivity.Connecting: + // Wait for the SubConn to report success or failure. + } + return + } + // All the remaining addresses in the list are in TRANSIENT_FAILURE, end the + // first pass. + b.endFirstPassLocked(lastErr) +} + +func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.SubConnState) { + b.mu.Lock() + defer b.mu.Unlock() + oldState := sd.state + sd.state = newState.ConnectivityState + // Previously relevant SubConns can still callback with state updates. + // To prevent pickers from returning these obsolete SubConns, this logic + // is included to check if the current list of active SubConns includes this + // SubConn. + if activeSD, found := b.subConns.Get(sd.addr); !found || activeSD != sd { + return + } + if newState.ConnectivityState == connectivity.Shutdown { + return + } + + if newState.ConnectivityState == connectivity.Ready { + b.shutdownRemainingLocked(sd) + if !b.addressList.seekTo(sd.addr) { + // This should not fail as we should have only one SubConn after + // entering READY. The SubConn should be present in the addressList. + b.logger.Errorf("Address %q not found address list in %v", sd.addr, b.addressList.addresses) + return + } + b.state = connectivity.Ready + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.Ready, + Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}}, + }) + return + } + + // If the LB policy is READY, and it receives a subchannel state change, + // it means that the READY subchannel has failed. + // A SubConn can also transition from CONNECTING directly to IDLE when + // a transport is successfully created, but the connection fails + // before the SubConn can send the notification for READY. We treat + // this as a successful connection and transition to IDLE. + if (b.state == connectivity.Ready && newState.ConnectivityState != connectivity.Ready) || (oldState == connectivity.Connecting && newState.ConnectivityState == connectivity.Idle) { + // Once a transport fails, the balancer enters IDLE and starts from + // the first address when the picker is used. + b.shutdownRemainingLocked(sd) + b.state = connectivity.Idle + b.addressList.reset() + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.Idle, + Picker: &idlePicker{exitIdle: sync.OnceFunc(b.ExitIdle)}, + }) + return + } + + if b.firstPass { + switch newState.ConnectivityState { + case connectivity.Connecting: + // The balancer can be in either IDLE, CONNECTING or + // TRANSIENT_FAILURE. If it's in TRANSIENT_FAILURE, stay in + // TRANSIENT_FAILURE until it's READY. See A62. + // If the balancer is already in CONNECTING, no update is needed. + if b.state == connectivity.Idle { + b.state = connectivity.Connecting + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.Connecting, + Picker: &picker{err: balancer.ErrNoSubConnAvailable}, + }) + } + case connectivity.TransientFailure: + sd.lastErr = newState.ConnectionError + // Since we're re-using common SubConns while handling resolver + // updates, we could receive an out of turn TRANSIENT_FAILURE from + // a pass over the previous address list. We ignore such updates. + + if curAddr := b.addressList.currentAddress(); !equalAddressIgnoringBalAttributes(&curAddr, &sd.addr) { + return + } + if b.addressList.increment() { + b.requestConnectionLocked() + return + } + // End of the first pass. + b.endFirstPassLocked(newState.ConnectionError) + } + return + } + + // We have finished the first pass, keep re-connecting failing SubConns. + switch newState.ConnectivityState { + case connectivity.TransientFailure: + b.numTF = (b.numTF + 1) % b.subConns.Len() + sd.lastErr = newState.ConnectionError + if b.numTF%b.subConns.Len() == 0 { + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.TransientFailure, + Picker: &picker{err: newState.ConnectionError}, + }) + } + // We don't need to request re-resolution since the SubConn already + // does that before reporting TRANSIENT_FAILURE. + // TODO: #7534 - Move re-resolution requests from SubConn into + // pick_first. + case connectivity.Idle: + sd.subConn.Connect() + } +} + +func (b *pickfirstBalancer) endFirstPassLocked(lastErr error) { + b.firstPass = false + b.numTF = 0 + b.state = connectivity.TransientFailure + + b.cc.UpdateState(balancer.State{ + ConnectivityState: connectivity.TransientFailure, + Picker: &picker{err: lastErr}, + }) + // Start re-connecting all the SubConns that are already in IDLE. + for _, v := range b.subConns.Values() { + sd := v.(*scData) + if sd.state == connectivity.Idle { + sd.subConn.Connect() + } + } +} + +type picker struct { + result balancer.PickResult + err error +} + +func (p *picker) Pick(balancer.PickInfo) (balancer.PickResult, error) { + return p.result, p.err +} + +// idlePicker is used when the SubConn is IDLE and kicks the SubConn into +// CONNECTING when Pick is called. +type idlePicker struct { + exitIdle func() +} + +func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { + i.exitIdle() + return balancer.PickResult{}, balancer.ErrNoSubConnAvailable +} + +// addressList manages sequentially iterating over addresses present in a list +// of endpoints. It provides a 1 dimensional view of the addresses present in +// the endpoints. +// This type is not safe for concurrent access. +type addressList struct { + addresses []resolver.Address + idx int +} + +func (al *addressList) isValid() bool { + return al.idx < len(al.addresses) +} + +func (al *addressList) size() int { + return len(al.addresses) +} + +// increment moves to the next index in the address list. +// This method returns false if it went off the list, true otherwise. +func (al *addressList) increment() bool { + if !al.isValid() { + return false + } + al.idx++ + return al.idx < len(al.addresses) +} + +// currentAddress returns the current address pointed to in the addressList. +// If the list is in an invalid state, it returns an empty address instead. +func (al *addressList) currentAddress() resolver.Address { + if !al.isValid() { + return resolver.Address{} + } + return al.addresses[al.idx] +} + +// first returns the first address in the list. If the list is empty, it returns +// an empty address instead. +func (al *addressList) first() resolver.Address { + if len(al.addresses) == 0 { + return resolver.Address{} + } + return al.addresses[0] +} + +func (al *addressList) reset() { + al.idx = 0 +} + +func (al *addressList) updateAddrs(addrs []resolver.Address) { + al.addresses = addrs + al.reset() +} + +// seekTo returns false if the needle was not found and the current index was +// left unchanged. +func (al *addressList) seekTo(needle resolver.Address) bool { + for ai, addr := range al.addresses { + if !equalAddressIgnoringBalAttributes(&addr, &needle) { + continue + } + al.idx = ai + return true + } + return false +} + +// equalAddressIgnoringBalAttributes returns true is a and b are considered +// equal. This is different from the Equal method on the resolver.Address type +// which considers all fields to determine equality. Here, we only consider +// fields that are meaningful to the SubConn. +func equalAddressIgnoringBalAttributes(a, b *resolver.Address) bool { + return a.Addr == b.Addr && a.ServerName == b.ServerName && + a.Attributes.Equal(b.Attributes) && + a.Metadata == b.Metadata +} diff --git a/go-controller/vendor/google.golang.org/grpc/balancer_wrapper.go b/go-controller/vendor/google.golang.org/grpc/balancer_wrapper.go index 4161fdf47a..2a4f2878ae 100644 --- a/go-controller/vendor/google.golang.org/grpc/balancer_wrapper.go +++ b/go-controller/vendor/google.golang.org/grpc/balancer_wrapper.go @@ -24,13 +24,18 @@ import ( "sync" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/codes" "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/internal" "google.golang.org/grpc/internal/balancer/gracefulswitch" "google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/resolver" + "google.golang.org/grpc/status" ) +var setConnectedAddress = internal.SetConnectedAddress.(func(*balancer.SubConnState, resolver.Address)) + // ccBalancerWrapper sits between the ClientConn and the Balancer. // // ccBalancerWrapper implements methods corresponding to the ones on the @@ -79,6 +84,7 @@ func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper { CustomUserAgent: cc.dopts.copts.UserAgent, ChannelzParent: cc.channelz, Target: cc.parsedTarget, + MetricsRecorder: cc.metricsRecorderList, }, serializer: grpcsync.NewCallbackSerializer(ctx), serializerCancel: cancel, @@ -92,7 +98,7 @@ func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper { // it is safe to call into the balancer here. func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error { errCh := make(chan error) - ok := ccb.serializer.Schedule(func(ctx context.Context) { + uccs := func(ctx context.Context) { defer close(errCh) if ctx.Err() != nil || ccb.balancer == nil { return @@ -107,17 +113,23 @@ func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnStat logger.Infof("error from balancer.UpdateClientConnState: %v", err) } errCh <- err - }) - if !ok { - return nil } + onFailure := func() { close(errCh) } + + // UpdateClientConnState can race with Close, and when the latter wins, the + // serializer is closed, and the attempt to schedule the callback will fail. + // It is acceptable to ignore this failure. But since we want to handle the + // state update in a blocking fashion (when we successfully schedule the + // callback), we have to use the ScheduleOr method and not the MaybeSchedule + // method on the serializer. + ccb.serializer.ScheduleOr(uccs, onFailure) return <-errCh } // resolverError is invoked by grpc to push a resolver error to the underlying // balancer. The call to the balancer is executed from the serializer. func (ccb *ccBalancerWrapper) resolverError(err error) { - ccb.serializer.Schedule(func(ctx context.Context) { + ccb.serializer.TrySchedule(func(ctx context.Context) { if ctx.Err() != nil || ccb.balancer == nil { return } @@ -133,7 +145,7 @@ func (ccb *ccBalancerWrapper) close() { ccb.closed = true ccb.mu.Unlock() channelz.Info(logger, ccb.cc.channelz, "ccBalancerWrapper: closing") - ccb.serializer.Schedule(func(context.Context) { + ccb.serializer.TrySchedule(func(context.Context) { if ccb.balancer == nil { return } @@ -145,7 +157,7 @@ func (ccb *ccBalancerWrapper) close() { // exitIdle invokes the balancer's exitIdle method in the serializer. func (ccb *ccBalancerWrapper) exitIdle() { - ccb.serializer.Schedule(func(ctx context.Context) { + ccb.serializer.TrySchedule(func(ctx context.Context) { if ctx.Err() != nil || ccb.balancer == nil { return } @@ -182,7 +194,7 @@ func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer return acbw, nil } -func (ccb *ccBalancerWrapper) RemoveSubConn(sc balancer.SubConn) { +func (ccb *ccBalancerWrapper) RemoveSubConn(balancer.SubConn) { // The graceful switch balancer will never call this. logger.Errorf("ccb RemoveSubConn(%v) called unexpectedly, sc") } @@ -246,21 +258,28 @@ type acBalancerWrapper struct { ccb *ccBalancerWrapper // read-only stateListener func(balancer.SubConnState) - mu sync.Mutex - producers map[balancer.ProducerBuilder]*refCountedProducer + producersMu sync.Mutex + producers map[balancer.ProducerBuilder]*refCountedProducer } // updateState is invoked by grpc to push a subConn state update to the // underlying balancer. -func (acbw *acBalancerWrapper) updateState(s connectivity.State, err error) { - acbw.ccb.serializer.Schedule(func(ctx context.Context) { +func (acbw *acBalancerWrapper) updateState(s connectivity.State, curAddr resolver.Address, err error) { + acbw.ccb.serializer.TrySchedule(func(ctx context.Context) { if ctx.Err() != nil || acbw.ccb.balancer == nil { return } + // Invalidate all producers on any state change. + acbw.closeProducers() + // Even though it is optional for balancers, gracefulswitch ensures // opts.StateListener is set, so this cannot ever be nil. // TODO: delete this comment when UpdateSubConnState is removed. - acbw.stateListener(balancer.SubConnState{ConnectivityState: s, ConnectionError: err}) + scs := balancer.SubConnState{ConnectivityState: s, ConnectionError: err} + if s == connectivity.Ready { + setConnectedAddress(&scs, curAddr) + } + acbw.stateListener(scs) }) } @@ -277,6 +296,7 @@ func (acbw *acBalancerWrapper) Connect() { } func (acbw *acBalancerWrapper) Shutdown() { + acbw.closeProducers() acbw.ccb.cc.removeAddrConn(acbw.ac, errConnDrain) } @@ -284,9 +304,10 @@ func (acbw *acBalancerWrapper) Shutdown() { // ready, blocks until it is or ctx expires. Returns an error when the context // expires or the addrConn is shut down. func (acbw *acBalancerWrapper) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) { - transport, err := acbw.ac.getTransport(ctx) - if err != nil { - return nil, err + transport := acbw.ac.getReadyTransport() + if transport == nil { + return nil, status.Errorf(codes.Unavailable, "SubConn state is not Ready") + } return newNonRetryClientStream(ctx, desc, method, transport, acbw.ac, opts...) } @@ -311,15 +332,15 @@ type refCountedProducer struct { } func (acbw *acBalancerWrapper) GetOrBuildProducer(pb balancer.ProducerBuilder) (balancer.Producer, func()) { - acbw.mu.Lock() - defer acbw.mu.Unlock() + acbw.producersMu.Lock() + defer acbw.producersMu.Unlock() // Look up existing producer from this builder. pData := acbw.producers[pb] if pData == nil { // Not found; create a new one and add it to the producers map. - p, close := pb.Build(acbw) - pData = &refCountedProducer{producer: p, close: close} + p, closeFn := pb.Build(acbw) + pData = &refCountedProducer{producer: p, close: closeFn} acbw.producers[pb] = pData } // Account for this new reference. @@ -329,13 +350,26 @@ func (acbw *acBalancerWrapper) GetOrBuildProducer(pb balancer.ProducerBuilder) ( // and delete the refCountedProducer from the map if the total reference // count goes to zero. unref := func() { - acbw.mu.Lock() + acbw.producersMu.Lock() + // If closeProducers has already closed this producer instance, refs is + // set to 0, so the check after decrementing will never pass, and the + // producer will not be double-closed. pData.refs-- if pData.refs == 0 { defer pData.close() // Run outside the acbw mutex delete(acbw.producers, pb) } - acbw.mu.Unlock() + acbw.producersMu.Unlock() } return pData.producer, grpcsync.OnceFunc(unref) } + +func (acbw *acBalancerWrapper) closeProducers() { + acbw.producersMu.Lock() + defer acbw.producersMu.Unlock() + for pb, pData := range acbw.producers { + pData.refs = 0 + pData.close() + delete(acbw.producers, pb) + } +} diff --git a/go-controller/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go b/go-controller/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go index 63c639e4fe..55bffaa77e 100644 --- a/go-controller/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go +++ b/go-controller/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go @@ -18,8 +18,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.2 +// protoc-gen-go v1.34.2 +// protoc v5.27.1 // source: grpc/binlog/v1/binarylog.proto package grpc_binarylog_v1 @@ -1015,7 +1015,7 @@ func file_grpc_binlog_v1_binarylog_proto_rawDescGZIP() []byte { var file_grpc_binlog_v1_binarylog_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_grpc_binlog_v1_binarylog_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_grpc_binlog_v1_binarylog_proto_goTypes = []interface{}{ +var file_grpc_binlog_v1_binarylog_proto_goTypes = []any{ (GrpcLogEntry_EventType)(0), // 0: grpc.binarylog.v1.GrpcLogEntry.EventType (GrpcLogEntry_Logger)(0), // 1: grpc.binarylog.v1.GrpcLogEntry.Logger (Address_Type)(0), // 2: grpc.binarylog.v1.Address.Type @@ -1058,7 +1058,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_grpc_binlog_v1_binarylog_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*GrpcLogEntry); i { case 0: return &v.state @@ -1070,7 +1070,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*ClientHeader); i { case 0: return &v.state @@ -1082,7 +1082,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*ServerHeader); i { case 0: return &v.state @@ -1094,7 +1094,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Trailer); i { case 0: return &v.state @@ -1106,7 +1106,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Message); i { case 0: return &v.state @@ -1118,7 +1118,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*Metadata); i { case 0: return &v.state @@ -1130,7 +1130,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*MetadataEntry); i { case 0: return &v.state @@ -1142,7 +1142,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { return nil } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_grpc_binlog_v1_binarylog_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*Address); i { case 0: return &v.state @@ -1155,7 +1155,7 @@ func file_grpc_binlog_v1_binarylog_proto_init() { } } } - file_grpc_binlog_v1_binarylog_proto_msgTypes[0].OneofWrappers = []interface{}{ + file_grpc_binlog_v1_binarylog_proto_msgTypes[0].OneofWrappers = []any{ (*GrpcLogEntry_ClientHeader)(nil), (*GrpcLogEntry_ServerHeader)(nil), (*GrpcLogEntry_Message)(nil), diff --git a/go-controller/vendor/google.golang.org/grpc/clientconn.go b/go-controller/vendor/google.golang.org/grpc/clientconn.go index 423be7b43b..19763f8edd 100644 --- a/go-controller/vendor/google.golang.org/grpc/clientconn.go +++ b/go-controller/vendor/google.golang.org/grpc/clientconn.go @@ -24,6 +24,7 @@ import ( "fmt" "math" "net/url" + "slices" "strings" "sync" "sync/atomic" @@ -39,6 +40,7 @@ import ( "google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/idle" iresolver "google.golang.org/grpc/internal/resolver" + "google.golang.org/grpc/internal/stats" "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/resolver" @@ -194,8 +196,11 @@ func NewClient(target string, opts ...DialOption) (conn *ClientConn, err error) cc.csMgr = newConnectivityStateManager(cc.ctx, cc.channelz) cc.pickerWrapper = newPickerWrapper(cc.dopts.copts.StatsHandlers) + cc.metricsRecorderList = stats.NewMetricsRecorderList(cc.dopts.copts.StatsHandlers) + cc.initIdleStateLocked() // Safe to call without the lock, since nothing else has a reference to cc. cc.idlenessMgr = idle.NewManager((*idler)(cc), cc.dopts.idleTimeout) + return cc, nil } @@ -590,13 +595,14 @@ type ClientConn struct { cancel context.CancelFunc // Cancelled on close. // The following are initialized at dial time, and are read-only after that. - target string // User's dial target. - parsedTarget resolver.Target // See initParsedTargetAndResolverBuilder(). - authority string // See initAuthority(). - dopts dialOptions // Default and user specified dial options. - channelz *channelz.Channel // Channelz object. - resolverBuilder resolver.Builder // See initParsedTargetAndResolverBuilder(). - idlenessMgr *idle.Manager + target string // User's dial target. + parsedTarget resolver.Target // See initParsedTargetAndResolverBuilder(). + authority string // See initAuthority(). + dopts dialOptions // Default and user specified dial options. + channelz *channelz.Channel // Channelz object. + resolverBuilder resolver.Builder // See initParsedTargetAndResolverBuilder(). + idlenessMgr *idle.Manager + metricsRecorderList *stats.MetricsRecorderList // The following provide their own synchronization, and therefore don't // require cc.mu to be held to access them. @@ -626,11 +632,6 @@ type ClientConn struct { // WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or // ctx expires. A true value is returned in former case and false in latter. -// -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a -// later release. func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connectivity.State) bool { ch := cc.csMgr.getNotifyChan() if cc.csMgr.getState() != sourceState { @@ -645,11 +646,6 @@ func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connec } // GetState returns the connectivity.State of ClientConn. -// -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a later -// release. func (cc *ClientConn) GetState() connectivity.State { return cc.csMgr.getState() } @@ -812,17 +808,11 @@ func (cc *ClientConn) applyFailingLBLocked(sc *serviceconfig.ParseResult) { cc.csMgr.updateState(connectivity.TransientFailure) } -// Makes a copy of the input addresses slice and clears out the balancer -// attributes field. Addresses are passed during subconn creation and address -// update operations. In both cases, we will clear the balancer attributes by -// calling this function, and therefore we will be able to use the Equal method -// provided by the resolver.Address type for comparison. -func copyAddressesWithoutBalancerAttributes(in []resolver.Address) []resolver.Address { +// Makes a copy of the input addresses slice. Addresses are passed during +// subconn creation and address update operations. +func copyAddresses(in []resolver.Address) []resolver.Address { out := make([]resolver.Address, len(in)) - for i := range in { - out[i] = in[i] - out[i].BalancerAttributes = nil - } + copy(out, in) return out } @@ -837,12 +827,11 @@ func (cc *ClientConn) newAddrConnLocked(addrs []resolver.Address, opts balancer. ac := &addrConn{ state: connectivity.Idle, cc: cc, - addrs: copyAddressesWithoutBalancerAttributes(addrs), + addrs: copyAddresses(addrs), scopts: opts, dopts: cc.dopts, channelz: channelz.RegisterSubChannel(cc.channelz, ""), resetBackoff: make(chan struct{}), - stateChan: make(chan struct{}), } ac.ctx, ac.cancel = context.WithCancel(cc.ctx) // Start with our address set to the first address; this may be updated if @@ -918,28 +907,29 @@ func (ac *addrConn) connect() error { ac.mu.Unlock() return nil } - ac.mu.Unlock() - ac.resetTransport() + ac.resetTransportAndUnlock() return nil } -func equalAddresses(a, b []resolver.Address) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if !v.Equal(b[i]) { - return false - } - } - return true +// equalAddressIgnoringBalAttributes returns true is a and b are considered equal. +// This is different from the Equal method on the resolver.Address type which +// considers all fields to determine equality. Here, we only consider fields +// that are meaningful to the subConn. +func equalAddressIgnoringBalAttributes(a, b *resolver.Address) bool { + return a.Addr == b.Addr && a.ServerName == b.ServerName && + a.Attributes.Equal(b.Attributes) && + a.Metadata == b.Metadata +} + +func equalAddressesIgnoringBalAttributes(a, b []resolver.Address) bool { + return slices.EqualFunc(a, b, func(a, b resolver.Address) bool { return equalAddressIgnoringBalAttributes(&a, &b) }) } // updateAddrs updates ac.addrs with the new addresses list and handles active // connections or connection attempts. func (ac *addrConn) updateAddrs(addrs []resolver.Address) { - addrs = copyAddressesWithoutBalancerAttributes(addrs) + addrs = copyAddresses(addrs) limit := len(addrs) if limit > 5 { limit = 5 @@ -947,7 +937,7 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) { channelz.Infof(logger, ac.channelz, "addrConn: updateAddrs addrs (%d of %d): %v", limit, len(addrs), addrs[:limit]) ac.mu.Lock() - if equalAddresses(ac.addrs, addrs) { + if equalAddressesIgnoringBalAttributes(ac.addrs, addrs) { ac.mu.Unlock() return } @@ -966,7 +956,7 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) { // Try to find the connected address. for _, a := range addrs { a.ServerName = ac.cc.getServerName(a) - if a.Equal(ac.curAddr) { + if equalAddressIgnoringBalAttributes(&a, &ac.curAddr) { // We are connected to a valid address, so do nothing but // update the addresses. ac.mu.Unlock() @@ -992,11 +982,9 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) { ac.updateConnectivityState(connectivity.Idle, nil) } - ac.mu.Unlock() - // Since we were connecting/connected, we should start a new connection // attempt. - go ac.resetTransport() + go ac.resetTransportAndUnlock() } // getServerName determines the serverName to be used in the connection @@ -1152,10 +1140,15 @@ func (cc *ClientConn) Close() error { <-cc.resolverWrapper.serializer.Done() <-cc.balancerWrapper.serializer.Done() - + var wg sync.WaitGroup for ac := range conns { - ac.tearDown(ErrClientConnClosing) + wg.Add(1) + go func(ac *addrConn) { + defer wg.Done() + ac.tearDown(ErrClientConnClosing) + }(ac) } + wg.Wait() cc.addTraceEvent("deleted") // TraceEvent needs to be called before RemoveEntry, as TraceEvent may add // trace reference to the entity being deleted, and thus prevent it from being @@ -1190,8 +1183,7 @@ type addrConn struct { addrs []resolver.Address // All addresses that the resolver resolved to. // Use updateConnectivityState for updating addrConn's connectivity state. - state connectivity.State - stateChan chan struct{} // closed and recreated on every state change. + state connectivity.State backoffIdx int // Needs to be stateful for resetConnectBackoff. resetBackoff chan struct{} @@ -1204,9 +1196,6 @@ func (ac *addrConn) updateConnectivityState(s connectivity.State, lastErr error) if ac.state == s { return } - // When changing states, reset the state change channel. - close(ac.stateChan) - ac.stateChan = make(chan struct{}) ac.state = s ac.channelz.ChannelMetrics.State.Store(&s) if lastErr == nil { @@ -1214,7 +1203,7 @@ func (ac *addrConn) updateConnectivityState(s connectivity.State, lastErr error) } else { channelz.Infof(logger, ac.channelz, "Subchannel Connectivity change to %v, last error: %s", s, lastErr) } - ac.acbw.updateState(s, lastErr) + ac.acbw.updateState(s, ac.curAddr, lastErr) } // adjustParams updates parameters used to create transports upon @@ -1231,8 +1220,10 @@ func (ac *addrConn) adjustParams(r transport.GoAwayReason) { } } -func (ac *addrConn) resetTransport() { - ac.mu.Lock() +// resetTransportAndUnlock unconditionally connects the addrConn. +// +// ac.mu must be held by the caller, and this function will guarantee it is released. +func (ac *addrConn) resetTransportAndUnlock() { acCtx := ac.ctx if acCtx.Err() != nil { ac.mu.Unlock() @@ -1263,6 +1254,8 @@ func (ac *addrConn) resetTransport() { ac.mu.Unlock() if err := ac.tryAllAddrs(acCtx, addrs, connectDeadline); err != nil { + // TODO: #7534 - Move re-resolution requests into the pick_first LB policy + // to ensure one resolution request per pass instead of per subconn failure. ac.cc.resolveNow(resolver.ResolveNowOptions{}) ac.mu.Lock() if acCtx.Err() != nil { @@ -1304,7 +1297,7 @@ func (ac *addrConn) resetTransport() { ac.mu.Unlock() } -// tryAllAddrs tries to creates a connection to the addresses, and stop when at +// tryAllAddrs tries to create a connection to the addresses, and stop when at // the first successful one. It returns an error if no address was successfully // connected, or updates ac appropriately with the new transport. func (ac *addrConn) tryAllAddrs(ctx context.Context, addrs []resolver.Address, connectDeadline time.Time) error { @@ -1516,29 +1509,6 @@ func (ac *addrConn) getReadyTransport() transport.ClientTransport { return nil } -// getTransport waits until the addrconn is ready and returns the transport. -// If the context expires first, returns an appropriate status. If the -// addrConn is stopped first, returns an Unavailable status error. -func (ac *addrConn) getTransport(ctx context.Context) (transport.ClientTransport, error) { - for ctx.Err() == nil { - ac.mu.Lock() - t, state, sc := ac.transport, ac.state, ac.stateChan - ac.mu.Unlock() - if state == connectivity.Ready { - return t, nil - } - if state == connectivity.Shutdown { - return nil, status.Errorf(codes.Unavailable, "SubConn shutting down") - } - - select { - case <-ctx.Done(): - case <-sc: - } - } - return nil, status.FromContextError(ctx.Err()).Err() -} - // tearDown starts to tear down the addrConn. // // Note that tearDown doesn't remove ac from ac.cc.conns, so the addrConn struct @@ -1585,7 +1555,7 @@ func (ac *addrConn) tearDown(err error) { } else { // Hard close the transport when the channel is entering idle or is // being shutdown. In the case where the channel is being shutdown, - // closing of transports is also taken care of by cancelation of cc.ctx. + // closing of transports is also taken care of by cancellation of cc.ctx. // But in the case where the channel is entering idle, we need to // explicitly close the transports here. Instead of distinguishing // between these two cases, it is simpler to close the transport diff --git a/go-controller/vendor/google.golang.org/grpc/codec.go b/go-controller/vendor/google.golang.org/grpc/codec.go index 411e3dfd47..e840858b77 100644 --- a/go-controller/vendor/google.golang.org/grpc/codec.go +++ b/go-controller/vendor/google.golang.org/grpc/codec.go @@ -21,18 +21,73 @@ package grpc import ( "google.golang.org/grpc/encoding" _ "google.golang.org/grpc/encoding/proto" // to register the Codec for "proto" + "google.golang.org/grpc/mem" ) -// baseCodec contains the functionality of both Codec and encoding.Codec, but -// omits the name/string, which vary between the two and are not needed for -// anything besides the registry in the encoding package. +// baseCodec captures the new encoding.CodecV2 interface without the Name +// function, allowing it to be implemented by older Codec and encoding.Codec +// implementations. The omitted Name function is only needed for the register in +// the encoding package and is not part of the core functionality. type baseCodec interface { - Marshal(v any) ([]byte, error) - Unmarshal(data []byte, v any) error + Marshal(v any) (mem.BufferSlice, error) + Unmarshal(data mem.BufferSlice, v any) error +} + +// getCodec returns an encoding.CodecV2 for the codec of the given name (if +// registered). Initially checks the V2 registry with encoding.GetCodecV2 and +// returns the V2 codec if it is registered. Otherwise, it checks the V1 registry +// with encoding.GetCodec and if it is registered wraps it with newCodecV1Bridge +// to turn it into an encoding.CodecV2. Returns nil otherwise. +func getCodec(name string) encoding.CodecV2 { + if codecV1 := encoding.GetCodec(name); codecV1 != nil { + return newCodecV1Bridge(codecV1) + } + + return encoding.GetCodecV2(name) +} + +func newCodecV0Bridge(c Codec) baseCodec { + return codecV0Bridge{codec: c} +} + +func newCodecV1Bridge(c encoding.Codec) encoding.CodecV2 { + return codecV1Bridge{ + codecV0Bridge: codecV0Bridge{codec: c}, + name: c.Name(), + } +} + +var _ baseCodec = codecV0Bridge{} + +type codecV0Bridge struct { + codec interface { + Marshal(v any) ([]byte, error) + Unmarshal(data []byte, v any) error + } +} + +func (c codecV0Bridge) Marshal(v any) (mem.BufferSlice, error) { + data, err := c.codec.Marshal(v) + if err != nil { + return nil, err + } + return mem.BufferSlice{mem.NewBuffer(&data, nil)}, nil +} + +func (c codecV0Bridge) Unmarshal(data mem.BufferSlice, v any) (err error) { + return c.codec.Unmarshal(data.Materialize(), v) } -var _ baseCodec = Codec(nil) -var _ baseCodec = encoding.Codec(nil) +var _ encoding.CodecV2 = codecV1Bridge{} + +type codecV1Bridge struct { + codecV0Bridge + name string +} + +func (c codecV1Bridge) Name() string { + return c.name +} // Codec defines the interface gRPC uses to encode and decode messages. // Note that implementations of this interface must be thread safe; diff --git a/go-controller/vendor/google.golang.org/grpc/credentials/insecure/insecure.go b/go-controller/vendor/google.golang.org/grpc/credentials/insecure/insecure.go index 82bee1443b..4c805c6446 100644 --- a/go-controller/vendor/google.golang.org/grpc/credentials/insecure/insecure.go +++ b/go-controller/vendor/google.golang.org/grpc/credentials/insecure/insecure.go @@ -40,7 +40,7 @@ func NewCredentials() credentials.TransportCredentials { // NoSecurity. type insecureTC struct{} -func (insecureTC) ClientHandshake(ctx context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { +func (insecureTC) ClientHandshake(_ context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { return conn, info{credentials.CommonAuthInfo{SecurityLevel: credentials.NoSecurity}}, nil } diff --git a/go-controller/vendor/google.golang.org/grpc/credentials/tls.go b/go-controller/vendor/google.golang.org/grpc/credentials/tls.go index 4114358545..e163a473df 100644 --- a/go-controller/vendor/google.golang.org/grpc/credentials/tls.go +++ b/go-controller/vendor/google.golang.org/grpc/credentials/tls.go @@ -200,25 +200,40 @@ var tls12ForbiddenCipherSuites = map[uint16]struct{}{ // NewTLS uses c to construct a TransportCredentials based on TLS. func NewTLS(c *tls.Config) TransportCredentials { - tc := &tlsCreds{credinternal.CloneTLSConfig(c)} - tc.config.NextProtos = credinternal.AppendH2ToNextProtos(tc.config.NextProtos) + config := applyDefaults(c) + if config.GetConfigForClient != nil { + oldFn := config.GetConfigForClient + config.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) { + cfgForClient, err := oldFn(hello) + if err != nil || cfgForClient == nil { + return cfgForClient, err + } + return applyDefaults(cfgForClient), nil + } + } + return &tlsCreds{config: config} +} + +func applyDefaults(c *tls.Config) *tls.Config { + config := credinternal.CloneTLSConfig(c) + config.NextProtos = credinternal.AppendH2ToNextProtos(config.NextProtos) // If the user did not configure a MinVersion and did not configure a // MaxVersion < 1.2, use MinVersion=1.2, which is required by // https://datatracker.ietf.org/doc/html/rfc7540#section-9.2 - if tc.config.MinVersion == 0 && (tc.config.MaxVersion == 0 || tc.config.MaxVersion >= tls.VersionTLS12) { - tc.config.MinVersion = tls.VersionTLS12 + if config.MinVersion == 0 && (config.MaxVersion == 0 || config.MaxVersion >= tls.VersionTLS12) { + config.MinVersion = tls.VersionTLS12 } // If the user did not configure CipherSuites, use all "secure" cipher // suites reported by the TLS package, but remove some explicitly forbidden // by https://datatracker.ietf.org/doc/html/rfc7540#appendix-A - if tc.config.CipherSuites == nil { + if config.CipherSuites == nil { for _, cs := range tls.CipherSuites() { if _, ok := tls12ForbiddenCipherSuites[cs.ID]; !ok { - tc.config.CipherSuites = append(tc.config.CipherSuites, cs.ID) + config.CipherSuites = append(config.CipherSuites, cs.ID) } } } - return tc + return config } // NewClientTLSFromCert constructs TLS credentials from the provided root diff --git a/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/pemfile/builder.go b/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/pemfile/builder.go index 8c15baeb59..ad4207892b 100644 --- a/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/pemfile/builder.go +++ b/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/pemfile/builder.go @@ -29,6 +29,7 @@ import ( ) const ( + // PluginName is the name of the PEM file watcher plugin. PluginName = "file_watcher" defaultRefreshInterval = 10 * time.Minute ) diff --git a/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/store.go b/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/store.go index 87528b4e23..a4b99e3d4a 100644 --- a/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/store.go +++ b/go-controller/vendor/google.golang.org/grpc/credentials/tls/certprovider/store.go @@ -58,7 +58,7 @@ type wrappedProvider struct { // closedProvider always returns errProviderClosed error. type closedProvider struct{} -func (c closedProvider) KeyMaterial(ctx context.Context) (*KeyMaterial, error) { +func (c closedProvider) KeyMaterial(context.Context) (*KeyMaterial, error) { return nil, errProviderClosed } diff --git a/go-controller/vendor/google.golang.org/grpc/dialoptions.go b/go-controller/vendor/google.golang.org/grpc/dialoptions.go index f5453d48a5..518692c3af 100644 --- a/go-controller/vendor/google.golang.org/grpc/dialoptions.go +++ b/go-controller/vendor/google.golang.org/grpc/dialoptions.go @@ -33,6 +33,7 @@ import ( "google.golang.org/grpc/internal/binarylog" "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/mem" "google.golang.org/grpc/resolver" "google.golang.org/grpc/stats" ) @@ -60,7 +61,7 @@ func init() { internal.WithBinaryLogger = withBinaryLogger internal.JoinDialOptions = newJoinDialOption internal.DisableGlobalDialOptions = newDisableGlobalDialOptions - internal.WithRecvBufferPool = withRecvBufferPool + internal.WithBufferPool = withBufferPool } // dialOptions configure a Dial call. dialOptions are set by the DialOption @@ -92,7 +93,6 @@ type dialOptions struct { defaultServiceConfigRawJSON *string resolvers []resolver.Builder idleTimeout time.Duration - recvBufferPool SharedBufferPool defaultScheme string maxCallAttempts int } @@ -436,7 +436,7 @@ func WithTimeout(d time.Duration) DialOption { // option to true from the Control field. For a concrete example of how to do // this, see internal.NetDialerWithTCPKeepalive(). // -// For more information, please see [issue 23459] in the Go github repo. +// For more information, please see [issue 23459] in the Go GitHub repo. // // [issue 23459]: https://github.com/golang/go/issues/23459 func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption { @@ -518,6 +518,8 @@ func WithUserAgent(s string) DialOption { // WithKeepaliveParams returns a DialOption that specifies keepalive parameters // for the client transport. +// +// Keepalive is disabled by default. func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption { if kp.Time < internal.KeepaliveMinPingTime { logger.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime) @@ -677,11 +679,11 @@ func defaultDialOptions() dialOptions { WriteBufferSize: defaultWriteBufSize, UseProxy: true, UserAgent: grpcUA, + BufferPool: mem.DefaultBufferPool(), }, bs: internalbackoff.DefaultExponential, healthCheckFunc: internal.HealthCheckFunc, idleTimeout: 30 * time.Minute, - recvBufferPool: nopBufferPool{}, defaultScheme: "dns", maxCallAttempts: defaultMaxCallAttempts, } @@ -758,25 +760,8 @@ func WithMaxCallAttempts(n int) DialOption { }) } -// WithRecvBufferPool returns a DialOption that configures the ClientConn -// to use the provided shared buffer pool for parsing incoming messages. Depending -// on the application's workload, this could result in reduced memory allocation. -// -// If you are unsure about how to implement a memory pool but want to utilize one, -// begin with grpc.NewSharedBufferPool. -// -// Note: The shared buffer pool feature will not be active if any of the following -// options are used: WithStatsHandler, EnableTracing, or binary logging. In such -// cases, the shared buffer pool will be ignored. -// -// Deprecated: use experimental.WithRecvBufferPool instead. Will be deleted in -// v1.60.0 or later. -func WithRecvBufferPool(bufferPool SharedBufferPool) DialOption { - return withRecvBufferPool(bufferPool) -} - -func withRecvBufferPool(bufferPool SharedBufferPool) DialOption { +func withBufferPool(bufferPool mem.BufferPool) DialOption { return newFuncDialOption(func(o *dialOptions) { - o.recvBufferPool = bufferPool + o.copts.BufferPool = bufferPool }) } diff --git a/go-controller/vendor/google.golang.org/grpc/doc.go b/go-controller/vendor/google.golang.org/grpc/doc.go index 0022859ad7..e7b532b6f8 100644 --- a/go-controller/vendor/google.golang.org/grpc/doc.go +++ b/go-controller/vendor/google.golang.org/grpc/doc.go @@ -16,7 +16,7 @@ * */ -//go:generate ./regenerate.sh +//go:generate ./scripts/regenerate.sh /* Package grpc implements an RPC system called gRPC. diff --git a/go-controller/vendor/google.golang.org/grpc/encoding/encoding.go b/go-controller/vendor/google.golang.org/grpc/encoding/encoding.go index 5ebf88d714..11d0ae142c 100644 --- a/go-controller/vendor/google.golang.org/grpc/encoding/encoding.go +++ b/go-controller/vendor/google.golang.org/grpc/encoding/encoding.go @@ -94,7 +94,7 @@ type Codec interface { Name() string } -var registeredCodecs = make(map[string]Codec) +var registeredCodecs = make(map[string]any) // RegisterCodec registers the provided Codec for use with all gRPC clients and // servers. @@ -126,5 +126,6 @@ func RegisterCodec(codec Codec) { // // The content-subtype is expected to be lowercase. func GetCodec(contentSubtype string) Codec { - return registeredCodecs[contentSubtype] + c, _ := registeredCodecs[contentSubtype].(Codec) + return c } diff --git a/go-controller/vendor/google.golang.org/grpc/encoding/encoding_v2.go b/go-controller/vendor/google.golang.org/grpc/encoding/encoding_v2.go new file mode 100644 index 0000000000..074c5e234a --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/encoding/encoding_v2.go @@ -0,0 +1,81 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 encoding + +import ( + "strings" + + "google.golang.org/grpc/mem" +) + +// CodecV2 defines the interface gRPC uses to encode and decode messages. Note +// that implementations of this interface must be thread safe; a CodecV2's +// methods can be called from concurrent goroutines. +type CodecV2 interface { + // Marshal returns the wire format of v. The buffers in the returned + // [mem.BufferSlice] must have at least one reference each, which will be freed + // by gRPC when they are no longer needed. + Marshal(v any) (out mem.BufferSlice, err error) + // Unmarshal parses the wire format into v. Note that data will be freed as soon + // as this function returns. If the codec wishes to guarantee access to the data + // after this function, it must take its own reference that it frees when it is + // no longer needed. + Unmarshal(data mem.BufferSlice, v any) error + // Name returns the name of the Codec implementation. The returned string + // will be used as part of content type in transmission. The result must be + // static; the result cannot change between calls. + Name() string +} + +// RegisterCodecV2 registers the provided CodecV2 for use with all gRPC clients and +// servers. +// +// The CodecV2 will be stored and looked up by result of its Name() method, which +// should match the content-subtype of the encoding handled by the CodecV2. This +// is case-insensitive, and is stored and looked up as lowercase. If the +// result of calling Name() is an empty string, RegisterCodecV2 will panic. See +// Content-Type on +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. +// +// If both a Codec and CodecV2 are registered with the same name, the CodecV2 +// will be used. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple Codecs are +// registered with the same name, the one registered last will take effect. +func RegisterCodecV2(codec CodecV2) { + if codec == nil { + panic("cannot register a nil CodecV2") + } + if codec.Name() == "" { + panic("cannot register CodecV2 with empty string result for Name()") + } + contentSubtype := strings.ToLower(codec.Name()) + registeredCodecs[contentSubtype] = codec +} + +// GetCodecV2 gets a registered CodecV2 by content-subtype, or nil if no CodecV2 is +// registered for the content-subtype. +// +// The content-subtype is expected to be lowercase. +func GetCodecV2(contentSubtype string) CodecV2 { + c, _ := registeredCodecs[contentSubtype].(CodecV2) + return c +} diff --git a/go-controller/vendor/google.golang.org/grpc/encoding/proto/proto.go b/go-controller/vendor/google.golang.org/grpc/encoding/proto/proto.go index 66d5cdf03e..ceec319dd2 100644 --- a/go-controller/vendor/google.golang.org/grpc/encoding/proto/proto.go +++ b/go-controller/vendor/google.golang.org/grpc/encoding/proto/proto.go @@ -1,6 +1,6 @@ /* * - * Copyright 2018 gRPC authors. + * Copyright 2024 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import ( "fmt" "google.golang.org/grpc/encoding" + "google.golang.org/grpc/mem" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/protoadapt" ) @@ -32,28 +33,51 @@ import ( const Name = "proto" func init() { - encoding.RegisterCodec(codec{}) + encoding.RegisterCodecV2(&codecV2{}) } -// codec is a Codec implementation with protobuf. It is the default codec for gRPC. -type codec struct{} +// codec is a CodecV2 implementation with protobuf. It is the default codec for +// gRPC. +type codecV2 struct{} -func (codec) Marshal(v any) ([]byte, error) { +func (c *codecV2) Marshal(v any) (data mem.BufferSlice, err error) { vv := messageV2Of(v) if vv == nil { - return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v) + return nil, fmt.Errorf("proto: failed to marshal, message is %T, want proto.Message", v) } - return proto.Marshal(vv) + size := proto.Size(vv) + if mem.IsBelowBufferPoolingThreshold(size) { + buf, err := proto.Marshal(vv) + if err != nil { + return nil, err + } + data = append(data, mem.SliceBuffer(buf)) + } else { + pool := mem.DefaultBufferPool() + buf := pool.Get(size) + if _, err := (proto.MarshalOptions{}).MarshalAppend((*buf)[:0], vv); err != nil { + pool.Put(buf) + return nil, err + } + data = append(data, mem.NewBuffer(buf, pool)) + } + + return data, nil } -func (codec) Unmarshal(data []byte, v any) error { +func (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) { vv := messageV2Of(v) if vv == nil { return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v) } - return proto.Unmarshal(data, vv) + buf := data.MaterializeToBuffer(mem.DefaultBufferPool()) + defer buf.Free() + // TODO: Upgrade proto.Unmarshal to support mem.BufferSlice. Right now, it's not + // really possible without a major overhaul of the proto package, but the + // vtprotobuf library may be able to support this. + return proto.Unmarshal(buf.ReadOnlyData(), vv) } func messageV2Of(v any) proto.Message { @@ -67,6 +91,6 @@ func messageV2Of(v any) proto.Message { return nil } -func (codec) Name() string { +func (c *codecV2) Name() string { return Name } diff --git a/go-controller/vendor/google.golang.org/grpc/experimental/stats/metricregistry.go b/go-controller/vendor/google.golang.org/grpc/experimental/stats/metricregistry.go new file mode 100644 index 0000000000..1d827dd5d9 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/experimental/stats/metricregistry.go @@ -0,0 +1,269 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 stats + +import ( + "maps" + + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/internal" +) + +func init() { + internal.SnapshotMetricRegistryForTesting = snapshotMetricsRegistryForTesting +} + +var logger = grpclog.Component("metrics-registry") + +// DefaultMetrics are the default metrics registered through global metrics +// registry. This is written to at initialization time only, and is read only +// after initialization. +var DefaultMetrics = NewMetrics() + +// MetricDescriptor is the data for a registered metric. +type MetricDescriptor struct { + // The name of this metric. This name must be unique across the whole binary + // (including any per call metrics). See + // https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md#metric-instrument-naming-conventions + // for metric naming conventions. + Name Metric + // The description of this metric. + Description string + // The unit (e.g. entries, seconds) of this metric. + Unit string + // The required label keys for this metric. These are intended to + // metrics emitted from a stats handler. + Labels []string + // The optional label keys for this metric. These are intended to attached + // to metrics emitted from a stats handler if configured. + OptionalLabels []string + // Whether this metric is on by default. + Default bool + // The type of metric. This is set by the metric registry, and not intended + // to be set by a component registering a metric. + Type MetricType + // Bounds are the bounds of this metric. This only applies to histogram + // metrics. If unset or set with length 0, stats handlers will fall back to + // default bounds. + Bounds []float64 +} + +// MetricType is the type of metric. +type MetricType int + +// Type of metric supported by this instrument registry. +const ( + MetricTypeIntCount MetricType = iota + MetricTypeFloatCount + MetricTypeIntHisto + MetricTypeFloatHisto + MetricTypeIntGauge +) + +// Int64CountHandle is a typed handle for a int count metric. This handle +// is passed at the recording point in order to know which metric to record +// on. +type Int64CountHandle MetricDescriptor + +// Descriptor returns the int64 count handle typecast to a pointer to a +// MetricDescriptor. +func (h *Int64CountHandle) Descriptor() *MetricDescriptor { + return (*MetricDescriptor)(h) +} + +// Record records the int64 count value on the metrics recorder provided. +func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) { + recorder.RecordInt64Count(h, incr, labels...) +} + +// Float64CountHandle is a typed handle for a float count metric. This handle is +// passed at the recording point in order to know which metric to record on. +type Float64CountHandle MetricDescriptor + +// Descriptor returns the float64 count handle typecast to a pointer to a +// MetricDescriptor. +func (h *Float64CountHandle) Descriptor() *MetricDescriptor { + return (*MetricDescriptor)(h) +} + +// Record records the float64 count value on the metrics recorder provided. +func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) { + recorder.RecordFloat64Count(h, incr, labels...) +} + +// Int64HistoHandle is a typed handle for an int histogram metric. This handle +// is passed at the recording point in order to know which metric to record on. +type Int64HistoHandle MetricDescriptor + +// Descriptor returns the int64 histo handle typecast to a pointer to a +// MetricDescriptor. +func (h *Int64HistoHandle) Descriptor() *MetricDescriptor { + return (*MetricDescriptor)(h) +} + +// Record records the int64 histo value on the metrics recorder provided. +func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) { + recorder.RecordInt64Histo(h, incr, labels...) +} + +// Float64HistoHandle is a typed handle for a float histogram metric. This +// handle is passed at the recording point in order to know which metric to +// record on. +type Float64HistoHandle MetricDescriptor + +// Descriptor returns the float64 histo handle typecast to a pointer to a +// MetricDescriptor. +func (h *Float64HistoHandle) Descriptor() *MetricDescriptor { + return (*MetricDescriptor)(h) +} + +// Record records the float64 histo value on the metrics recorder provided. +func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) { + recorder.RecordFloat64Histo(h, incr, labels...) +} + +// Int64GaugeHandle is a typed handle for an int gauge metric. This handle is +// passed at the recording point in order to know which metric to record on. +type Int64GaugeHandle MetricDescriptor + +// Descriptor returns the int64 gauge handle typecast to a pointer to a +// MetricDescriptor. +func (h *Int64GaugeHandle) Descriptor() *MetricDescriptor { + return (*MetricDescriptor)(h) +} + +// Record records the int64 histo value on the metrics recorder provided. +func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) { + recorder.RecordInt64Gauge(h, incr, labels...) +} + +// registeredMetrics are the registered metric descriptor names. +var registeredMetrics = make(map[Metric]bool) + +// metricsRegistry contains all of the registered metrics. +// +// This is written to only at init time, and read only after that. +var metricsRegistry = make(map[Metric]*MetricDescriptor) + +// DescriptorForMetric returns the MetricDescriptor from the global registry. +// +// Returns nil if MetricDescriptor not present. +func DescriptorForMetric(metric Metric) *MetricDescriptor { + return metricsRegistry[metric] +} + +func registerMetric(name Metric, def bool) { + if registeredMetrics[name] { + logger.Fatalf("metric %v already registered", name) + } + registeredMetrics[name] = true + if def { + DefaultMetrics = DefaultMetrics.Add(name) + } +} + +// RegisterInt64Count registers the metric description onto the global registry. +// It returns a typed handle to use to recording data. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple metrics are +// registered with the same name, this function will panic. +func RegisterInt64Count(descriptor MetricDescriptor) *Int64CountHandle { + registerMetric(descriptor.Name, descriptor.Default) + descriptor.Type = MetricTypeIntCount + descPtr := &descriptor + metricsRegistry[descriptor.Name] = descPtr + return (*Int64CountHandle)(descPtr) +} + +// RegisterFloat64Count registers the metric description onto the global +// registry. It returns a typed handle to use to recording data. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple metrics are +// registered with the same name, this function will panic. +func RegisterFloat64Count(descriptor MetricDescriptor) *Float64CountHandle { + registerMetric(descriptor.Name, descriptor.Default) + descriptor.Type = MetricTypeFloatCount + descPtr := &descriptor + metricsRegistry[descriptor.Name] = descPtr + return (*Float64CountHandle)(descPtr) +} + +// RegisterInt64Histo registers the metric description onto the global registry. +// It returns a typed handle to use to recording data. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple metrics are +// registered with the same name, this function will panic. +func RegisterInt64Histo(descriptor MetricDescriptor) *Int64HistoHandle { + registerMetric(descriptor.Name, descriptor.Default) + descriptor.Type = MetricTypeIntHisto + descPtr := &descriptor + metricsRegistry[descriptor.Name] = descPtr + return (*Int64HistoHandle)(descPtr) +} + +// RegisterFloat64Histo registers the metric description onto the global +// registry. It returns a typed handle to use to recording data. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple metrics are +// registered with the same name, this function will panic. +func RegisterFloat64Histo(descriptor MetricDescriptor) *Float64HistoHandle { + registerMetric(descriptor.Name, descriptor.Default) + descriptor.Type = MetricTypeFloatHisto + descPtr := &descriptor + metricsRegistry[descriptor.Name] = descPtr + return (*Float64HistoHandle)(descPtr) +} + +// RegisterInt64Gauge registers the metric description onto the global registry. +// It returns a typed handle to use to recording data. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple metrics are +// registered with the same name, this function will panic. +func RegisterInt64Gauge(descriptor MetricDescriptor) *Int64GaugeHandle { + registerMetric(descriptor.Name, descriptor.Default) + descriptor.Type = MetricTypeIntGauge + descPtr := &descriptor + metricsRegistry[descriptor.Name] = descPtr + return (*Int64GaugeHandle)(descPtr) +} + +// snapshotMetricsRegistryForTesting snapshots the global data of the metrics +// registry. Returns a cleanup function that sets the metrics registry to its +// original state. +func snapshotMetricsRegistryForTesting() func() { + oldDefaultMetrics := DefaultMetrics + oldRegisteredMetrics := registeredMetrics + oldMetricsRegistry := metricsRegistry + + registeredMetrics = make(map[Metric]bool) + metricsRegistry = make(map[Metric]*MetricDescriptor) + maps.Copy(registeredMetrics, registeredMetrics) + maps.Copy(metricsRegistry, metricsRegistry) + + return func() { + DefaultMetrics = oldDefaultMetrics + registeredMetrics = oldRegisteredMetrics + metricsRegistry = oldMetricsRegistry + } +} diff --git a/go-controller/vendor/google.golang.org/grpc/experimental/stats/metrics.go b/go-controller/vendor/google.golang.org/grpc/experimental/stats/metrics.go new file mode 100644 index 0000000000..3221f7a633 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/experimental/stats/metrics.go @@ -0,0 +1,114 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 stats contains experimental metrics/stats API's. +package stats + +import "maps" + +// MetricsRecorder records on metrics derived from metric registry. +type MetricsRecorder interface { + // RecordInt64Count records the measurement alongside labels on the int + // count associated with the provided handle. + RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string) + // RecordFloat64Count records the measurement alongside labels on the float + // count associated with the provided handle. + RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string) + // RecordInt64Histo records the measurement alongside labels on the int + // histo associated with the provided handle. + RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string) + // RecordFloat64Histo records the measurement alongside labels on the float + // histo associated with the provided handle. + RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string) + // RecordInt64Gauge records the measurement alongside labels on the int + // gauge associated with the provided handle. + RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) +} + +// Metric is an identifier for a metric. +type Metric string + +// Metrics is a set of metrics to record. Once created, Metrics is immutable, +// however Add and Remove can make copies with specific metrics added or +// removed, respectively. +// +// Do not construct directly; use NewMetrics instead. +type Metrics struct { + // metrics are the set of metrics to initialize. + metrics map[Metric]bool +} + +// NewMetrics returns a Metrics containing Metrics. +func NewMetrics(metrics ...Metric) *Metrics { + newMetrics := make(map[Metric]bool) + for _, metric := range metrics { + newMetrics[metric] = true + } + return &Metrics{ + metrics: newMetrics, + } +} + +// Metrics returns the metrics set. The returned map is read-only and must not +// be modified. +func (m *Metrics) Metrics() map[Metric]bool { + return m.metrics +} + +// Add adds the metrics to the metrics set and returns a new copy with the +// additional metrics. +func (m *Metrics) Add(metrics ...Metric) *Metrics { + newMetrics := make(map[Metric]bool) + for metric := range m.metrics { + newMetrics[metric] = true + } + + for _, metric := range metrics { + newMetrics[metric] = true + } + return &Metrics{ + metrics: newMetrics, + } +} + +// Join joins the metrics passed in with the metrics set, and returns a new copy +// with the merged metrics. +func (m *Metrics) Join(metrics *Metrics) *Metrics { + newMetrics := make(map[Metric]bool) + maps.Copy(newMetrics, m.metrics) + maps.Copy(newMetrics, metrics.metrics) + return &Metrics{ + metrics: newMetrics, + } +} + +// Remove removes the metrics from the metrics set and returns a new copy with +// the metrics removed. +func (m *Metrics) Remove(metrics ...Metric) *Metrics { + newMetrics := make(map[Metric]bool) + for metric := range m.metrics { + newMetrics[metric] = true + } + + for _, metric := range metrics { + delete(newMetrics, metric) + } + return &Metrics{ + metrics: newMetrics, + } +} diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/component.go b/go-controller/vendor/google.golang.org/grpc/grpclog/component.go index ac73c9ced2..f1ae080dcb 100644 --- a/go-controller/vendor/google.golang.org/grpc/grpclog/component.go +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/component.go @@ -20,8 +20,6 @@ package grpclog import ( "fmt" - - "google.golang.org/grpc/internal/grpclog" ) // componentData records the settings for a component. @@ -33,22 +31,22 @@ var cache = map[string]*componentData{} func (c *componentData) InfoDepth(depth int, args ...any) { args = append([]any{"[" + string(c.name) + "]"}, args...) - grpclog.InfoDepth(depth+1, args...) + InfoDepth(depth+1, args...) } func (c *componentData) WarningDepth(depth int, args ...any) { args = append([]any{"[" + string(c.name) + "]"}, args...) - grpclog.WarningDepth(depth+1, args...) + WarningDepth(depth+1, args...) } func (c *componentData) ErrorDepth(depth int, args ...any) { args = append([]any{"[" + string(c.name) + "]"}, args...) - grpclog.ErrorDepth(depth+1, args...) + ErrorDepth(depth+1, args...) } func (c *componentData) FatalDepth(depth int, args ...any) { args = append([]any{"[" + string(c.name) + "]"}, args...) - grpclog.FatalDepth(depth+1, args...) + FatalDepth(depth+1, args...) } func (c *componentData) Info(args ...any) { diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/grpclog.go b/go-controller/vendor/google.golang.org/grpc/grpclog/grpclog.go index 16928c9cb9..db320105e6 100644 --- a/go-controller/vendor/google.golang.org/grpc/grpclog/grpclog.go +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/grpclog.go @@ -18,18 +18,15 @@ // Package grpclog defines logging for grpc. // -// All logs in transport and grpclb packages only go to verbose level 2. -// All logs in other packages in grpc are logged in spite of the verbosity level. -// -// In the default logger, -// severity level can be set by environment variable GRPC_GO_LOG_SEVERITY_LEVEL, -// verbosity level can be set by GRPC_GO_LOG_VERBOSITY_LEVEL. -package grpclog // import "google.golang.org/grpc/grpclog" +// In the default logger, severity level can be set by environment variable +// GRPC_GO_LOG_SEVERITY_LEVEL, verbosity level can be set by +// GRPC_GO_LOG_VERBOSITY_LEVEL. +package grpclog import ( "os" - "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/grpclog/internal" ) func init() { @@ -38,58 +35,58 @@ func init() { // V reports whether verbosity level l is at least the requested verbose level. func V(l int) bool { - return grpclog.Logger.V(l) + return internal.LoggerV2Impl.V(l) } // Info logs to the INFO log. func Info(args ...any) { - grpclog.Logger.Info(args...) + internal.LoggerV2Impl.Info(args...) } // Infof logs to the INFO log. Arguments are handled in the manner of fmt.Printf. func Infof(format string, args ...any) { - grpclog.Logger.Infof(format, args...) + internal.LoggerV2Impl.Infof(format, args...) } // Infoln logs to the INFO log. Arguments are handled in the manner of fmt.Println. func Infoln(args ...any) { - grpclog.Logger.Infoln(args...) + internal.LoggerV2Impl.Infoln(args...) } // Warning logs to the WARNING log. func Warning(args ...any) { - grpclog.Logger.Warning(args...) + internal.LoggerV2Impl.Warning(args...) } // Warningf logs to the WARNING log. Arguments are handled in the manner of fmt.Printf. func Warningf(format string, args ...any) { - grpclog.Logger.Warningf(format, args...) + internal.LoggerV2Impl.Warningf(format, args...) } // Warningln logs to the WARNING log. Arguments are handled in the manner of fmt.Println. func Warningln(args ...any) { - grpclog.Logger.Warningln(args...) + internal.LoggerV2Impl.Warningln(args...) } // Error logs to the ERROR log. func Error(args ...any) { - grpclog.Logger.Error(args...) + internal.LoggerV2Impl.Error(args...) } // Errorf logs to the ERROR log. Arguments are handled in the manner of fmt.Printf. func Errorf(format string, args ...any) { - grpclog.Logger.Errorf(format, args...) + internal.LoggerV2Impl.Errorf(format, args...) } // Errorln logs to the ERROR log. Arguments are handled in the manner of fmt.Println. func Errorln(args ...any) { - grpclog.Logger.Errorln(args...) + internal.LoggerV2Impl.Errorln(args...) } // Fatal logs to the FATAL log. Arguments are handled in the manner of fmt.Print. // It calls os.Exit() with exit code 1. func Fatal(args ...any) { - grpclog.Logger.Fatal(args...) + internal.LoggerV2Impl.Fatal(args...) // Make sure fatal logs will exit. os.Exit(1) } @@ -97,15 +94,15 @@ func Fatal(args ...any) { // Fatalf logs to the FATAL log. Arguments are handled in the manner of fmt.Printf. // It calls os.Exit() with exit code 1. func Fatalf(format string, args ...any) { - grpclog.Logger.Fatalf(format, args...) + internal.LoggerV2Impl.Fatalf(format, args...) // Make sure fatal logs will exit. os.Exit(1) } // Fatalln logs to the FATAL log. Arguments are handled in the manner of fmt.Println. -// It calle os.Exit()) with exit code 1. +// It calls os.Exit() with exit code 1. func Fatalln(args ...any) { - grpclog.Logger.Fatalln(args...) + internal.LoggerV2Impl.Fatalln(args...) // Make sure fatal logs will exit. os.Exit(1) } @@ -114,19 +111,76 @@ func Fatalln(args ...any) { // // Deprecated: use Info. func Print(args ...any) { - grpclog.Logger.Info(args...) + internal.LoggerV2Impl.Info(args...) } // Printf prints to the logger. Arguments are handled in the manner of fmt.Printf. // // Deprecated: use Infof. func Printf(format string, args ...any) { - grpclog.Logger.Infof(format, args...) + internal.LoggerV2Impl.Infof(format, args...) } // Println prints to the logger. Arguments are handled in the manner of fmt.Println. // // Deprecated: use Infoln. func Println(args ...any) { - grpclog.Logger.Infoln(args...) + internal.LoggerV2Impl.Infoln(args...) +} + +// InfoDepth logs to the INFO log at the specified depth. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func InfoDepth(depth int, args ...any) { + if internal.DepthLoggerV2Impl != nil { + internal.DepthLoggerV2Impl.InfoDepth(depth, args...) + } else { + internal.LoggerV2Impl.Infoln(args...) + } +} + +// WarningDepth logs to the WARNING log at the specified depth. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func WarningDepth(depth int, args ...any) { + if internal.DepthLoggerV2Impl != nil { + internal.DepthLoggerV2Impl.WarningDepth(depth, args...) + } else { + internal.LoggerV2Impl.Warningln(args...) + } +} + +// ErrorDepth logs to the ERROR log at the specified depth. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func ErrorDepth(depth int, args ...any) { + if internal.DepthLoggerV2Impl != nil { + internal.DepthLoggerV2Impl.ErrorDepth(depth, args...) + } else { + internal.LoggerV2Impl.Errorln(args...) + } +} + +// FatalDepth logs to the FATAL log at the specified depth. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func FatalDepth(depth int, args ...any) { + if internal.DepthLoggerV2Impl != nil { + internal.DepthLoggerV2Impl.FatalDepth(depth, args...) + } else { + internal.LoggerV2Impl.Fatalln(args...) + } + os.Exit(1) } diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/internal/grpclog.go b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/grpclog.go new file mode 100644 index 0000000000..59c03bc14c --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/grpclog.go @@ -0,0 +1,26 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 internal contains functionality internal to the grpclog package. +package internal + +// LoggerV2Impl is the logger used for the non-depth log functions. +var LoggerV2Impl LoggerV2 + +// DepthLoggerV2Impl is the logger used for the depth log functions. +var DepthLoggerV2Impl DepthLoggerV2 diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/internal/logger.go b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/logger.go new file mode 100644 index 0000000000..e524fdd40b --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/logger.go @@ -0,0 +1,87 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 internal + +// Logger mimics golang's standard Logger as an interface. +// +// Deprecated: use LoggerV2. +type Logger interface { + Fatal(args ...any) + Fatalf(format string, args ...any) + Fatalln(args ...any) + Print(args ...any) + Printf(format string, args ...any) + Println(args ...any) +} + +// LoggerWrapper wraps Logger into a LoggerV2. +type LoggerWrapper struct { + Logger +} + +// Info logs to INFO log. Arguments are handled in the manner of fmt.Print. +func (l *LoggerWrapper) Info(args ...any) { + l.Logger.Print(args...) +} + +// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println. +func (l *LoggerWrapper) Infoln(args ...any) { + l.Logger.Println(args...) +} + +// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf. +func (l *LoggerWrapper) Infof(format string, args ...any) { + l.Logger.Printf(format, args...) +} + +// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print. +func (l *LoggerWrapper) Warning(args ...any) { + l.Logger.Print(args...) +} + +// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println. +func (l *LoggerWrapper) Warningln(args ...any) { + l.Logger.Println(args...) +} + +// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf. +func (l *LoggerWrapper) Warningf(format string, args ...any) { + l.Logger.Printf(format, args...) +} + +// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print. +func (l *LoggerWrapper) Error(args ...any) { + l.Logger.Print(args...) +} + +// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println. +func (l *LoggerWrapper) Errorln(args ...any) { + l.Logger.Println(args...) +} + +// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf. +func (l *LoggerWrapper) Errorf(format string, args ...any) { + l.Logger.Printf(format, args...) +} + +// V reports whether verbosity level l is at least the requested verbose level. +func (*LoggerWrapper) V(int) bool { + // Returns true for all verbose level. + return true +} diff --git a/go-controller/vendor/google.golang.org/grpc/internal/grpclog/grpclog.go b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/loggerv2.go similarity index 52% rename from go-controller/vendor/google.golang.org/grpc/internal/grpclog/grpclog.go rename to go-controller/vendor/google.golang.org/grpc/grpclog/internal/loggerv2.go index bfc45102ab..07df71e98a 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/grpclog/grpclog.go +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/internal/loggerv2.go @@ -1,6 +1,6 @@ /* * - * Copyright 2020 gRPC authors. + * Copyright 2024 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,59 +16,17 @@ * */ -// Package grpclog (internal) defines depth logging for grpc. -package grpclog +package internal import ( + "encoding/json" + "fmt" + "io" + "log" "os" ) -// Logger is the logger used for the non-depth log functions. -var Logger LoggerV2 - -// DepthLogger is the logger used for the depth log functions. -var DepthLogger DepthLoggerV2 - -// InfoDepth logs to the INFO log at the specified depth. -func InfoDepth(depth int, args ...any) { - if DepthLogger != nil { - DepthLogger.InfoDepth(depth, args...) - } else { - Logger.Infoln(args...) - } -} - -// WarningDepth logs to the WARNING log at the specified depth. -func WarningDepth(depth int, args ...any) { - if DepthLogger != nil { - DepthLogger.WarningDepth(depth, args...) - } else { - Logger.Warningln(args...) - } -} - -// ErrorDepth logs to the ERROR log at the specified depth. -func ErrorDepth(depth int, args ...any) { - if DepthLogger != nil { - DepthLogger.ErrorDepth(depth, args...) - } else { - Logger.Errorln(args...) - } -} - -// FatalDepth logs to the FATAL log at the specified depth. -func FatalDepth(depth int, args ...any) { - if DepthLogger != nil { - DepthLogger.FatalDepth(depth, args...) - } else { - Logger.Fatalln(args...) - } - os.Exit(1) -} - // LoggerV2 does underlying logging work for grpclog. -// This is a copy of the LoggerV2 defined in the external grpclog package. It -// is defined here to avoid a circular dependency. type LoggerV2 interface { // Info logs to INFO log. Arguments are handled in the manner of fmt.Print. Info(args ...any) @@ -107,14 +65,13 @@ type LoggerV2 interface { // DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements // DepthLoggerV2, the below functions will be called with the appropriate stack // depth set for trivial functions the logger may ignore. -// This is a copy of the DepthLoggerV2 defined in the external grpclog package. -// It is defined here to avoid a circular dependency. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type DepthLoggerV2 interface { + LoggerV2 // InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Println. InfoDepth(depth int, args ...any) // WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Println. @@ -124,3 +81,124 @@ type DepthLoggerV2 interface { // FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Println. FatalDepth(depth int, args ...any) } + +const ( + // infoLog indicates Info severity. + infoLog int = iota + // warningLog indicates Warning severity. + warningLog + // errorLog indicates Error severity. + errorLog + // fatalLog indicates Fatal severity. + fatalLog +) + +// severityName contains the string representation of each severity. +var severityName = []string{ + infoLog: "INFO", + warningLog: "WARNING", + errorLog: "ERROR", + fatalLog: "FATAL", +} + +// loggerT is the default logger used by grpclog. +type loggerT struct { + m []*log.Logger + v int + jsonFormat bool +} + +func (g *loggerT) output(severity int, s string) { + sevStr := severityName[severity] + if !g.jsonFormat { + g.m[severity].Output(2, fmt.Sprintf("%v: %v", sevStr, s)) + return + } + // TODO: we can also include the logging component, but that needs more + // (API) changes. + b, _ := json.Marshal(map[string]string{ + "severity": sevStr, + "message": s, + }) + g.m[severity].Output(2, string(b)) +} + +func (g *loggerT) Info(args ...any) { + g.output(infoLog, fmt.Sprint(args...)) +} + +func (g *loggerT) Infoln(args ...any) { + g.output(infoLog, fmt.Sprintln(args...)) +} + +func (g *loggerT) Infof(format string, args ...any) { + g.output(infoLog, fmt.Sprintf(format, args...)) +} + +func (g *loggerT) Warning(args ...any) { + g.output(warningLog, fmt.Sprint(args...)) +} + +func (g *loggerT) Warningln(args ...any) { + g.output(warningLog, fmt.Sprintln(args...)) +} + +func (g *loggerT) Warningf(format string, args ...any) { + g.output(warningLog, fmt.Sprintf(format, args...)) +} + +func (g *loggerT) Error(args ...any) { + g.output(errorLog, fmt.Sprint(args...)) +} + +func (g *loggerT) Errorln(args ...any) { + g.output(errorLog, fmt.Sprintln(args...)) +} + +func (g *loggerT) Errorf(format string, args ...any) { + g.output(errorLog, fmt.Sprintf(format, args...)) +} + +func (g *loggerT) Fatal(args ...any) { + g.output(fatalLog, fmt.Sprint(args...)) + os.Exit(1) +} + +func (g *loggerT) Fatalln(args ...any) { + g.output(fatalLog, fmt.Sprintln(args...)) + os.Exit(1) +} + +func (g *loggerT) Fatalf(format string, args ...any) { + g.output(fatalLog, fmt.Sprintf(format, args...)) + os.Exit(1) +} + +func (g *loggerT) V(l int) bool { + return l <= g.v +} + +// LoggerV2Config configures the LoggerV2 implementation. +type LoggerV2Config struct { + // Verbosity sets the verbosity level of the logger. + Verbosity int + // FormatJSON controls whether the logger should output logs in JSON format. + FormatJSON bool +} + +// NewLoggerV2 creates a new LoggerV2 instance with the provided configuration. +// The infoW, warningW, and errorW writers are used to write log messages of +// different severity levels. +func NewLoggerV2(infoW, warningW, errorW io.Writer, c LoggerV2Config) LoggerV2 { + var m []*log.Logger + flag := log.LstdFlags + if c.FormatJSON { + flag = 0 + } + m = append(m, log.New(infoW, "", flag)) + m = append(m, log.New(io.MultiWriter(infoW, warningW), "", flag)) + ew := io.MultiWriter(infoW, warningW, errorW) // ew will be used for error and fatal. + m = append(m, log.New(ew, "", flag)) + m = append(m, log.New(ew, "", flag)) + return &loggerT{m: m, v: c.Verbosity, jsonFormat: c.FormatJSON} +} diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/logger.go b/go-controller/vendor/google.golang.org/grpc/grpclog/logger.go index b1674d8267..4b20358570 100644 --- a/go-controller/vendor/google.golang.org/grpc/grpclog/logger.go +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/logger.go @@ -18,70 +18,17 @@ package grpclog -import "google.golang.org/grpc/internal/grpclog" +import "google.golang.org/grpc/grpclog/internal" // Logger mimics golang's standard Logger as an interface. // // Deprecated: use LoggerV2. -type Logger interface { - Fatal(args ...any) - Fatalf(format string, args ...any) - Fatalln(args ...any) - Print(args ...any) - Printf(format string, args ...any) - Println(args ...any) -} +type Logger internal.Logger // SetLogger sets the logger that is used in grpc. Call only from // init() functions. // // Deprecated: use SetLoggerV2. func SetLogger(l Logger) { - grpclog.Logger = &loggerWrapper{Logger: l} -} - -// loggerWrapper wraps Logger into a LoggerV2. -type loggerWrapper struct { - Logger -} - -func (g *loggerWrapper) Info(args ...any) { - g.Logger.Print(args...) -} - -func (g *loggerWrapper) Infoln(args ...any) { - g.Logger.Println(args...) -} - -func (g *loggerWrapper) Infof(format string, args ...any) { - g.Logger.Printf(format, args...) -} - -func (g *loggerWrapper) Warning(args ...any) { - g.Logger.Print(args...) -} - -func (g *loggerWrapper) Warningln(args ...any) { - g.Logger.Println(args...) -} - -func (g *loggerWrapper) Warningf(format string, args ...any) { - g.Logger.Printf(format, args...) -} - -func (g *loggerWrapper) Error(args ...any) { - g.Logger.Print(args...) -} - -func (g *loggerWrapper) Errorln(args ...any) { - g.Logger.Println(args...) -} - -func (g *loggerWrapper) Errorf(format string, args ...any) { - g.Logger.Printf(format, args...) -} - -func (g *loggerWrapper) V(l int) bool { - // Returns true for all verbose level. - return true + internal.LoggerV2Impl = &internal.LoggerWrapper{Logger: l} } diff --git a/go-controller/vendor/google.golang.org/grpc/grpclog/loggerv2.go b/go-controller/vendor/google.golang.org/grpc/grpclog/loggerv2.go index ecfd36d713..892dc13d16 100644 --- a/go-controller/vendor/google.golang.org/grpc/grpclog/loggerv2.go +++ b/go-controller/vendor/google.golang.org/grpc/grpclog/loggerv2.go @@ -19,52 +19,16 @@ package grpclog import ( - "encoding/json" - "fmt" "io" - "log" "os" "strconv" "strings" - "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/grpclog/internal" ) // LoggerV2 does underlying logging work for grpclog. -type LoggerV2 interface { - // Info logs to INFO log. Arguments are handled in the manner of fmt.Print. - Info(args ...any) - // Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println. - Infoln(args ...any) - // Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf. - Infof(format string, args ...any) - // Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print. - Warning(args ...any) - // Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println. - Warningln(args ...any) - // Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf. - Warningf(format string, args ...any) - // Error logs to ERROR log. Arguments are handled in the manner of fmt.Print. - Error(args ...any) - // Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println. - Errorln(args ...any) - // Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf. - Errorf(format string, args ...any) - // Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print. - // gRPC ensures that all Fatal logs will exit with os.Exit(1). - // Implementations may also call os.Exit() with a non-zero exit code. - Fatal(args ...any) - // Fatalln logs to ERROR log. Arguments are handled in the manner of fmt.Println. - // gRPC ensures that all Fatal logs will exit with os.Exit(1). - // Implementations may also call os.Exit() with a non-zero exit code. - Fatalln(args ...any) - // Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf. - // gRPC ensures that all Fatal logs will exit with os.Exit(1). - // Implementations may also call os.Exit() with a non-zero exit code. - Fatalf(format string, args ...any) - // V reports whether verbosity level l is at least the requested verbose level. - V(l int) bool -} +type LoggerV2 internal.LoggerV2 // SetLoggerV2 sets logger that is used in grpc to a V2 logger. // Not mutex-protected, should be called before any gRPC functions. @@ -72,34 +36,8 @@ func SetLoggerV2(l LoggerV2) { if _, ok := l.(*componentData); ok { panic("cannot use component logger as grpclog logger") } - grpclog.Logger = l - grpclog.DepthLogger, _ = l.(grpclog.DepthLoggerV2) -} - -const ( - // infoLog indicates Info severity. - infoLog int = iota - // warningLog indicates Warning severity. - warningLog - // errorLog indicates Error severity. - errorLog - // fatalLog indicates Fatal severity. - fatalLog -) - -// severityName contains the string representation of each severity. -var severityName = []string{ - infoLog: "INFO", - warningLog: "WARNING", - errorLog: "ERROR", - fatalLog: "FATAL", -} - -// loggerT is the default logger used by grpclog. -type loggerT struct { - m []*log.Logger - v int - jsonFormat bool + internal.LoggerV2Impl = l + internal.DepthLoggerV2Impl, _ = l.(internal.DepthLoggerV2) } // NewLoggerV2 creates a loggerV2 with the provided writers. @@ -108,32 +46,13 @@ type loggerT struct { // Warning logs will be written to warningW and infoW. // Info logs will be written to infoW. func NewLoggerV2(infoW, warningW, errorW io.Writer) LoggerV2 { - return newLoggerV2WithConfig(infoW, warningW, errorW, loggerV2Config{}) + return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{}) } // NewLoggerV2WithVerbosity creates a loggerV2 with the provided writers and // verbosity level. func NewLoggerV2WithVerbosity(infoW, warningW, errorW io.Writer, v int) LoggerV2 { - return newLoggerV2WithConfig(infoW, warningW, errorW, loggerV2Config{verbose: v}) -} - -type loggerV2Config struct { - verbose int - jsonFormat bool -} - -func newLoggerV2WithConfig(infoW, warningW, errorW io.Writer, c loggerV2Config) LoggerV2 { - var m []*log.Logger - flag := log.LstdFlags - if c.jsonFormat { - flag = 0 - } - m = append(m, log.New(infoW, "", flag)) - m = append(m, log.New(io.MultiWriter(infoW, warningW), "", flag)) - ew := io.MultiWriter(infoW, warningW, errorW) // ew will be used for error and fatal. - m = append(m, log.New(ew, "", flag)) - m = append(m, log.New(ew, "", flag)) - return &loggerT{m: m, v: c.verbose, jsonFormat: c.jsonFormat} + return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{Verbosity: v}) } // newLoggerV2 creates a loggerV2 to be used as default logger. @@ -161,80 +80,10 @@ func newLoggerV2() LoggerV2 { jsonFormat := strings.EqualFold(os.Getenv("GRPC_GO_LOG_FORMATTER"), "json") - return newLoggerV2WithConfig(infoW, warningW, errorW, loggerV2Config{ - verbose: v, - jsonFormat: jsonFormat, - }) -} - -func (g *loggerT) output(severity int, s string) { - sevStr := severityName[severity] - if !g.jsonFormat { - g.m[severity].Output(2, fmt.Sprintf("%v: %v", sevStr, s)) - return - } - // TODO: we can also include the logging component, but that needs more - // (API) changes. - b, _ := json.Marshal(map[string]string{ - "severity": sevStr, - "message": s, + return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{ + Verbosity: v, + FormatJSON: jsonFormat, }) - g.m[severity].Output(2, string(b)) -} - -func (g *loggerT) Info(args ...any) { - g.output(infoLog, fmt.Sprint(args...)) -} - -func (g *loggerT) Infoln(args ...any) { - g.output(infoLog, fmt.Sprintln(args...)) -} - -func (g *loggerT) Infof(format string, args ...any) { - g.output(infoLog, fmt.Sprintf(format, args...)) -} - -func (g *loggerT) Warning(args ...any) { - g.output(warningLog, fmt.Sprint(args...)) -} - -func (g *loggerT) Warningln(args ...any) { - g.output(warningLog, fmt.Sprintln(args...)) -} - -func (g *loggerT) Warningf(format string, args ...any) { - g.output(warningLog, fmt.Sprintf(format, args...)) -} - -func (g *loggerT) Error(args ...any) { - g.output(errorLog, fmt.Sprint(args...)) -} - -func (g *loggerT) Errorln(args ...any) { - g.output(errorLog, fmt.Sprintln(args...)) -} - -func (g *loggerT) Errorf(format string, args ...any) { - g.output(errorLog, fmt.Sprintf(format, args...)) -} - -func (g *loggerT) Fatal(args ...any) { - g.output(fatalLog, fmt.Sprint(args...)) - os.Exit(1) -} - -func (g *loggerT) Fatalln(args ...any) { - g.output(fatalLog, fmt.Sprintln(args...)) - os.Exit(1) -} - -func (g *loggerT) Fatalf(format string, args ...any) { - g.output(fatalLog, fmt.Sprintf(format, args...)) - os.Exit(1) -} - -func (g *loggerT) V(l int) bool { - return l <= g.v } // DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements @@ -245,14 +94,4 @@ func (g *loggerT) V(l int) bool { // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. -type DepthLoggerV2 interface { - LoggerV2 - // InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Println. - InfoDepth(depth int, args ...any) - // WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Println. - WarningDepth(depth int, args ...any) - // ErrorDepth logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Println. - ErrorDepth(depth int, args ...any) - // FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Println. - FatalDepth(depth int, args ...any) -} +type DepthLoggerV2 internal.DepthLoggerV2 diff --git a/go-controller/vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go b/go-controller/vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go index 13821a9266..85540f86a7 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go @@ -33,6 +33,8 @@ type lbConfig struct { childConfig serviceconfig.LoadBalancingConfig } +// ChildName returns the name of the child balancer of the gracefulswitch +// Balancer. func ChildName(l serviceconfig.LoadBalancingConfig) string { return l.(*lbConfig).childBuilder.Name() } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go b/go-controller/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go index aa4505a871..9669328914 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go @@ -106,7 +106,7 @@ func (ml *TruncatingMethodLogger) Build(c LogEntryConfig) *binlogpb.GrpcLogEntry } // Log creates a proto binary log entry, and logs it to the sink. -func (ml *TruncatingMethodLogger) Log(ctx context.Context, c LogEntryConfig) { +func (ml *TruncatingMethodLogger) Log(_ context.Context, c LogEntryConfig) { ml.sink.Write(ml.Build(c)) } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/channel.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/channel.go index d7e9e1d54e..3ec662799a 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/channel.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/channel.go @@ -43,6 +43,8 @@ type Channel struct { // Non-zero traceRefCount means the trace of this channel cannot be deleted. traceRefCount int32 + // ChannelMetrics holds connectivity state, target and call metrics for the + // channel within channelz. ChannelMetrics ChannelMetrics } @@ -50,6 +52,8 @@ type Channel struct { // nesting. func (c *Channel) channelzIdentifier() {} +// String returns a string representation of the Channel, including its parent +// entity and ID. func (c *Channel) String() string { if c.Parent == nil { return fmt.Sprintf("Channel #%d", c.ID) @@ -61,24 +65,31 @@ func (c *Channel) id() int64 { return c.ID } +// SubChans returns a copy of the map of sub-channels associated with the +// Channel. func (c *Channel) SubChans() map[int64]string { db.mu.RLock() defer db.mu.RUnlock() return copyMap(c.subChans) } +// NestedChans returns a copy of the map of nested channels associated with the +// Channel. func (c *Channel) NestedChans() map[int64]string { db.mu.RLock() defer db.mu.RUnlock() return copyMap(c.nestedChans) } +// Trace returns a copy of the Channel's trace data. func (c *Channel) Trace() *ChannelTrace { db.mu.RLock() defer db.mu.RUnlock() return c.trace.copy() } +// ChannelMetrics holds connectivity state, target and call metrics for the +// channel within channelz. type ChannelMetrics struct { // The current connectivity state of the channel. State atomic.Pointer[connectivity.State] @@ -136,12 +147,16 @@ func strFromPointer(s *string) string { return *s } +// String returns a string representation of the ChannelMetrics, including its +// state, target, and call metrics. func (c *ChannelMetrics) String() string { return fmt.Sprintf("State: %v, Target: %s, CallsStarted: %v, CallsSucceeded: %v, CallsFailed: %v, LastCallStartedTimestamp: %v", c.State.Load(), strFromPointer(c.Target.Load()), c.CallsStarted.Load(), c.CallsSucceeded.Load(), c.CallsFailed.Load(), c.LastCallStartedTimestamp.Load(), ) } +// NewChannelMetricForTesting creates a new instance of ChannelMetrics with +// specified initial values for testing purposes. func NewChannelMetricForTesting(state connectivity.State, target string, started, succeeded, failed, timestamp int64) *ChannelMetrics { c := &ChannelMetrics{} c.State.Store(&state) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/channelmap.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/channelmap.go index dfe18b0892..64c791953d 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/channelmap.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/channelmap.go @@ -46,7 +46,7 @@ type entry interface { // channelMap is the storage data structure for channelz. // -// Methods of channelMap can be divided in two two categories with respect to +// Methods of channelMap can be divided into two categories with respect to // locking. // // 1. Methods acquire the global lock. @@ -234,13 +234,6 @@ func copyMap(m map[int64]string) map[int64]string { return n } -func min(a, b int) int { - if a < b { - return a - } - return b -} - func (c *channelMap) getTopChannels(id int64, maxResults int) ([]*Channel, bool) { if maxResults <= 0 { maxResults = EntriesPerPage diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/funcs.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/funcs.go index 03e24e1507..078bb81238 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/funcs.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/funcs.go @@ -33,7 +33,7 @@ var ( // outside this package except by tests. IDGen IDGenerator - db *channelMap = newChannelMap() + db = newChannelMap() // EntriesPerPage defines the number of channelz entries to be shown on a web page. EntriesPerPage = 50 curState int32 diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/server.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/server.go index cdfc49d6ea..b5a8249929 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/server.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/server.go @@ -59,6 +59,8 @@ func NewServerMetricsForTesting(started, succeeded, failed, timestamp int64) *Se return sm } +// CopyFrom copies the metrics data from the provided ServerMetrics +// instance into the current instance. func (sm *ServerMetrics) CopyFrom(o *ServerMetrics) { sm.CallsStarted.Store(o.CallsStarted.Load()) sm.CallsSucceeded.Store(o.CallsSucceeded.Load()) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/socket.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/socket.go index fa64834b25..90103847c5 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/socket.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/socket.go @@ -70,13 +70,18 @@ type EphemeralSocketMetrics struct { RemoteFlowControlWindow int64 } +// SocketType represents the type of socket. type SocketType string +// SocketType can be one of these. const ( SocketTypeNormal = "NormalSocket" SocketTypeListen = "ListenSocket" ) +// Socket represents a socket within channelz which includes socket +// metrics and data related to socket activity and provides methods +// for managing and interacting with sockets. type Socket struct { Entity SocketType SocketType @@ -100,6 +105,8 @@ type Socket struct { Security credentials.ChannelzSecurityValue } +// String returns a string representation of the Socket, including its parent +// entity, socket type, and ID. func (ls *Socket) String() string { return fmt.Sprintf("%s %s #%d", ls.Parent, ls.SocketType, ls.ID) } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/subchannel.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/subchannel.go index 3b88e4cba8..b20802e6e9 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/subchannel.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/subchannel.go @@ -47,12 +47,14 @@ func (sc *SubChannel) id() int64 { return sc.ID } +// Sockets returns a copy of the sockets map associated with the SubChannel. func (sc *SubChannel) Sockets() map[int64]string { db.mu.RLock() defer db.mu.RUnlock() return copyMap(sc.sockets) } +// Trace returns a copy of the ChannelTrace associated with the SubChannel. func (sc *SubChannel) Trace() *ChannelTrace { db.mu.RLock() defer db.mu.RUnlock() diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/syscall_nonlinux.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/syscall_nonlinux.go index d1ed8df6a5..0e6e18e185 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/syscall_nonlinux.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/syscall_nonlinux.go @@ -35,13 +35,13 @@ type SocketOptionData struct { // Getsockopt defines the function to get socket options requested by channelz. // It is to be passed to syscall.RawConn.Control(). // Windows OS doesn't support Socket Option -func (s *SocketOptionData) Getsockopt(fd uintptr) { +func (s *SocketOptionData) Getsockopt(uintptr) { once.Do(func() { logger.Warning("Channelz: socket options are not supported on non-linux environments") }) } // GetSocketOption gets the socket option info of the conn. -func GetSocketOption(c any) *SocketOptionData { +func GetSocketOption(any) *SocketOptionData { return nil } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/channelz/trace.go b/go-controller/vendor/google.golang.org/grpc/internal/channelz/trace.go index 36b8674032..2bffe47776 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/channelz/trace.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/channelz/trace.go @@ -79,13 +79,21 @@ type TraceEvent struct { Parent *TraceEvent } +// ChannelTrace provides tracing information for a channel. +// It tracks various events and metadata related to the channel's lifecycle +// and operations. type ChannelTrace struct { - cm *channelMap - clearCalled bool + cm *channelMap + clearCalled bool + // The time when the trace was created. CreationTime time.Time - EventNum int64 - mu sync.Mutex - Events []*traceEvent + // A counter for the number of events recorded in the + // trace. + EventNum int64 + mu sync.Mutex + // A slice of traceEvent pointers representing the events recorded for + // this channel. + Events []*traceEvent } func (c *ChannelTrace) copy() *ChannelTrace { @@ -175,6 +183,7 @@ var refChannelTypeToString = map[RefChannelType]string{ RefNormalSocket: "NormalSocket", } +// String returns a string representation of the RefChannelType func (r RefChannelType) String() string { return refChannelTypeToString[r] } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/go-controller/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index d906487139..6e7dd6b772 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -45,7 +45,16 @@ var ( // option is present for backward compatibility. This option may be overridden // by setting the environment variable "GRPC_ENFORCE_ALPN_ENABLED" to "true" // or "false". - EnforceALPNEnabled = boolFromEnv("GRPC_ENFORCE_ALPN_ENABLED", false) + EnforceALPNEnabled = boolFromEnv("GRPC_ENFORCE_ALPN_ENABLED", true) + // XDSFallbackSupport is the env variable that controls whether support for + // xDS fallback is turned on. If this is unset or is false, only the first + // xDS server in the list of server configs will be used. + XDSFallbackSupport = boolFromEnv("GRPC_EXPERIMENTAL_XDS_FALLBACK", false) + // NewPickFirstEnabled is set if the new pickfirst leaf policy is to be used + // instead of the exiting pickfirst implementation. This can be enabled by + // setting the environment variable "GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST" + // to "true". + NewPickFirstEnabled = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST", false) ) func boolFromEnv(envVar string, def bool) bool { diff --git a/go-controller/vendor/google.golang.org/grpc/internal/experimental.go b/go-controller/vendor/google.golang.org/grpc/internal/experimental.go index 7f7044e173..7617be2158 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/experimental.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/experimental.go @@ -18,11 +18,11 @@ package internal var ( - // WithRecvBufferPool is implemented by the grpc package and returns a dial + // WithBufferPool is implemented by the grpc package and returns a dial // option to configure a shared buffer pool for a grpc.ClientConn. - WithRecvBufferPool any // func (grpc.SharedBufferPool) grpc.DialOption + WithBufferPool any // func (grpc.SharedBufferPool) grpc.DialOption - // RecvBufferPool is implemented by the grpc package and returns a server + // BufferPool is implemented by the grpc package and returns a server // option to configure a shared buffer pool for a grpc.Server. - RecvBufferPool any // func (grpc.SharedBufferPool) grpc.ServerOption + BufferPool any // func (grpc.SharedBufferPool) grpc.ServerOption ) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefixLogger.go b/go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefix_logger.go similarity index 63% rename from go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefixLogger.go rename to go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefix_logger.go index faa998de76..092ad187a2 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefixLogger.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/grpclog/prefix_logger.go @@ -16,17 +16,21 @@ * */ +// Package grpclog provides logging functionality for internal gRPC packages, +// outside of the functionality provided by the external `grpclog` package. package grpclog import ( "fmt" + + "google.golang.org/grpc/grpclog" ) // PrefixLogger does logging with a prefix. // // Logging method on a nil logs without any prefix. type PrefixLogger struct { - logger DepthLoggerV2 + logger grpclog.DepthLoggerV2 prefix string } @@ -38,7 +42,7 @@ func (pl *PrefixLogger) Infof(format string, args ...any) { pl.logger.InfoDepth(1, fmt.Sprintf(format, args...)) return } - InfoDepth(1, fmt.Sprintf(format, args...)) + grpclog.InfoDepth(1, fmt.Sprintf(format, args...)) } // Warningf does warning logging. @@ -48,7 +52,7 @@ func (pl *PrefixLogger) Warningf(format string, args ...any) { pl.logger.WarningDepth(1, fmt.Sprintf(format, args...)) return } - WarningDepth(1, fmt.Sprintf(format, args...)) + grpclog.WarningDepth(1, fmt.Sprintf(format, args...)) } // Errorf does error logging. @@ -58,36 +62,18 @@ func (pl *PrefixLogger) Errorf(format string, args ...any) { pl.logger.ErrorDepth(1, fmt.Sprintf(format, args...)) return } - ErrorDepth(1, fmt.Sprintf(format, args...)) -} - -// Debugf does info logging at verbose level 2. -func (pl *PrefixLogger) Debugf(format string, args ...any) { - // TODO(6044): Refactor interfaces LoggerV2 and DepthLogger, and maybe - // rewrite PrefixLogger a little to ensure that we don't use the global - // `Logger` here, and instead use the `logger` field. - if !Logger.V(2) { - return - } - if pl != nil { - // Handle nil, so the tests can pass in a nil logger. - format = pl.prefix + format - pl.logger.InfoDepth(1, fmt.Sprintf(format, args...)) - return - } - InfoDepth(1, fmt.Sprintf(format, args...)) - + grpclog.ErrorDepth(1, fmt.Sprintf(format, args...)) } // V reports whether verbosity level l is at least the requested verbose level. func (pl *PrefixLogger) V(l int) bool { - // TODO(6044): Refactor interfaces LoggerV2 and DepthLogger, and maybe - // rewrite PrefixLogger a little to ensure that we don't use the global - // `Logger` here, and instead use the `logger` field. - return Logger.V(l) + if pl != nil { + return pl.logger.V(l) + } + return true } // NewPrefixLogger creates a prefix logger with the given prefix. -func NewPrefixLogger(logger DepthLoggerV2, prefix string) *PrefixLogger { +func NewPrefixLogger(logger grpclog.DepthLoggerV2, prefix string) *PrefixLogger { return &PrefixLogger{logger: logger, prefix: prefix} } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go b/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go index f7f40a16ac..8e8e861280 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/callback_serializer.go @@ -53,16 +53,28 @@ func NewCallbackSerializer(ctx context.Context) *CallbackSerializer { return cs } -// Schedule adds a callback to be scheduled after existing callbacks are run. +// TrySchedule tries to schedule the provided callback function f to be +// executed in the order it was added. This is a best-effort operation. If the +// context passed to NewCallbackSerializer was canceled before this method is +// called, the callback will not be scheduled. // // Callbacks are expected to honor the context when performing any blocking // operations, and should return early when the context is canceled. +func (cs *CallbackSerializer) TrySchedule(f func(ctx context.Context)) { + cs.callbacks.Put(f) +} + +// ScheduleOr schedules the provided callback function f to be executed in the +// order it was added. If the context passed to NewCallbackSerializer has been +// canceled before this method is called, the onFailure callback will be +// executed inline instead. // -// Return value indicates if the callback was successfully added to the list of -// callbacks to be executed by the serializer. It is not possible to add -// callbacks once the context passed to NewCallbackSerializer is cancelled. -func (cs *CallbackSerializer) Schedule(f func(ctx context.Context)) bool { - return cs.callbacks.Put(f) == nil +// Callbacks are expected to honor the context when performing any blocking +// operations, and should return early when the context is canceled. +func (cs *CallbackSerializer) ScheduleOr(f func(ctx context.Context), onFailure func()) { + if cs.callbacks.Put(f) != nil { + onFailure() + } } func (cs *CallbackSerializer) run(ctx context.Context) { diff --git a/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/pubsub.go b/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/pubsub.go index aef8cec1ab..6d8c2f518d 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/pubsub.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/grpcsync/pubsub.go @@ -77,7 +77,7 @@ func (ps *PubSub) Subscribe(sub Subscriber) (cancel func()) { if ps.msg != nil { msg := ps.msg - ps.cs.Schedule(func(context.Context) { + ps.cs.TrySchedule(func(context.Context) { ps.mu.Lock() defer ps.mu.Unlock() if !ps.subscribers[sub] { @@ -103,7 +103,7 @@ func (ps *PubSub) Publish(msg any) { ps.msg = msg for sub := range ps.subscribers { s := sub - ps.cs.Schedule(func(context.Context) { + ps.cs.TrySchedule(func(context.Context) { ps.mu.Lock() defer ps.mu.Unlock() if !ps.subscribers[s] { diff --git a/go-controller/vendor/google.golang.org/grpc/internal/grpcutil/method.go b/go-controller/vendor/google.golang.org/grpc/internal/grpcutil/method.go index ec62b4775e..683d1955c6 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/grpcutil/method.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/grpcutil/method.go @@ -39,7 +39,7 @@ func ParseMethod(methodName string) (service, method string, _ error) { } // baseContentType is the base content-type for gRPC. This is a valid -// content-type on it's own, but can also include a content-subtype such as +// content-type on its own, but can also include a content-subtype such as // "proto" as a suffix after "+" or ";". See // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests // for more details. diff --git a/go-controller/vendor/google.golang.org/grpc/internal/idle/idle.go b/go-controller/vendor/google.golang.org/grpc/internal/idle/idle.go index fe49cb74c5..2c13ee9dac 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/idle/idle.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/idle/idle.go @@ -182,6 +182,7 @@ func (m *Manager) tryEnterIdleMode() bool { return true } +// EnterIdleModeForTesting instructs the channel to enter idle mode. func (m *Manager) EnterIdleModeForTesting() { m.tryEnterIdleMode() } @@ -225,7 +226,7 @@ func (m *Manager) ExitIdleMode() error { // came in and OnCallBegin() noticed that the calls count is negative. // - Channel is in idle mode, and multiple new RPCs come in at the same // time, all of them notice a negative calls count in OnCallBegin and get - // here. The first one to get the lock would got the channel to exit idle. + // here. The first one to get the lock would get the channel to exit idle. // - Channel is not in idle mode, and the user calls Connect which calls // m.ExitIdleMode. // @@ -266,6 +267,7 @@ func (m *Manager) isClosed() bool { return atomic.LoadInt32(&m.closed) == 1 } +// Close stops the timer associated with the Manager, if it exists. func (m *Manager) Close() { atomic.StoreInt32(&m.closed, 1) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/internal.go b/go-controller/vendor/google.golang.org/grpc/internal/internal.go index 5d66539869..20b4dc3d35 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/internal.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/internal.go @@ -183,7 +183,7 @@ var ( // GRPCResolverSchemeExtraMetadata determines when gRPC will add extra // metadata to RPCs. - GRPCResolverSchemeExtraMetadata string = "xds" + GRPCResolverSchemeExtraMetadata = "xds" // EnterIdleModeForTesting gets the ClientConn to enter IDLE mode. EnterIdleModeForTesting any // func(*grpc.ClientConn) @@ -191,6 +191,8 @@ var ( // ExitIdleModeForTesting gets the ClientConn to exit IDLE mode. ExitIdleModeForTesting any // func(*grpc.ClientConn) error + // ChannelzTurnOffForTesting disables the Channelz service for testing + // purposes. ChannelzTurnOffForTesting func() // TriggerXDSResourceNotFoundForTesting causes the provided xDS Client to @@ -203,11 +205,27 @@ var ( // UserSetDefaultScheme is set to true if the user has overridden the // default resolver scheme. - UserSetDefaultScheme bool = false + UserSetDefaultScheme = false - // ShuffleAddressListForTesting pseudo-randomizes the order of addresses. n - // is the number of elements. swap swaps the elements with indexes i and j. - ShuffleAddressListForTesting any // func(n int, swap func(i, j int)) + // ConnectedAddress returns the connected address for a SubConnState. The + // address is only valid if the state is READY. + ConnectedAddress any // func (scs SubConnState) resolver.Address + + // SetConnectedAddress sets the connected address for a SubConnState. + SetConnectedAddress any // func(scs *SubConnState, addr resolver.Address) + + // SnapshotMetricRegistryForTesting snapshots the global data of the metric + // registry. Returns a cleanup function that sets the metric registry to its + // original state. Only called in testing functions. + SnapshotMetricRegistryForTesting func() func() + + // SetDefaultBufferPoolForTesting updates the default buffer pool, for + // testing purposes. + SetDefaultBufferPoolForTesting any // func(mem.BufferPool) + + // SetBufferPoolingThresholdForTesting updates the buffer pooling threshold, for + // testing purposes. + SetBufferPoolingThresholdForTesting any // func(int) ) // HealthChecker defines the signature of the client-side LB channel health @@ -215,7 +233,7 @@ var ( // // The implementation is expected to create a health checking RPC stream by // calling newStream(), watch for the health status of serviceName, and report -// it's health back by calling setConnectivityState(). +// its health back by calling setConnectivityState(). // // The health checking protocol is defined at: // https://github.com/grpc/grpc/blob/master/doc/health-checking.md diff --git a/go-controller/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go b/go-controller/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go index 4552db16b0..374c12fb77 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go @@ -177,7 +177,7 @@ type dnsResolver struct { // finished. Otherwise, data race will be possible. [Race Example] in // dns_resolver_test we replace the real lookup functions with mocked ones to // facilitate testing. If Close() doesn't wait for watcher() goroutine - // finishes, race detector sometimes will warns lookup (READ the lookup + // finishes, race detector sometimes will warn lookup (READ the lookup // function pointers) inside watcher() goroutine has data race with // replaceNetFunc (WRITE the lookup function pointers). wg sync.WaitGroup @@ -237,7 +237,9 @@ func (d *dnsResolver) watcher() { } func (d *dnsResolver) lookupSRV(ctx context.Context) ([]resolver.Address, error) { - if !EnableSRVLookups { + // Skip this particular host to avoid timeouts with some versions of + // systemd-resolved. + if !EnableSRVLookups || d.host == "metadata.google.internal." { return nil, nil } var newAddrs []resolver.Address diff --git a/go-controller/vendor/google.golang.org/grpc/internal/resolver/passthrough/passthrough.go b/go-controller/vendor/google.golang.org/grpc/internal/resolver/passthrough/passthrough.go index afac56572a..b901c7bace 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/resolver/passthrough/passthrough.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/resolver/passthrough/passthrough.go @@ -55,7 +55,7 @@ func (r *passthroughResolver) start() { r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint()}}}) } -func (*passthroughResolver) ResolveNow(o resolver.ResolveNowOptions) {} +func (*passthroughResolver) ResolveNow(resolver.ResolveNowOptions) {} func (*passthroughResolver) Close() {} diff --git a/go-controller/vendor/google.golang.org/grpc/internal/stats/labels.go b/go-controller/vendor/google.golang.org/grpc/internal/stats/labels.go new file mode 100644 index 0000000000..fd33af51ae --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/internal/stats/labels.go @@ -0,0 +1,42 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 stats provides internal stats related functionality. +package stats + +import "context" + +// Labels are the labels for metrics. +type Labels struct { + // TelemetryLabels are the telemetry labels to record. + TelemetryLabels map[string]string +} + +type labelsKey struct{} + +// GetLabels returns the Labels stored in the context, or nil if there is one. +func GetLabels(ctx context.Context) *Labels { + labels, _ := ctx.Value(labelsKey{}).(*Labels) + return labels +} + +// SetLabels sets the Labels in the context. +func SetLabels(ctx context.Context, labels *Labels) context.Context { + // could also append + return context.WithValue(ctx, labelsKey{}, labels) +} diff --git a/go-controller/vendor/google.golang.org/grpc/internal/stats/metrics_recorder_list.go b/go-controller/vendor/google.golang.org/grpc/internal/stats/metrics_recorder_list.go new file mode 100644 index 0000000000..79044657be --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/internal/stats/metrics_recorder_list.go @@ -0,0 +1,105 @@ +/* + * Copyright 2024 gRPC authors. + * + * 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 stats + +import ( + "fmt" + + estats "google.golang.org/grpc/experimental/stats" + "google.golang.org/grpc/stats" +) + +// MetricsRecorderList forwards Record calls to all of its metricsRecorders. +// +// It eats any record calls where the label values provided do not match the +// number of label keys. +type MetricsRecorderList struct { + // metricsRecorders are the metrics recorders this list will forward to. + metricsRecorders []estats.MetricsRecorder +} + +// NewMetricsRecorderList creates a new metric recorder list with all the stats +// handlers provided which implement the MetricsRecorder interface. +// If no stats handlers provided implement the MetricsRecorder interface, +// the MetricsRecorder list returned is a no-op. +func NewMetricsRecorderList(shs []stats.Handler) *MetricsRecorderList { + var mrs []estats.MetricsRecorder + for _, sh := range shs { + if mr, ok := sh.(estats.MetricsRecorder); ok { + mrs = append(mrs, mr) + } + } + return &MetricsRecorderList{ + metricsRecorders: mrs, + } +} + +func verifyLabels(desc *estats.MetricDescriptor, labelsRecv ...string) { + if got, want := len(labelsRecv), len(desc.Labels)+len(desc.OptionalLabels); got != want { + panic(fmt.Sprintf("Received %d labels in call to record metric %q, but expected %d.", got, desc.Name, want)) + } +} + +// RecordInt64Count records the measurement alongside labels on the int +// count associated with the provided handle. +func (l *MetricsRecorderList) RecordInt64Count(handle *estats.Int64CountHandle, incr int64, labels ...string) { + verifyLabels(handle.Descriptor(), labels...) + + for _, metricRecorder := range l.metricsRecorders { + metricRecorder.RecordInt64Count(handle, incr, labels...) + } +} + +// RecordFloat64Count records the measurement alongside labels on the float +// count associated with the provided handle. +func (l *MetricsRecorderList) RecordFloat64Count(handle *estats.Float64CountHandle, incr float64, labels ...string) { + verifyLabels(handle.Descriptor(), labels...) + + for _, metricRecorder := range l.metricsRecorders { + metricRecorder.RecordFloat64Count(handle, incr, labels...) + } +} + +// RecordInt64Histo records the measurement alongside labels on the int +// histo associated with the provided handle. +func (l *MetricsRecorderList) RecordInt64Histo(handle *estats.Int64HistoHandle, incr int64, labels ...string) { + verifyLabels(handle.Descriptor(), labels...) + + for _, metricRecorder := range l.metricsRecorders { + metricRecorder.RecordInt64Histo(handle, incr, labels...) + } +} + +// RecordFloat64Histo records the measurement alongside labels on the float +// histo associated with the provided handle. +func (l *MetricsRecorderList) RecordFloat64Histo(handle *estats.Float64HistoHandle, incr float64, labels ...string) { + verifyLabels(handle.Descriptor(), labels...) + + for _, metricRecorder := range l.metricsRecorders { + metricRecorder.RecordFloat64Histo(handle, incr, labels...) + } +} + +// RecordInt64Gauge records the measurement alongside labels on the int +// gauge associated with the provided handle. +func (l *MetricsRecorderList) RecordInt64Gauge(handle *estats.Int64GaugeHandle, incr int64, labels ...string) { + verifyLabels(handle.Descriptor(), labels...) + + for _, metricRecorder := range l.metricsRecorders { + metricRecorder.RecordInt64Gauge(handle, incr, labels...) + } +} diff --git a/go-controller/vendor/google.golang.org/grpc/internal/status/status.go b/go-controller/vendor/google.golang.org/grpc/internal/status/status.go index c7dbc82059..1186f1e9a9 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/status/status.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/status/status.go @@ -138,17 +138,19 @@ func (s *Status) WithDetails(details ...protoadapt.MessageV1) (*Status, error) { // s.Code() != OK implies that s.Proto() != nil. p := s.Proto() for _, detail := range details { - any, err := anypb.New(protoadapt.MessageV2Of(detail)) + m, err := anypb.New(protoadapt.MessageV2Of(detail)) if err != nil { return nil, err } - p.Details = append(p.Details, any) + p.Details = append(p.Details, m) } return &Status{s: p}, nil } // Details returns a slice of details messages attached to the status. // If a detail cannot be decoded, the error is returned in place of the detail. +// If the detail can be decoded, the proto message returned is of the same +// type that was given to WithDetails(). func (s *Status) Details() []any { if s == nil || s.s == nil { return nil @@ -160,7 +162,38 @@ func (s *Status) Details() []any { details = append(details, err) continue } - details = append(details, detail) + // The call to MessageV1Of is required to unwrap the proto message if + // it implemented only the MessageV1 API. The proto message would have + // been wrapped in a V2 wrapper in Status.WithDetails. V2 messages are + // added to a global registry used by any.UnmarshalNew(). + // MessageV1Of has the following behaviour: + // 1. If the given message is a wrapped MessageV1, it returns the + // unwrapped value. + // 2. If the given message already implements MessageV1, it returns it + // as is. + // 3. Else, it wraps the MessageV2 in a MessageV1 wrapper. + // + // Since the Status.WithDetails() API only accepts MessageV1, calling + // MessageV1Of ensures we return the same type that was given to + // WithDetails: + // * If the give type implemented only MessageV1, the unwrapping from + // point 1 above will restore the type. + // * If the given type implemented both MessageV1 and MessageV2, point 2 + // above will ensure no wrapping is performed. + // * If the given type implemented only MessageV2 and was wrapped using + // MessageV1Of before passing to WithDetails(), it would be unwrapped + // in WithDetails by calling MessageV2Of(). Point 3 above will ensure + // that the type is wrapped in a MessageV1 wrapper again before + // returning. Note that protoc-gen-go doesn't generate code which + // implements ONLY MessageV2 at the time of writing. + // + // NOTE: Status details can also be added using the FromProto method. + // This could theoretically allow passing a Detail message that only + // implements the V2 API. In such a case the message will be wrapped in + // a MessageV1 wrapper when fetched using Details(). + // Since protoc-gen-go generates only code that implements both V1 and + // V2 APIs for backward compatibility, this is not a concern. + details = append(details, protoadapt.MessageV1Of(detail)) } return details } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/syscall/syscall_nonlinux.go b/go-controller/vendor/google.golang.org/grpc/internal/syscall/syscall_nonlinux.go index 999f52cd75..54c24c2ff3 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/syscall/syscall_nonlinux.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/syscall/syscall_nonlinux.go @@ -58,20 +58,20 @@ func GetRusage() *Rusage { // CPUTimeDiff returns the differences of user CPU time and system CPU time used // between two Rusage structs. It a no-op function for non-linux environments. -func CPUTimeDiff(first *Rusage, latest *Rusage) (float64, float64) { +func CPUTimeDiff(*Rusage, *Rusage) (float64, float64) { log() return 0, 0 } // SetTCPUserTimeout is a no-op function under non-linux environments. -func SetTCPUserTimeout(conn net.Conn, timeout time.Duration) error { +func SetTCPUserTimeout(net.Conn, time.Duration) error { log() return nil } // GetTCPUserTimeout is a no-op function under non-linux environments. // A negative return value indicates the operation is not supported -func GetTCPUserTimeout(conn net.Conn) (int, error) { +func GetTCPUserTimeout(net.Conn) (int, error) { log() return -1, nil } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_unix.go b/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_unix.go index 078137b7fd..7e7aaa5463 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_unix.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_unix.go @@ -44,7 +44,7 @@ func NetDialerWithTCPKeepalive() *net.Dialer { // combination of unconditionally enabling TCP keepalives here, and // disabling the overriding of TCP keepalive parameters by setting the // KeepAlive field to a negative value above, results in OS defaults for - // the TCP keealive interval and time parameters. + // the TCP keepalive interval and time parameters. Control: func(_, _ string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_KEEPALIVE, 1) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_windows.go b/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_windows.go index fd7d43a890..d5c1085eea 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_windows.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/tcp_keepalive_windows.go @@ -44,7 +44,7 @@ func NetDialerWithTCPKeepalive() *net.Dialer { // combination of unconditionally enabling TCP keepalives here, and // disabling the overriding of TCP keepalive parameters by setting the // KeepAlive field to a negative value above, results in OS defaults for - // the TCP keealive interval and time parameters. + // the TCP keepalive interval and time parameters. Control: func(_, _ string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_KEEPALIVE, 1) diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/controlbuf.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/controlbuf.go index 3deadfb4a2..ef72fbb3a0 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/controlbuf.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/controlbuf.go @@ -32,6 +32,7 @@ import ( "golang.org/x/net/http2/hpack" "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" + "google.golang.org/grpc/mem" "google.golang.org/grpc/status" ) @@ -148,9 +149,9 @@ type dataFrame struct { streamID uint32 endStream bool h []byte - d []byte + reader mem.Reader // onEachWrite is called every time - // a part of d is written out. + // a part of data is written out. onEachWrite func() } @@ -289,18 +290,22 @@ func (l *outStreamList) dequeue() *outStream { } // controlBuffer is a way to pass information to loopy. -// Information is passed as specific struct types called control frames. -// A control frame not only represents data, messages or headers to be sent out -// but can also be used to instruct loopy to update its internal state. -// It shouldn't be confused with an HTTP2 frame, although some of the control frames -// like dataFrame and headerFrame do go out on wire as HTTP2 frames. +// +// Information is passed as specific struct types called control frames. A +// control frame not only represents data, messages or headers to be sent out +// but can also be used to instruct loopy to update its internal state. It +// shouldn't be confused with an HTTP2 frame, although some of the control +// frames like dataFrame and headerFrame do go out on wire as HTTP2 frames. type controlBuffer struct { - ch chan struct{} - done <-chan struct{} + wakeupCh chan struct{} // Unblocks readers waiting for something to read. + done <-chan struct{} // Closed when the transport is done. + + // Mutex guards all the fields below, except trfChan which can be read + // atomically without holding mu. mu sync.Mutex - consumerWaiting bool - list *itemList - err error + consumerWaiting bool // True when readers are blocked waiting for new data. + closed bool // True when the controlbuf is finished. + list *itemList // List of queued control frames. // transportResponseFrames counts the number of queued items that represent // the response of an action initiated by the peer. trfChan is created @@ -308,47 +313,59 @@ type controlBuffer struct { // closed and nilled when transportResponseFrames drops below the // threshold. Both fields are protected by mu. transportResponseFrames int - trfChan atomic.Value // chan struct{} + trfChan atomic.Pointer[chan struct{}] } func newControlBuffer(done <-chan struct{}) *controlBuffer { return &controlBuffer{ - ch: make(chan struct{}, 1), - list: &itemList{}, - done: done, + wakeupCh: make(chan struct{}, 1), + list: &itemList{}, + done: done, } } -// throttle blocks if there are too many incomingSettings/cleanupStreams in the -// controlbuf. +// throttle blocks if there are too many frames in the control buf that +// represent the response of an action initiated by the peer, like +// incomingSettings cleanupStreams etc. func (c *controlBuffer) throttle() { - ch, _ := c.trfChan.Load().(chan struct{}) - if ch != nil { + if ch := c.trfChan.Load(); ch != nil { select { - case <-ch: + case <-(*ch): case <-c.done: } } } +// put adds an item to the controlbuf. func (c *controlBuffer) put(it cbItem) error { _, err := c.executeAndPut(nil, it) return err } +// executeAndPut runs f, and if the return value is true, adds the given item to +// the controlbuf. The item could be nil, in which case, this method simply +// executes f and does not add the item to the controlbuf. +// +// The first return value indicates whether the item was successfully added to +// the control buffer. A non-nil error, specifically ErrConnClosing, is returned +// if the control buffer is already closed. func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) { - var wakeUp bool c.mu.Lock() - if c.err != nil { - c.mu.Unlock() - return false, c.err + defer c.mu.Unlock() + + if c.closed { + return false, ErrConnClosing } if f != nil { if !f() { // f wasn't successful - c.mu.Unlock() return false, nil } } + if it == nil { + return true, nil + } + + var wakeUp bool if c.consumerWaiting { wakeUp = true c.consumerWaiting = false @@ -359,98 +376,102 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) { if c.transportResponseFrames == maxQueuedTransportResponseFrames { // We are adding the frame that puts us over the threshold; create // a throttling channel. - c.trfChan.Store(make(chan struct{})) + ch := make(chan struct{}) + c.trfChan.Store(&ch) } } - c.mu.Unlock() if wakeUp { select { - case c.ch <- struct{}{}: + case c.wakeupCh <- struct{}{}: default: } } return true, nil } -// Note argument f should never be nil. -func (c *controlBuffer) execute(f func(it any) bool, it any) (bool, error) { - c.mu.Lock() - if c.err != nil { - c.mu.Unlock() - return false, c.err - } - if !f(it) { // f wasn't successful - c.mu.Unlock() - return false, nil - } - c.mu.Unlock() - return true, nil -} - +// get returns the next control frame from the control buffer. If block is true +// **and** there are no control frames in the control buffer, the call blocks +// until one of the conditions is met: there is a frame to return or the +// transport is closed. func (c *controlBuffer) get(block bool) (any, error) { for { c.mu.Lock() - if c.err != nil { + frame, err := c.getOnceLocked() + if frame != nil || err != nil || !block { + // If we read a frame or an error, we can return to the caller. The + // call to getOnceLocked() returns a nil frame and a nil error if + // there is nothing to read, and in that case, if the caller asked + // us not to block, we can return now as well. c.mu.Unlock() - return nil, c.err - } - if !c.list.isEmpty() { - h := c.list.dequeue().(cbItem) - if h.isTransportResponseFrame() { - if c.transportResponseFrames == maxQueuedTransportResponseFrames { - // We are removing the frame that put us over the - // threshold; close and clear the throttling channel. - ch := c.trfChan.Load().(chan struct{}) - close(ch) - c.trfChan.Store((chan struct{})(nil)) - } - c.transportResponseFrames-- - } - c.mu.Unlock() - return h, nil - } - if !block { - c.mu.Unlock() - return nil, nil + return frame, err } c.consumerWaiting = true c.mu.Unlock() + + // Release the lock above and wait to be woken up. select { - case <-c.ch: + case <-c.wakeupCh: case <-c.done: return nil, errors.New("transport closed by client") } } } +// Callers must not use this method, but should instead use get(). +// +// Caller must hold c.mu. +func (c *controlBuffer) getOnceLocked() (any, error) { + if c.closed { + return false, ErrConnClosing + } + if c.list.isEmpty() { + return nil, nil + } + h := c.list.dequeue().(cbItem) + if h.isTransportResponseFrame() { + if c.transportResponseFrames == maxQueuedTransportResponseFrames { + // We are removing the frame that put us over the + // threshold; close and clear the throttling channel. + ch := c.trfChan.Swap(nil) + close(*ch) + } + c.transportResponseFrames-- + } + return h, nil +} + +// finish closes the control buffer, cleaning up any streams that have queued +// header frames. Once this method returns, no more frames can be added to the +// control buffer, and attempts to do so will return ErrConnClosing. func (c *controlBuffer) finish() { c.mu.Lock() - if c.err != nil { - c.mu.Unlock() + defer c.mu.Unlock() + + if c.closed { return } - c.err = ErrConnClosing + c.closed = true // There may be headers for streams in the control buffer. // These streams need to be cleaned out since the transport // is still not aware of these yet. for head := c.list.dequeueAll(); head != nil; head = head.next { - hdr, ok := head.it.(*headerFrame) - if !ok { - continue - } - if hdr.onOrphaned != nil { // It will be nil on the server-side. - hdr.onOrphaned(ErrConnClosing) + switch v := head.it.(type) { + case *headerFrame: + if v.onOrphaned != nil { // It will be nil on the server-side. + v.onOrphaned(ErrConnClosing) + } + case *dataFrame: + _ = v.reader.Close() } } + // In case throttle() is currently in flight, it needs to be unblocked. // Otherwise, the transport may not close, since the transport is closed by // the reader encountering the connection error. - ch, _ := c.trfChan.Load().(chan struct{}) + ch := c.trfChan.Swap(nil) if ch != nil { - close(ch) + close(*ch) } - c.trfChan.Store((chan struct{})(nil)) - c.mu.Unlock() } type side int @@ -466,7 +487,7 @@ const ( // stream maintains a queue of data frames; as loopy receives data frames // it gets added to the queue of the relevant stream. // Loopy goes over this list of active streams by processing one node every iteration, -// thereby closely resemebling to a round-robin scheduling over all streams. While +// thereby closely resembling a round-robin scheduling over all streams. While // processing a stream, loopy writes out data bytes from this stream capped by the min // of http2MaxFrameLen, connection-level flow control and stream-level flow control. type loopyWriter struct { @@ -490,12 +511,13 @@ type loopyWriter struct { draining bool conn net.Conn logger *grpclog.PrefixLogger + bufferPool mem.BufferPool // Side-specific handlers ssGoAwayHandler func(*goAway) (bool, error) } -func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator, conn net.Conn, logger *grpclog.PrefixLogger, goAwayHandler func(*goAway) (bool, error)) *loopyWriter { +func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator, conn net.Conn, logger *grpclog.PrefixLogger, goAwayHandler func(*goAway) (bool, error), bufferPool mem.BufferPool) *loopyWriter { var buf bytes.Buffer l := &loopyWriter{ side: s, @@ -511,6 +533,7 @@ func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimato conn: conn, logger: logger, ssGoAwayHandler: goAwayHandler, + bufferPool: bufferPool, } return l } @@ -768,6 +791,11 @@ func (l *loopyWriter) cleanupStreamHandler(c *cleanupStream) error { // not be established yet. delete(l.estdStreams, c.streamID) str.deleteSelf() + for head := str.itl.dequeueAll(); head != nil; head = head.next { + if df, ok := head.it.(*dataFrame); ok { + _ = df.reader.Close() + } + } } if c.rst { // If RST_STREAM needs to be sent. if err := l.framer.fr.WriteRSTStream(c.streamID, c.rstCode); err != nil { @@ -903,16 +931,18 @@ func (l *loopyWriter) processData() (bool, error) { dataItem := str.itl.peek().(*dataFrame) // Peek at the first data item this stream. // A data item is represented by a dataFrame, since it later translates into // multiple HTTP2 data frames. - // Every dataFrame has two buffers; h that keeps grpc-message header and d that is actual data. - // As an optimization to keep wire traffic low, data from d is copied to h to make as big as the - // maximum possible HTTP2 frame size. + // Every dataFrame has two buffers; h that keeps grpc-message header and data + // that is the actual message. As an optimization to keep wire traffic low, data + // from data is copied to h to make as big as the maximum possible HTTP2 frame + // size. - if len(dataItem.h) == 0 && len(dataItem.d) == 0 { // Empty data frame + if len(dataItem.h) == 0 && dataItem.reader.Remaining() == 0 { // Empty data frame // Client sends out empty data frame with endStream = true if err := l.framer.fr.WriteData(dataItem.streamID, dataItem.endStream, nil); err != nil { return false, err } str.itl.dequeue() // remove the empty data item from stream + _ = dataItem.reader.Close() if str.itl.isEmpty() { str.state = empty } else if trailer, ok := str.itl.peek().(*headerFrame); ok { // the next item is trailers. @@ -927,9 +957,7 @@ func (l *loopyWriter) processData() (bool, error) { } return false, nil } - var ( - buf []byte - ) + // Figure out the maximum size we can send maxSize := http2MaxFrameLen if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { // stream-level flow control. @@ -943,43 +971,50 @@ func (l *loopyWriter) processData() (bool, error) { } // Compute how much of the header and data we can send within quota and max frame length hSize := min(maxSize, len(dataItem.h)) - dSize := min(maxSize-hSize, len(dataItem.d)) - if hSize != 0 { - if dSize == 0 { - buf = dataItem.h - } else { - // We can add some data to grpc message header to distribute bytes more equally across frames. - // Copy on the stack to avoid generating garbage - var localBuf [http2MaxFrameLen]byte - copy(localBuf[:hSize], dataItem.h) - copy(localBuf[hSize:], dataItem.d[:dSize]) - buf = localBuf[:hSize+dSize] - } + dSize := min(maxSize-hSize, dataItem.reader.Remaining()) + remainingBytes := len(dataItem.h) + dataItem.reader.Remaining() - hSize - dSize + size := hSize + dSize + + var buf *[]byte + + if hSize != 0 && dSize == 0 { + buf = &dataItem.h } else { - buf = dataItem.d - } + // Note: this is only necessary because the http2.Framer does not support + // partially writing a frame, so the sequence must be materialized into a buffer. + // TODO: Revisit once https://github.com/golang/go/issues/66655 is addressed. + pool := l.bufferPool + if pool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream is + // always initialized with a BufferPool. + pool = mem.DefaultBufferPool() + } + buf = pool.Get(size) + defer pool.Put(buf) - size := hSize + dSize + copy((*buf)[:hSize], dataItem.h) + _, _ = dataItem.reader.Read((*buf)[hSize:]) + } // Now that outgoing flow controls are checked we can replenish str's write quota str.wq.replenish(size) var endStream bool // If this is the last data message on this stream and all of it can be written in this iteration. - if dataItem.endStream && len(dataItem.h)+len(dataItem.d) <= size { + if dataItem.endStream && remainingBytes == 0 { endStream = true } if dataItem.onEachWrite != nil { dataItem.onEachWrite() } - if err := l.framer.fr.WriteData(dataItem.streamID, endStream, buf[:size]); err != nil { + if err := l.framer.fr.WriteData(dataItem.streamID, endStream, (*buf)[:size]); err != nil { return false, err } str.bytesOutStanding += size l.sendQuota -= uint32(size) dataItem.h = dataItem.h[hSize:] - dataItem.d = dataItem.d[dSize:] - if len(dataItem.h) == 0 && len(dataItem.d) == 0 { // All the data from that message was written out. + if remainingBytes == 0 { // All the data from that message was written out. + _ = dataItem.reader.Close() str.itl.dequeue() } if str.itl.isEmpty() { @@ -998,10 +1033,3 @@ func (l *loopyWriter) processData() (bool, error) { } return false, nil } - -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/handler_server.go index 4a3ddce29a..ce878693bd 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/handler_server.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/handler_server.go @@ -24,7 +24,6 @@ package transport import ( - "bytes" "context" "errors" "fmt" @@ -40,6 +39,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" @@ -50,7 +50,7 @@ import ( // NewServerHandlerTransport returns a ServerTransport handling gRPC from // inside an http.Handler, or writes an HTTP error to w and returns an error. // It requires that the http Server supports HTTP/2. -func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats []stats.Handler) (ServerTransport, error) { +func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats []stats.Handler, bufferPool mem.BufferPool) (ServerTransport, error) { if r.Method != http.MethodPost { w.Header().Set("Allow", http.MethodPost) msg := fmt.Sprintf("invalid gRPC request method %q", r.Method) @@ -98,6 +98,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats []s contentType: contentType, contentSubtype: contentSubtype, stats: stats, + bufferPool: bufferPool, } st.logger = prefixLoggerForServerHandlerTransport(st) @@ -171,6 +172,8 @@ type serverHandlerTransport struct { stats []stats.Handler logger *grpclog.PrefixLogger + + bufferPool mem.BufferPool } func (ht *serverHandlerTransport) Close(err error) { @@ -244,6 +247,7 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro } s.hdrMu.Lock() + defer s.hdrMu.Unlock() if p := st.Proto(); p != nil && len(p.Details) > 0 { delete(s.trailer, grpcStatusDetailsBinHeader) stBytes, err := proto.Marshal(p) @@ -268,7 +272,6 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro } } } - s.hdrMu.Unlock() }) if err == nil { // transport has not been closed @@ -330,16 +333,28 @@ func (ht *serverHandlerTransport) writeCustomHeaders(s *Stream) { s.hdrMu.Unlock() } -func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { +func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data mem.BufferSlice, _ *Options) error { + // Always take a reference because otherwise there is no guarantee the data will + // be available after this function returns. This is what callers to Write + // expect. + data.Ref() headersWritten := s.updateHeaderSent() - return ht.do(func() { + err := ht.do(func() { + defer data.Free() if !headersWritten { ht.writePendingHeaders(s) } ht.rw.Write(hdr) - ht.rw.Write(data) + for _, b := range data { + _, _ = ht.rw.Write(b.ReadOnlyData()) + } ht.rw.(http.Flusher).Flush() }) + if err != nil { + data.Free() + return err + } + return nil } func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { @@ -406,7 +421,7 @@ func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream headerWireLength: 0, // won't have access to header wire length until golang/go#18997. } s.trReader = &transportReader{ - reader: &recvBufferReader{ctx: s.ctx, ctxDone: s.ctx.Done(), recv: s.buf, freeBuffer: func(*bytes.Buffer) {}}, + reader: &recvBufferReader{ctx: s.ctx, ctxDone: s.ctx.Done(), recv: s.buf}, windowHandler: func(int) {}, } @@ -415,21 +430,19 @@ func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream go func() { defer close(readerDone) - // TODO: minimize garbage, optimize recvBuffer code/ownership - const readSize = 8196 - for buf := make([]byte, readSize); ; { - n, err := req.Body.Read(buf) + for { + buf := ht.bufferPool.Get(http2MaxFrameLen) + n, err := req.Body.Read(*buf) if n > 0 { - s.buf.put(recvMsg{buffer: bytes.NewBuffer(buf[:n:n])}) - buf = buf[n:] + *buf = (*buf)[:n] + s.buf.put(recvMsg{buffer: mem.NewBuffer(buf, ht.bufferPool)}) + } else { + ht.bufferPool.Put(buf) } if err != nil { s.buf.put(recvMsg{err: mapRecvMsgError(err)}) return } - if len(buf) == 0 { - buf = make([]byte, readSize) - } } }() @@ -462,7 +475,7 @@ func (ht *serverHandlerTransport) IncrMsgSent() {} func (ht *serverHandlerTransport) IncrMsgRecv() {} -func (ht *serverHandlerTransport) Drain(debugData string) { +func (ht *serverHandlerTransport) Drain(string) { panic("Drain() is not implemented") } diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 3c63c70698..62b81885d8 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -47,6 +47,7 @@ import ( isyscall "google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/internal/transport/networktype" "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/resolver" @@ -59,6 +60,8 @@ import ( // atomically. var clientConnectionCounter uint64 +var goAwayLoopyWriterTimeout = 5 * time.Second + var metadataFromOutgoingContextRaw = internal.FromOutgoingContextRaw.(func(context.Context) (metadata.MD, [][]string, bool)) // http2Client implements the ClientTransport interface with HTTP2. @@ -83,9 +86,9 @@ type http2Client struct { writerDone chan struct{} // sync point to enable testing. // goAway is closed to notify the upper layer (i.e., addrConn.transportMonitor) // that the server sent GoAway on this transport. - goAway chan struct{} - - framer *framer + goAway chan struct{} + keepaliveDone chan struct{} // Closed when the keepalive goroutine exits. + framer *framer // controlBuf delivers all the control related tasks (e.g., window // updates, reset streams, and various settings) to the controller. // Do not access controlBuf with mu held. @@ -144,7 +147,7 @@ type http2Client struct { onClose func(GoAwayReason) - bufferPool *bufferPool + bufferPool mem.BufferPool connectionID uint64 logger *grpclog.PrefixLogger @@ -229,7 +232,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } }(conn) - // The following defer and goroutine monitor the connectCtx for cancelation + // The following defer and goroutine monitor the connectCtx for cancellation // and deadline. On context expiration, the connection is hard closed and // this function will naturally fail as a result. Otherwise, the defer // waits for the goroutine to exit to prevent the context from being @@ -332,6 +335,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts readerDone: make(chan struct{}), writerDone: make(chan struct{}), goAway: make(chan struct{}), + keepaliveDone: make(chan struct{}), framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize), fc: &trInFlow{limit: uint32(icwz)}, scheme: scheme, @@ -346,7 +350,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts streamQuota: defaultMaxStreamsClient, streamsQuotaAvailable: make(chan struct{}, 1), keepaliveEnabled: keepaliveEnabled, - bufferPool: newBufferPool(), + bufferPool: opts.BufferPool, onClose: onClose, } var czSecurity credentials.ChannelzSecurityValue @@ -463,7 +467,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts return nil, err } go func() { - t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger, t.outgoingGoAwayHandler) + t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger, t.outgoingGoAwayHandler, t.bufferPool) if err := t.loopy.run(); !isIOError(err) { // Immediately close the connection, as the loopy writer returns // when there are no more active streams and we were draining (the @@ -504,7 +508,6 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { closeStream: func(err error) { t.CloseStream(s, err) }, - freeBuffer: t.bufferPool.put, }, windowHandler: func(n int) { t.updateWindow(s, uint32(n)) @@ -525,8 +528,9 @@ func (t *http2Client) getPeer() *peer.Peer { // to be the last frame loopy writes to the transport. func (t *http2Client) outgoingGoAwayHandler(g *goAway) (bool, error) { t.mu.Lock() - defer t.mu.Unlock() - if err := t.framer.fr.WriteGoAway(t.nextID-2, http2.ErrCodeNo, g.debugData); err != nil { + maxStreamID := t.nextID - 2 + t.mu.Unlock() + if err := t.framer.fr.WriteGoAway(maxStreamID, http2.ErrCodeNo, g.debugData); err != nil { return false, err } return false, g.closeConn @@ -770,7 +774,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, hdr := &headerFrame{ hf: headerFields, endStream: false, - initStream: func(id uint32) error { + initStream: func(uint32) error { t.mu.Lock() // TODO: handle transport closure in loopy instead and remove this // initStream is never called when transport is draining. @@ -983,6 +987,7 @@ func (t *http2Client) closeStream(s *Stream, err error, rst bool, rstCode http2. // only once on a transport. Once it is called, the transport should not be // accessed anymore. func (t *http2Client) Close(err error) { + t.conn.SetWriteDeadline(time.Now().Add(time.Second * 10)) t.mu.Lock() // Make sure we only close once. if t.state == closing { @@ -1005,18 +1010,33 @@ func (t *http2Client) Close(err error) { // should unblock it so that the goroutine eventually exits. t.kpDormancyCond.Signal() } + // Append info about previous goaways if there were any, since this may be important + // for understanding the root cause for this connection to be closed. + goAwayDebugMessage := t.goAwayDebugMessage t.mu.Unlock() + // Per HTTP/2 spec, a GOAWAY frame must be sent before closing the - // connection. See https://httpwg.org/specs/rfc7540.html#GOAWAY. + // connection. See https://httpwg.org/specs/rfc7540.html#GOAWAY. It + // also waits for loopyWriter to be closed with a timer to avoid the + // long blocking in case the connection is blackholed, i.e. TCP is + // just stuck. t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte("client transport shutdown"), closeConn: err}) - <-t.writerDone + timer := time.NewTimer(goAwayLoopyWriterTimeout) + defer timer.Stop() + select { + case <-t.writerDone: // success + case <-timer.C: + t.logger.Infof("Failed to write a GOAWAY frame as part of connection close after %s. Giving up and closing the transport.", goAwayLoopyWriterTimeout) + } t.cancel() t.conn.Close() + // Waits for the reader and keepalive goroutines to exit before returning to + // ensure all resources are cleaned up before Close can return. + <-t.readerDone + if t.keepaliveEnabled { + <-t.keepaliveDone + } channelz.RemoveEntry(t.channelz.ID) - // Append info about previous goaways if there were any, since this may be important - // for understanding the root cause for this connection to be closed. - _, goAwayDebugMessage := t.GetGoAwayReason() - var st *status.Status if len(goAwayDebugMessage) > 0 { st = status.Newf(codes.Unavailable, "closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage) @@ -1065,27 +1085,36 @@ func (t *http2Client) GracefulClose() { // Write formats the data into HTTP2 data frame(s) and sends it out. The caller // should proceed only if Write returns nil. -func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { +func (t *http2Client) Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error { + reader := data.Reader() + if opts.Last { // If it's the last message, update stream state. if !s.compareAndSwapState(streamActive, streamWriteDone) { + _ = reader.Close() return errStreamDone } } else if s.getState() != streamActive { + _ = reader.Close() return errStreamDone } df := &dataFrame{ streamID: s.id, endStream: opts.Last, h: hdr, - d: data, + reader: reader, } - if hdr != nil || data != nil { // If it's not an empty data frame, check quota. - if err := s.wq.get(int32(len(hdr) + len(data))); err != nil { + if hdr != nil || df.reader.Remaining() != 0 { // If it's not an empty data frame, check quota. + if err := s.wq.get(int32(len(hdr) + df.reader.Remaining())); err != nil { + _ = reader.Close() return err } } - return t.controlBuf.put(df) + if err := t.controlBuf.put(df); err != nil { + _ = reader.Close() + return err + } + return nil } func (t *http2Client) getStream(f http2.Frame) *Stream { @@ -1190,10 +1219,13 @@ func (t *http2Client) handleData(f *http2.DataFrame) { // guarantee f.Data() is consumed before the arrival of next frame. // Can this copy be eliminated? if len(f.Data()) > 0 { - buffer := t.bufferPool.get() - buffer.Reset() - buffer.Write(f.Data()) - s.write(recvMsg{buffer: buffer}) + pool := t.bufferPool + if pool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream is + // always initialized with a BufferPool. + pool = mem.DefaultBufferPool() + } + s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)}) } } // The server has closed the stream without sending trailers. Record that @@ -1222,7 +1254,7 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) { if statusCode == codes.Canceled { if d, ok := s.ctx.Deadline(); ok && !d.After(time.Now()) { // Our deadline was already exceeded, and that was likely the cause - // of this cancelation. Alter the status code accordingly. + // of this cancellation. Alter the status code accordingly. statusCode = codes.DeadlineExceeded } } @@ -1291,11 +1323,11 @@ func (t *http2Client) handlePing(f *http2.PingFrame) { t.controlBuf.put(pingAck) } -func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { +func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) error { t.mu.Lock() if t.state == closing { t.mu.Unlock() - return + return nil } if f.ErrCode == http2.ErrCodeEnhanceYourCalm && string(f.DebugData()) == "too_many_pings" { // When a client receives a GOAWAY with error code ENHANCE_YOUR_CALM and debug @@ -1307,8 +1339,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { id := f.LastStreamID if id > 0 && id%2 == 0 { t.mu.Unlock() - t.Close(connectionErrorf(true, nil, "received goaway with non-zero even-numbered numbered stream id: %v", id)) - return + return connectionErrorf(true, nil, "received goaway with non-zero even-numbered stream id: %v", id) } // A client can receive multiple GoAways from the server (see // https://github.com/grpc/grpc-go/issues/1387). The idea is that the first @@ -1325,8 +1356,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { // If there are multiple GoAways the first one should always have an ID greater than the following ones. if id > t.prevGoAwayID { t.mu.Unlock() - t.Close(connectionErrorf(true, nil, "received goaway with stream id: %v, which exceeds stream id of previous goaway: %v", id, t.prevGoAwayID)) - return + return connectionErrorf(true, nil, "received goaway with stream id: %v, which exceeds stream id of previous goaway: %v", id, t.prevGoAwayID) } default: t.setGoAwayReason(f) @@ -1350,8 +1380,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { t.prevGoAwayID = id if len(t.activeStreams) == 0 { t.mu.Unlock() - t.Close(connectionErrorf(true, nil, "received goaway and there are no active streams")) - return + return connectionErrorf(true, nil, "received goaway and there are no active streams") } streamsToClose := make([]*Stream, 0) @@ -1368,6 +1397,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { for _, stream := range streamsToClose { t.closeStream(stream, errStreamDrain, false, http2.ErrCodeNo, statusGoAway, nil, false) } + return nil } // setGoAwayReason sets the value of t.goAwayReason based @@ -1603,7 +1633,13 @@ func (t *http2Client) readServerPreface() error { // network connection. If the server preface is not read successfully, an // error is pushed to errCh; otherwise errCh is closed with no error. func (t *http2Client) reader(errCh chan<- error) { - defer close(t.readerDone) + var errClose error + defer func() { + close(t.readerDone) + if errClose != nil { + t.Close(errClose) + } + }() if err := t.readServerPreface(); err != nil { errCh <- err @@ -1642,11 +1678,10 @@ func (t *http2Client) reader(errCh chan<- error) { t.closeStream(s, status.Error(code, msg), true, http2.ErrCodeProtocol, status.New(code, msg), nil, false) } continue - } else { - // Transport error. - t.Close(connectionErrorf(true, err, "error reading from server: %v", err)) - return } + // Transport error. + errClose = connectionErrorf(true, err, "error reading from server: %v", err) + return } switch frame := frame.(type) { case *http2.MetaHeadersFrame: @@ -1660,7 +1695,7 @@ func (t *http2Client) reader(errCh chan<- error) { case *http2.PingFrame: t.handlePing(frame) case *http2.GoAwayFrame: - t.handleGoAway(frame) + errClose = t.handleGoAway(frame) case *http2.WindowUpdateFrame: t.handleWindowUpdate(frame) default: @@ -1671,15 +1706,15 @@ func (t *http2Client) reader(errCh chan<- error) { } } -func minTime(a, b time.Duration) time.Duration { - if a < b { - return a - } - return b -} - // keepalive running in a separate goroutine makes sure the connection is alive by sending pings. func (t *http2Client) keepalive() { + var err error + defer func() { + close(t.keepaliveDone) + if err != nil { + t.Close(err) + } + }() p := &ping{data: [8]byte{}} // True iff a ping has been sent, and no data has been received since then. outstandingPing := false @@ -1703,7 +1738,7 @@ func (t *http2Client) keepalive() { continue } if outstandingPing && timeoutLeft <= 0 { - t.Close(connectionErrorf(true, nil, "keepalive ping failed to receive ACK within timeout")) + err = connectionErrorf(true, nil, "keepalive ping failed to receive ACK within timeout") return } t.mu.Lock() @@ -1745,7 +1780,7 @@ func (t *http2Client) keepalive() { // timeoutLeft. This will ensure that we wait only for kp.Time // before sending out the next ping (for cases where the ping is // acked). - sleepDuration := minTime(t.kp.Time, timeoutLeft) + sleepDuration := min(t.kp.Time, timeoutLeft) timeoutLeft -= sleepDuration timer.Reset(sleepDuration) case <-t.ctx.Done(): diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_server.go index b7091165b5..584b50fe55 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -39,6 +39,7 @@ import ( "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/internal/syscall" + "google.golang.org/grpc/mem" "google.golang.org/protobuf/proto" "google.golang.org/grpc/codes" @@ -119,7 +120,7 @@ type http2Server struct { // Fields below are for channelz metric collection. channelz *channelz.Socket - bufferPool *bufferPool + bufferPool mem.BufferPool connectionID uint64 @@ -261,7 +262,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, idle: time.Now(), kep: kep, initialWindowSize: iwz, - bufferPool: newBufferPool(), + bufferPool: config.BufferPool, } var czSecurity credentials.ChannelzSecurityValue if au, ok := authInfo.(credentials.ChannelzSecurityInfo); ok { @@ -330,7 +331,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, t.handleSettings(sf) go func() { - t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger, t.outgoingGoAwayHandler) + t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger, t.outgoingGoAwayHandler, t.bufferPool) err := t.loopy.run() close(t.loopyWriterDone) if !isIOError(err) { @@ -613,10 +614,9 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade s.wq = newWriteQuota(defaultWriteQuota, s.ctxDone) s.trReader = &transportReader{ reader: &recvBufferReader{ - ctx: s.ctx, - ctxDone: s.ctxDone, - recv: s.buf, - freeBuffer: t.bufferPool.put, + ctx: s.ctx, + ctxDone: s.ctxDone, + recv: s.buf, }, windowHandler: func(n int) { t.updateWindow(s, uint32(n)) @@ -813,10 +813,13 @@ func (t *http2Server) handleData(f *http2.DataFrame) { // guarantee f.Data() is consumed before the arrival of next frame. // Can this copy be eliminated? if len(f.Data()) > 0 { - buffer := t.bufferPool.get() - buffer.Reset() - buffer.Write(f.Data()) - s.write(recvMsg{buffer: buffer}) + pool := t.bufferPool + if pool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream is + // always initialized with a BufferPool. + pool = mem.DefaultBufferPool() + } + s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)}) } } if f.StreamEnded() { @@ -1089,7 +1092,9 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { onWrite: t.setResetPingStrikes, } - success, err := t.controlBuf.execute(t.checkForHeaderListSize, trailingHeader) + success, err := t.controlBuf.executeAndPut(func() bool { + return t.checkForHeaderListSize(trailingHeader) + }, nil) if !success { if err != nil { return err @@ -1112,27 +1117,37 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { // Write converts the data into HTTP2 data frame and sends it out. Non-nil error // is returns if it fails (e.g., framing error, transport error). -func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { +func (t *http2Server) Write(s *Stream, hdr []byte, data mem.BufferSlice, _ *Options) error { + reader := data.Reader() + if !s.isHeaderSent() { // Headers haven't been written yet. if err := t.WriteHeader(s, nil); err != nil { + _ = reader.Close() return err } } else { // Writing headers checks for this condition. if s.getState() == streamDone { + _ = reader.Close() return t.streamContextErr(s) } } + df := &dataFrame{ streamID: s.id, h: hdr, - d: data, + reader: reader, onEachWrite: t.setResetPingStrikes, } - if err := s.wq.get(int32(len(hdr) + len(data))); err != nil { + if err := s.wq.get(int32(len(hdr) + df.reader.Remaining())); err != nil { + _ = reader.Close() return t.streamContextErr(s) } - return t.controlBuf.put(df) + if err := t.controlBuf.put(df); err != nil { + _ = reader.Close() + return err + } + return nil } // keepalive running in a separate goroutine does the following: @@ -1223,7 +1238,7 @@ func (t *http2Server) keepalive() { // timeoutLeft. This will ensure that we wait only for kp.Time // before sending out the next ping (for cases where the ping is // acked). - sleepDuration := minTime(t.kp.Time, kpTimeoutLeft) + sleepDuration := min(t.kp.Time, kpTimeoutLeft) kpTimeoutLeft -= sleepDuration kpTimer.Reset(sleepDuration) case <-t.done: diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/http_util.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/http_util.go index 39cef3bd44..3613d7b648 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/http_util.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/http_util.go @@ -317,28 +317,32 @@ func newBufWriter(conn net.Conn, batchSize int, pool *sync.Pool) *bufWriter { return w } -func (w *bufWriter) Write(b []byte) (n int, err error) { +func (w *bufWriter) Write(b []byte) (int, error) { if w.err != nil { return 0, w.err } if w.batchSize == 0 { // Buffer has been disabled. - n, err = w.conn.Write(b) + n, err := w.conn.Write(b) return n, toIOError(err) } if w.buf == nil { b := w.pool.Get().(*[]byte) w.buf = *b } + written := 0 for len(b) > 0 { - nn := copy(w.buf[w.offset:], b) - b = b[nn:] - w.offset += nn - n += nn - if w.offset >= w.batchSize { - err = w.flushKeepBuffer() + copied := copy(w.buf[w.offset:], b) + b = b[copied:] + written += copied + w.offset += copied + if w.offset < w.batchSize { + continue + } + if err := w.flushKeepBuffer(); err != nil { + return written, err } } - return n, err + return written, nil } func (w *bufWriter) Flush() error { @@ -389,7 +393,7 @@ type framer struct { fr *http2.Framer } -var writeBufferPoolMap map[int]*sync.Pool = make(map[int]*sync.Pool) +var writeBufferPoolMap = make(map[int]*sync.Pool) var writeBufferMutex sync.Mutex func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32) *framer { diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/proxy.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/proxy.go index 24fa103257..54b2244365 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/proxy.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/proxy.go @@ -107,8 +107,14 @@ func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr stri } return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump) } - - return &bufConn{Conn: conn, r: r}, nil + // The buffer could contain extra bytes from the target server, so we can't + // discard it. However, in many cases where the server waits for the client + // to send the first message (e.g. when TLS is being used), the buffer will + // be empty, so we can avoid the overhead of reading through this buffer. + if r.Buffered() != 0 { + return &bufConn{Conn: conn, r: r}, nil + } + return conn, nil } // proxyDial dials, connecting to a proxy first if necessary. Checks if a proxy diff --git a/go-controller/vendor/google.golang.org/grpc/internal/transport/transport.go b/go-controller/vendor/google.golang.org/grpc/internal/transport/transport.go index 4b39c0ade9..e12cb0bc91 100644 --- a/go-controller/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/go-controller/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -22,7 +22,6 @@ package transport import ( - "bytes" "context" "errors" "fmt" @@ -37,6 +36,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/resolver" @@ -47,32 +47,10 @@ import ( const logLevel = 2 -type bufferPool struct { - pool sync.Pool -} - -func newBufferPool() *bufferPool { - return &bufferPool{ - pool: sync.Pool{ - New: func() any { - return new(bytes.Buffer) - }, - }, - } -} - -func (p *bufferPool) get() *bytes.Buffer { - return p.pool.Get().(*bytes.Buffer) -} - -func (p *bufferPool) put(b *bytes.Buffer) { - p.pool.Put(b) -} - // recvMsg represents the received msg from the transport. All transport // protocol specific info has been removed. type recvMsg struct { - buffer *bytes.Buffer + buffer mem.Buffer // nil: received some data // io.EOF: stream is completed. data is nil. // other non-nil error: transport failure. data is nil. @@ -102,6 +80,9 @@ func newRecvBuffer() *recvBuffer { func (b *recvBuffer) put(r recvMsg) { b.mu.Lock() if b.err != nil { + // drop the buffer on the floor. Since b.err is not nil, any subsequent reads + // will always return an error, making this buffer inaccessible. + r.buffer.Free() b.mu.Unlock() // An error had occurred earlier, don't accept more // data or errors. @@ -148,45 +129,97 @@ type recvBufferReader struct { ctx context.Context ctxDone <-chan struct{} // cache of ctx.Done() (for performance). recv *recvBuffer - last *bytes.Buffer // Stores the remaining data in the previous calls. + last mem.Buffer // Stores the remaining data in the previous calls. err error - freeBuffer func(*bytes.Buffer) } -// Read reads the next len(p) bytes from last. If last is drained, it tries to -// read additional data from recv. It blocks if there no additional data available -// in recv. If Read returns any non-nil error, it will continue to return that error. -func (r *recvBufferReader) Read(p []byte) (n int, err error) { +func (r *recvBufferReader) ReadHeader(header []byte) (n int, err error) { if r.err != nil { return 0, r.err } if r.last != nil { - // Read remaining data left in last call. - copied, _ := r.last.Read(p) - if r.last.Len() == 0 { - r.freeBuffer(r.last) + n, r.last = mem.ReadUnsafe(header, r.last) + return n, nil + } + if r.closeStream != nil { + n, r.err = r.readHeaderClient(header) + } else { + n, r.err = r.readHeader(header) + } + return n, r.err +} + +// Read reads the next n bytes from last. If last is drained, it tries to read +// additional data from recv. It blocks if there no additional data available in +// recv. If Read returns any non-nil error, it will continue to return that +// error. +func (r *recvBufferReader) Read(n int) (buf mem.Buffer, err error) { + if r.err != nil { + return nil, r.err + } + if r.last != nil { + buf = r.last + if r.last.Len() > n { + buf, r.last = mem.SplitUnsafe(buf, n) + } else { r.last = nil } - return copied, nil + return buf, nil } if r.closeStream != nil { - n, r.err = r.readClient(p) + buf, r.err = r.readClient(n) } else { - n, r.err = r.read(p) + buf, r.err = r.read(n) } - return n, r.err + return buf, r.err } -func (r *recvBufferReader) read(p []byte) (n int, err error) { +func (r *recvBufferReader) readHeader(header []byte) (n int, err error) { select { case <-r.ctxDone: return 0, ContextErr(r.ctx.Err()) case m := <-r.recv.get(): - return r.readAdditional(m, p) + return r.readHeaderAdditional(m, header) + } +} + +func (r *recvBufferReader) read(n int) (buf mem.Buffer, err error) { + select { + case <-r.ctxDone: + return nil, ContextErr(r.ctx.Err()) + case m := <-r.recv.get(): + return r.readAdditional(m, n) + } +} + +func (r *recvBufferReader) readHeaderClient(header []byte) (n int, err error) { + // If the context is canceled, then closes the stream with nil metadata. + // closeStream writes its error parameter to r.recv as a recvMsg. + // r.readAdditional acts on that message and returns the necessary error. + select { + case <-r.ctxDone: + // Note that this adds the ctx error to the end of recv buffer, and + // reads from the head. This will delay the error until recv buffer is + // empty, thus will delay ctx cancellation in Recv(). + // + // It's done this way to fix a race between ctx cancel and trailer. The + // race was, stream.Recv() may return ctx error if ctxDone wins the + // race, but stream.Trailer() may return a non-nil md because the stream + // was not marked as done when trailer is received. This closeStream + // call will mark stream as done, thus fix the race. + // + // TODO: delaying ctx error seems like a unnecessary side effect. What + // we really want is to mark the stream as done, and return ctx error + // faster. + r.closeStream(ContextErr(r.ctx.Err())) + m := <-r.recv.get() + return r.readHeaderAdditional(m, header) + case m := <-r.recv.get(): + return r.readHeaderAdditional(m, header) } } -func (r *recvBufferReader) readClient(p []byte) (n int, err error) { +func (r *recvBufferReader) readClient(n int) (buf mem.Buffer, err error) { // If the context is canceled, then closes the stream with nil metadata. // closeStream writes its error parameter to r.recv as a recvMsg. // r.readAdditional acts on that message and returns the necessary error. @@ -207,25 +240,40 @@ func (r *recvBufferReader) readClient(p []byte) (n int, err error) { // faster. r.closeStream(ContextErr(r.ctx.Err())) m := <-r.recv.get() - return r.readAdditional(m, p) + return r.readAdditional(m, n) case m := <-r.recv.get(): - return r.readAdditional(m, p) + return r.readAdditional(m, n) } } -func (r *recvBufferReader) readAdditional(m recvMsg, p []byte) (n int, err error) { +func (r *recvBufferReader) readHeaderAdditional(m recvMsg, header []byte) (n int, err error) { r.recv.load() if m.err != nil { + if m.buffer != nil { + m.buffer.Free() + } return 0, m.err } - copied, _ := m.buffer.Read(p) - if m.buffer.Len() == 0 { - r.freeBuffer(m.buffer) - r.last = nil - } else { - r.last = m.buffer + + n, r.last = mem.ReadUnsafe(header, m.buffer) + + return n, nil +} + +func (r *recvBufferReader) readAdditional(m recvMsg, n int) (b mem.Buffer, err error) { + r.recv.load() + if m.err != nil { + if m.buffer != nil { + m.buffer.Free() + } + return nil, m.err + } + + if m.buffer.Len() > n { + m.buffer, r.last = mem.SplitUnsafe(m.buffer, n) } - return copied, nil + + return m.buffer, nil } type streamState uint32 @@ -241,7 +289,7 @@ const ( type Stream struct { id uint32 st ServerTransport // nil for client side Stream - ct *http2Client // nil for server side Stream + ct ClientTransport // nil for server side Stream ctx context.Context // the associated context of the stream cancel context.CancelFunc // always nil for client side Stream done chan struct{} // closed at the end of stream to unblock writers. On the client side. @@ -251,7 +299,7 @@ type Stream struct { recvCompress string sendCompress string buf *recvBuffer - trReader io.Reader + trReader *transportReader fc *inFlow wq *writeQuota @@ -408,7 +456,7 @@ func (s *Stream) TrailersOnly() bool { return s.noHeaders } -// Trailer returns the cached trailer metedata. Note that if it is not called +// Trailer returns the cached trailer metadata. Note that if it is not called // after the entire stream is done, it could return an empty MD. Client // side only. // It can be safely read only after stream has ended that is either read @@ -499,36 +547,96 @@ func (s *Stream) write(m recvMsg) { s.buf.put(m) } -// Read reads all p bytes from the wire for this stream. -func (s *Stream) Read(p []byte) (n int, err error) { +// ReadHeader reads data into the provided header slice from the stream. It +// first checks if there was an error during a previous read operation and +// returns it if present. It then requests a read operation for the length of +// the header. It continues to read from the stream until the entire header +// slice is filled or an error occurs. If an `io.EOF` error is encountered +// with partially read data, it is converted to `io.ErrUnexpectedEOF` to +// indicate an unexpected end of the stream. The method returns any error +// encountered during the read process or nil if the header was successfully +// read. +func (s *Stream) ReadHeader(header []byte) (err error) { + // Don't request a read if there was an error earlier + if er := s.trReader.er; er != nil { + return er + } + s.requestRead(len(header)) + for len(header) != 0 { + n, err := s.trReader.ReadHeader(header) + header = header[n:] + if len(header) == 0 { + err = nil + } + if err != nil { + if n > 0 && err == io.EOF { + err = io.ErrUnexpectedEOF + } + return err + } + } + return nil +} + +// Read reads n bytes from the wire for this stream. +func (s *Stream) Read(n int) (data mem.BufferSlice, err error) { // Don't request a read if there was an error earlier - if er := s.trReader.(*transportReader).er; er != nil { - return 0, er + if er := s.trReader.er; er != nil { + return nil, er } - s.requestRead(len(p)) - return io.ReadFull(s.trReader, p) + s.requestRead(n) + for n != 0 { + buf, err := s.trReader.Read(n) + var bufLen int + if buf != nil { + bufLen = buf.Len() + } + n -= bufLen + if n == 0 { + err = nil + } + if err != nil { + if bufLen > 0 && err == io.EOF { + err = io.ErrUnexpectedEOF + } + data.Free() + return nil, err + } + data = append(data, buf) + } + return data, nil } -// tranportReader reads all the data available for this Stream from the transport and +// transportReader reads all the data available for this Stream from the transport and // passes them into the decoder, which converts them into a gRPC message stream. // The error is io.EOF when the stream is done or another non-nil error if // the stream broke. type transportReader struct { - reader io.Reader + reader *recvBufferReader // The handler to control the window update procedure for both this // particular stream and the associated transport. windowHandler func(int) er error } -func (t *transportReader) Read(p []byte) (n int, err error) { - n, err = t.reader.Read(p) +func (t *transportReader) ReadHeader(header []byte) (int, error) { + n, err := t.reader.ReadHeader(header) if err != nil { t.er = err - return + return 0, err } t.windowHandler(n) - return + return n, nil +} + +func (t *transportReader) Read(n int) (mem.Buffer, error) { + buf, err := t.reader.Read(n) + if err != nil { + t.er = err + return buf, err + } + t.windowHandler(buf.Len()) + return buf, nil } // BytesReceived indicates whether any bytes have been received on this stream. @@ -574,6 +682,7 @@ type ServerConfig struct { ChannelzParent *channelz.Server MaxHeaderListSize *uint32 HeaderTableSize *uint32 + BufferPool mem.BufferPool } // ConnectOptions covers all relevant options for communicating with the server. @@ -612,6 +721,8 @@ type ConnectOptions struct { MaxHeaderListSize *uint32 // UseProxy specifies if a proxy should be used. UseProxy bool + // The mem.BufferPool to use when reading/writing to the wire. + BufferPool mem.BufferPool } // NewClientTransport establishes the transport with the required ConnectOptions @@ -673,7 +784,7 @@ type ClientTransport interface { // Write sends the data for the given stream. A nil stream indicates // the write is to be performed on the transport as a whole. - Write(s *Stream, hdr []byte, data []byte, opts *Options) error + Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error // NewStream creates a Stream for an RPC. NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, error) @@ -725,7 +836,7 @@ type ServerTransport interface { // Write sends the data for the given stream. // Write may not be called on all streams. - Write(s *Stream, hdr []byte, data []byte, opts *Options) error + Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error // WriteStatus sends the status of a stream to the client. WriteStatus is // the final call made on a stream and always occurs. @@ -798,7 +909,7 @@ var ( // connection is draining. This could be caused by goaway or balancer // removing the address. errStreamDrain = status.Error(codes.Unavailable, "the connection is draining") - // errStreamDone is returned from write at the client side to indiacte application + // errStreamDone is returned from write at the client side to indicate application // layer of an error. errStreamDone = errors.New("the stream is done") // StatusGoAway indicates that the server sent a GOAWAY that included this diff --git a/go-controller/vendor/google.golang.org/grpc/keepalive/keepalive.go b/go-controller/vendor/google.golang.org/grpc/keepalive/keepalive.go index 34d31b5e7d..eb42b19fb9 100644 --- a/go-controller/vendor/google.golang.org/grpc/keepalive/keepalive.go +++ b/go-controller/vendor/google.golang.org/grpc/keepalive/keepalive.go @@ -34,15 +34,29 @@ type ClientParameters struct { // After a duration of this time if the client doesn't see any activity it // pings the server to see if the transport is still alive. // If set below 10s, a minimum value of 10s will be used instead. - Time time.Duration // The current default value is infinity. + // + // Note that gRPC servers have a default EnforcementPolicy.MinTime of 5 + // minutes (which means the client shouldn't ping more frequently than every + // 5 minutes). + // + // Though not ideal, it's not a strong requirement for Time to be less than + // EnforcementPolicy.MinTime. Time will automatically double if the server + // disconnects due to its enforcement policy. + // + // For more details, see + // https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md + Time time.Duration // After having pinged for keepalive check, the client waits for a duration // of Timeout and if no activity is seen even after that the connection is // closed. - Timeout time.Duration // The current default value is 20 seconds. + // + // If keepalive is enabled, and this value is not explicitly set, the default + // is 20 seconds. + Timeout time.Duration // If true, client sends keepalive pings even with no active RPCs. If false, // when there are no active RPCs, Time and Timeout will be ignored and no // keepalive pings will be sent. - PermitWithoutStream bool // false by default. + PermitWithoutStream bool } // ServerParameters is used to set keepalive and max-age parameters on the diff --git a/go-controller/vendor/google.golang.org/grpc/mem/buffer_pool.go b/go-controller/vendor/google.golang.org/grpc/mem/buffer_pool.go new file mode 100644 index 0000000000..c37c58c023 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/mem/buffer_pool.go @@ -0,0 +1,194 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 mem + +import ( + "sort" + "sync" + + "google.golang.org/grpc/internal" +) + +// BufferPool is a pool of buffers that can be shared and reused, resulting in +// decreased memory allocation. +type BufferPool interface { + // Get returns a buffer with specified length from the pool. + Get(length int) *[]byte + + // Put returns a buffer to the pool. + Put(*[]byte) +} + +var defaultBufferPoolSizes = []int{ + 256, + 4 << 10, // 4KB (go page size) + 16 << 10, // 16KB (max HTTP/2 frame size used by gRPC) + 32 << 10, // 32KB (default buffer size for io.Copy) + 1 << 20, // 1MB +} + +var defaultBufferPool BufferPool + +func init() { + defaultBufferPool = NewTieredBufferPool(defaultBufferPoolSizes...) + + internal.SetDefaultBufferPoolForTesting = func(pool BufferPool) { + defaultBufferPool = pool + } + + internal.SetBufferPoolingThresholdForTesting = func(threshold int) { + bufferPoolingThreshold = threshold + } +} + +// DefaultBufferPool returns the current default buffer pool. It is a BufferPool +// created with NewBufferPool that uses a set of default sizes optimized for +// expected workflows. +func DefaultBufferPool() BufferPool { + return defaultBufferPool +} + +// NewTieredBufferPool returns a BufferPool implementation that uses multiple +// underlying pools of the given pool sizes. +func NewTieredBufferPool(poolSizes ...int) BufferPool { + sort.Ints(poolSizes) + pools := make([]*sizedBufferPool, len(poolSizes)) + for i, s := range poolSizes { + pools[i] = newSizedBufferPool(s) + } + return &tieredBufferPool{ + sizedPools: pools, + } +} + +// tieredBufferPool implements the BufferPool interface with multiple tiers of +// buffer pools for different sizes of buffers. +type tieredBufferPool struct { + sizedPools []*sizedBufferPool + fallbackPool simpleBufferPool +} + +func (p *tieredBufferPool) Get(size int) *[]byte { + return p.getPool(size).Get(size) +} + +func (p *tieredBufferPool) Put(buf *[]byte) { + p.getPool(cap(*buf)).Put(buf) +} + +func (p *tieredBufferPool) getPool(size int) BufferPool { + poolIdx := sort.Search(len(p.sizedPools), func(i int) bool { + return p.sizedPools[i].defaultSize >= size + }) + + if poolIdx == len(p.sizedPools) { + return &p.fallbackPool + } + + return p.sizedPools[poolIdx] +} + +// sizedBufferPool is a BufferPool implementation that is optimized for specific +// buffer sizes. For example, HTTP/2 frames within gRPC have a default max size +// of 16kb and a sizedBufferPool can be configured to only return buffers with a +// capacity of 16kb. Note that however it does not support returning larger +// buffers and in fact panics if such a buffer is requested. Because of this, +// this BufferPool implementation is not meant to be used on its own and rather +// is intended to be embedded in a tieredBufferPool such that Get is only +// invoked when the required size is smaller than or equal to defaultSize. +type sizedBufferPool struct { + pool sync.Pool + defaultSize int +} + +func (p *sizedBufferPool) Get(size int) *[]byte { + buf := p.pool.Get().(*[]byte) + b := *buf + clear(b[:cap(b)]) + *buf = b[:size] + return buf +} + +func (p *sizedBufferPool) Put(buf *[]byte) { + if cap(*buf) < p.defaultSize { + // Ignore buffers that are too small to fit in the pool. Otherwise, when + // Get is called it will panic as it tries to index outside the bounds + // of the buffer. + return + } + p.pool.Put(buf) +} + +func newSizedBufferPool(size int) *sizedBufferPool { + return &sizedBufferPool{ + pool: sync.Pool{ + New: func() any { + buf := make([]byte, size) + return &buf + }, + }, + defaultSize: size, + } +} + +var _ BufferPool = (*simpleBufferPool)(nil) + +// simpleBufferPool is an implementation of the BufferPool interface that +// attempts to pool buffers with a sync.Pool. When Get is invoked, it tries to +// acquire a buffer from the pool but if that buffer is too small, it returns it +// to the pool and creates a new one. +type simpleBufferPool struct { + pool sync.Pool +} + +func (p *simpleBufferPool) Get(size int) *[]byte { + bs, ok := p.pool.Get().(*[]byte) + if ok && cap(*bs) >= size { + *bs = (*bs)[:size] + return bs + } + + // A buffer was pulled from the pool, but it is too small. Put it back in + // the pool and create one large enough. + if ok { + p.pool.Put(bs) + } + + b := make([]byte, size) + return &b +} + +func (p *simpleBufferPool) Put(buf *[]byte) { + p.pool.Put(buf) +} + +var _ BufferPool = NopBufferPool{} + +// NopBufferPool is a buffer pool that returns new buffers without pooling. +type NopBufferPool struct{} + +// Get returns a buffer with specified length from the pool. +func (NopBufferPool) Get(length int) *[]byte { + b := make([]byte, length) + return &b +} + +// Put returns a buffer to the pool. +func (NopBufferPool) Put(*[]byte) { +} diff --git a/go-controller/vendor/google.golang.org/grpc/mem/buffer_slice.go b/go-controller/vendor/google.golang.org/grpc/mem/buffer_slice.go new file mode 100644 index 0000000000..228e9c2f20 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/mem/buffer_slice.go @@ -0,0 +1,226 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 mem + +import ( + "io" +) + +// BufferSlice offers a means to represent data that spans one or more Buffer +// instances. A BufferSlice is meant to be immutable after creation, and methods +// like Ref create and return copies of the slice. This is why all methods have +// value receivers rather than pointer receivers. +// +// Note that any of the methods that read the underlying buffers such as Ref, +// Len or CopyTo etc., will panic if any underlying buffers have already been +// freed. It is recommended to not directly interact with any of the underlying +// buffers directly, rather such interactions should be mediated through the +// various methods on this type. +// +// By convention, any APIs that return (mem.BufferSlice, error) should reduce +// the burden on the caller by never returning a mem.BufferSlice that needs to +// be freed if the error is non-nil, unless explicitly stated. +type BufferSlice []Buffer + +// Len returns the sum of the length of all the Buffers in this slice. +// +// # Warning +// +// Invoking the built-in len on a BufferSlice will return the number of buffers +// in the slice, and *not* the value returned by this function. +func (s BufferSlice) Len() int { + var length int + for _, b := range s { + length += b.Len() + } + return length +} + +// Ref invokes Ref on each buffer in the slice. +func (s BufferSlice) Ref() { + for _, b := range s { + b.Ref() + } +} + +// Free invokes Buffer.Free() on each Buffer in the slice. +func (s BufferSlice) Free() { + for _, b := range s { + b.Free() + } +} + +// CopyTo copies each of the underlying Buffer's data into the given buffer, +// returning the number of bytes copied. Has the same semantics as the copy +// builtin in that it will copy as many bytes as it can, stopping when either dst +// is full or s runs out of data, returning the minimum of s.Len() and len(dst). +func (s BufferSlice) CopyTo(dst []byte) int { + off := 0 + for _, b := range s { + off += copy(dst[off:], b.ReadOnlyData()) + } + return off +} + +// Materialize concatenates all the underlying Buffer's data into a single +// contiguous buffer using CopyTo. +func (s BufferSlice) Materialize() []byte { + l := s.Len() + if l == 0 { + return nil + } + out := make([]byte, l) + s.CopyTo(out) + return out +} + +// MaterializeToBuffer functions like Materialize except that it writes the data +// to a single Buffer pulled from the given BufferPool. +// +// As a special case, if the input BufferSlice only actually has one Buffer, this +// function simply increases the refcount before returning said Buffer. Freeing this +// buffer won't release it until the BufferSlice is itself released. +func (s BufferSlice) MaterializeToBuffer(pool BufferPool) Buffer { + if len(s) == 1 { + s[0].Ref() + return s[0] + } + sLen := s.Len() + if sLen == 0 { + return emptyBuffer{} + } + buf := pool.Get(sLen) + s.CopyTo(*buf) + return NewBuffer(buf, pool) +} + +// Reader returns a new Reader for the input slice after taking references to +// each underlying buffer. +func (s BufferSlice) Reader() Reader { + s.Ref() + return &sliceReader{ + data: s, + len: s.Len(), + } +} + +// Reader exposes a BufferSlice's data as an io.Reader, allowing it to interface +// with other parts systems. It also provides an additional convenience method +// Remaining(), which returns the number of unread bytes remaining in the slice. +// Buffers will be freed as they are read. +type Reader interface { + io.Reader + io.ByteReader + // Close frees the underlying BufferSlice and never returns an error. Subsequent + // calls to Read will return (0, io.EOF). + Close() error + // Remaining returns the number of unread bytes remaining in the slice. + Remaining() int +} + +type sliceReader struct { + data BufferSlice + len int + // The index into data[0].ReadOnlyData(). + bufferIdx int +} + +func (r *sliceReader) Remaining() int { + return r.len +} + +func (r *sliceReader) Close() error { + r.data.Free() + r.data = nil + r.len = 0 + return nil +} + +func (r *sliceReader) freeFirstBufferIfEmpty() bool { + if len(r.data) == 0 || r.bufferIdx != len(r.data[0].ReadOnlyData()) { + return false + } + + r.data[0].Free() + r.data = r.data[1:] + r.bufferIdx = 0 + return true +} + +func (r *sliceReader) Read(buf []byte) (n int, _ error) { + if r.len == 0 { + return 0, io.EOF + } + + for len(buf) != 0 && r.len != 0 { + // Copy as much as possible from the first Buffer in the slice into the + // given byte slice. + data := r.data[0].ReadOnlyData() + copied := copy(buf, data[r.bufferIdx:]) + r.len -= copied // Reduce len by the number of bytes copied. + r.bufferIdx += copied // Increment the buffer index. + n += copied // Increment the total number of bytes read. + buf = buf[copied:] // Shrink the given byte slice. + + // If we have copied all the data from the first Buffer, free it and advance to + // the next in the slice. + r.freeFirstBufferIfEmpty() + } + + return n, nil +} + +func (r *sliceReader) ReadByte() (byte, error) { + if r.len == 0 { + return 0, io.EOF + } + + // There may be any number of empty buffers in the slice, clear them all until a + // non-empty buffer is reached. This is guaranteed to exit since r.len is not 0. + for r.freeFirstBufferIfEmpty() { + } + + b := r.data[0].ReadOnlyData()[r.bufferIdx] + r.len-- + r.bufferIdx++ + // Free the first buffer in the slice if the last byte was read + r.freeFirstBufferIfEmpty() + return b, nil +} + +var _ io.Writer = (*writer)(nil) + +type writer struct { + buffers *BufferSlice + pool BufferPool +} + +func (w *writer) Write(p []byte) (n int, err error) { + b := Copy(p, w.pool) + *w.buffers = append(*w.buffers, b) + return b.Len(), nil +} + +// NewWriter wraps the given BufferSlice and BufferPool to implement the +// io.Writer interface. Every call to Write copies the contents of the given +// buffer into a new Buffer pulled from the given pool and the Buffer is added to +// the given BufferSlice. +func NewWriter(buffers *BufferSlice, pool BufferPool) io.Writer { + return &writer{buffers: buffers, pool: pool} +} diff --git a/go-controller/vendor/google.golang.org/grpc/mem/buffers.go b/go-controller/vendor/google.golang.org/grpc/mem/buffers.go new file mode 100644 index 0000000000..ecbf0b9a73 --- /dev/null +++ b/go-controller/vendor/google.golang.org/grpc/mem/buffers.go @@ -0,0 +1,268 @@ +/* + * + * Copyright 2024 gRPC authors. + * + * 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 mem provides utilities that facilitate memory reuse in byte slices +// that are used as buffers. +// +// # Experimental +// +// Notice: All APIs in this package are EXPERIMENTAL and may be changed or +// removed in a later release. +package mem + +import ( + "fmt" + "sync" + "sync/atomic" +) + +// A Buffer represents a reference counted piece of data (in bytes) that can be +// acquired by a call to NewBuffer() or Copy(). A reference to a Buffer may be +// released by calling Free(), which invokes the free function given at creation +// only after all references are released. +// +// Note that a Buffer is not safe for concurrent access and instead each +// goroutine should use its own reference to the data, which can be acquired via +// a call to Ref(). +// +// Attempts to access the underlying data after releasing the reference to the +// Buffer will panic. +type Buffer interface { + // ReadOnlyData returns the underlying byte slice. Note that it is undefined + // behavior to modify the contents of this slice in any way. + ReadOnlyData() []byte + // Ref increases the reference counter for this Buffer. + Ref() + // Free decrements this Buffer's reference counter and frees the underlying + // byte slice if the counter reaches 0 as a result of this call. + Free() + // Len returns the Buffer's size. + Len() int + + split(n int) (left, right Buffer) + read(buf []byte) (int, Buffer) +} + +var ( + bufferPoolingThreshold = 1 << 10 + + bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }} + refObjectPool = sync.Pool{New: func() any { return new(atomic.Int32) }} +) + +// IsBelowBufferPoolingThreshold returns true if the given size is less than or +// equal to the threshold for buffer pooling. This is used to determine whether +// to pool buffers or allocate them directly. +func IsBelowBufferPoolingThreshold(size int) bool { + return size <= bufferPoolingThreshold +} + +type buffer struct { + origData *[]byte + data []byte + refs *atomic.Int32 + pool BufferPool +} + +func newBuffer() *buffer { + return bufferObjectPool.Get().(*buffer) +} + +// NewBuffer creates a new Buffer from the given data, initializing the reference +// counter to 1. The data will then be returned to the given pool when all +// references to the returned Buffer are released. As a special case to avoid +// additional allocations, if the given buffer pool is nil, the returned buffer +// will be a "no-op" Buffer where invoking Buffer.Free() does nothing and the +// underlying data is never freed. +// +// Note that the backing array of the given data is not copied. +func NewBuffer(data *[]byte, pool BufferPool) Buffer { + // Use the buffer's capacity instead of the length, otherwise buffers may + // not be reused under certain conditions. For example, if a large buffer + // is acquired from the pool, but fewer bytes than the buffering threshold + // are written to it, the buffer will not be returned to the pool. + if pool == nil || IsBelowBufferPoolingThreshold(cap(*data)) { + return (SliceBuffer)(*data) + } + b := newBuffer() + b.origData = data + b.data = *data + b.pool = pool + b.refs = refObjectPool.Get().(*atomic.Int32) + b.refs.Add(1) + return b +} + +// Copy creates a new Buffer from the given data, initializing the reference +// counter to 1. +// +// It acquires a []byte from the given pool and copies over the backing array +// of the given data. The []byte acquired from the pool is returned to the +// pool when all references to the returned Buffer are released. +func Copy(data []byte, pool BufferPool) Buffer { + if IsBelowBufferPoolingThreshold(len(data)) { + buf := make(SliceBuffer, len(data)) + copy(buf, data) + return buf + } + + buf := pool.Get(len(data)) + copy(*buf, data) + return NewBuffer(buf, pool) +} + +func (b *buffer) ReadOnlyData() []byte { + if b.refs == nil { + panic("Cannot read freed buffer") + } + return b.data +} + +func (b *buffer) Ref() { + if b.refs == nil { + panic("Cannot ref freed buffer") + } + b.refs.Add(1) +} + +func (b *buffer) Free() { + if b.refs == nil { + panic("Cannot free freed buffer") + } + + refs := b.refs.Add(-1) + switch { + case refs > 0: + return + case refs == 0: + if b.pool != nil { + b.pool.Put(b.origData) + } + + refObjectPool.Put(b.refs) + b.origData = nil + b.data = nil + b.refs = nil + b.pool = nil + bufferObjectPool.Put(b) + default: + panic("Cannot free freed buffer") + } +} + +func (b *buffer) Len() int { + return len(b.ReadOnlyData()) +} + +func (b *buffer) split(n int) (Buffer, Buffer) { + if b.refs == nil { + panic("Cannot split freed buffer") + } + + b.refs.Add(1) + split := newBuffer() + split.origData = b.origData + split.data = b.data[n:] + split.refs = b.refs + split.pool = b.pool + + b.data = b.data[:n] + + return b, split +} + +func (b *buffer) read(buf []byte) (int, Buffer) { + if b.refs == nil { + panic("Cannot read freed buffer") + } + + n := copy(buf, b.data) + if n == len(b.data) { + b.Free() + return n, nil + } + + b.data = b.data[n:] + return n, b +} + +func (b *buffer) String() string { + return fmt.Sprintf("mem.Buffer(%p, data: %p, length: %d)", b, b.ReadOnlyData(), len(b.ReadOnlyData())) +} + +// ReadUnsafe reads bytes from the given Buffer into the provided slice. +// It does not perform safety checks. +func ReadUnsafe(dst []byte, buf Buffer) (int, Buffer) { + return buf.read(dst) +} + +// SplitUnsafe modifies the receiver to point to the first n bytes while it +// returns a new reference to the remaining bytes. The returned Buffer +// functions just like a normal reference acquired using Ref(). +func SplitUnsafe(buf Buffer, n int) (left, right Buffer) { + return buf.split(n) +} + +type emptyBuffer struct{} + +func (e emptyBuffer) ReadOnlyData() []byte { + return nil +} + +func (e emptyBuffer) Ref() {} +func (e emptyBuffer) Free() {} + +func (e emptyBuffer) Len() int { + return 0 +} + +func (e emptyBuffer) split(int) (left, right Buffer) { + return e, e +} + +func (e emptyBuffer) read([]byte) (int, Buffer) { + return 0, e +} + +// SliceBuffer is a Buffer implementation that wraps a byte slice. It provides +// methods for reading, splitting, and managing the byte slice. +type SliceBuffer []byte + +// ReadOnlyData returns the byte slice. +func (s SliceBuffer) ReadOnlyData() []byte { return s } + +// Ref is a noop implementation of Ref. +func (s SliceBuffer) Ref() {} + +// Free is a noop implementation of Free. +func (s SliceBuffer) Free() {} + +// Len is a noop implementation of Len. +func (s SliceBuffer) Len() int { return len(s) } + +func (s SliceBuffer) split(n int) (left, right Buffer) { + return s[:n], s[n:] +} + +func (s SliceBuffer) read(buf []byte) (int, Buffer) { + n := copy(buf, s) + if n == len(s) { + return n, nil + } + return n, s[n:] +} diff --git a/go-controller/vendor/google.golang.org/grpc/metadata/metadata.go b/go-controller/vendor/google.golang.org/grpc/metadata/metadata.go index 1e9485fd6e..d2e15253bb 100644 --- a/go-controller/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/go-controller/vendor/google.golang.org/grpc/metadata/metadata.go @@ -213,11 +213,6 @@ func FromIncomingContext(ctx context.Context) (MD, bool) { // ValueFromIncomingContext returns the metadata value corresponding to the metadata // key from the incoming metadata if it exists. Keys are matched in a case insensitive // manner. -// -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a -// later release. func ValueFromIncomingContext(ctx context.Context, key string) []string { md, ok := ctx.Value(mdIncomingKey{}).(MD) if !ok { @@ -228,7 +223,7 @@ func ValueFromIncomingContext(ctx context.Context, key string) []string { return copyOf(v) } for k, v := range md { - // Case insenitive comparison: MD is a map, and there's no guarantee + // Case insensitive comparison: MD is a map, and there's no guarantee // that the MD attached to the context is created using our helper // functions. if strings.EqualFold(k, key) { diff --git a/go-controller/vendor/google.golang.org/grpc/preloader.go b/go-controller/vendor/google.golang.org/grpc/preloader.go index 73bd633643..e87a17f36a 100644 --- a/go-controller/vendor/google.golang.org/grpc/preloader.go +++ b/go-controller/vendor/google.golang.org/grpc/preloader.go @@ -20,6 +20,7 @@ package grpc import ( "google.golang.org/grpc/codes" + "google.golang.org/grpc/mem" "google.golang.org/grpc/status" ) @@ -31,9 +32,10 @@ import ( // later release. type PreparedMsg struct { // Struct for preparing msg before sending them - encodedData []byte + encodedData mem.BufferSlice hdr []byte - payload []byte + payload mem.BufferSlice + pf payloadFormat } // Encode marshalls and compresses the message using the codec and compressor for the stream. @@ -57,11 +59,27 @@ func (p *PreparedMsg) Encode(s Stream, msg any) error { if err != nil { return err } - p.encodedData = data - compData, err := compress(data, rpcInfo.preloaderInfo.cp, rpcInfo.preloaderInfo.comp) + + materializedData := data.Materialize() + data.Free() + p.encodedData = mem.BufferSlice{mem.NewBuffer(&materializedData, nil)} + + // TODO: it should be possible to grab the bufferPool from the underlying + // stream implementation with a type cast to its actual type (such as + // addrConnStream) and accessing the buffer pool directly. + var compData mem.BufferSlice + compData, p.pf, err = compress(p.encodedData, rpcInfo.preloaderInfo.cp, rpcInfo.preloaderInfo.comp, mem.DefaultBufferPool()) if err != nil { return err } - p.hdr, p.payload = msgHeader(data, compData) + + if p.pf.isCompressed() { + materializedCompData := compData.Materialize() + compData.Free() + compData = mem.BufferSlice{mem.NewBuffer(&materializedCompData, nil)} + } + + p.hdr, p.payload = msgHeader(p.encodedData, compData, p.pf) + return nil } diff --git a/go-controller/vendor/google.golang.org/grpc/regenerate.sh b/go-controller/vendor/google.golang.org/grpc/regenerate.sh deleted file mode 100644 index 3edca296c2..0000000000 --- a/go-controller/vendor/google.golang.org/grpc/regenerate.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -# Copyright 2020 gRPC authors. -# -# 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. - -set -eu -o pipefail - -WORKDIR=$(mktemp -d) - -function finish { - rm -rf "$WORKDIR" -} -trap finish EXIT - -export GOBIN=${WORKDIR}/bin -export PATH=${GOBIN}:${PATH} -mkdir -p ${GOBIN} - -echo "remove existing generated files" -# grpc_testing_not_regenerate/*.pb.go is not re-generated, -# see grpc_testing_not_regenerate/README.md for details. -rm -f $(find . -name '*.pb.go' | grep -v 'grpc_testing_not_regenerate') - -echo "go install google.golang.org/protobuf/cmd/protoc-gen-go" -(cd test/tools && go install google.golang.org/protobuf/cmd/protoc-gen-go) - -echo "go install cmd/protoc-gen-go-grpc" -(cd cmd/protoc-gen-go-grpc && go install .) - -echo "git clone https://github.com/grpc/grpc-proto" -git clone --quiet https://github.com/grpc/grpc-proto ${WORKDIR}/grpc-proto - -echo "git clone https://github.com/protocolbuffers/protobuf" -git clone --quiet https://github.com/protocolbuffers/protobuf ${WORKDIR}/protobuf - -# Pull in code.proto as a proto dependency -mkdir -p ${WORKDIR}/googleapis/google/rpc -echo "curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/rpc/code.proto" -curl --silent https://raw.githubusercontent.com/googleapis/googleapis/master/google/rpc/code.proto > ${WORKDIR}/googleapis/google/rpc/code.proto - -mkdir -p ${WORKDIR}/out - -# Generates sources without the embed requirement -LEGACY_SOURCES=( - ${WORKDIR}/grpc-proto/grpc/binlog/v1/binarylog.proto - ${WORKDIR}/grpc-proto/grpc/channelz/v1/channelz.proto - ${WORKDIR}/grpc-proto/grpc/health/v1/health.proto - ${WORKDIR}/grpc-proto/grpc/lb/v1/load_balancer.proto - profiling/proto/service.proto - ${WORKDIR}/grpc-proto/grpc/reflection/v1alpha/reflection.proto - ${WORKDIR}/grpc-proto/grpc/reflection/v1/reflection.proto -) - -# Generates only the new gRPC Service symbols -SOURCES=( - $(git ls-files --exclude-standard --cached --others "*.proto" | grep -v '^profiling/proto/service.proto$') - ${WORKDIR}/grpc-proto/grpc/gcp/altscontext.proto - ${WORKDIR}/grpc-proto/grpc/gcp/handshaker.proto - ${WORKDIR}/grpc-proto/grpc/gcp/transport_security_common.proto - ${WORKDIR}/grpc-proto/grpc/lookup/v1/rls.proto - ${WORKDIR}/grpc-proto/grpc/lookup/v1/rls_config.proto - ${WORKDIR}/grpc-proto/grpc/testing/*.proto - ${WORKDIR}/grpc-proto/grpc/core/*.proto -) - -# These options of the form 'Mfoo.proto=bar' instruct the codegen to use an -# import path of 'bar' in the generated code when 'foo.proto' is imported in -# one of the sources. -# -# Note that the protos listed here are all for testing purposes. All protos to -# be used externally should have a go_package option (and they don't need to be -# listed here). -OPTS=Mgrpc/core/stats.proto=google.golang.org/grpc/interop/grpc_testing/core,\ -Mgrpc/testing/benchmark_service.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/stats.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/report_qps_scenario_service.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/messages.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/worker_service.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/control.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/test.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/payloads.proto=google.golang.org/grpc/interop/grpc_testing,\ -Mgrpc/testing/empty.proto=google.golang.org/grpc/interop/grpc_testing - -for src in ${SOURCES[@]}; do - echo "protoc ${src}" - protoc --go_out=${OPTS}:${WORKDIR}/out --go-grpc_out=${OPTS},use_generic_streams_experimental=true:${WORKDIR}/out \ - -I"." \ - -I${WORKDIR}/grpc-proto \ - -I${WORKDIR}/googleapis \ - -I${WORKDIR}/protobuf/src \ - ${src} -done - -for src in ${LEGACY_SOURCES[@]}; do - echo "protoc ${src}" - protoc --go_out=${OPTS}:${WORKDIR}/out --go-grpc_out=${OPTS},require_unimplemented_servers=false:${WORKDIR}/out \ - -I"." \ - -I${WORKDIR}/grpc-proto \ - -I${WORKDIR}/googleapis \ - -I${WORKDIR}/protobuf/src \ - ${src} -done - -# The go_package option in grpc/lookup/v1/rls.proto doesn't match the -# current location. Move it into the right place. -mkdir -p ${WORKDIR}/out/google.golang.org/grpc/internal/proto/grpc_lookup_v1 -mv ${WORKDIR}/out/google.golang.org/grpc/lookup/grpc_lookup_v1/* ${WORKDIR}/out/google.golang.org/grpc/internal/proto/grpc_lookup_v1 - -# grpc_testing_not_regenerate/*.pb.go are not re-generated, -# see grpc_testing_not_regenerate/README.md for details. -rm ${WORKDIR}/out/google.golang.org/grpc/reflection/test/grpc_testing_not_regenerate/*.pb.go - -cp -R ${WORKDIR}/out/google.golang.org/grpc/* . diff --git a/go-controller/vendor/google.golang.org/grpc/resolver_wrapper.go b/go-controller/vendor/google.golang.org/grpc/resolver_wrapper.go index c5fb45236f..23bb3fb258 100644 --- a/go-controller/vendor/google.golang.org/grpc/resolver_wrapper.go +++ b/go-controller/vendor/google.golang.org/grpc/resolver_wrapper.go @@ -66,7 +66,7 @@ func newCCResolverWrapper(cc *ClientConn) *ccResolverWrapper { // any newly created ccResolverWrapper, except that close may be called instead. func (ccr *ccResolverWrapper) start() error { errCh := make(chan error) - ccr.serializer.Schedule(func(ctx context.Context) { + ccr.serializer.TrySchedule(func(ctx context.Context) { if ctx.Err() != nil { return } @@ -85,7 +85,7 @@ func (ccr *ccResolverWrapper) start() error { } func (ccr *ccResolverWrapper) resolveNow(o resolver.ResolveNowOptions) { - ccr.serializer.Schedule(func(ctx context.Context) { + ccr.serializer.TrySchedule(func(ctx context.Context) { if ctx.Err() != nil || ccr.resolver == nil { return } @@ -102,7 +102,7 @@ func (ccr *ccResolverWrapper) close() { ccr.closed = true ccr.mu.Unlock() - ccr.serializer.Schedule(func(context.Context) { + ccr.serializer.TrySchedule(func(context.Context) { if ccr.resolver == nil { return } @@ -177,6 +177,9 @@ func (ccr *ccResolverWrapper) ParseServiceConfig(scJSON string) *serviceconfig.P // addChannelzTraceEvent adds a channelz trace event containing the new // state received from resolver implementations. func (ccr *ccResolverWrapper) addChannelzTraceEvent(s resolver.State) { + if !logger.V(0) && !channelz.IsOn() { + return + } var updates []string var oldSC, newSC *ServiceConfig var oldOK, newOK bool diff --git a/go-controller/vendor/google.golang.org/grpc/rpc_util.go b/go-controller/vendor/google.golang.org/grpc/rpc_util.go index fdd49e6e91..aba1ae3e67 100644 --- a/go-controller/vendor/google.golang.org/grpc/rpc_util.go +++ b/go-controller/vendor/google.golang.org/grpc/rpc_util.go @@ -19,7 +19,6 @@ package grpc import ( - "bytes" "compress/gzip" "context" "encoding/binary" @@ -35,6 +34,7 @@ import ( "google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding/proto" "google.golang.org/grpc/internal/transport" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" @@ -220,8 +220,8 @@ type HeaderCallOption struct { HeaderAddr *metadata.MD } -func (o HeaderCallOption) before(c *callInfo) error { return nil } -func (o HeaderCallOption) after(c *callInfo, attempt *csAttempt) { +func (o HeaderCallOption) before(*callInfo) error { return nil } +func (o HeaderCallOption) after(_ *callInfo, attempt *csAttempt) { *o.HeaderAddr, _ = attempt.s.Header() } @@ -242,8 +242,8 @@ type TrailerCallOption struct { TrailerAddr *metadata.MD } -func (o TrailerCallOption) before(c *callInfo) error { return nil } -func (o TrailerCallOption) after(c *callInfo, attempt *csAttempt) { +func (o TrailerCallOption) before(*callInfo) error { return nil } +func (o TrailerCallOption) after(_ *callInfo, attempt *csAttempt) { *o.TrailerAddr = attempt.s.Trailer() } @@ -264,24 +264,20 @@ type PeerCallOption struct { PeerAddr *peer.Peer } -func (o PeerCallOption) before(c *callInfo) error { return nil } -func (o PeerCallOption) after(c *callInfo, attempt *csAttempt) { +func (o PeerCallOption) before(*callInfo) error { return nil } +func (o PeerCallOption) after(_ *callInfo, attempt *csAttempt) { if x, ok := peer.FromContext(attempt.s.Context()); ok { *o.PeerAddr = *x } } -// WaitForReady configures the action to take when an RPC is attempted on broken -// connections or unreachable servers. If waitForReady is false and the -// connection is in the TRANSIENT_FAILURE state, the RPC will fail -// immediately. Otherwise, the RPC client will block the call until a -// connection is available (or the call is canceled or times out) and will -// retry the call if it fails due to a transient error. gRPC will not retry if -// data was written to the wire unless the server indicates it did not process -// the data. Please refer to -// https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md. +// WaitForReady configures the RPC's behavior when the client is in +// TRANSIENT_FAILURE, which occurs when all addresses fail to connect. If +// waitForReady is false, the RPC will fail immediately. Otherwise, the client +// will wait until a connection becomes available or the RPC's deadline is +// reached. // -// By default, RPCs don't "wait for ready". +// By default, RPCs do not "wait for ready". func WaitForReady(waitForReady bool) CallOption { return FailFastCallOption{FailFast: !waitForReady} } @@ -308,7 +304,7 @@ func (o FailFastCallOption) before(c *callInfo) error { c.failFast = o.FailFast return nil } -func (o FailFastCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o FailFastCallOption) after(*callInfo, *csAttempt) {} // OnFinish returns a CallOption that configures a callback to be called when // the call completes. The error passed to the callback is the status of the @@ -343,7 +339,7 @@ func (o OnFinishCallOption) before(c *callInfo) error { return nil } -func (o OnFinishCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o OnFinishCallOption) after(*callInfo, *csAttempt) {} // MaxCallRecvMsgSize returns a CallOption which sets the maximum message size // in bytes the client can receive. If this is not set, gRPC uses the default @@ -367,7 +363,7 @@ func (o MaxRecvMsgSizeCallOption) before(c *callInfo) error { c.maxReceiveMessageSize = &o.MaxRecvMsgSize return nil } -func (o MaxRecvMsgSizeCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o MaxRecvMsgSizeCallOption) after(*callInfo, *csAttempt) {} // MaxCallSendMsgSize returns a CallOption which sets the maximum message size // in bytes the client can send. If this is not set, gRPC uses the default @@ -391,7 +387,7 @@ func (o MaxSendMsgSizeCallOption) before(c *callInfo) error { c.maxSendMessageSize = &o.MaxSendMsgSize return nil } -func (o MaxSendMsgSizeCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o MaxSendMsgSizeCallOption) after(*callInfo, *csAttempt) {} // PerRPCCredentials returns a CallOption that sets credentials.PerRPCCredentials // for a call. @@ -414,7 +410,7 @@ func (o PerRPCCredsCallOption) before(c *callInfo) error { c.creds = o.Creds return nil } -func (o PerRPCCredsCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o PerRPCCredsCallOption) after(*callInfo, *csAttempt) {} // UseCompressor returns a CallOption which sets the compressor used when // sending the request. If WithCompressor is also set, UseCompressor has @@ -442,7 +438,7 @@ func (o CompressorCallOption) before(c *callInfo) error { c.compressorType = o.CompressorType return nil } -func (o CompressorCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o CompressorCallOption) after(*callInfo, *csAttempt) {} // CallContentSubtype returns a CallOption that will set the content-subtype // for a call. For example, if content-subtype is "json", the Content-Type over @@ -479,7 +475,7 @@ func (o ContentSubtypeCallOption) before(c *callInfo) error { c.contentSubtype = o.ContentSubtype return nil } -func (o ContentSubtypeCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o ContentSubtypeCallOption) after(*callInfo, *csAttempt) {} // ForceCodec returns a CallOption that will set codec to be used for all // request and response messages for a call. The result of calling Name() will @@ -515,10 +511,50 @@ type ForceCodecCallOption struct { } func (o ForceCodecCallOption) before(c *callInfo) error { - c.codec = o.Codec + c.codec = newCodecV1Bridge(o.Codec) return nil } -func (o ForceCodecCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o ForceCodecCallOption) after(*callInfo, *csAttempt) {} + +// ForceCodecV2 returns a CallOption that will set codec to be used for all +// request and response messages for a call. The result of calling Name() will +// be used as the content-subtype after converting to lowercase, unless +// CallContentSubtype is also used. +// +// See Content-Type on +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. Also see the documentation on RegisterCodec and +// CallContentSubtype for more details on the interaction between Codec and +// content-subtype. +// +// This function is provided for advanced users; prefer to use only +// CallContentSubtype to select a registered codec instead. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func ForceCodecV2(codec encoding.CodecV2) CallOption { + return ForceCodecV2CallOption{CodecV2: codec} +} + +// ForceCodecV2CallOption is a CallOption that indicates the codec used for +// marshaling messages. +// +// # Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a +// later release. +type ForceCodecV2CallOption struct { + CodecV2 encoding.CodecV2 +} + +func (o ForceCodecV2CallOption) before(c *callInfo) error { + c.codec = o.CodecV2 + return nil +} + +func (o ForceCodecV2CallOption) after(*callInfo, *csAttempt) {} // CallCustomCodec behaves like ForceCodec, but accepts a grpc.Codec instead of // an encoding.Codec. @@ -540,10 +576,10 @@ type CustomCodecCallOption struct { } func (o CustomCodecCallOption) before(c *callInfo) error { - c.codec = o.Codec + c.codec = newCodecV0Bridge(o.Codec) return nil } -func (o CustomCodecCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o CustomCodecCallOption) after(*callInfo, *csAttempt) {} // MaxRetryRPCBufferSize returns a CallOption that limits the amount of memory // used for buffering this RPC's requests for retry purposes. @@ -571,7 +607,7 @@ func (o MaxRetryRPCBufferSizeCallOption) before(c *callInfo) error { c.maxRetryRPCBufferSize = o.MaxRetryRPCBufferSize return nil } -func (o MaxRetryRPCBufferSizeCallOption) after(c *callInfo, attempt *csAttempt) {} +func (o MaxRetryRPCBufferSizeCallOption) after(*callInfo, *csAttempt) {} // The format of the payload: compressed or not? type payloadFormat uint8 @@ -581,19 +617,28 @@ const ( compressionMade payloadFormat = 1 // compressed ) +func (pf payloadFormat) isCompressed() bool { + return pf == compressionMade +} + +type streamReader interface { + ReadHeader(header []byte) error + Read(n int) (mem.BufferSlice, error) +} + // parser reads complete gRPC messages from the underlying reader. type parser struct { // r is the underlying reader. // See the comment on recvMsg for the permissible // error types. - r io.Reader + r streamReader // The header of a gRPC message. Find more detail at // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md header [5]byte - // recvBufferPool is the pool of shared receive buffers. - recvBufferPool SharedBufferPool + // bufferPool is the pool of shared receive buffers. + bufferPool mem.BufferPool } // recvMsg reads a complete gRPC message from the stream. @@ -608,14 +653,15 @@ type parser struct { // - an error from the status package // // No other error values or types must be returned, which also means -// that the underlying io.Reader must not return an incompatible +// that the underlying streamReader must not return an incompatible // error. -func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byte, err error) { - if _, err := p.r.Read(p.header[:]); err != nil { +func (p *parser) recvMsg(maxReceiveMessageSize int) (payloadFormat, mem.BufferSlice, error) { + err := p.r.ReadHeader(p.header[:]) + if err != nil { return 0, nil, err } - pf = payloadFormat(p.header[0]) + pf := payloadFormat(p.header[0]) length := binary.BigEndian.Uint32(p.header[1:]) if length == 0 { @@ -627,20 +673,21 @@ func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byt if int(length) > maxReceiveMessageSize { return 0, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", length, maxReceiveMessageSize) } - msg = p.recvBufferPool.Get(int(length)) - if _, err := p.r.Read(msg); err != nil { + + data, err := p.r.Read(int(length)) + if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return 0, nil, err } - return pf, msg, nil + return pf, data, nil } // encode serializes msg and returns a buffer containing the message, or an // error if it is too large to be transmitted by grpc. If msg is nil, it // generates an empty message. -func encode(c baseCodec, msg any) ([]byte, error) { +func encode(c baseCodec, msg any) (mem.BufferSlice, error) { if msg == nil { // NOTE: typed nils will not be caught by this check return nil, nil } @@ -648,7 +695,8 @@ func encode(c baseCodec, msg any) ([]byte, error) { if err != nil { return nil, status.Errorf(codes.Internal, "grpc: error while marshaling: %v", err.Error()) } - if uint(len(b)) > math.MaxUint32 { + if uint(b.Len()) > math.MaxUint32 { + b.Free() return nil, status.Errorf(codes.ResourceExhausted, "grpc: message too large (%d bytes)", len(b)) } return b, nil @@ -659,34 +707,41 @@ func encode(c baseCodec, msg any) ([]byte, error) { // indicating no compression was done. // // TODO(dfawley): eliminate cp parameter by wrapping Compressor in an encoding.Compressor. -func compress(in []byte, cp Compressor, compressor encoding.Compressor) ([]byte, error) { - if compressor == nil && cp == nil { - return nil, nil - } - if len(in) == 0 { - return nil, nil +func compress(in mem.BufferSlice, cp Compressor, compressor encoding.Compressor, pool mem.BufferPool) (mem.BufferSlice, payloadFormat, error) { + if (compressor == nil && cp == nil) || in.Len() == 0 { + return nil, compressionNone, nil } + var out mem.BufferSlice + w := mem.NewWriter(&out, pool) wrapErr := func(err error) error { + out.Free() return status.Errorf(codes.Internal, "grpc: error while compressing: %v", err.Error()) } - cbuf := &bytes.Buffer{} if compressor != nil { - z, err := compressor.Compress(cbuf) + z, err := compressor.Compress(w) if err != nil { - return nil, wrapErr(err) + return nil, 0, wrapErr(err) } - if _, err := z.Write(in); err != nil { - return nil, wrapErr(err) + for _, b := range in { + if _, err := z.Write(b.ReadOnlyData()); err != nil { + return nil, 0, wrapErr(err) + } } if err := z.Close(); err != nil { - return nil, wrapErr(err) + return nil, 0, wrapErr(err) } } else { - if err := cp.Do(cbuf, in); err != nil { - return nil, wrapErr(err) + // This is obviously really inefficient since it fully materializes the data, but + // there is no way around this with the old Compressor API. At least it attempts + // to return the buffer to the provider, in the hopes it can be reused (maybe + // even by a subsequent call to this very function). + buf := in.MaterializeToBuffer(pool) + defer buf.Free() + if err := cp.Do(w, buf.ReadOnlyData()); err != nil { + return nil, 0, wrapErr(err) } } - return cbuf.Bytes(), nil + return out, compressionMade, nil } const ( @@ -697,33 +752,36 @@ const ( // msgHeader returns a 5-byte header for the message being transmitted and the // payload, which is compData if non-nil or data otherwise. -func msgHeader(data, compData []byte) (hdr []byte, payload []byte) { +func msgHeader(data, compData mem.BufferSlice, pf payloadFormat) (hdr []byte, payload mem.BufferSlice) { hdr = make([]byte, headerLen) - if compData != nil { - hdr[0] = byte(compressionMade) - data = compData + hdr[0] = byte(pf) + + var length uint32 + if pf.isCompressed() { + length = uint32(compData.Len()) + payload = compData } else { - hdr[0] = byte(compressionNone) + length = uint32(data.Len()) + payload = data } // Write length of payload into buf - binary.BigEndian.PutUint32(hdr[payloadLen:], uint32(len(data))) - return hdr, data + binary.BigEndian.PutUint32(hdr[payloadLen:], length) + return hdr, payload } -func outPayload(client bool, msg any, data, payload []byte, t time.Time) *stats.OutPayload { +func outPayload(client bool, msg any, dataLength, payloadLength int, t time.Time) *stats.OutPayload { return &stats.OutPayload{ Client: client, Payload: msg, - Data: data, - Length: len(data), - WireLength: len(payload) + headerLen, - CompressedLength: len(payload), + Length: dataLength, + WireLength: payloadLength + headerLen, + CompressedLength: payloadLength, SentTime: t, } } -func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool) *status.Status { +func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool, isServer bool) *status.Status { switch pf { case compressionNone: case compressionMade: @@ -731,7 +789,10 @@ func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool return status.New(codes.Internal, "grpc: compressed flag set with identity or empty encoding") } if !haveCompressor { - return status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress) + if isServer { + return status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress) + } + return status.Newf(codes.Internal, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress) } default: return status.Newf(codes.Internal, "grpc: received unexpected payload format %d", pf) @@ -741,104 +802,129 @@ func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool type payloadInfo struct { compressedLength int // The compressed length got from wire. - uncompressedBytes []byte + uncompressedBytes mem.BufferSlice +} + +func (p *payloadInfo) free() { + if p != nil && p.uncompressedBytes != nil { + p.uncompressedBytes.Free() + } } // recvAndDecompress reads a message from the stream, decompressing it if necessary. // // Cancelling the returned cancel function releases the buffer back to the pool. So the caller should cancel as soon as // the buffer is no longer needed. -func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, -) (uncompressedBuf []byte, cancel func(), err error) { - pf, compressedBuf, err := p.recvMsg(maxReceiveMessageSize) +// TODO: Refactor this function to reduce the number of arguments. +// See: https://google.github.io/styleguide/go/best-practices.html#function-argument-lists +func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool, +) (out mem.BufferSlice, err error) { + pf, compressed, err := p.recvMsg(maxReceiveMessageSize) if err != nil { - return nil, nil, err + return nil, err } - if st := checkRecvPayload(pf, s.RecvCompress(), compressor != nil || dc != nil); st != nil { - return nil, nil, st.Err() + compressedLength := compressed.Len() + + if st := checkRecvPayload(pf, s.RecvCompress(), compressor != nil || dc != nil, isServer); st != nil { + compressed.Free() + return nil, st.Err() } var size int - if pf == compressionMade { + if pf.isCompressed() { + defer compressed.Free() + // To match legacy behavior, if the decompressor is set by WithDecompressor or RPCDecompressor, // use this decompressor as the default. if dc != nil { - uncompressedBuf, err = dc.Do(bytes.NewReader(compressedBuf)) + var uncompressedBuf []byte + uncompressedBuf, err = dc.Do(compressed.Reader()) + if err == nil { + out = mem.BufferSlice{mem.NewBuffer(&uncompressedBuf, nil)} + } size = len(uncompressedBuf) } else { - uncompressedBuf, size, err = decompress(compressor, compressedBuf, maxReceiveMessageSize) + out, size, err = decompress(compressor, compressed, maxReceiveMessageSize, p.bufferPool) } if err != nil { - return nil, nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err) + return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err) } if size > maxReceiveMessageSize { + out.Free() // TODO: Revisit the error code. Currently keep it consistent with java // implementation. - return nil, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message after decompression larger than max (%d vs. %d)", size, maxReceiveMessageSize) + return nil, status.Errorf(codes.ResourceExhausted, "grpc: received message after decompression larger than max (%d vs. %d)", size, maxReceiveMessageSize) } } else { - uncompressedBuf = compressedBuf + out = compressed } if payInfo != nil { - payInfo.compressedLength = len(compressedBuf) - payInfo.uncompressedBytes = uncompressedBuf - - cancel = func() {} - } else { - cancel = func() { - p.recvBufferPool.Put(&compressedBuf) - } + payInfo.compressedLength = compressedLength + out.Ref() + payInfo.uncompressedBytes = out } - return uncompressedBuf, cancel, nil + return out, nil } // Using compressor, decompress d, returning data and size. // Optionally, if data will be over maxReceiveMessageSize, just return the size. -func decompress(compressor encoding.Compressor, d []byte, maxReceiveMessageSize int) ([]byte, int, error) { - dcReader, err := compressor.Decompress(bytes.NewReader(d)) +func decompress(compressor encoding.Compressor, d mem.BufferSlice, maxReceiveMessageSize int, pool mem.BufferPool) (mem.BufferSlice, int, error) { + dcReader, err := compressor.Decompress(d.Reader()) if err != nil { return nil, 0, err } - if sizer, ok := compressor.(interface { - DecompressedSize(compressedBytes []byte) int - }); ok { - if size := sizer.DecompressedSize(d); size >= 0 { - if size > maxReceiveMessageSize { - return nil, size, nil - } - // size is used as an estimate to size the buffer, but we - // will read more data if available. - // +MinRead so ReadFrom will not reallocate if size is correct. - // - // TODO: If we ensure that the buffer size is the same as the DecompressedSize, - // we can also utilize the recv buffer pool here. - buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead)) - bytesRead, err := buf.ReadFrom(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1)) - return buf.Bytes(), int(bytesRead), err - } + + // TODO: Can/should this still be preserved with the new BufferSlice API? Are + // there any actual benefits to allocating a single large buffer instead of + // multiple smaller ones? + //if sizer, ok := compressor.(interface { + // DecompressedSize(compressedBytes []byte) int + //}); ok { + // if size := sizer.DecompressedSize(d); size >= 0 { + // if size > maxReceiveMessageSize { + // return nil, size, nil + // } + // // size is used as an estimate to size the buffer, but we + // // will read more data if available. + // // +MinRead so ReadFrom will not reallocate if size is correct. + // // + // // TODO: If we ensure that the buffer size is the same as the DecompressedSize, + // // we can also utilize the recv buffer pool here. + // buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead)) + // bytesRead, err := buf.ReadFrom(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1)) + // return buf.Bytes(), int(bytesRead), err + // } + //} + + var out mem.BufferSlice + _, err = io.Copy(mem.NewWriter(&out, pool), io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1)) + if err != nil { + out.Free() + return nil, 0, err } - // Read from LimitReader with limit max+1. So if the underlying - // reader is over limit, the result will be bigger than max. - d, err = io.ReadAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1)) - return d, len(d), err + return out, out.Len(), nil } // For the two compressor parameters, both should not be set, but if they are, // dc takes precedence over compressor. // TODO(dfawley): wrap the old compressor/decompressor using the new API? -func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m any, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) error { - buf, cancel, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor) +func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m any, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool) error { + data, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor, isServer) if err != nil { return err } - defer cancel() - if err := c.Unmarshal(buf, m); err != nil { + // If the codec wants its own reference to the data, it can get it. Otherwise, always + // free the buffers. + defer data.Free() + + if err := c.Unmarshal(data, m); err != nil { return status.Errorf(codes.Internal, "grpc: failed to unmarshal the received message: %v", err) } + return nil } @@ -941,7 +1027,7 @@ func setCallInfoCodec(c *callInfo) error { // encoding.Codec (Name vs. String method name). We only support // setting content subtype from encoding.Codec to avoid a behavior // change with the deprecated version. - if ec, ok := c.codec.(encoding.Codec); ok { + if ec, ok := c.codec.(encoding.CodecV2); ok { c.contentSubtype = strings.ToLower(ec.Name()) } } @@ -950,12 +1036,12 @@ func setCallInfoCodec(c *callInfo) error { if c.contentSubtype == "" { // No codec specified in CallOptions; use proto by default. - c.codec = encoding.GetCodec(proto.Name) + c.codec = getCodec(proto.Name) return nil } // c.contentSubtype is already lowercased in CallContentSubtype - c.codec = encoding.GetCodec(c.contentSubtype) + c.codec = getCodec(c.contentSubtype) if c.codec == nil { return status.Errorf(codes.Internal, "no codec registered for content-subtype %s", c.contentSubtype) } diff --git a/go-controller/vendor/google.golang.org/grpc/server.go b/go-controller/vendor/google.golang.org/grpc/server.go index 89f8e4792b..d1e1415a40 100644 --- a/go-controller/vendor/google.golang.org/grpc/server.go +++ b/go-controller/vendor/google.golang.org/grpc/server.go @@ -45,6 +45,7 @@ import ( "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" @@ -80,7 +81,7 @@ func init() { } internal.BinaryLogger = binaryLogger internal.JoinServerOptions = newJoinServerOption - internal.RecvBufferPool = recvBufferPool + internal.BufferPool = bufferPool } var statusOK = status.New(codes.OK, "") @@ -170,7 +171,7 @@ type serverOptions struct { maxHeaderListSize *uint32 headerTableSize *uint32 numServerWorkers uint32 - recvBufferPool SharedBufferPool + bufferPool mem.BufferPool waitForHandlers bool } @@ -181,7 +182,7 @@ var defaultServerOptions = serverOptions{ connectionTimeout: 120 * time.Second, writeBufferSize: defaultWriteBufSize, readBufferSize: defaultReadBufSize, - recvBufferPool: nopBufferPool{}, + bufferPool: mem.DefaultBufferPool(), } var globalServerOptions []ServerOption @@ -313,7 +314,7 @@ func KeepaliveEnforcementPolicy(kep keepalive.EnforcementPolicy) ServerOption { // Will be supported throughout 1.x. func CustomCodec(codec Codec) ServerOption { return newFuncServerOption(func(o *serverOptions) { - o.codec = codec + o.codec = newCodecV0Bridge(codec) }) } @@ -342,7 +343,22 @@ func CustomCodec(codec Codec) ServerOption { // later release. func ForceServerCodec(codec encoding.Codec) ServerOption { return newFuncServerOption(func(o *serverOptions) { - o.codec = codec + o.codec = newCodecV1Bridge(codec) + }) +} + +// ForceServerCodecV2 is the equivalent of ForceServerCodec, but for the new +// CodecV2 interface. +// +// Will be supported throughout 1.x. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a +// later release. +func ForceServerCodecV2(codecV2 encoding.CodecV2) ServerOption { + return newFuncServerOption(func(o *serverOptions) { + o.codec = codecV2 }) } @@ -592,26 +608,9 @@ func WaitForHandlers(w bool) ServerOption { }) } -// RecvBufferPool returns a ServerOption that configures the server -// to use the provided shared buffer pool for parsing incoming messages. Depending -// on the application's workload, this could result in reduced memory allocation. -// -// If you are unsure about how to implement a memory pool but want to utilize one, -// begin with grpc.NewSharedBufferPool. -// -// Note: The shared buffer pool feature will not be active if any of the following -// options are used: StatsHandler, EnableTracing, or binary logging. In such -// cases, the shared buffer pool will be ignored. -// -// Deprecated: use experimental.WithRecvBufferPool instead. Will be deleted in -// v1.60.0 or later. -func RecvBufferPool(bufferPool SharedBufferPool) ServerOption { - return recvBufferPool(bufferPool) -} - -func recvBufferPool(bufferPool SharedBufferPool) ServerOption { +func bufferPool(bufferPool mem.BufferPool) ServerOption { return newFuncServerOption(func(o *serverOptions) { - o.recvBufferPool = bufferPool + o.bufferPool = bufferPool }) } @@ -622,7 +621,7 @@ func recvBufferPool(bufferPool SharedBufferPool) ServerOption { // workload (assuming a QPS of a few thousand requests/sec). const serverWorkerResetThreshold = 1 << 16 -// serverWorkers blocks on a *transport.Stream channel forever and waits for +// serverWorker blocks on a *transport.Stream channel forever and waits for // data to be fed by serveStreams. This allows multiple requests to be // processed by the same goroutine, removing the need for expensive stack // re-allocations (see the runtime.morestack problem [1]). @@ -980,6 +979,7 @@ func (s *Server) newHTTP2Transport(c net.Conn) transport.ServerTransport { ChannelzParent: s.channelz, MaxHeaderListSize: s.opts.maxHeaderListSize, HeaderTableSize: s.opts.headerTableSize, + BufferPool: s.opts.bufferPool, } st, err := transport.NewServerTransport(c, config) if err != nil { @@ -1072,7 +1072,7 @@ var _ http.Handler = (*Server)(nil) // Notice: This API is EXPERIMENTAL and may be changed or removed in a // later release. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - st, err := transport.NewServerHandlerTransport(w, r, s.opts.statsHandlers) + st, err := transport.NewServerHandlerTransport(w, r, s.opts.statsHandlers, s.opts.bufferPool) if err != nil { // Errors returned from transport.NewServerHandlerTransport have // already been written to w. @@ -1142,20 +1142,35 @@ func (s *Server) sendResponse(ctx context.Context, t transport.ServerTransport, channelz.Error(logger, s.channelz, "grpc: server failed to encode response: ", err) return err } - compData, err := compress(data, cp, comp) + + compData, pf, err := compress(data, cp, comp, s.opts.bufferPool) if err != nil { + data.Free() channelz.Error(logger, s.channelz, "grpc: server failed to compress response: ", err) return err } - hdr, payload := msgHeader(data, compData) + + hdr, payload := msgHeader(data, compData, pf) + + defer func() { + compData.Free() + data.Free() + // payload does not need to be freed here, it is either data or compData, both of + // which are already freed. + }() + + dataLen := data.Len() + payloadLen := payload.Len() // TODO(dfawley): should we be checking len(data) instead? - if len(payload) > s.opts.maxSendMessageSize { - return status.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max (%d vs. %d)", len(payload), s.opts.maxSendMessageSize) + if payloadLen > s.opts.maxSendMessageSize { + return status.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max (%d vs. %d)", payloadLen, s.opts.maxSendMessageSize) } err = t.Write(stream, hdr, payload, opts) if err == nil { - for _, sh := range s.opts.statsHandlers { - sh.HandleRPC(ctx, outPayload(false, msg, data, payload, time.Now())) + if len(s.opts.statsHandlers) != 0 { + for _, sh := range s.opts.statsHandlers { + sh.HandleRPC(ctx, outPayload(false, msg, dataLen, payloadLen, time.Now())) + } } } return err @@ -1334,37 +1349,37 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor var payInfo *payloadInfo if len(shs) != 0 || len(binlogs) != 0 { payInfo = &payloadInfo{} + defer payInfo.free() } - d, cancel, err := recvAndDecompress(&parser{r: stream, recvBufferPool: s.opts.recvBufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp) + d, err := recvAndDecompress(&parser{r: stream, bufferPool: s.opts.bufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp, true) if err != nil { if e := t.WriteStatus(stream, status.Convert(err)); e != nil { channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e) } return err } + defer d.Free() if channelz.IsOn() { t.IncrMsgRecv() } df := func(v any) error { - defer cancel() - if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil { return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err) } + for _, sh := range shs { sh.HandleRPC(ctx, &stats.InPayload{ RecvTime: time.Now(), Payload: v, - Length: len(d), + Length: d.Len(), WireLength: payInfo.compressedLength + headerLen, CompressedLength: payInfo.compressedLength, - Data: d, }) } if len(binlogs) != 0 { cm := &binarylog.ClientMessage{ - Message: d, + Message: d.Materialize(), } for _, binlog := range binlogs { binlog.Log(ctx, cm) @@ -1548,7 +1563,7 @@ func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTran ctx: ctx, t: t, s: stream, - p: &parser{r: stream, recvBufferPool: s.opts.recvBufferPool}, + p: &parser{r: stream, bufferPool: s.opts.bufferPool}, codec: s.getCodec(stream.ContentSubtype()), maxReceiveMessageSize: s.opts.maxReceiveMessageSize, maxSendMessageSize: s.opts.maxSendMessageSize, @@ -1963,12 +1978,12 @@ func (s *Server) getCodec(contentSubtype string) baseCodec { return s.opts.codec } if contentSubtype == "" { - return encoding.GetCodec(proto.Name) + return getCodec(proto.Name) } - codec := encoding.GetCodec(contentSubtype) + codec := getCodec(contentSubtype) if codec == nil { logger.Warningf("Unsupported codec %q. Defaulting to %q for now. This will start to fail in future releases.", contentSubtype, proto.Name) - return encoding.GetCodec(proto.Name) + return getCodec(proto.Name) } return codec } diff --git a/go-controller/vendor/google.golang.org/grpc/shared_buffer_pool.go b/go-controller/vendor/google.golang.org/grpc/shared_buffer_pool.go deleted file mode 100644 index 48a64cfe8e..0000000000 --- a/go-controller/vendor/google.golang.org/grpc/shared_buffer_pool.go +++ /dev/null @@ -1,154 +0,0 @@ -/* - * - * Copyright 2023 gRPC authors. - * - * 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 grpc - -import "sync" - -// SharedBufferPool is a pool of buffers that can be shared, resulting in -// decreased memory allocation. Currently, in gRPC-go, it is only utilized -// for parsing incoming messages. -// -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a -// later release. -type SharedBufferPool interface { - // Get returns a buffer with specified length from the pool. - // - // The returned byte slice may be not zero initialized. - Get(length int) []byte - - // Put returns a buffer to the pool. - Put(*[]byte) -} - -// NewSharedBufferPool creates a simple SharedBufferPool with buckets -// of different sizes to optimize memory usage. This prevents the pool from -// wasting large amounts of memory, even when handling messages of varying sizes. -// -// # Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a -// later release. -func NewSharedBufferPool() SharedBufferPool { - return &simpleSharedBufferPool{ - pools: [poolArraySize]simpleSharedBufferChildPool{ - newBytesPool(level0PoolMaxSize), - newBytesPool(level1PoolMaxSize), - newBytesPool(level2PoolMaxSize), - newBytesPool(level3PoolMaxSize), - newBytesPool(level4PoolMaxSize), - newBytesPool(0), - }, - } -} - -// simpleSharedBufferPool is a simple implementation of SharedBufferPool. -type simpleSharedBufferPool struct { - pools [poolArraySize]simpleSharedBufferChildPool -} - -func (p *simpleSharedBufferPool) Get(size int) []byte { - return p.pools[p.poolIdx(size)].Get(size) -} - -func (p *simpleSharedBufferPool) Put(bs *[]byte) { - p.pools[p.poolIdx(cap(*bs))].Put(bs) -} - -func (p *simpleSharedBufferPool) poolIdx(size int) int { - switch { - case size <= level0PoolMaxSize: - return level0PoolIdx - case size <= level1PoolMaxSize: - return level1PoolIdx - case size <= level2PoolMaxSize: - return level2PoolIdx - case size <= level3PoolMaxSize: - return level3PoolIdx - case size <= level4PoolMaxSize: - return level4PoolIdx - default: - return levelMaxPoolIdx - } -} - -const ( - level0PoolMaxSize = 16 // 16 B - level1PoolMaxSize = level0PoolMaxSize * 16 // 256 B - level2PoolMaxSize = level1PoolMaxSize * 16 // 4 KB - level3PoolMaxSize = level2PoolMaxSize * 16 // 64 KB - level4PoolMaxSize = level3PoolMaxSize * 16 // 1 MB -) - -const ( - level0PoolIdx = iota - level1PoolIdx - level2PoolIdx - level3PoolIdx - level4PoolIdx - levelMaxPoolIdx - poolArraySize -) - -type simpleSharedBufferChildPool interface { - Get(size int) []byte - Put(any) -} - -type bufferPool struct { - sync.Pool - - defaultSize int -} - -func (p *bufferPool) Get(size int) []byte { - bs := p.Pool.Get().(*[]byte) - - if cap(*bs) < size { - p.Pool.Put(bs) - - return make([]byte, size) - } - - return (*bs)[:size] -} - -func newBytesPool(size int) simpleSharedBufferChildPool { - return &bufferPool{ - Pool: sync.Pool{ - New: func() any { - bs := make([]byte, size) - return &bs - }, - }, - defaultSize: size, - } -} - -// nopBufferPool is a buffer pool just makes new buffer without pooling. -type nopBufferPool struct { -} - -func (nopBufferPool) Get(length int) []byte { - return make([]byte, length) -} - -func (nopBufferPool) Put(*[]byte) { -} diff --git a/go-controller/vendor/google.golang.org/grpc/stats/stats.go b/go-controller/vendor/google.golang.org/grpc/stats/stats.go index fdb0bd6518..71195c4943 100644 --- a/go-controller/vendor/google.golang.org/grpc/stats/stats.go +++ b/go-controller/vendor/google.golang.org/grpc/stats/stats.go @@ -77,9 +77,6 @@ type InPayload struct { // the call to HandleRPC which provides the InPayload returns and must be // copied if needed later. Payload any - // Data is the serialized message payload. - // Deprecated: Data will be removed in the next release. - Data []byte // Length is the size of the uncompressed payload data. Does not include any // framing (gRPC or HTTP/2). @@ -150,9 +147,6 @@ type OutPayload struct { // the call to HandleRPC which provides the OutPayload returns and must be // copied if needed later. Payload any - // Data is the serialized message payload. - // Deprecated: Data will be removed in the next release. - Data []byte // Length is the size of the uncompressed payload data. Does not include any // framing (gRPC or HTTP/2). Length int diff --git a/go-controller/vendor/google.golang.org/grpc/stream.go b/go-controller/vendor/google.golang.org/grpc/stream.go index 8051ef5b51..bb2b2a216c 100644 --- a/go-controller/vendor/google.golang.org/grpc/stream.go +++ b/go-controller/vendor/google.golang.org/grpc/stream.go @@ -41,6 +41,7 @@ import ( "google.golang.org/grpc/internal/serviceconfig" istatus "google.golang.org/grpc/internal/status" "google.golang.org/grpc/internal/transport" + "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" @@ -359,7 +360,7 @@ func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *Client cs.attempt = a return nil } - if err := cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op) }); err != nil { + if err := cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op, nil) }); err != nil { return nil, err } @@ -517,7 +518,7 @@ func (a *csAttempt) newStream() error { } a.s = s a.ctx = s.Context() - a.p = &parser{r: s, recvBufferPool: a.cs.cc.dopts.recvBufferPool} + a.p = &parser{r: s, bufferPool: a.cs.cc.dopts.copts.BufferPool} return nil } @@ -566,10 +567,15 @@ type clientStream struct { // place where we need to check if the attempt is nil. attempt *csAttempt // TODO(hedging): hedging will have multiple attempts simultaneously. - committed bool // active attempt committed for retry? - onCommit func() - buffer []func(a *csAttempt) error // operations to replay on retry - bufferSize int // current size of buffer + committed bool // active attempt committed for retry? + onCommit func() + replayBuffer []replayOp // operations to replay on retry + replayBufferSize int // current size of replayBuffer +} + +type replayOp struct { + op func(a *csAttempt) error + cleanup func() } // csAttempt implements a single transport stream attempt within a @@ -607,7 +613,12 @@ func (cs *clientStream) commitAttemptLocked() { cs.onCommit() } cs.committed = true - cs.buffer = nil + for _, op := range cs.replayBuffer { + if op.cleanup != nil { + op.cleanup() + } + } + cs.replayBuffer = nil } func (cs *clientStream) commitAttempt() { @@ -732,7 +743,7 @@ func (cs *clientStream) retryLocked(attempt *csAttempt, lastErr error) error { // the stream is canceled. return err } - // Note that the first op in the replay buffer always sets cs.attempt + // Note that the first op in replayBuffer always sets cs.attempt // if it is able to pick a transport and create a stream. if lastErr = cs.replayBufferLocked(attempt); lastErr == nil { return nil @@ -761,7 +772,7 @@ func (cs *clientStream) withRetry(op func(a *csAttempt) error, onSuccess func()) // already be status errors. return toRPCErr(op(cs.attempt)) } - if len(cs.buffer) == 0 { + if len(cs.replayBuffer) == 0 { // For the first op, which controls creation of the stream and // assigns cs.attempt, we need to create a new attempt inline // before executing the first op. On subsequent ops, the attempt @@ -851,25 +862,26 @@ func (cs *clientStream) Trailer() metadata.MD { } func (cs *clientStream) replayBufferLocked(attempt *csAttempt) error { - for _, f := range cs.buffer { - if err := f(attempt); err != nil { + for _, f := range cs.replayBuffer { + if err := f.op(attempt); err != nil { return err } } return nil } -func (cs *clientStream) bufferForRetryLocked(sz int, op func(a *csAttempt) error) { +func (cs *clientStream) bufferForRetryLocked(sz int, op func(a *csAttempt) error, cleanup func()) { // Note: we still will buffer if retry is disabled (for transparent retries). if cs.committed { return } - cs.bufferSize += sz - if cs.bufferSize > cs.callInfo.maxRetryRPCBufferSize { + cs.replayBufferSize += sz + if cs.replayBufferSize > cs.callInfo.maxRetryRPCBufferSize { cs.commitAttemptLocked() + cleanup() return } - cs.buffer = append(cs.buffer, op) + cs.replayBuffer = append(cs.replayBuffer, replayOp{op: op, cleanup: cleanup}) } func (cs *clientStream) SendMsg(m any) (err error) { @@ -891,23 +903,50 @@ func (cs *clientStream) SendMsg(m any) (err error) { } // load hdr, payload, data - hdr, payload, data, err := prepareMsg(m, cs.codec, cs.cp, cs.comp) + hdr, data, payload, pf, err := prepareMsg(m, cs.codec, cs.cp, cs.comp, cs.cc.dopts.copts.BufferPool) if err != nil { return err } + defer func() { + data.Free() + // only free payload if compression was made, and therefore it is a different set + // of buffers from data. + if pf.isCompressed() { + payload.Free() + } + }() + + dataLen := data.Len() + payloadLen := payload.Len() // TODO(dfawley): should we be checking len(data) instead? - if len(payload) > *cs.callInfo.maxSendMessageSize { - return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(payload), *cs.callInfo.maxSendMessageSize) + if payloadLen > *cs.callInfo.maxSendMessageSize { + return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", payloadLen, *cs.callInfo.maxSendMessageSize) } + + // always take an extra ref in case data == payload (i.e. when the data isn't + // compressed). The original ref will always be freed by the deferred free above. + payload.Ref() op := func(a *csAttempt) error { - return a.sendMsg(m, hdr, payload, data) + return a.sendMsg(m, hdr, payload, dataLen, payloadLen) + } + + // onSuccess is invoked when the op is captured for a subsequent retry. If the + // stream was established by a previous message and therefore retries are + // disabled, onSuccess will not be invoked, and payloadRef can be freed + // immediately. + onSuccessCalled := false + err = cs.withRetry(op, func() { + cs.bufferForRetryLocked(len(hdr)+payloadLen, op, payload.Free) + onSuccessCalled = true + }) + if !onSuccessCalled { + payload.Free() } - err = cs.withRetry(op, func() { cs.bufferForRetryLocked(len(hdr)+len(payload), op) }) if len(cs.binlogs) != 0 && err == nil { cm := &binarylog.ClientMessage{ OnClientSide: true, - Message: data, + Message: data.Materialize(), } for _, binlog := range cs.binlogs { binlog.Log(cs.ctx, cm) @@ -924,6 +963,7 @@ func (cs *clientStream) RecvMsg(m any) error { var recvInfo *payloadInfo if len(cs.binlogs) != 0 { recvInfo = &payloadInfo{} + defer recvInfo.free() } err := cs.withRetry(func(a *csAttempt) error { return a.recvMsg(m, recvInfo) @@ -931,7 +971,7 @@ func (cs *clientStream) RecvMsg(m any) error { if len(cs.binlogs) != 0 && err == nil { sm := &binarylog.ServerMessage{ OnClientSide: true, - Message: recvInfo.uncompressedBytes, + Message: recvInfo.uncompressedBytes.Materialize(), } for _, binlog := range cs.binlogs { binlog.Log(cs.ctx, sm) @@ -958,7 +998,7 @@ func (cs *clientStream) CloseSend() error { // RecvMsg. This also matches historical behavior. return nil } - cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op) }) + cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op, nil) }) if len(cs.binlogs) != 0 { chc := &binarylog.ClientHalfClose{ OnClientSide: true, @@ -1034,7 +1074,7 @@ func (cs *clientStream) finish(err error) { cs.cancel() } -func (a *csAttempt) sendMsg(m any, hdr, payld, data []byte) error { +func (a *csAttempt) sendMsg(m any, hdr []byte, payld mem.BufferSlice, dataLength, payloadLength int) error { cs := a.cs if a.trInfo != nil { a.mu.Lock() @@ -1052,8 +1092,10 @@ func (a *csAttempt) sendMsg(m any, hdr, payld, data []byte) error { } return io.EOF } - for _, sh := range a.statsHandlers { - sh.HandleRPC(a.ctx, outPayload(true, m, data, payld, time.Now())) + if len(a.statsHandlers) != 0 { + for _, sh := range a.statsHandlers { + sh.HandleRPC(a.ctx, outPayload(true, m, dataLength, payloadLength, time.Now())) + } } if channelz.IsOn() { a.t.IncrMsgSent() @@ -1065,6 +1107,7 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) { cs := a.cs if len(a.statsHandlers) != 0 && payInfo == nil { payInfo = &payloadInfo{} + defer payInfo.free() } if !a.decompSet { @@ -1083,8 +1126,7 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) { // Only initialize this state once per stream. a.decompSet = true } - err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, payInfo, a.decomp) - if err != nil { + if err := recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, payInfo, a.decomp, false); err != nil { if err == io.EOF { if statusErr := a.s.Status().Err(); statusErr != nil { return statusErr @@ -1103,14 +1145,12 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) { } for _, sh := range a.statsHandlers { sh.HandleRPC(a.ctx, &stats.InPayload{ - Client: true, - RecvTime: time.Now(), - Payload: m, - // TODO truncate large payload. - Data: payInfo.uncompressedBytes, + Client: true, + RecvTime: time.Now(), + Payload: m, WireLength: payInfo.compressedLength + headerLen, CompressedLength: payInfo.compressedLength, - Length: len(payInfo.uncompressedBytes), + Length: payInfo.uncompressedBytes.Len(), }) } if channelz.IsOn() { @@ -1122,14 +1162,12 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) { } // Special handling for non-server-stream rpcs. // This recv expects EOF or errors, so we don't collect inPayload. - err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, nil, a.decomp) - if err == nil { - return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) - } - if err == io.EOF { + if err := recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, nil, a.decomp, false); err == io.EOF { return a.s.Status().Err() // non-server streaming Recv returns nil on success + } else if err != nil { + return toRPCErr(err) } - return toRPCErr(err) + return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) } func (a *csAttempt) finish(err error) { @@ -1185,12 +1223,12 @@ func (a *csAttempt) finish(err error) { a.mu.Unlock() } -// newClientStream creates a ClientStream with the specified transport, on the +// newNonRetryClientStream creates a ClientStream with the specified transport, on the // given addrConn. // // It's expected that the given transport is either the same one in addrConn, or // is already closed. To avoid race, transport is specified separately, instead -// of using ac.transpot. +// of using ac.transport. // // Main difference between this and ClientConn.NewStream: // - no retry @@ -1276,7 +1314,7 @@ func newNonRetryClientStream(ctx context.Context, desc *StreamDesc, method strin return nil, err } as.s = s - as.p = &parser{r: s, recvBufferPool: ac.dopts.recvBufferPool} + as.p = &parser{r: s, bufferPool: ac.dopts.copts.BufferPool} ac.incrCallsStarted() if desc != unaryStreamDesc { // Listen on stream context to cleanup when the stream context is @@ -1373,17 +1411,26 @@ func (as *addrConnStream) SendMsg(m any) (err error) { } // load hdr, payload, data - hdr, payld, _, err := prepareMsg(m, as.codec, as.cp, as.comp) + hdr, data, payload, pf, err := prepareMsg(m, as.codec, as.cp, as.comp, as.ac.dopts.copts.BufferPool) if err != nil { return err } + defer func() { + data.Free() + // only free payload if compression was made, and therefore it is a different set + // of buffers from data. + if pf.isCompressed() { + payload.Free() + } + }() + // TODO(dfawley): should we be checking len(data) instead? - if len(payld) > *as.callInfo.maxSendMessageSize { - return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(payld), *as.callInfo.maxSendMessageSize) + if payload.Len() > *as.callInfo.maxSendMessageSize { + return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", payload.Len(), *as.callInfo.maxSendMessageSize) } - if err := as.t.Write(as.s, hdr, payld, &transport.Options{Last: !as.desc.ClientStreams}); err != nil { + if err := as.t.Write(as.s, hdr, payload, &transport.Options{Last: !as.desc.ClientStreams}); err != nil { if !as.desc.ClientStreams { // For non-client-streaming RPCs, we return nil instead of EOF on error // because the generated code requires it. finish is not called; RecvMsg() @@ -1423,8 +1470,7 @@ func (as *addrConnStream) RecvMsg(m any) (err error) { // Only initialize this state once per stream. as.decompSet = true } - err = recv(as.p, as.codec, as.s, as.dc, m, *as.callInfo.maxReceiveMessageSize, nil, as.decomp) - if err != nil { + if err := recv(as.p, as.codec, as.s, as.dc, m, *as.callInfo.maxReceiveMessageSize, nil, as.decomp, false); err != nil { if err == io.EOF { if statusErr := as.s.Status().Err(); statusErr != nil { return statusErr @@ -1444,14 +1490,12 @@ func (as *addrConnStream) RecvMsg(m any) (err error) { // Special handling for non-server-stream rpcs. // This recv expects EOF or errors, so we don't collect inPayload. - err = recv(as.p, as.codec, as.s, as.dc, m, *as.callInfo.maxReceiveMessageSize, nil, as.decomp) - if err == nil { - return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) - } - if err == io.EOF { + if err := recv(as.p, as.codec, as.s, as.dc, m, *as.callInfo.maxReceiveMessageSize, nil, as.decomp, false); err == io.EOF { return as.s.Status().Err() // non-server streaming Recv returns nil on success + } else if err != nil { + return toRPCErr(err) } - return toRPCErr(err) + return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) } func (as *addrConnStream) finish(err error) { @@ -1645,18 +1689,31 @@ func (ss *serverStream) SendMsg(m any) (err error) { } // load hdr, payload, data - hdr, payload, data, err := prepareMsg(m, ss.codec, ss.cp, ss.comp) + hdr, data, payload, pf, err := prepareMsg(m, ss.codec, ss.cp, ss.comp, ss.p.bufferPool) if err != nil { return err } + defer func() { + data.Free() + // only free payload if compression was made, and therefore it is a different set + // of buffers from data. + if pf.isCompressed() { + payload.Free() + } + }() + + dataLen := data.Len() + payloadLen := payload.Len() + // TODO(dfawley): should we be checking len(data) instead? - if len(payload) > ss.maxSendMessageSize { - return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(payload), ss.maxSendMessageSize) + if payloadLen > ss.maxSendMessageSize { + return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", payloadLen, ss.maxSendMessageSize) } if err := ss.t.Write(ss.s, hdr, payload, &transport.Options{Last: false}); err != nil { return toRPCErr(err) } + if len(ss.binlogs) != 0 { if !ss.serverHeaderBinlogged { h, _ := ss.s.Header() @@ -1669,7 +1726,7 @@ func (ss *serverStream) SendMsg(m any) (err error) { } } sm := &binarylog.ServerMessage{ - Message: data, + Message: data.Materialize(), } for _, binlog := range ss.binlogs { binlog.Log(ss.ctx, sm) @@ -1677,7 +1734,7 @@ func (ss *serverStream) SendMsg(m any) (err error) { } if len(ss.statsHandler) != 0 { for _, sh := range ss.statsHandler { - sh.HandleRPC(ss.s.Context(), outPayload(false, m, data, payload, time.Now())) + sh.HandleRPC(ss.s.Context(), outPayload(false, m, dataLen, payloadLen, time.Now())) } } return nil @@ -1714,8 +1771,9 @@ func (ss *serverStream) RecvMsg(m any) (err error) { var payInfo *payloadInfo if len(ss.statsHandler) != 0 || len(ss.binlogs) != 0 { payInfo = &payloadInfo{} + defer payInfo.free() } - if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxReceiveMessageSize, payInfo, ss.decomp); err != nil { + if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxReceiveMessageSize, payInfo, ss.decomp, true); err != nil { if err == io.EOF { if len(ss.binlogs) != 0 { chc := &binarylog.ClientHalfClose{} @@ -1733,11 +1791,9 @@ func (ss *serverStream) RecvMsg(m any) (err error) { if len(ss.statsHandler) != 0 { for _, sh := range ss.statsHandler { sh.HandleRPC(ss.s.Context(), &stats.InPayload{ - RecvTime: time.Now(), - Payload: m, - // TODO truncate large payload. - Data: payInfo.uncompressedBytes, - Length: len(payInfo.uncompressedBytes), + RecvTime: time.Now(), + Payload: m, + Length: payInfo.uncompressedBytes.Len(), WireLength: payInfo.compressedLength + headerLen, CompressedLength: payInfo.compressedLength, }) @@ -1745,7 +1801,7 @@ func (ss *serverStream) RecvMsg(m any) (err error) { } if len(ss.binlogs) != 0 { cm := &binarylog.ClientMessage{ - Message: payInfo.uncompressedBytes, + Message: payInfo.uncompressedBytes.Materialize(), } for _, binlog := range ss.binlogs { binlog.Log(ss.ctx, cm) @@ -1760,23 +1816,26 @@ func MethodFromServerStream(stream ServerStream) (string, bool) { return Method(stream.Context()) } -// prepareMsg returns the hdr, payload and data -// using the compressors passed or using the -// passed preparedmsg -func prepareMsg(m any, codec baseCodec, cp Compressor, comp encoding.Compressor) (hdr, payload, data []byte, err error) { +// prepareMsg returns the hdr, payload and data using the compressors passed or +// using the passed preparedmsg. The returned boolean indicates whether +// compression was made and therefore whether the payload needs to be freed in +// addition to the returned data. Freeing the payload if the returned boolean is +// false can lead to undefined behavior. +func prepareMsg(m any, codec baseCodec, cp Compressor, comp encoding.Compressor, pool mem.BufferPool) (hdr []byte, data, payload mem.BufferSlice, pf payloadFormat, err error) { if preparedMsg, ok := m.(*PreparedMsg); ok { - return preparedMsg.hdr, preparedMsg.payload, preparedMsg.encodedData, nil + return preparedMsg.hdr, preparedMsg.encodedData, preparedMsg.payload, preparedMsg.pf, nil } // The input interface is not a prepared msg. // Marshal and Compress the data at this point data, err = encode(codec, m) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, 0, err } - compData, err := compress(data, cp, comp) + compData, pf, err := compress(data, cp, comp, pool) if err != nil { - return nil, nil, nil, err + data.Free() + return nil, nil, nil, 0, err } - hdr, payload = msgHeader(data, compData) - return hdr, payload, data, nil + hdr, payload = msgHeader(data, compData, pf) + return hdr, data, payload, pf, nil } diff --git a/go-controller/vendor/google.golang.org/grpc/stream_interfaces.go b/go-controller/vendor/google.golang.org/grpc/stream_interfaces.go index 8b813529c0..0037fee0bd 100644 --- a/go-controller/vendor/google.golang.org/grpc/stream_interfaces.go +++ b/go-controller/vendor/google.golang.org/grpc/stream_interfaces.go @@ -22,15 +22,35 @@ package grpc // request, many responses) RPC. It is generic over the type of the response // message. It is used in generated code. type ServerStreamingClient[Res any] interface { + // Recv receives the next response message from the server. The client may + // repeatedly call Recv to read messages from the response stream. If + // io.EOF is returned, the stream has terminated with an OK status. Any + // other error is compatible with the status package and indicates the + // RPC's status code and message. Recv() (*Res, error) + + // ClientStream is embedded to provide Context, Header, and Trailer + // functionality. No other methods in the ClientStream should be called + // directly. ClientStream } // ServerStreamingServer represents the server side of a server-streaming (one // request, many responses) RPC. It is generic over the type of the response // message. It is used in generated code. +// +// To terminate the response stream, return from the handler method and return +// an error from the status package, or use nil to indicate an OK status code. type ServerStreamingServer[Res any] interface { + // Send sends a response message to the client. The server handler may + // call Send multiple times to send multiple messages to the client. An + // error is returned if the stream was terminated unexpectedly, and the + // handler method should return, as the stream is no longer usable. Send(*Res) error + + // ServerStream is embedded to provide Context, SetHeader, SendHeader, and + // SetTrailer functionality. No other methods in the ServerStream should + // be called directly. ServerStream } @@ -39,8 +59,22 @@ type ServerStreamingServer[Res any] interface { // message stream and the type of the unary response message. It is used in // generated code. type ClientStreamingClient[Req any, Res any] interface { + // Send sends a request message to the server. The client may call Send + // multiple times to send multiple messages to the server. On error, Send + // aborts the stream. If the error was generated by the client, the status + // is returned directly. Otherwise, io.EOF is returned, and the status of + // the stream may be discovered using CloseAndRecv(). Send(*Req) error + + // CloseAndRecv closes the request stream and waits for the server's + // response. This method must be called once and only once after sending + // all request messages. Any error returned is implemented by the status + // package. CloseAndRecv() (*Res, error) + + // ClientStream is embedded to provide Context, Header, and Trailer + // functionality. No other methods in the ClientStream should be called + // directly. ClientStream } @@ -48,9 +82,28 @@ type ClientStreamingClient[Req any, Res any] interface { // requests, one response) RPC. It is generic over both the type of the request // message stream and the type of the unary response message. It is used in // generated code. +// +// To terminate the RPC, call SendAndClose and return nil from the method +// handler or do not call SendAndClose and return an error from the status +// package. type ClientStreamingServer[Req any, Res any] interface { + // Recv receives the next request message from the client. The server may + // repeatedly call Recv to read messages from the request stream. If + // io.EOF is returned, it indicates the client called CloseAndRecv on its + // ClientStreamingClient. Any other error indicates the stream was + // terminated unexpectedly, and the handler method should return, as the + // stream is no longer usable. Recv() (*Req, error) + + // SendAndClose sends a single response message to the client and closes + // the stream. This method must be called once and only once after all + // request messages have been processed. Recv should not be called after + // calling SendAndClose. SendAndClose(*Res) error + + // ServerStream is embedded to provide Context, SetHeader, SendHeader, and + // SetTrailer functionality. No other methods in the ServerStream should + // be called directly. ServerStream } @@ -59,8 +112,23 @@ type ClientStreamingServer[Req any, Res any] interface { // request message stream and the type of the response message stream. It is // used in generated code. type BidiStreamingClient[Req any, Res any] interface { + // Send sends a request message to the server. The client may call Send + // multiple times to send multiple messages to the server. On error, Send + // aborts the stream. If the error was generated by the client, the status + // is returned directly. Otherwise, io.EOF is returned, and the status of + // the stream may be discovered using Recv(). Send(*Req) error + + // Recv receives the next response message from the server. The client may + // repeatedly call Recv to read messages from the response stream. If + // io.EOF is returned, the stream has terminated with an OK status. Any + // other error is compatible with the status package and indicates the + // RPC's status code and message. Recv() (*Res, error) + + // ClientStream is embedded to provide Context, Header, Trailer, and + // CloseSend functionality. No other methods in the ClientStream should be + // called directly. ClientStream } @@ -68,9 +136,27 @@ type BidiStreamingClient[Req any, Res any] interface { // (many requests, many responses) RPC. It is generic over both the type of the // request message stream and the type of the response message stream. It is // used in generated code. +// +// To terminate the stream, return from the handler method and return +// an error from the status package, or use nil to indicate an OK status code. type BidiStreamingServer[Req any, Res any] interface { + // Recv receives the next request message from the client. The server may + // repeatedly call Recv to read messages from the request stream. If + // io.EOF is returned, it indicates the client called CloseSend on its + // BidiStreamingClient. Any other error indicates the stream was + // terminated unexpectedly, and the handler method should return, as the + // stream is no longer usable. Recv() (*Req, error) + + // Send sends a response message to the client. The server handler may + // call Send multiple times to send multiple messages to the client. An + // error is returned if the stream was terminated unexpectedly, and the + // handler method should return, as the stream is no longer usable. Send(*Res) error + + // ServerStream is embedded to provide Context, SetHeader, SendHeader, and + // SetTrailer functionality. No other methods in the ServerStream should + // be called directly. ServerStream } diff --git a/go-controller/vendor/google.golang.org/grpc/version.go b/go-controller/vendor/google.golang.org/grpc/version.go index bafaef99be..5a47094ae8 100644 --- a/go-controller/vendor/google.golang.org/grpc/version.go +++ b/go-controller/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.65.0" +const Version = "1.68.1" diff --git a/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/decode.go b/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/decode.go index 8f9e592f87..737d6876d5 100644 --- a/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/decode.go +++ b/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/decode.go @@ -192,11 +192,6 @@ func (d decoder) unmarshalMessage(m protoreflect.Message, skipTypeURL bool) erro fd = fieldDescs.ByTextName(name) } } - if flags.ProtoLegacy { - if fd != nil && fd.IsWeak() && fd.Message().IsPlaceholder() { - fd = nil // reset since the weak reference is not linked in - } - } if fd == nil { // Field is unknown. diff --git a/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/well_known_types.go b/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/well_known_types.go index 4b177c8206..e9fe103943 100644 --- a/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/well_known_types.go +++ b/go-controller/vendor/google.golang.org/protobuf/encoding/protojson/well_known_types.go @@ -348,7 +348,11 @@ func (d decoder) unmarshalAnyValue(unmarshal unmarshalFunc, m protoreflect.Messa switch tok.Kind() { case json.ObjectClose: if !found { - return d.newError(tok.Pos(), `missing "value" field`) + // We tolerate an omitted `value` field with the google.protobuf.Empty Well-Known-Type, + // for compatibility with other proto runtimes that have interpreted the spec differently. + if m.Descriptor().FullName() != genid.Empty_message_fullname { + return d.newError(tok.Pos(), `missing "value" field`) + } } return nil diff --git a/go-controller/vendor/google.golang.org/protobuf/encoding/prototext/decode.go b/go-controller/vendor/google.golang.org/protobuf/encoding/prototext/decode.go index 24bc98ac42..b53805056a 100644 --- a/go-controller/vendor/google.golang.org/protobuf/encoding/prototext/decode.go +++ b/go-controller/vendor/google.golang.org/protobuf/encoding/prototext/decode.go @@ -185,11 +185,6 @@ func (d decoder) unmarshalMessage(m protoreflect.Message, checkDelims bool) erro } else if xtErr != nil && xtErr != protoregistry.NotFound { return d.newError(tok.Pos(), "unable to resolve [%s]: %v", tok.RawString(), xtErr) } - if flags.ProtoLegacy { - if fd != nil && fd.IsWeak() && fd.Message().IsPlaceholder() { - fd = nil // reset since the weak reference is not linked in - } - } // Handle unknown fields. if fd == nil { diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/editiondefaults/editions_defaults.binpb b/go-controller/vendor/google.golang.org/protobuf/internal/editiondefaults/editions_defaults.binpb index ff6a38360add36f53d48bb0863b701696e0d7b2d..5a57ef6f3c80a4a930b7bdb33b039ea94d1eb5f2 100644 GIT binary patch literal 138 zcmd;*muO*EV!mX@pe4$|D8MAaq`<7fXux#Ijt$6VkYMDJmv|0Wz$CyZ!KlClRKN&Q wzyMY7f?Y`%s2WL*1th1%ddZFnY{E-+C6MVz3P75fB^b3pHY+@1*LcYe04AXnGXMYp literal 93 zcmd;*mUzal#C*w)K}(Q>QGiK;Nr72|(SYfa9TNv5m$bxlxFnMRqXeS@6Ht;7B*_4j Ve8H{+(u69m1u{(G8N0>{b^xZ!4_5#H diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go b/go-controller/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go deleted file mode 100644 index 08dad7692c..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package editionssupport defines constants for editions that are supported. -package editionssupport - -import "google.golang.org/protobuf/types/descriptorpb" - -const ( - Minimum = descriptorpb.Edition_EDITION_PROTO2 - Maximum = descriptorpb.Edition_EDITION_2023 -) diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go b/go-controller/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go index 7e87c76044..669133d04d 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go @@ -26,7 +26,7 @@ var byteType = reflect.TypeOf(byte(0)) // The type is the underlying field type (e.g., a repeated field may be // represented by []T, but the Go type passed in is just T). // A list of enum value descriptors must be provided for enum fields. -// This does not populate the Enum or Message (except for weak message). +// This does not populate the Enum or Message. // // This function is a best effort attempt; parsing errors are ignored. func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescriptors) protoreflect.FieldDescriptor { @@ -109,9 +109,6 @@ func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescri } case s == "packed": f.L1.EditionFeatures.IsPacked = true - case strings.HasPrefix(s, "weak="): - f.L1.IsWeak = true - f.L1.Message = filedesc.PlaceholderMessage(protoreflect.FullName(s[len("weak="):])) case strings.HasPrefix(s, "def="): // The default tag is special in that everything afterwards is the // default regardless of the presence of commas. @@ -183,9 +180,6 @@ func Marshal(fd protoreflect.FieldDescriptor, enumName string) string { // the exact same semantics from the previous generator. tag = append(tag, "json="+jsonName) } - if fd.IsWeak() { - tag = append(tag, "weak="+string(fd.Message().FullName())) - } // The previous implementation does not tag extension fields as proto3, // even when the field is defined in a proto3 file. Match that behavior // for consistency. diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go112.go b/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go112.go deleted file mode 100644 index fbcd349207..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go112.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.13 -// +build !go1.13 - -package errors - -import "reflect" - -// Is is a copy of Go 1.13's errors.Is for use with older Go versions. -func Is(err, target error) bool { - if target == nil { - return err == target - } - - isComparable := reflect.TypeOf(target).Comparable() - for { - if isComparable && err == target { - return true - } - if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { - return true - } - if err = unwrap(err); err == nil { - return false - } - } -} - -func unwrap(err error) error { - u, ok := err.(interface { - Unwrap() error - }) - if !ok { - return nil - } - return u.Unwrap() -} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go113.go b/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go113.go deleted file mode 100644 index 5e72f1cde9..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/errors/is_go113.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.13 -// +build go1.13 - -package errors - -import "errors" - -// Is is errors.Is. -func Is(err, target error) bool { return errors.Is(err, target) } diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc.go b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc.go index fa790e0ff1..688aabe434 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc.go @@ -19,7 +19,6 @@ import ( "google.golang.org/protobuf/internal/pragma" "google.golang.org/protobuf/internal/strs" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" ) // Edition is an Enum for proto2.Edition @@ -32,6 +31,7 @@ const ( EditionProto2 Edition = 998 EditionProto3 Edition = 999 Edition2023 Edition = 1000 + Edition2024 Edition = 1001 EditionUnsupported Edition = 100000 ) @@ -77,31 +77,48 @@ type ( Locations SourceLocations } + // EditionFeatures is a frequently-instantiated struct, so please take care + // to minimize padding when adding new fields to this struct (add them in + // the right place/order). EditionFeatures struct { + // StripEnumPrefix determines if the plugin generates enum value + // constants as-is, with their prefix stripped, or both variants. + StripEnumPrefix int + // IsFieldPresence is true if field_presence is EXPLICIT // https://protobuf.dev/editions/features/#field_presence IsFieldPresence bool + // IsFieldPresence is true if field_presence is LEGACY_REQUIRED // https://protobuf.dev/editions/features/#field_presence IsLegacyRequired bool + // IsOpenEnum is true if enum_type is OPEN // https://protobuf.dev/editions/features/#enum_type IsOpenEnum bool + // IsPacked is true if repeated_field_encoding is PACKED // https://protobuf.dev/editions/features/#repeated_field_encoding IsPacked bool + // IsUTF8Validated is true if utf_validation is VERIFY // https://protobuf.dev/editions/features/#utf8_validation IsUTF8Validated bool + // IsDelimitedEncoded is true if message_encoding is DELIMITED // https://protobuf.dev/editions/features/#message_encoding IsDelimitedEncoded bool + // IsJSONCompliant is true if json_format is ALLOW // https://protobuf.dev/editions/features/#json_format IsJSONCompliant bool + // GenerateLegacyUnmarshalJSON determines if the plugin generates the // UnmarshalJSON([]byte) error method for enums. GenerateLegacyUnmarshalJSON bool + // APILevel controls which API (Open, Hybrid or Opaque) should be used + // for generated code (.pb.go files). + APILevel int } ) @@ -257,7 +274,6 @@ type ( Kind protoreflect.Kind StringName stringName IsProto3Optional bool // promoted from google.protobuf.FieldDescriptorProto - IsWeak bool // promoted from google.protobuf.FieldOptions IsLazy bool // promoted from google.protobuf.FieldOptions Default defaultValue ContainingOneof protoreflect.OneofDescriptor // must be consistent with Message.Oneofs.Fields @@ -351,7 +367,7 @@ func (fd *Field) IsPacked() bool { return fd.L1.EditionFeatures.IsPacked } func (fd *Field) IsExtension() bool { return false } -func (fd *Field) IsWeak() bool { return fd.L1.IsWeak } +func (fd *Field) IsWeak() bool { return false } func (fd *Field) IsLazy() bool { return fd.L1.IsLazy } func (fd *Field) IsList() bool { return fd.Cardinality() == protoreflect.Repeated && !fd.IsMap() } func (fd *Field) IsMap() bool { return fd.Message() != nil && fd.Message().IsMapEntry() } @@ -378,11 +394,6 @@ func (fd *Field) Enum() protoreflect.EnumDescriptor { return fd.L1.Enum } func (fd *Field) Message() protoreflect.MessageDescriptor { - if fd.L1.IsWeak { - if d, _ := protoregistry.GlobalFiles.FindDescriptorByName(fd.L1.Message.FullName()); d != nil { - return d.(protoreflect.MessageDescriptor) - } - } return fd.L1.Message } func (fd *Field) IsMapEntry() bool { diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go index 67a51b327c..d4c94458bd 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go @@ -32,11 +32,6 @@ func (file *File) resolveMessages() { for j := range md.L2.Fields.List { fd := &md.L2.Fields.List[j] - // Weak fields are resolved upon actual use. - if fd.L1.IsWeak { - continue - } - // Resolve message field dependency. switch fd.L1.Kind { case protoreflect.EnumKind: @@ -150,8 +145,6 @@ func (fd *File) unmarshalFull(b []byte) { switch num { case genid.FileDescriptorProto_PublicDependency_field_number: fd.L2.Imports[v].IsPublic = true - case genid.FileDescriptorProto_WeakDependency_field_number: - fd.L2.Imports[v].IsWeak = true } case protowire.BytesType: v, m := protowire.ConsumeBytes(b) @@ -502,8 +495,6 @@ func (fd *Field) unmarshalOptions(b []byte) { switch num { case genid.FieldOptions_Packed_field_number: fd.L1.EditionFeatures.IsPacked = protowire.DecodeBool(v) - case genid.FieldOptions_Weak_field_number: - fd.L1.IsWeak = protowire.DecodeBool(v) case genid.FieldOptions_Lazy_field_number: fd.L1.IsLazy = protowire.DecodeBool(v) case FieldOptions_EnforceUTF8: diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/editions.go b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/editions.go index fd4d0c83d2..10132c9b38 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/editions.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/filedesc/editions.go @@ -32,6 +32,14 @@ func unmarshalGoFeature(b []byte, parent EditionFeatures) EditionFeatures { v, m := protowire.ConsumeVarint(b) b = b[m:] parent.GenerateLegacyUnmarshalJSON = protowire.DecodeBool(v) + case genid.GoFeatures_ApiLevel_field_number: + v, m := protowire.ConsumeVarint(b) + b = b[m:] + parent.APILevel = int(v) + case genid.GoFeatures_StripEnumPrefix_field_number: + v, m := protowire.ConsumeVarint(b) + b = b[m:] + parent.StripEnumPrefix = int(v) default: panic(fmt.Sprintf("unkown field number %d while unmarshalling GoFeatures", num)) } diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/filetype/build.go b/go-controller/vendor/google.golang.org/protobuf/internal/filetype/build.go index ba83fea44c..e1b4130bd2 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/filetype/build.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/filetype/build.go @@ -63,7 +63,7 @@ type Builder struct { // message declarations in "flattened ordering". // // Dependencies are Go types for enums or messages referenced by - // message fields (excluding weak fields), for parent extended messages of + // message fields, for parent extended messages of // extension fields, for enums or messages referenced by extension fields, // and for input and output messages referenced by service methods. // Dependencies must come after declarations, but the ordering of diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/flags/flags.go b/go-controller/vendor/google.golang.org/protobuf/internal/flags/flags.go index 58372dd348..a06ccabc2f 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/flags/flags.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/flags/flags.go @@ -6,7 +6,7 @@ package flags // ProtoLegacy specifies whether to enable support for legacy functionality -// such as MessageSets, weak fields, and various other obscure behavior +// such as MessageSets, and various other obscure behavior // that is necessary to maintain backwards compatibility with proto1 or // the pre-release variants of proto2 and proto3. // diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go b/go-controller/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go index 7f67cbb6e9..f5ee7f5c2b 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go @@ -21,13 +21,47 @@ const ( // Field names for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_name protoreflect.Name = "legacy_unmarshal_json_enum" + GoFeatures_ApiLevel_field_name protoreflect.Name = "api_level" + GoFeatures_StripEnumPrefix_field_name protoreflect.Name = "strip_enum_prefix" GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "pb.GoFeatures.legacy_unmarshal_json_enum" + GoFeatures_ApiLevel_field_fullname protoreflect.FullName = "pb.GoFeatures.api_level" + GoFeatures_StripEnumPrefix_field_fullname protoreflect.FullName = "pb.GoFeatures.strip_enum_prefix" ) // Field numbers for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_number protoreflect.FieldNumber = 1 + GoFeatures_ApiLevel_field_number protoreflect.FieldNumber = 2 + GoFeatures_StripEnumPrefix_field_number protoreflect.FieldNumber = 3 +) + +// Full and short names for pb.GoFeatures.APILevel. +const ( + GoFeatures_APILevel_enum_fullname = "pb.GoFeatures.APILevel" + GoFeatures_APILevel_enum_name = "APILevel" +) + +// Enum values for pb.GoFeatures.APILevel. +const ( + GoFeatures_API_LEVEL_UNSPECIFIED_enum_value = 0 + GoFeatures_API_OPEN_enum_value = 1 + GoFeatures_API_HYBRID_enum_value = 2 + GoFeatures_API_OPAQUE_enum_value = 3 +) + +// Full and short names for pb.GoFeatures.StripEnumPrefix. +const ( + GoFeatures_StripEnumPrefix_enum_fullname = "pb.GoFeatures.StripEnumPrefix" + GoFeatures_StripEnumPrefix_enum_name = "StripEnumPrefix" +) + +// Enum values for pb.GoFeatures.StripEnumPrefix. +const ( + GoFeatures_STRIP_ENUM_PREFIX_UNSPECIFIED_enum_value = 0 + GoFeatures_STRIP_ENUM_PREFIX_KEEP_enum_value = 1 + GoFeatures_STRIP_ENUM_PREFIX_GENERATE_BOTH_enum_value = 2 + GoFeatures_STRIP_ENUM_PREFIX_STRIP_enum_value = 3 ) // Extension numbers diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/genid/goname.go b/go-controller/vendor/google.golang.org/protobuf/internal/genid/goname.go index 693d2e9e1f..99bb95bafd 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/genid/goname.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/genid/goname.go @@ -11,15 +11,10 @@ const ( SizeCache_goname = "sizeCache" SizeCacheA_goname = "XXX_sizecache" - WeakFields_goname = "weakFields" - WeakFieldsA_goname = "XXX_weak" - UnknownFields_goname = "unknownFields" UnknownFieldsA_goname = "XXX_unrecognized" ExtensionFields_goname = "extensionFields" ExtensionFieldsA_goname = "XXX_InternalExtensions" ExtensionFieldsB_goname = "XXX_extensions" - - WeakFieldPrefix_goname = "XXX_weak_" ) diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/genid/name.go b/go-controller/vendor/google.golang.org/protobuf/internal/genid/name.go new file mode 100644 index 0000000000..224f339302 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/genid/name.go @@ -0,0 +1,12 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package genid + +const ( + NoUnkeyedLiteral_goname = "noUnkeyedLiteral" + NoUnkeyedLiteralA_goname = "XXX_NoUnkeyedLiteral" + + BuilderSuffix_goname = "_builder" +) diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/api_export_opaque.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/api_export_opaque.go new file mode 100644 index 0000000000..6075d6f696 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/api_export_opaque.go @@ -0,0 +1,128 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "strconv" + "sync/atomic" + "unsafe" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +func (Export) UnmarshalField(msg any, fieldNum int32) { + UnmarshalField(msg.(protoreflect.ProtoMessage).ProtoReflect(), protoreflect.FieldNumber(fieldNum)) +} + +// Present checks the presence set for a certain field number (zero +// based, ordered by appearance in original proto file). part is +// a pointer to the correct element in the bitmask array, num is the +// field number unaltered. Example (field number 70 -> part = +// &m.XXX_presence[1], num = 70) +func (Export) Present(part *uint32, num uint32) bool { + // This hook will read an unprotected shadow presence set if + // we're unning under the race detector + raceDetectHookPresent(part, num) + return atomic.LoadUint32(part)&(1<<(num%32)) > 0 +} + +// SetPresent adds a field to the presence set. part is a pointer to +// the relevant element in the array and num is the field number +// unaltered. size is the number of fields in the protocol +// buffer. +func (Export) SetPresent(part *uint32, num uint32, size uint32) { + // This hook will mutate an unprotected shadow presence set if + // we're running under the race detector + raceDetectHookSetPresent(part, num, presenceSize(size)) + for { + old := atomic.LoadUint32(part) + if atomic.CompareAndSwapUint32(part, old, old|(1<<(num%32))) { + return + } + } +} + +// SetPresentNonAtomic is like SetPresent, but operates non-atomically. +// It is meant for use by builder methods, where the message is known not +// to be accessible yet by other goroutines. +func (Export) SetPresentNonAtomic(part *uint32, num uint32, size uint32) { + // This hook will mutate an unprotected shadow presence set if + // we're running under the race detector + raceDetectHookSetPresent(part, num, presenceSize(size)) + *part |= 1 << (num % 32) +} + +// ClearPresence removes a field from the presence set. part is a +// pointer to the relevant element in the presence array and num is +// the field number unaltered. +func (Export) ClearPresent(part *uint32, num uint32) { + // This hook will mutate an unprotected shadow presence set if + // we're running under the race detector + raceDetectHookClearPresent(part, num) + for { + old := atomic.LoadUint32(part) + if atomic.CompareAndSwapUint32(part, old, old&^(1<<(num%32))) { + return + } + } +} + +// interfaceToPointer takes a pointer to an empty interface whose value is a +// pointer type, and converts it into a "pointer" that points to the same +// target +func interfaceToPointer(i *any) pointer { + return pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]} +} + +func (p pointer) atomicGetPointer() pointer { + return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))} +} + +func (p pointer) atomicSetPointer(q pointer) { + atomic.StorePointer((*unsafe.Pointer)(p.p), q.p) +} + +// AtomicCheckPointerIsNil takes an interface (which is a pointer to a +// pointer) and returns true if the pointed-to pointer is nil (using an +// atomic load). This function is inlineable and, on x86, just becomes a +// simple load and compare. +func (Export) AtomicCheckPointerIsNil(ptr any) bool { + return interfaceToPointer(&ptr).atomicGetPointer().IsNil() +} + +// AtomicSetPointer takes two interfaces (first is a pointer to a pointer, +// second is a pointer) and atomically sets the second pointer into location +// referenced by first pointer. Unfortunately, atomicSetPointer() does not inline +// (even on x86), so this does not become a simple store on x86. +func (Export) AtomicSetPointer(dstPtr, valPtr any) { + interfaceToPointer(&dstPtr).atomicSetPointer(interfaceToPointer(&valPtr)) +} + +// AtomicLoadPointer loads the pointer at the location pointed at by src, +// and stores that pointer value into the location pointed at by dst. +func (Export) AtomicLoadPointer(ptr Pointer, dst Pointer) { + *(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr))) +} + +// AtomicInitializePointer makes ptr and dst point to the same value. +// +// If *ptr is a nil pointer, it sets *ptr = *dst. +// +// If *ptr is a non-nil pointer, it sets *dst = *ptr. +func (Export) AtomicInitializePointer(ptr Pointer, dst Pointer) { + if !atomic.CompareAndSwapPointer((*unsafe.Pointer)(ptr), unsafe.Pointer(nil), *(*unsafe.Pointer)(dst)) { + *(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr))) + } +} + +// MessageFieldStringOf returns the field formatted as a string, +// either as the field name if resolvable otherwise as a decimal string. +func (Export) MessageFieldStringOf(md protoreflect.MessageDescriptor, n protoreflect.FieldNumber) string { + fd := md.Fields().ByNumber(n) + if fd != nil { + return string(fd.Name()) + } + return strconv.Itoa(int(n)) +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap.go new file mode 100644 index 0000000000..ea276547cd --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !race + +package impl + +// There is no additional data as we're not running under race detector. +type RaceDetectHookData struct{} + +// Empty stubs for when not using the race detector. Calls to these from index.go should be optimized away. +func (presence) raceDetectHookPresent(num uint32) {} +func (presence) raceDetectHookSetPresent(num uint32, size presenceSize) {} +func (presence) raceDetectHookClearPresent(num uint32) {} +func (presence) raceDetectHookAllocAndCopy(src presence) {} + +// raceDetectHookPresent is called by the generated file interface +// (*proto.internalFuncs) Present to optionally read an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookPresent(field *uint32, num uint32) {} + +// raceDetectHookSetPresent is called by the generated file interface +// (*proto.internalFuncs) SetPresent to optionally write an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) {} + +// raceDetectHookClearPresent is called by the generated file interface +// (*proto.internalFuncs) ClearPresent to optionally write an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookClearPresent(field *uint32, num uint32) {} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap_race.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap_race.go new file mode 100644 index 0000000000..e9a27583ae --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/bitmap_race.go @@ -0,0 +1,126 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build race + +package impl + +// When running under race detector, we add a presence map of bytes, that we can access +// in the hook functions so that we trigger the race detection whenever we have concurrent +// Read-Writes or Write-Writes. The race detector does not otherwise detect invalid concurrent +// access to lazy fields as all updates of bitmaps and pointers are done using atomic operations. +type RaceDetectHookData struct { + shadowPresence *[]byte +} + +// Hooks for presence bitmap operations that allocate, read and write the shadowPresence +// using non-atomic operations. +func (data *RaceDetectHookData) raceDetectHookAlloc(size presenceSize) { + sp := make([]byte, size) + atomicStoreShadowPresence(&data.shadowPresence, &sp) +} + +func (p presence) raceDetectHookPresent(num uint32) { + data := p.toRaceDetectData() + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp != nil { + _ = (*sp)[num] + } +} + +func (p presence) raceDetectHookSetPresent(num uint32, size presenceSize) { + data := p.toRaceDetectData() + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp == nil { + data.raceDetectHookAlloc(size) + sp = atomicLoadShadowPresence(&data.shadowPresence) + } + (*sp)[num] = 1 +} + +func (p presence) raceDetectHookClearPresent(num uint32) { + data := p.toRaceDetectData() + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp != nil { + (*sp)[num] = 0 + + } +} + +// raceDetectHookAllocAndCopy allocates a new shadowPresence slice at lazy and copies +// shadowPresence bytes from src to lazy. +func (p presence) raceDetectHookAllocAndCopy(q presence) { + sData := q.toRaceDetectData() + dData := p.toRaceDetectData() + if sData == nil { + return + } + srcSp := atomicLoadShadowPresence(&sData.shadowPresence) + if srcSp == nil { + atomicStoreShadowPresence(&dData.shadowPresence, nil) + return + } + n := len(*srcSp) + dSlice := make([]byte, n) + atomicStoreShadowPresence(&dData.shadowPresence, &dSlice) + for i := 0; i < n; i++ { + dSlice[i] = (*srcSp)[i] + } +} + +// raceDetectHookPresent is called by the generated file interface +// (*proto.internalFuncs) Present to optionally read an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookPresent(field *uint32, num uint32) { + data := findPointerToRaceDetectData(field, num) + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp != nil { + _ = (*sp)[num] + } +} + +// raceDetectHookSetPresent is called by the generated file interface +// (*proto.internalFuncs) SetPresent to optionally write an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) { + data := findPointerToRaceDetectData(field, num) + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp == nil { + data.raceDetectHookAlloc(size) + sp = atomicLoadShadowPresence(&data.shadowPresence) + } + (*sp)[num] = 1 +} + +// raceDetectHookClearPresent is called by the generated file interface +// (*proto.internalFuncs) ClearPresent to optionally write an unprotected +// shadow bitmap when race detection is enabled. In regular code it is +// a noop. +func raceDetectHookClearPresent(field *uint32, num uint32) { + data := findPointerToRaceDetectData(field, num) + if data == nil { + return + } + sp := atomicLoadShadowPresence(&data.shadowPresence) + if sp != nil { + (*sp)[num] = 0 + } +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/checkinit.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/checkinit.go index f29e6a8fa8..fe2c719ce4 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/checkinit.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/checkinit.go @@ -35,6 +35,12 @@ func (mi *MessageInfo) checkInitializedPointer(p pointer) error { } return nil } + + var presence presence + if mi.presenceOffset.IsValid() { + presence = p.Apply(mi.presenceOffset).PresenceInfo() + } + if mi.extensionOffset.IsValid() { e := p.Apply(mi.extensionOffset).Extensions() if err := mi.isInitExtensions(e); err != nil { @@ -45,6 +51,33 @@ func (mi *MessageInfo) checkInitializedPointer(p pointer) error { if !f.isRequired && f.funcs.isInit == nil { continue } + + if f.presenceIndex != noPresence { + if !presence.Present(f.presenceIndex) { + if f.isRequired { + return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName())) + } + continue + } + if f.funcs.isInit != nil { + f.mi.init() + if f.mi.needsInitCheck { + if f.isLazy && p.Apply(f.offset).AtomicGetPointer().IsNil() { + lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr() + if !lazy.AllowedPartial() { + // Nothing to see here, it was checked on unmarshal + continue + } + mi.lazyUnmarshal(p, f.num) + } + if err := f.funcs.isInit(p.Apply(f.offset), f); err != nil { + return err + } + } + } + continue + } + fptr := p.Apply(f.offset) if f.isPointer && fptr.Elem().IsNil() { if f.isRequired { diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field.go index 7c1f66c8c1..d14d7d93cc 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field.go @@ -5,15 +5,12 @@ package impl import ( - "fmt" "reflect" - "sync" "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/internal/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/runtime/protoiface" ) @@ -121,78 +118,6 @@ func (mi *MessageInfo) initOneofFieldCoders(od protoreflect.OneofDescriptor, si } } -func makeWeakMessageFieldCoder(fd protoreflect.FieldDescriptor) pointerCoderFuncs { - var once sync.Once - var messageType protoreflect.MessageType - lazyInit := func() { - once.Do(func() { - messageName := fd.Message().FullName() - messageType, _ = protoregistry.GlobalTypes.FindMessageByName(messageName) - }) - } - - return pointerCoderFuncs{ - size: func(p pointer, f *coderFieldInfo, opts marshalOptions) int { - m, ok := p.WeakFields().get(f.num) - if !ok { - return 0 - } - lazyInit() - if messageType == nil { - panic(fmt.Sprintf("weak message %v is not linked in", fd.Message().FullName())) - } - return sizeMessage(m, f.tagsize, opts) - }, - marshal: func(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - m, ok := p.WeakFields().get(f.num) - if !ok { - return b, nil - } - lazyInit() - if messageType == nil { - panic(fmt.Sprintf("weak message %v is not linked in", fd.Message().FullName())) - } - return appendMessage(b, m, f.wiretag, opts) - }, - unmarshal: func(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (unmarshalOutput, error) { - fs := p.WeakFields() - m, ok := fs.get(f.num) - if !ok { - lazyInit() - if messageType == nil { - return unmarshalOutput{}, errUnknown - } - m = messageType.New().Interface() - fs.set(f.num, m) - } - return consumeMessage(b, m, wtyp, opts) - }, - isInit: func(p pointer, f *coderFieldInfo) error { - m, ok := p.WeakFields().get(f.num) - if !ok { - return nil - } - return proto.CheckInitialized(m) - }, - merge: func(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { - sm, ok := src.WeakFields().get(f.num) - if !ok { - return - } - dm, ok := dst.WeakFields().get(f.num) - if !ok { - lazyInit() - if messageType == nil { - panic(fmt.Sprintf("weak message %v is not linked in", fd.Message().FullName())) - } - dm = messageType.New().Interface() - dst.WeakFields().set(f.num, dm) - } - opts.Merge(dm, sm) - }, - } -} - func makeMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) pointerCoderFuncs { if mi := getMessageInfo(ft); mi != nil { funcs := pointerCoderFuncs{ diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field_opaque.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field_opaque.go new file mode 100644 index 0000000000..76818ea252 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_field_opaque.go @@ -0,0 +1,264 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "fmt" + "reflect" + + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/internal/errors" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func makeOpaqueMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) { + mi := getMessageInfo(ft) + if mi == nil { + panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), ft)) + } + switch fd.Kind() { + case protoreflect.MessageKind: + return mi, pointerCoderFuncs{ + size: sizeOpaqueMessage, + marshal: appendOpaqueMessage, + unmarshal: consumeOpaqueMessage, + isInit: isInitOpaqueMessage, + merge: mergeOpaqueMessage, + } + case protoreflect.GroupKind: + return mi, pointerCoderFuncs{ + size: sizeOpaqueGroup, + marshal: appendOpaqueGroup, + unmarshal: consumeOpaqueGroup, + isInit: isInitOpaqueMessage, + merge: mergeOpaqueMessage, + } + } + panic("unexpected field kind") +} + +func sizeOpaqueMessage(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { + return protowire.SizeBytes(f.mi.sizePointer(p.AtomicGetPointer(), opts)) + f.tagsize +} + +func appendOpaqueMessage(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { + mp := p.AtomicGetPointer() + calculatedSize := f.mi.sizePointer(mp, opts) + b = protowire.AppendVarint(b, f.wiretag) + b = protowire.AppendVarint(b, uint64(calculatedSize)) + before := len(b) + b, err := f.mi.marshalAppendPointer(b, mp, opts) + if measuredSize := len(b) - before; calculatedSize != measuredSize && err == nil { + return nil, errors.MismatchedSizeCalculation(calculatedSize, measuredSize) + } + return b, err +} + +func consumeOpaqueMessage(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if wtyp != protowire.BytesType { + return out, errUnknown + } + v, n := protowire.ConsumeBytes(b) + if n < 0 { + return out, errDecode + } + mp := p.AtomicGetPointer() + if mp.IsNil() { + mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))) + } + o, err := f.mi.unmarshalPointer(v, mp, 0, opts) + if err != nil { + return out, err + } + out.n = n + out.initialized = o.initialized + return out, nil +} + +func isInitOpaqueMessage(p pointer, f *coderFieldInfo) error { + mp := p.AtomicGetPointer() + if mp.IsNil() { + return nil + } + return f.mi.checkInitializedPointer(mp) +} + +func mergeOpaqueMessage(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { + dstmp := dst.AtomicGetPointer() + if dstmp.IsNil() { + dstmp = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))) + } + f.mi.mergePointer(dstmp, src.AtomicGetPointer(), opts) +} + +func sizeOpaqueGroup(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { + return 2*f.tagsize + f.mi.sizePointer(p.AtomicGetPointer(), opts) +} + +func appendOpaqueGroup(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { + b = protowire.AppendVarint(b, f.wiretag) // start group + b, err := f.mi.marshalAppendPointer(b, p.AtomicGetPointer(), opts) + b = protowire.AppendVarint(b, f.wiretag+1) // end group + return b, err +} + +func consumeOpaqueGroup(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if wtyp != protowire.StartGroupType { + return out, errUnknown + } + mp := p.AtomicGetPointer() + if mp.IsNil() { + mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))) + } + o, e := f.mi.unmarshalPointer(b, mp, f.num, opts) + return o, e +} + +func makeOpaqueRepeatedMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) { + if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice { + panic(fmt.Sprintf("invalid field: %v: unsupported type for opaque repeated message: %v", fd.FullName(), ft)) + } + mt := ft.Elem().Elem() // *[]*T -> *T + mi := getMessageInfo(mt) + if mi == nil { + panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), mt)) + } + switch fd.Kind() { + case protoreflect.MessageKind: + return mi, pointerCoderFuncs{ + size: sizeOpaqueMessageSlice, + marshal: appendOpaqueMessageSlice, + unmarshal: consumeOpaqueMessageSlice, + isInit: isInitOpaqueMessageSlice, + merge: mergeOpaqueMessageSlice, + } + case protoreflect.GroupKind: + return mi, pointerCoderFuncs{ + size: sizeOpaqueGroupSlice, + marshal: appendOpaqueGroupSlice, + unmarshal: consumeOpaqueGroupSlice, + isInit: isInitOpaqueMessageSlice, + merge: mergeOpaqueMessageSlice, + } + } + panic("unexpected field kind") +} + +func sizeOpaqueMessageSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { + s := p.AtomicGetPointer().PointerSlice() + n := 0 + for _, v := range s { + n += protowire.SizeBytes(f.mi.sizePointer(v, opts)) + f.tagsize + } + return n +} + +func appendOpaqueMessageSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { + s := p.AtomicGetPointer().PointerSlice() + var err error + for _, v := range s { + b = protowire.AppendVarint(b, f.wiretag) + siz := f.mi.sizePointer(v, opts) + b = protowire.AppendVarint(b, uint64(siz)) + before := len(b) + b, err = f.mi.marshalAppendPointer(b, v, opts) + if err != nil { + return b, err + } + if measuredSize := len(b) - before; siz != measuredSize { + return nil, errors.MismatchedSizeCalculation(siz, measuredSize) + } + } + return b, nil +} + +func consumeOpaqueMessageSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if wtyp != protowire.BytesType { + return out, errUnknown + } + v, n := protowire.ConsumeBytes(b) + if n < 0 { + return out, errDecode + } + mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())) + o, err := f.mi.unmarshalPointer(v, mp, 0, opts) + if err != nil { + return out, err + } + sp := p.AtomicGetPointer() + if sp.IsNil() { + sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem()))) + } + sp.AppendPointerSlice(mp) + out.n = n + out.initialized = o.initialized + return out, nil +} + +func isInitOpaqueMessageSlice(p pointer, f *coderFieldInfo) error { + sp := p.AtomicGetPointer() + if sp.IsNil() { + return nil + } + s := sp.PointerSlice() + for _, v := range s { + if err := f.mi.checkInitializedPointer(v); err != nil { + return err + } + } + return nil +} + +func mergeOpaqueMessageSlice(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { + ds := dst.AtomicGetPointer() + if ds.IsNil() { + ds = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem()))) + } + for _, sp := range src.AtomicGetPointer().PointerSlice() { + dm := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())) + f.mi.mergePointer(dm, sp, opts) + ds.AppendPointerSlice(dm) + } +} + +func sizeOpaqueGroupSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { + s := p.AtomicGetPointer().PointerSlice() + n := 0 + for _, v := range s { + n += 2*f.tagsize + f.mi.sizePointer(v, opts) + } + return n +} + +func appendOpaqueGroupSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { + s := p.AtomicGetPointer().PointerSlice() + var err error + for _, v := range s { + b = protowire.AppendVarint(b, f.wiretag) // start group + b, err = f.mi.marshalAppendPointer(b, v, opts) + if err != nil { + return b, err + } + b = protowire.AppendVarint(b, f.wiretag+1) // end group + } + return b, nil +} + +func consumeOpaqueGroupSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { + if wtyp != protowire.StartGroupType { + return out, errUnknown + } + mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())) + out, err = f.mi.unmarshalPointer(b, mp, f.num, opts) + if err != nil { + return out, err + } + sp := p.AtomicGetPointer() + if sp.IsNil() { + sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem()))) + } + sp.AppendPointerSlice(mp) + return out, err +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map.go index fb35f0bae9..229c698013 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map.go @@ -94,7 +94,7 @@ func sizeMap(mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo, opts marshalO return 0 } n := 0 - iter := mapRange(mapv) + iter := mapv.MapRange() for iter.Next() { key := mapi.conv.keyConv.PBValueOf(iter.Key()).MapKey() keySize := mapi.keyFuncs.size(key.Value(), mapKeyTagSize, opts) @@ -281,7 +281,7 @@ func appendMap(b []byte, mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo, o if opts.Deterministic() { return appendMapDeterministic(b, mapv, mapi, f, opts) } - iter := mapRange(mapv) + iter := mapv.MapRange() for iter.Next() { var err error b = protowire.AppendVarint(b, f.wiretag) @@ -328,7 +328,7 @@ func isInitMap(mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo) error { if !mi.needsInitCheck { return nil } - iter := mapRange(mapv) + iter := mapv.MapRange() for iter.Next() { val := pointerOfValue(iter.Value()) if err := mi.checkInitializedPointer(val); err != nil { @@ -336,7 +336,7 @@ func isInitMap(mapv reflect.Value, mapi *mapInfo, f *coderFieldInfo) error { } } } else { - iter := mapRange(mapv) + iter := mapv.MapRange() for iter.Next() { val := mapi.conv.valConv.PBValueOf(iter.Value()) if err := mapi.valFuncs.isInit(val); err != nil { @@ -356,7 +356,7 @@ func mergeMap(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { if dstm.IsNil() { dstm.Set(reflect.MakeMap(f.ft)) } - iter := mapRange(srcm) + iter := srcm.MapRange() for iter.Next() { dstm.SetMapIndex(iter.Key(), iter.Value()) } @@ -371,7 +371,7 @@ func mergeMapOfBytes(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { if dstm.IsNil() { dstm.Set(reflect.MakeMap(f.ft)) } - iter := mapRange(srcm) + iter := srcm.MapRange() for iter.Next() { dstm.SetMapIndex(iter.Key(), reflect.ValueOf(append(emptyBuf[:], iter.Value().Bytes()...))) } @@ -386,7 +386,7 @@ func mergeMapOfMessage(dst, src pointer, f *coderFieldInfo, opts mergeOptions) { if dstm.IsNil() { dstm.Set(reflect.MakeMap(f.ft)) } - iter := mapRange(srcm) + iter := srcm.MapRange() for iter.Next() { val := reflect.New(f.ft.Elem().Elem()) if f.mi != nil { diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go111.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go111.go deleted file mode 100644 index 4b15493f2f..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go111.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.12 -// +build !go1.12 - -package impl - -import "reflect" - -type mapIter struct { - v reflect.Value - keys []reflect.Value -} - -// mapRange provides a less-efficient equivalent to -// the Go 1.12 reflect.Value.MapRange method. -func mapRange(v reflect.Value) *mapIter { - return &mapIter{v: v} -} - -func (i *mapIter) Next() bool { - if i.keys == nil { - i.keys = i.v.MapKeys() - } else { - i.keys = i.keys[1:] - } - return len(i.keys) > 0 -} - -func (i *mapIter) Key() reflect.Value { - return i.keys[0] -} - -func (i *mapIter) Value() reflect.Value { - return i.v.MapIndex(i.keys[0]) -} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go112.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go112.go deleted file mode 100644 index 0b31b66eaf..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_map_go112.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.12 -// +build go1.12 - -package impl - -import "reflect" - -func mapRange(v reflect.Value) *reflect.MapIter { return v.MapRange() } diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message.go index 78be9df342..f78b57b046 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message.go @@ -32,6 +32,10 @@ type coderMessageInfo struct { needsInitCheck bool isMessageSet bool numRequiredFields uint8 + + lazyOffset offset + presenceOffset offset + presenceSize presenceSize } type coderFieldInfo struct { @@ -45,12 +49,19 @@ type coderFieldInfo struct { tagsize int // size of the varint-encoded tag isPointer bool // true if IsNil may be called on the struct field isRequired bool // true if field is required + + isLazy bool + presenceIndex uint32 } +const noPresence = 0xffffffff + func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) { mi.sizecacheOffset = invalidOffset mi.unknownOffset = invalidOffset mi.extensionOffset = invalidOffset + mi.lazyOffset = invalidOffset + mi.presenceOffset = si.presenceOffset if si.sizecacheOffset.IsValid() && si.sizecacheType == sizecacheType { mi.sizecacheOffset = si.sizecacheOffset @@ -107,12 +118,9 @@ func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) { }, } case isOneof: - fieldOffset = offsetOf(fs, mi.Exporter) - case fd.IsWeak(): - fieldOffset = si.weakOffset - funcs = makeWeakMessageFieldCoder(fd) + fieldOffset = offsetOf(fs) default: - fieldOffset = offsetOf(fs, mi.Exporter) + fieldOffset = offsetOf(fs) childMessage, funcs = fieldCoder(fd, ft) } cf := &preallocFields[i] @@ -127,6 +135,8 @@ func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) { validation: newFieldValidationInfo(mi, si, fd, ft), isPointer: fd.Cardinality() == protoreflect.Repeated || fd.HasPresence(), isRequired: fd.Cardinality() == protoreflect.Required, + + presenceIndex: noPresence, } mi.orderedCoderFields = append(mi.orderedCoderFields, cf) mi.coderFields[cf.num] = cf diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message_opaque.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message_opaque.go new file mode 100644 index 0000000000..41c1f74ef8 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/codec_message_opaque.go @@ -0,0 +1,153 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "fmt" + "reflect" + "sort" + + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/internal/encoding/messageset" + "google.golang.org/protobuf/internal/order" + "google.golang.org/protobuf/reflect/protoreflect" + piface "google.golang.org/protobuf/runtime/protoiface" +) + +func (mi *MessageInfo) makeOpaqueCoderMethods(t reflect.Type, si opaqueStructInfo) { + mi.sizecacheOffset = si.sizecacheOffset + mi.unknownOffset = si.unknownOffset + mi.unknownPtrKind = si.unknownType.Kind() == reflect.Ptr + mi.extensionOffset = si.extensionOffset + mi.lazyOffset = si.lazyOffset + mi.presenceOffset = si.presenceOffset + + mi.coderFields = make(map[protowire.Number]*coderFieldInfo) + fields := mi.Desc.Fields() + for i := 0; i < fields.Len(); i++ { + fd := fields.Get(i) + + fs := si.fieldsByNumber[fd.Number()] + if fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic() { + fs = si.oneofsByName[fd.ContainingOneof().Name()] + } + ft := fs.Type + var wiretag uint64 + if !fd.IsPacked() { + wiretag = protowire.EncodeTag(fd.Number(), wireTypes[fd.Kind()]) + } else { + wiretag = protowire.EncodeTag(fd.Number(), protowire.BytesType) + } + var fieldOffset offset + var funcs pointerCoderFuncs + var childMessage *MessageInfo + switch { + case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic(): + fieldOffset = offsetOf(fs) + case fd.Message() != nil && !fd.IsMap(): + fieldOffset = offsetOf(fs) + if fd.IsList() { + childMessage, funcs = makeOpaqueRepeatedMessageFieldCoder(fd, ft) + } else { + childMessage, funcs = makeOpaqueMessageFieldCoder(fd, ft) + } + default: + fieldOffset = offsetOf(fs) + childMessage, funcs = fieldCoder(fd, ft) + } + cf := &coderFieldInfo{ + num: fd.Number(), + offset: fieldOffset, + wiretag: wiretag, + ft: ft, + tagsize: protowire.SizeVarint(wiretag), + funcs: funcs, + mi: childMessage, + validation: newFieldValidationInfo(mi, si.structInfo, fd, ft), + isPointer: (fd.Cardinality() == protoreflect.Repeated || + fd.Kind() == protoreflect.MessageKind || + fd.Kind() == protoreflect.GroupKind), + isRequired: fd.Cardinality() == protoreflect.Required, + presenceIndex: noPresence, + } + + // TODO: Use presence for all fields. + // + // In some cases, such as maps, presence means only "might be set" rather + // than "is definitely set", but every field should have a presence bit to + // permit us to skip over definitely-unset fields at marshal time. + + var hasPresence bool + hasPresence, cf.isLazy = usePresenceForField(si, fd) + + if hasPresence { + cf.presenceIndex, mi.presenceSize = presenceIndex(mi.Desc, fd) + } + + mi.orderedCoderFields = append(mi.orderedCoderFields, cf) + mi.coderFields[cf.num] = cf + } + for i, oneofs := 0, mi.Desc.Oneofs(); i < oneofs.Len(); i++ { + if od := oneofs.Get(i); !od.IsSynthetic() { + mi.initOneofFieldCoders(od, si.structInfo) + } + } + if messageset.IsMessageSet(mi.Desc) { + if !mi.extensionOffset.IsValid() { + panic(fmt.Sprintf("%v: MessageSet with no extensions field", mi.Desc.FullName())) + } + if !mi.unknownOffset.IsValid() { + panic(fmt.Sprintf("%v: MessageSet with no unknown field", mi.Desc.FullName())) + } + mi.isMessageSet = true + } + sort.Slice(mi.orderedCoderFields, func(i, j int) bool { + return mi.orderedCoderFields[i].num < mi.orderedCoderFields[j].num + }) + + var maxDense protoreflect.FieldNumber + for _, cf := range mi.orderedCoderFields { + if cf.num >= 16 && cf.num >= 2*maxDense { + break + } + maxDense = cf.num + } + mi.denseCoderFields = make([]*coderFieldInfo, maxDense+1) + for _, cf := range mi.orderedCoderFields { + if int(cf.num) > len(mi.denseCoderFields) { + break + } + mi.denseCoderFields[cf.num] = cf + } + + // To preserve compatibility with historic wire output, marshal oneofs last. + if mi.Desc.Oneofs().Len() > 0 { + sort.Slice(mi.orderedCoderFields, func(i, j int) bool { + fi := fields.ByNumber(mi.orderedCoderFields[i].num) + fj := fields.ByNumber(mi.orderedCoderFields[j].num) + return order.LegacyFieldOrder(fi, fj) + }) + } + + mi.needsInitCheck = needsInitCheck(mi.Desc) + if mi.methods.Marshal == nil && mi.methods.Size == nil { + mi.methods.Flags |= piface.SupportMarshalDeterministic + mi.methods.Marshal = mi.marshal + mi.methods.Size = mi.size + } + if mi.methods.Unmarshal == nil { + mi.methods.Flags |= piface.SupportUnmarshalDiscardUnknown + mi.methods.Unmarshal = mi.unmarshal + } + if mi.methods.CheckInitialized == nil { + mi.methods.CheckInitialized = mi.checkInitialized + } + if mi.methods.Merge == nil { + mi.methods.Merge = mi.merge + } + if mi.methods.Equal == nil { + mi.methods.Equal = equal + } +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/convert_map.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/convert_map.go index 304244a651..e4580b3ac2 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/convert_map.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/convert_map.go @@ -101,7 +101,7 @@ func (ms *mapReflect) Mutable(k protoreflect.MapKey) protoreflect.Value { return v } func (ms *mapReflect) Range(f func(protoreflect.MapKey, protoreflect.Value) bool) { - iter := mapRange(ms.v) + iter := ms.v.MapRange() for iter.Next() { k := ms.keyConv.PBValueOf(iter.Key()).MapKey() v := ms.valConv.PBValueOf(iter.Value()) diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/decode.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/decode.go index cda0520c27..e0dd21fa5f 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/decode.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/decode.go @@ -34,6 +34,8 @@ func (o unmarshalOptions) Options() proto.UnmarshalOptions { AllowPartial: true, DiscardUnknown: o.DiscardUnknown(), Resolver: o.resolver, + + NoLazyDecoding: o.NoLazyDecoding(), } } @@ -41,13 +43,26 @@ func (o unmarshalOptions) DiscardUnknown() bool { return o.flags&protoiface.UnmarshalDiscardUnknown != 0 } -func (o unmarshalOptions) IsDefault() bool { - return o.flags == 0 && o.resolver == protoregistry.GlobalTypes +func (o unmarshalOptions) AliasBuffer() bool { return o.flags&protoiface.UnmarshalAliasBuffer != 0 } +func (o unmarshalOptions) Validated() bool { return o.flags&protoiface.UnmarshalValidated != 0 } +func (o unmarshalOptions) NoLazyDecoding() bool { + return o.flags&protoiface.UnmarshalNoLazyDecoding != 0 +} + +func (o unmarshalOptions) CanBeLazy() bool { + if o.resolver != protoregistry.GlobalTypes { + return false + } + // We ignore the UnmarshalInvalidateSizeCache even though it's not in the default set + return (o.flags & ^(protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated | protoiface.UnmarshalCheckRequired)) == 0 } var lazyUnmarshalOptions = unmarshalOptions{ resolver: protoregistry.GlobalTypes, - depth: protowire.DefaultRecursionLimit, + + flags: protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated, + + depth: protowire.DefaultRecursionLimit, } type unmarshalOutput struct { @@ -94,9 +109,30 @@ func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire. if flags.ProtoLegacy && mi.isMessageSet { return unmarshalMessageSet(mi, b, p, opts) } + + lazyDecoding := LazyEnabled() // default + if opts.NoLazyDecoding() { + lazyDecoding = false // explicitly disabled + } + if mi.lazyOffset.IsValid() && lazyDecoding { + return mi.unmarshalPointerLazy(b, p, groupTag, opts) + } + return mi.unmarshalPointerEager(b, p, groupTag, opts) +} + +// unmarshalPointerEager is the message unmarshalling function for all messages that are not lazy. +// The corresponding function for Lazy is in google_lazy.go. +func (mi *MessageInfo) unmarshalPointerEager(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) { + initialized := true var requiredMask uint64 var exts *map[int32]ExtensionField + + var presence presence + if mi.presenceOffset.IsValid() { + presence = p.Apply(mi.presenceOffset).PresenceInfo() + } + start := len(b) for len(b) > 0 { // Parse the tag (field number and wire type). @@ -154,6 +190,11 @@ func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire. if f.funcs.isInit != nil && !o.initialized { initialized = false } + + if f.presenceIndex != noPresence { + presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize) + } + default: // Possible extension. if exts == nil && mi.extensionOffset.IsValid() { @@ -222,7 +263,7 @@ func (mi *MessageInfo) unmarshalExtension(b []byte, num protowire.Number, wtyp p return out, errUnknown } if flags.LazyUnmarshalExtensions { - if opts.IsDefault() && x.canLazy(xt) { + if opts.CanBeLazy() && x.canLazy(xt) { out, valid := skipExtension(b, xi, num, wtyp, opts) switch valid { case ValidationValid: @@ -270,6 +311,13 @@ func skipExtension(b []byte, xi *extensionFieldInfo, num protowire.Number, wtyp if n < 0 { return out, ValidationUnknown } + + if opts.Validated() { + out.initialized = true + out.n = n + return out, ValidationValid + } + out, st := xi.validation.mi.validate(v, 0, opts) out.n = n return out, st diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/encode.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/encode.go index 6254f5de41..b2e212291d 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/encode.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/encode.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "google.golang.org/protobuf/internal/flags" + "google.golang.org/protobuf/internal/protolazy" "google.golang.org/protobuf/proto" piface "google.golang.org/protobuf/runtime/protoiface" ) @@ -71,11 +72,39 @@ func (mi *MessageInfo) sizePointerSlow(p pointer, opts marshalOptions) (size int e := p.Apply(mi.extensionOffset).Extensions() size += mi.sizeExtensions(e, opts) } + + var lazy **protolazy.XXX_lazyUnmarshalInfo + var presence presence + if mi.presenceOffset.IsValid() { + presence = p.Apply(mi.presenceOffset).PresenceInfo() + if mi.lazyOffset.IsValid() { + lazy = p.Apply(mi.lazyOffset).LazyInfoPtr() + } + } + for _, f := range mi.orderedCoderFields { if f.funcs.size == nil { continue } fptr := p.Apply(f.offset) + + if f.presenceIndex != noPresence { + if !presence.Present(f.presenceIndex) { + continue + } + + if f.isLazy && fptr.AtomicGetPointer().IsNil() { + if lazyFields(opts) { + size += (*lazy).SizeField(uint32(f.num)) + continue + } else { + mi.lazyUnmarshal(p, f.num) + } + } + size += f.funcs.size(fptr, f, opts) + continue + } + if f.isPointer && fptr.Elem().IsNil() { continue } @@ -134,11 +163,52 @@ func (mi *MessageInfo) marshalAppendPointer(b []byte, p pointer, opts marshalOpt return b, err } } + + var lazy **protolazy.XXX_lazyUnmarshalInfo + var presence presence + if mi.presenceOffset.IsValid() { + presence = p.Apply(mi.presenceOffset).PresenceInfo() + if mi.lazyOffset.IsValid() { + lazy = p.Apply(mi.lazyOffset).LazyInfoPtr() + } + } + for _, f := range mi.orderedCoderFields { if f.funcs.marshal == nil { continue } fptr := p.Apply(f.offset) + + if f.presenceIndex != noPresence { + if !presence.Present(f.presenceIndex) { + continue + } + if f.isLazy { + // Be careful, this field needs to be read atomically, like for a get + if f.isPointer && fptr.AtomicGetPointer().IsNil() { + if lazyFields(opts) { + b, _ = (*lazy).AppendField(b, uint32(f.num)) + continue + } else { + mi.lazyUnmarshal(p, f.num) + } + } + + b, err = f.funcs.marshal(b, fptr, f, opts) + if err != nil { + return b, err + } + continue + } else if f.isPointer && fptr.Elem().IsNil() { + continue + } + b, err = f.funcs.marshal(b, fptr, f, opts) + if err != nil { + return b, err + } + continue + } + if f.isPointer && fptr.Elem().IsNil() { continue } @@ -163,6 +233,14 @@ func fullyLazyExtensions(opts marshalOptions) bool { return opts.flags&piface.MarshalDeterministic == 0 } +// lazyFields returns true if we should attempt to keep fields lazy over size and marshal. +func lazyFields(opts marshalOptions) bool { + // When deterministic marshaling is requested, force an unmarshal for lazy + // fields to produce a deterministic result, instead of passing through + // bytes lazily that may or may not match what Go Protobuf would produce. + return opts.flags&piface.MarshalDeterministic == 0 +} + func (mi *MessageInfo) sizeExtensions(ext *map[int32]ExtensionField, opts marshalOptions) (n int) { if ext == nil { return 0 diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/lazy.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/lazy.go new file mode 100644 index 0000000000..c7de31e243 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/lazy.go @@ -0,0 +1,433 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "fmt" + "math/bits" + "os" + "reflect" + "sort" + "sync/atomic" + + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/internal/errors" + "google.golang.org/protobuf/internal/protolazy" + "google.golang.org/protobuf/reflect/protoreflect" + preg "google.golang.org/protobuf/reflect/protoregistry" + piface "google.golang.org/protobuf/runtime/protoiface" +) + +var enableLazy int32 = func() int32 { + if os.Getenv("GOPROTODEBUG") == "nolazy" { + return 0 + } + return 1 +}() + +// EnableLazyUnmarshal enables lazy unmarshaling. +func EnableLazyUnmarshal(enable bool) { + if enable { + atomic.StoreInt32(&enableLazy, 1) + return + } + atomic.StoreInt32(&enableLazy, 0) +} + +// LazyEnabled reports whether lazy unmarshalling is currently enabled. +func LazyEnabled() bool { + return atomic.LoadInt32(&enableLazy) != 0 +} + +// UnmarshalField unmarshals a field in a message. +func UnmarshalField(m interface{}, num protowire.Number) { + switch m := m.(type) { + case *messageState: + m.messageInfo().lazyUnmarshal(m.pointer(), num) + case *messageReflectWrapper: + m.messageInfo().lazyUnmarshal(m.pointer(), num) + default: + panic(fmt.Sprintf("unsupported wrapper type %T", m)) + } +} + +func (mi *MessageInfo) lazyUnmarshal(p pointer, num protoreflect.FieldNumber) { + var f *coderFieldInfo + if int(num) < len(mi.denseCoderFields) { + f = mi.denseCoderFields[num] + } else { + f = mi.coderFields[num] + } + if f == nil { + panic(fmt.Sprintf("lazyUnmarshal: field info for %v.%v", mi.Desc.FullName(), num)) + } + lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr() + start, end, found, _, multipleEntries := lazy.FindFieldInProto(uint32(num)) + if !found && multipleEntries == nil { + panic(fmt.Sprintf("lazyUnmarshal: can't find field data for %v.%v", mi.Desc.FullName(), num)) + } + // The actual pointer in the message can not be set until the whole struct is filled in, otherwise we will have races. + // Create another pointer and set it atomically, if we won the race and the pointer in the original message is still nil. + fp := pointerOfValue(reflect.New(f.ft)) + if multipleEntries != nil { + for _, entry := range multipleEntries { + mi.unmarshalField(lazy.Buffer()[entry.Start:entry.End], fp, f, lazy, lazy.UnmarshalFlags()) + } + } else { + mi.unmarshalField(lazy.Buffer()[start:end], fp, f, lazy, lazy.UnmarshalFlags()) + } + p.Apply(f.offset).AtomicSetPointerIfNil(fp.Elem()) +} + +func (mi *MessageInfo) unmarshalField(b []byte, p pointer, f *coderFieldInfo, lazyInfo *protolazy.XXX_lazyUnmarshalInfo, flags piface.UnmarshalInputFlags) error { + opts := lazyUnmarshalOptions + opts.flags |= flags + for len(b) > 0 { + // Parse the tag (field number and wire type). + var tag uint64 + if b[0] < 0x80 { + tag = uint64(b[0]) + b = b[1:] + } else if len(b) >= 2 && b[1] < 128 { + tag = uint64(b[0]&0x7f) + uint64(b[1])<<7 + b = b[2:] + } else { + var n int + tag, n = protowire.ConsumeVarint(b) + if n < 0 { + return errors.New("invalid wire data") + } + b = b[n:] + } + var num protowire.Number + if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) { + return errors.New("invalid wire data") + } else { + num = protowire.Number(n) + } + wtyp := protowire.Type(tag & 7) + if num == f.num { + o, err := f.funcs.unmarshal(b, p, wtyp, f, opts) + if err == nil { + b = b[o.n:] + continue + } + if err != errUnknown { + return err + } + } + n := protowire.ConsumeFieldValue(num, wtyp, b) + if n < 0 { + return errors.New("invalid wire data") + } + b = b[n:] + } + return nil +} + +func (mi *MessageInfo) skipField(b []byte, f *coderFieldInfo, wtyp protowire.Type, opts unmarshalOptions) (out unmarshalOutput, _ ValidationStatus) { + fmi := f.validation.mi + if fmi == nil { + fd := mi.Desc.Fields().ByNumber(f.num) + if fd == nil { + return out, ValidationUnknown + } + messageName := fd.Message().FullName() + messageType, err := preg.GlobalTypes.FindMessageByName(messageName) + if err != nil { + return out, ValidationUnknown + } + var ok bool + fmi, ok = messageType.(*MessageInfo) + if !ok { + return out, ValidationUnknown + } + } + fmi.init() + switch f.validation.typ { + case validationTypeMessage: + if wtyp != protowire.BytesType { + return out, ValidationWrongWireType + } + v, n := protowire.ConsumeBytes(b) + if n < 0 { + return out, ValidationInvalid + } + out, st := fmi.validate(v, 0, opts) + out.n = n + return out, st + case validationTypeGroup: + if wtyp != protowire.StartGroupType { + return out, ValidationWrongWireType + } + out, st := fmi.validate(b, f.num, opts) + return out, st + default: + return out, ValidationUnknown + } +} + +// unmarshalPointerLazy is similar to unmarshalPointerEager, but it +// specifically handles lazy unmarshalling. it expects lazyOffset and +// presenceOffset to both be valid. +func (mi *MessageInfo) unmarshalPointerLazy(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) { + initialized := true + var requiredMask uint64 + var lazy **protolazy.XXX_lazyUnmarshalInfo + var presence presence + var lazyIndex []protolazy.IndexEntry + var lastNum protowire.Number + outOfOrder := false + lazyDecode := false + presence = p.Apply(mi.presenceOffset).PresenceInfo() + lazy = p.Apply(mi.lazyOffset).LazyInfoPtr() + if !presence.AnyPresent(mi.presenceSize) { + if opts.CanBeLazy() { + // If the message contains existing data, we need to merge into it. + // Lazy unmarshaling doesn't merge, so only enable it when the + // message is empty (has no presence bitmap). + lazyDecode = true + if *lazy == nil { + *lazy = &protolazy.XXX_lazyUnmarshalInfo{} + } + (*lazy).SetUnmarshalFlags(opts.flags) + if !opts.AliasBuffer() { + // Make a copy of the buffer for lazy unmarshaling. + // Set the AliasBuffer flag so recursive unmarshal + // operations reuse the copy. + b = append([]byte{}, b...) + opts.flags |= piface.UnmarshalAliasBuffer + } + (*lazy).SetBuffer(b) + } + } + // Track special handling of lazy fields. + // + // In the common case, all fields are lazyValidateOnly (and lazyFields remains nil). + // In the event that validation for a field fails, this map tracks handling of the field. + type lazyAction uint8 + const ( + lazyValidateOnly lazyAction = iota // validate the field only + lazyUnmarshalNow // eagerly unmarshal the field + lazyUnmarshalLater // unmarshal the field after the message is fully processed + ) + var lazyFields map[*coderFieldInfo]lazyAction + var exts *map[int32]ExtensionField + start := len(b) + pos := 0 + for len(b) > 0 { + // Parse the tag (field number and wire type). + var tag uint64 + if b[0] < 0x80 { + tag = uint64(b[0]) + b = b[1:] + } else if len(b) >= 2 && b[1] < 128 { + tag = uint64(b[0]&0x7f) + uint64(b[1])<<7 + b = b[2:] + } else { + var n int + tag, n = protowire.ConsumeVarint(b) + if n < 0 { + return out, errDecode + } + b = b[n:] + } + var num protowire.Number + if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) { + return out, errors.New("invalid field number") + } else { + num = protowire.Number(n) + } + wtyp := protowire.Type(tag & 7) + + if wtyp == protowire.EndGroupType { + if num != groupTag { + return out, errors.New("mismatching end group marker") + } + groupTag = 0 + break + } + + var f *coderFieldInfo + if int(num) < len(mi.denseCoderFields) { + f = mi.denseCoderFields[num] + } else { + f = mi.coderFields[num] + } + var n int + err := errUnknown + discardUnknown := false + Field: + switch { + case f != nil: + if f.funcs.unmarshal == nil { + break + } + if f.isLazy && lazyDecode { + switch { + case lazyFields == nil || lazyFields[f] == lazyValidateOnly: + // Attempt to validate this field and leave it for later lazy unmarshaling. + o, valid := mi.skipField(b, f, wtyp, opts) + switch valid { + case ValidationValid: + // Skip over the valid field and continue. + err = nil + presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize) + requiredMask |= f.validation.requiredBit + if !o.initialized { + initialized = false + } + n = o.n + break Field + case ValidationInvalid: + return out, errors.New("invalid proto wire format") + case ValidationWrongWireType: + break Field + case ValidationUnknown: + if lazyFields == nil { + lazyFields = make(map[*coderFieldInfo]lazyAction) + } + if presence.Present(f.presenceIndex) { + // We were unable to determine if the field is valid or not, + // and we've already skipped over at least one instance of this + // field. Clear the presence bit (so if we stop decoding early, + // we don't leave a partially-initialized field around) and flag + // the field for unmarshaling before we return. + presence.ClearPresent(f.presenceIndex) + lazyFields[f] = lazyUnmarshalLater + discardUnknown = true + break Field + } else { + // We were unable to determine if the field is valid or not, + // but this is the first time we've seen it. Flag it as needing + // eager unmarshaling and fall through to the eager unmarshal case below. + lazyFields[f] = lazyUnmarshalNow + } + } + case lazyFields[f] == lazyUnmarshalLater: + // This field will be unmarshaled in a separate pass below. + // Skip over it here. + discardUnknown = true + break Field + default: + // Eagerly unmarshal the field. + } + } + if f.isLazy && !lazyDecode && presence.Present(f.presenceIndex) { + if p.Apply(f.offset).AtomicGetPointer().IsNil() { + mi.lazyUnmarshal(p, f.num) + } + } + var o unmarshalOutput + o, err = f.funcs.unmarshal(b, p.Apply(f.offset), wtyp, f, opts) + n = o.n + if err != nil { + break + } + requiredMask |= f.validation.requiredBit + if f.funcs.isInit != nil && !o.initialized { + initialized = false + } + if f.presenceIndex != noPresence { + presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize) + } + default: + // Possible extension. + if exts == nil && mi.extensionOffset.IsValid() { + exts = p.Apply(mi.extensionOffset).Extensions() + if *exts == nil { + *exts = make(map[int32]ExtensionField) + } + } + if exts == nil { + break + } + var o unmarshalOutput + o, err = mi.unmarshalExtension(b, num, wtyp, *exts, opts) + if err != nil { + break + } + n = o.n + if !o.initialized { + initialized = false + } + } + if err != nil { + if err != errUnknown { + return out, err + } + n = protowire.ConsumeFieldValue(num, wtyp, b) + if n < 0 { + return out, errDecode + } + if !discardUnknown && !opts.DiscardUnknown() && mi.unknownOffset.IsValid() { + u := mi.mutableUnknownBytes(p) + *u = protowire.AppendTag(*u, num, wtyp) + *u = append(*u, b[:n]...) + } + } + b = b[n:] + end := start - len(b) + if lazyDecode && f != nil && f.isLazy { + if num != lastNum { + lazyIndex = append(lazyIndex, protolazy.IndexEntry{ + FieldNum: uint32(num), + Start: uint32(pos), + End: uint32(end), + }) + } else { + i := len(lazyIndex) - 1 + lazyIndex[i].End = uint32(end) + lazyIndex[i].MultipleContiguous = true + } + } + if num < lastNum { + outOfOrder = true + } + pos = end + lastNum = num + } + if groupTag != 0 { + return out, errors.New("missing end group marker") + } + if lazyFields != nil { + // Some fields failed validation, and now need to be unmarshaled. + for f, action := range lazyFields { + if action != lazyUnmarshalLater { + continue + } + initialized = false + if *lazy == nil { + *lazy = &protolazy.XXX_lazyUnmarshalInfo{} + } + if err := mi.unmarshalField((*lazy).Buffer(), p.Apply(f.offset), f, *lazy, opts.flags); err != nil { + return out, err + } + presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize) + } + } + if lazyDecode { + if outOfOrder { + sort.Slice(lazyIndex, func(i, j int) bool { + return lazyIndex[i].FieldNum < lazyIndex[j].FieldNum || + (lazyIndex[i].FieldNum == lazyIndex[j].FieldNum && + lazyIndex[i].Start < lazyIndex[j].Start) + }) + } + if *lazy == nil { + *lazy = &protolazy.XXX_lazyUnmarshalInfo{} + } + + (*lazy).SetIndex(lazyIndex) + } + if mi.numRequiredFields > 0 && bits.OnesCount64(requiredMask) != int(mi.numRequiredFields) { + initialized = false + } + if initialized { + out.initialized = true + } + out.n = start - len(b) + return out, nil +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/legacy_message.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/legacy_message.go index bf0b6049b4..a51dffbe29 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/legacy_message.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/legacy_message.go @@ -310,12 +310,9 @@ func aberrantAppendField(md *filedesc.Message, goType reflect.Type, tag, tagKey, fd.L0.Parent = md fd.L0.Index = n - if fd.L1.IsWeak || fd.L1.EditionFeatures.IsPacked { + if fd.L1.EditionFeatures.IsPacked { fd.L1.Options = func() protoreflect.ProtoMessage { opts := descopts.Field.ProtoReflect().New() - if fd.L1.IsWeak { - opts.Set(opts.Descriptor().Fields().ByName("weak"), protoreflect.ValueOfBool(true)) - } if fd.L1.EditionFeatures.IsPacked { opts.Set(opts.Descriptor().Fields().ByName("packed"), protoreflect.ValueOfBool(fd.L1.EditionFeatures.IsPacked)) } diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/merge.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/merge.go index 7e65f64f28..8ffdce67d3 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/merge.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/merge.go @@ -41,11 +41,38 @@ func (mi *MessageInfo) mergePointer(dst, src pointer, opts mergeOptions) { if src.IsNil() { return } + + var presenceSrc presence + var presenceDst presence + if mi.presenceOffset.IsValid() { + presenceSrc = src.Apply(mi.presenceOffset).PresenceInfo() + presenceDst = dst.Apply(mi.presenceOffset).PresenceInfo() + } + for _, f := range mi.orderedCoderFields { if f.funcs.merge == nil { continue } sfptr := src.Apply(f.offset) + + if f.presenceIndex != noPresence { + if !presenceSrc.Present(f.presenceIndex) { + continue + } + dfptr := dst.Apply(f.offset) + if f.isLazy { + if sfptr.AtomicGetPointer().IsNil() { + mi.lazyUnmarshal(src, f.num) + } + if presenceDst.Present(f.presenceIndex) && dfptr.AtomicGetPointer().IsNil() { + mi.lazyUnmarshal(dst, f.num) + } + } + f.funcs.merge(dst.Apply(f.offset), sfptr, f, opts) + presenceDst.SetPresentUnatomic(f.presenceIndex, mi.presenceSize) + continue + } + if f.isPointer && sfptr.Elem().IsNil() { continue } diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message.go index 741b5ed29c..d50423dcb7 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message.go @@ -14,7 +14,6 @@ import ( "google.golang.org/protobuf/internal/genid" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" ) // MessageInfo provides protobuf related functionality for a given Go type @@ -79,6 +78,9 @@ func (mi *MessageInfo) initOnce() { if mi.initDone == 1 { return } + if opaqueInitHook(mi) { + return + } t := mi.GoReflectType if t.Kind() != reflect.Ptr && t.Elem().Kind() != reflect.Struct { @@ -117,7 +119,6 @@ type ( var ( sizecacheType = reflect.TypeOf(SizeCache(0)) - weakFieldsType = reflect.TypeOf(WeakFields(nil)) unknownFieldsAType = reflect.TypeOf(unknownFieldsA(nil)) unknownFieldsBType = reflect.TypeOf(unknownFieldsB(nil)) extensionFieldsType = reflect.TypeOf(ExtensionFields(nil)) @@ -126,13 +127,14 @@ var ( type structInfo struct { sizecacheOffset offset sizecacheType reflect.Type - weakOffset offset - weakType reflect.Type unknownOffset offset unknownType reflect.Type extensionOffset offset extensionType reflect.Type + lazyOffset offset + presenceOffset offset + fieldsByNumber map[protoreflect.FieldNumber]reflect.StructField oneofsByName map[protoreflect.Name]reflect.StructField oneofWrappersByType map[reflect.Type]protoreflect.FieldNumber @@ -142,9 +144,10 @@ type structInfo struct { func (mi *MessageInfo) makeStructInfo(t reflect.Type) structInfo { si := structInfo{ sizecacheOffset: invalidOffset, - weakOffset: invalidOffset, unknownOffset: invalidOffset, extensionOffset: invalidOffset, + lazyOffset: invalidOffset, + presenceOffset: invalidOffset, fieldsByNumber: map[protoreflect.FieldNumber]reflect.StructField{}, oneofsByName: map[protoreflect.Name]reflect.StructField{}, @@ -157,24 +160,23 @@ fieldLoop: switch f := t.Field(i); f.Name { case genid.SizeCache_goname, genid.SizeCacheA_goname: if f.Type == sizecacheType { - si.sizecacheOffset = offsetOf(f, mi.Exporter) + si.sizecacheOffset = offsetOf(f) si.sizecacheType = f.Type } - case genid.WeakFields_goname, genid.WeakFieldsA_goname: - if f.Type == weakFieldsType { - si.weakOffset = offsetOf(f, mi.Exporter) - si.weakType = f.Type - } case genid.UnknownFields_goname, genid.UnknownFieldsA_goname: if f.Type == unknownFieldsAType || f.Type == unknownFieldsBType { - si.unknownOffset = offsetOf(f, mi.Exporter) + si.unknownOffset = offsetOf(f) si.unknownType = f.Type } case genid.ExtensionFields_goname, genid.ExtensionFieldsA_goname, genid.ExtensionFieldsB_goname: if f.Type == extensionFieldsType { - si.extensionOffset = offsetOf(f, mi.Exporter) + si.extensionOffset = offsetOf(f) si.extensionType = f.Type } + case "lazyFields", "XXX_lazyUnmarshalInfo": + si.lazyOffset = offsetOf(f) + case "XXX_presence": + si.presenceOffset = offsetOf(f) default: for _, s := range strings.Split(f.Tag.Get("protobuf"), ",") { if len(s) > 0 && strings.Trim(s, "0123456789") == "" { @@ -244,9 +246,6 @@ func (mi *MessageInfo) Message(i int) protoreflect.MessageType { mi.init() fd := mi.Desc.Fields().Get(i) switch { - case fd.IsWeak(): - mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()) - return mt case fd.IsMap(): return mapEntryType{fd.Message(), mi.fieldTypes[fd.Number()]} default: diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque.go new file mode 100644 index 0000000000..dd55e8e009 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque.go @@ -0,0 +1,627 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "fmt" + "math" + "reflect" + "strings" + "sync/atomic" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +type opaqueStructInfo struct { + structInfo +} + +// isOpaque determines whether a protobuf message type is on the Opaque API. It +// checks whether the type is a Go struct that protoc-gen-go would generate. +// +// This function only detects newly generated messages from the v2 +// implementation of protoc-gen-go. It is unable to classify generated messages +// that are too old or those that are generated by a different generator +// such as protoc-gen-gogo. +func isOpaque(t reflect.Type) bool { + // The current detection mechanism is to simply check the first field + // for a struct tag with the "protogen" key. + if t.Kind() == reflect.Struct && t.NumField() > 0 { + pgt := t.Field(0).Tag.Get("protogen") + return strings.HasPrefix(pgt, "opaque.") + } + return false +} + +func opaqueInitHook(mi *MessageInfo) bool { + mt := mi.GoReflectType.Elem() + si := opaqueStructInfo{ + structInfo: mi.makeStructInfo(mt), + } + + if !isOpaque(mt) { + return false + } + + defer atomic.StoreUint32(&mi.initDone, 1) + + mi.fields = map[protoreflect.FieldNumber]*fieldInfo{} + fds := mi.Desc.Fields() + for i := 0; i < fds.Len(); i++ { + fd := fds.Get(i) + fs := si.fieldsByNumber[fd.Number()] + var fi fieldInfo + usePresence, _ := usePresenceForField(si, fd) + + switch { + case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic(): + // Oneofs are no different for opaque. + fi = fieldInfoForOneof(fd, si.oneofsByName[fd.ContainingOneof().Name()], mi.Exporter, si.oneofWrappersByNumber[fd.Number()]) + case fd.IsMap(): + fi = mi.fieldInfoForMapOpaque(si, fd, fs) + case fd.IsList() && fd.Message() == nil && usePresence: + fi = mi.fieldInfoForScalarListOpaque(si, fd, fs) + case fd.IsList() && fd.Message() == nil: + // Proto3 lists without presence can use same access methods as open + fi = fieldInfoForList(fd, fs, mi.Exporter) + case fd.IsList() && usePresence: + fi = mi.fieldInfoForMessageListOpaque(si, fd, fs) + case fd.IsList(): + // Proto3 opaque messages that does not need presence bitmap. + // Different representation than open struct, but same logic + fi = mi.fieldInfoForMessageListOpaqueNoPresence(si, fd, fs) + case fd.Message() != nil && usePresence: + fi = mi.fieldInfoForMessageOpaque(si, fd, fs) + case fd.Message() != nil: + // Proto3 messages without presence can use same access methods as open + fi = fieldInfoForMessage(fd, fs, mi.Exporter) + default: + fi = mi.fieldInfoForScalarOpaque(si, fd, fs) + } + mi.fields[fd.Number()] = &fi + } + mi.oneofs = map[protoreflect.Name]*oneofInfo{} + for i := 0; i < mi.Desc.Oneofs().Len(); i++ { + od := mi.Desc.Oneofs().Get(i) + mi.oneofs[od.Name()] = makeOneofInfoOpaque(mi, od, si.structInfo, mi.Exporter) + } + + mi.denseFields = make([]*fieldInfo, fds.Len()*2) + for i := 0; i < fds.Len(); i++ { + if fd := fds.Get(i); int(fd.Number()) < len(mi.denseFields) { + mi.denseFields[fd.Number()] = mi.fields[fd.Number()] + } + } + + for i := 0; i < fds.Len(); { + fd := fds.Get(i) + if od := fd.ContainingOneof(); od != nil && !fd.ContainingOneof().IsSynthetic() { + mi.rangeInfos = append(mi.rangeInfos, mi.oneofs[od.Name()]) + i += od.Fields().Len() + } else { + mi.rangeInfos = append(mi.rangeInfos, mi.fields[fd.Number()]) + i++ + } + } + + mi.makeExtensionFieldsFunc(mt, si.structInfo) + mi.makeUnknownFieldsFunc(mt, si.structInfo) + mi.makeOpaqueCoderMethods(mt, si) + mi.makeFieldTypes(si.structInfo) + + return true +} + +func makeOneofInfoOpaque(mi *MessageInfo, od protoreflect.OneofDescriptor, si structInfo, x exporter) *oneofInfo { + oi := &oneofInfo{oneofDesc: od} + if od.IsSynthetic() { + fd := od.Fields().Get(0) + index, _ := presenceIndex(mi.Desc, fd) + oi.which = func(p pointer) protoreflect.FieldNumber { + if p.IsNil() { + return 0 + } + if !mi.present(p, index) { + return 0 + } + return od.Fields().Get(0).Number() + } + return oi + } + // Dispatch to non-opaque oneof implementation for non-synthetic oneofs. + return makeOneofInfo(od, si, x) +} + +func (mi *MessageInfo) fieldInfoForMapOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + if ft.Kind() != reflect.Map { + panic(fmt.Sprintf("invalid type: got %v, want map kind", ft)) + } + fieldOffset := offsetOf(fs) + conv := NewConverter(ft, fd) + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + // Don't bother checking presence bits, since we need to + // look at the map length even if the presence bit is set. + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + return rv.Len() > 0 + }, + clear: func(p pointer) { + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + rv.Set(reflect.Zero(rv.Type())) + }, + get: func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + if rv.Len() == 0 { + return conv.Zero() + } + return conv.PBValueOf(rv) + }, + set: func(p pointer, v protoreflect.Value) { + pv := conv.GoValueOf(v) + if pv.IsNil() { + panic(fmt.Sprintf("invalid value: setting map field to read-only value")) + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + rv.Set(pv) + }, + mutable: func(p pointer) protoreflect.Value { + v := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + if v.IsNil() { + v.Set(reflect.MakeMap(fs.Type)) + } + return conv.PBValueOf(v) + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +func (mi *MessageInfo) fieldInfoForScalarListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + if ft.Kind() != reflect.Slice { + panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft)) + } + conv := NewConverter(reflect.PtrTo(ft), fd) + fieldOffset := offsetOf(fs) + index, _ := presenceIndex(mi.Desc, fd) + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + return rv.Len() > 0 + }, + clear: func(p pointer) { + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + rv.Set(reflect.Zero(rv.Type())) + }, + get: func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type) + if rv.Elem().Len() == 0 { + return conv.Zero() + } + return conv.PBValueOf(rv) + }, + set: func(p pointer, v protoreflect.Value) { + pv := conv.GoValueOf(v) + if pv.IsNil() { + panic(fmt.Sprintf("invalid value: setting repeated field to read-only value")) + } + mi.setPresent(p, index) + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + rv.Set(pv.Elem()) + }, + mutable: func(p pointer) protoreflect.Value { + mi.setPresent(p, index) + return conv.PBValueOf(p.Apply(fieldOffset).AsValueOf(fs.Type)) + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +func (mi *MessageInfo) fieldInfoForMessageListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice { + panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft)) + } + conv := NewConverter(ft, fd) + fieldOffset := offsetOf(fs) + index, _ := presenceIndex(mi.Desc, fd) + fieldNumber := fd.Number() + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + if !mi.present(p, index) { + return false + } + sp := p.Apply(fieldOffset).AtomicGetPointer() + if sp.IsNil() { + // Lazily unmarshal this field. + mi.lazyUnmarshal(p, fieldNumber) + sp = p.Apply(fieldOffset).AtomicGetPointer() + } + rv := sp.AsValueOf(fs.Type.Elem()) + return rv.Elem().Len() > 0 + }, + clear: func(p pointer) { + fp := p.Apply(fieldOffset) + sp := fp.AtomicGetPointer() + if sp.IsNil() { + sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem()))) + mi.setPresent(p, index) + } + rv := sp.AsValueOf(fs.Type.Elem()) + rv.Elem().Set(reflect.Zero(rv.Type().Elem())) + }, + get: func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + if !mi.present(p, index) { + return conv.Zero() + } + sp := p.Apply(fieldOffset).AtomicGetPointer() + if sp.IsNil() { + // Lazily unmarshal this field. + mi.lazyUnmarshal(p, fieldNumber) + sp = p.Apply(fieldOffset).AtomicGetPointer() + } + rv := sp.AsValueOf(fs.Type.Elem()) + if rv.Elem().Len() == 0 { + return conv.Zero() + } + return conv.PBValueOf(rv) + }, + set: func(p pointer, v protoreflect.Value) { + fp := p.Apply(fieldOffset) + sp := fp.AtomicGetPointer() + if sp.IsNil() { + sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem()))) + mi.setPresent(p, index) + } + rv := sp.AsValueOf(fs.Type.Elem()) + val := conv.GoValueOf(v) + if val.IsNil() { + panic(fmt.Sprintf("invalid value: setting repeated field to read-only value")) + } else { + rv.Elem().Set(val.Elem()) + } + }, + mutable: func(p pointer) protoreflect.Value { + fp := p.Apply(fieldOffset) + sp := fp.AtomicGetPointer() + if sp.IsNil() { + if mi.present(p, index) { + // Lazily unmarshal this field. + mi.lazyUnmarshal(p, fieldNumber) + sp = p.Apply(fieldOffset).AtomicGetPointer() + } else { + sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem()))) + mi.setPresent(p, index) + } + } + rv := sp.AsValueOf(fs.Type.Elem()) + return conv.PBValueOf(rv) + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +func (mi *MessageInfo) fieldInfoForMessageListOpaqueNoPresence(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice { + panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft)) + } + conv := NewConverter(ft, fd) + fieldOffset := offsetOf(fs) + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + sp := p.Apply(fieldOffset).AtomicGetPointer() + if sp.IsNil() { + return false + } + rv := sp.AsValueOf(fs.Type.Elem()) + return rv.Elem().Len() > 0 + }, + clear: func(p pointer) { + sp := p.Apply(fieldOffset).AtomicGetPointer() + if !sp.IsNil() { + rv := sp.AsValueOf(fs.Type.Elem()) + rv.Elem().Set(reflect.Zero(rv.Type().Elem())) + } + }, + get: func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + sp := p.Apply(fieldOffset).AtomicGetPointer() + if sp.IsNil() { + return conv.Zero() + } + rv := sp.AsValueOf(fs.Type.Elem()) + if rv.Elem().Len() == 0 { + return conv.Zero() + } + return conv.PBValueOf(rv) + }, + set: func(p pointer, v protoreflect.Value) { + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + if rv.IsNil() { + rv.Set(reflect.New(fs.Type.Elem())) + } + val := conv.GoValueOf(v) + if val.IsNil() { + panic(fmt.Sprintf("invalid value: setting repeated field to read-only value")) + } else { + rv.Elem().Set(val.Elem()) + } + }, + mutable: func(p pointer) protoreflect.Value { + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + if rv.IsNil() { + rv.Set(reflect.New(fs.Type.Elem())) + } + return conv.PBValueOf(rv) + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +func (mi *MessageInfo) fieldInfoForScalarOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + nullable := fd.HasPresence() + if oneof := fd.ContainingOneof(); oneof != nil && oneof.IsSynthetic() { + nullable = true + } + deref := false + if nullable && ft.Kind() == reflect.Ptr { + ft = ft.Elem() + deref = true + } + conv := NewConverter(ft, fd) + fieldOffset := offsetOf(fs) + index, _ := presenceIndex(mi.Desc, fd) + var getter func(p pointer) protoreflect.Value + if !nullable { + getter = getterForDirectScalar(fd, fs, conv, fieldOffset) + } else { + getter = getterForOpaqueNullableScalar(mi, index, fd, fs, conv, fieldOffset) + } + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + if nullable { + return mi.present(p, index) + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + switch rv.Kind() { + case reflect.Bool: + return rv.Bool() + case reflect.Int32, reflect.Int64: + return rv.Int() != 0 + case reflect.Uint32, reflect.Uint64: + return rv.Uint() != 0 + case reflect.Float32, reflect.Float64: + return rv.Float() != 0 || math.Signbit(rv.Float()) + case reflect.String, reflect.Slice: + return rv.Len() > 0 + default: + panic(fmt.Sprintf("invalid type: %v", rv.Type())) // should never happen + } + }, + clear: func(p pointer) { + if nullable { + mi.clearPresent(p, index) + } + // This is only valuable for bytes and strings, but we do it unconditionally. + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + rv.Set(reflect.Zero(rv.Type())) + }, + get: getter, + // TODO: Implement unsafe fast path for set? + set: func(p pointer, v protoreflect.Value) { + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + if deref { + if rv.IsNil() { + rv.Set(reflect.New(ft)) + } + rv = rv.Elem() + } + + rv.Set(conv.GoValueOf(v)) + if nullable && rv.Kind() == reflect.Slice && rv.IsNil() { + rv.Set(emptyBytes) + } + if nullable { + mi.setPresent(p, index) + } + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +func (mi *MessageInfo) fieldInfoForMessageOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo { + ft := fs.Type + conv := NewConverter(ft, fd) + fieldOffset := offsetOf(fs) + index, _ := presenceIndex(mi.Desc, fd) + fieldNumber := fd.Number() + elemType := fs.Type.Elem() + return fieldInfo{ + fieldDesc: fd, + has: func(p pointer) bool { + if p.IsNil() { + return false + } + return mi.present(p, index) + }, + clear: func(p pointer) { + mi.clearPresent(p, index) + p.Apply(fieldOffset).AtomicSetNilPointer() + }, + get: func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + fp := p.Apply(fieldOffset) + mp := fp.AtomicGetPointer() + if mp.IsNil() { + // Lazily unmarshal this field. + mi.lazyUnmarshal(p, fieldNumber) + mp = fp.AtomicGetPointer() + } + rv := mp.AsValueOf(elemType) + return conv.PBValueOf(rv) + }, + set: func(p pointer, v protoreflect.Value) { + val := pointerOfValue(conv.GoValueOf(v)) + if val.IsNil() { + panic("invalid nil pointer") + } + p.Apply(fieldOffset).AtomicSetPointer(val) + mi.setPresent(p, index) + }, + mutable: func(p pointer) protoreflect.Value { + fp := p.Apply(fieldOffset) + mp := fp.AtomicGetPointer() + if mp.IsNil() { + if mi.present(p, index) { + // Lazily unmarshal this field. + mi.lazyUnmarshal(p, fieldNumber) + mp = fp.AtomicGetPointer() + } else { + mp = pointerOfValue(conv.GoValueOf(conv.New())) + fp.AtomicSetPointer(mp) + mi.setPresent(p, index) + } + } + return conv.PBValueOf(mp.AsValueOf(fs.Type.Elem())) + }, + newMessage: func() protoreflect.Message { + return conv.New().Message() + }, + newField: func() protoreflect.Value { + return conv.New() + }, + } +} + +// A presenceList wraps a List, updating presence bits as necessary when the +// list contents change. +type presenceList struct { + pvalueList + setPresence func(bool) +} +type pvalueList interface { + protoreflect.List + //Unwrapper +} + +func (list presenceList) Append(v protoreflect.Value) { + list.pvalueList.Append(v) + list.setPresence(true) +} +func (list presenceList) Truncate(i int) { + list.pvalueList.Truncate(i) + list.setPresence(i > 0) +} + +// presenceIndex returns the index to pass to presence functions. +// +// TODO: field.Desc.Index() would be simpler, and would give space to record the presence of oneof fields. +func presenceIndex(md protoreflect.MessageDescriptor, fd protoreflect.FieldDescriptor) (uint32, presenceSize) { + found := false + var index, numIndices uint32 + for i := 0; i < md.Fields().Len(); i++ { + f := md.Fields().Get(i) + if f == fd { + found = true + index = numIndices + } + if f.ContainingOneof() == nil || isLastOneofField(f) { + numIndices++ + } + } + if !found { + panic(fmt.Sprintf("BUG: %v not in %v", fd.Name(), md.FullName())) + } + return index, presenceSize(numIndices) +} + +func isLastOneofField(fd protoreflect.FieldDescriptor) bool { + fields := fd.ContainingOneof().Fields() + return fields.Get(fields.Len()-1) == fd +} + +func (mi *MessageInfo) setPresent(p pointer, index uint32) { + p.Apply(mi.presenceOffset).PresenceInfo().SetPresent(index, mi.presenceSize) +} + +func (mi *MessageInfo) clearPresent(p pointer, index uint32) { + p.Apply(mi.presenceOffset).PresenceInfo().ClearPresent(index) +} + +func (mi *MessageInfo) present(p pointer, index uint32) bool { + return p.Apply(mi.presenceOffset).PresenceInfo().Present(index) +} + +// usePresenceForField implements the somewhat intricate logic of when +// the presence bitmap is used for a field. The main logic is that a +// field that is optional or that can be lazy will use the presence +// bit, but for proto2, also maps have a presence bit. It also records +// if the field can ever be lazy, which is true if we have a +// lazyOffset and the field is a message or a slice of messages. A +// field that is lazy will always need a presence bit. Oneofs are not +// lazy and do not use presence, unless they are a synthetic oneof, +// which is a proto3 optional field. For proto3 optionals, we use the +// presence and they can also be lazy when applicable (a message). +func usePresenceForField(si opaqueStructInfo, fd protoreflect.FieldDescriptor) (usePresence, canBeLazy bool) { + hasLazyField := fd.(interface{ IsLazy() bool }).IsLazy() + + // Non-oneof scalar fields with explicit field presence use the presence array. + usesPresenceArray := fd.HasPresence() && fd.Message() == nil && (fd.ContainingOneof() == nil || fd.ContainingOneof().IsSynthetic()) + switch { + case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic(): + return false, false + case fd.IsMap(): + return false, false + case fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind: + return hasLazyField, hasLazyField + default: + return usesPresenceArray || (hasLazyField && fd.HasPresence()), false + } +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque_gen.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque_gen.go new file mode 100644 index 0000000000..a69825699a --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_opaque_gen.go @@ -0,0 +1,132 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by generate-types. DO NOT EDIT. + +package impl + +import ( + "reflect" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +func getterForOpaqueNullableScalar(mi *MessageInfo, index uint32, fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value { + ft := fs.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if fd.Kind() == protoreflect.EnumKind { + // Enums for nullable opaque types. + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + return conv.PBValueOf(rv) + } + } + switch ft.Kind() { + case reflect.Bool: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bool() + return protoreflect.ValueOfBool(*x) + } + case reflect.Int32: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int32() + return protoreflect.ValueOfInt32(*x) + } + case reflect.Uint32: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint32() + return protoreflect.ValueOfUint32(*x) + } + case reflect.Int64: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int64() + return protoreflect.ValueOfInt64(*x) + } + case reflect.Uint64: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint64() + return protoreflect.ValueOfUint64(*x) + } + case reflect.Float32: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float32() + return protoreflect.ValueOfFloat32(*x) + } + case reflect.Float64: + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float64() + return protoreflect.ValueOfFloat64(*x) + } + case reflect.String: + if fd.Kind() == protoreflect.BytesKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).StringPtr() + if *x == nil { + return conv.Zero() + } + if len(**x) == 0 { + return protoreflect.ValueOfBytes(nil) + } + return protoreflect.ValueOfBytes([]byte(**x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).StringPtr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfString(**x) + } + case reflect.Slice: + if fd.Kind() == protoreflect.StringKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + return protoreflect.ValueOfString(string(*x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() || !mi.present(p, index) { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + return protoreflect.ValueOfBytes(*x) + } + } + panic("unexpected protobuf kind: " + ft.Kind().String()) +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect.go index ecb4623d70..0d20132fa2 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect.go @@ -72,8 +72,6 @@ func (mi *MessageInfo) makeKnownFieldsFunc(si structInfo) { fi = fieldInfoForMap(fd, fs, mi.Exporter) case fd.IsList(): fi = fieldInfoForList(fd, fs, mi.Exporter) - case fd.IsWeak(): - fi = fieldInfoForWeakMessage(fd, si.weakOffset) case fd.Message() != nil: fi = fieldInfoForMessage(fd, fs, mi.Exporter) default: @@ -205,6 +203,11 @@ func (mi *MessageInfo) makeFieldTypes(si structInfo) { case fd.IsList(): if fd.Enum() != nil || fd.Message() != nil { ft = fs.Type.Elem() + + if ft.Kind() == reflect.Slice { + ft = ft.Elem() + } + } isMessage = fd.Message() != nil case fd.Enum() != nil: @@ -214,9 +217,6 @@ func (mi *MessageInfo) makeFieldTypes(si structInfo) { } case fd.Message() != nil: ft = fs.Type - if fd.IsWeak() { - ft = nil - } isMessage = true } if isMessage && ft != nil && ft.Kind() != reflect.Ptr { diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field.go index 986322b195..68d4ae32ec 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field.go @@ -8,11 +8,8 @@ import ( "fmt" "math" "reflect" - "sync" - "google.golang.org/protobuf/internal/flags" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" ) type fieldInfo struct { @@ -76,7 +73,7 @@ func fieldInfoForOneof(fd protoreflect.FieldDescriptor, fs reflect.StructField, isMessage := fd.Message() != nil // TODO: Implement unsafe fast path? - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) return fieldInfo{ // NOTE: The logic below intentionally assumes that oneof fields are // well-formatted. That is, the oneof interface never contains a @@ -152,7 +149,7 @@ func fieldInfoForMap(fd protoreflect.FieldDescriptor, fs reflect.StructField, x conv := NewConverter(ft, fd) // TODO: Implement unsafe fast path? - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) return fieldInfo{ fieldDesc: fd, has: func(p pointer) bool { @@ -205,7 +202,7 @@ func fieldInfoForList(fd protoreflect.FieldDescriptor, fs reflect.StructField, x conv := NewConverter(reflect.PtrTo(ft), fd) // TODO: Implement unsafe fast path? - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) return fieldInfo{ fieldDesc: fd, has: func(p pointer) bool { @@ -256,6 +253,7 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, ft := fs.Type nullable := fd.HasPresence() isBytes := ft.Kind() == reflect.Slice && ft.Elem().Kind() == reflect.Uint8 + var getter func(p pointer) protoreflect.Value if nullable { if ft.Kind() != reflect.Ptr && ft.Kind() != reflect.Slice { // This never occurs for generated message types. @@ -268,19 +266,25 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, } } conv := NewConverter(ft, fd) + fieldOffset := offsetOf(fs) + + // Generate specialized getter functions to avoid going through reflect.Value + if nullable { + getter = getterForNullableScalar(fd, fs, conv, fieldOffset) + } else { + getter = getterForDirectScalar(fd, fs, conv, fieldOffset) + } - // TODO: Implement unsafe fast path? - fieldOffset := offsetOf(fs, x) return fieldInfo{ fieldDesc: fd, has: func(p pointer) bool { if p.IsNil() { return false } - rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() if nullable { - return !rv.IsNil() + return !p.Apply(fieldOffset).Elem().IsNil() } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() switch rv.Kind() { case reflect.Bool: return rv.Bool() @@ -300,21 +304,8 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() rv.Set(reflect.Zero(rv.Type())) }, - get: func(p pointer) protoreflect.Value { - if p.IsNil() { - return conv.Zero() - } - rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() - if nullable { - if rv.IsNil() { - return conv.Zero() - } - if rv.Kind() == reflect.Ptr { - rv = rv.Elem() - } - } - return conv.PBValueOf(rv) - }, + get: getter, + // TODO: Implement unsafe fast path for set? set: func(p pointer, v protoreflect.Value) { rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() if nullable && rv.Kind() == reflect.Ptr { @@ -338,85 +329,12 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, } } -func fieldInfoForWeakMessage(fd protoreflect.FieldDescriptor, weakOffset offset) fieldInfo { - if !flags.ProtoLegacy { - panic("no support for proto1 weak fields") - } - - var once sync.Once - var messageType protoreflect.MessageType - lazyInit := func() { - once.Do(func() { - messageName := fd.Message().FullName() - messageType, _ = protoregistry.GlobalTypes.FindMessageByName(messageName) - if messageType == nil { - panic(fmt.Sprintf("weak message %v for field %v is not linked in", messageName, fd.FullName())) - } - }) - } - - num := fd.Number() - return fieldInfo{ - fieldDesc: fd, - has: func(p pointer) bool { - if p.IsNil() { - return false - } - _, ok := p.Apply(weakOffset).WeakFields().get(num) - return ok - }, - clear: func(p pointer) { - p.Apply(weakOffset).WeakFields().clear(num) - }, - get: func(p pointer) protoreflect.Value { - lazyInit() - if p.IsNil() { - return protoreflect.ValueOfMessage(messageType.Zero()) - } - m, ok := p.Apply(weakOffset).WeakFields().get(num) - if !ok { - return protoreflect.ValueOfMessage(messageType.Zero()) - } - return protoreflect.ValueOfMessage(m.ProtoReflect()) - }, - set: func(p pointer, v protoreflect.Value) { - lazyInit() - m := v.Message() - if m.Descriptor() != messageType.Descriptor() { - if got, want := m.Descriptor().FullName(), messageType.Descriptor().FullName(); got != want { - panic(fmt.Sprintf("field %v has mismatching message descriptor: got %v, want %v", fd.FullName(), got, want)) - } - panic(fmt.Sprintf("field %v has mismatching message descriptor: %v", fd.FullName(), m.Descriptor().FullName())) - } - p.Apply(weakOffset).WeakFields().set(num, m.Interface()) - }, - mutable: func(p pointer) protoreflect.Value { - lazyInit() - fs := p.Apply(weakOffset).WeakFields() - m, ok := fs.get(num) - if !ok { - m = messageType.New().Interface() - fs.set(num, m) - } - return protoreflect.ValueOfMessage(m.ProtoReflect()) - }, - newMessage: func() protoreflect.Message { - lazyInit() - return messageType.New() - }, - newField: func() protoreflect.Value { - lazyInit() - return protoreflect.ValueOfMessage(messageType.New()) - }, - } -} - func fieldInfoForMessage(fd protoreflect.FieldDescriptor, fs reflect.StructField, x exporter) fieldInfo { ft := fs.Type conv := NewConverter(ft, fd) // TODO: Implement unsafe fast path? - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) return fieldInfo{ fieldDesc: fd, has: func(p pointer) bool { @@ -425,7 +343,7 @@ func fieldInfoForMessage(fd protoreflect.FieldDescriptor, fs reflect.StructField } rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() if fs.Type.Kind() != reflect.Ptr { - return !isZero(rv) + return !rv.IsZero() } return !rv.IsNil() }, @@ -472,7 +390,7 @@ func makeOneofInfo(od protoreflect.OneofDescriptor, si structInfo, x exporter) * oi := &oneofInfo{oneofDesc: od} if od.IsSynthetic() { fs := si.fieldsByNumber[od.Fields().Get(0).Number()] - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) oi.which = func(p pointer) protoreflect.FieldNumber { if p.IsNil() { return 0 @@ -485,7 +403,7 @@ func makeOneofInfo(od protoreflect.OneofDescriptor, si structInfo, x exporter) * } } else { fs := si.oneofsByName[od.Name()] - fieldOffset := offsetOf(fs, x) + fieldOffset := offsetOf(fs) oi.which = func(p pointer) protoreflect.FieldNumber { if p.IsNil() { return 0 @@ -503,41 +421,3 @@ func makeOneofInfo(od protoreflect.OneofDescriptor, si structInfo, x exporter) * } return oi } - -// isZero is identical to reflect.Value.IsZero. -// TODO: Remove this when Go1.13 is the minimally supported Go version. -func isZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return math.Float64bits(v.Float()) == 0 - case reflect.Complex64, reflect.Complex128: - c := v.Complex() - return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 - case reflect.Array: - for i := 0; i < v.Len(); i++ { - if !isZero(v.Index(i)) { - return false - } - } - return true - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: - return v.IsNil() - case reflect.String: - return v.Len() == 0 - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if !isZero(v.Field(i)) { - return false - } - } - return true - default: - panic(&reflect.ValueError{Method: "reflect.Value.IsZero", Kind: v.Kind()}) - } -} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field_gen.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field_gen.go new file mode 100644 index 0000000000..af5e063a1e --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/message_reflect_field_gen.go @@ -0,0 +1,273 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by generate-types. DO NOT EDIT. + +package impl + +import ( + "reflect" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +func getterForNullableScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value { + ft := fs.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if fd.Kind() == protoreflect.EnumKind { + elemType := fs.Type.Elem() + // Enums for nullable types. + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + rv := p.Apply(fieldOffset).Elem().AsValueOf(elemType) + if rv.IsNil() { + return conv.Zero() + } + return conv.PBValueOf(rv.Elem()) + } + } + switch ft.Kind() { + case reflect.Bool: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).BoolPtr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfBool(**x) + } + case reflect.Int32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int32Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfInt32(**x) + } + case reflect.Uint32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint32Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfUint32(**x) + } + case reflect.Int64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int64Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfInt64(**x) + } + case reflect.Uint64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint64Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfUint64(**x) + } + case reflect.Float32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float32Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfFloat32(**x) + } + case reflect.Float64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float64Ptr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfFloat64(**x) + } + case reflect.String: + if fd.Kind() == protoreflect.BytesKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).StringPtr() + if *x == nil { + return conv.Zero() + } + if len(**x) == 0 { + return protoreflect.ValueOfBytes(nil) + } + return protoreflect.ValueOfBytes([]byte(**x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).StringPtr() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfString(**x) + } + case reflect.Slice: + if fd.Kind() == protoreflect.StringKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + if len(*x) == 0 { + return conv.Zero() + } + return protoreflect.ValueOfString(string(*x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + if *x == nil { + return conv.Zero() + } + return protoreflect.ValueOfBytes(*x) + } + } + panic("unexpected protobuf kind: " + ft.Kind().String()) +} + +func getterForDirectScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value { + ft := fs.Type + if fd.Kind() == protoreflect.EnumKind { + // Enums for non nullable types. + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() + return conv.PBValueOf(rv) + } + } + switch ft.Kind() { + case reflect.Bool: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bool() + return protoreflect.ValueOfBool(*x) + } + case reflect.Int32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int32() + return protoreflect.ValueOfInt32(*x) + } + case reflect.Uint32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint32() + return protoreflect.ValueOfUint32(*x) + } + case reflect.Int64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Int64() + return protoreflect.ValueOfInt64(*x) + } + case reflect.Uint64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Uint64() + return protoreflect.ValueOfUint64(*x) + } + case reflect.Float32: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float32() + return protoreflect.ValueOfFloat32(*x) + } + case reflect.Float64: + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Float64() + return protoreflect.ValueOfFloat64(*x) + } + case reflect.String: + if fd.Kind() == protoreflect.BytesKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).String() + if len(*x) == 0 { + return protoreflect.ValueOfBytes(nil) + } + return protoreflect.ValueOfBytes([]byte(*x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).String() + return protoreflect.ValueOfString(*x) + } + case reflect.Slice: + if fd.Kind() == protoreflect.StringKind { + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + return protoreflect.ValueOfString(string(*x)) + } + } + return func(p pointer) protoreflect.Value { + if p.IsNil() { + return conv.Zero() + } + x := p.Apply(fieldOffset).Bytes() + return protoreflect.ValueOfBytes(*x) + } + } + panic("unexpected protobuf kind: " + ft.Kind().String()) +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go index 79e186667b..62f8bf663e 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go @@ -8,6 +8,8 @@ import ( "reflect" "sync/atomic" "unsafe" + + "google.golang.org/protobuf/internal/protolazy" ) const UnsafeEnabled = true @@ -20,7 +22,7 @@ type Pointer unsafe.Pointer type offset uintptr // offsetOf returns a field offset for the struct field. -func offsetOf(f reflect.StructField, x exporter) offset { +func offsetOf(f reflect.StructField) offset { return offset(f.Offset) } @@ -109,8 +111,14 @@ func (p pointer) StringSlice() *[]string { return (*[]string)(p.p func (p pointer) Bytes() *[]byte { return (*[]byte)(p.p) } func (p pointer) BytesPtr() **[]byte { return (**[]byte)(p.p) } func (p pointer) BytesSlice() *[][]byte { return (*[][]byte)(p.p) } -func (p pointer) WeakFields() *weakFields { return (*weakFields)(p.p) } func (p pointer) Extensions() *map[int32]ExtensionField { return (*map[int32]ExtensionField)(p.p) } +func (p pointer) LazyInfoPtr() **protolazy.XXX_lazyUnmarshalInfo { + return (**protolazy.XXX_lazyUnmarshalInfo)(p.p) +} + +func (p pointer) PresenceInfo() presence { + return presence{P: p.p} +} func (p pointer) Elem() pointer { return pointer{p: *(*unsafe.Pointer)(p.p)} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe_opaque.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe_opaque.go new file mode 100644 index 0000000000..38aa7b7dcf --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe_opaque.go @@ -0,0 +1,42 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "sync/atomic" + "unsafe" +) + +func (p pointer) AtomicGetPointer() pointer { + return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))} +} + +func (p pointer) AtomicSetPointer(v pointer) { + atomic.StorePointer((*unsafe.Pointer)(p.p), v.p) +} + +func (p pointer) AtomicSetNilPointer() { + atomic.StorePointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil)) +} + +func (p pointer) AtomicSetPointerIfNil(v pointer) pointer { + if atomic.CompareAndSwapPointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil), v.p) { + return v + } + return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))} +} + +type atomicV1MessageInfo struct{ p Pointer } + +func (mi *atomicV1MessageInfo) Get() Pointer { + return Pointer(atomic.LoadPointer((*unsafe.Pointer)(&mi.p))) +} + +func (mi *atomicV1MessageInfo) SetIfNil(p Pointer) Pointer { + if atomic.CompareAndSwapPointer((*unsafe.Pointer)(&mi.p), nil, unsafe.Pointer(p)) { + return p + } + return mi.Get() +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/presence.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/presence.go new file mode 100644 index 0000000000..914cb1deda --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/presence.go @@ -0,0 +1,142 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "sync/atomic" + "unsafe" +) + +// presenceSize represents the size of a presence set, which should be the largest index of the set+1 +type presenceSize uint32 + +// presence is the internal representation of the bitmap array in a generated protobuf +type presence struct { + // This is a pointer to the beginning of an array of uint32 + P unsafe.Pointer +} + +func (p presence) toElem(num uint32) (ret *uint32) { + const ( + bitsPerByte = 8 + siz = unsafe.Sizeof(*ret) + ) + // p.P points to an array of uint32, num is the bit in this array that the + // caller wants to check/manipulate. Calculate the index in the array that + // contains this specific bit. E.g.: 76 / 32 = 2 (integer division). + offset := uintptr(num) / (siz * bitsPerByte) * siz + return (*uint32)(unsafe.Pointer(uintptr(p.P) + offset)) +} + +// Present checks for the presence of a specific field number in a presence set. +func (p presence) Present(num uint32) bool { + if p.P == nil { + return false + } + return Export{}.Present(p.toElem(num), num) +} + +// SetPresent adds presence for a specific field number in a presence set. +func (p presence) SetPresent(num uint32, size presenceSize) { + Export{}.SetPresent(p.toElem(num), num, uint32(size)) +} + +// SetPresentUnatomic adds presence for a specific field number in a presence set without using +// atomic operations. Only to be called during unmarshaling. +func (p presence) SetPresentUnatomic(num uint32, size presenceSize) { + Export{}.SetPresentNonAtomic(p.toElem(num), num, uint32(size)) +} + +// ClearPresent removes presence for a specific field number in a presence set. +func (p presence) ClearPresent(num uint32) { + Export{}.ClearPresent(p.toElem(num), num) +} + +// LoadPresenceCache (together with PresentInCache) allows for a +// cached version of checking for presence without re-reading the word +// for every field. It is optimized for efficiency and assumes no +// simltaneous mutation of the presence set (or at least does not have +// a problem with simultaneous mutation giving inconsistent results). +func (p presence) LoadPresenceCache() (current uint32) { + if p.P == nil { + return 0 + } + return atomic.LoadUint32((*uint32)(p.P)) +} + +// PresentInCache reads presence from a cached word in the presence +// bitmap. It caches up a new word if the bit is outside the +// word. This is for really fast iteration through bitmaps in cases +// where we either know that the bitmap will not be altered, or we +// don't care about inconsistencies caused by simultaneous writes. +func (p presence) PresentInCache(num uint32, cachedElement *uint32, current *uint32) bool { + if num/32 != *cachedElement { + o := uintptr(num/32) * unsafe.Sizeof(uint32(0)) + q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o)) + *current = atomic.LoadUint32(q) + *cachedElement = num / 32 + } + return (*current & (1 << (num % 32))) > 0 +} + +// AnyPresent checks if any field is marked as present in the bitmap. +func (p presence) AnyPresent(size presenceSize) bool { + n := uintptr((size + 31) / 32) + for j := uintptr(0); j < n; j++ { + o := j * unsafe.Sizeof(uint32(0)) + q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o)) + b := atomic.LoadUint32(q) + if b > 0 { + return true + } + } + return false +} + +// toRaceDetectData finds the preceding RaceDetectHookData in a +// message by using pointer arithmetic. As the type of the presence +// set (bitmap) varies with the number of fields in the protobuf, we +// can not have a struct type containing the array and the +// RaceDetectHookData. instead the RaceDetectHookData is placed +// immediately before the bitmap array, and we find it by walking +// backwards in the struct. +// +// This method is only called from the race-detect version of the code, +// so RaceDetectHookData is never an empty struct. +func (p presence) toRaceDetectData() *RaceDetectHookData { + var template struct { + d RaceDetectHookData + a [1]uint32 + } + o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d))) + return (*RaceDetectHookData)(unsafe.Pointer(uintptr(p.P) - o)) +} + +func atomicLoadShadowPresence(p **[]byte) *[]byte { + return (*[]byte)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p)))) +} +func atomicStoreShadowPresence(p **[]byte, v *[]byte) { + atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(p)), nil, unsafe.Pointer(v)) +} + +// findPointerToRaceDetectData finds the preceding RaceDetectHookData +// in a message by using pointer arithmetic. For the methods called +// directy from generated code, we don't have a pointer to the +// beginning of the presence set, but a pointer inside the array. As +// we know the index of the bit we're manipulating (num), we can +// calculate which element of the array ptr is pointing to. With that +// information we find the preceding RaceDetectHookData and can +// manipulate the shadow bitmap. +// +// This method is only called from the race-detect version of the +// code, so RaceDetectHookData is never an empty struct. +func findPointerToRaceDetectData(ptr *uint32, num uint32) *RaceDetectHookData { + var template struct { + d RaceDetectHookData + a [1]uint32 + } + o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d))) + uintptr(num/32)*unsafe.Sizeof(uint32(0)) + return (*RaceDetectHookData)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) - o)) +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/validate.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/validate.go index a24e6bbd7a..7b2995dde5 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/validate.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/impl/validate.go @@ -37,6 +37,10 @@ const ( // ValidationValid indicates that unmarshaling the message will succeed. ValidationValid + + // ValidationWrongWireType indicates that a validated field does not have + // the expected wire type. + ValidationWrongWireType ) func (v ValidationStatus) String() string { @@ -149,11 +153,23 @@ func newValidationInfo(fd protoreflect.FieldDescriptor, ft reflect.Type) validat switch fd.Kind() { case protoreflect.MessageKind: vi.typ = validationTypeMessage + + if ft.Kind() == reflect.Ptr { + // Repeated opaque message fields are *[]*T. + ft = ft.Elem() + } + if ft.Kind() == reflect.Slice { vi.mi = getMessageInfo(ft.Elem()) } case protoreflect.GroupKind: vi.typ = validationTypeGroup + + if ft.Kind() == reflect.Ptr { + // Repeated opaque message fields are *[]*T. + ft = ft.Elem() + } + if ft.Kind() == reflect.Slice { vi.mi = getMessageInfo(ft.Elem()) } @@ -195,9 +211,7 @@ func newValidationInfo(fd protoreflect.FieldDescriptor, ft reflect.Type) validat switch fd.Kind() { case protoreflect.MessageKind: vi.typ = validationTypeMessage - if !fd.IsWeak() { - vi.mi = getMessageInfo(ft) - } + vi.mi = getMessageInfo(ft) case protoreflect.GroupKind: vi.typ = validationTypeGroup vi.mi = getMessageInfo(ft) @@ -304,26 +318,6 @@ State: } if f != nil { vi = f.validation - if vi.typ == validationTypeMessage && vi.mi == nil { - // Probable weak field. - // - // TODO: Consider storing the results of this lookup somewhere - // rather than recomputing it on every validation. - fd := st.mi.Desc.Fields().ByNumber(num) - if fd == nil || !fd.IsWeak() { - break - } - messageName := fd.Message().FullName() - messageType, err := protoregistry.GlobalTypes.FindMessageByName(messageName) - switch err { - case nil: - vi.mi, _ = messageType.(*MessageInfo) - case protoregistry.NotFound: - vi.typ = validationTypeBytes - default: - return out, ValidationUnknown - } - } break } // Possible extension field. diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/impl/weak.go b/go-controller/vendor/google.golang.org/protobuf/internal/impl/weak.go deleted file mode 100644 index eb79a7ba94..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/internal/impl/weak.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package impl - -import ( - "fmt" - - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" -) - -// weakFields adds methods to the exported WeakFields type for internal use. -// -// The exported type is an alias to an unnamed type, so methods can't be -// defined directly on it. -type weakFields WeakFields - -func (w weakFields) get(num protoreflect.FieldNumber) (protoreflect.ProtoMessage, bool) { - m, ok := w[int32(num)] - return m, ok -} - -func (w *weakFields) set(num protoreflect.FieldNumber, m protoreflect.ProtoMessage) { - if *w == nil { - *w = make(weakFields) - } - (*w)[int32(num)] = m -} - -func (w *weakFields) clear(num protoreflect.FieldNumber) { - delete(*w, int32(num)) -} - -func (Export) HasWeak(w WeakFields, num protoreflect.FieldNumber) bool { - _, ok := w[int32(num)] - return ok -} - -func (Export) ClearWeak(w *WeakFields, num protoreflect.FieldNumber) { - delete(*w, int32(num)) -} - -func (Export) GetWeak(w WeakFields, num protoreflect.FieldNumber, name protoreflect.FullName) protoreflect.ProtoMessage { - if m, ok := w[int32(num)]; ok { - return m - } - mt, _ := protoregistry.GlobalTypes.FindMessageByName(name) - if mt == nil { - panic(fmt.Sprintf("message %v for weak field is not linked in", name)) - } - return mt.Zero().Interface() -} - -func (Export) SetWeak(w *WeakFields, num protoreflect.FieldNumber, name protoreflect.FullName, m protoreflect.ProtoMessage) { - if m != nil { - mt, _ := protoregistry.GlobalTypes.FindMessageByName(name) - if mt == nil { - panic(fmt.Sprintf("message %v for weak field is not linked in", name)) - } - if mt != m.ProtoReflect().Type() { - panic(fmt.Sprintf("invalid message type for weak field: got %T, want %T", m, mt.Zero().Interface())) - } - } - if m == nil || !m.ProtoReflect().IsValid() { - delete(*w, int32(num)) - return - } - if *w == nil { - *w = make(weakFields) - } - (*w)[int32(num)] = m -} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/bufferreader.go b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/bufferreader.go new file mode 100644 index 0000000000..82e5cab4aa --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/bufferreader.go @@ -0,0 +1,364 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Helper code for parsing a protocol buffer + +package protolazy + +import ( + "errors" + "fmt" + "io" + + "google.golang.org/protobuf/encoding/protowire" +) + +// BufferReader is a structure encapsulating a protobuf and a current position +type BufferReader struct { + Buf []byte + Pos int +} + +// NewBufferReader creates a new BufferRead from a protobuf +func NewBufferReader(buf []byte) BufferReader { + return BufferReader{Buf: buf, Pos: 0} +} + +var errOutOfBounds = errors.New("protobuf decoding: out of bounds") +var errOverflow = errors.New("proto: integer overflow") + +func (b *BufferReader) DecodeVarintSlow() (x uint64, err error) { + i := b.Pos + l := len(b.Buf) + + for shift := uint(0); shift < 64; shift += 7 { + if i >= l { + err = io.ErrUnexpectedEOF + return + } + v := b.Buf[i] + i++ + x |= (uint64(v) & 0x7F) << shift + if v < 0x80 { + b.Pos = i + return + } + } + + // The number is too large to represent in a 64-bit value. + err = errOverflow + return +} + +// decodeVarint decodes a varint at the current position +func (b *BufferReader) DecodeVarint() (x uint64, err error) { + i := b.Pos + buf := b.Buf + + if i >= len(buf) { + return 0, io.ErrUnexpectedEOF + } else if buf[i] < 0x80 { + b.Pos++ + return uint64(buf[i]), nil + } else if len(buf)-i < 10 { + return b.DecodeVarintSlow() + } + + var v uint64 + // we already checked the first byte + x = uint64(buf[i]) & 127 + i++ + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 7 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 14 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 21 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 28 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 35 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 42 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 49 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 56 + if v < 128 { + goto done + } + + v = uint64(buf[i]) + i++ + x |= (v & 127) << 63 + if v < 128 { + goto done + } + + return 0, errOverflow + +done: + b.Pos = i + return +} + +// decodeVarint32 decodes a varint32 at the current position +func (b *BufferReader) DecodeVarint32() (x uint32, err error) { + i := b.Pos + buf := b.Buf + + if i >= len(buf) { + return 0, io.ErrUnexpectedEOF + } else if buf[i] < 0x80 { + b.Pos++ + return uint32(buf[i]), nil + } else if len(buf)-i < 5 { + v, err := b.DecodeVarintSlow() + return uint32(v), err + } + + var v uint32 + // we already checked the first byte + x = uint32(buf[i]) & 127 + i++ + + v = uint32(buf[i]) + i++ + x |= (v & 127) << 7 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + x |= (v & 127) << 14 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + x |= (v & 127) << 21 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + x |= (v & 127) << 28 + if v < 128 { + goto done + } + + return 0, errOverflow + +done: + b.Pos = i + return +} + +// skipValue skips a value in the protobuf, based on the specified tag +func (b *BufferReader) SkipValue(tag uint32) (err error) { + wireType := tag & 0x7 + switch protowire.Type(wireType) { + case protowire.VarintType: + err = b.SkipVarint() + case protowire.Fixed64Type: + err = b.SkipFixed64() + case protowire.BytesType: + var n uint32 + n, err = b.DecodeVarint32() + if err == nil { + err = b.Skip(int(n)) + } + case protowire.StartGroupType: + err = b.SkipGroup(tag) + case protowire.Fixed32Type: + err = b.SkipFixed32() + default: + err = fmt.Errorf("Unexpected wire type (%d)", wireType) + } + return +} + +// skipGroup skips a group with the specified tag. It executes efficiently using a tag stack +func (b *BufferReader) SkipGroup(tag uint32) (err error) { + tagStack := make([]uint32, 0, 16) + tagStack = append(tagStack, tag) + var n uint32 + for len(tagStack) > 0 { + tag, err = b.DecodeVarint32() + if err != nil { + return err + } + switch protowire.Type(tag & 0x7) { + case protowire.VarintType: + err = b.SkipVarint() + case protowire.Fixed64Type: + err = b.Skip(8) + case protowire.BytesType: + n, err = b.DecodeVarint32() + if err == nil { + err = b.Skip(int(n)) + } + case protowire.StartGroupType: + tagStack = append(tagStack, tag) + case protowire.Fixed32Type: + err = b.SkipFixed32() + case protowire.EndGroupType: + if protoFieldNumber(tagStack[len(tagStack)-1]) == protoFieldNumber(tag) { + tagStack = tagStack[:len(tagStack)-1] + } else { + err = fmt.Errorf("end group tag %d does not match begin group tag %d at pos %d", + protoFieldNumber(tag), protoFieldNumber(tagStack[len(tagStack)-1]), b.Pos) + } + } + if err != nil { + return err + } + } + return nil +} + +// skipVarint effiently skips a varint +func (b *BufferReader) SkipVarint() (err error) { + i := b.Pos + + if len(b.Buf)-i < 10 { + // Use DecodeVarintSlow() to check for buffer overflow, but ignore result + if _, err := b.DecodeVarintSlow(); err != nil { + return err + } + return nil + } + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + i++ + + if b.Buf[i] < 0x80 { + goto out + } + return errOverflow + +out: + b.Pos = i + 1 + return nil +} + +// skip skips the specified number of bytes +func (b *BufferReader) Skip(n int) (err error) { + if len(b.Buf) < b.Pos+n { + return io.ErrUnexpectedEOF + } + b.Pos += n + return +} + +// skipFixed64 skips a fixed64 +func (b *BufferReader) SkipFixed64() (err error) { + return b.Skip(8) +} + +// skipFixed32 skips a fixed32 +func (b *BufferReader) SkipFixed32() (err error) { + return b.Skip(4) +} + +// skipBytes skips a set of bytes +func (b *BufferReader) SkipBytes() (err error) { + n, err := b.DecodeVarint32() + if err != nil { + return err + } + return b.Skip(int(n)) +} + +// Done returns whether we are at the end of the protobuf +func (b *BufferReader) Done() bool { + return b.Pos == len(b.Buf) +} + +// Remaining returns how many bytes remain +func (b *BufferReader) Remaining() int { + return len(b.Buf) - b.Pos +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/lazy.go b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/lazy.go new file mode 100644 index 0000000000..ff4d4834bb --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/lazy.go @@ -0,0 +1,359 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package protolazy contains internal data structures for lazy message decoding. +package protolazy + +import ( + "fmt" + "sort" + + "google.golang.org/protobuf/encoding/protowire" + piface "google.golang.org/protobuf/runtime/protoiface" +) + +// IndexEntry is the structure for an index of the fields in a message of a +// proto (not descending to sub-messages) +type IndexEntry struct { + FieldNum uint32 + // first byte of this tag/field + Start uint32 + // first byte after a contiguous sequence of bytes for this tag/field, which could + // include a single encoding of the field, or multiple encodings for the field + End uint32 + // True if this protobuf segment includes multiple encodings of the field + MultipleContiguous bool +} + +// XXX_lazyUnmarshalInfo has information about a particular lazily decoded message +// +// Deprecated: Do not use. This will be deleted in the near future. +type XXX_lazyUnmarshalInfo struct { + // Index of fields and their positions in the protobuf for this + // message. Make index be a pointer to a slice so it can be updated + // atomically. The index pointer is only set once (lazily when/if + // the index is first needed), and must always be SET and LOADED + // ATOMICALLY. + index *[]IndexEntry + // The protobuf associated with this lazily decoded message. It is + // only set during proto.Unmarshal(). It doesn't need to be set and + // loaded atomically, since any simultaneous set (Unmarshal) and read + // (during a get) would already be a race in the app code. + Protobuf []byte + // The flags present when Unmarshal was originally called for this particular message + unmarshalFlags piface.UnmarshalInputFlags +} + +// The Buffer and SetBuffer methods let v2/internal/impl interact with +// XXX_lazyUnmarshalInfo via an interface, to avoid an import cycle. + +// Buffer returns the lazy unmarshal buffer. +// +// Deprecated: Do not use. This will be deleted in the near future. +func (lazy *XXX_lazyUnmarshalInfo) Buffer() []byte { + return lazy.Protobuf +} + +// SetBuffer sets the lazy unmarshal buffer. +// +// Deprecated: Do not use. This will be deleted in the near future. +func (lazy *XXX_lazyUnmarshalInfo) SetBuffer(b []byte) { + lazy.Protobuf = b +} + +// SetUnmarshalFlags is called to set a copy of the original unmarshalInputFlags. +// The flags should reflect how Unmarshal was called. +func (lazy *XXX_lazyUnmarshalInfo) SetUnmarshalFlags(f piface.UnmarshalInputFlags) { + lazy.unmarshalFlags = f +} + +// UnmarshalFlags returns the original unmarshalInputFlags. +func (lazy *XXX_lazyUnmarshalInfo) UnmarshalFlags() piface.UnmarshalInputFlags { + return lazy.unmarshalFlags +} + +// AllowedPartial returns true if the user originally unmarshalled this message with +// AllowPartial set to true +func (lazy *XXX_lazyUnmarshalInfo) AllowedPartial() bool { + return (lazy.unmarshalFlags & piface.UnmarshalCheckRequired) == 0 +} + +func protoFieldNumber(tag uint32) uint32 { + return tag >> 3 +} + +// buildIndex builds an index of the specified protobuf, return the index +// array and an error. +func buildIndex(buf []byte) ([]IndexEntry, error) { + index := make([]IndexEntry, 0, 16) + var lastProtoFieldNum uint32 + var outOfOrder bool + + var r BufferReader = NewBufferReader(buf) + + for !r.Done() { + var tag uint32 + var err error + var curPos = r.Pos + // INLINED: tag, err = r.DecodeVarint32() + { + i := r.Pos + buf := r.Buf + + if i >= len(buf) { + return nil, errOutOfBounds + } else if buf[i] < 0x80 { + r.Pos++ + tag = uint32(buf[i]) + } else if r.Remaining() < 5 { + var v uint64 + v, err = r.DecodeVarintSlow() + tag = uint32(v) + } else { + var v uint32 + // we already checked the first byte + tag = uint32(buf[i]) & 127 + i++ + + v = uint32(buf[i]) + i++ + tag |= (v & 127) << 7 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + tag |= (v & 127) << 14 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + tag |= (v & 127) << 21 + if v < 128 { + goto done + } + + v = uint32(buf[i]) + i++ + tag |= (v & 127) << 28 + if v < 128 { + goto done + } + + return nil, errOutOfBounds + + done: + r.Pos = i + } + } + // DONE: tag, err = r.DecodeVarint32() + + fieldNum := protoFieldNumber(tag) + if fieldNum < lastProtoFieldNum { + outOfOrder = true + } + + // Skip the current value -- will skip over an entire group as well. + // INLINED: err = r.SkipValue(tag) + wireType := tag & 0x7 + switch protowire.Type(wireType) { + case protowire.VarintType: + // INLINED: err = r.SkipVarint() + i := r.Pos + + if len(r.Buf)-i < 10 { + // Use DecodeVarintSlow() to skip while + // checking for buffer overflow, but ignore result + _, err = r.DecodeVarintSlow() + goto out2 + } + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + i++ + + if r.Buf[i] < 0x80 { + goto out + } + return nil, errOverflow + out: + r.Pos = i + 1 + // DONE: err = r.SkipVarint() + case protowire.Fixed64Type: + err = r.SkipFixed64() + case protowire.BytesType: + var n uint32 + n, err = r.DecodeVarint32() + if err == nil { + err = r.Skip(int(n)) + } + case protowire.StartGroupType: + err = r.SkipGroup(tag) + case protowire.Fixed32Type: + err = r.SkipFixed32() + default: + err = fmt.Errorf("Unexpected wire type (%d)", wireType) + } + // DONE: err = r.SkipValue(tag) + + out2: + if err != nil { + return nil, err + } + if fieldNum != lastProtoFieldNum { + index = append(index, IndexEntry{FieldNum: fieldNum, + Start: uint32(curPos), + End: uint32(r.Pos)}, + ) + } else { + index[len(index)-1].End = uint32(r.Pos) + index[len(index)-1].MultipleContiguous = true + } + lastProtoFieldNum = fieldNum + } + if outOfOrder { + sort.Slice(index, func(i, j int) bool { + return index[i].FieldNum < index[j].FieldNum || + (index[i].FieldNum == index[j].FieldNum && + index[i].Start < index[j].Start) + }) + } + return index, nil +} + +func (lazy *XXX_lazyUnmarshalInfo) SizeField(num uint32) (size int) { + start, end, found, _, multipleEntries := lazy.FindFieldInProto(num) + if multipleEntries != nil { + for _, entry := range multipleEntries { + size += int(entry.End - entry.Start) + } + return size + } + if !found { + return 0 + } + return int(end - start) +} + +func (lazy *XXX_lazyUnmarshalInfo) AppendField(b []byte, num uint32) ([]byte, bool) { + start, end, found, _, multipleEntries := lazy.FindFieldInProto(num) + if multipleEntries != nil { + for _, entry := range multipleEntries { + b = append(b, lazy.Protobuf[entry.Start:entry.End]...) + } + return b, true + } + if !found { + return nil, false + } + b = append(b, lazy.Protobuf[start:end]...) + return b, true +} + +func (lazy *XXX_lazyUnmarshalInfo) SetIndex(index []IndexEntry) { + atomicStoreIndex(&lazy.index, &index) +} + +// FindFieldInProto looks for field fieldNum in lazyUnmarshalInfo information +// (including protobuf), returns startOffset/endOffset/found. +func (lazy *XXX_lazyUnmarshalInfo) FindFieldInProto(fieldNum uint32) (start, end uint32, found, multipleContiguous bool, multipleEntries []IndexEntry) { + if lazy.Protobuf == nil { + // There is no backing protobuf for this message -- it was made from a builder + return 0, 0, false, false, nil + } + index := atomicLoadIndex(&lazy.index) + if index == nil { + r, err := buildIndex(lazy.Protobuf) + if err != nil { + panic(fmt.Sprintf("findFieldInfo: error building index when looking for field %d: %v", fieldNum, err)) + } + // lazy.index is a pointer to the slice returned by BuildIndex + index = &r + atomicStoreIndex(&lazy.index, index) + } + return lookupField(index, fieldNum) +} + +// lookupField returns the offset at which the indicated field starts using +// the index, offset immediately after field ends (including all instances of +// a repeated field), and bools indicating if field was found and if there +// are multiple encodings of the field in the byte range. +// +// To hande the uncommon case where there are repeated encodings for the same +// field which are not consecutive in the protobuf (so we need to returns +// multiple start/end offsets), we also return a slice multipleEntries. If +// multipleEntries is non-nil, then multiple entries were found, and the +// values in the slice should be used, rather than start/end/found. +func lookupField(indexp *[]IndexEntry, fieldNum uint32) (start, end uint32, found bool, multipleContiguous bool, multipleEntries []IndexEntry) { + // The pointer indexp to the index was already loaded atomically. + // The slice is uniquely associated with the pointer, so it doesn't + // need to be loaded atomically. + index := *indexp + for i, entry := range index { + if fieldNum == entry.FieldNum { + if i < len(index)-1 && entry.FieldNum == index[i+1].FieldNum { + // Handle the uncommon case where there are + // repeated entries for the same field which + // are not contiguous in the protobuf. + multiple := make([]IndexEntry, 1, 2) + multiple[0] = IndexEntry{fieldNum, entry.Start, entry.End, entry.MultipleContiguous} + i++ + for i < len(index) && index[i].FieldNum == fieldNum { + multiple = append(multiple, IndexEntry{fieldNum, index[i].Start, index[i].End, index[i].MultipleContiguous}) + i++ + } + return 0, 0, false, false, multiple + + } + return entry.Start, entry.End, true, entry.MultipleContiguous, nil + } + if fieldNum < entry.FieldNum { + return 0, 0, false, false, nil + } + } + return 0, 0, false, false, nil +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/pointer_unsafe.go b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/pointer_unsafe.go new file mode 100644 index 0000000000..dc2a64ca64 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/internal/protolazy/pointer_unsafe.go @@ -0,0 +1,17 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package protolazy + +import ( + "sync/atomic" + "unsafe" +) + +func atomicLoadIndex(p **[]IndexEntry) *[]IndexEntry { + return (*[]IndexEntry)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p)))) +} +func atomicStoreIndex(p **[]IndexEntry, v *[]IndexEntry) { + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v)) +} diff --git a/go-controller/vendor/google.golang.org/protobuf/internal/version/version.go b/go-controller/vendor/google.golang.org/protobuf/internal/version/version.go index fb8e15e8da..01efc33030 100644 --- a/go-controller/vendor/google.golang.org/protobuf/internal/version/version.go +++ b/go-controller/vendor/google.golang.org/protobuf/internal/version/version.go @@ -51,8 +51,8 @@ import ( // 10. Send out the CL for review and submit it. const ( Major = 1 - Minor = 35 - Patch = 1 + Minor = 36 + Patch = 5 PreRelease = "" ) diff --git a/go-controller/vendor/google.golang.org/protobuf/proto/decode.go b/go-controller/vendor/google.golang.org/protobuf/proto/decode.go index d75a6534c1..4cbf1aeaf7 100644 --- a/go-controller/vendor/google.golang.org/protobuf/proto/decode.go +++ b/go-controller/vendor/google.golang.org/protobuf/proto/decode.go @@ -8,7 +8,6 @@ import ( "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/internal/encoding/messageset" "google.golang.org/protobuf/internal/errors" - "google.golang.org/protobuf/internal/flags" "google.golang.org/protobuf/internal/genid" "google.golang.org/protobuf/internal/pragma" "google.golang.org/protobuf/reflect/protoreflect" @@ -47,6 +46,12 @@ type UnmarshalOptions struct { // RecursionLimit limits how deeply messages may be nested. // If zero, a default limit is applied. RecursionLimit int + + // + // NoLazyDecoding turns off lazy decoding, which otherwise is enabled by + // default. Lazy decoding only affects submessages (annotated with [lazy = + // true] in the .proto file) within messages that use the Opaque API. + NoLazyDecoding bool } // Unmarshal parses the wire-format message in b and places the result in m. @@ -104,6 +109,16 @@ func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out proto if o.DiscardUnknown { in.Flags |= protoiface.UnmarshalDiscardUnknown } + + if !allowPartial { + // This does not affect how current unmarshal functions work, it just allows them + // to record this for lazy the decoding case. + in.Flags |= protoiface.UnmarshalCheckRequired + } + if o.NoLazyDecoding { + in.Flags |= protoiface.UnmarshalNoLazyDecoding + } + out, err = methods.Unmarshal(in) } else { o.RecursionLimit-- @@ -156,10 +171,6 @@ func (o UnmarshalOptions) unmarshalMessageSlow(b []byte, m protoreflect.Message) var err error if fd == nil { err = errUnknown - } else if flags.ProtoLegacy { - if fd.IsWeak() && fd.Message().IsPlaceholder() { - err = errUnknown // weak referent is not linked in - } } // Parse the field value. diff --git a/go-controller/vendor/google.golang.org/protobuf/proto/encode.go b/go-controller/vendor/google.golang.org/protobuf/proto/encode.go index 1f847bcc35..f0473c5869 100644 --- a/go-controller/vendor/google.golang.org/protobuf/proto/encode.go +++ b/go-controller/vendor/google.golang.org/protobuf/proto/encode.go @@ -63,7 +63,8 @@ type MarshalOptions struct { // options (except for UseCachedSize itself). // // 2. The message and all its submessages have not changed in any - // way since the Size call. + // way since the Size call. For lazily decoded messages, accessing + // a message results in decoding the message, which is a change. // // If either of these invariants is violated, // the results are undefined and may include panics or corrupted output. diff --git a/go-controller/vendor/google.golang.org/protobuf/proto/size.go b/go-controller/vendor/google.golang.org/protobuf/proto/size.go index 052fb5ae31..c8675806c6 100644 --- a/go-controller/vendor/google.golang.org/protobuf/proto/size.go +++ b/go-controller/vendor/google.golang.org/protobuf/proto/size.go @@ -12,11 +12,19 @@ import ( ) // Size returns the size in bytes of the wire-format encoding of m. +// +// Note that Size might return more bytes than Marshal will write in the case of +// lazily decoded messages that arrive in non-minimal wire format: see +// https://protobuf.dev/reference/go/size/ for more details. func Size(m Message) int { return MarshalOptions{}.Size(m) } // Size returns the size in bytes of the wire-format encoding of m. +// +// Note that Size might return more bytes than Marshal will write in the case of +// lazily decoded messages that arrive in non-minimal wire format: see +// https://protobuf.dev/reference/go/size/ for more details. func (o MarshalOptions) Size(m Message) int { // Treat a nil message interface as an empty message; nothing to output. if m == nil { diff --git a/go-controller/vendor/google.golang.org/protobuf/proto/wrapperopaque.go b/go-controller/vendor/google.golang.org/protobuf/proto/wrapperopaque.go new file mode 100644 index 0000000000..267fd0f1f6 --- /dev/null +++ b/go-controller/vendor/google.golang.org/protobuf/proto/wrapperopaque.go @@ -0,0 +1,80 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proto + +// ValueOrNil returns nil if has is false, or a pointer to a new variable +// containing the value returned by the specified getter. +// +// This function is similar to the wrappers (proto.Int32(), proto.String(), +// etc.), but is generic (works for any field type) and works with the hasser +// and getter of a field, as opposed to a value. +// +// This is convenient when populating builder fields. +// +// Example: +// +// hop := attr.GetDirectHop() +// injectedRoute := ripb.InjectedRoute_builder{ +// Prefixes: route.GetPrefixes(), +// NextHop: proto.ValueOrNil(hop.HasAddress(), hop.GetAddress), +// } +func ValueOrNil[T any](has bool, getter func() T) *T { + if !has { + return nil + } + v := getter() + return &v +} + +// ValueOrDefault returns the protobuf message val if val is not nil, otherwise +// it returns a pointer to an empty val message. +// +// This function allows for translating code from the old Open Struct API to the +// new Opaque API. +// +// The old Open Struct API represented oneof fields with a wrapper struct: +// +// var signedImg *accountpb.SignedImage +// profile := &accountpb.Profile{ +// // The Avatar oneof will be set, with an empty SignedImage. +// Avatar: &accountpb.Profile_SignedImage{signedImg}, +// } +// +// The new Opaque API treats oneof fields like regular fields, there are no more +// wrapper structs: +// +// var signedImg *accountpb.SignedImage +// profile := &accountpb.Profile{} +// profile.SetSignedImage(signedImg) +// +// For convenience, the Opaque API also offers Builders, which allow for a +// direct translation of struct initialization. However, because Builders use +// nilness to represent field presence (but there is no non-nil wrapper struct +// anymore), Builders cannot distinguish between an unset oneof and a set oneof +// with nil message. The above code would need to be translated with help of the +// ValueOrDefault function to retain the same behavior: +// +// var signedImg *accountpb.SignedImage +// return &accountpb.Profile_builder{ +// SignedImage: proto.ValueOrDefault(signedImg), +// }.Build() +func ValueOrDefault[T interface { + *P + Message +}, P any](val T) T { + if val == nil { + return T(new(P)) + } + return val +} + +// ValueOrDefaultBytes is like ValueOrDefault but for working with fields of +// type []byte. +func ValueOrDefaultBytes(val []byte) []byte { + if val == nil { + return []byte{} + } + return val +} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc.go deleted file mode 100644 index 8fbecb4f58..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package protodesc provides functionality for converting -// FileDescriptorProto messages to/from [protoreflect.FileDescriptor] values. -// -// The google.protobuf.FileDescriptorProto is a protobuf message that describes -// the type information for a .proto file in a form that is easily serializable. -// The [protoreflect.FileDescriptor] is a more structured representation of -// the FileDescriptorProto message where references and remote dependencies -// can be directly followed. -package protodesc - -import ( - "google.golang.org/protobuf/internal/editionssupport" - "google.golang.org/protobuf/internal/errors" - "google.golang.org/protobuf/internal/filedesc" - "google.golang.org/protobuf/internal/pragma" - "google.golang.org/protobuf/internal/strs" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - - "google.golang.org/protobuf/types/descriptorpb" -) - -// Resolver is the resolver used by [NewFile] to resolve dependencies. -// The enums and messages provided must belong to some parent file, -// which is also registered. -// -// It is implemented by [protoregistry.Files]. -type Resolver interface { - FindFileByPath(string) (protoreflect.FileDescriptor, error) - FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error) -} - -// FileOptions configures the construction of file descriptors. -type FileOptions struct { - pragma.NoUnkeyedLiterals - - // AllowUnresolvable configures New to permissively allow unresolvable - // file, enum, or message dependencies. Unresolved dependencies are replaced - // by placeholder equivalents. - // - // The following dependencies may be left unresolved: - // • Resolving an imported file. - // • Resolving the type for a message field or extension field. - // If the kind of the field is unknown, then a placeholder is used for both - // the Enum and Message accessors on the protoreflect.FieldDescriptor. - // • Resolving an enum value set as the default for an optional enum field. - // If unresolvable, the protoreflect.FieldDescriptor.Default is set to the - // first value in the associated enum (or zero if the also enum dependency - // is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue - // is populated with a placeholder. - // • Resolving the extended message type for an extension field. - // • Resolving the input or output message type for a service method. - // - // If the unresolved dependency uses a relative name, - // then the placeholder will contain an invalid FullName with a "*." prefix, - // indicating that the starting prefix of the full name is unknown. - AllowUnresolvable bool -} - -// NewFile creates a new [protoreflect.FileDescriptor] from the provided -// file descriptor message. See [FileOptions.New] for more information. -func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) { - return FileOptions{}.New(fd, r) -} - -// NewFiles creates a new [protoregistry.Files] from the provided -// FileDescriptorSet message. See [FileOptions.NewFiles] for more information. -func NewFiles(fd *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) { - return FileOptions{}.NewFiles(fd) -} - -// New creates a new [protoreflect.FileDescriptor] from the provided -// file descriptor message. The file must represent a valid proto file according -// to protobuf semantics. The returned descriptor is a deep copy of the input. -// -// Any imported files, enum types, or message types referenced in the file are -// resolved using the provided registry. When looking up an import file path, -// the path must be unique. The newly created file descriptor is not registered -// back into the provided file registry. -func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) { - if r == nil { - r = (*protoregistry.Files)(nil) // empty resolver - } - - // Handle the file descriptor content. - f := &filedesc.File{L2: &filedesc.FileL2{}} - switch fd.GetSyntax() { - case "proto2", "": - f.L1.Syntax = protoreflect.Proto2 - f.L1.Edition = filedesc.EditionProto2 - case "proto3": - f.L1.Syntax = protoreflect.Proto3 - f.L1.Edition = filedesc.EditionProto3 - case "editions": - f.L1.Syntax = protoreflect.Editions - f.L1.Edition = fromEditionProto(fd.GetEdition()) - default: - return nil, errors.New("invalid syntax: %q", fd.GetSyntax()) - } - if f.L1.Syntax == protoreflect.Editions && (fd.GetEdition() < editionssupport.Minimum || fd.GetEdition() > editionssupport.Maximum) { - return nil, errors.New("use of edition %v not yet supported by the Go Protobuf runtime", fd.GetEdition()) - } - f.L1.Path = fd.GetName() - if f.L1.Path == "" { - return nil, errors.New("file path must be populated") - } - f.L1.Package = protoreflect.FullName(fd.GetPackage()) - if !f.L1.Package.IsValid() && f.L1.Package != "" { - return nil, errors.New("invalid package: %q", f.L1.Package) - } - if opts := fd.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.FileOptions) - f.L2.Options = func() protoreflect.ProtoMessage { return opts } - } - initFileDescFromFeatureSet(f, fd.GetOptions().GetFeatures()) - - f.L2.Imports = make(filedesc.FileImports, len(fd.GetDependency())) - for _, i := range fd.GetPublicDependency() { - if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsPublic { - return nil, errors.New("invalid or duplicate public import index: %d", i) - } - f.L2.Imports[i].IsPublic = true - } - for _, i := range fd.GetWeakDependency() { - if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsWeak { - return nil, errors.New("invalid or duplicate weak import index: %d", i) - } - f.L2.Imports[i].IsWeak = true - } - imps := importSet{f.Path(): true} - for i, path := range fd.GetDependency() { - imp := &f.L2.Imports[i] - f, err := r.FindFileByPath(path) - if err == protoregistry.NotFound && (o.AllowUnresolvable || imp.IsWeak) { - f = filedesc.PlaceholderFile(path) - } else if err != nil { - return nil, errors.New("could not resolve import %q: %v", path, err) - } - imp.FileDescriptor = f - - if imps[imp.Path()] { - return nil, errors.New("already imported %q", path) - } - imps[imp.Path()] = true - } - for i := range fd.GetDependency() { - imp := &f.L2.Imports[i] - imps.importPublic(imp.Imports()) - } - - // Handle source locations. - f.L2.Locations.File = f - for _, loc := range fd.GetSourceCodeInfo().GetLocation() { - var l protoreflect.SourceLocation - // TODO: Validate that the path points to an actual declaration? - l.Path = protoreflect.SourcePath(loc.GetPath()) - s := loc.GetSpan() - switch len(s) { - case 3: - l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[0]), int(s[2]) - case 4: - l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[2]), int(s[3]) - default: - return nil, errors.New("invalid span: %v", s) - } - // TODO: Validate that the span information is sensible? - // See https://github.com/protocolbuffers/protobuf/issues/6378. - if false && (l.EndLine < l.StartLine || l.StartLine < 0 || l.StartColumn < 0 || l.EndColumn < 0 || - (l.StartLine == l.EndLine && l.EndColumn <= l.StartColumn)) { - return nil, errors.New("invalid span: %v", s) - } - l.LeadingDetachedComments = loc.GetLeadingDetachedComments() - l.LeadingComments = loc.GetLeadingComments() - l.TrailingComments = loc.GetTrailingComments() - f.L2.Locations.List = append(f.L2.Locations.List, l) - } - - // Step 1: Allocate and derive the names for all declarations. - // This copies all fields from the descriptor proto except: - // google.protobuf.FieldDescriptorProto.type_name - // google.protobuf.FieldDescriptorProto.default_value - // google.protobuf.FieldDescriptorProto.oneof_index - // google.protobuf.FieldDescriptorProto.extendee - // google.protobuf.MethodDescriptorProto.input - // google.protobuf.MethodDescriptorProto.output - var err error - sb := new(strs.Builder) - r1 := make(descsByName) - if f.L1.Enums.List, err = r1.initEnumDeclarations(fd.GetEnumType(), f, sb); err != nil { - return nil, err - } - if f.L1.Messages.List, err = r1.initMessagesDeclarations(fd.GetMessageType(), f, sb); err != nil { - return nil, err - } - if f.L1.Extensions.List, err = r1.initExtensionDeclarations(fd.GetExtension(), f, sb); err != nil { - return nil, err - } - if f.L1.Services.List, err = r1.initServiceDeclarations(fd.GetService(), f, sb); err != nil { - return nil, err - } - - // Step 2: Resolve every dependency reference not handled by step 1. - r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: o.AllowUnresolvable} - if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil { - return nil, err - } - if err := r2.resolveExtensionDependencies(f.L1.Extensions.List, fd.GetExtension()); err != nil { - return nil, err - } - if err := r2.resolveServiceDependencies(f.L1.Services.List, fd.GetService()); err != nil { - return nil, err - } - - // Step 3: Validate every enum, message, and extension declaration. - if err := validateEnumDeclarations(f.L1.Enums.List, fd.GetEnumType()); err != nil { - return nil, err - } - if err := validateMessageDeclarations(f, f.L1.Messages.List, fd.GetMessageType()); err != nil { - return nil, err - } - if err := validateExtensionDeclarations(f, f.L1.Extensions.List, fd.GetExtension()); err != nil { - return nil, err - } - - return f, nil -} - -type importSet map[string]bool - -func (is importSet) importPublic(imps protoreflect.FileImports) { - for i := 0; i < imps.Len(); i++ { - if imp := imps.Get(i); imp.IsPublic { - is[imp.Path()] = true - is.importPublic(imp.Imports()) - } - } -} - -// NewFiles creates a new [protoregistry.Files] from the provided -// FileDescriptorSet message. The descriptor set must include only -// valid files according to protobuf semantics. The returned descriptors -// are a deep copy of the input. -func (o FileOptions) NewFiles(fds *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) { - files := make(map[string]*descriptorpb.FileDescriptorProto) - for _, fd := range fds.File { - if _, ok := files[fd.GetName()]; ok { - return nil, errors.New("file appears multiple times: %q", fd.GetName()) - } - files[fd.GetName()] = fd - } - r := &protoregistry.Files{} - for _, fd := range files { - if err := o.addFileDeps(r, fd, files); err != nil { - return nil, err - } - } - return r, nil -} -func (o FileOptions) addFileDeps(r *protoregistry.Files, fd *descriptorpb.FileDescriptorProto, files map[string]*descriptorpb.FileDescriptorProto) error { - // Set the entry to nil while descending into a file's dependencies to detect cycles. - files[fd.GetName()] = nil - for _, dep := range fd.Dependency { - depfd, ok := files[dep] - if depfd == nil { - if ok { - return errors.New("import cycle in file: %q", dep) - } - continue - } - if err := o.addFileDeps(r, depfd, files); err != nil { - return err - } - } - // Delete the entry once dependencies are processed. - delete(files, fd.GetName()) - f, err := o.New(fd, r) - if err != nil { - return err - } - return r.RegisterFile(f) -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go deleted file mode 100644 index ebcb4a8ab1..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protodesc - -import ( - "google.golang.org/protobuf/internal/errors" - "google.golang.org/protobuf/internal/filedesc" - "google.golang.org/protobuf/internal/strs" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - - "google.golang.org/protobuf/types/descriptorpb" -) - -type descsByName map[protoreflect.FullName]protoreflect.Descriptor - -func (r descsByName) initEnumDeclarations(eds []*descriptorpb.EnumDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (es []filedesc.Enum, err error) { - es = make([]filedesc.Enum, len(eds)) // allocate up-front to ensure stable pointers - for i, ed := range eds { - e := &es[i] - e.L2 = new(filedesc.EnumL2) - if e.L0, err = r.makeBase(e, parent, ed.GetName(), i, sb); err != nil { - return nil, err - } - if opts := ed.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.EnumOptions) - e.L2.Options = func() protoreflect.ProtoMessage { return opts } - } - e.L1.EditionFeatures = mergeEditionFeatures(parent, ed.GetOptions().GetFeatures()) - for _, s := range ed.GetReservedName() { - e.L2.ReservedNames.List = append(e.L2.ReservedNames.List, protoreflect.Name(s)) - } - for _, rr := range ed.GetReservedRange() { - e.L2.ReservedRanges.List = append(e.L2.ReservedRanges.List, [2]protoreflect.EnumNumber{ - protoreflect.EnumNumber(rr.GetStart()), - protoreflect.EnumNumber(rr.GetEnd()), - }) - } - if e.L2.Values.List, err = r.initEnumValuesFromDescriptorProto(ed.GetValue(), e, sb); err != nil { - return nil, err - } - } - return es, nil -} - -func (r descsByName) initEnumValuesFromDescriptorProto(vds []*descriptorpb.EnumValueDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (vs []filedesc.EnumValue, err error) { - vs = make([]filedesc.EnumValue, len(vds)) // allocate up-front to ensure stable pointers - for i, vd := range vds { - v := &vs[i] - if v.L0, err = r.makeBase(v, parent, vd.GetName(), i, sb); err != nil { - return nil, err - } - if opts := vd.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.EnumValueOptions) - v.L1.Options = func() protoreflect.ProtoMessage { return opts } - } - v.L1.Number = protoreflect.EnumNumber(vd.GetNumber()) - } - return vs, nil -} - -func (r descsByName) initMessagesDeclarations(mds []*descriptorpb.DescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (ms []filedesc.Message, err error) { - ms = make([]filedesc.Message, len(mds)) // allocate up-front to ensure stable pointers - for i, md := range mds { - m := &ms[i] - m.L2 = new(filedesc.MessageL2) - if m.L0, err = r.makeBase(m, parent, md.GetName(), i, sb); err != nil { - return nil, err - } - m.L1.EditionFeatures = mergeEditionFeatures(parent, md.GetOptions().GetFeatures()) - if opts := md.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.MessageOptions) - m.L2.Options = func() protoreflect.ProtoMessage { return opts } - m.L1.IsMapEntry = opts.GetMapEntry() - m.L1.IsMessageSet = opts.GetMessageSetWireFormat() - } - for _, s := range md.GetReservedName() { - m.L2.ReservedNames.List = append(m.L2.ReservedNames.List, protoreflect.Name(s)) - } - for _, rr := range md.GetReservedRange() { - m.L2.ReservedRanges.List = append(m.L2.ReservedRanges.List, [2]protoreflect.FieldNumber{ - protoreflect.FieldNumber(rr.GetStart()), - protoreflect.FieldNumber(rr.GetEnd()), - }) - } - for _, xr := range md.GetExtensionRange() { - m.L2.ExtensionRanges.List = append(m.L2.ExtensionRanges.List, [2]protoreflect.FieldNumber{ - protoreflect.FieldNumber(xr.GetStart()), - protoreflect.FieldNumber(xr.GetEnd()), - }) - var optsFunc func() protoreflect.ProtoMessage - if opts := xr.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.ExtensionRangeOptions) - optsFunc = func() protoreflect.ProtoMessage { return opts } - } - m.L2.ExtensionRangeOptions = append(m.L2.ExtensionRangeOptions, optsFunc) - } - if m.L2.Fields.List, err = r.initFieldsFromDescriptorProto(md.GetField(), m, sb); err != nil { - return nil, err - } - if m.L2.Oneofs.List, err = r.initOneofsFromDescriptorProto(md.GetOneofDecl(), m, sb); err != nil { - return nil, err - } - if m.L1.Enums.List, err = r.initEnumDeclarations(md.GetEnumType(), m, sb); err != nil { - return nil, err - } - if m.L1.Messages.List, err = r.initMessagesDeclarations(md.GetNestedType(), m, sb); err != nil { - return nil, err - } - if m.L1.Extensions.List, err = r.initExtensionDeclarations(md.GetExtension(), m, sb); err != nil { - return nil, err - } - } - return ms, nil -} - -// canBePacked returns whether the field can use packed encoding: -// https://protobuf.dev/programming-guides/encoding/#packed -func canBePacked(fd *descriptorpb.FieldDescriptorProto) bool { - if fd.GetLabel() != descriptorpb.FieldDescriptorProto_LABEL_REPEATED { - return false // not a repeated field - } - - switch protoreflect.Kind(fd.GetType()) { - case protoreflect.MessageKind, protoreflect.GroupKind: - return false // not a scalar type field - - case protoreflect.StringKind, protoreflect.BytesKind: - // string and bytes can explicitly not be declared as packed, - // see https://protobuf.dev/programming-guides/encoding/#packed - return false - - default: - return true - } -} - -func (r descsByName) initFieldsFromDescriptorProto(fds []*descriptorpb.FieldDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (fs []filedesc.Field, err error) { - fs = make([]filedesc.Field, len(fds)) // allocate up-front to ensure stable pointers - for i, fd := range fds { - f := &fs[i] - if f.L0, err = r.makeBase(f, parent, fd.GetName(), i, sb); err != nil { - return nil, err - } - f.L1.EditionFeatures = mergeEditionFeatures(parent, fd.GetOptions().GetFeatures()) - f.L1.IsProto3Optional = fd.GetProto3Optional() - if opts := fd.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.FieldOptions) - f.L1.Options = func() protoreflect.ProtoMessage { return opts } - f.L1.IsWeak = opts.GetWeak() - f.L1.IsLazy = opts.GetLazy() - if opts.Packed != nil { - f.L1.EditionFeatures.IsPacked = opts.GetPacked() - } - } - f.L1.Number = protoreflect.FieldNumber(fd.GetNumber()) - f.L1.Cardinality = protoreflect.Cardinality(fd.GetLabel()) - if fd.Type != nil { - f.L1.Kind = protoreflect.Kind(fd.GetType()) - } - if fd.JsonName != nil { - f.L1.StringName.InitJSON(fd.GetJsonName()) - } - - if f.L1.EditionFeatures.IsLegacyRequired { - f.L1.Cardinality = protoreflect.Required - } - - if f.L1.Kind == protoreflect.MessageKind && f.L1.EditionFeatures.IsDelimitedEncoded { - f.L1.Kind = protoreflect.GroupKind - } - } - return fs, nil -} - -func (r descsByName) initOneofsFromDescriptorProto(ods []*descriptorpb.OneofDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (os []filedesc.Oneof, err error) { - os = make([]filedesc.Oneof, len(ods)) // allocate up-front to ensure stable pointers - for i, od := range ods { - o := &os[i] - if o.L0, err = r.makeBase(o, parent, od.GetName(), i, sb); err != nil { - return nil, err - } - o.L1.EditionFeatures = mergeEditionFeatures(parent, od.GetOptions().GetFeatures()) - if opts := od.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.OneofOptions) - o.L1.Options = func() protoreflect.ProtoMessage { return opts } - } - } - return os, nil -} - -func (r descsByName) initExtensionDeclarations(xds []*descriptorpb.FieldDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (xs []filedesc.Extension, err error) { - xs = make([]filedesc.Extension, len(xds)) // allocate up-front to ensure stable pointers - for i, xd := range xds { - x := &xs[i] - x.L2 = new(filedesc.ExtensionL2) - if x.L0, err = r.makeBase(x, parent, xd.GetName(), i, sb); err != nil { - return nil, err - } - x.L1.EditionFeatures = mergeEditionFeatures(parent, xd.GetOptions().GetFeatures()) - if opts := xd.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.FieldOptions) - x.L2.Options = func() protoreflect.ProtoMessage { return opts } - if opts.Packed != nil { - x.L1.EditionFeatures.IsPacked = opts.GetPacked() - } - } - x.L1.Number = protoreflect.FieldNumber(xd.GetNumber()) - x.L1.Cardinality = protoreflect.Cardinality(xd.GetLabel()) - if xd.Type != nil { - x.L1.Kind = protoreflect.Kind(xd.GetType()) - } - if xd.JsonName != nil { - x.L2.StringName.InitJSON(xd.GetJsonName()) - } - if x.L1.Kind == protoreflect.MessageKind && x.L1.EditionFeatures.IsDelimitedEncoded { - x.L1.Kind = protoreflect.GroupKind - } - } - return xs, nil -} - -func (r descsByName) initServiceDeclarations(sds []*descriptorpb.ServiceDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (ss []filedesc.Service, err error) { - ss = make([]filedesc.Service, len(sds)) // allocate up-front to ensure stable pointers - for i, sd := range sds { - s := &ss[i] - s.L2 = new(filedesc.ServiceL2) - if s.L0, err = r.makeBase(s, parent, sd.GetName(), i, sb); err != nil { - return nil, err - } - if opts := sd.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.ServiceOptions) - s.L2.Options = func() protoreflect.ProtoMessage { return opts } - } - if s.L2.Methods.List, err = r.initMethodsFromDescriptorProto(sd.GetMethod(), s, sb); err != nil { - return nil, err - } - } - return ss, nil -} - -func (r descsByName) initMethodsFromDescriptorProto(mds []*descriptorpb.MethodDescriptorProto, parent protoreflect.Descriptor, sb *strs.Builder) (ms []filedesc.Method, err error) { - ms = make([]filedesc.Method, len(mds)) // allocate up-front to ensure stable pointers - for i, md := range mds { - m := &ms[i] - if m.L0, err = r.makeBase(m, parent, md.GetName(), i, sb); err != nil { - return nil, err - } - if opts := md.GetOptions(); opts != nil { - opts = proto.Clone(opts).(*descriptorpb.MethodOptions) - m.L1.Options = func() protoreflect.ProtoMessage { return opts } - } - m.L1.IsStreamingClient = md.GetClientStreaming() - m.L1.IsStreamingServer = md.GetServerStreaming() - } - return ms, nil -} - -func (r descsByName) makeBase(child, parent protoreflect.Descriptor, name string, idx int, sb *strs.Builder) (filedesc.BaseL0, error) { - if !protoreflect.Name(name).IsValid() { - return filedesc.BaseL0{}, errors.New("descriptor %q has an invalid nested name: %q", parent.FullName(), name) - } - - // Derive the full name of the child. - // Note that enum values are a sibling to the enum parent in the namespace. - var fullName protoreflect.FullName - if _, ok := parent.(protoreflect.EnumDescriptor); ok { - fullName = sb.AppendFullName(parent.FullName().Parent(), protoreflect.Name(name)) - } else { - fullName = sb.AppendFullName(parent.FullName(), protoreflect.Name(name)) - } - if _, ok := r[fullName]; ok { - return filedesc.BaseL0{}, errors.New("descriptor %q already declared", fullName) - } - r[fullName] = child - - // TODO: Verify that the full name does not already exist in the resolver? - // This is not as critical since most usages of NewFile will register - // the created file back into the registry, which will perform this check. - - return filedesc.BaseL0{ - FullName: fullName, - ParentFile: parent.ParentFile().(*filedesc.File), - Parent: parent, - Index: idx, - }, nil -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_resolve.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_resolve.go deleted file mode 100644 index f3cebab29c..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_resolve.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protodesc - -import ( - "google.golang.org/protobuf/internal/encoding/defval" - "google.golang.org/protobuf/internal/errors" - "google.golang.org/protobuf/internal/filedesc" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - - "google.golang.org/protobuf/types/descriptorpb" -) - -// resolver is a wrapper around a local registry of declarations within the file -// and the remote resolver. The remote resolver is restricted to only return -// descriptors that have been imported. -type resolver struct { - local descsByName - remote Resolver - imports importSet - - allowUnresolvable bool -} - -func (r *resolver) resolveMessageDependencies(ms []filedesc.Message, mds []*descriptorpb.DescriptorProto) (err error) { - for i, md := range mds { - m := &ms[i] - for j, fd := range md.GetField() { - f := &m.L2.Fields.List[j] - if f.L1.Cardinality == protoreflect.Required { - m.L2.RequiredNumbers.List = append(m.L2.RequiredNumbers.List, f.L1.Number) - } - if fd.OneofIndex != nil { - k := int(fd.GetOneofIndex()) - if !(0 <= k && k < len(md.GetOneofDecl())) { - return errors.New("message field %q has an invalid oneof index: %d", f.FullName(), k) - } - o := &m.L2.Oneofs.List[k] - f.L1.ContainingOneof = o - o.L1.Fields.List = append(o.L1.Fields.List, f) - } - - if f.L1.Kind, f.L1.Enum, f.L1.Message, err = r.findTarget(f.Kind(), f.Parent().FullName(), partialName(fd.GetTypeName()), f.IsWeak()); err != nil { - return errors.New("message field %q cannot resolve type: %v", f.FullName(), err) - } - if f.L1.Kind == protoreflect.GroupKind && (f.IsMap() || f.IsMapEntry()) { - // A map field might inherit delimited encoding from a file-wide default feature. - // But maps never actually use delimited encoding. (At least for now...) - f.L1.Kind = protoreflect.MessageKind - } - if fd.DefaultValue != nil { - v, ev, err := unmarshalDefault(fd.GetDefaultValue(), f, r.allowUnresolvable) - if err != nil { - return errors.New("message field %q has invalid default: %v", f.FullName(), err) - } - f.L1.Default = filedesc.DefaultValue(v, ev) - } - } - - if err := r.resolveMessageDependencies(m.L1.Messages.List, md.GetNestedType()); err != nil { - return err - } - if err := r.resolveExtensionDependencies(m.L1.Extensions.List, md.GetExtension()); err != nil { - return err - } - } - return nil -} - -func (r *resolver) resolveExtensionDependencies(xs []filedesc.Extension, xds []*descriptorpb.FieldDescriptorProto) (err error) { - for i, xd := range xds { - x := &xs[i] - if x.L1.Extendee, err = r.findMessageDescriptor(x.Parent().FullName(), partialName(xd.GetExtendee()), false); err != nil { - return errors.New("extension field %q cannot resolve extendee: %v", x.FullName(), err) - } - if x.L1.Kind, x.L2.Enum, x.L2.Message, err = r.findTarget(x.Kind(), x.Parent().FullName(), partialName(xd.GetTypeName()), false); err != nil { - return errors.New("extension field %q cannot resolve type: %v", x.FullName(), err) - } - if xd.DefaultValue != nil { - v, ev, err := unmarshalDefault(xd.GetDefaultValue(), x, r.allowUnresolvable) - if err != nil { - return errors.New("extension field %q has invalid default: %v", x.FullName(), err) - } - x.L2.Default = filedesc.DefaultValue(v, ev) - } - } - return nil -} - -func (r *resolver) resolveServiceDependencies(ss []filedesc.Service, sds []*descriptorpb.ServiceDescriptorProto) (err error) { - for i, sd := range sds { - s := &ss[i] - for j, md := range sd.GetMethod() { - m := &s.L2.Methods.List[j] - m.L1.Input, err = r.findMessageDescriptor(m.Parent().FullName(), partialName(md.GetInputType()), false) - if err != nil { - return errors.New("service method %q cannot resolve input: %v", m.FullName(), err) - } - m.L1.Output, err = r.findMessageDescriptor(s.FullName(), partialName(md.GetOutputType()), false) - if err != nil { - return errors.New("service method %q cannot resolve output: %v", m.FullName(), err) - } - } - } - return nil -} - -// findTarget finds an enum or message descriptor if k is an enum, message, -// group, or unknown. If unknown, and the name could be resolved, the kind -// returned kind is set based on the type of the resolved descriptor. -func (r *resolver) findTarget(k protoreflect.Kind, scope protoreflect.FullName, ref partialName, isWeak bool) (protoreflect.Kind, protoreflect.EnumDescriptor, protoreflect.MessageDescriptor, error) { - switch k { - case protoreflect.EnumKind: - ed, err := r.findEnumDescriptor(scope, ref, isWeak) - if err != nil { - return 0, nil, nil, err - } - return k, ed, nil, nil - case protoreflect.MessageKind, protoreflect.GroupKind: - md, err := r.findMessageDescriptor(scope, ref, isWeak) - if err != nil { - return 0, nil, nil, err - } - return k, nil, md, nil - case 0: - // Handle unspecified kinds (possible with parsers that operate - // on a per-file basis without knowledge of dependencies). - d, err := r.findDescriptor(scope, ref) - if err == protoregistry.NotFound && (r.allowUnresolvable || isWeak) { - return k, filedesc.PlaceholderEnum(ref.FullName()), filedesc.PlaceholderMessage(ref.FullName()), nil - } else if err == protoregistry.NotFound { - return 0, nil, nil, errors.New("%q not found", ref.FullName()) - } else if err != nil { - return 0, nil, nil, err - } - switch d := d.(type) { - case protoreflect.EnumDescriptor: - return protoreflect.EnumKind, d, nil, nil - case protoreflect.MessageDescriptor: - return protoreflect.MessageKind, nil, d, nil - default: - return 0, nil, nil, errors.New("unknown kind") - } - default: - if ref != "" { - return 0, nil, nil, errors.New("target name cannot be specified for %v", k) - } - if !k.IsValid() { - return 0, nil, nil, errors.New("invalid kind: %d", k) - } - return k, nil, nil, nil - } -} - -// findDescriptor finds the descriptor by name, -// which may be a relative name within some scope. -// -// Suppose the scope was "fizz.buzz" and the reference was "Foo.Bar", -// then the following full names are searched: -// - fizz.buzz.Foo.Bar -// - fizz.Foo.Bar -// - Foo.Bar -func (r *resolver) findDescriptor(scope protoreflect.FullName, ref partialName) (protoreflect.Descriptor, error) { - if !ref.IsValid() { - return nil, errors.New("invalid name reference: %q", ref) - } - if ref.IsFull() { - scope, ref = "", ref[1:] - } - var foundButNotImported protoreflect.Descriptor - for { - // Derive the full name to search. - s := protoreflect.FullName(ref) - if scope != "" { - s = scope + "." + s - } - - // Check the current file for the descriptor. - if d, ok := r.local[s]; ok { - return d, nil - } - - // Check the remote registry for the descriptor. - d, err := r.remote.FindDescriptorByName(s) - if err == nil { - // Only allow descriptors covered by one of the imports. - if r.imports[d.ParentFile().Path()] { - return d, nil - } - foundButNotImported = d - } else if err != protoregistry.NotFound { - return nil, errors.Wrap(err, "%q", s) - } - - // Continue on at a higher level of scoping. - if scope == "" { - if d := foundButNotImported; d != nil { - return nil, errors.New("resolved %q, but %q is not imported", d.FullName(), d.ParentFile().Path()) - } - return nil, protoregistry.NotFound - } - scope = scope.Parent() - } -} - -func (r *resolver) findEnumDescriptor(scope protoreflect.FullName, ref partialName, isWeak bool) (protoreflect.EnumDescriptor, error) { - d, err := r.findDescriptor(scope, ref) - if err == protoregistry.NotFound && (r.allowUnresolvable || isWeak) { - return filedesc.PlaceholderEnum(ref.FullName()), nil - } else if err == protoregistry.NotFound { - return nil, errors.New("%q not found", ref.FullName()) - } else if err != nil { - return nil, err - } - ed, ok := d.(protoreflect.EnumDescriptor) - if !ok { - return nil, errors.New("resolved %q, but it is not an enum", d.FullName()) - } - return ed, nil -} - -func (r *resolver) findMessageDescriptor(scope protoreflect.FullName, ref partialName, isWeak bool) (protoreflect.MessageDescriptor, error) { - d, err := r.findDescriptor(scope, ref) - if err == protoregistry.NotFound && (r.allowUnresolvable || isWeak) { - return filedesc.PlaceholderMessage(ref.FullName()), nil - } else if err == protoregistry.NotFound { - return nil, errors.New("%q not found", ref.FullName()) - } else if err != nil { - return nil, err - } - md, ok := d.(protoreflect.MessageDescriptor) - if !ok { - return nil, errors.New("resolved %q, but it is not an message", d.FullName()) - } - return md, nil -} - -// partialName is the partial name. A leading dot means that the name is full, -// otherwise the name is relative to some current scope. -// See google.protobuf.FieldDescriptorProto.type_name. -type partialName string - -func (s partialName) IsFull() bool { - return len(s) > 0 && s[0] == '.' -} - -func (s partialName) IsValid() bool { - if s.IsFull() { - return protoreflect.FullName(s[1:]).IsValid() - } - return protoreflect.FullName(s).IsValid() -} - -const unknownPrefix = "*." - -// FullName converts the partial name to a full name on a best-effort basis. -// If relative, it creates an invalid full name, using a "*." prefix -// to indicate that the start of the full name is unknown. -func (s partialName) FullName() protoreflect.FullName { - if s.IsFull() { - return protoreflect.FullName(s[1:]) - } - return protoreflect.FullName(unknownPrefix + s) -} - -func unmarshalDefault(s string, fd protoreflect.FieldDescriptor, allowUnresolvable bool) (protoreflect.Value, protoreflect.EnumValueDescriptor, error) { - var evs protoreflect.EnumValueDescriptors - if fd.Enum() != nil { - evs = fd.Enum().Values() - } - v, ev, err := defval.Unmarshal(s, fd.Kind(), evs, defval.Descriptor) - if err != nil && allowUnresolvable && evs != nil && protoreflect.Name(s).IsValid() { - v = protoreflect.ValueOfEnum(0) - if evs.Len() > 0 { - v = protoreflect.ValueOfEnum(evs.Get(0).Number()) - } - ev = filedesc.PlaceholderEnumValue(fd.Enum().FullName().Parent().Append(protoreflect.Name(s))) - } else if err != nil { - return v, ev, err - } - if !fd.HasPresence() { - return v, ev, errors.New("cannot be specified with implicit field presence") - } - if fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind || fd.Cardinality() == protoreflect.Repeated { - return v, ev, errors.New("cannot be specified on composite types") - } - return v, ev, nil -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_validate.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_validate.go deleted file mode 100644 index 6de31c2ebd..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/desc_validate.go +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protodesc - -import ( - "strings" - "unicode" - - "google.golang.org/protobuf/encoding/protowire" - "google.golang.org/protobuf/internal/errors" - "google.golang.org/protobuf/internal/filedesc" - "google.golang.org/protobuf/internal/flags" - "google.golang.org/protobuf/internal/genid" - "google.golang.org/protobuf/internal/strs" - "google.golang.org/protobuf/reflect/protoreflect" - - "google.golang.org/protobuf/types/descriptorpb" -) - -func validateEnumDeclarations(es []filedesc.Enum, eds []*descriptorpb.EnumDescriptorProto) error { - for i, ed := range eds { - e := &es[i] - if err := e.L2.ReservedNames.CheckValid(); err != nil { - return errors.New("enum %q reserved names has %v", e.FullName(), err) - } - if err := e.L2.ReservedRanges.CheckValid(); err != nil { - return errors.New("enum %q reserved ranges has %v", e.FullName(), err) - } - if len(ed.GetValue()) == 0 { - return errors.New("enum %q must contain at least one value declaration", e.FullName()) - } - allowAlias := ed.GetOptions().GetAllowAlias() - foundAlias := false - for i := 0; i < e.Values().Len(); i++ { - v1 := e.Values().Get(i) - if v2 := e.Values().ByNumber(v1.Number()); v1 != v2 { - foundAlias = true - if !allowAlias { - return errors.New("enum %q has conflicting non-aliased values on number %d: %q with %q", e.FullName(), v1.Number(), v1.Name(), v2.Name()) - } - } - } - if allowAlias && !foundAlias { - return errors.New("enum %q allows aliases, but none were found", e.FullName()) - } - if !e.IsClosed() { - if v := e.Values().Get(0); v.Number() != 0 { - return errors.New("enum %q using open semantics must have zero number for the first value", v.FullName()) - } - // Verify that value names in open enums do not conflict if the - // case-insensitive prefix is removed. - // See protoc v3.8.0: src/google/protobuf/descriptor.cc:4991-5055 - names := map[string]protoreflect.EnumValueDescriptor{} - prefix := strings.Replace(strings.ToLower(string(e.Name())), "_", "", -1) - for i := 0; i < e.Values().Len(); i++ { - v1 := e.Values().Get(i) - s := strs.EnumValueName(strs.TrimEnumPrefix(string(v1.Name()), prefix)) - if v2, ok := names[s]; ok && v1.Number() != v2.Number() { - return errors.New("enum %q using open semantics has conflict: %q with %q", e.FullName(), v1.Name(), v2.Name()) - } - names[s] = v1 - } - } - - for j, vd := range ed.GetValue() { - v := &e.L2.Values.List[j] - if vd.Number == nil { - return errors.New("enum value %q must have a specified number", v.FullName()) - } - if e.L2.ReservedNames.Has(v.Name()) { - return errors.New("enum value %q must not use reserved name", v.FullName()) - } - if e.L2.ReservedRanges.Has(v.Number()) { - return errors.New("enum value %q must not use reserved number %d", v.FullName(), v.Number()) - } - } - } - return nil -} - -func validateMessageDeclarations(file *filedesc.File, ms []filedesc.Message, mds []*descriptorpb.DescriptorProto) error { - // There are a few limited exceptions only for proto3 - isProto3 := file.L1.Edition == fromEditionProto(descriptorpb.Edition_EDITION_PROTO3) - for i, md := range mds { - m := &ms[i] - - // Handle the message descriptor itself. - isMessageSet := md.GetOptions().GetMessageSetWireFormat() - if err := m.L2.ReservedNames.CheckValid(); err != nil { - return errors.New("message %q reserved names has %v", m.FullName(), err) - } - if err := m.L2.ReservedRanges.CheckValid(isMessageSet); err != nil { - return errors.New("message %q reserved ranges has %v", m.FullName(), err) - } - if err := m.L2.ExtensionRanges.CheckValid(isMessageSet); err != nil { - return errors.New("message %q extension ranges has %v", m.FullName(), err) - } - if err := (*filedesc.FieldRanges).CheckOverlap(&m.L2.ReservedRanges, &m.L2.ExtensionRanges); err != nil { - return errors.New("message %q reserved and extension ranges has %v", m.FullName(), err) - } - for i := 0; i < m.Fields().Len(); i++ { - f1 := m.Fields().Get(i) - if f2 := m.Fields().ByNumber(f1.Number()); f1 != f2 { - return errors.New("message %q has conflicting fields: %q with %q", m.FullName(), f1.Name(), f2.Name()) - } - } - if isMessageSet && !flags.ProtoLegacy { - return errors.New("message %q is a MessageSet, which is a legacy proto1 feature that is no longer supported", m.FullName()) - } - if isMessageSet && (isProto3 || m.Fields().Len() > 0 || m.ExtensionRanges().Len() == 0) { - return errors.New("message %q is an invalid proto1 MessageSet", m.FullName()) - } - if isProto3 { - if m.ExtensionRanges().Len() > 0 { - return errors.New("message %q using proto3 semantics cannot have extension ranges", m.FullName()) - } - } - - for j, fd := range md.GetField() { - f := &m.L2.Fields.List[j] - if m.L2.ReservedNames.Has(f.Name()) { - return errors.New("message field %q must not use reserved name", f.FullName()) - } - if !f.Number().IsValid() { - return errors.New("message field %q has an invalid number: %d", f.FullName(), f.Number()) - } - if !f.Cardinality().IsValid() { - return errors.New("message field %q has an invalid cardinality: %d", f.FullName(), f.Cardinality()) - } - if m.L2.ReservedRanges.Has(f.Number()) { - return errors.New("message field %q must not use reserved number %d", f.FullName(), f.Number()) - } - if m.L2.ExtensionRanges.Has(f.Number()) { - return errors.New("message field %q with number %d in extension range", f.FullName(), f.Number()) - } - if fd.Extendee != nil { - return errors.New("message field %q may not have extendee: %q", f.FullName(), fd.GetExtendee()) - } - if f.L1.IsProto3Optional { - if !isProto3 { - return errors.New("message field %q under proto3 optional semantics must be specified in the proto3 syntax", f.FullName()) - } - if f.Cardinality() != protoreflect.Optional { - return errors.New("message field %q under proto3 optional semantics must have optional cardinality", f.FullName()) - } - if f.ContainingOneof() != nil && f.ContainingOneof().Fields().Len() != 1 { - return errors.New("message field %q under proto3 optional semantics must be within a single element oneof", f.FullName()) - } - } - if f.IsWeak() && !flags.ProtoLegacy { - return errors.New("message field %q is a weak field, which is a legacy proto1 feature that is no longer supported", f.FullName()) - } - if f.IsWeak() && (!f.HasPresence() || !isOptionalMessage(f) || f.ContainingOneof() != nil) { - return errors.New("message field %q may only be weak for an optional message", f.FullName()) - } - if f.IsPacked() && !isPackable(f) { - return errors.New("message field %q is not packable", f.FullName()) - } - if err := checkValidGroup(file, f); err != nil { - return errors.New("message field %q is an invalid group: %v", f.FullName(), err) - } - if err := checkValidMap(f); err != nil { - return errors.New("message field %q is an invalid map: %v", f.FullName(), err) - } - if isProto3 { - if f.Cardinality() == protoreflect.Required { - return errors.New("message field %q using proto3 semantics cannot be required", f.FullName()) - } - if f.Enum() != nil && !f.Enum().IsPlaceholder() && f.Enum().IsClosed() { - return errors.New("message field %q using proto3 semantics may only depend on open enums", f.FullName()) - } - } - if f.Cardinality() == protoreflect.Optional && !f.HasPresence() && f.Enum() != nil && !f.Enum().IsPlaceholder() && f.Enum().IsClosed() { - return errors.New("message field %q with implicit presence may only use open enums", f.FullName()) - } - } - seenSynthetic := false // synthetic oneofs for proto3 optional must come after real oneofs - for j := range md.GetOneofDecl() { - o := &m.L2.Oneofs.List[j] - if o.Fields().Len() == 0 { - return errors.New("message oneof %q must contain at least one field declaration", o.FullName()) - } - if n := o.Fields().Len(); n-1 != (o.Fields().Get(n-1).Index() - o.Fields().Get(0).Index()) { - return errors.New("message oneof %q must have consecutively declared fields", o.FullName()) - } - - if o.IsSynthetic() { - seenSynthetic = true - continue - } - if !o.IsSynthetic() && seenSynthetic { - return errors.New("message oneof %q must be declared before synthetic oneofs", o.FullName()) - } - - for i := 0; i < o.Fields().Len(); i++ { - f := o.Fields().Get(i) - if f.Cardinality() != protoreflect.Optional { - return errors.New("message field %q belongs in a oneof and must be optional", f.FullName()) - } - if f.IsWeak() { - return errors.New("message field %q belongs in a oneof and must not be a weak reference", f.FullName()) - } - } - } - - if err := validateEnumDeclarations(m.L1.Enums.List, md.GetEnumType()); err != nil { - return err - } - if err := validateMessageDeclarations(file, m.L1.Messages.List, md.GetNestedType()); err != nil { - return err - } - if err := validateExtensionDeclarations(file, m.L1.Extensions.List, md.GetExtension()); err != nil { - return err - } - } - return nil -} - -func validateExtensionDeclarations(f *filedesc.File, xs []filedesc.Extension, xds []*descriptorpb.FieldDescriptorProto) error { - for i, xd := range xds { - x := &xs[i] - // NOTE: Avoid using the IsValid method since extensions to MessageSet - // may have a field number higher than normal. This check only verifies - // that the number is not negative or reserved. We check again later - // if we know that the extendee is definitely not a MessageSet. - if n := x.Number(); n < 0 || (protowire.FirstReservedNumber <= n && n <= protowire.LastReservedNumber) { - return errors.New("extension field %q has an invalid number: %d", x.FullName(), x.Number()) - } - if !x.Cardinality().IsValid() || x.Cardinality() == protoreflect.Required { - return errors.New("extension field %q has an invalid cardinality: %d", x.FullName(), x.Cardinality()) - } - if xd.JsonName != nil { - // A bug in older versions of protoc would always populate the - // "json_name" option for extensions when it is meaningless. - // When it did so, it would always use the camel-cased field name. - if xd.GetJsonName() != strs.JSONCamelCase(string(x.Name())) { - return errors.New("extension field %q may not have an explicitly set JSON name: %q", x.FullName(), xd.GetJsonName()) - } - } - if xd.OneofIndex != nil { - return errors.New("extension field %q may not be part of a oneof", x.FullName()) - } - if md := x.ContainingMessage(); !md.IsPlaceholder() { - if !md.ExtensionRanges().Has(x.Number()) { - return errors.New("extension field %q extends %q with non-extension field number: %d", x.FullName(), md.FullName(), x.Number()) - } - isMessageSet := md.Options().(*descriptorpb.MessageOptions).GetMessageSetWireFormat() - if isMessageSet && !isOptionalMessage(x) { - return errors.New("extension field %q extends MessageSet and must be an optional message", x.FullName()) - } - if !isMessageSet && !x.Number().IsValid() { - return errors.New("extension field %q has an invalid number: %d", x.FullName(), x.Number()) - } - } - if xd.GetOptions().GetWeak() { - return errors.New("extension field %q cannot be a weak reference", x.FullName()) - } - if x.IsPacked() && !isPackable(x) { - return errors.New("extension field %q is not packable", x.FullName()) - } - if err := checkValidGroup(f, x); err != nil { - return errors.New("extension field %q is an invalid group: %v", x.FullName(), err) - } - if md := x.Message(); md != nil && md.IsMapEntry() { - return errors.New("extension field %q cannot be a map entry", x.FullName()) - } - if f.L1.Edition == fromEditionProto(descriptorpb.Edition_EDITION_PROTO3) { - switch x.ContainingMessage().FullName() { - case (*descriptorpb.FileOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.EnumOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.EnumValueOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.MessageOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.FieldOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.OneofOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.ExtensionRangeOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.ServiceOptions)(nil).ProtoReflect().Descriptor().FullName(): - case (*descriptorpb.MethodOptions)(nil).ProtoReflect().Descriptor().FullName(): - default: - return errors.New("extension field %q cannot be declared in proto3 unless extended descriptor options", x.FullName()) - } - } - } - return nil -} - -// isOptionalMessage reports whether this is an optional message. -// If the kind is unknown, it is assumed to be a message. -func isOptionalMessage(fd protoreflect.FieldDescriptor) bool { - return (fd.Kind() == 0 || fd.Kind() == protoreflect.MessageKind) && fd.Cardinality() == protoreflect.Optional -} - -// isPackable checks whether the pack option can be specified. -func isPackable(fd protoreflect.FieldDescriptor) bool { - switch fd.Kind() { - case protoreflect.StringKind, protoreflect.BytesKind, protoreflect.MessageKind, protoreflect.GroupKind: - return false - } - return fd.IsList() -} - -// checkValidGroup reports whether fd is a valid group according to the same -// rules that protoc imposes. -func checkValidGroup(f *filedesc.File, fd protoreflect.FieldDescriptor) error { - md := fd.Message() - switch { - case fd.Kind() != protoreflect.GroupKind: - return nil - case f.L1.Edition == fromEditionProto(descriptorpb.Edition_EDITION_PROTO3): - return errors.New("invalid under proto3 semantics") - case md == nil || md.IsPlaceholder(): - return errors.New("message must be resolvable") - } - if f.L1.Edition < fromEditionProto(descriptorpb.Edition_EDITION_2023) { - switch { - case fd.FullName().Parent() != md.FullName().Parent(): - return errors.New("message and field must be declared in the same scope") - case !unicode.IsUpper(rune(md.Name()[0])): - return errors.New("message name must start with an uppercase") - case fd.Name() != protoreflect.Name(strings.ToLower(string(md.Name()))): - return errors.New("field name must be lowercased form of the message name") - } - } - return nil -} - -// checkValidMap checks whether the field is a valid map according to the same -// rules that protoc imposes. -// See protoc v3.8.0: src/google/protobuf/descriptor.cc:6045-6115 -func checkValidMap(fd protoreflect.FieldDescriptor) error { - md := fd.Message() - switch { - case md == nil || !md.IsMapEntry(): - return nil - case fd.FullName().Parent() != md.FullName().Parent(): - return errors.New("message and field must be declared in the same scope") - case md.Name() != protoreflect.Name(strs.MapEntryName(string(fd.Name()))): - return errors.New("incorrect implicit map entry name") - case fd.Cardinality() != protoreflect.Repeated: - return errors.New("field must be repeated") - case md.Fields().Len() != 2: - return errors.New("message must have exactly two fields") - case md.ExtensionRanges().Len() > 0: - return errors.New("message must not have any extension ranges") - case md.Enums().Len()+md.Messages().Len()+md.Extensions().Len() > 0: - return errors.New("message must not have any nested declarations") - } - kf := md.Fields().Get(0) - vf := md.Fields().Get(1) - switch { - case kf.Name() != genid.MapEntry_Key_field_name || kf.Number() != genid.MapEntry_Key_field_number || kf.Cardinality() != protoreflect.Optional || kf.ContainingOneof() != nil || kf.HasDefault(): - return errors.New("invalid key field") - case vf.Name() != genid.MapEntry_Value_field_name || vf.Number() != genid.MapEntry_Value_field_number || vf.Cardinality() != protoreflect.Optional || vf.ContainingOneof() != nil || vf.HasDefault(): - return errors.New("invalid value field") - } - switch kf.Kind() { - case protoreflect.BoolKind: // bool - case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: // int32 - case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: // int64 - case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: // uint32 - case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: // uint64 - case protoreflect.StringKind: // string - default: - return errors.New("invalid key kind: %v", kf.Kind()) - } - if e := vf.Enum(); e != nil && e.Values().Len() > 0 && e.Values().Get(0).Number() != 0 { - return errors.New("map enum value must have zero number for the first value") - } - return nil -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go deleted file mode 100644 index 002e0047ae..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protodesc - -import ( - "fmt" - "os" - "sync" - - "google.golang.org/protobuf/internal/editiondefaults" - "google.golang.org/protobuf/internal/filedesc" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/descriptorpb" - "google.golang.org/protobuf/types/gofeaturespb" -) - -var defaults = &descriptorpb.FeatureSetDefaults{} -var defaultsCacheMu sync.Mutex -var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet) - -func init() { - err := proto.Unmarshal(editiondefaults.Defaults, defaults) - if err != nil { - fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err) - os.Exit(1) - } -} - -func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition { - return filedesc.Edition(epb) -} - -func toEditionProto(ed filedesc.Edition) descriptorpb.Edition { - switch ed { - case filedesc.EditionUnknown: - return descriptorpb.Edition_EDITION_UNKNOWN - case filedesc.EditionProto2: - return descriptorpb.Edition_EDITION_PROTO2 - case filedesc.EditionProto3: - return descriptorpb.Edition_EDITION_PROTO3 - case filedesc.Edition2023: - return descriptorpb.Edition_EDITION_2023 - default: - panic(fmt.Sprintf("unknown value for edition: %v", ed)) - } -} - -func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet { - defaultsCacheMu.Lock() - defer defaultsCacheMu.Unlock() - if def, ok := defaultsCache[ed]; ok { - return def - } - edpb := toEditionProto(ed) - if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb { - // This should never happen protodesc.(FileOptions).New would fail when - // initializing the file descriptor. - // This most likely means the embedded defaults were not updated. - fmt.Fprintf(os.Stderr, "internal error: unsupported edition %v (did you forget to update the embedded defaults (i.e. the bootstrap descriptor proto)?)\n", edpb) - os.Exit(1) - } - fsed := defaults.GetDefaults()[0] - // Using a linear search for now. - // Editions are guaranteed to be sorted and thus we could use a binary search. - // Given that there are only a handful of editions (with one more per year) - // there is not much reason to use a binary search. - for _, def := range defaults.GetDefaults() { - if def.GetEdition() <= edpb { - fsed = def - } else { - break - } - } - fs := proto.Clone(fsed.GetFixedFeatures()).(*descriptorpb.FeatureSet) - proto.Merge(fs, fsed.GetOverridableFeatures()) - defaultsCache[ed] = fs - return fs -} - -// mergeEditionFeatures merges the parent and child feature sets. This function -// should be used when initializing Go descriptors from descriptor protos which -// is why the parent is a filedesc.EditionsFeatures (Go representation) while -// the child is a descriptorproto.FeatureSet (protoc representation). -// Any feature set by the child overwrites what is set by the parent. -func mergeEditionFeatures(parentDesc protoreflect.Descriptor, child *descriptorpb.FeatureSet) filedesc.EditionFeatures { - var parentFS filedesc.EditionFeatures - switch p := parentDesc.(type) { - case *filedesc.File: - parentFS = p.L1.EditionFeatures - case *filedesc.Message: - parentFS = p.L1.EditionFeatures - default: - panic(fmt.Sprintf("unknown parent type %T", parentDesc)) - } - if child == nil { - return parentFS - } - if fp := child.FieldPresence; fp != nil { - parentFS.IsFieldPresence = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED || - *fp == descriptorpb.FeatureSet_EXPLICIT - parentFS.IsLegacyRequired = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED - } - if et := child.EnumType; et != nil { - parentFS.IsOpenEnum = *et == descriptorpb.FeatureSet_OPEN - } - - if rfe := child.RepeatedFieldEncoding; rfe != nil { - parentFS.IsPacked = *rfe == descriptorpb.FeatureSet_PACKED - } - - if utf8val := child.Utf8Validation; utf8val != nil { - parentFS.IsUTF8Validated = *utf8val == descriptorpb.FeatureSet_VERIFY - } - - if me := child.MessageEncoding; me != nil { - parentFS.IsDelimitedEncoded = *me == descriptorpb.FeatureSet_DELIMITED - } - - if jf := child.JsonFormat; jf != nil { - parentFS.IsJSONCompliant = *jf == descriptorpb.FeatureSet_ALLOW - } - - if goFeatures, ok := proto.GetExtension(child, gofeaturespb.E_Go).(*gofeaturespb.GoFeatures); ok && goFeatures != nil { - if luje := goFeatures.LegacyUnmarshalJsonEnum; luje != nil { - parentFS.GenerateLegacyUnmarshalJSON = *luje - } - } - - return parentFS -} - -// initFileDescFromFeatureSet initializes editions related fields in fd based -// on fs. If fs is nil it is assumed to be an empty featureset and all fields -// will be initialized with the appropriate default. fd.L1.Edition must be set -// before calling this function. -func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) { - dfs := getFeatureSetFor(fd.L1.Edition) - // initialize the featureset with the defaults - fd.L1.EditionFeatures = mergeEditionFeatures(fd, dfs) - // overwrite any options explicitly specified - fd.L1.EditionFeatures = mergeEditionFeatures(fd, fs) -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/proto.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/proto.go deleted file mode 100644 index a5de8d4001..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protodesc/proto.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protodesc - -import ( - "fmt" - "strings" - - "google.golang.org/protobuf/internal/encoding/defval" - "google.golang.org/protobuf/internal/strs" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - - "google.golang.org/protobuf/types/descriptorpb" -) - -// ToFileDescriptorProto copies a [protoreflect.FileDescriptor] into a -// google.protobuf.FileDescriptorProto message. -func ToFileDescriptorProto(file protoreflect.FileDescriptor) *descriptorpb.FileDescriptorProto { - p := &descriptorpb.FileDescriptorProto{ - Name: proto.String(file.Path()), - Options: proto.Clone(file.Options()).(*descriptorpb.FileOptions), - } - if file.Package() != "" { - p.Package = proto.String(string(file.Package())) - } - for i, imports := 0, file.Imports(); i < imports.Len(); i++ { - imp := imports.Get(i) - p.Dependency = append(p.Dependency, imp.Path()) - if imp.IsPublic { - p.PublicDependency = append(p.PublicDependency, int32(i)) - } - if imp.IsWeak { - p.WeakDependency = append(p.WeakDependency, int32(i)) - } - } - for i, locs := 0, file.SourceLocations(); i < locs.Len(); i++ { - loc := locs.Get(i) - l := &descriptorpb.SourceCodeInfo_Location{} - l.Path = append(l.Path, loc.Path...) - if loc.StartLine == loc.EndLine { - l.Span = []int32{int32(loc.StartLine), int32(loc.StartColumn), int32(loc.EndColumn)} - } else { - l.Span = []int32{int32(loc.StartLine), int32(loc.StartColumn), int32(loc.EndLine), int32(loc.EndColumn)} - } - l.LeadingDetachedComments = append([]string(nil), loc.LeadingDetachedComments...) - if loc.LeadingComments != "" { - l.LeadingComments = proto.String(loc.LeadingComments) - } - if loc.TrailingComments != "" { - l.TrailingComments = proto.String(loc.TrailingComments) - } - if p.SourceCodeInfo == nil { - p.SourceCodeInfo = &descriptorpb.SourceCodeInfo{} - } - p.SourceCodeInfo.Location = append(p.SourceCodeInfo.Location, l) - - } - for i, messages := 0, file.Messages(); i < messages.Len(); i++ { - p.MessageType = append(p.MessageType, ToDescriptorProto(messages.Get(i))) - } - for i, enums := 0, file.Enums(); i < enums.Len(); i++ { - p.EnumType = append(p.EnumType, ToEnumDescriptorProto(enums.Get(i))) - } - for i, services := 0, file.Services(); i < services.Len(); i++ { - p.Service = append(p.Service, ToServiceDescriptorProto(services.Get(i))) - } - for i, exts := 0, file.Extensions(); i < exts.Len(); i++ { - p.Extension = append(p.Extension, ToFieldDescriptorProto(exts.Get(i))) - } - if syntax := file.Syntax(); syntax != protoreflect.Proto2 && syntax.IsValid() { - p.Syntax = proto.String(file.Syntax().String()) - } - if file.Syntax() == protoreflect.Editions { - desc := file - if fileImportDesc, ok := file.(protoreflect.FileImport); ok { - desc = fileImportDesc.FileDescriptor - } - - if editionsInterface, ok := desc.(interface{ Edition() int32 }); ok { - p.Edition = descriptorpb.Edition(editionsInterface.Edition()).Enum() - } - } - return p -} - -// ToDescriptorProto copies a [protoreflect.MessageDescriptor] into a -// google.protobuf.DescriptorProto message. -func ToDescriptorProto(message protoreflect.MessageDescriptor) *descriptorpb.DescriptorProto { - p := &descriptorpb.DescriptorProto{ - Name: proto.String(string(message.Name())), - Options: proto.Clone(message.Options()).(*descriptorpb.MessageOptions), - } - for i, fields := 0, message.Fields(); i < fields.Len(); i++ { - p.Field = append(p.Field, ToFieldDescriptorProto(fields.Get(i))) - } - for i, exts := 0, message.Extensions(); i < exts.Len(); i++ { - p.Extension = append(p.Extension, ToFieldDescriptorProto(exts.Get(i))) - } - for i, messages := 0, message.Messages(); i < messages.Len(); i++ { - p.NestedType = append(p.NestedType, ToDescriptorProto(messages.Get(i))) - } - for i, enums := 0, message.Enums(); i < enums.Len(); i++ { - p.EnumType = append(p.EnumType, ToEnumDescriptorProto(enums.Get(i))) - } - for i, xranges := 0, message.ExtensionRanges(); i < xranges.Len(); i++ { - xrange := xranges.Get(i) - p.ExtensionRange = append(p.ExtensionRange, &descriptorpb.DescriptorProto_ExtensionRange{ - Start: proto.Int32(int32(xrange[0])), - End: proto.Int32(int32(xrange[1])), - Options: proto.Clone(message.ExtensionRangeOptions(i)).(*descriptorpb.ExtensionRangeOptions), - }) - } - for i, oneofs := 0, message.Oneofs(); i < oneofs.Len(); i++ { - p.OneofDecl = append(p.OneofDecl, ToOneofDescriptorProto(oneofs.Get(i))) - } - for i, ranges := 0, message.ReservedRanges(); i < ranges.Len(); i++ { - rrange := ranges.Get(i) - p.ReservedRange = append(p.ReservedRange, &descriptorpb.DescriptorProto_ReservedRange{ - Start: proto.Int32(int32(rrange[0])), - End: proto.Int32(int32(rrange[1])), - }) - } - for i, names := 0, message.ReservedNames(); i < names.Len(); i++ { - p.ReservedName = append(p.ReservedName, string(names.Get(i))) - } - return p -} - -// ToFieldDescriptorProto copies a [protoreflect.FieldDescriptor] into a -// google.protobuf.FieldDescriptorProto message. -func ToFieldDescriptorProto(field protoreflect.FieldDescriptor) *descriptorpb.FieldDescriptorProto { - p := &descriptorpb.FieldDescriptorProto{ - Name: proto.String(string(field.Name())), - Number: proto.Int32(int32(field.Number())), - Label: descriptorpb.FieldDescriptorProto_Label(field.Cardinality()).Enum(), - Options: proto.Clone(field.Options()).(*descriptorpb.FieldOptions), - } - if field.IsExtension() { - p.Extendee = fullNameOf(field.ContainingMessage()) - } - if field.Kind().IsValid() { - p.Type = descriptorpb.FieldDescriptorProto_Type(field.Kind()).Enum() - } - if field.Enum() != nil { - p.TypeName = fullNameOf(field.Enum()) - } - if field.Message() != nil { - p.TypeName = fullNameOf(field.Message()) - } - if field.HasJSONName() { - // A bug in older versions of protoc would always populate the - // "json_name" option for extensions when it is meaningless. - // When it did so, it would always use the camel-cased field name. - if field.IsExtension() { - p.JsonName = proto.String(strs.JSONCamelCase(string(field.Name()))) - } else { - p.JsonName = proto.String(field.JSONName()) - } - } - if field.Syntax() == protoreflect.Proto3 && field.HasOptionalKeyword() { - p.Proto3Optional = proto.Bool(true) - } - if field.Syntax() == protoreflect.Editions { - // Editions have no group keyword, this type is only set so that downstream users continue - // treating this as delimited encoding. - if p.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP { - p.Type = descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum() - } - // Editions have no required keyword, this label is only set so that downstream users continue - // treating it as required. - if p.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED { - p.Label = descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum() - } - } - if field.HasDefault() { - def, err := defval.Marshal(field.Default(), field.DefaultEnumValue(), field.Kind(), defval.Descriptor) - if err != nil && field.DefaultEnumValue() != nil { - def = string(field.DefaultEnumValue().Name()) // occurs for unresolved enum values - } else if err != nil { - panic(fmt.Sprintf("%v: %v", field.FullName(), err)) - } - p.DefaultValue = proto.String(def) - } - if oneof := field.ContainingOneof(); oneof != nil { - p.OneofIndex = proto.Int32(int32(oneof.Index())) - } - return p -} - -// ToOneofDescriptorProto copies a [protoreflect.OneofDescriptor] into a -// google.protobuf.OneofDescriptorProto message. -func ToOneofDescriptorProto(oneof protoreflect.OneofDescriptor) *descriptorpb.OneofDescriptorProto { - return &descriptorpb.OneofDescriptorProto{ - Name: proto.String(string(oneof.Name())), - Options: proto.Clone(oneof.Options()).(*descriptorpb.OneofOptions), - } -} - -// ToEnumDescriptorProto copies a [protoreflect.EnumDescriptor] into a -// google.protobuf.EnumDescriptorProto message. -func ToEnumDescriptorProto(enum protoreflect.EnumDescriptor) *descriptorpb.EnumDescriptorProto { - p := &descriptorpb.EnumDescriptorProto{ - Name: proto.String(string(enum.Name())), - Options: proto.Clone(enum.Options()).(*descriptorpb.EnumOptions), - } - for i, values := 0, enum.Values(); i < values.Len(); i++ { - p.Value = append(p.Value, ToEnumValueDescriptorProto(values.Get(i))) - } - for i, ranges := 0, enum.ReservedRanges(); i < ranges.Len(); i++ { - rrange := ranges.Get(i) - p.ReservedRange = append(p.ReservedRange, &descriptorpb.EnumDescriptorProto_EnumReservedRange{ - Start: proto.Int32(int32(rrange[0])), - End: proto.Int32(int32(rrange[1])), - }) - } - for i, names := 0, enum.ReservedNames(); i < names.Len(); i++ { - p.ReservedName = append(p.ReservedName, string(names.Get(i))) - } - return p -} - -// ToEnumValueDescriptorProto copies a [protoreflect.EnumValueDescriptor] into a -// google.protobuf.EnumValueDescriptorProto message. -func ToEnumValueDescriptorProto(value protoreflect.EnumValueDescriptor) *descriptorpb.EnumValueDescriptorProto { - return &descriptorpb.EnumValueDescriptorProto{ - Name: proto.String(string(value.Name())), - Number: proto.Int32(int32(value.Number())), - Options: proto.Clone(value.Options()).(*descriptorpb.EnumValueOptions), - } -} - -// ToServiceDescriptorProto copies a [protoreflect.ServiceDescriptor] into a -// google.protobuf.ServiceDescriptorProto message. -func ToServiceDescriptorProto(service protoreflect.ServiceDescriptor) *descriptorpb.ServiceDescriptorProto { - p := &descriptorpb.ServiceDescriptorProto{ - Name: proto.String(string(service.Name())), - Options: proto.Clone(service.Options()).(*descriptorpb.ServiceOptions), - } - for i, methods := 0, service.Methods(); i < methods.Len(); i++ { - p.Method = append(p.Method, ToMethodDescriptorProto(methods.Get(i))) - } - return p -} - -// ToMethodDescriptorProto copies a [protoreflect.MethodDescriptor] into a -// google.protobuf.MethodDescriptorProto message. -func ToMethodDescriptorProto(method protoreflect.MethodDescriptor) *descriptorpb.MethodDescriptorProto { - p := &descriptorpb.MethodDescriptorProto{ - Name: proto.String(string(method.Name())), - InputType: fullNameOf(method.Input()), - OutputType: fullNameOf(method.Output()), - Options: proto.Clone(method.Options()).(*descriptorpb.MethodOptions), - } - if method.IsStreamingClient() { - p.ClientStreaming = proto.Bool(true) - } - if method.IsStreamingServer() { - p.ServerStreaming = proto.Bool(true) - } - return p -} - -func fullNameOf(d protoreflect.Descriptor) *string { - if d == nil { - return nil - } - if strings.HasPrefix(string(d.FullName()), unknownPrefix) { - return proto.String(string(d.FullName()[len(unknownPrefix):])) - } - return proto.String("." + string(d.FullName())) -} diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/type.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/type.go index cd8fadbaf8..cd7fbc87a4 100644 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/type.go +++ b/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/type.go @@ -68,7 +68,7 @@ type Descriptor interface { // dependency is not resolved, in which case only name information is known. // // Placeholder types may only be returned by the following accessors - // as a result of unresolved dependencies or weak imports: + // as a result of unresolved dependencies: // // ╔═══════════════════════════════════╤═════════════════════╗ // ā•‘ Accessor │ Descriptor ā•‘ @@ -168,11 +168,7 @@ type FileImport struct { // The current file and the imported file must be within proto package. IsPublic bool - // IsWeak reports whether this is a weak import, which does not impose - // a direct dependency on the target file. - // - // Weak imports are a legacy proto1 feature. Equivalent behavior is - // achieved using proto2 extension fields or proto3 Any messages. + // Deprecated: support for weak fields has been removed. IsWeak bool } @@ -325,9 +321,7 @@ type FieldDescriptor interface { // specified in the source .proto file. HasOptionalKeyword() bool - // IsWeak reports whether this is a weak field, which does not impose a - // direct dependency on the target type. - // If true, then Message returns a placeholder type. + // Deprecated: support for weak fields has been removed. IsWeak() bool // IsPacked reports whether repeated primitive numeric kinds should be diff --git a/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/value.go b/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/value.go index a7b0d06ff3..a4b78acef6 100644 --- a/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/value.go +++ b/go-controller/vendor/google.golang.org/protobuf/reflect/protoreflect/value.go @@ -152,7 +152,7 @@ type Message interface { // This method may return nil. // // The returned methods type is identical to - // google.golang.org/protobuf/runtime/protoiface.Methods. + // [google.golang.org/protobuf/runtime/protoiface.Methods]. // Consult the protoiface package documentation for details. ProtoMethods() *methods } diff --git a/go-controller/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go b/go-controller/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go index 246156561c..28e9e9f039 100644 --- a/go-controller/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go +++ b/go-controller/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go @@ -122,6 +122,22 @@ type UnmarshalInputFlags = uint8 const ( UnmarshalDiscardUnknown UnmarshalInputFlags = 1 << iota + + // UnmarshalAliasBuffer permits unmarshal operations to alias the input buffer. + // The unmarshaller must not modify the contents of the buffer. + UnmarshalAliasBuffer + + // UnmarshalValidated indicates that validation has already been + // performed on the input buffer. + UnmarshalValidated + + // UnmarshalCheckRequired is set if this unmarshal operation ultimately will care if required fields are + // initialized. + UnmarshalCheckRequired + + // UnmarshalNoLazyDecoding is set if this unmarshal operation should not use + // lazy decoding, even when otherwise available. + UnmarshalNoLazyDecoding ) // UnmarshalOutputFlags are output from the Unmarshal method. diff --git a/go-controller/vendor/google.golang.org/protobuf/runtime/protoimpl/impl.go b/go-controller/vendor/google.golang.org/protobuf/runtime/protoimpl/impl.go index 4a1ab7fb3d..93df1b569b 100644 --- a/go-controller/vendor/google.golang.org/protobuf/runtime/protoimpl/impl.go +++ b/go-controller/vendor/google.golang.org/protobuf/runtime/protoimpl/impl.go @@ -15,6 +15,7 @@ import ( "google.golang.org/protobuf/internal/filedesc" "google.golang.org/protobuf/internal/filetype" "google.golang.org/protobuf/internal/impl" + "google.golang.org/protobuf/internal/protolazy" ) // UnsafeEnabled specifies whether package unsafe can be used. @@ -39,6 +40,9 @@ type ( ExtensionFieldV1 = impl.ExtensionField Pointer = impl.Pointer + + LazyUnmarshalInfo = *protolazy.XXX_lazyUnmarshalInfo + RaceDetectHookData = impl.RaceDetectHookData ) var X impl.Export diff --git a/go-controller/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go b/go-controller/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go index 6dea75cd5b..a516337674 100644 --- a/go-controller/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go +++ b/go-controller/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go @@ -46,6 +46,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) // The full set of known editions. @@ -69,7 +70,7 @@ const ( Edition_EDITION_2023 Edition = 1000 Edition_EDITION_2024 Edition = 1001 // Placeholder editions for testing feature resolution. These should not be - // used or relyed on outside of tests. + // used or relied on outside of tests. Edition_EDITION_1_TEST_ONLY Edition = 1 Edition_EDITION_2_TEST_ONLY Edition = 2 Edition_EDITION_99997_TEST_ONLY Edition = 99997 @@ -577,8 +578,6 @@ func (FieldOptions_JSType) EnumDescriptor() ([]byte, []int) { } // If set to RETENTION_SOURCE, the option will be omitted from the binary. -// Note: as of January 2023, support for this is in progress and does not yet -// have an effect (b/264593489). type FieldOptions_OptionRetention int32 const ( @@ -640,8 +639,7 @@ func (FieldOptions_OptionRetention) EnumDescriptor() ([]byte, []int) { // This indicates the types of entities that the field may apply to when used // as an option. If it is unset, then the field may be freely used as an -// option on any kind of entity. Note: as of January 2023, support for this is -// in progress and does not yet have an effect (b/264593489). +// option on any kind of entity. type FieldOptions_OptionTargetType int32 const ( @@ -1208,11 +1206,11 @@ func (GeneratedCodeInfo_Annotation_Semantic) EnumDescriptor() ([]byte, []int) { // The protocol compiler can output a FileDescriptorSet containing the .proto // files it parses. type FileDescriptorSet struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - File []*FileDescriptorProto `protobuf:"bytes,1,rep,name=file" json:"file,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + File []*FileDescriptorProto `protobuf:"bytes,1,rep,name=file" json:"file,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FileDescriptorSet) Reset() { @@ -1254,12 +1252,9 @@ func (x *FileDescriptorSet) GetFile() []*FileDescriptorProto { // Describes a complete .proto file. type FileDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // file name, relative to root of source tree - Package *string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` // e.g. "foo", "foo.bar", etc. + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // file name, relative to root of source tree + Package *string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` // e.g. "foo", "foo.bar", etc. // Names of files imported by this file. Dependency []string `protobuf:"bytes,3,rep,name=dependency" json:"dependency,omitempty"` // Indexes of the public imported files in the dependency list above. @@ -1284,7 +1279,9 @@ type FileDescriptorProto struct { // If `edition` is present, this value must be "editions". Syntax *string `protobuf:"bytes,12,opt,name=syntax" json:"syntax,omitempty"` // The edition of the proto file. - Edition *Edition `protobuf:"varint,14,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` + Edition *Edition `protobuf:"varint,14,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FileDescriptorProto) Reset() { @@ -1410,10 +1407,7 @@ func (x *FileDescriptorProto) GetEdition() Edition { // Describes a message type. type DescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Field []*FieldDescriptorProto `protobuf:"bytes,2,rep,name=field" json:"field,omitempty"` Extension []*FieldDescriptorProto `protobuf:"bytes,6,rep,name=extension" json:"extension,omitempty"` @@ -1425,7 +1419,9 @@ type DescriptorProto struct { ReservedRange []*DescriptorProto_ReservedRange `protobuf:"bytes,9,rep,name=reserved_range,json=reservedRange" json:"reserved_range,omitempty"` // Reserved field names, which may not be used by fields in the same message. // A given name may only be reserved once. - ReservedName []string `protobuf:"bytes,10,rep,name=reserved_name,json=reservedName" json:"reserved_name,omitempty"` + ReservedName []string `protobuf:"bytes,10,rep,name=reserved_name,json=reservedName" json:"reserved_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DescriptorProto) Reset() { @@ -1529,11 +1525,7 @@ func (x *DescriptorProto) GetReservedName() []string { } type ExtensionRangeOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` // For external users: DO NOT USE. We are in the process of open sourcing @@ -1545,7 +1537,10 @@ type ExtensionRangeOptions struct { // The verification state of the range. // TODO: flip the default to DECLARATION once all empty ranges // are marked as UNVERIFIED. - Verification *ExtensionRangeOptions_VerificationState `protobuf:"varint,3,opt,name=verification,enum=google.protobuf.ExtensionRangeOptions_VerificationState,def=1" json:"verification,omitempty"` + Verification *ExtensionRangeOptions_VerificationState `protobuf:"varint,3,opt,name=verification,enum=google.protobuf.ExtensionRangeOptions_VerificationState,def=1" json:"verification,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for ExtensionRangeOptions fields. @@ -1613,10 +1608,7 @@ func (x *ExtensionRangeOptions) GetVerification() ExtensionRangeOptions_Verifica // Describes a field within a message. type FieldDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Number *int32 `protobuf:"varint,3,opt,name=number" json:"number,omitempty"` Label *FieldDescriptorProto_Label `protobuf:"varint,4,opt,name=label,enum=google.protobuf.FieldDescriptorProto_Label" json:"label,omitempty"` @@ -1668,6 +1660,8 @@ type FieldDescriptorProto struct { // Proto2 optional fields do not set this flag, because they already indicate // optional with `LABEL_OPTIONAL`. Proto3Optional *bool `protobuf:"varint,17,opt,name=proto3_optional,json=proto3Optional" json:"proto3_optional,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FieldDescriptorProto) Reset() { @@ -1779,12 +1773,11 @@ func (x *FieldDescriptorProto) GetProto3Optional() bool { // Describes a oneof. type OneofDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Options *OneofOptions `protobuf:"bytes,2,opt,name=options" json:"options,omitempty"` unknownFields protoimpl.UnknownFields - - Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Options *OneofOptions `protobuf:"bytes,2,opt,name=options" json:"options,omitempty"` + sizeCache protoimpl.SizeCache } func (x *OneofDescriptorProto) Reset() { @@ -1833,10 +1826,7 @@ func (x *OneofDescriptorProto) GetOptions() *OneofOptions { // Describes an enum type. type EnumDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Value []*EnumValueDescriptorProto `protobuf:"bytes,2,rep,name=value" json:"value,omitempty"` Options *EnumOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` @@ -1846,7 +1836,9 @@ type EnumDescriptorProto struct { ReservedRange []*EnumDescriptorProto_EnumReservedRange `protobuf:"bytes,4,rep,name=reserved_range,json=reservedRange" json:"reserved_range,omitempty"` // Reserved enum value names, which may not be reused. A given name may only // be reserved once. - ReservedName []string `protobuf:"bytes,5,rep,name=reserved_name,json=reservedName" json:"reserved_name,omitempty"` + ReservedName []string `protobuf:"bytes,5,rep,name=reserved_name,json=reservedName" json:"reserved_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *EnumDescriptorProto) Reset() { @@ -1916,13 +1908,12 @@ func (x *EnumDescriptorProto) GetReservedName() []string { // Describes a value within an enum. type EnumValueDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Number *int32 `protobuf:"varint,2,opt,name=number" json:"number,omitempty"` + Options *EnumValueOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` unknownFields protoimpl.UnknownFields - - Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Number *int32 `protobuf:"varint,2,opt,name=number" json:"number,omitempty"` - Options *EnumValueOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + sizeCache protoimpl.SizeCache } func (x *EnumValueDescriptorProto) Reset() { @@ -1978,13 +1969,12 @@ func (x *EnumValueDescriptorProto) GetOptions() *EnumValueOptions { // Describes a service. type ServiceDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Method []*MethodDescriptorProto `protobuf:"bytes,2,rep,name=method" json:"method,omitempty"` + Options *ServiceOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` unknownFields protoimpl.UnknownFields - - Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Method []*MethodDescriptorProto `protobuf:"bytes,2,rep,name=method" json:"method,omitempty"` - Options *ServiceOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ServiceDescriptorProto) Reset() { @@ -2040,11 +2030,8 @@ func (x *ServiceDescriptorProto) GetOptions() *ServiceOptions { // Describes a method of a service. type MethodDescriptorProto struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // Input and output type names. These are resolved in the same way as // FieldDescriptorProto.type_name, but must refer to a message type. InputType *string `protobuf:"bytes,2,opt,name=input_type,json=inputType" json:"input_type,omitempty"` @@ -2054,6 +2041,8 @@ type MethodDescriptorProto struct { ClientStreaming *bool `protobuf:"varint,5,opt,name=client_streaming,json=clientStreaming,def=0" json:"client_streaming,omitempty"` // Identifies if server streams multiple server messages ServerStreaming *bool `protobuf:"varint,6,opt,name=server_streaming,json=serverStreaming,def=0" json:"server_streaming,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for MethodDescriptorProto fields. @@ -2135,11 +2124,7 @@ func (x *MethodDescriptorProto) GetServerStreaming() bool { } type FileOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Sets the Java package where classes generated from this .proto will be // placed. By default, the proto package is used, but this is often // inappropriate because proto packages do not normally start with backwards @@ -2231,6 +2216,9 @@ type FileOptions struct { // The parser stores options it doesn't recognize here. // See the documentation for the "Options" section above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for FileOptions fields. @@ -2424,11 +2412,7 @@ func (x *FileOptions) GetUninterpretedOption() []*UninterpretedOption { } type MessageOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Set true to use the old proto1 MessageSet wire format for extensions. // This is provided for backwards-compatibility with the MessageSet wire // format. You should not use this for any other reason: It's less @@ -2501,6 +2485,9 @@ type MessageOptions struct { Features *FeatureSet `protobuf:"bytes,12,opt,name=features" json:"features,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for MessageOptions fields. @@ -2591,17 +2578,14 @@ func (x *MessageOptions) GetUninterpretedOption() []*UninterpretedOption { } type FieldOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` + // NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. // The ctype option instructs the C++ code generator to use a different // representation of the field than it normally would. See the specific // options below. This option is only implemented to support use of // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of - // type "bytes" in the open source release -- sorry, we'll try to include - // other types in a future version! + // type "bytes" in the open source release. + // TODO: make ctype actually deprecated. Ctype *FieldOptions_CType `protobuf:"varint,1,opt,name=ctype,enum=google.protobuf.FieldOptions_CType,def=0" json:"ctype,omitempty"` // The packed option can be enabled for repeated primitive fields to enable // a more efficient representation on the wire. Rather than repeatedly @@ -2668,6 +2652,9 @@ type FieldOptions struct { FeatureSupport *FieldOptions_FeatureSupport `protobuf:"bytes,22,opt,name=feature_support,json=featureSupport" json:"feature_support,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for FieldOptions fields. @@ -2810,15 +2797,14 @@ func (x *FieldOptions) GetUninterpretedOption() []*UninterpretedOption { } type OneofOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Any features defined in the specific edition. Features *FeatureSet `protobuf:"bytes,1,opt,name=features" json:"features,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OneofOptions) Reset() { @@ -2866,11 +2852,7 @@ func (x *OneofOptions) GetUninterpretedOption() []*UninterpretedOption { } type EnumOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Set this option to true to allow mapping different tag names to the same // value. AllowAlias *bool `protobuf:"varint,2,opt,name=allow_alias,json=allowAlias" json:"allow_alias,omitempty"` @@ -2892,6 +2874,9 @@ type EnumOptions struct { Features *FeatureSet `protobuf:"bytes,7,opt,name=features" json:"features,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for EnumOptions fields. @@ -2966,11 +2951,7 @@ func (x *EnumOptions) GetUninterpretedOption() []*UninterpretedOption { } type EnumValueOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Is this enum value deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the enum value, or it will be completely ignored; in the very least, @@ -2986,6 +2967,9 @@ type EnumValueOptions struct { FeatureSupport *FieldOptions_FeatureSupport `protobuf:"bytes,4,opt,name=feature_support,json=featureSupport" json:"feature_support,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for EnumValueOptions fields. @@ -3060,11 +3044,7 @@ func (x *EnumValueOptions) GetUninterpretedOption() []*UninterpretedOption { } type ServiceOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Any features defined in the specific edition. Features *FeatureSet `protobuf:"bytes,34,opt,name=features" json:"features,omitempty"` // Is this service deprecated? @@ -3074,6 +3054,9 @@ type ServiceOptions struct { Deprecated *bool `protobuf:"varint,33,opt,name=deprecated,def=0" json:"deprecated,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for ServiceOptions fields. @@ -3133,11 +3116,7 @@ func (x *ServiceOptions) GetUninterpretedOption() []*UninterpretedOption { } type MethodOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` // Is this method deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the method, or it will be completely ignored; in the very least, @@ -3148,6 +3127,9 @@ type MethodOptions struct { Features *FeatureSet `protobuf:"bytes,35,opt,name=features" json:"features,omitempty"` // The parser stores options it doesn't recognize here. See above. UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option,json=uninterpretedOption" json:"uninterpreted_option,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Default values for MethodOptions fields. @@ -3221,11 +3203,8 @@ func (x *MethodOptions) GetUninterpretedOption() []*UninterpretedOption { // or produced by Descriptor::CopyTo()) will never have UninterpretedOptions // in them. type UninterpretedOption struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name []*UninterpretedOption_NamePart `protobuf:"bytes,2,rep,name=name" json:"name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name []*UninterpretedOption_NamePart `protobuf:"bytes,2,rep,name=name" json:"name,omitempty"` // The value of the uninterpreted option, in whatever type the tokenizer // identified it as during parsing. Exactly one of these should be set. IdentifierValue *string `protobuf:"bytes,3,opt,name=identifier_value,json=identifierValue" json:"identifier_value,omitempty"` @@ -3234,6 +3213,8 @@ type UninterpretedOption struct { DoubleValue *float64 `protobuf:"fixed64,6,opt,name=double_value,json=doubleValue" json:"double_value,omitempty"` StringValue []byte `protobuf:"bytes,7,opt,name=string_value,json=stringValue" json:"string_value,omitempty"` AggregateValue *string `protobuf:"bytes,8,opt,name=aggregate_value,json=aggregateValue" json:"aggregate_value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UninterpretedOption) Reset() { @@ -3322,17 +3303,16 @@ func (x *UninterpretedOption) GetAggregateValue() string { // be designed and implemented to handle this, hopefully before we ever hit a // conflict here. type FeatureSet struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - extensionFields protoimpl.ExtensionFields - + state protoimpl.MessageState `protogen:"open.v1"` FieldPresence *FeatureSet_FieldPresence `protobuf:"varint,1,opt,name=field_presence,json=fieldPresence,enum=google.protobuf.FeatureSet_FieldPresence" json:"field_presence,omitempty"` EnumType *FeatureSet_EnumType `protobuf:"varint,2,opt,name=enum_type,json=enumType,enum=google.protobuf.FeatureSet_EnumType" json:"enum_type,omitempty"` RepeatedFieldEncoding *FeatureSet_RepeatedFieldEncoding `protobuf:"varint,3,opt,name=repeated_field_encoding,json=repeatedFieldEncoding,enum=google.protobuf.FeatureSet_RepeatedFieldEncoding" json:"repeated_field_encoding,omitempty"` Utf8Validation *FeatureSet_Utf8Validation `protobuf:"varint,4,opt,name=utf8_validation,json=utf8Validation,enum=google.protobuf.FeatureSet_Utf8Validation" json:"utf8_validation,omitempty"` MessageEncoding *FeatureSet_MessageEncoding `protobuf:"varint,5,opt,name=message_encoding,json=messageEncoding,enum=google.protobuf.FeatureSet_MessageEncoding" json:"message_encoding,omitempty"` JsonFormat *FeatureSet_JsonFormat `protobuf:"varint,6,opt,name=json_format,json=jsonFormat,enum=google.protobuf.FeatureSet_JsonFormat" json:"json_format,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeatureSet) Reset() { @@ -3412,10 +3392,7 @@ func (x *FeatureSet) GetJsonFormat() FeatureSet_JsonFormat { // feature resolution. The resolution with this object becomes a simple search // for the closest matching edition, followed by proto merges. type FeatureSetDefaults struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` Defaults []*FeatureSetDefaults_FeatureSetEditionDefault `protobuf:"bytes,1,rep,name=defaults" json:"defaults,omitempty"` // The minimum supported edition (inclusive) when this was constructed. // Editions before this will not have defaults. @@ -3423,6 +3400,8 @@ type FeatureSetDefaults struct { // The maximum known edition (inclusive) when this was constructed. Editions // after this will not have reliable defaults. MaximumEdition *Edition `protobuf:"varint,5,opt,name=maximum_edition,json=maximumEdition,enum=google.protobuf.Edition" json:"maximum_edition,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeatureSetDefaults) Reset() { @@ -3479,10 +3458,7 @@ func (x *FeatureSetDefaults) GetMaximumEdition() Edition { // Encapsulates information about the original source file from which a // FileDescriptorProto was generated. type SourceCodeInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A Location identifies a piece of source code in a .proto file which // corresponds to a particular definition. This information is intended // to be useful to IDEs, code indexers, documentation generators, and similar @@ -3531,7 +3507,10 @@ type SourceCodeInfo struct { // - Code which tries to interpret locations should probably be designed to // ignore those that it doesn't understand, as more types of locations could // be recorded in the future. - Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"` + Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SourceCodeInfo) Reset() { @@ -3575,13 +3554,12 @@ func (x *SourceCodeInfo) GetLocation() []*SourceCodeInfo_Location { // file. A GeneratedCodeInfo message is associated with only one generated // source file, but may contain references to different source .proto files. type GeneratedCodeInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // An Annotation connects some span of text in generated code to an element // of its generating .proto file. - Annotation []*GeneratedCodeInfo_Annotation `protobuf:"bytes,1,rep,name=annotation" json:"annotation,omitempty"` + Annotation []*GeneratedCodeInfo_Annotation `protobuf:"bytes,1,rep,name=annotation" json:"annotation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GeneratedCodeInfo) Reset() { @@ -3622,13 +3600,12 @@ func (x *GeneratedCodeInfo) GetAnnotation() []*GeneratedCodeInfo_Annotation { } type DescriptorProto_ExtensionRange struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. + End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Exclusive. + Options *ExtensionRangeOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` unknownFields protoimpl.UnknownFields - - Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. - End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Exclusive. - Options *ExtensionRangeOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DescriptorProto_ExtensionRange) Reset() { @@ -3686,12 +3663,11 @@ func (x *DescriptorProto_ExtensionRange) GetOptions() *ExtensionRangeOptions { // fields or extension ranges in the same message. Reserved ranges may // not overlap. type DescriptorProto_ReservedRange struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. + End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Exclusive. unknownFields protoimpl.UnknownFields - - Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. - End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Exclusive. + sizeCache protoimpl.SizeCache } func (x *DescriptorProto_ReservedRange) Reset() { @@ -3739,10 +3715,7 @@ func (x *DescriptorProto_ReservedRange) GetEnd() int32 { } type ExtensionRangeOptions_Declaration struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The extension number declared within the extension range. Number *int32 `protobuf:"varint,1,opt,name=number" json:"number,omitempty"` // The fully-qualified name of the extension field. There must be a leading @@ -3758,7 +3731,9 @@ type ExtensionRangeOptions_Declaration struct { Reserved *bool `protobuf:"varint,5,opt,name=reserved" json:"reserved,omitempty"` // If true, indicates that the extension must be defined as repeated. // Otherwise the extension must be defined as optional. - Repeated *bool `protobuf:"varint,6,opt,name=repeated" json:"repeated,omitempty"` + Repeated *bool `protobuf:"varint,6,opt,name=repeated" json:"repeated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ExtensionRangeOptions_Declaration) Reset() { @@ -3833,12 +3808,11 @@ func (x *ExtensionRangeOptions_Declaration) GetRepeated() bool { // is inclusive such that it can appropriately represent the entire int32 // domain. type EnumDescriptorProto_EnumReservedRange struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. + End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Inclusive. unknownFields protoimpl.UnknownFields - - Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` // Inclusive. - End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` // Inclusive. + sizeCache protoimpl.SizeCache } func (x *EnumDescriptorProto_EnumReservedRange) Reset() { @@ -3886,12 +3860,11 @@ func (x *EnumDescriptorProto_EnumReservedRange) GetEnd() int32 { } type FieldOptions_EditionDefault struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Edition *Edition `protobuf:"varint,3,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` + Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` // Textproto value. unknownFields protoimpl.UnknownFields - - Edition *Edition `protobuf:"varint,3,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` - Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` // Textproto value. + sizeCache protoimpl.SizeCache } func (x *FieldOptions_EditionDefault) Reset() { @@ -3940,10 +3913,7 @@ func (x *FieldOptions_EditionDefault) GetValue() string { // Information about the support window of a feature. type FieldOptions_FeatureSupport struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The edition that this feature was first available in. In editions // earlier than this one, the default assigned to EDITION_LEGACY will be // used, and proto files will not be able to override it. @@ -3958,6 +3928,8 @@ type FieldOptions_FeatureSupport struct { // this one, the last default assigned will be used, and proto files will // not be able to override it. EditionRemoved *Edition `protobuf:"varint,4,opt,name=edition_removed,json=editionRemoved,enum=google.protobuf.Edition" json:"edition_removed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FieldOptions_FeatureSupport) Reset() { @@ -4024,12 +3996,11 @@ func (x *FieldOptions_FeatureSupport) GetEditionRemoved() Edition { // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents // "foo.(bar.baz).moo". type UninterpretedOption_NamePart struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + NamePart *string `protobuf:"bytes,1,req,name=name_part,json=namePart" json:"name_part,omitempty"` + IsExtension *bool `protobuf:"varint,2,req,name=is_extension,json=isExtension" json:"is_extension,omitempty"` unknownFields protoimpl.UnknownFields - - NamePart *string `protobuf:"bytes,1,req,name=name_part,json=namePart" json:"name_part,omitempty"` - IsExtension *bool `protobuf:"varint,2,req,name=is_extension,json=isExtension" json:"is_extension,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UninterpretedOption_NamePart) Reset() { @@ -4081,15 +4052,14 @@ func (x *UninterpretedOption_NamePart) GetIsExtension() bool { // the defaults at the closest matching edition ordered at or before it should // be used. This field must be in strict ascending order by edition. type FeatureSetDefaults_FeatureSetEditionDefault struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Edition *Edition `protobuf:"varint,3,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Edition *Edition `protobuf:"varint,3,opt,name=edition,enum=google.protobuf.Edition" json:"edition,omitempty"` // Defaults of features that can be overridden in this edition. OverridableFeatures *FeatureSet `protobuf:"bytes,4,opt,name=overridable_features,json=overridableFeatures" json:"overridable_features,omitempty"` // Defaults of features that can't be overridden in this edition. FixedFeatures *FeatureSet `protobuf:"bytes,5,opt,name=fixed_features,json=fixedFeatures" json:"fixed_features,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeatureSetDefaults_FeatureSetEditionDefault) Reset() { @@ -4144,10 +4114,7 @@ func (x *FeatureSetDefaults_FeatureSetEditionDefault) GetFixedFeatures() *Featur } type SourceCodeInfo_Location struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Identifies which part of the FileDescriptorProto was defined at this // location. // @@ -4239,6 +4206,8 @@ type SourceCodeInfo_Location struct { LeadingComments *string `protobuf:"bytes,3,opt,name=leading_comments,json=leadingComments" json:"leading_comments,omitempty"` TrailingComments *string `protobuf:"bytes,4,opt,name=trailing_comments,json=trailingComments" json:"trailing_comments,omitempty"` LeadingDetachedComments []string `protobuf:"bytes,6,rep,name=leading_detached_comments,json=leadingDetachedComments" json:"leading_detached_comments,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SourceCodeInfo_Location) Reset() { @@ -4307,10 +4276,7 @@ func (x *SourceCodeInfo_Location) GetLeadingDetachedComments() []string { } type GeneratedCodeInfo_Annotation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Identifies the element in the original source .proto file. This field // is formatted the same as SourceCodeInfo.Location.path. Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"` @@ -4322,8 +4288,10 @@ type GeneratedCodeInfo_Annotation struct { // Identifies the ending offset in bytes in the generated code that // relates to the identified object. The end offset should be one past // the last relevant byte (so the length of the text = end - begin). - End *int32 `protobuf:"varint,4,opt,name=end" json:"end,omitempty"` - Semantic *GeneratedCodeInfo_Annotation_Semantic `protobuf:"varint,5,opt,name=semantic,enum=google.protobuf.GeneratedCodeInfo_Annotation_Semantic" json:"semantic,omitempty"` + End *int32 `protobuf:"varint,4,opt,name=end" json:"end,omitempty"` + Semantic *GeneratedCodeInfo_Annotation_Semantic `protobuf:"varint,5,opt,name=semantic,enum=google.protobuf.GeneratedCodeInfo_Annotation_Semantic" json:"semantic,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GeneratedCodeInfo_Annotation) Reset() { @@ -4393,498 +4361,478 @@ func (x *GeneratedCodeInfo_Annotation) GetSemantic() GeneratedCodeInfo_Annotatio var File_google_protobuf_descriptor_proto protoreflect.FileDescriptor -var file_google_protobuf_descriptor_proto_rawDesc = []byte{ +var file_google_protobuf_descriptor_proto_rawDesc = string([]byte{ 0x0a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x22, 0x4d, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x62, 0x75, 0x66, 0x22, 0x5b, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x04, 0x66, 0x69, - 0x6c, 0x65, 0x22, 0x98, 0x05, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65, - 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, - 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x05, 0x52, 0x10, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x44, 0x65, 0x70, 0x65, 0x6e, - 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x64, 0x65, - 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0e, - 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x43, - 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, - 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, 0x6e, - 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78, 0x74, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, - 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb9, 0x06, - 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0b, 0x6e, 0x65, 0x73, 0x74, 0x65, - 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0a, - 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, - 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, + 0x6c, 0x65, 0x2a, 0x0c, 0x08, 0x80, 0xec, 0xca, 0xff, 0x01, 0x10, 0x81, 0xec, 0xca, 0xff, 0x01, + 0x22, 0x98, 0x05, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x05, 0x52, 0x10, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0e, 0x77, 0x65, + 0x61, 0x6b, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x43, 0x0a, 0x0c, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x07, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x58, 0x0a, - 0x0f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, - 0x5f, 0x64, 0x65, 0x63, 0x6c, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, - 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x52, 0x09, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x63, 0x6c, 0x12, 0x39, 0x0a, - 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, - 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x7a, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x40, - 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x1a, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xcc, 0x04, 0x0a, 0x15, 0x45, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, - 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, - 0x0b, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, - 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x6c, 0x61, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x03, 0x88, 0x01, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x63, - 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x73, 0x12, 0x6d, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x3a, 0x0a, 0x55, 0x4e, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x42, 0x03, 0x88, - 0x01, 0x02, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x94, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, - 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x34, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x0a, 0x0b, - 0x44, 0x45, 0x43, 0x4c, 0x41, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, - 0x0a, 0x55, 0x4e, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x01, 0x2a, 0x09, 0x08, - 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xc1, 0x06, 0x0a, 0x14, 0x46, 0x69, 0x65, + 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb9, 0x06, 0x0a, 0x0f, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0b, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0a, 0x6e, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, + 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x64, + 0x65, 0x63, 0x6c, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, + 0x66, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x52, 0x09, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x63, 0x6c, 0x12, 0x39, 0x0a, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, + 0x6d, 0x65, 0x1a, 0x7a, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x07, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x37, + 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xcc, 0x04, 0x0a, 0x15, 0x45, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, + 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, + 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x0b, 0x64, + 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x32, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x03, 0x88, 0x01, 0x02, 0x52, 0x0b, 0x64, 0x65, 0x63, 0x6c, 0x61, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, + 0x6d, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x3a, + 0x0a, 0x55, 0x4e, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x42, 0x03, 0x88, 0x01, 0x02, + 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x94, + 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, + 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x34, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x45, + 0x43, 0x4c, 0x41, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x55, + 0x4e, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x01, 0x2a, 0x09, 0x08, 0xe8, 0x07, + 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xc1, 0x06, 0x0a, 0x14, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x41, 0x0a, - 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x67, + 0x6f, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3e, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, + 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x5f, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0xb6, 0x02, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, + 0x55, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, + 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, + 0x4e, 0x54, 0x36, 0x34, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0b, 0x12, 0x0e, 0x0a, + 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x0c, 0x12, 0x0f, 0x0a, + 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x0d, 0x12, 0x0d, + 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x10, 0x0e, 0x12, 0x11, 0x0a, + 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x0f, + 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, + 0x34, 0x10, 0x10, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, + 0x33, 0x32, 0x10, 0x11, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, + 0x54, 0x36, 0x34, 0x10, 0x12, 0x22, 0x43, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, + 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, 0x4c, + 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x50, 0x45, + 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, + 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x02, 0x22, 0x63, 0x0a, 0x14, 0x4f, 0x6e, + 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0xe3, 0x02, 0x0a, 0x13, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, + 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, 0x0a, 0x07, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x5f, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0xb6, - 0x02, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x07, 0x12, 0x0d, 0x0a, - 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x09, 0x12, 0x0e, 0x0a, - 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x0a, 0x12, 0x10, 0x0a, - 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0b, 0x12, - 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x0c, 0x12, - 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x0d, - 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x10, 0x0e, 0x12, - 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, - 0x10, 0x0f, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, - 0x44, 0x36, 0x34, 0x10, 0x10, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, - 0x4e, 0x54, 0x33, 0x32, 0x10, 0x11, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, - 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x12, 0x22, 0x43, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, - 0x41, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x52, 0x45, - 0x50, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, - 0x4c, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x02, 0x22, 0x63, 0x0a, 0x14, - 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, - 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x13, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x67, + 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5d, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, + 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, + 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x3b, 0x0a, 0x11, 0x45, 0x6e, 0x75, 0x6d, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x18, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5d, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x3b, 0x0a, 0x11, 0x45, 0x6e, - 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x18, 0x45, 0x6e, 0x75, 0x6d, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa7, 0x01, - 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x39, 0x0a, 0x07, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x16, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x39, 0x0a, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x89, 0x02, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, + 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, + 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0f, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, + 0x30, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, + 0x67, 0x22, 0xad, 0x09, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6a, 0x61, 0x76, 0x61, 0x50, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x6a, 0x61, 0x76, 0x61, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x61, + 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x6a, 0x61, 0x76, 0x61, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x44, 0x0a, + 0x1d, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x65, + 0x71, 0x75, 0x61, 0x6c, 0x73, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x19, 0x6a, 0x61, 0x76, 0x61, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x41, 0x6e, 0x64, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x3a, 0x0a, 0x16, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, 0x74, 0x66, 0x38, 0x18, 0x1b, 0x20, + 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x13, 0x6a, 0x61, 0x76, 0x61, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x74, 0x66, 0x38, 0x12, + 0x53, 0x0a, 0x0c, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, 0x64, 0x65, + 0x3a, 0x05, 0x53, 0x50, 0x45, 0x45, 0x44, 0x52, 0x0b, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, + 0x65, 0x46, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x50, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x63, 0x63, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, + 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, + 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x63, 0x63, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x6a, 0x61, + 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x52, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x13, 0x70, 0x79, 0x5f, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x70, 0x79, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0a, + 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, + 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x10, 0x63, 0x63, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x61, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x04, 0x74, + 0x72, 0x75, 0x65, 0x52, 0x0e, 0x63, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x72, 0x65, + 0x6e, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x62, 0x6a, 0x63, 0x5f, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6f, 0x62, 0x6a, 0x63, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, + 0x29, 0x0a, 0x10, 0x63, 0x73, 0x68, 0x61, 0x72, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x73, 0x68, 0x61, 0x72, + 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x77, + 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x27, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x77, 0x69, 0x66, 0x74, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x28, 0x0a, + 0x10, 0x70, 0x68, 0x70, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, + 0x78, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x68, 0x70, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x68, 0x70, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x29, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x68, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x16, + 0x70, 0x68, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x70, 0x68, + 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x62, 0x79, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x75, 0x62, 0x79, 0x50, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, + 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x89, 0x02, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x30, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, - 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, - 0x67, 0x12, 0x30, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, - 0x73, 0x65, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x69, 0x6e, 0x67, 0x22, 0xad, 0x09, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x70, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6a, 0x61, 0x76, 0x61, 0x50, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, 0x61, 0x76, 0x61, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, - 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x6a, 0x61, 0x76, 0x61, - 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x6a, 0x61, - 0x76, 0x61, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, - 0x44, 0x0a, 0x1d, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, - 0x5f, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x19, 0x6a, 0x61, 0x76, 0x61, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x41, 0x6e, - 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3a, 0x0a, 0x16, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, 0x74, 0x66, 0x38, 0x18, - 0x1b, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x13, 0x6a, 0x61, - 0x76, 0x61, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x74, 0x66, - 0x38, 0x12, 0x53, 0x0a, 0x0c, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x5f, 0x66, 0x6f, - 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, - 0x64, 0x65, 0x3a, 0x05, 0x53, 0x50, 0x45, 0x45, 0x44, 0x52, 0x0b, 0x6f, 0x70, 0x74, 0x69, 0x6d, - 0x69, 0x7a, 0x65, 0x46, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x70, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x50, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x63, 0x63, 0x5f, 0x67, 0x65, 0x6e, 0x65, - 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x63, 0x63, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x15, - 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, - 0x73, 0x65, 0x52, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x13, 0x70, 0x79, 0x5f, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x12, - 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x70, 0x79, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x25, - 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x17, 0x20, 0x01, - 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x10, 0x63, 0x63, 0x5f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x61, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x3a, - 0x04, 0x74, 0x72, 0x75, 0x65, 0x52, 0x0e, 0x63, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x41, - 0x72, 0x65, 0x6e, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x62, 0x6a, 0x63, 0x5f, 0x63, 0x6c, - 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x6f, 0x62, 0x6a, 0x63, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x73, 0x68, 0x61, 0x72, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x73, 0x68, - 0x61, 0x72, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x73, 0x77, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x27, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x73, 0x77, 0x69, 0x66, 0x74, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, - 0x28, 0x0a, 0x10, 0x70, 0x68, 0x70, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x68, 0x70, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x68, 0x70, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x29, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x70, 0x68, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x34, - 0x0a, 0x16, 0x70, 0x68, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, - 0x70, 0x68, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x62, 0x79, 0x5f, 0x70, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x75, 0x62, 0x79, - 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, - 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x0a, 0x0c, 0x4f, 0x70, - 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x50, - 0x45, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x49, - 0x5a, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x54, 0x45, 0x5f, 0x52, 0x55, 0x4e, - 0x54, 0x49, 0x4d, 0x45, 0x10, 0x03, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, - 0x02, 0x4a, 0x04, 0x08, 0x2a, 0x10, 0x2b, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, 0x52, 0x14, 0x70, - 0x68, 0x70, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x22, 0xf4, 0x03, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x14, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x57, 0x69, 0x72, 0x65, 0x46, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x12, 0x4c, 0x0a, 0x1f, 0x6e, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x6e, 0x64, - 0x61, 0x72, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, - 0x61, 0x6c, 0x73, 0x65, 0x52, 0x1c, 0x6e, 0x6f, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, - 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x70, - 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, - 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x56, 0x0a, 0x26, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, - 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x22, 0x64, 0x65, 0x70, 0x72, - 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x37, - 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x04, - 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, - 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x9d, 0x0d, 0x0a, 0x0c, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x63, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x43, 0x54, 0x79, 0x70, 0x65, 0x3a, - 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x52, 0x05, 0x63, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, - 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x09, 0x4a, 0x53, - 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x52, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x19, 0x0a, 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, - 0x61, 0x6c, 0x73, 0x65, 0x52, 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x12, 0x2e, 0x0a, 0x0f, 0x75, 0x6e, - 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0e, 0x75, 0x6e, 0x76, 0x65, - 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4c, 0x61, 0x7a, 0x79, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, - 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, - 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x12, 0x19, 0x0a, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, - 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x28, 0x0a, 0x0c, - 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, - 0x52, 0x65, 0x64, 0x61, 0x63, 0x74, 0x12, 0x4b, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x13, - 0x20, 0x03, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, + 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x0a, 0x0c, 0x4f, 0x70, 0x74, 0x69, + 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x50, 0x45, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x49, 0x5a, 0x45, + 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x54, 0x45, 0x5f, 0x52, 0x55, 0x4e, 0x54, 0x49, + 0x4d, 0x45, 0x10, 0x03, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, + 0x04, 0x08, 0x2a, 0x10, 0x2b, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, 0x52, 0x14, 0x70, 0x68, 0x70, + 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x22, 0xf4, 0x03, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x14, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x57, 0x69, 0x72, 0x65, 0x46, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x12, 0x4c, 0x0a, 0x1f, 0x6e, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, + 0x64, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x52, 0x1c, 0x6e, 0x6f, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, + 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, + 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x5f, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x56, 0x0a, 0x26, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x22, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, + 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, + 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x08, + 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x9d, 0x0d, 0x0a, 0x0c, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x63, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x43, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x06, 0x53, + 0x54, 0x52, 0x49, 0x4e, 0x47, 0x52, 0x05, 0x63, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x57, 0x0a, - 0x10, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, + 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x52, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, + 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x52, 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x12, 0x2e, 0x0a, 0x0f, 0x75, 0x6e, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0e, 0x75, 0x6e, 0x76, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x4c, 0x61, 0x7a, 0x79, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x19, 0x0a, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x52, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x28, 0x0a, 0x0c, 0x64, 0x65, + 0x62, 0x75, 0x67, 0x5f, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, + 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, + 0x64, 0x61, 0x63, 0x74, 0x12, 0x4b, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x0f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, - 0x55, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, - 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, - 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x5a, 0x0a, 0x0e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x65, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x96, 0x02, 0x0a, - 0x0e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x12, - 0x47, 0x0a, 0x12, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, - 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x12, 0x65, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, - 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, - 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x72, 0x6e, 0x69, - 0x6e, 0x67, 0x12, 0x41, 0x0a, 0x0f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x22, 0x2f, 0x0a, 0x05, 0x43, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, - 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, - 0x52, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x50, - 0x49, 0x45, 0x43, 0x45, 0x10, 0x02, 0x22, 0x35, 0x0a, 0x06, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, - 0x0d, 0x0a, 0x09, 0x4a, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x02, 0x22, 0x55, 0x0a, - 0x0f, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x54, 0x45, 0x4e, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x55, 0x4e, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, - 0x0a, 0x10, 0x52, 0x45, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x55, 0x52, - 0x43, 0x45, 0x10, 0x02, 0x22, 0x8c, 0x02, 0x0a, 0x10, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, - 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x52, 0x47, - 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, - 0x4e, 0x5f, 0x52, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, - 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, - 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x41, 0x52, - 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46, 0x10, 0x05, - 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x45, 0x4e, 0x55, 0x4d, 0x10, 0x06, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x45, 0x4e, 0x54, 0x52, 0x59, - 0x10, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x54, - 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, - 0x44, 0x10, 0x09, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, - 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x12, 0x10, 0x13, 0x22, 0xac, 0x01, 0x0a, 0x0c, 0x4f, - 0x6e, 0x65, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x48, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20, 0x03, + 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x57, 0x0a, 0x10, 0x65, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, + 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x52, 0x0f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x55, 0x0a, + 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, - 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xd1, 0x02, 0x0a, 0x0b, 0x45, 0x6e, - 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, - 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, - 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x12, 0x56, 0x0a, 0x26, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x22, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x5a, + 0x0a, 0x0e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x65, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x96, 0x02, 0x0a, 0x0e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x47, 0x0a, + 0x12, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x12, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x65, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, + 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, + 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, + 0x12, 0x41, 0x0a, 0x0f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x22, 0x2f, 0x0a, 0x05, 0x43, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, + 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x52, 0x44, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x49, 0x45, + 0x43, 0x45, 0x10, 0x02, 0x22, 0x35, 0x0a, 0x06, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, + 0x0a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0d, 0x0a, + 0x09, 0x4a, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, + 0x4a, 0x53, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x02, 0x22, 0x55, 0x0a, 0x0f, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, + 0x0a, 0x11, 0x52, 0x45, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x54, 0x45, 0x4e, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x52, 0x55, 0x4e, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, + 0x52, 0x45, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, + 0x10, 0x02, 0x22, 0x8c, 0x02, 0x0a, 0x10, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, 0x47, 0x45, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x46, 0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x5f, + 0x52, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, 0x47, 0x45, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x03, + 0x12, 0x15, 0x0a, 0x11, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x46, 0x49, 0x45, 0x4c, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x41, 0x52, 0x47, 0x45, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46, 0x10, 0x05, 0x12, 0x14, + 0x0a, 0x10, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, + 0x55, 0x4d, 0x10, 0x06, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x45, 0x4e, 0x54, 0x52, 0x59, 0x10, 0x07, + 0x12, 0x17, 0x0a, 0x13, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x41, 0x52, + 0x47, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, + 0x09, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x04, + 0x10, 0x05, 0x4a, 0x04, 0x08, 0x12, 0x10, 0x13, 0x22, 0xac, 0x01, 0x0a, 0x0c, 0x4f, 0x6e, 0x65, + 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, @@ -4893,284 +4841,306 @@ var file_google_protobuf_descriptor_proto_rawDesc = []byte{ 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, - 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xd8, 0x02, - 0x0a, 0x10, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, - 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, + 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xd1, 0x02, 0x0a, 0x0b, 0x45, 0x6e, 0x75, 0x6d, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x56, 0x0a, 0x26, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x22, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4c, + 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, + 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, + 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, + 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xd8, 0x02, 0x0a, 0x10, + 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, + 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x28, 0x0a, 0x0c, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0b, 0x64, + 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, 0x64, 0x61, 0x63, 0x74, 0x12, 0x55, 0x0a, 0x0f, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, + 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, + 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, + 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xd5, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0c, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x72, 0x65, 0x64, 0x61, - 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, - 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, 0x52, 0x65, 0x64, 0x61, 0x63, 0x74, 0x12, 0x55, 0x0a, 0x0f, - 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, + 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, + 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x99, + 0x03, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x21, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, + 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x71, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, + 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x22, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x3a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x43, 0x59, + 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, - 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0xd5, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, + 0x10, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x43, 0x59, + 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4e, 0x4f, + 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x53, 0x10, 0x01, 0x12, + 0x0e, 0x0a, 0x0a, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x2a, + 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x9a, 0x03, 0x0a, 0x13, 0x55, + 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, + 0x0a, 0x12, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x67, 0x67, + 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x4a, 0x0a, 0x08, 0x4e, + 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x5f, + 0x70, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, + 0x50, 0x61, 0x72, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x02, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x45, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa7, 0x0a, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x91, 0x01, 0x0a, 0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x42, 0x3f, 0x88, 0x01, 0x01, 0x98, + 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, + 0x49, 0x54, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x43, + 0x49, 0x54, 0x18, 0xe7, 0x07, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, + 0x49, 0x54, 0x18, 0xe8, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0d, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x6c, 0x0a, 0x09, 0x65, 0x6e, + 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, - 0x65, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, - 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75, - 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, - 0x22, 0x99, 0x03, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, - 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x71, 0x0a, 0x11, 0x69, 0x64, 0x65, - 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x22, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, - 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x10, 0x69, 0x64, 0x65, 0x6d, - 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x37, 0x0a, 0x08, - 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x50, 0x0a, 0x10, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, - 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, - 0x4e, 0x4f, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x53, 0x10, - 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x54, 0x10, - 0x02, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x9a, 0x03, 0x0a, - 0x13, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, - 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, - 0x74, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, - 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, - 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, - 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x4a, 0x0a, - 0x08, 0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x61, - 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x65, 0x78, 0x74, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x02, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, - 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa7, 0x0a, 0x0a, 0x0a, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x91, 0x01, 0x0a, 0x0e, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x42, 0x3f, 0x88, 0x01, - 0x01, 0x98, 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, 0x58, 0x50, 0x4c, - 0x49, 0x43, 0x49, 0x54, 0x18, 0xe6, 0x07, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x49, 0x4d, 0x50, 0x4c, - 0x49, 0x43, 0x49, 0x54, 0x18, 0xe7, 0x07, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, 0x58, 0x50, 0x4c, - 0x49, 0x43, 0x49, 0x54, 0x18, 0xe8, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0d, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x6c, 0x0a, 0x09, - 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x75, - 0x6d, 0x54, 0x79, 0x70, 0x65, 0x42, 0x29, 0x88, 0x01, 0x01, 0x98, 0x01, 0x06, 0x98, 0x01, 0x01, - 0xa2, 0x01, 0x0b, 0x12, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x18, 0xe6, 0x07, 0xa2, 0x01, - 0x09, 0x12, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, - 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x17, 0x72, - 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x65, 0x6e, - 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x42, - 0x2d, 0x88, 0x01, 0x01, 0x98, 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, - 0x58, 0x50, 0x41, 0x4e, 0x44, 0x45, 0x44, 0x18, 0xe6, 0x07, 0xa2, 0x01, 0x0b, 0x12, 0x06, 0x50, - 0x41, 0x43, 0x4b, 0x45, 0x44, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x15, - 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x63, - 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x7e, 0x0a, 0x0f, 0x75, 0x74, 0x66, 0x38, 0x5f, 0x76, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x55, 0x74, 0x66, 0x38, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x88, 0x01, 0x01, 0x98, - 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x09, 0x12, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x18, 0xe6, - 0x07, 0xa2, 0x01, 0x0b, 0x12, 0x06, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x18, 0xe7, 0x07, 0xb2, - 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0e, 0x75, 0x74, 0x66, 0x38, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x7e, 0x0a, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x2b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x26, 0x88, 0x01, - 0x01, 0x98, 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x14, 0x12, 0x0f, 0x4c, 0x45, 0x4e, 0x47, - 0x54, 0x48, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x45, 0x44, 0x18, 0xe6, 0x07, 0xb2, 0x01, - 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x63, - 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x82, 0x01, 0x0a, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x66, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x42, 0x39, 0x88, 0x01, 0x01, 0x98, 0x01, 0x03, 0x98, 0x01, 0x06, 0x98, 0x01, - 0x01, 0xa2, 0x01, 0x17, 0x12, 0x12, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x42, 0x45, 0x53, - 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, 0x54, 0x18, 0xe6, 0x07, 0xa2, 0x01, 0x0a, 0x12, 0x05, - 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0a, - 0x6a, 0x73, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x5c, 0x0a, 0x0d, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x46, - 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, - 0x43, 0x49, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x43, 0x49, - 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x52, 0x45, - 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, 0x22, 0x37, 0x0a, 0x08, 0x45, 0x6e, 0x75, 0x6d, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4f, - 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x10, - 0x02, 0x22, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, - 0x50, 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x45, 0x4e, 0x43, - 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, - 0x58, 0x50, 0x41, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x02, 0x22, 0x49, 0x0a, 0x0e, 0x55, 0x74, 0x66, - 0x38, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x17, 0x55, - 0x54, 0x46, 0x38, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x45, 0x52, 0x49, - 0x46, 0x59, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x03, 0x22, 0x04, - 0x08, 0x01, 0x10, 0x01, 0x22, 0x53, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, - 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x53, 0x53, 0x41, - 0x47, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48, 0x5f, - 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x45, - 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x10, 0x02, 0x22, 0x48, 0x0a, 0x0a, 0x4a, 0x73, 0x6f, - 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x13, 0x4a, 0x53, 0x4f, 0x4e, 0x5f, - 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x4c, - 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x42, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, - 0x54, 0x10, 0x02, 0x2a, 0x06, 0x08, 0xe8, 0x07, 0x10, 0x8b, 0x4e, 0x2a, 0x06, 0x08, 0x8b, 0x4e, - 0x10, 0x90, 0x4e, 0x2a, 0x06, 0x08, 0x90, 0x4e, 0x10, 0x91, 0x4e, 0x4a, 0x06, 0x08, 0xe7, 0x07, - 0x10, 0xe8, 0x07, 0x22, 0xef, 0x03, 0x0a, 0x12, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, - 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x08, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x67, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x54, + 0x79, 0x70, 0x65, 0x42, 0x29, 0x88, 0x01, 0x01, 0x98, 0x01, 0x06, 0x98, 0x01, 0x01, 0xa2, 0x01, + 0x0b, 0x12, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x09, 0x12, + 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x08, + 0x65, 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x17, 0x72, 0x65, 0x70, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x63, 0x6f, + 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x2d, 0x88, + 0x01, 0x01, 0x98, 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x0d, 0x12, 0x08, 0x45, 0x58, 0x50, + 0x41, 0x4e, 0x44, 0x45, 0x44, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x0b, 0x12, 0x06, 0x50, 0x41, 0x43, + 0x4b, 0x45, 0x44, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x15, 0x72, 0x65, + 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x63, 0x6f, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x7e, 0x0a, 0x0f, 0x75, 0x74, 0x66, 0x38, 0x5f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x73, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x45, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, - 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x55, 0x74, 0x66, 0x38, 0x56, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x88, 0x01, 0x01, 0x98, 0x01, 0x04, + 0x98, 0x01, 0x01, 0xa2, 0x01, 0x09, 0x12, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x18, 0x84, 0x07, 0xa2, + 0x01, 0x0b, 0x12, 0x06, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, + 0x08, 0xe8, 0x07, 0x52, 0x0e, 0x75, 0x74, 0x66, 0x38, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x7e, 0x0a, 0x10, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x65, + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, - 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x69, 0x6d, - 0x75, 0x6d, 0x5f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x69, - 0x6d, 0x75, 0x6d, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xf8, 0x01, 0x0a, 0x18, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x14, 0x6f, - 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x26, 0x88, 0x01, 0x01, 0x98, + 0x01, 0x04, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x14, 0x12, 0x0f, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48, + 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x45, 0x44, 0x18, 0x84, 0x07, 0xb2, 0x01, 0x03, 0x08, + 0xe8, 0x07, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x82, 0x01, 0x0a, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x13, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x61, - 0x62, 0x6c, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x66, - 0x69, 0x78, 0x65, 0x64, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, - 0x52, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x4a, - 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0xa7, 0x02, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xce, - 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x6c, - 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x10, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, - 0x65, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, - 0x65, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, - 0xd0, 0x02, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, - 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xeb, 0x01, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x65, 0x67, - 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x03, 0x65, 0x6e, 0x64, 0x12, 0x52, 0x0a, 0x08, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x52, 0x08, - 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x22, 0x28, 0x0a, 0x08, 0x53, 0x65, 0x6d, 0x61, - 0x6e, 0x74, 0x69, 0x63, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x07, - 0x0a, 0x03, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x4c, 0x49, 0x41, 0x53, - 0x10, 0x02, 0x2a, 0xa7, 0x02, 0x0a, 0x07, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, - 0x0a, 0x0f, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0e, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, - 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x84, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x45, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x32, 0x10, 0xe6, 0x07, 0x12, 0x13, 0x0a, - 0x0e, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x33, 0x10, - 0xe7, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x32, 0x30, - 0x32, 0x33, 0x10, 0xe8, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x32, 0x30, 0x32, 0x34, 0x10, 0xe9, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x31, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, - 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x32, 0x5f, 0x54, - 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, - 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x39, 0x39, 0x39, 0x39, 0x37, 0x5f, 0x54, 0x45, 0x53, 0x54, - 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x9d, 0x8d, 0x06, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, 0x49, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x39, 0x39, 0x39, 0x39, 0x38, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, - 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x9e, 0x8d, 0x06, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x39, 0x39, 0x39, 0x39, 0x39, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, - 0x4e, 0x4c, 0x59, 0x10, 0x9f, 0x8d, 0x06, 0x12, 0x13, 0x0a, 0x0b, 0x45, 0x44, 0x49, 0x54, 0x49, - 0x4f, 0x4e, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0xff, 0xff, 0xff, 0xff, 0x07, 0x42, 0x7e, 0x0a, 0x13, - 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x42, 0x10, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x48, 0x01, 0x5a, 0x2d, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03, 0x47, 0x50, 0x42, 0xaa, - 0x02, 0x1a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, -} + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x42, 0x39, 0x88, 0x01, 0x01, 0x98, 0x01, 0x03, 0x98, 0x01, 0x06, 0x98, 0x01, 0x01, 0xa2, + 0x01, 0x17, 0x12, 0x12, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x42, 0x45, 0x53, 0x54, 0x5f, + 0x45, 0x46, 0x46, 0x4f, 0x52, 0x54, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x0a, 0x12, 0x05, 0x41, 0x4c, + 0x4c, 0x4f, 0x57, 0x18, 0xe7, 0x07, 0xb2, 0x01, 0x03, 0x08, 0xe8, 0x07, 0x52, 0x0a, 0x6a, 0x73, + 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x5c, 0x0a, 0x0d, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49, 0x45, + 0x4c, 0x44, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49, + 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x43, 0x49, 0x54, 0x10, + 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, + 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, 0x22, 0x37, 0x0a, 0x08, 0x45, 0x6e, 0x75, 0x6d, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, + 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x10, 0x02, 0x22, + 0x56, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x50, 0x45, + 0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, + 0x49, 0x4e, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, + 0x06, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, + 0x41, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x02, 0x22, 0x49, 0x0a, 0x0e, 0x55, 0x74, 0x66, 0x38, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x54, 0x46, + 0x38, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, + 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x03, 0x22, 0x04, 0x08, 0x01, + 0x10, 0x01, 0x22, 0x53, 0x0a, 0x0f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x63, + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, + 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48, 0x5f, 0x50, 0x52, + 0x45, 0x46, 0x49, 0x58, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x45, 0x4c, 0x49, + 0x4d, 0x49, 0x54, 0x45, 0x44, 0x10, 0x02, 0x22, 0x48, 0x0a, 0x0a, 0x4a, 0x73, 0x6f, 0x6e, 0x46, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x13, 0x4a, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x4f, + 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x45, 0x47, + 0x41, 0x43, 0x59, 0x5f, 0x42, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, 0x54, 0x10, + 0x02, 0x2a, 0x06, 0x08, 0xe8, 0x07, 0x10, 0x8b, 0x4e, 0x2a, 0x06, 0x08, 0x8b, 0x4e, 0x10, 0x90, + 0x4e, 0x2a, 0x06, 0x08, 0x90, 0x4e, 0x10, 0x91, 0x4e, 0x4a, 0x06, 0x08, 0xe7, 0x07, 0x10, 0xe8, + 0x07, 0x22, 0xef, 0x03, 0x0a, 0x12, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x58, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x65, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x45, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, + 0x5f, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, + 0x6d, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xf8, 0x01, 0x0a, 0x18, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x14, 0x6f, 0x76, 0x65, + 0x72, 0x72, 0x69, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x52, 0x13, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x61, 0x62, 0x6c, + 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x69, 0x78, + 0x65, 0x64, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0d, + 0x66, 0x69, 0x78, 0x65, 0x64, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x22, 0xb5, 0x02, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xce, 0x01, 0x0a, + 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x12, 0x16, 0x0a, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x42, + 0x02, 0x10, 0x01, 0x52, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x6c, 0x65, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x74, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x74, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x0c, 0x08, + 0x80, 0xec, 0xca, 0xff, 0x01, 0x10, 0x81, 0xec, 0xca, 0xff, 0x01, 0x22, 0xd0, 0x02, 0x0a, 0x11, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x4d, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x1a, 0xeb, 0x01, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, + 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x67, 0x69, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x10, + 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, + 0x12, 0x52, 0x0a, 0x08, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x52, 0x08, 0x73, 0x65, 0x6d, 0x61, + 0x6e, 0x74, 0x69, 0x63, 0x22, 0x28, 0x0a, 0x08, 0x53, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, + 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x45, + 0x54, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x4c, 0x49, 0x41, 0x53, 0x10, 0x02, 0x2a, 0xa7, + 0x02, 0x0a, 0x07, 0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x13, 0x0a, 0x0e, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, + 0x59, 0x10, 0x84, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x32, 0x10, 0xe6, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x45, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x33, 0x10, 0xe7, 0x07, 0x12, 0x11, + 0x0a, 0x0c, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x32, 0x30, 0x32, 0x33, 0x10, 0xe8, + 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x32, 0x30, 0x32, + 0x34, 0x10, 0xe9, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x31, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x12, 0x17, 0x0a, + 0x13, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x32, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, + 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x39, 0x39, 0x39, 0x39, 0x37, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, + 0x59, 0x10, 0x9d, 0x8d, 0x06, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x39, 0x39, 0x39, 0x39, 0x38, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, + 0x10, 0x9e, 0x8d, 0x06, 0x12, 0x1d, 0x0a, 0x17, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, + 0x9f, 0x8d, 0x06, 0x12, 0x13, 0x0a, 0x0b, 0x45, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, + 0x41, 0x58, 0x10, 0xff, 0xff, 0xff, 0xff, 0x07, 0x42, 0x7e, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, + 0x10, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x48, 0x01, 0x5a, 0x2d, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, + 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x70, 0x62, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03, 0x47, 0x50, 0x42, 0xaa, 0x02, 0x1a, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65, + 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, +}) var ( file_google_protobuf_descriptor_proto_rawDescOnce sync.Once - file_google_protobuf_descriptor_proto_rawDescData = file_google_protobuf_descriptor_proto_rawDesc + file_google_protobuf_descriptor_proto_rawDescData []byte ) func file_google_protobuf_descriptor_proto_rawDescGZIP() []byte { file_google_protobuf_descriptor_proto_rawDescOnce.Do(func() { - file_google_protobuf_descriptor_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_protobuf_descriptor_proto_rawDescData) + file_google_protobuf_descriptor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_google_protobuf_descriptor_proto_rawDesc), len(file_google_protobuf_descriptor_proto_rawDesc))) }) return file_google_protobuf_descriptor_proto_rawDescData } @@ -5323,7 +5293,7 @@ func file_google_protobuf_descriptor_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_google_protobuf_descriptor_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_google_protobuf_descriptor_proto_rawDesc), len(file_google_protobuf_descriptor_proto_rawDesc)), NumEnums: 17, NumMessages: 33, NumExtensions: 0, @@ -5335,7 +5305,6 @@ func file_google_protobuf_descriptor_proto_init() { MessageInfos: file_google_protobuf_descriptor_proto_msgTypes, }.Build() File_google_protobuf_descriptor_proto = out.File - file_google_protobuf_descriptor_proto_rawDesc = nil file_google_protobuf_descriptor_proto_goTypes = nil file_google_protobuf_descriptor_proto_depIdxs = nil } diff --git a/go-controller/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go b/go-controller/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go deleted file mode 100644 index c7e860fcd6..0000000000 --- a/go-controller/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go +++ /dev/null @@ -1,165 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2023 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: google/protobuf/go_features.proto - -package gofeaturespb - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - descriptorpb "google.golang.org/protobuf/types/descriptorpb" - reflect "reflect" - sync "sync" -) - -type GoFeatures struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Whether or not to generate the deprecated UnmarshalJSON method for enums. - LegacyUnmarshalJsonEnum *bool `protobuf:"varint,1,opt,name=legacy_unmarshal_json_enum,json=legacyUnmarshalJsonEnum" json:"legacy_unmarshal_json_enum,omitempty"` -} - -func (x *GoFeatures) Reset() { - *x = GoFeatures{} - mi := &file_google_protobuf_go_features_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GoFeatures) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GoFeatures) ProtoMessage() {} - -func (x *GoFeatures) ProtoReflect() protoreflect.Message { - mi := &file_google_protobuf_go_features_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GoFeatures.ProtoReflect.Descriptor instead. -func (*GoFeatures) Descriptor() ([]byte, []int) { - return file_google_protobuf_go_features_proto_rawDescGZIP(), []int{0} -} - -func (x *GoFeatures) GetLegacyUnmarshalJsonEnum() bool { - if x != nil && x.LegacyUnmarshalJsonEnum != nil { - return *x.LegacyUnmarshalJsonEnum - } - return false -} - -var file_google_protobuf_go_features_proto_extTypes = []protoimpl.ExtensionInfo{ - { - ExtendedType: (*descriptorpb.FeatureSet)(nil), - ExtensionType: (*GoFeatures)(nil), - Field: 1002, - Name: "pb.go", - Tag: "bytes,1002,opt,name=go", - Filename: "google/protobuf/go_features.proto", - }, -} - -// Extension fields to descriptorpb.FeatureSet. -var ( - // optional pb.GoFeatures go = 1002; - E_Go = &file_google_protobuf_go_features_proto_extTypes[0] -) - -var File_google_protobuf_go_features_proto protoreflect.FileDescriptor - -var file_google_protobuf_go_features_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2f, 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcd, 0x01, 0x0a, 0x0a, 0x47, 0x6f, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0xbe, 0x01, 0x0a, 0x1a, 0x6c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x5f, 0x6a, 0x73, - 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x80, 0x01, - 0x88, 0x01, 0x01, 0x98, 0x01, 0x06, 0x98, 0x01, 0x01, 0xa2, 0x01, 0x09, 0x12, 0x04, 0x74, 0x72, - 0x75, 0x65, 0x18, 0x84, 0x07, 0xa2, 0x01, 0x0a, 0x12, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x18, - 0xe7, 0x07, 0xb2, 0x01, 0x5b, 0x08, 0xe8, 0x07, 0x10, 0xe8, 0x07, 0x1a, 0x53, 0x54, 0x68, 0x65, - 0x20, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x20, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, - 0x6c, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x41, 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x70, - 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x6c, 0x6c, - 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, - 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x20, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x52, 0x17, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, - 0x6c, 0x4a, 0x73, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x3a, 0x3c, 0x0a, 0x02, 0x67, 0x6f, 0x12, - 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x18, 0xea, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x6f, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x52, 0x02, 0x67, 0x6f, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x67, 0x6f, 0x66, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x62, -} - -var ( - file_google_protobuf_go_features_proto_rawDescOnce sync.Once - file_google_protobuf_go_features_proto_rawDescData = file_google_protobuf_go_features_proto_rawDesc -) - -func file_google_protobuf_go_features_proto_rawDescGZIP() []byte { - file_google_protobuf_go_features_proto_rawDescOnce.Do(func() { - file_google_protobuf_go_features_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_protobuf_go_features_proto_rawDescData) - }) - return file_google_protobuf_go_features_proto_rawDescData -} - -var file_google_protobuf_go_features_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_google_protobuf_go_features_proto_goTypes = []any{ - (*GoFeatures)(nil), // 0: pb.GoFeatures - (*descriptorpb.FeatureSet)(nil), // 1: google.protobuf.FeatureSet -} -var file_google_protobuf_go_features_proto_depIdxs = []int32{ - 1, // 0: pb.go:extendee -> google.protobuf.FeatureSet - 0, // 1: pb.go:type_name -> pb.GoFeatures - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 1, // [1:2] is the sub-list for extension type_name - 0, // [0:1] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_google_protobuf_go_features_proto_init() } -func file_google_protobuf_go_features_proto_init() { - if File_google_protobuf_go_features_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_google_protobuf_go_features_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 1, - NumServices: 0, - }, - GoTypes: file_google_protobuf_go_features_proto_goTypes, - DependencyIndexes: file_google_protobuf_go_features_proto_depIdxs, - MessageInfos: file_google_protobuf_go_features_proto_msgTypes, - ExtensionInfos: file_google_protobuf_go_features_proto_extTypes, - }.Build() - File_google_protobuf_go_features_proto = out.File - file_google_protobuf_go_features_proto_rawDesc = nil - file_google_protobuf_go_features_proto_goTypes = nil - file_google_protobuf_go_features_proto_depIdxs = nil -} diff --git a/go-controller/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go b/go-controller/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go index 87da199a38..497da66e91 100644 --- a/go-controller/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go +++ b/go-controller/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go @@ -122,6 +122,7 @@ import ( reflect "reflect" strings "strings" sync "sync" + unsafe "unsafe" ) // `Any` contains an arbitrary serialized protocol buffer message along with a @@ -210,10 +211,7 @@ import ( // "value": "1.212s" // } type Any struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A URL/resource name that uniquely identifies the type of the serialized // protocol buffer message. This string must contain at least // one "/" character. The last segment of the URL's path must represent @@ -244,7 +242,9 @@ type Any struct { // used with implementation specific semantics. TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"` // Must be a valid serialized protocol buffer of the above specified type. - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // New marshals src into a new Any instance. @@ -412,7 +412,7 @@ func (x *Any) GetValue() []byte { var File_google_protobuf_any_proto protoreflect.FileDescriptor -var file_google_protobuf_any_proto_rawDesc = []byte{ +var file_google_protobuf_any_proto_rawDesc = string([]byte{ 0x0a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x36, 0x0a, 0x03, @@ -428,16 +428,16 @@ var file_google_protobuf_any_proto_rawDesc = []byte{ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_google_protobuf_any_proto_rawDescOnce sync.Once - file_google_protobuf_any_proto_rawDescData = file_google_protobuf_any_proto_rawDesc + file_google_protobuf_any_proto_rawDescData []byte ) func file_google_protobuf_any_proto_rawDescGZIP() []byte { file_google_protobuf_any_proto_rawDescOnce.Do(func() { - file_google_protobuf_any_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_protobuf_any_proto_rawDescData) + file_google_protobuf_any_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_google_protobuf_any_proto_rawDesc), len(file_google_protobuf_any_proto_rawDesc))) }) return file_google_protobuf_any_proto_rawDescData } @@ -463,7 +463,7 @@ func file_google_protobuf_any_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_google_protobuf_any_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_google_protobuf_any_proto_rawDesc), len(file_google_protobuf_any_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -474,7 +474,6 @@ func file_google_protobuf_any_proto_init() { MessageInfos: file_google_protobuf_any_proto_msgTypes, }.Build() File_google_protobuf_any_proto = out.File - file_google_protobuf_any_proto_rawDesc = nil file_google_protobuf_any_proto_goTypes = nil file_google_protobuf_any_proto_depIdxs = nil } diff --git a/go-controller/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go b/go-controller/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go index b99d4d2410..193880d181 100644 --- a/go-controller/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go +++ b/go-controller/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go @@ -80,6 +80,7 @@ import ( reflect "reflect" sync "sync" time "time" + unsafe "unsafe" ) // A Duration represents a signed, fixed-length span of time represented @@ -141,10 +142,7 @@ import ( // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 // microsecond should be expressed in JSON format as "3.000001s". type Duration struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Signed seconds of the span of time. Must be from -315,576,000,000 // to +315,576,000,000 inclusive. Note: these bounds are computed from: // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years @@ -155,7 +153,9 @@ type Duration struct { // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. - Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // New constructs a new Duration from the provided time.Duration. @@ -289,7 +289,7 @@ func (x *Duration) GetNanos() int32 { var File_google_protobuf_duration_proto protoreflect.FileDescriptor -var file_google_protobuf_duration_proto_rawDesc = []byte{ +var file_google_protobuf_duration_proto_rawDesc = string([]byte{ 0x0a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, @@ -306,16 +306,16 @@ var file_google_protobuf_duration_proto_rawDesc = []byte{ 0x50, 0x42, 0xaa, 0x02, 0x1e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_google_protobuf_duration_proto_rawDescOnce sync.Once - file_google_protobuf_duration_proto_rawDescData = file_google_protobuf_duration_proto_rawDesc + file_google_protobuf_duration_proto_rawDescData []byte ) func file_google_protobuf_duration_proto_rawDescGZIP() []byte { file_google_protobuf_duration_proto_rawDescOnce.Do(func() { - file_google_protobuf_duration_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_protobuf_duration_proto_rawDescData) + file_google_protobuf_duration_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_google_protobuf_duration_proto_rawDesc), len(file_google_protobuf_duration_proto_rawDesc))) }) return file_google_protobuf_duration_proto_rawDescData } @@ -341,7 +341,7 @@ func file_google_protobuf_duration_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_google_protobuf_duration_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_google_protobuf_duration_proto_rawDesc), len(file_google_protobuf_duration_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -352,7 +352,6 @@ func file_google_protobuf_duration_proto_init() { MessageInfos: file_google_protobuf_duration_proto_msgTypes, }.Build() File_google_protobuf_duration_proto = out.File - file_google_protobuf_duration_proto_rawDesc = nil file_google_protobuf_duration_proto_goTypes = nil file_google_protobuf_duration_proto_depIdxs = nil } diff --git a/go-controller/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go b/go-controller/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go index 0d20722d70..00ac835c0b 100644 --- a/go-controller/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go +++ b/go-controller/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go @@ -78,6 +78,7 @@ import ( reflect "reflect" sync "sync" time "time" + unsafe "unsafe" ) // A Timestamp represents a point in time independent of any time zone or local @@ -170,10 +171,7 @@ import ( // http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() // ) to obtain a formatter capable of generating timestamps in this format. type Timestamp struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. @@ -182,7 +180,9 @@ type Timestamp struct { // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. - Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } // Now constructs a new Timestamp from the current time. @@ -298,7 +298,7 @@ func (x *Timestamp) GetNanos() int32 { var File_google_protobuf_timestamp_proto protoreflect.FileDescriptor -var file_google_protobuf_timestamp_proto_rawDesc = []byte{ +var file_google_protobuf_timestamp_proto_rawDesc = string([]byte{ 0x0a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, @@ -315,16 +315,16 @@ var file_google_protobuf_timestamp_proto_rawDesc = []byte{ 0xa2, 0x02, 0x03, 0x47, 0x50, 0x42, 0xaa, 0x02, 0x1e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_google_protobuf_timestamp_proto_rawDescOnce sync.Once - file_google_protobuf_timestamp_proto_rawDescData = file_google_protobuf_timestamp_proto_rawDesc + file_google_protobuf_timestamp_proto_rawDescData []byte ) func file_google_protobuf_timestamp_proto_rawDescGZIP() []byte { file_google_protobuf_timestamp_proto_rawDescOnce.Do(func() { - file_google_protobuf_timestamp_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_protobuf_timestamp_proto_rawDescData) + file_google_protobuf_timestamp_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_google_protobuf_timestamp_proto_rawDesc), len(file_google_protobuf_timestamp_proto_rawDesc))) }) return file_google_protobuf_timestamp_proto_rawDescData } @@ -350,7 +350,7 @@ func file_google_protobuf_timestamp_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_google_protobuf_timestamp_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_google_protobuf_timestamp_proto_rawDesc), len(file_google_protobuf_timestamp_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -361,7 +361,6 @@ func file_google_protobuf_timestamp_proto_init() { MessageInfos: file_google_protobuf_timestamp_proto_msgTypes, }.Build() File_google_protobuf_timestamp_proto = out.File - file_google_protobuf_timestamp_proto_rawDesc = nil file_google_protobuf_timestamp_proto_goTypes = nil file_google_protobuf_timestamp_proto_depIdxs = nil } diff --git a/go-controller/vendor/k8s.io/api/admission/v1/doc.go b/go-controller/vendor/k8s.io/api/admission/v1/doc.go index e7df9f629c..cab6528214 100644 --- a/go-controller/vendor/k8s.io/api/admission/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/admission/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=admission.k8s.io -package v1 // import "k8s.io/api/admission/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/admission/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/admission/v1beta1/doc.go index a5669022a0..447495684e 100644 --- a/go-controller/vendor/k8s.io/api/admission/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/admission/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=admission.k8s.io -package v1beta1 // import "k8s.io/api/admission/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1/doc.go b/go-controller/vendor/k8s.io/api/admissionregistration/v1/doc.go index ca0086188a..ec0ebb9c49 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1/doc.go @@ -24,4 +24,4 @@ limitations under the License. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration // MutatingWebhookConfiguration and ValidatingWebhookConfiguration are for the // new dynamic admission controller configuration. -package v1 // import "k8s.io/api/admissionregistration/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/doc.go index 98066211d8..344af9ae09 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=admissionregistration.k8s.io // Package v1alpha1 is the v1alpha1 version of the API. -package v1alpha1 // import "k8s.io/api/admissionregistration/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/generated.proto b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/generated.proto index 88344ce87a..d23f21cc84 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/generated.proto +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/generated.proto @@ -272,9 +272,9 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 1; - // ObjectSelector decides whether to run the validation based on if the + // ObjectSelector decides whether to run the policy based on if the // object has matching labels. objectSelector is evaluated against both - // the oldObject and newObject that would be sent to the cel validation, and + // the oldObject and newObject that would be sent to the policy's expression (CEL), and // is considered to match if either object matches the selector. A null // object (oldObject in the case of create, or newObject in the case of // delete) or an object that cannot have labels (like a @@ -286,13 +286,13 @@ message MatchResources { // +optional optional .k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 2; - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // ResourceRules describes what operations on what resources/subresources the admission policy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional repeated NamedRuleWithOperations resourceRules = 3; - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -304,12 +304,13 @@ message MatchResources { // - Exact: match a request only if it exactly matches a specified rule. // For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, // but "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`, - // a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy. + // the admission policy does not consider requests to apps/v1beta1 or extensions/v1beta1 API groups. // // - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. // For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, // and "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`, - // a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy. + // the admission policy **does** consider requests made to apps/v1beta1 or extensions/v1beta1 + // API groups. The API server translates the request to a matched resource API if necessary. // // Defaults to "Equivalent" // +optional diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types.go b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types.go index ee50fbe2d4..f183498a55 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types.go +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types.go @@ -56,9 +56,9 @@ const ( type FailurePolicyType string const ( - // Ignore means that an error calling the webhook is ignored. + // Ignore means that an error calling the admission webhook or admission policy is ignored. Ignore FailurePolicyType = "Ignore" - // Fail means that an error calling the webhook causes the admission to fail. + // Fail means that an error calling the admission webhook or admission policy causes resource admission to fail. Fail FailurePolicyType = "Fail" ) @@ -67,9 +67,11 @@ const ( type MatchPolicyType string const ( - // Exact means requests should only be sent to the webhook if they exactly match a given rule. + // Exact means requests should only be sent to the admission webhook or admission policy if they exactly match a given rule. Exact MatchPolicyType = "Exact" - // Equivalent means requests should be sent to the webhook if they modify a resource listed in rules via another API group or version. + // Equivalent means requests should be sent to the admission webhook or admission policy if they modify a resource listed + // in rules via an equivalent API group or version. For example, `autoscaling/v1` and `autoscaling/v2` + // HorizontalPodAutoscalers are equivalent: the same set of resources appear via both APIs. Equivalent MatchPolicyType = "Equivalent" ) @@ -577,9 +579,9 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,1,opt,name=namespaceSelector"` - // ObjectSelector decides whether to run the validation based on if the + // ObjectSelector decides whether to run the policy based on if the // object has matching labels. objectSelector is evaluated against both - // the oldObject and newObject that would be sent to the cel validation, and + // the oldObject and newObject that would be sent to the policy's expression (CEL), and // is considered to match if either object matches the selector. A null // object (oldObject in the case of create, or newObject in the case of // delete) or an object that cannot have labels (like a @@ -590,12 +592,12 @@ type MatchResources struct { // Default to the empty LabelSelector, which matches everything. // +optional ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,2,opt,name=objectSelector"` - // ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. + // ResourceRules describes what operations on what resources/subresources the admission policy matches. // The policy cares about an operation if it matches _any_ Rule. // +listType=atomic // +optional ResourceRules []NamedRuleWithOperations `json:"resourceRules,omitempty" protobuf:"bytes,3,rep,name=resourceRules"` - // ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. + // ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. // The exclude rules take precedence over include rules (if a resource matches both, it is excluded) // +listType=atomic // +optional @@ -606,12 +608,13 @@ type MatchResources struct { // - Exact: match a request only if it exactly matches a specified rule. // For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, // but "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`, - // a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy. + // the admission policy does not consider requests to apps/v1beta1 or extensions/v1beta1 API groups. // // - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. // For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, // and "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`, - // a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy. + // the admission policy **does** consider requests made to apps/v1beta1 or extensions/v1beta1 + // API groups. The API server translates the request to a matched resource API if necessary. // // Defaults to "Equivalent" // +optional diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go index 32222a81b8..116e56e065 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1alpha1/types_swagger_doc_generated.go @@ -68,10 +68,10 @@ func (JSONPatch) SwaggerDoc() map[string]string { var map_MatchResources = map[string]string{ "": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", "namespaceSelector": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "objectSelector": "ObjectSelector decides whether to run the validation based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the cel validation, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", - "resourceRules": "ResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy matches. The policy cares about an operation if it matches _any_ Rule.", - "excludeResourceRules": "ExcludeResourceRules describes what operations on what resources/subresources the ValidatingAdmissionPolicy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", - "matchPolicy": "matchPolicy defines how the \"MatchResources\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the ValidatingAdmissionPolicy.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the ValidatingAdmissionPolicy.\n\nDefaults to \"Equivalent\"", + "objectSelector": "ObjectSelector decides whether to run the policy based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the policy's expression (CEL), and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "resourceRules": "ResourceRules describes what operations on what resources/subresources the admission policy matches. The policy cares about an operation if it matches _any_ Rule.", + "excludeResourceRules": "ExcludeResourceRules describes what operations on what resources/subresources the policy should not care about. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)", + "matchPolicy": "matchPolicy defines how the \"MatchResources\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, the admission policy does not consider requests to apps/v1beta1 or extensions/v1beta1 API groups.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, the admission policy **does** consider requests made to apps/v1beta1 or extensions/v1beta1 API groups. The API server translates the request to a matched resource API if necessary.\n\nDefaults to \"Equivalent\"", } func (MatchResources) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/admissionregistration/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/admissionregistration/v1beta1/doc.go index 0095cb257a..40d8315738 100644 --- a/go-controller/vendor/k8s.io/api/admissionregistration/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/admissionregistration/v1beta1/doc.go @@ -24,4 +24,4 @@ limitations under the License. // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration // MutatingWebhookConfiguration and ValidatingWebhookConfiguration are for the // new dynamic admission controller configuration. -package v1beta1 // import "k8s.io/api/admissionregistration/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/apidiscovery/v2/doc.go b/go-controller/vendor/k8s.io/api/apidiscovery/v2/doc.go index 4f3ad5f139..f46d33e942 100644 --- a/go-controller/vendor/k8s.io/api/apidiscovery/v2/doc.go +++ b/go-controller/vendor/k8s.io/api/apidiscovery/v2/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=apidiscovery.k8s.io -package v2 // import "k8s.io/api/apidiscovery/v2" +package v2 diff --git a/go-controller/vendor/k8s.io/api/apidiscovery/v2beta1/doc.go b/go-controller/vendor/k8s.io/api/apidiscovery/v2beta1/doc.go index e85da226e0..d4fceab68d 100644 --- a/go-controller/vendor/k8s.io/api/apidiscovery/v2beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/apidiscovery/v2beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=apidiscovery.k8s.io -package v2beta1 // import "k8s.io/api/apidiscovery/v2beta1" +package v2beta1 diff --git a/go-controller/vendor/k8s.io/api/apiserverinternal/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/apiserverinternal/v1alpha1/doc.go index a4da95d44d..867d741651 100644 --- a/go-controller/vendor/k8s.io/api/apiserverinternal/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/apiserverinternal/v1alpha1/doc.go @@ -22,4 +22,4 @@ limitations under the License. // Package v1alpha1 contains the v1alpha1 version of the API used by the // apiservers themselves. -package v1alpha1 // import "k8s.io/api/apiserverinternal/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/apps/v1/doc.go b/go-controller/vendor/k8s.io/api/apps/v1/doc.go index d189e860f2..51fe12c53d 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/apps/v1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1 // import "k8s.io/api/apps/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/apps/v1/generated.pb.go b/go-controller/vendor/k8s.io/api/apps/v1/generated.pb.go index ea62a099fe..eacc25931b 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/apps/v1/generated.pb.go @@ -928,145 +928,147 @@ func init() { } var fileDescriptor_5b781835628d5338 = []byte{ - // 2194 bytes of a gzipped FileDescriptorProto + // 2225 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0xcd, 0x6f, 0x1b, 0xc7, - 0x15, 0xd7, 0xf2, 0x43, 0xa2, 0x86, 0x96, 0x64, 0x8f, 0x54, 0x89, 0xb1, 0x1b, 0xd2, 0xdd, 0xb8, - 0xb6, 0x12, 0xc7, 0x64, 0xed, 0x38, 0x41, 0xe0, 0x14, 0x09, 0x44, 0x2a, 0x4d, 0xd3, 0xe8, 0xab, - 0x43, 0xcb, 0x01, 0xdc, 0xb4, 0xe8, 0x68, 0x39, 0xa6, 0x36, 0xde, 0x2f, 0xec, 0x0e, 0x15, 0x0b, - 0xbd, 0x14, 0x05, 0x7a, 0xeb, 0xa1, 0x7f, 0x43, 0xff, 0x81, 0xa2, 0x28, 0x9a, 0x5b, 0x10, 0x04, - 0xbd, 0xf8, 0x52, 0x20, 0xe8, 0xa5, 0x39, 0x11, 0x35, 0x73, 0x2a, 0x8a, 0xde, 0xda, 0x8b, 0x2f, - 0x2d, 0x66, 0x76, 0xf6, 0x7b, 0x56, 0xa4, 0xe4, 0x58, 0x69, 0x82, 0xdc, 0xb8, 0x33, 0xbf, 0xf7, - 0xdb, 0x37, 0x33, 0xef, 0xcd, 0xfb, 0xcd, 0x2c, 0x81, 0x7a, 0xff, 0x55, 0xaf, 0xa9, 0xdb, 0x2d, - 0xec, 0xe8, 0x2d, 0xec, 0x38, 0x5e, 0xeb, 0xe0, 0x7a, 0xab, 0x4f, 0x2c, 0xe2, 0x62, 0x4a, 0x7a, - 0x4d, 0xc7, 0xb5, 0xa9, 0x0d, 0xa1, 0x8f, 0x69, 0x62, 0x47, 0x6f, 0x32, 0x4c, 0xf3, 0xe0, 0xfa, - 0xf9, 0x6b, 0x7d, 0x9d, 0xee, 0x0f, 0xf6, 0x9a, 0x9a, 0x6d, 0xb6, 0xfa, 0x76, 0xdf, 0x6e, 0x71, - 0xe8, 0xde, 0xe0, 0x1e, 0x7f, 0xe2, 0x0f, 0xfc, 0x97, 0x4f, 0x71, 0x3e, 0xfe, 0x1a, 0xcd, 0x76, - 0x89, 0xe4, 0x35, 0xe7, 0x6f, 0x46, 0x18, 0x13, 0x6b, 0xfb, 0xba, 0x45, 0xdc, 0xc3, 0x96, 0x73, - 0xbf, 0xcf, 0x1a, 0xbc, 0x96, 0x49, 0x28, 0x96, 0x59, 0xb5, 0xf2, 0xac, 0xdc, 0x81, 0x45, 0x75, - 0x93, 0x64, 0x0c, 0x5e, 0x19, 0x67, 0xe0, 0x69, 0xfb, 0xc4, 0xc4, 0x19, 0xbb, 0x97, 0xf2, 0xec, - 0x06, 0x54, 0x37, 0x5a, 0xba, 0x45, 0x3d, 0xea, 0xa6, 0x8d, 0xd4, 0xff, 0x28, 0x00, 0x76, 0x6c, - 0x8b, 0xba, 0xb6, 0x61, 0x10, 0x17, 0x91, 0x03, 0xdd, 0xd3, 0x6d, 0x0b, 0xfe, 0x1c, 0x54, 0xd8, - 0x78, 0x7a, 0x98, 0xe2, 0x9a, 0x72, 0x51, 0x59, 0xad, 0xde, 0xf8, 0x5e, 0x33, 0x9a, 0xe4, 0x90, - 0xbe, 0xe9, 0xdc, 0xef, 0xb3, 0x06, 0xaf, 0xc9, 0xd0, 0xcd, 0x83, 0xeb, 0xcd, 0xed, 0xbd, 0xf7, - 0x89, 0x46, 0x37, 0x09, 0xc5, 0x6d, 0xf8, 0x70, 0xd8, 0x98, 0x1a, 0x0d, 0x1b, 0x20, 0x6a, 0x43, - 0x21, 0x2b, 0xdc, 0x06, 0x25, 0xce, 0x5e, 0xe0, 0xec, 0xd7, 0x72, 0xd9, 0xc5, 0xa0, 0x9b, 0x08, - 0x7f, 0xf0, 0xe6, 0x03, 0x4a, 0x2c, 0xe6, 0x5e, 0xfb, 0x8c, 0xa0, 0x2e, 0xad, 0x63, 0x8a, 0x11, - 0x27, 0x82, 0x2f, 0x82, 0x8a, 0x2b, 0xdc, 0xaf, 0x15, 0x2f, 0x2a, 0xab, 0xc5, 0xf6, 0x59, 0x81, - 0xaa, 0x04, 0xc3, 0x42, 0x21, 0x42, 0xfd, 0xb3, 0x02, 0x96, 0xb3, 0xe3, 0xde, 0xd0, 0x3d, 0x0a, - 0xdf, 0xcb, 0x8c, 0xbd, 0x39, 0xd9, 0xd8, 0x99, 0x35, 0x1f, 0x79, 0xf8, 0xe2, 0xa0, 0x25, 0x36, - 0xee, 0x77, 0x40, 0x59, 0xa7, 0xc4, 0xf4, 0x6a, 0x85, 0x8b, 0xc5, 0xd5, 0xea, 0x8d, 0xcb, 0xcd, - 0x6c, 0xec, 0x36, 0xb3, 0x8e, 0xb5, 0xe7, 0x04, 0x65, 0xf9, 0x6d, 0x66, 0x8c, 0x7c, 0x0e, 0xf5, - 0xbf, 0x0a, 0x98, 0x5d, 0xc7, 0xc4, 0xb4, 0xad, 0x2e, 0xa1, 0xa7, 0xb0, 0x68, 0x1d, 0x50, 0xf2, - 0x1c, 0xa2, 0x89, 0x45, 0xfb, 0x8e, 0xcc, 0xf7, 0xd0, 0x9d, 0xae, 0x43, 0xb4, 0x68, 0xa1, 0xd8, - 0x13, 0xe2, 0xc6, 0xf0, 0x1d, 0x30, 0xed, 0x51, 0x4c, 0x07, 0x1e, 0x5f, 0xa6, 0xea, 0x8d, 0xe7, - 0x8e, 0xa6, 0xe1, 0xd0, 0xf6, 0xbc, 0x20, 0x9a, 0xf6, 0x9f, 0x91, 0xa0, 0x50, 0xff, 0x51, 0x00, - 0x30, 0xc4, 0x76, 0x6c, 0xab, 0xa7, 0x53, 0x16, 0xbf, 0xb7, 0x40, 0x89, 0x1e, 0x3a, 0x84, 0x4f, - 0xc3, 0x6c, 0xfb, 0x72, 0xe0, 0xc5, 0xed, 0x43, 0x87, 0x3c, 0x1e, 0x36, 0x96, 0xb3, 0x16, 0xac, - 0x07, 0x71, 0x1b, 0xb8, 0x11, 0xfa, 0x57, 0xe0, 0xd6, 0x37, 0x93, 0xaf, 0x7e, 0x3c, 0x6c, 0x48, - 0x36, 0x8b, 0x66, 0xc8, 0x94, 0x74, 0x10, 0x1e, 0x00, 0x68, 0x60, 0x8f, 0xde, 0x76, 0xb1, 0xe5, - 0xf9, 0x6f, 0xd2, 0x4d, 0x22, 0x46, 0xfe, 0xc2, 0x64, 0xcb, 0xc3, 0x2c, 0xda, 0xe7, 0x85, 0x17, - 0x70, 0x23, 0xc3, 0x86, 0x24, 0x6f, 0x80, 0x97, 0xc1, 0xb4, 0x4b, 0xb0, 0x67, 0x5b, 0xb5, 0x12, - 0x1f, 0x45, 0x38, 0x81, 0x88, 0xb7, 0x22, 0xd1, 0x0b, 0x9f, 0x07, 0x33, 0x26, 0xf1, 0x3c, 0xdc, - 0x27, 0xb5, 0x32, 0x07, 0x2e, 0x08, 0xe0, 0xcc, 0xa6, 0xdf, 0x8c, 0x82, 0x7e, 0xf5, 0x0f, 0x0a, - 0x98, 0x0b, 0x67, 0xee, 0x14, 0x52, 0xa5, 0x9d, 0x4c, 0x95, 0x67, 0x8f, 0x8c, 0x93, 0x9c, 0x0c, - 0xf9, 0xb8, 0x18, 0xf3, 0x99, 0x05, 0x21, 0xfc, 0x29, 0xa8, 0x78, 0xc4, 0x20, 0x1a, 0xb5, 0x5d, - 0xe1, 0xf3, 0x4b, 0x13, 0xfa, 0x8c, 0xf7, 0x88, 0xd1, 0x15, 0xa6, 0xed, 0x33, 0xcc, 0xe9, 0xe0, - 0x09, 0x85, 0x94, 0xf0, 0xc7, 0xa0, 0x42, 0x89, 0xe9, 0x18, 0x98, 0x12, 0x91, 0x26, 0x89, 0xf8, - 0x66, 0xe1, 0xc2, 0xc8, 0x76, 0xec, 0xde, 0x6d, 0x01, 0xe3, 0x89, 0x12, 0xce, 0x43, 0xd0, 0x8a, - 0x42, 0x1a, 0x78, 0x1f, 0xcc, 0x0f, 0x9c, 0x1e, 0x43, 0x52, 0xb6, 0x75, 0xf7, 0x0f, 0x45, 0xf8, - 0x5c, 0x3d, 0x72, 0x42, 0x76, 0x13, 0x26, 0xed, 0x65, 0xf1, 0x82, 0xf9, 0x64, 0x3b, 0x4a, 0x51, - 0xc3, 0x35, 0xb0, 0x60, 0xea, 0x16, 0x22, 0xb8, 0x77, 0xd8, 0x25, 0x9a, 0x6d, 0xf5, 0x3c, 0x1e, - 0x40, 0xe5, 0xf6, 0x8a, 0x20, 0x58, 0xd8, 0x4c, 0x76, 0xa3, 0x34, 0x1e, 0x6e, 0x80, 0xa5, 0x60, - 0x9f, 0xfd, 0xa1, 0xee, 0x51, 0xdb, 0x3d, 0xdc, 0xd0, 0x4d, 0x9d, 0xd6, 0xa6, 0x39, 0x4f, 0x6d, - 0x34, 0x6c, 0x2c, 0x21, 0x49, 0x3f, 0x92, 0x5a, 0xa9, 0xbf, 0x99, 0x06, 0x0b, 0xa9, 0xdd, 0x00, - 0xde, 0x01, 0xcb, 0xda, 0xc0, 0x75, 0x89, 0x45, 0xb7, 0x06, 0xe6, 0x1e, 0x71, 0xbb, 0xda, 0x3e, - 0xe9, 0x0d, 0x0c, 0xd2, 0xe3, 0x2b, 0x5a, 0x6e, 0xd7, 0x85, 0xaf, 0xcb, 0x1d, 0x29, 0x0a, 0xe5, - 0x58, 0xc3, 0x1f, 0x01, 0x68, 0xf1, 0xa6, 0x4d, 0xdd, 0xf3, 0x42, 0xce, 0x02, 0xe7, 0x0c, 0x13, - 0x70, 0x2b, 0x83, 0x40, 0x12, 0x2b, 0xe6, 0x63, 0x8f, 0x78, 0xba, 0x4b, 0x7a, 0x69, 0x1f, 0x8b, - 0x49, 0x1f, 0xd7, 0xa5, 0x28, 0x94, 0x63, 0x0d, 0x5f, 0x06, 0x55, 0xff, 0x6d, 0x7c, 0xce, 0xc5, - 0xe2, 0x2c, 0x0a, 0xb2, 0xea, 0x56, 0xd4, 0x85, 0xe2, 0x38, 0x36, 0x34, 0x7b, 0xcf, 0x23, 0xee, - 0x01, 0xe9, 0xbd, 0xe5, 0x6b, 0x00, 0x56, 0x28, 0xcb, 0xbc, 0x50, 0x86, 0x43, 0xdb, 0xce, 0x20, - 0x90, 0xc4, 0x8a, 0x0d, 0xcd, 0x8f, 0x9a, 0xcc, 0xd0, 0xa6, 0x93, 0x43, 0xdb, 0x95, 0xa2, 0x50, - 0x8e, 0x35, 0x8b, 0x3d, 0xdf, 0xe5, 0xb5, 0x03, 0xac, 0x1b, 0x78, 0xcf, 0x20, 0xb5, 0x99, 0x64, - 0xec, 0x6d, 0x25, 0xbb, 0x51, 0x1a, 0x0f, 0xdf, 0x02, 0xe7, 0xfc, 0xa6, 0x5d, 0x0b, 0x87, 0x24, - 0x15, 0x4e, 0xf2, 0x8c, 0x20, 0x39, 0xb7, 0x95, 0x06, 0xa0, 0xac, 0x0d, 0xbc, 0x05, 0xe6, 0x35, - 0xdb, 0x30, 0x78, 0x3c, 0x76, 0xec, 0x81, 0x45, 0x6b, 0xb3, 0x9c, 0x05, 0xb2, 0x1c, 0xea, 0x24, - 0x7a, 0x50, 0x0a, 0x09, 0xef, 0x02, 0xa0, 0x05, 0xe5, 0xc0, 0xab, 0x81, 0xfc, 0x42, 0x9f, 0xad, - 0x43, 0x51, 0x01, 0x0e, 0x9b, 0x3c, 0x14, 0x63, 0x53, 0x3f, 0x56, 0xc0, 0x4a, 0x4e, 0x8e, 0xc3, - 0x37, 0x12, 0x55, 0xef, 0x6a, 0xaa, 0xea, 0x5d, 0xc8, 0x31, 0x8b, 0x95, 0x3e, 0x0d, 0xcc, 0x31, - 0xdd, 0xa1, 0x5b, 0x7d, 0x1f, 0x22, 0x76, 0xb0, 0x17, 0x64, 0xbe, 0xa3, 0x38, 0x30, 0xda, 0x86, - 0xcf, 0x8d, 0x86, 0x8d, 0xb9, 0x44, 0x1f, 0x4a, 0x72, 0xaa, 0xbf, 0x2a, 0x00, 0xb0, 0x4e, 0x1c, - 0xc3, 0x3e, 0x34, 0x89, 0x75, 0x1a, 0xaa, 0x65, 0x3d, 0xa1, 0x5a, 0x54, 0xe9, 0x42, 0x84, 0xfe, - 0xe4, 0xca, 0x96, 0x8d, 0x94, 0x6c, 0xb9, 0x34, 0x86, 0xe7, 0x68, 0xdd, 0xf2, 0xb7, 0x22, 0x58, - 0x8c, 0xc0, 0x91, 0x70, 0x79, 0x2d, 0xb1, 0x84, 0x57, 0x52, 0x4b, 0xb8, 0x22, 0x31, 0x79, 0x6a, - 0xca, 0xe5, 0x7d, 0x30, 0xcf, 0x74, 0x85, 0xbf, 0x6a, 0x5c, 0xb5, 0x4c, 0x1f, 0x5b, 0xb5, 0x84, - 0x55, 0x67, 0x23, 0xc1, 0x84, 0x52, 0xcc, 0x39, 0x2a, 0x69, 0xe6, 0xab, 0xa8, 0x92, 0xfe, 0xa8, - 0x80, 0xf9, 0x68, 0x99, 0x4e, 0x41, 0x26, 0x75, 0x92, 0x32, 0xa9, 0x7e, 0x74, 0x5c, 0xe6, 0xe8, - 0xa4, 0xbf, 0x96, 0xe2, 0x5e, 0x73, 0xa1, 0xb4, 0xca, 0x0e, 0x54, 0x8e, 0xa1, 0x6b, 0xd8, 0x13, - 0x65, 0xf5, 0x8c, 0x7f, 0x98, 0xf2, 0xdb, 0x50, 0xd8, 0x9b, 0x90, 0x54, 0x85, 0xa7, 0x2b, 0xa9, - 0x8a, 0x5f, 0x8c, 0xa4, 0xba, 0x0d, 0x2a, 0x5e, 0x20, 0xa6, 0x4a, 0x9c, 0xf2, 0xf2, 0xb8, 0x74, - 0x16, 0x3a, 0x2a, 0x64, 0x0d, 0x15, 0x54, 0xc8, 0x24, 0xd3, 0x4e, 0xe5, 0x2f, 0x53, 0x3b, 0xb1, - 0xf0, 0x76, 0xf0, 0xc0, 0x23, 0x3d, 0x9e, 0x4a, 0x95, 0x28, 0xbc, 0x77, 0x78, 0x2b, 0x12, 0xbd, - 0x70, 0x17, 0xac, 0x38, 0xae, 0xdd, 0x77, 0x89, 0xe7, 0xad, 0x13, 0xdc, 0x33, 0x74, 0x8b, 0x04, - 0x03, 0xf0, 0xab, 0xde, 0x85, 0xd1, 0xb0, 0xb1, 0xb2, 0x23, 0x87, 0xa0, 0x3c, 0x5b, 0xf5, 0xa3, - 0x12, 0x38, 0x9b, 0xde, 0x11, 0x73, 0x84, 0x88, 0x72, 0x22, 0x21, 0xf2, 0x62, 0x2c, 0x44, 0x7d, - 0x95, 0x16, 0x3b, 0xf3, 0x67, 0xc2, 0x74, 0x0d, 0x2c, 0x08, 0xe1, 0x11, 0x74, 0x0a, 0x29, 0x16, - 0x2e, 0xcf, 0x6e, 0xb2, 0x1b, 0xa5, 0xf1, 0xf0, 0x35, 0x30, 0xe7, 0x72, 0x6d, 0x15, 0x10, 0xf8, - 0xfa, 0xe4, 0x5b, 0x82, 0x60, 0x0e, 0xc5, 0x3b, 0x51, 0x12, 0xcb, 0xb4, 0x49, 0x24, 0x39, 0x02, - 0x82, 0x52, 0x52, 0x9b, 0xac, 0xa5, 0x01, 0x28, 0x6b, 0x03, 0x37, 0xc1, 0xe2, 0xc0, 0xca, 0x52, - 0xf9, 0xb1, 0x76, 0x41, 0x50, 0x2d, 0xee, 0x66, 0x21, 0x48, 0x66, 0x07, 0x7f, 0x92, 0x90, 0x2b, - 0xd3, 0x7c, 0x17, 0xb9, 0x72, 0x74, 0x3a, 0x4c, 0xac, 0x57, 0x24, 0x3a, 0xaa, 0x32, 0xa9, 0x8e, - 0x52, 0x3f, 0x54, 0x00, 0xcc, 0xa6, 0xe0, 0xd8, 0xc3, 0x7d, 0xc6, 0x22, 0x56, 0x22, 0x7b, 0x72, - 0x85, 0x73, 0x75, 0xbc, 0xc2, 0x89, 0x76, 0xd0, 0xc9, 0x24, 0x8e, 0x98, 0xde, 0xd3, 0xb9, 0x98, - 0x99, 0x40, 0xe2, 0x44, 0xfe, 0x3c, 0x99, 0xc4, 0x89, 0xf1, 0x1c, 0x2d, 0x71, 0xfe, 0x59, 0x00, - 0x8b, 0x11, 0x78, 0x62, 0x89, 0x23, 0x31, 0xf9, 0xe6, 0x72, 0x66, 0x32, 0xd9, 0x11, 0x4d, 0xdd, - 0xff, 0x89, 0xec, 0x88, 0x1c, 0xca, 0x91, 0x1d, 0xbf, 0x2f, 0xc4, 0xbd, 0x3e, 0xa6, 0xec, 0xf8, - 0x02, 0xae, 0x2a, 0xbe, 0x72, 0xca, 0x45, 0xfd, 0xa4, 0x08, 0xce, 0xa6, 0x53, 0x30, 0x51, 0x07, - 0x95, 0xb1, 0x75, 0x70, 0x07, 0x2c, 0xdd, 0x1b, 0x18, 0xc6, 0x21, 0x1f, 0x43, 0xac, 0x18, 0xfa, - 0x15, 0xf4, 0xdb, 0xc2, 0x72, 0xe9, 0x07, 0x12, 0x0c, 0x92, 0x5a, 0x66, 0xcb, 0x62, 0xe9, 0x49, - 0xcb, 0x62, 0xf9, 0x04, 0x65, 0x51, 0xae, 0x2c, 0x8a, 0x27, 0x52, 0x16, 0x13, 0xd7, 0x44, 0xc9, - 0x76, 0x35, 0xf6, 0x0c, 0x3f, 0x52, 0xc0, 0xb2, 0xfc, 0xf8, 0x0c, 0x0d, 0x30, 0x6f, 0xe2, 0x07, - 0xf1, 0xcb, 0x8b, 0x71, 0x05, 0x63, 0x40, 0x75, 0xa3, 0xe9, 0x7f, 0xdd, 0x69, 0xbe, 0x6d, 0xd1, - 0x6d, 0xb7, 0x4b, 0x5d, 0xdd, 0xea, 0xfb, 0x05, 0x76, 0x33, 0xc1, 0x85, 0x52, 0xdc, 0xf0, 0x2e, - 0xa8, 0x98, 0xf8, 0x41, 0x77, 0xe0, 0xf6, 0x83, 0x42, 0x78, 0xfc, 0xf7, 0xf0, 0xd8, 0xdf, 0x14, - 0x2c, 0x28, 0xe4, 0x53, 0x3f, 0x57, 0xc0, 0x4a, 0x4e, 0x05, 0xfd, 0x1a, 0x8d, 0xf2, 0x23, 0x05, - 0x5c, 0x4c, 0x8c, 0x92, 0x65, 0x24, 0xb9, 0x37, 0x30, 0x78, 0x72, 0x0a, 0xc1, 0x72, 0x15, 0xcc, - 0x3a, 0xd8, 0xa5, 0x7a, 0xa8, 0x74, 0xcb, 0xed, 0xb9, 0xd1, 0xb0, 0x31, 0xbb, 0x13, 0x34, 0xa2, - 0xa8, 0x5f, 0x32, 0x37, 0x85, 0xa7, 0x37, 0x37, 0xea, 0xaf, 0x0b, 0xa0, 0x1a, 0x73, 0xf9, 0x14, - 0xa4, 0xca, 0x9b, 0x09, 0xa9, 0x22, 0xfd, 0xf8, 0x13, 0x9f, 0xc3, 0x3c, 0xad, 0xb2, 0x99, 0xd2, - 0x2a, 0xdf, 0x1d, 0x47, 0x74, 0xb4, 0x58, 0xf9, 0x57, 0x01, 0x2c, 0xc5, 0xd0, 0x91, 0x5a, 0xf9, - 0x7e, 0x42, 0xad, 0xac, 0xa6, 0xd4, 0x4a, 0x4d, 0x66, 0xf3, 0x8d, 0x5c, 0x19, 0x2f, 0x57, 0xfe, - 0xa4, 0x80, 0x85, 0xd8, 0xdc, 0x9d, 0x82, 0x5e, 0x59, 0x4f, 0xea, 0x95, 0xc6, 0x98, 0x78, 0xc9, - 0x11, 0x2c, 0xb7, 0xc0, 0x62, 0x0c, 0xb4, 0xed, 0xf6, 0x74, 0x0b, 0x1b, 0x1e, 0x7c, 0x0e, 0x94, - 0x3d, 0x8a, 0x5d, 0x1a, 0x64, 0x77, 0x60, 0xdb, 0x65, 0x8d, 0xc8, 0xef, 0x53, 0xff, 0xad, 0x80, - 0x56, 0xcc, 0x78, 0x87, 0xb8, 0x9e, 0xee, 0x51, 0x62, 0xd1, 0x3b, 0xb6, 0x31, 0x30, 0x49, 0xc7, - 0xc0, 0xba, 0x89, 0x08, 0x6b, 0xd0, 0x6d, 0x6b, 0xc7, 0x36, 0x74, 0xed, 0x10, 0x62, 0x50, 0xfd, - 0x60, 0x9f, 0x58, 0xeb, 0xc4, 0x20, 0x54, 0x7c, 0xde, 0x98, 0x6d, 0xbf, 0x11, 0xdc, 0xf6, 0xbf, - 0x1b, 0x75, 0x3d, 0x1e, 0x36, 0x56, 0x27, 0x61, 0xe4, 0xc1, 0x19, 0xe7, 0x84, 0x3f, 0x03, 0x80, - 0x3d, 0x76, 0x35, 0x1c, 0x7c, 0xec, 0x98, 0x6d, 0xbf, 0x1e, 0xa4, 0xf0, 0xbb, 0x61, 0xcf, 0xb1, - 0x5e, 0x10, 0x63, 0x54, 0x7f, 0x57, 0x49, 0x2c, 0xf5, 0xd7, 0xfe, 0x6e, 0xe9, 0x17, 0x60, 0xe9, - 0x20, 0x9a, 0x9d, 0x00, 0xc0, 0x34, 0x11, 0x8b, 0xbb, 0xe7, 0xa5, 0xf4, 0xb2, 0x79, 0x8d, 0x94, - 0xd8, 0x1d, 0x09, 0x1d, 0x92, 0xbe, 0x04, 0xbe, 0x0c, 0xaa, 0x4c, 0xcb, 0xe8, 0x1a, 0xd9, 0xc2, - 0x66, 0x90, 0x86, 0xe1, 0xd7, 0xa1, 0x6e, 0xd4, 0x85, 0xe2, 0x38, 0xb8, 0x0f, 0x16, 0x1d, 0xbb, - 0xb7, 0x89, 0x2d, 0xdc, 0x27, 0xac, 0x42, 0xfb, 0x4b, 0xc9, 0x6f, 0x9d, 0x66, 0xdb, 0xaf, 0x04, - 0x37, 0x0a, 0x3b, 0x59, 0x08, 0x3b, 0xb1, 0x49, 0x9a, 0x79, 0x10, 0xc8, 0x28, 0xa1, 0x99, 0xf9, - 0x98, 0x39, 0x93, 0xf9, 0x07, 0x88, 0x2c, 0x1f, 0x4f, 0xf8, 0x39, 0x33, 0xef, 0x3e, 0xad, 0x72, - 0xa2, 0xfb, 0x34, 0xc9, 0x89, 0x63, 0xf6, 0x98, 0x27, 0x8e, 0x4f, 0x14, 0x70, 0xc9, 0x99, 0x20, - 0x8d, 0x6a, 0x80, 0x4f, 0x4b, 0x67, 0xcc, 0xb4, 0x4c, 0x92, 0x91, 0xed, 0xd5, 0xd1, 0xb0, 0x71, - 0x69, 0x12, 0x24, 0x9a, 0xc8, 0x35, 0x96, 0x34, 0xb6, 0xd8, 0xf9, 0x6a, 0x55, 0xee, 0xe6, 0x95, - 0x31, 0x6e, 0x06, 0x1b, 0xa5, 0x9f, 0x87, 0xc1, 0x13, 0x0a, 0x69, 0xd4, 0x0f, 0xcb, 0xe0, 0x5c, - 0xa6, 0x5a, 0x7f, 0x89, 0x77, 0x85, 0x99, 0x13, 0x4d, 0xf1, 0x18, 0x27, 0x9a, 0x35, 0xb0, 0x20, - 0x3e, 0x30, 0xa7, 0x0e, 0x44, 0x61, 0x98, 0x74, 0x92, 0xdd, 0x28, 0x8d, 0x97, 0xdd, 0x55, 0x96, - 0x8f, 0x79, 0x57, 0x19, 0xf7, 0x42, 0xfc, 0x2f, 0xca, 0xcf, 0xe7, 0xac, 0x17, 0xe2, 0xef, 0x51, - 0x69, 0x3c, 0x7c, 0x3d, 0x48, 0xd6, 0x90, 0x61, 0x86, 0x33, 0xa4, 0xb2, 0x2f, 0x24, 0x48, 0xa1, - 0x9f, 0xe8, 0x23, 0xea, 0x7b, 0x92, 0x8f, 0xa8, 0xab, 0x63, 0xc2, 0x6c, 0xf2, 0x6b, 0x49, 0xe9, - 0xa1, 0xb3, 0x7a, 0xfc, 0x43, 0xa7, 0xfa, 0x17, 0x05, 0x3c, 0x93, 0xbb, 0x4d, 0xc1, 0xb5, 0x84, - 0x7a, 0xbc, 0x96, 0x52, 0x8f, 0xcf, 0xe6, 0x1a, 0xc6, 0x24, 0xa4, 0x29, 0xbf, 0xb1, 0xbc, 0x39, - 0xf6, 0xc6, 0x52, 0x72, 0x12, 0x19, 0x7f, 0x75, 0xd9, 0x7e, 0xf5, 0xe1, 0xa3, 0xfa, 0xd4, 0xa7, - 0x8f, 0xea, 0x53, 0x9f, 0x3d, 0xaa, 0x4f, 0xfd, 0x72, 0x54, 0x57, 0x1e, 0x8e, 0xea, 0xca, 0xa7, - 0xa3, 0xba, 0xf2, 0xd9, 0xa8, 0xae, 0xfc, 0x7d, 0x54, 0x57, 0x7e, 0xfb, 0x79, 0x7d, 0xea, 0x2e, - 0xcc, 0xfe, 0x2b, 0xf3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfa, 0xed, 0x70, 0xaa, 0x29, - 0x00, 0x00, + 0x15, 0xd7, 0x52, 0xa4, 0x44, 0x0d, 0x2d, 0xc9, 0x1e, 0xa9, 0x12, 0x63, 0x37, 0xa4, 0xbb, 0x71, + 0x6d, 0x25, 0x8e, 0xc9, 0xda, 0x71, 0x82, 0xc0, 0x29, 0x12, 0x88, 0x54, 0x9a, 0xba, 0xd1, 0x57, + 0x87, 0x92, 0x03, 0xb8, 0x69, 0xd1, 0xd1, 0x72, 0x4c, 0x6d, 0xbc, 0x5f, 0xd8, 0x1d, 0x2a, 0x16, + 0x7a, 0x29, 0x0a, 0x14, 0xe8, 0x21, 0x87, 0xfe, 0x0d, 0xfd, 0x07, 0x8a, 0xa2, 0x68, 0x6e, 0x45, + 0x50, 0xf4, 0xe2, 0x4b, 0x81, 0xa0, 0x97, 0xe6, 0x44, 0xd4, 0xcc, 0xa9, 0x28, 0x7a, 0x6b, 0x2f, + 0xbe, 0xb4, 0x98, 0xd9, 0xd9, 0xef, 0x59, 0x91, 0x92, 0x63, 0xa5, 0x09, 0x7c, 0xe3, 0xce, 0x7b, + 0xef, 0x37, 0x6f, 0x66, 0xde, 0x9b, 0xf7, 0x9b, 0x19, 0x02, 0xf5, 0xfe, 0xeb, 0x5e, 0x43, 0xb7, + 0x9b, 0xd8, 0xd1, 0x9b, 0xd8, 0x71, 0xbc, 0xe6, 0xc1, 0xf5, 0x66, 0x8f, 0x58, 0xc4, 0xc5, 0x94, + 0x74, 0x1b, 0x8e, 0x6b, 0x53, 0x1b, 0x42, 0x5f, 0xa7, 0x81, 0x1d, 0xbd, 0xc1, 0x74, 0x1a, 0x07, + 0xd7, 0xcf, 0x5f, 0xeb, 0xe9, 0x74, 0xbf, 0xbf, 0xd7, 0xd0, 0x6c, 0xb3, 0xd9, 0xb3, 0x7b, 0x76, + 0x93, 0xab, 0xee, 0xf5, 0xef, 0xf1, 0x2f, 0xfe, 0xc1, 0x7f, 0xf9, 0x10, 0xe7, 0xe3, 0xdd, 0x68, + 0xb6, 0x4b, 0x24, 0xdd, 0x9c, 0xbf, 0x19, 0xe9, 0x98, 0x58, 0xdb, 0xd7, 0x2d, 0xe2, 0x1e, 0x36, + 0x9d, 0xfb, 0x3d, 0xd6, 0xe0, 0x35, 0x4d, 0x42, 0xb1, 0xcc, 0xaa, 0x99, 0x67, 0xe5, 0xf6, 0x2d, + 0xaa, 0x9b, 0x24, 0x63, 0xf0, 0xda, 0x28, 0x03, 0x4f, 0xdb, 0x27, 0x26, 0xce, 0xd8, 0xbd, 0x92, + 0x67, 0xd7, 0xa7, 0xba, 0xd1, 0xd4, 0x2d, 0xea, 0x51, 0x37, 0x6d, 0xa4, 0xfe, 0x47, 0x01, 0xb0, + 0x6d, 0x5b, 0xd4, 0xb5, 0x0d, 0x83, 0xb8, 0x88, 0x1c, 0xe8, 0x9e, 0x6e, 0x5b, 0xf0, 0xa7, 0xa0, + 0xcc, 0xc6, 0xd3, 0xc5, 0x14, 0x57, 0x95, 0x8b, 0xca, 0x4a, 0xe5, 0xc6, 0x77, 0x1a, 0xd1, 0x24, + 0x87, 0xf0, 0x0d, 0xe7, 0x7e, 0x8f, 0x35, 0x78, 0x0d, 0xa6, 0xdd, 0x38, 0xb8, 0xde, 0xd8, 0xda, + 0xfb, 0x80, 0x68, 0x74, 0x83, 0x50, 0xdc, 0x82, 0x0f, 0x07, 0xf5, 0x89, 0xe1, 0xa0, 0x0e, 0xa2, + 0x36, 0x14, 0xa2, 0xc2, 0x2d, 0x50, 0xe4, 0xe8, 0x05, 0x8e, 0x7e, 0x2d, 0x17, 0x5d, 0x0c, 0xba, + 0x81, 0xf0, 0x87, 0x6f, 0x3f, 0xa0, 0xc4, 0x62, 0xee, 0xb5, 0xce, 0x08, 0xe8, 0xe2, 0x1a, 0xa6, + 0x18, 0x71, 0x20, 0xf8, 0x32, 0x28, 0xbb, 0xc2, 0xfd, 0xea, 0xe4, 0x45, 0x65, 0x65, 0xb2, 0x75, + 0x56, 0x68, 0x95, 0x83, 0x61, 0xa1, 0x50, 0x43, 0xfd, 0xb3, 0x02, 0x96, 0xb2, 0xe3, 0x5e, 0xd7, + 0x3d, 0x0a, 0xdf, 0xcf, 0x8c, 0xbd, 0x31, 0xde, 0xd8, 0x99, 0x35, 0x1f, 0x79, 0xd8, 0x71, 0xd0, + 0x12, 0x1b, 0xf7, 0xbb, 0xa0, 0xa4, 0x53, 0x62, 0x7a, 0xd5, 0xc2, 0xc5, 0xc9, 0x95, 0xca, 0x8d, + 0xcb, 0x8d, 0x6c, 0xec, 0x36, 0xb2, 0x8e, 0xb5, 0x66, 0x05, 0x64, 0xe9, 0x36, 0x33, 0x46, 0x3e, + 0x86, 0xfa, 0x5f, 0x05, 0xcc, 0xac, 0x61, 0x62, 0xda, 0x56, 0x87, 0xd0, 0x53, 0x58, 0xb4, 0x36, + 0x28, 0x7a, 0x0e, 0xd1, 0xc4, 0xa2, 0x7d, 0x4b, 0xe6, 0x7b, 0xe8, 0x4e, 0xc7, 0x21, 0x5a, 0xb4, + 0x50, 0xec, 0x0b, 0x71, 0x63, 0xf8, 0x2e, 0x98, 0xf2, 0x28, 0xa6, 0x7d, 0x8f, 0x2f, 0x53, 0xe5, + 0xc6, 0x0b, 0x47, 0xc3, 0x70, 0xd5, 0xd6, 0x9c, 0x00, 0x9a, 0xf2, 0xbf, 0x91, 0x80, 0x50, 0xff, + 0x51, 0x00, 0x30, 0xd4, 0x6d, 0xdb, 0x56, 0x57, 0xa7, 0x2c, 0x7e, 0x6f, 0x81, 0x22, 0x3d, 0x74, + 0x08, 0x9f, 0x86, 0x99, 0xd6, 0xe5, 0xc0, 0x8b, 0x9d, 0x43, 0x87, 0x3c, 0x1e, 0xd4, 0x97, 0xb2, + 0x16, 0x4c, 0x82, 0xb8, 0x0d, 0x5c, 0x0f, 0xfd, 0x2b, 0x70, 0xeb, 0x9b, 0xc9, 0xae, 0x1f, 0x0f, + 0xea, 0x92, 0xcd, 0xa2, 0x11, 0x22, 0x25, 0x1d, 0x84, 0x07, 0x00, 0x1a, 0xd8, 0xa3, 0x3b, 0x2e, + 0xb6, 0x3c, 0xbf, 0x27, 0xdd, 0x24, 0x62, 0xe4, 0x2f, 0x8d, 0xb7, 0x3c, 0xcc, 0xa2, 0x75, 0x5e, + 0x78, 0x01, 0xd7, 0x33, 0x68, 0x48, 0xd2, 0x03, 0xbc, 0x0c, 0xa6, 0x5c, 0x82, 0x3d, 0xdb, 0xaa, + 0x16, 0xf9, 0x28, 0xc2, 0x09, 0x44, 0xbc, 0x15, 0x09, 0x29, 0x7c, 0x11, 0x4c, 0x9b, 0xc4, 0xf3, + 0x70, 0x8f, 0x54, 0x4b, 0x5c, 0x71, 0x5e, 0x28, 0x4e, 0x6f, 0xf8, 0xcd, 0x28, 0x90, 0xab, 0xbf, + 0x53, 0xc0, 0x6c, 0x38, 0x73, 0xa7, 0x90, 0x2a, 0xad, 0x64, 0xaa, 0x3c, 0x7f, 0x64, 0x9c, 0xe4, + 0x64, 0xc8, 0x27, 0x93, 0x31, 0x9f, 0x59, 0x10, 0xc2, 0x1f, 0x83, 0xb2, 0x47, 0x0c, 0xa2, 0x51, + 0xdb, 0x15, 0x3e, 0xbf, 0x32, 0xa6, 0xcf, 0x78, 0x8f, 0x18, 0x1d, 0x61, 0xda, 0x3a, 0xc3, 0x9c, + 0x0e, 0xbe, 0x50, 0x08, 0x09, 0x7f, 0x08, 0xca, 0x94, 0x98, 0x8e, 0x81, 0x29, 0x11, 0x69, 0x92, + 0x88, 0x6f, 0x16, 0x2e, 0x0c, 0x6c, 0xdb, 0xee, 0xee, 0x08, 0x35, 0x9e, 0x28, 0xe1, 0x3c, 0x04, + 0xad, 0x28, 0x84, 0x81, 0xf7, 0xc1, 0x5c, 0xdf, 0xe9, 0x32, 0x4d, 0xca, 0xb6, 0xee, 0xde, 0xa1, + 0x08, 0x9f, 0xab, 0x47, 0x4e, 0xc8, 0x6e, 0xc2, 0xa4, 0xb5, 0x24, 0x3a, 0x98, 0x4b, 0xb6, 0xa3, + 0x14, 0x34, 0x5c, 0x05, 0xf3, 0xa6, 0x6e, 0x21, 0x82, 0xbb, 0x87, 0x1d, 0xa2, 0xd9, 0x56, 0xd7, + 0xe3, 0x01, 0x54, 0x6a, 0x2d, 0x0b, 0x80, 0xf9, 0x8d, 0xa4, 0x18, 0xa5, 0xf5, 0xe1, 0x3a, 0x58, + 0x0c, 0xf6, 0xd9, 0xef, 0xeb, 0x1e, 0xb5, 0xdd, 0xc3, 0x75, 0xdd, 0xd4, 0x69, 0x75, 0x8a, 0xe3, + 0x54, 0x87, 0x83, 0xfa, 0x22, 0x92, 0xc8, 0x91, 0xd4, 0x4a, 0xfd, 0x68, 0x0a, 0xcc, 0xa7, 0x76, + 0x03, 0x78, 0x07, 0x2c, 0x69, 0x7d, 0xd7, 0x25, 0x16, 0xdd, 0xec, 0x9b, 0x7b, 0xc4, 0xed, 0x68, + 0xfb, 0xa4, 0xdb, 0x37, 0x48, 0x97, 0xaf, 0x68, 0xa9, 0x55, 0x13, 0xbe, 0x2e, 0xb5, 0xa5, 0x5a, + 0x28, 0xc7, 0x1a, 0xfe, 0x00, 0x40, 0x8b, 0x37, 0x6d, 0xe8, 0x9e, 0x17, 0x62, 0x16, 0x38, 0x66, + 0x98, 0x80, 0x9b, 0x19, 0x0d, 0x24, 0xb1, 0x62, 0x3e, 0x76, 0x89, 0xa7, 0xbb, 0xa4, 0x9b, 0xf6, + 0x71, 0x32, 0xe9, 0xe3, 0x9a, 0x54, 0x0b, 0xe5, 0x58, 0xc3, 0x57, 0x41, 0xc5, 0xef, 0x8d, 0xcf, + 0xb9, 0x58, 0x9c, 0x05, 0x01, 0x56, 0xd9, 0x8c, 0x44, 0x28, 0xae, 0xc7, 0x86, 0x66, 0xef, 0x79, + 0xc4, 0x3d, 0x20, 0xdd, 0x77, 0x7c, 0x0e, 0xc0, 0x0a, 0x65, 0x89, 0x17, 0xca, 0x70, 0x68, 0x5b, + 0x19, 0x0d, 0x24, 0xb1, 0x62, 0x43, 0xf3, 0xa3, 0x26, 0x33, 0xb4, 0xa9, 0xe4, 0xd0, 0x76, 0xa5, + 0x5a, 0x28, 0xc7, 0x9a, 0xc5, 0x9e, 0xef, 0xf2, 0xea, 0x01, 0xd6, 0x0d, 0xbc, 0x67, 0x90, 0xea, + 0x74, 0x32, 0xf6, 0x36, 0x93, 0x62, 0x94, 0xd6, 0x87, 0xef, 0x80, 0x73, 0x7e, 0xd3, 0xae, 0x85, + 0x43, 0x90, 0x32, 0x07, 0x79, 0x4e, 0x80, 0x9c, 0xdb, 0x4c, 0x2b, 0xa0, 0xac, 0x0d, 0xbc, 0x05, + 0xe6, 0x34, 0xdb, 0x30, 0x78, 0x3c, 0xb6, 0xed, 0xbe, 0x45, 0xab, 0x33, 0x1c, 0x05, 0xb2, 0x1c, + 0x6a, 0x27, 0x24, 0x28, 0xa5, 0x09, 0xef, 0x02, 0xa0, 0x05, 0xe5, 0xc0, 0xab, 0x82, 0xfc, 0x42, + 0x9f, 0xad, 0x43, 0x51, 0x01, 0x0e, 0x9b, 0x3c, 0x14, 0x43, 0x53, 0x3f, 0x51, 0xc0, 0x72, 0x4e, + 0x8e, 0xc3, 0xb7, 0x12, 0x55, 0xef, 0x6a, 0xaa, 0xea, 0x5d, 0xc8, 0x31, 0x8b, 0x95, 0x3e, 0x0d, + 0xcc, 0x32, 0xde, 0xa1, 0x5b, 0x3d, 0x5f, 0x45, 0xec, 0x60, 0x2f, 0xc9, 0x7c, 0x47, 0x71, 0xc5, + 0x68, 0x1b, 0x3e, 0x37, 0x1c, 0xd4, 0x67, 0x13, 0x32, 0x94, 0xc4, 0x54, 0x7f, 0x51, 0x00, 0x60, + 0x8d, 0x38, 0x86, 0x7d, 0x68, 0x12, 0xeb, 0x34, 0x58, 0xcb, 0x5a, 0x82, 0xb5, 0xa8, 0xd2, 0x85, + 0x08, 0xfd, 0xc9, 0xa5, 0x2d, 0xeb, 0x29, 0xda, 0x72, 0x69, 0x04, 0xce, 0xd1, 0xbc, 0xe5, 0x6f, + 0x93, 0x60, 0x21, 0x52, 0x8e, 0x88, 0xcb, 0x1b, 0x89, 0x25, 0xbc, 0x92, 0x5a, 0xc2, 0x65, 0x89, + 0xc9, 0x53, 0x63, 0x2e, 0x1f, 0x80, 0x39, 0xc6, 0x2b, 0xfc, 0x55, 0xe3, 0xac, 0x65, 0xea, 0xd8, + 0xac, 0x25, 0xac, 0x3a, 0xeb, 0x09, 0x24, 0x94, 0x42, 0xce, 0x61, 0x49, 0xd3, 0x5f, 0x45, 0x96, + 0xf4, 0x7b, 0x05, 0xcc, 0x45, 0xcb, 0x74, 0x0a, 0x34, 0xa9, 0x9d, 0xa4, 0x49, 0xb5, 0xa3, 0xe3, + 0x32, 0x87, 0x27, 0xfd, 0xb5, 0x18, 0xf7, 0x9a, 0x13, 0xa5, 0x15, 0x76, 0xa0, 0x72, 0x0c, 0x5d, + 0xc3, 0x9e, 0x28, 0xab, 0x67, 0xfc, 0xc3, 0x94, 0xdf, 0x86, 0x42, 0x69, 0x82, 0x52, 0x15, 0x9e, + 0x2e, 0xa5, 0x9a, 0xfc, 0x62, 0x28, 0xd5, 0x0e, 0x28, 0x7b, 0x01, 0x99, 0x2a, 0x72, 0xc8, 0xcb, + 0xa3, 0xd2, 0x59, 0xf0, 0xa8, 0x10, 0x35, 0x64, 0x50, 0x21, 0x92, 0x8c, 0x3b, 0x95, 0xbe, 0x4c, + 0xee, 0xc4, 0xc2, 0xdb, 0xc1, 0x7d, 0x8f, 0x74, 0x79, 0x2a, 0x95, 0xa3, 0xf0, 0xde, 0xe6, 0xad, + 0x48, 0x48, 0xe1, 0x2e, 0x58, 0x76, 0x5c, 0xbb, 0xe7, 0x12, 0xcf, 0x5b, 0x23, 0xb8, 0x6b, 0xe8, + 0x16, 0x09, 0x06, 0xe0, 0x57, 0xbd, 0x0b, 0xc3, 0x41, 0x7d, 0x79, 0x5b, 0xae, 0x82, 0xf2, 0x6c, + 0xd5, 0x5f, 0x95, 0xc0, 0xd9, 0xf4, 0x8e, 0x98, 0x43, 0x44, 0x94, 0x13, 0x11, 0x91, 0x97, 0x63, + 0x21, 0xea, 0xb3, 0xb4, 0xd8, 0x99, 0x3f, 0x13, 0xa6, 0xab, 0x60, 0x5e, 0x10, 0x8f, 0x40, 0x28, + 0xa8, 0x58, 0xb8, 0x3c, 0xbb, 0x49, 0x31, 0x4a, 0xeb, 0xc3, 0x37, 0xc0, 0xac, 0xcb, 0xb9, 0x55, + 0x00, 0xe0, 0xf3, 0x93, 0x6f, 0x08, 0x80, 0x59, 0x14, 0x17, 0xa2, 0xa4, 0x2e, 0xe3, 0x26, 0x11, + 0xe5, 0x08, 0x00, 0x8a, 0x49, 0x6e, 0xb2, 0x9a, 0x56, 0x40, 0x59, 0x1b, 0xb8, 0x01, 0x16, 0xfa, + 0x56, 0x16, 0xca, 0x8f, 0xb5, 0x0b, 0x02, 0x6a, 0x61, 0x37, 0xab, 0x82, 0x64, 0x76, 0xf0, 0x36, + 0x58, 0xa0, 0xc4, 0x35, 0x75, 0x0b, 0x53, 0xdd, 0xea, 0x85, 0x70, 0xfe, 0xca, 0x2f, 0x33, 0xa8, + 0x9d, 0xac, 0x18, 0xc9, 0x6c, 0xe0, 0x8f, 0x12, 0xcc, 0x67, 0x8a, 0x6f, 0x48, 0x57, 0x8e, 0xce, + 0xac, 0xb1, 0xa9, 0x8f, 0x84, 0x92, 0x95, 0xc7, 0xa5, 0x64, 0xea, 0xc7, 0x0a, 0x80, 0xd9, 0x6c, + 0x1e, 0x79, 0x4f, 0x90, 0xb1, 0x88, 0x55, 0xdb, 0xae, 0x9c, 0x2c, 0x5d, 0x1d, 0x4d, 0x96, 0xa2, + 0xcd, 0x78, 0x3c, 0xb6, 0x24, 0xa6, 0xf7, 0x74, 0xee, 0x78, 0xc6, 0x60, 0x4b, 0x91, 0x3f, 0x4f, + 0xc6, 0x96, 0x62, 0x38, 0x47, 0xb3, 0xa5, 0x7f, 0x16, 0xc0, 0x42, 0xa4, 0x3c, 0x36, 0x5b, 0x92, + 0x98, 0x3c, 0xbb, 0xe7, 0x19, 0x8f, 0xc1, 0x44, 0x53, 0xf7, 0x7f, 0xc2, 0x60, 0x22, 0x87, 0x72, + 0x18, 0xcc, 0x6f, 0x0b, 0x71, 0xaf, 0x8f, 0xc9, 0x60, 0xbe, 0x80, 0x5b, 0x8f, 0xaf, 0x1c, 0x09, + 0x52, 0x3f, 0x2a, 0x82, 0xb3, 0xe9, 0x14, 0x4c, 0x94, 0x54, 0x65, 0x64, 0x49, 0xdd, 0x06, 0x8b, + 0xf7, 0xfa, 0x86, 0x71, 0xc8, 0xc7, 0x10, 0xab, 0xab, 0x7e, 0x31, 0xfe, 0xa6, 0xb0, 0x5c, 0xfc, + 0x9e, 0x44, 0x07, 0x49, 0x2d, 0xb3, 0x15, 0xb6, 0xf8, 0xa4, 0x15, 0xb6, 0x74, 0x82, 0x0a, 0x9b, + 0x53, 0x12, 0xa7, 0x4f, 0x50, 0x12, 0xe5, 0x7c, 0x67, 0xf2, 0x44, 0x7c, 0x67, 0xec, 0xf2, 0x2a, + 0xd9, 0xf9, 0x46, 0xde, 0x2c, 0x0c, 0x15, 0xb0, 0x24, 0x3f, 0xd4, 0x43, 0x03, 0xcc, 0x99, 0xf8, + 0x41, 0xfc, 0x4a, 0x65, 0x54, 0xed, 0xe9, 0x53, 0xdd, 0x68, 0xf8, 0x6f, 0x4e, 0x8d, 0xdb, 0x16, + 0xdd, 0x72, 0x3b, 0xd4, 0xd5, 0xad, 0x9e, 0x5f, 0xab, 0x37, 0x12, 0x58, 0x28, 0x85, 0x0d, 0xef, + 0x82, 0xb2, 0x89, 0x1f, 0x74, 0xfa, 0x6e, 0x2f, 0xa8, 0xa9, 0xc7, 0xef, 0x87, 0xa7, 0xd1, 0x86, + 0x40, 0x41, 0x21, 0x9e, 0xfa, 0xb9, 0x02, 0x96, 0x73, 0x8a, 0xf1, 0xd7, 0x68, 0x94, 0x7f, 0x54, + 0xc0, 0xc5, 0xc4, 0x28, 0x59, 0x72, 0x93, 0x7b, 0x7d, 0x83, 0xe7, 0xb9, 0xe0, 0x3e, 0x57, 0xc1, + 0x8c, 0x83, 0x5d, 0xaa, 0x87, 0xfc, 0xbb, 0xd4, 0x9a, 0x1d, 0x0e, 0xea, 0x33, 0xdb, 0x41, 0x23, + 0x8a, 0xe4, 0x92, 0xb9, 0x29, 0x3c, 0xbd, 0xb9, 0x51, 0x7f, 0x59, 0x00, 0x95, 0x98, 0xcb, 0xa7, + 0xc0, 0x7a, 0xde, 0x4e, 0xb0, 0x1e, 0xe9, 0x93, 0x54, 0x7c, 0x0e, 0xf3, 0x68, 0xcf, 0x46, 0x8a, + 0xf6, 0x7c, 0x7b, 0x14, 0xd0, 0xd1, 0xbc, 0xe7, 0x5f, 0x05, 0xb0, 0x18, 0xd3, 0x8e, 0x88, 0xcf, + 0x77, 0x13, 0xc4, 0x67, 0x25, 0x45, 0x7c, 0xaa, 0x32, 0x9b, 0x67, 0xcc, 0x67, 0x34, 0xf3, 0xf9, + 0x83, 0x02, 0xe6, 0x63, 0x73, 0x77, 0x0a, 0xd4, 0x67, 0x2d, 0x49, 0x7d, 0xea, 0x23, 0xe2, 0x25, + 0x87, 0xfb, 0xdc, 0x02, 0x0b, 0x31, 0xa5, 0x2d, 0xb7, 0xab, 0x5b, 0xd8, 0xf0, 0xe0, 0x0b, 0xa0, + 0xe4, 0x51, 0xec, 0xd2, 0x20, 0xbb, 0x03, 0xdb, 0x0e, 0x6b, 0x44, 0xbe, 0x4c, 0xfd, 0xb7, 0x02, + 0x9a, 0x31, 0xe3, 0x6d, 0xe2, 0x7a, 0xba, 0x47, 0x89, 0x45, 0xef, 0xd8, 0x46, 0xdf, 0x24, 0x6d, + 0x03, 0xeb, 0x26, 0x22, 0xac, 0x41, 0xb7, 0xad, 0x6d, 0xdb, 0xd0, 0xb5, 0x43, 0x88, 0x41, 0xe5, + 0xc3, 0x7d, 0x62, 0xad, 0x11, 0x83, 0x50, 0xf1, 0xe8, 0x32, 0xd3, 0x7a, 0x2b, 0x78, 0x83, 0x78, + 0x2f, 0x12, 0x3d, 0x1e, 0xd4, 0x57, 0xc6, 0x41, 0xe4, 0xc1, 0x19, 0xc7, 0x84, 0x3f, 0x01, 0x80, + 0x7d, 0x76, 0x34, 0x1c, 0x3c, 0xc1, 0xcc, 0xb4, 0xde, 0x0c, 0x52, 0xf8, 0xbd, 0x50, 0x72, 0xac, + 0x0e, 0x62, 0x88, 0xea, 0x6f, 0xca, 0x89, 0xa5, 0xfe, 0xda, 0xdf, 0x78, 0xfd, 0x0c, 0x2c, 0x1e, + 0x44, 0xb3, 0x13, 0x28, 0x30, 0x7a, 0xc5, 0xe2, 0xee, 0x45, 0x29, 0xbc, 0x6c, 0x5e, 0x23, 0x52, + 0x77, 0x47, 0x02, 0x87, 0xa4, 0x9d, 0xc0, 0x57, 0x41, 0x85, 0x71, 0x19, 0x5d, 0x23, 0x9b, 0xd8, + 0x0c, 0xd2, 0x30, 0x7c, 0xb3, 0xea, 0x44, 0x22, 0x14, 0xd7, 0x83, 0xfb, 0x60, 0xc1, 0xb1, 0xbb, + 0x1b, 0xd8, 0xc2, 0x3d, 0xc2, 0x2a, 0xb4, 0xbf, 0x94, 0xfc, 0x2e, 0x6c, 0xa6, 0xf5, 0x5a, 0x70, + 0xcf, 0xb1, 0x9d, 0x55, 0x61, 0x87, 0x3f, 0x49, 0x33, 0x0f, 0x02, 0x19, 0x24, 0x34, 0x33, 0x4f, + 0xac, 0xd3, 0x99, 0xff, 0xa5, 0xc8, 0xf2, 0xf1, 0x84, 0x8f, 0xac, 0x79, 0xb7, 0x7c, 0xe5, 0x13, + 0xdd, 0xf2, 0x49, 0x0e, 0x2f, 0x33, 0xc7, 0x3c, 0xbc, 0xfc, 0x49, 0x01, 0x97, 0x9c, 0x31, 0xd2, + 0xa8, 0x0a, 0xf8, 0xb4, 0xb4, 0x47, 0x4c, 0xcb, 0x38, 0x19, 0xd9, 0x5a, 0x19, 0x0e, 0xea, 0x97, + 0xc6, 0xd1, 0x44, 0x63, 0xb9, 0xc6, 0x92, 0xc6, 0x16, 0x3b, 0x5f, 0xb5, 0xc2, 0xdd, 0xbc, 0x32, + 0xc2, 0xcd, 0x60, 0xa3, 0xf4, 0xf3, 0x30, 0xf8, 0x42, 0x21, 0x8c, 0xfa, 0x71, 0x09, 0x9c, 0xcb, + 0x54, 0xeb, 0x2f, 0xf1, 0x06, 0x33, 0x73, 0x38, 0x9a, 0x3c, 0xc6, 0xe1, 0x68, 0x15, 0xcc, 0x8b, + 0x67, 0xef, 0xd4, 0xd9, 0x2a, 0x0c, 0x93, 0x76, 0x52, 0x8c, 0xd2, 0xfa, 0xb2, 0x1b, 0xd4, 0xd2, + 0x31, 0x6f, 0x50, 0xe3, 0x5e, 0x88, 0x7f, 0x6b, 0xf9, 0xf9, 0x9c, 0xf5, 0x42, 0xfc, 0x69, 0x2b, + 0xad, 0x0f, 0xdf, 0x0c, 0x92, 0x35, 0x44, 0x98, 0xe6, 0x08, 0xa9, 0xec, 0x0b, 0x01, 0x52, 0xda, + 0x4f, 0xf4, 0xb4, 0xfb, 0xbe, 0xe4, 0x69, 0x77, 0x65, 0x44, 0x98, 0x8d, 0x7f, 0xc3, 0x29, 0x3d, + 0xbf, 0x56, 0x8e, 0x7f, 0x7e, 0x55, 0xff, 0xa2, 0x80, 0xe7, 0x72, 0xb7, 0x29, 0xb8, 0x9a, 0x60, + 0x8f, 0xd7, 0x52, 0xec, 0xf1, 0xf9, 0x5c, 0xc3, 0x18, 0x85, 0x34, 0xe5, 0x97, 0x9f, 0x37, 0x47, + 0x5e, 0x7e, 0x4a, 0x4e, 0x22, 0xa3, 0x6f, 0x41, 0x5b, 0xaf, 0x3f, 0x7c, 0x54, 0x9b, 0xf8, 0xf4, + 0x51, 0x6d, 0xe2, 0xb3, 0x47, 0xb5, 0x89, 0x9f, 0x0f, 0x6b, 0xca, 0xc3, 0x61, 0x4d, 0xf9, 0x74, + 0x58, 0x53, 0x3e, 0x1b, 0xd6, 0x94, 0xbf, 0x0f, 0x6b, 0xca, 0xaf, 0x3f, 0xaf, 0x4d, 0xdc, 0x85, + 0xd9, 0xff, 0x8a, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x0a, 0xea, 0xf9, 0x40, 0x2a, 0x00, + 0x00, } func (m *ControllerRevision) Marshal() (dAtA []byte, err error) { @@ -1748,6 +1750,11 @@ func (m *DeploymentStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x48 + } if m.CollisionCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) i-- @@ -2054,6 +2061,11 @@ func (m *ReplicaSetStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x38 + } if len(m.Conditions) > 0 { for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { { @@ -2915,6 +2927,9 @@ func (m *DeploymentStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -3020,6 +3035,9 @@ func (m *ReplicaSetStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -3435,6 +3453,7 @@ func (this *DeploymentStatus) String() string { `Conditions:` + repeatedStringForConditions + `,`, `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -3521,6 +3540,7 @@ func (this *ReplicaSetStatus) String() string { `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `AvailableReplicas:` + fmt.Sprintf("%v", this.AvailableReplicas) + `,`, `Conditions:` + repeatedStringForConditions + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -5941,6 +5961,26 @@ func (m *DeploymentStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6873,6 +6913,26 @@ func (m *ReplicaSetStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/apps/v1/generated.proto b/go-controller/vendor/k8s.io/api/apps/v1/generated.proto index 388e638f4d..38c8997e99 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/apps/v1/generated.proto @@ -318,19 +318,19 @@ message DeploymentStatus { // +optional optional int64 observedGeneration = 1; - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional optional int32 replicas = 2; - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional optional int32 updatedReplicas = 3; - // readyReplicas is the number of pods targeted by this Deployment with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional optional int32 readyReplicas = 7; - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional optional int32 availableReplicas = 4; @@ -340,6 +340,13 @@ message DeploymentStatus { // +optional optional int32 unavailableReplicas = 5; + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 9; + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -421,16 +428,16 @@ message ReplicaSetList { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset repeated ReplicaSet items = 2; } // ReplicaSetSpec is the specification of a ReplicaSet. message ReplicaSetSpec { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional optional int32 replicas = 1; @@ -448,29 +455,36 @@ message ReplicaSetSpec { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional optional .k8s.io.api.core.v1.PodTemplateSpec template = 3; } // ReplicaSetStatus represents the current status of a ReplicaSet. message ReplicaSetStatus { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset optional int32 replicas = 1; - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional optional int32 fullyLabeledReplicas = 2; - // readyReplicas is the number of pods targeted by this ReplicaSet with a Ready Condition. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional optional int32 readyReplicas = 4; - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional optional int32 availableReplicas = 5; + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 7; + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional optional int64 observedGeneration = 3; @@ -702,6 +716,7 @@ message StatefulSetSpec { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional optional string serviceName = 5; // podManagementPolicy controls how pods are created during initial scale up, diff --git a/go-controller/vendor/k8s.io/api/apps/v1/types.go b/go-controller/vendor/k8s.io/api/apps/v1/types.go index a68690b447..1362d875d8 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/types.go +++ b/go-controller/vendor/k8s.io/api/apps/v1/types.go @@ -220,6 +220,7 @@ type StatefulSetSpec struct { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` // podManagementPolicy controls how pods are created during initial scale up, @@ -486,19 +487,19 @@ type DeploymentStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` - // readyReplicas is the number of pods targeted by this Deployment with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` @@ -508,6 +509,13 @@ type DeploymentStatus struct { // +optional UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,9,opt,name=terminatingReplicas"` + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -839,16 +847,16 @@ type ReplicaSetList struct { metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Items []ReplicaSet `json:"items" protobuf:"bytes,2,rep,name=items"` } // ReplicaSetSpec is the specification of a ReplicaSet. type ReplicaSetSpec struct { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` @@ -866,29 +874,36 @@ type ReplicaSetSpec struct { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional Template v1.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,3,opt,name=template"` } // ReplicaSetStatus represents the current status of a ReplicaSet. type ReplicaSetStatus struct { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty" protobuf:"varint,2,opt,name=fullyLabeledReplicas"` - // readyReplicas is the number of pods targeted by this ReplicaSet with a Ready Condition. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,4,opt,name=readyReplicas"` - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"` + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,7,opt,name=terminatingReplicas"` + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` diff --git a/go-controller/vendor/k8s.io/api/apps/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/apps/v1/types_swagger_doc_generated.go index 341ecdadb2..f44ba7bc33 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/apps/v1/types_swagger_doc_generated.go @@ -177,11 +177,12 @@ func (DeploymentSpec) SwaggerDoc() map[string]string { var map_DeploymentStatus = map[string]string{ "": "DeploymentStatus is the most recently observed status of the Deployment.", "observedGeneration": "The generation observed by the deployment controller.", - "replicas": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", - "updatedReplicas": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", - "readyReplicas": "readyReplicas is the number of pods targeted by this Deployment with a Ready Condition.", - "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "replicas": "Total number of non-terminating pods targeted by this deployment (their labels match the selector).", + "updatedReplicas": "Total number of non-terminating pods targeted by this deployment that have the desired template spec.", + "readyReplicas": "Total number of non-terminating pods targeted by this Deployment with a Ready Condition.", + "availableReplicas": "Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment.", "unavailableReplicas": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "terminatingReplicas": "Total number of terminating pods targeted by this deployment. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "conditions": "Represents the latest available observations of a deployment's current state.", "collisionCount": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", } @@ -227,7 +228,7 @@ func (ReplicaSetCondition) SwaggerDoc() map[string]string { var map_ReplicaSetList = map[string]string{ "": "ReplicaSetList is a collection of ReplicaSets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", + "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", } func (ReplicaSetList) SwaggerDoc() map[string]string { @@ -236,10 +237,10 @@ func (ReplicaSetList) SwaggerDoc() map[string]string { var map_ReplicaSetSpec = map[string]string{ "": "ReplicaSetSpec is the specification of a ReplicaSet.", - "replicas": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "replicas": "Replicas is the number of desired pods. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", "minReadySeconds": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "selector": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", + "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template", } func (ReplicaSetSpec) SwaggerDoc() map[string]string { @@ -248,10 +249,11 @@ func (ReplicaSetSpec) SwaggerDoc() map[string]string { var map_ReplicaSetStatus = map[string]string{ "": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "replicas": "Replicas is the most recently observed number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", - "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replicaset.", - "readyReplicas": "readyReplicas is the number of pods targeted by this ReplicaSet with a Ready Condition.", - "availableReplicas": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", + "replicas": "Replicas is the most recently observed number of non-terminating pods. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", + "fullyLabeledReplicas": "The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset.", + "readyReplicas": "The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition.", + "availableReplicas": "The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set.", + "terminatingReplicas": "The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", "conditions": "Represents the latest available observations of a replica set's current state.", } diff --git a/go-controller/vendor/k8s.io/api/apps/v1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/apps/v1/zz_generated.deepcopy.go index 6912986ac3..9e67658ba6 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/apps/v1/zz_generated.deepcopy.go @@ -363,6 +363,11 @@ func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]DeploymentCondition, len(*in)) @@ -517,6 +522,11 @@ func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]ReplicaSetCondition, len(*in)) diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/apps/v1beta1/doc.go index 38a358551a..7770fab5d2 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1beta1 // import "k8s.io/api/apps/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.pb.go b/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.pb.go index 76e755b4a3..ae84aaf487 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.pb.go @@ -728,134 +728,135 @@ func init() { } var fileDescriptor_2747f709ac7c95e7 = []byte{ - // 2018 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x59, 0xcd, 0x6f, 0x1b, 0xc7, - 0x15, 0xf7, 0x52, 0xa2, 0x44, 0x3d, 0x45, 0x94, 0x3d, 0x52, 0x2d, 0x46, 0x69, 0x25, 0x61, 0x63, - 0xc4, 0x4a, 0x62, 0x2f, 0x63, 0x25, 0x0d, 0x12, 0xbb, 0x75, 0x21, 0x4a, 0x6e, 0xec, 0x40, 0x8a, - 0x94, 0x91, 0x64, 0xa3, 0xe9, 0x07, 0x32, 0x22, 0xc7, 0xd4, 0x46, 0xfb, 0x85, 0xdd, 0x21, 0x63, - 0xa2, 0x97, 0xfe, 0x01, 0x05, 0xd2, 0x73, 0xff, 0x8a, 0xf6, 0xd4, 0xa2, 0x45, 0x2f, 0x3d, 0x14, - 0x3e, 0x06, 0xbd, 0x34, 0x27, 0xa2, 0x66, 0xae, 0xed, 0xad, 0xbd, 0x18, 0x28, 0x50, 0xcc, 0xec, - 0xec, 0xf7, 0xae, 0xb4, 0x2c, 0x60, 0x01, 0xcd, 0x8d, 0x3b, 0xef, 0xbd, 0xdf, 0x7b, 0xf3, 0xe6, - 0xbd, 0x37, 0xef, 0x0d, 0xe1, 0xfa, 0xe9, 0x7b, 0x9e, 0xa6, 0xdb, 0x4d, 0xe2, 0xe8, 0x4d, 0xe2, - 0x38, 0x5e, 0xb3, 0x7f, 0xeb, 0x98, 0x32, 0x72, 0xab, 0xd9, 0xa5, 0x16, 0x75, 0x09, 0xa3, 0x1d, - 0xcd, 0x71, 0x6d, 0x66, 0xa3, 0x25, 0x9f, 0x51, 0x23, 0x8e, 0xae, 0x71, 0x46, 0x4d, 0x32, 0x2e, - 0xdf, 0xec, 0xea, 0xec, 0xa4, 0x77, 0xac, 0xb5, 0x6d, 0xb3, 0xd9, 0xb5, 0xbb, 0x76, 0x53, 0xf0, - 0x1f, 0xf7, 0x1e, 0x8b, 0x2f, 0xf1, 0x21, 0x7e, 0xf9, 0x38, 0xcb, 0x6a, 0x4c, 0x61, 0xdb, 0x76, - 0x69, 0xb3, 0x9f, 0xd1, 0xb5, 0xfc, 0x4e, 0xc4, 0x63, 0x92, 0xf6, 0x89, 0x6e, 0x51, 0x77, 0xd0, - 0x74, 0x4e, 0xbb, 0x7c, 0xc1, 0x6b, 0x9a, 0x94, 0x91, 0x3c, 0xa9, 0x66, 0x91, 0x94, 0xdb, 0xb3, - 0x98, 0x6e, 0xd2, 0x8c, 0xc0, 0xbb, 0xe7, 0x09, 0x78, 0xed, 0x13, 0x6a, 0x92, 0x8c, 0xdc, 0xdb, - 0x45, 0x72, 0x3d, 0xa6, 0x1b, 0x4d, 0xdd, 0x62, 0x1e, 0x73, 0xd3, 0x42, 0xea, 0xbf, 0x15, 0x40, - 0x5b, 0xb6, 0xc5, 0x5c, 0xdb, 0x30, 0xa8, 0x8b, 0x69, 0x5f, 0xf7, 0x74, 0xdb, 0x42, 0x9f, 0x42, - 0x8d, 0xef, 0xa7, 0x43, 0x18, 0x69, 0x28, 0x6b, 0xca, 0xfa, 0xec, 0xc6, 0x5b, 0x5a, 0xe4, 0xe9, - 0x10, 0x5e, 0x73, 0x4e, 0xbb, 0x7c, 0xc1, 0xd3, 0x38, 0xb7, 0xd6, 0xbf, 0xa5, 0xed, 0x1d, 0x7f, - 0x46, 0xdb, 0x6c, 0x97, 0x32, 0xd2, 0x42, 0x4f, 0x87, 0xab, 0x97, 0x46, 0xc3, 0x55, 0x88, 0xd6, - 0x70, 0x88, 0x8a, 0xf6, 0x60, 0x52, 0xa0, 0x57, 0x04, 0xfa, 0xcd, 0x42, 0x74, 0xb9, 0x69, 0x0d, - 0x93, 0xcf, 0xef, 0x3d, 0x61, 0xd4, 0xe2, 0xe6, 0xb5, 0x5e, 0x92, 0xd0, 0x93, 0xdb, 0x84, 0x11, - 0x2c, 0x80, 0xd0, 0x0d, 0xa8, 0xb9, 0xd2, 0xfc, 0xc6, 0xc4, 0x9a, 0xb2, 0x3e, 0xd1, 0xba, 0x2c, - 0xb9, 0x6a, 0xc1, 0xb6, 0x70, 0xc8, 0xa1, 0x3e, 0x55, 0xe0, 0x6a, 0x76, 0xdf, 0x3b, 0xba, 0xc7, - 0xd0, 0x4f, 0x32, 0x7b, 0xd7, 0xca, 0xed, 0x9d, 0x4b, 0x8b, 0x9d, 0x87, 0x8a, 0x83, 0x95, 0xd8, - 0xbe, 0xf7, 0xa1, 0xaa, 0x33, 0x6a, 0x7a, 0x8d, 0xca, 0xda, 0xc4, 0xfa, 0xec, 0xc6, 0x9b, 0x5a, - 0x41, 0x00, 0x6b, 0x59, 0xeb, 0x5a, 0x73, 0x12, 0xb7, 0xfa, 0x80, 0x23, 0x60, 0x1f, 0x48, 0xfd, - 0x65, 0x05, 0x60, 0x9b, 0x3a, 0x86, 0x3d, 0x30, 0xa9, 0xc5, 0x2e, 0xe0, 0xe8, 0x1e, 0xc0, 0xa4, - 0xe7, 0xd0, 0xb6, 0x3c, 0xba, 0xeb, 0x85, 0x3b, 0x88, 0x8c, 0x3a, 0x70, 0x68, 0x3b, 0x3a, 0x34, - 0xfe, 0x85, 0x05, 0x04, 0xfa, 0x18, 0xa6, 0x3c, 0x46, 0x58, 0xcf, 0x13, 0x47, 0x36, 0xbb, 0xf1, - 0x7a, 0x19, 0x30, 0x21, 0xd0, 0xaa, 0x4b, 0xb8, 0x29, 0xff, 0x1b, 0x4b, 0x20, 0xf5, 0x6f, 0x13, - 0xb0, 0x10, 0x31, 0x6f, 0xd9, 0x56, 0x47, 0x67, 0x3c, 0xa4, 0xef, 0xc0, 0x24, 0x1b, 0x38, 0x54, - 0xf8, 0x64, 0xa6, 0x75, 0x3d, 0x30, 0xe6, 0x70, 0xe0, 0xd0, 0xe7, 0xc3, 0xd5, 0xa5, 0x1c, 0x11, - 0x4e, 0xc2, 0x42, 0x08, 0xed, 0x84, 0x76, 0x56, 0x84, 0xf8, 0x3b, 0x49, 0xe5, 0xcf, 0x87, 0xab, - 0x39, 0x05, 0x44, 0x0b, 0x91, 0x92, 0x26, 0xa2, 0xcf, 0xa0, 0x6e, 0x10, 0x8f, 0x1d, 0x39, 0x1d, - 0xc2, 0xe8, 0xa1, 0x6e, 0xd2, 0xc6, 0x94, 0xd8, 0xfd, 0x1b, 0xe5, 0x0e, 0x8a, 0x4b, 0xb4, 0xae, - 0x4a, 0x0b, 0xea, 0x3b, 0x09, 0x24, 0x9c, 0x42, 0x46, 0x7d, 0x40, 0x7c, 0xe5, 0xd0, 0x25, 0x96, - 0xe7, 0xef, 0x8a, 0xeb, 0x9b, 0x1e, 0x5b, 0xdf, 0xb2, 0xd4, 0x87, 0x76, 0x32, 0x68, 0x38, 0x47, - 0x03, 0x7a, 0x0d, 0xa6, 0x5c, 0x4a, 0x3c, 0xdb, 0x6a, 0x4c, 0x0a, 0x8f, 0x85, 0xc7, 0x85, 0xc5, - 0x2a, 0x96, 0x54, 0xf4, 0x3a, 0x4c, 0x9b, 0xd4, 0xf3, 0x48, 0x97, 0x36, 0xaa, 0x82, 0x71, 0x5e, - 0x32, 0x4e, 0xef, 0xfa, 0xcb, 0x38, 0xa0, 0xab, 0xbf, 0x57, 0xa0, 0x1e, 0x1d, 0xd3, 0x05, 0xe4, - 0xea, 0xfd, 0x64, 0xae, 0xbe, 0x5a, 0x22, 0x38, 0x0b, 0x72, 0xf4, 0x1f, 0x15, 0x40, 0x11, 0x13, - 0xb6, 0x0d, 0xe3, 0x98, 0xb4, 0x4f, 0xd1, 0x1a, 0x4c, 0x5a, 0xc4, 0x0c, 0x62, 0x32, 0x4c, 0x90, - 0x8f, 0x88, 0x49, 0xb1, 0xa0, 0xa0, 0x2f, 0x14, 0x40, 0x3d, 0x71, 0x9a, 0x9d, 0x4d, 0xcb, 0xb2, - 0x19, 0xe1, 0x0e, 0x0e, 0x0c, 0xda, 0x2a, 0x61, 0x50, 0xa0, 0x4b, 0x3b, 0xca, 0xa0, 0xdc, 0xb3, - 0x98, 0x3b, 0x88, 0x0e, 0x36, 0xcb, 0x80, 0x73, 0x54, 0xa3, 0x1f, 0x03, 0xb8, 0x12, 0xf3, 0xd0, - 0x96, 0x69, 0x5b, 0x5c, 0x03, 0x02, 0xf5, 0x5b, 0xb6, 0xf5, 0x58, 0xef, 0x46, 0x85, 0x05, 0x87, - 0x10, 0x38, 0x06, 0xb7, 0x7c, 0x0f, 0x96, 0x0a, 0xec, 0x44, 0x97, 0x61, 0xe2, 0x94, 0x0e, 0x7c, - 0x57, 0x61, 0xfe, 0x13, 0x2d, 0x42, 0xb5, 0x4f, 0x8c, 0x1e, 0xf5, 0x73, 0x12, 0xfb, 0x1f, 0xb7, - 0x2b, 0xef, 0x29, 0xea, 0x6f, 0xaa, 0xf1, 0x48, 0xe1, 0xf5, 0x06, 0xad, 0xf3, 0xeb, 0xc1, 0x31, - 0xf4, 0x36, 0xf1, 0x04, 0x46, 0xb5, 0xf5, 0x92, 0x7f, 0x35, 0xf8, 0x6b, 0x38, 0xa4, 0xa2, 0x9f, - 0x42, 0xcd, 0xa3, 0x06, 0x6d, 0x33, 0xdb, 0x95, 0x25, 0xee, 0xed, 0x92, 0x31, 0x45, 0x8e, 0xa9, - 0x71, 0x20, 0x45, 0x7d, 0xf8, 0xe0, 0x0b, 0x87, 0x90, 0xe8, 0x63, 0xa8, 0x31, 0x6a, 0x3a, 0x06, - 0x61, 0x54, 0x7a, 0x2f, 0x11, 0x57, 0xbc, 0x76, 0x70, 0xb0, 0x7d, 0xbb, 0x73, 0x28, 0xd9, 0x44, - 0xf5, 0x0c, 0xe3, 0x34, 0x58, 0xc5, 0x21, 0x0c, 0xfa, 0x11, 0xd4, 0x3c, 0xc6, 0x6f, 0xf5, 0xee, - 0x40, 0x64, 0xdb, 0x59, 0xd7, 0x4a, 0xbc, 0x8e, 0xfa, 0x22, 0x11, 0x74, 0xb0, 0x82, 0x43, 0x38, - 0xb4, 0x09, 0xf3, 0xa6, 0x6e, 0x61, 0x4a, 0x3a, 0x83, 0x03, 0xda, 0xb6, 0xad, 0x8e, 0x27, 0xd2, - 0xb4, 0xda, 0x5a, 0x92, 0x42, 0xf3, 0xbb, 0x49, 0x32, 0x4e, 0xf3, 0xa3, 0x1d, 0x58, 0x0c, 0xae, - 0xdd, 0xfb, 0xba, 0xc7, 0x6c, 0x77, 0xb0, 0xa3, 0x9b, 0x3a, 0x13, 0x35, 0xaf, 0xda, 0x6a, 0x8c, - 0x86, 0xab, 0x8b, 0x38, 0x87, 0x8e, 0x73, 0xa5, 0x78, 0x5d, 0x71, 0x48, 0xcf, 0xa3, 0x1d, 0x51, - 0xc3, 0x6a, 0x51, 0x5d, 0xd9, 0x17, 0xab, 0x58, 0x52, 0xd1, 0xa3, 0x44, 0x98, 0xd6, 0xc6, 0x0b, - 0xd3, 0x7a, 0x71, 0x88, 0xa2, 0x23, 0x58, 0x72, 0x5c, 0xbb, 0xeb, 0x52, 0xcf, 0xdb, 0xa6, 0xa4, - 0x63, 0xe8, 0x16, 0x0d, 0x3c, 0x33, 0x23, 0x76, 0xf4, 0xca, 0x68, 0xb8, 0xba, 0xb4, 0x9f, 0xcf, - 0x82, 0x8b, 0x64, 0xd5, 0x3f, 0x4f, 0xc2, 0xe5, 0xf4, 0x1d, 0x87, 0x3e, 0x04, 0x64, 0x1f, 0x7b, - 0xd4, 0xed, 0xd3, 0xce, 0x07, 0x7e, 0xe3, 0xc6, 0xbb, 0x1b, 0x45, 0x74, 0x37, 0x61, 0xde, 0xee, - 0x65, 0x38, 0x70, 0x8e, 0x94, 0xdf, 0x1f, 0xc9, 0x04, 0xa8, 0x08, 0x43, 0x63, 0xfd, 0x51, 0x26, - 0x09, 0x36, 0x61, 0x5e, 0xe6, 0x7e, 0x40, 0x14, 0xc1, 0x1a, 0x3b, 0xf7, 0xa3, 0x24, 0x19, 0xa7, - 0xf9, 0xd1, 0x1d, 0x98, 0x73, 0x79, 0x1c, 0x84, 0x00, 0xd3, 0x02, 0xe0, 0x5b, 0x12, 0x60, 0x0e, - 0xc7, 0x89, 0x38, 0xc9, 0x8b, 0x3e, 0x80, 0x2b, 0xa4, 0x4f, 0x74, 0x83, 0x1c, 0x1b, 0x34, 0x04, - 0x98, 0x14, 0x00, 0x2f, 0x4b, 0x80, 0x2b, 0x9b, 0x69, 0x06, 0x9c, 0x95, 0x41, 0xbb, 0xb0, 0xd0, - 0xb3, 0xb2, 0x50, 0x7e, 0x10, 0xbf, 0x22, 0xa1, 0x16, 0x8e, 0xb2, 0x2c, 0x38, 0x4f, 0x0e, 0x7d, - 0x0a, 0xd0, 0x0e, 0x6e, 0x75, 0xaf, 0x31, 0x25, 0xca, 0xf0, 0x8d, 0x12, 0xc9, 0x16, 0xb6, 0x02, - 0x51, 0x09, 0x0c, 0x97, 0x3c, 0x1c, 0xc3, 0x44, 0xb7, 0xa1, 0xde, 0xb6, 0x0d, 0x43, 0x44, 0xfe, - 0x96, 0xdd, 0xb3, 0x98, 0x08, 0xde, 0x6a, 0x0b, 0xf1, 0xcb, 0x7e, 0x2b, 0x41, 0xc1, 0x29, 0x4e, - 0xf5, 0x8f, 0x4a, 0xfc, 0x9a, 0x09, 0xd2, 0x19, 0xdd, 0x4e, 0xb4, 0x3e, 0xaf, 0xa5, 0x5a, 0x9f, - 0xab, 0x59, 0x89, 0x58, 0xe7, 0xa3, 0xc3, 0x1c, 0x0f, 0x7e, 0xdd, 0xea, 0xfa, 0x07, 0x2e, 0x4b, - 0xe2, 0x5b, 0x67, 0xa6, 0x52, 0xc8, 0x1d, 0xbb, 0x18, 0xaf, 0x88, 0x33, 0x8f, 0x13, 0x71, 0x12, - 0x59, 0xbd, 0x0b, 0xf5, 0x64, 0x1e, 0x26, 0x7a, 0x7a, 0xe5, 0xdc, 0x9e, 0xfe, 0x6b, 0x05, 0x96, - 0x0a, 0xb4, 0x23, 0x03, 0xea, 0x26, 0x79, 0x12, 0x3b, 0xe6, 0x73, 0x7b, 0x63, 0x3e, 0x35, 0x69, - 0xfe, 0xd4, 0xa4, 0x3d, 0xb0, 0xd8, 0x9e, 0x7b, 0xc0, 0x5c, 0xdd, 0xea, 0xfa, 0xe7, 0xb0, 0x9b, - 0xc0, 0xc2, 0x29, 0x6c, 0xf4, 0x09, 0xd4, 0x4c, 0xf2, 0xe4, 0xa0, 0xe7, 0x76, 0xf3, 0xfc, 0x55, - 0x4e, 0x8f, 0xb8, 0x3f, 0x76, 0x25, 0x0a, 0x0e, 0xf1, 0xd4, 0x3f, 0x29, 0xb0, 0x96, 0xd8, 0x25, - 0xaf, 0x15, 0xf4, 0x71, 0xcf, 0x38, 0xa0, 0xd1, 0x89, 0xbf, 0x09, 0x33, 0x0e, 0x71, 0x99, 0x1e, - 0xd6, 0x8b, 0x6a, 0x6b, 0x6e, 0x34, 0x5c, 0x9d, 0xd9, 0x0f, 0x16, 0x71, 0x44, 0xcf, 0xf1, 0x4d, - 0xe5, 0xc5, 0xf9, 0x46, 0xfd, 0x8f, 0x02, 0xd5, 0x83, 0x36, 0x31, 0xe8, 0x05, 0x4c, 0x2a, 0xdb, - 0x89, 0x49, 0x45, 0x2d, 0x8c, 0x59, 0x61, 0x4f, 0xe1, 0x90, 0xb2, 0x93, 0x1a, 0x52, 0xae, 0x9d, - 0x83, 0x73, 0xf6, 0x7c, 0xf2, 0x3e, 0xcc, 0x84, 0xea, 0x12, 0x45, 0x59, 0x39, 0xaf, 0x28, 0xab, - 0xbf, 0xae, 0xc0, 0x6c, 0x4c, 0xc5, 0x78, 0xd2, 0xdc, 0xdd, 0xb1, 0xbe, 0x86, 0x17, 0xae, 0x8d, - 0x32, 0x1b, 0xd1, 0x82, 0x1e, 0xc6, 0x6f, 0x17, 0xa3, 0x66, 0x21, 0xdb, 0xda, 0xdc, 0x85, 0x3a, - 0x23, 0x6e, 0x97, 0xb2, 0x80, 0x26, 0x1c, 0x36, 0x13, 0xcd, 0x2a, 0x87, 0x09, 0x2a, 0x4e, 0x71, - 0x2f, 0xdf, 0x81, 0xb9, 0x84, 0xb2, 0xb1, 0x7a, 0xbe, 0x2f, 0xb8, 0x73, 0xa2, 0x54, 0xb8, 0x80, - 0xe8, 0xfa, 0x30, 0x11, 0x5d, 0xeb, 0xc5, 0xce, 0x8c, 0x25, 0x68, 0x51, 0x8c, 0xe1, 0x54, 0x8c, - 0xbd, 0x51, 0x0a, 0xed, 0xec, 0x48, 0xfb, 0x67, 0x05, 0x16, 0x63, 0xdc, 0xd1, 0x28, 0xfc, 0xbd, - 0xc4, 0x7d, 0xb0, 0x9e, 0xba, 0x0f, 0x1a, 0x79, 0x32, 0x2f, 0x6c, 0x16, 0xce, 0x9f, 0x4f, 0x27, - 0xfe, 0x1f, 0xe7, 0xd3, 0x3f, 0x28, 0x30, 0x1f, 0xf3, 0xdd, 0x05, 0x0c, 0xa8, 0x0f, 0x92, 0x03, - 0xea, 0xb5, 0x32, 0x41, 0x53, 0x30, 0xa1, 0xde, 0x86, 0x85, 0x18, 0xd3, 0x9e, 0xdb, 0xd1, 0x2d, - 0x62, 0x78, 0xe8, 0x55, 0xa8, 0x7a, 0x8c, 0xb8, 0x2c, 0xb8, 0x44, 0x02, 0xd9, 0x03, 0xbe, 0x88, - 0x7d, 0x9a, 0xfa, 0x2f, 0x05, 0x9a, 0x31, 0xe1, 0x7d, 0xea, 0x7a, 0xba, 0xc7, 0xa8, 0xc5, 0x1e, - 0xda, 0x46, 0xcf, 0xa4, 0x5b, 0x06, 0xd1, 0x4d, 0x4c, 0xf9, 0x82, 0x6e, 0x5b, 0xfb, 0xb6, 0xa1, - 0xb7, 0x07, 0x88, 0xc0, 0xec, 0xe7, 0x27, 0xd4, 0xda, 0xa6, 0x06, 0x65, 0xb4, 0x23, 0x43, 0xf1, - 0x07, 0x12, 0x7e, 0xf6, 0x51, 0x44, 0x7a, 0x3e, 0x5c, 0x5d, 0x2f, 0x83, 0x28, 0x22, 0x34, 0x8e, - 0x89, 0x7e, 0x06, 0xc0, 0x3f, 0x45, 0x2d, 0xeb, 0xc8, 0x60, 0xbd, 0x1b, 0x64, 0xf4, 0xa3, 0x90, - 0x32, 0x96, 0x82, 0x18, 0xa2, 0xfa, 0xdb, 0x5a, 0xe2, 0xbc, 0xbf, 0xf1, 0x63, 0xe6, 0xcf, 0x61, - 0xb1, 0x1f, 0x79, 0x27, 0x60, 0xe0, 0x6d, 0xf9, 0x44, 0xfa, 0xe9, 0x2e, 0x84, 0xcf, 0xf3, 0x6b, - 0xeb, 0xdb, 0x52, 0xc9, 0xe2, 0xc3, 0x1c, 0x38, 0x9c, 0xab, 0x04, 0x7d, 0x17, 0x66, 0xf9, 0x48, - 0xa3, 0xb7, 0xe9, 0x47, 0xc4, 0x0c, 0x72, 0x71, 0x21, 0x88, 0x97, 0x83, 0x88, 0x84, 0xe3, 0x7c, - 0xe8, 0x04, 0x16, 0x1c, 0xbb, 0xb3, 0x4b, 0x2c, 0xd2, 0xa5, 0xbc, 0x11, 0xf4, 0x8f, 0x52, 0xcc, - 0x9e, 0x33, 0xad, 0x77, 0x83, 0xf6, 0x7f, 0x3f, 0xcb, 0xf2, 0x9c, 0x0f, 0x71, 0xd9, 0x65, 0x11, - 0x04, 0x79, 0x90, 0xc8, 0x85, 0x7a, 0x4f, 0xf6, 0x63, 0x72, 0x14, 0xf7, 0x1f, 0xd9, 0x36, 0xca, - 0x24, 0xe5, 0x51, 0x42, 0x32, 0xba, 0x30, 0x93, 0xeb, 0x38, 0xa5, 0xa1, 0x70, 0xb4, 0xae, 0xfd, - 0x4f, 0xa3, 0x75, 0xce, 0xac, 0x3f, 0x33, 0xe6, 0xac, 0xff, 0x17, 0x05, 0xae, 0x39, 0x25, 0x72, - 0xa9, 0x01, 0xc2, 0x37, 0xf7, 0xcb, 0xf8, 0xa6, 0x4c, 0x6e, 0xb6, 0xd6, 0x47, 0xc3, 0xd5, 0x6b, - 0x65, 0x38, 0x71, 0x29, 0xfb, 0xd0, 0x43, 0xa8, 0xd9, 0xb2, 0x06, 0x36, 0x66, 0x85, 0xad, 0x37, - 0xca, 0xd8, 0x1a, 0xd4, 0x4d, 0x3f, 0x2d, 0x83, 0x2f, 0x1c, 0x62, 0xa9, 0xbf, 0xab, 0xc2, 0x95, - 0xcc, 0x0d, 0x8e, 0x7e, 0x78, 0xc6, 0x9c, 0x7f, 0xf5, 0x85, 0xcd, 0xf8, 0x99, 0x01, 0x7d, 0x62, - 0x8c, 0x01, 0x7d, 0x13, 0xe6, 0xdb, 0x3d, 0xd7, 0xa5, 0x16, 0x4b, 0x8d, 0xe7, 0x61, 0xb0, 0x6c, - 0x25, 0xc9, 0x38, 0xcd, 0x9f, 0xf7, 0xc6, 0x50, 0x1d, 0xf3, 0x8d, 0x21, 0x6e, 0x85, 0x9c, 0x13, - 0xfd, 0xd4, 0xce, 0x5a, 0x21, 0xc7, 0xc5, 0x34, 0x3f, 0x6f, 0x5a, 0x7d, 0xd4, 0x10, 0x61, 0x3a, - 0xd9, 0xb4, 0x1e, 0x25, 0xa8, 0x38, 0xc5, 0x9d, 0x33, 0xaf, 0xcf, 0x94, 0x9d, 0xd7, 0x11, 0x49, - 0xbc, 0x26, 0x80, 0xa8, 0xa3, 0x37, 0xcb, 0xc4, 0x59, 0xf9, 0xe7, 0x84, 0xdc, 0x87, 0x94, 0xd9, - 0xf1, 0x1f, 0x52, 0xd4, 0xbf, 0x2a, 0xf0, 0x72, 0x61, 0xc5, 0x42, 0x9b, 0x89, 0x96, 0xf2, 0x66, - 0xaa, 0xa5, 0xfc, 0x4e, 0xa1, 0x60, 0xac, 0xaf, 0x74, 0xf3, 0x5f, 0x1a, 0xde, 0x2f, 0xf7, 0xd2, - 0x90, 0x33, 0x05, 0x9f, 0xff, 0xe4, 0xd0, 0xfa, 0xfe, 0xd3, 0x67, 0x2b, 0x97, 0xbe, 0x7c, 0xb6, - 0x72, 0xe9, 0xab, 0x67, 0x2b, 0x97, 0x7e, 0x31, 0x5a, 0x51, 0x9e, 0x8e, 0x56, 0x94, 0x2f, 0x47, - 0x2b, 0xca, 0x57, 0xa3, 0x15, 0xe5, 0xef, 0xa3, 0x15, 0xe5, 0x57, 0x5f, 0xaf, 0x5c, 0xfa, 0x64, - 0xa9, 0xe0, 0xdf, 0xe8, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xc9, 0xe6, 0x8c, 0xa7, 0x1e, - 0x00, 0x00, + // 2041 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x59, 0xdd, 0x6f, 0x1b, 0xc7, + 0x11, 0xd7, 0x51, 0xa2, 0x44, 0x8d, 0x22, 0xca, 0x5e, 0xa9, 0x16, 0xa3, 0xb4, 0x92, 0x70, 0x31, + 0x62, 0x25, 0xb1, 0x8f, 0xb1, 0x92, 0x06, 0x89, 0xdd, 0xba, 0x10, 0x25, 0x37, 0x56, 0x20, 0x45, + 0xca, 0x4a, 0xb2, 0xd1, 0xf4, 0x03, 0x59, 0x91, 0x6b, 0xea, 0xa2, 0xfb, 0xc2, 0xdd, 0x52, 0x31, + 0xd1, 0x97, 0xfe, 0x01, 0x2d, 0xd2, 0xe7, 0xfe, 0x15, 0xed, 0x53, 0x8b, 0x16, 0x7d, 0x2d, 0xfc, + 0x18, 0xf4, 0xa5, 0x79, 0x22, 0x6a, 0xe6, 0xb5, 0x7d, 0x6b, 0x5f, 0x0c, 0x14, 0x28, 0x76, 0x6f, + 0xef, 0xfb, 0x4e, 0x3a, 0x16, 0xb0, 0x80, 0xe6, 0x8d, 0xb7, 0x33, 0xf3, 0x9b, 0xd9, 0xd9, 0x99, + 0xd9, 0x99, 0x25, 0xdc, 0x38, 0x7d, 0xcf, 0xd3, 0x74, 0xbb, 0x49, 0x1c, 0xbd, 0x49, 0x1c, 0xc7, + 0x6b, 0x9e, 0xdd, 0x3e, 0xa6, 0x8c, 0xdc, 0x6e, 0x76, 0xa9, 0x45, 0x5d, 0xc2, 0x68, 0x47, 0x73, + 0x5c, 0x9b, 0xd9, 0x68, 0xd1, 0x67, 0xd4, 0x88, 0xa3, 0x6b, 0x9c, 0x51, 0x93, 0x8c, 0x4b, 0xb7, + 0xba, 0x3a, 0x3b, 0xe9, 0x1d, 0x6b, 0x6d, 0xdb, 0x6c, 0x76, 0xed, 0xae, 0xdd, 0x14, 0xfc, 0xc7, + 0xbd, 0xc7, 0xe2, 0x4b, 0x7c, 0x88, 0x5f, 0x3e, 0xce, 0x92, 0x1a, 0x53, 0xd8, 0xb6, 0x5d, 0xda, + 0x3c, 0xcb, 0xe8, 0x5a, 0x7a, 0x27, 0xe2, 0x31, 0x49, 0xfb, 0x44, 0xb7, 0xa8, 0xdb, 0x6f, 0x3a, + 0xa7, 0x5d, 0xbe, 0xe0, 0x35, 0x4d, 0xca, 0x48, 0x9e, 0x54, 0xb3, 0x48, 0xca, 0xed, 0x59, 0x4c, + 0x37, 0x69, 0x46, 0xe0, 0xdd, 0x8b, 0x04, 0xbc, 0xf6, 0x09, 0x35, 0x49, 0x46, 0xee, 0xed, 0x22, + 0xb9, 0x1e, 0xd3, 0x8d, 0xa6, 0x6e, 0x31, 0x8f, 0xb9, 0x69, 0x21, 0xf5, 0xdf, 0x0a, 0xa0, 0x4d, + 0xdb, 0x62, 0xae, 0x6d, 0x18, 0xd4, 0xc5, 0xf4, 0x4c, 0xf7, 0x74, 0xdb, 0x42, 0x9f, 0x42, 0x8d, + 0xef, 0xa7, 0x43, 0x18, 0x69, 0x28, 0xab, 0xca, 0xda, 0xcc, 0xfa, 0x5b, 0x5a, 0xe4, 0xe9, 0x10, + 0x5e, 0x73, 0x4e, 0xbb, 0x7c, 0xc1, 0xd3, 0x38, 0xb7, 0x76, 0x76, 0x5b, 0xdb, 0x3b, 0xfe, 0x8c, + 0xb6, 0xd9, 0x2e, 0x65, 0xa4, 0x85, 0x9e, 0x0e, 0x56, 0xc6, 0x86, 0x83, 0x15, 0x88, 0xd6, 0x70, + 0x88, 0x8a, 0xf6, 0x60, 0x42, 0xa0, 0x57, 0x04, 0xfa, 0xad, 0x42, 0x74, 0xb9, 0x69, 0x0d, 0x93, + 0xcf, 0xef, 0x3f, 0x61, 0xd4, 0xe2, 0xe6, 0xb5, 0x5e, 0x92, 0xd0, 0x13, 0x5b, 0x84, 0x11, 0x2c, + 0x80, 0xd0, 0x4d, 0xa8, 0xb9, 0xd2, 0xfc, 0xc6, 0xf8, 0xaa, 0xb2, 0x36, 0xde, 0xba, 0x22, 0xb9, + 0x6a, 0xc1, 0xb6, 0x70, 0xc8, 0xa1, 0x3e, 0x55, 0xe0, 0x5a, 0x76, 0xdf, 0x3b, 0xba, 0xc7, 0xd0, + 0x4f, 0x32, 0x7b, 0xd7, 0xca, 0xed, 0x9d, 0x4b, 0x8b, 0x9d, 0x87, 0x8a, 0x83, 0x95, 0xd8, 0xbe, + 0xf7, 0xa1, 0xaa, 0x33, 0x6a, 0x7a, 0x8d, 0xca, 0xea, 0xf8, 0xda, 0xcc, 0xfa, 0x9b, 0x5a, 0x41, + 0x00, 0x6b, 0x59, 0xeb, 0x5a, 0xb3, 0x12, 0xb7, 0xba, 0xcd, 0x11, 0xb0, 0x0f, 0xa4, 0xfe, 0xb2, + 0x02, 0xb0, 0x45, 0x1d, 0xc3, 0xee, 0x9b, 0xd4, 0x62, 0x97, 0x70, 0x74, 0xdb, 0x30, 0xe1, 0x39, + 0xb4, 0x2d, 0x8f, 0xee, 0x46, 0xe1, 0x0e, 0x22, 0xa3, 0x0e, 0x1c, 0xda, 0x8e, 0x0e, 0x8d, 0x7f, + 0x61, 0x01, 0x81, 0x3e, 0x86, 0x49, 0x8f, 0x11, 0xd6, 0xf3, 0xc4, 0x91, 0xcd, 0xac, 0xbf, 0x5e, + 0x06, 0x4c, 0x08, 0xb4, 0xea, 0x12, 0x6e, 0xd2, 0xff, 0xc6, 0x12, 0x48, 0xfd, 0xdb, 0x38, 0xcc, + 0x47, 0xcc, 0x9b, 0xb6, 0xd5, 0xd1, 0x19, 0x0f, 0xe9, 0xbb, 0x30, 0xc1, 0xfa, 0x0e, 0x15, 0x3e, + 0x99, 0x6e, 0xdd, 0x08, 0x8c, 0x39, 0xec, 0x3b, 0xf4, 0xf9, 0x60, 0x65, 0x31, 0x47, 0x84, 0x93, + 0xb0, 0x10, 0x42, 0x3b, 0xa1, 0x9d, 0x15, 0x21, 0xfe, 0x4e, 0x52, 0xf9, 0xf3, 0xc1, 0x4a, 0x4e, + 0x01, 0xd1, 0x42, 0xa4, 0xa4, 0x89, 0xe8, 0x33, 0xa8, 0x1b, 0xc4, 0x63, 0x47, 0x4e, 0x87, 0x30, + 0x7a, 0xa8, 0x9b, 0xb4, 0x31, 0x29, 0x76, 0xff, 0x46, 0xb9, 0x83, 0xe2, 0x12, 0xad, 0x6b, 0xd2, + 0x82, 0xfa, 0x4e, 0x02, 0x09, 0xa7, 0x90, 0xd1, 0x19, 0x20, 0xbe, 0x72, 0xe8, 0x12, 0xcb, 0xf3, + 0x77, 0xc5, 0xf5, 0x4d, 0x8d, 0xac, 0x6f, 0x49, 0xea, 0x43, 0x3b, 0x19, 0x34, 0x9c, 0xa3, 0x01, + 0xbd, 0x06, 0x93, 0x2e, 0x25, 0x9e, 0x6d, 0x35, 0x26, 0x84, 0xc7, 0xc2, 0xe3, 0xc2, 0x62, 0x15, + 0x4b, 0x2a, 0x7a, 0x1d, 0xa6, 0x4c, 0xea, 0x79, 0xa4, 0x4b, 0x1b, 0x55, 0xc1, 0x38, 0x27, 0x19, + 0xa7, 0x76, 0xfd, 0x65, 0x1c, 0xd0, 0xd5, 0x3f, 0x28, 0x50, 0x8f, 0x8e, 0xe9, 0x12, 0x72, 0xf5, + 0x41, 0x32, 0x57, 0x5f, 0x2d, 0x11, 0x9c, 0x05, 0x39, 0xfa, 0x8f, 0x0a, 0xa0, 0x88, 0x09, 0xdb, + 0x86, 0x71, 0x4c, 0xda, 0xa7, 0x68, 0x15, 0x26, 0x2c, 0x62, 0x06, 0x31, 0x19, 0x26, 0xc8, 0x47, + 0xc4, 0xa4, 0x58, 0x50, 0xd0, 0x17, 0x0a, 0xa0, 0x9e, 0x38, 0xcd, 0xce, 0x86, 0x65, 0xd9, 0x8c, + 0x70, 0x07, 0x07, 0x06, 0x6d, 0x96, 0x30, 0x28, 0xd0, 0xa5, 0x1d, 0x65, 0x50, 0xee, 0x5b, 0xcc, + 0xed, 0x47, 0x07, 0x9b, 0x65, 0xc0, 0x39, 0xaa, 0xd1, 0x8f, 0x01, 0x5c, 0x89, 0x79, 0x68, 0xcb, + 0xb4, 0x2d, 0xae, 0x01, 0x81, 0xfa, 0x4d, 0xdb, 0x7a, 0xac, 0x77, 0xa3, 0xc2, 0x82, 0x43, 0x08, + 0x1c, 0x83, 0x5b, 0xba, 0x0f, 0x8b, 0x05, 0x76, 0xa2, 0x2b, 0x30, 0x7e, 0x4a, 0xfb, 0xbe, 0xab, + 0x30, 0xff, 0x89, 0x16, 0xa0, 0x7a, 0x46, 0x8c, 0x1e, 0xf5, 0x73, 0x12, 0xfb, 0x1f, 0x77, 0x2a, + 0xef, 0x29, 0xea, 0x6f, 0xab, 0xf1, 0x48, 0xe1, 0xf5, 0x06, 0xad, 0xf1, 0xeb, 0xc1, 0x31, 0xf4, + 0x36, 0xf1, 0x04, 0x46, 0xb5, 0xf5, 0x92, 0x7f, 0x35, 0xf8, 0x6b, 0x38, 0xa4, 0xa2, 0x9f, 0x42, + 0xcd, 0xa3, 0x06, 0x6d, 0x33, 0xdb, 0x95, 0x25, 0xee, 0xed, 0x92, 0x31, 0x45, 0x8e, 0xa9, 0x71, + 0x20, 0x45, 0x7d, 0xf8, 0xe0, 0x0b, 0x87, 0x90, 0xe8, 0x63, 0xa8, 0x31, 0x6a, 0x3a, 0x06, 0x61, + 0x54, 0x7a, 0x2f, 0x11, 0x57, 0xbc, 0x76, 0x70, 0xb0, 0x7d, 0xbb, 0x73, 0x28, 0xd9, 0x44, 0xf5, + 0x0c, 0xe3, 0x34, 0x58, 0xc5, 0x21, 0x0c, 0xfa, 0x11, 0xd4, 0x3c, 0xc6, 0x6f, 0xf5, 0x6e, 0x5f, + 0x64, 0xdb, 0x79, 0xd7, 0x4a, 0xbc, 0x8e, 0xfa, 0x22, 0x11, 0x74, 0xb0, 0x82, 0x43, 0x38, 0xb4, + 0x01, 0x73, 0xa6, 0x6e, 0x61, 0x4a, 0x3a, 0xfd, 0x03, 0xda, 0xb6, 0xad, 0x8e, 0x27, 0xd2, 0xb4, + 0xda, 0x5a, 0x94, 0x42, 0x73, 0xbb, 0x49, 0x32, 0x4e, 0xf3, 0xa3, 0x1d, 0x58, 0x08, 0xae, 0xdd, + 0x07, 0xba, 0xc7, 0x6c, 0xb7, 0xbf, 0xa3, 0x9b, 0x3a, 0x13, 0x35, 0xaf, 0xda, 0x6a, 0x0c, 0x07, + 0x2b, 0x0b, 0x38, 0x87, 0x8e, 0x73, 0xa5, 0x78, 0x5d, 0x71, 0x48, 0xcf, 0xa3, 0x1d, 0x51, 0xc3, + 0x6a, 0x51, 0x5d, 0xd9, 0x17, 0xab, 0x58, 0x52, 0xd1, 0xa3, 0x44, 0x98, 0xd6, 0x46, 0x0b, 0xd3, + 0x7a, 0x71, 0x88, 0xa2, 0x23, 0x58, 0x74, 0x5c, 0xbb, 0xeb, 0x52, 0xcf, 0xdb, 0xa2, 0xa4, 0x63, + 0xe8, 0x16, 0x0d, 0x3c, 0x33, 0x2d, 0x76, 0xf4, 0xca, 0x70, 0xb0, 0xb2, 0xb8, 0x9f, 0xcf, 0x82, + 0x8b, 0x64, 0xd5, 0x5f, 0x55, 0xe1, 0x4a, 0xfa, 0x8e, 0x43, 0x1f, 0x02, 0xb2, 0x8f, 0x3d, 0xea, + 0x9e, 0xd1, 0xce, 0x07, 0x7e, 0xe3, 0xc6, 0xbb, 0x1b, 0x45, 0x74, 0x37, 0x61, 0xde, 0xee, 0x65, + 0x38, 0x70, 0x8e, 0x94, 0xdf, 0x1f, 0xc9, 0x04, 0xa8, 0x08, 0x43, 0x63, 0xfd, 0x51, 0x26, 0x09, + 0x36, 0x60, 0x4e, 0xe6, 0x7e, 0x40, 0x14, 0xc1, 0x1a, 0x3b, 0xf7, 0xa3, 0x24, 0x19, 0xa7, 0xf9, + 0xd1, 0x5d, 0x98, 0x75, 0x79, 0x1c, 0x84, 0x00, 0x53, 0x02, 0xe0, 0x5b, 0x12, 0x60, 0x16, 0xc7, + 0x89, 0x38, 0xc9, 0x8b, 0x3e, 0x80, 0xab, 0xe4, 0x8c, 0xe8, 0x06, 0x39, 0x36, 0x68, 0x08, 0x30, + 0x21, 0x00, 0x5e, 0x96, 0x00, 0x57, 0x37, 0xd2, 0x0c, 0x38, 0x2b, 0x83, 0x76, 0x61, 0xbe, 0x67, + 0x65, 0xa1, 0xfc, 0x20, 0x7e, 0x45, 0x42, 0xcd, 0x1f, 0x65, 0x59, 0x70, 0x9e, 0x1c, 0xda, 0x86, + 0x79, 0x46, 0x5d, 0x53, 0xb7, 0x08, 0xd3, 0xad, 0x6e, 0x08, 0xe7, 0x9f, 0xfc, 0x22, 0x87, 0x3a, + 0xcc, 0x92, 0x71, 0x9e, 0x0c, 0xfa, 0x14, 0xa0, 0x1d, 0x34, 0x08, 0x5e, 0x63, 0x52, 0x54, 0xf4, + 0x9b, 0x25, 0xf2, 0x36, 0xec, 0x2a, 0xa2, 0x6a, 0x1a, 0x2e, 0x79, 0x38, 0x86, 0x89, 0xee, 0x40, + 0xbd, 0x6d, 0x1b, 0x86, 0x48, 0xa2, 0x4d, 0xbb, 0x67, 0x31, 0x91, 0x07, 0xd5, 0x16, 0xe2, 0x7d, + 0xc3, 0x66, 0x82, 0x82, 0x53, 0x9c, 0xea, 0x9f, 0x94, 0xf8, 0x8d, 0x15, 0x54, 0x06, 0x74, 0x27, + 0xd1, 0x45, 0xbd, 0x96, 0xea, 0xa2, 0xae, 0x65, 0x25, 0x62, 0x4d, 0x94, 0x0e, 0xb3, 0x3c, 0x8f, + 0x74, 0xab, 0xeb, 0xc7, 0x8e, 0xac, 0xae, 0x6f, 0x9d, 0x9b, 0x95, 0x21, 0x77, 0xec, 0x8e, 0xbd, + 0x2a, 0xc2, 0x27, 0x4e, 0xc4, 0x49, 0x64, 0xf5, 0x1e, 0xd4, 0x93, 0x29, 0x9d, 0x18, 0x0f, 0x94, + 0x0b, 0xc7, 0x83, 0xaf, 0x15, 0x58, 0x2c, 0xd0, 0x8e, 0x0c, 0xa8, 0x9b, 0xe4, 0x49, 0x2c, 0x62, + 0x2e, 0x6c, 0xb3, 0xf9, 0x00, 0xa6, 0xf9, 0x03, 0x98, 0xb6, 0x6d, 0xb1, 0x3d, 0xf7, 0x80, 0xb9, + 0xba, 0xd5, 0xf5, 0xcf, 0x61, 0x37, 0x81, 0x85, 0x53, 0xd8, 0xe8, 0x13, 0xa8, 0x99, 0xe4, 0xc9, + 0x41, 0xcf, 0xed, 0xe6, 0xf9, 0xab, 0x9c, 0x1e, 0x71, 0x15, 0xed, 0x4a, 0x14, 0x1c, 0xe2, 0xa9, + 0x7f, 0x56, 0x60, 0x35, 0xb1, 0x4b, 0x5e, 0x76, 0xe8, 0xe3, 0x9e, 0x71, 0x40, 0xa3, 0x13, 0x7f, + 0x13, 0xa6, 0x1d, 0xe2, 0x32, 0x3d, 0x2c, 0x3d, 0xd5, 0xd6, 0xec, 0x70, 0xb0, 0x32, 0xbd, 0x1f, + 0x2c, 0xe2, 0x88, 0x9e, 0xe3, 0x9b, 0xca, 0x8b, 0xf3, 0x8d, 0xfa, 0x1f, 0x05, 0xaa, 0x07, 0x6d, + 0x62, 0xd0, 0x4b, 0x18, 0x7a, 0xb6, 0x12, 0x43, 0x8f, 0x5a, 0x18, 0xb3, 0xc2, 0x9e, 0xc2, 0x79, + 0x67, 0x27, 0x35, 0xef, 0x5c, 0xbf, 0x00, 0xe7, 0xfc, 0x51, 0xe7, 0x7d, 0x98, 0x0e, 0xd5, 0x25, + 0xea, 0xbb, 0x72, 0x51, 0x7d, 0x57, 0x7f, 0x53, 0x81, 0x99, 0x98, 0x8a, 0xd1, 0xa4, 0xb9, 0xbb, + 0x63, 0x2d, 0x12, 0x2f, 0x5c, 0xeb, 0x65, 0x36, 0xa2, 0x05, 0xed, 0x90, 0xdf, 0x79, 0x46, 0x7d, + 0x47, 0xb6, 0x4b, 0xba, 0x07, 0x75, 0x46, 0xdc, 0x2e, 0x65, 0x01, 0x4d, 0x38, 0x6c, 0x3a, 0x1a, + 0x7b, 0x0e, 0x13, 0x54, 0x9c, 0xe2, 0x5e, 0xba, 0x0b, 0xb3, 0x09, 0x65, 0x23, 0xb5, 0x8f, 0x5f, + 0x70, 0xe7, 0x44, 0xa9, 0x70, 0x09, 0xd1, 0xf5, 0x61, 0x22, 0xba, 0xd6, 0x8a, 0x9d, 0x19, 0x4b, + 0xd0, 0xa2, 0x18, 0xc3, 0xa9, 0x18, 0x7b, 0xa3, 0x14, 0xda, 0xf9, 0x91, 0xf6, 0xcf, 0x0a, 0x2c, + 0xc4, 0xb8, 0xa3, 0xa9, 0xfa, 0x7b, 0x89, 0xfb, 0x60, 0x2d, 0x75, 0x1f, 0x34, 0xf2, 0x64, 0x5e, + 0xd8, 0x58, 0x9d, 0x3f, 0xea, 0x8e, 0xff, 0x3f, 0x8e, 0xba, 0x7f, 0x54, 0x60, 0x2e, 0xe6, 0xbb, + 0x4b, 0x98, 0x75, 0xb7, 0x93, 0xb3, 0xee, 0xf5, 0x32, 0x41, 0x53, 0x30, 0xec, 0xde, 0x81, 0xf9, + 0x18, 0xd3, 0x9e, 0xdb, 0xd1, 0x2d, 0x62, 0x78, 0xe8, 0x55, 0xa8, 0x7a, 0x8c, 0xb8, 0x2c, 0xb8, + 0x44, 0x02, 0xd9, 0x03, 0xbe, 0x88, 0x7d, 0x9a, 0xfa, 0x2f, 0x05, 0x9a, 0x31, 0xe1, 0x7d, 0xea, + 0x7a, 0xba, 0xc7, 0xa8, 0xc5, 0x1e, 0xda, 0x46, 0xcf, 0xa4, 0x9b, 0x06, 0xd1, 0x4d, 0x4c, 0xf9, + 0x82, 0x6e, 0x5b, 0xfb, 0xb6, 0xa1, 0xb7, 0xfb, 0x88, 0xc0, 0xcc, 0xe7, 0x27, 0xd4, 0xda, 0xa2, + 0x06, 0x65, 0xb4, 0x23, 0x43, 0xf1, 0x07, 0x12, 0x7e, 0xe6, 0x51, 0x44, 0x7a, 0x3e, 0x58, 0x59, + 0x2b, 0x83, 0x28, 0x22, 0x34, 0x8e, 0x89, 0x7e, 0x06, 0xc0, 0x3f, 0x45, 0x2d, 0xeb, 0xc8, 0x60, + 0xbd, 0x17, 0x64, 0xf4, 0xa3, 0x90, 0x32, 0x92, 0x82, 0x18, 0xa2, 0xfa, 0xbb, 0x5a, 0xe2, 0xbc, + 0xbf, 0xf1, 0x13, 0xeb, 0xcf, 0x61, 0xe1, 0x2c, 0xf2, 0x4e, 0xc0, 0xc0, 0x3b, 0xfc, 0xf1, 0xf4, + 0x2b, 0x60, 0x08, 0x9f, 0xe7, 0xd7, 0xd6, 0xb7, 0xa5, 0x92, 0x85, 0x87, 0x39, 0x70, 0x38, 0x57, + 0x09, 0xfa, 0x2e, 0xcc, 0xf0, 0xe9, 0x48, 0x6f, 0xd3, 0x8f, 0x88, 0x19, 0xe4, 0xe2, 0x7c, 0x10, + 0x2f, 0x07, 0x11, 0x09, 0xc7, 0xf9, 0xd0, 0x09, 0xcc, 0x3b, 0x76, 0x67, 0x97, 0x58, 0xa4, 0x4b, + 0x79, 0x23, 0xe8, 0x1f, 0xa5, 0x18, 0x63, 0xa7, 0x5b, 0xef, 0x06, 0x93, 0xc4, 0x7e, 0x96, 0xe5, + 0x39, 0x9f, 0x07, 0xb3, 0xcb, 0x22, 0x08, 0xf2, 0x20, 0x91, 0x0b, 0xf5, 0x9e, 0xec, 0xc7, 0xe4, + 0x54, 0xef, 0xbf, 0xd7, 0xad, 0x97, 0x49, 0xca, 0xa3, 0x84, 0x64, 0x74, 0x61, 0x26, 0xd7, 0x71, + 0x4a, 0x43, 0xe1, 0x94, 0x5e, 0xfb, 0x9f, 0xa6, 0xf4, 0x9c, 0x67, 0x83, 0xe9, 0x11, 0x9f, 0x0d, + 0xfe, 0xa2, 0xc0, 0x75, 0xa7, 0x44, 0x2e, 0x35, 0x40, 0xf8, 0xe6, 0x41, 0x19, 0xdf, 0x94, 0xc9, + 0xcd, 0xd6, 0xda, 0x70, 0xb0, 0x72, 0xbd, 0x0c, 0x27, 0x2e, 0x65, 0x1f, 0x7a, 0x08, 0x35, 0x5b, + 0xd6, 0xc0, 0xc6, 0x8c, 0xb0, 0xf5, 0x66, 0x19, 0x5b, 0x83, 0xba, 0xe9, 0xa7, 0x65, 0xf0, 0x85, + 0x43, 0x2c, 0xf5, 0xf7, 0x55, 0xb8, 0x9a, 0xb9, 0xc1, 0xd1, 0x0f, 0xcf, 0x79, 0x32, 0xb8, 0xf6, + 0xc2, 0x9e, 0x0b, 0x32, 0xb3, 0xfe, 0xf8, 0x08, 0xb3, 0xfe, 0x06, 0xcc, 0xb5, 0x7b, 0xae, 0x4b, + 0x2d, 0x96, 0x9a, 0xf4, 0xc3, 0x60, 0xd9, 0x4c, 0x92, 0x71, 0x9a, 0x3f, 0xef, 0xb9, 0xa2, 0x3a, + 0xe2, 0x73, 0x45, 0xdc, 0x0a, 0x39, 0x27, 0xfa, 0xa9, 0x9d, 0xb5, 0x42, 0x8e, 0x8b, 0x69, 0x7e, + 0xde, 0xb4, 0xfa, 0xa8, 0x21, 0xc2, 0x54, 0xb2, 0x69, 0x3d, 0x4a, 0x50, 0x71, 0x8a, 0x3b, 0x67, + 0x5e, 0x9f, 0x2e, 0x3b, 0xaf, 0x23, 0x92, 0x78, 0x4d, 0x00, 0x51, 0x47, 0x6f, 0x95, 0x89, 0xb3, + 0xf2, 0xcf, 0x09, 0xb9, 0x6f, 0x32, 0x33, 0xa3, 0xbf, 0xc9, 0xa8, 0x7f, 0x55, 0xe0, 0xe5, 0xc2, + 0x8a, 0x85, 0x36, 0x12, 0x2d, 0xe5, 0xad, 0x54, 0x4b, 0xf9, 0x9d, 0x42, 0xc1, 0x58, 0x5f, 0xe9, + 0xe6, 0xbf, 0x34, 0xbc, 0x5f, 0xee, 0xa5, 0x21, 0x67, 0x0a, 0xbe, 0xf8, 0xc9, 0xa1, 0xf5, 0xfd, + 0xa7, 0xcf, 0x96, 0xc7, 0xbe, 0x7c, 0xb6, 0x3c, 0xf6, 0xd5, 0xb3, 0xe5, 0xb1, 0x5f, 0x0c, 0x97, + 0x95, 0xa7, 0xc3, 0x65, 0xe5, 0xcb, 0xe1, 0xb2, 0xf2, 0xd5, 0x70, 0x59, 0xf9, 0xfb, 0x70, 0x59, + 0xf9, 0xf5, 0xd7, 0xcb, 0x63, 0x9f, 0x2c, 0x16, 0xfc, 0xb1, 0xfd, 0xdf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x40, 0xa4, 0x4b, 0xb9, 0xf2, 0x1e, 0x00, 0x00, } func (m *ControllerRevision) Marshal() (dAtA []byte, err error) { @@ -1289,6 +1290,11 @@ func (m *DeploymentStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x48 + } if m.CollisionCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) i-- @@ -2225,6 +2231,9 @@ func (m *DeploymentStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -2627,6 +2636,7 @@ func (this *DeploymentStatus) String() string { `Conditions:` + repeatedStringForConditions + `,`, `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -4337,6 +4347,26 @@ func (m *DeploymentStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.proto index 46d7bfdf92..0601efc3c4 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/generated.proto @@ -179,33 +179,40 @@ message DeploymentSpec { // DeploymentStatus is the most recently observed status of the Deployment. message DeploymentStatus { - // observedGeneration is the generation observed by the deployment controller. + // The generation observed by the deployment controller. // +optional optional int64 observedGeneration = 1; - // replicas is the total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional optional int32 replicas = 2; - // updatedReplicas is the total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional optional int32 updatedReplicas = 3; - // readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional optional int32 readyReplicas = 7; - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional optional int32 availableReplicas = 4; - // unavailableReplicas is the total number of unavailable pods targeted by this deployment. This is the total number of + // Total number of unavailable pods targeted by this deployment. This is the total number of // pods that are still required for the deployment to have 100% available capacity. They may // either be pods that are running but not yet available or pods that still have not been created. // +optional optional int32 unavailableReplicas = 5; - // Conditions represent the latest available observations of a deployment's current state. + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 9; + + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge // +listType=map @@ -455,6 +462,7 @@ message StatefulSetSpec { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional optional string serviceName = 5; // podManagementPolicy controls how pods are created during initial scale up, diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/types.go b/go-controller/vendor/k8s.io/api/apps/v1beta1/types.go index bc48519578..5530c990da 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/types.go @@ -259,6 +259,7 @@ type StatefulSetSpec struct { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` // podManagementPolicy controls how pods are created during initial scale up, @@ -548,33 +549,40 @@ type RollingUpdateDeployment struct { // DeploymentStatus is the most recently observed status of the Deployment. type DeploymentStatus struct { - // observedGeneration is the generation observed by the deployment controller. + // The generation observed by the deployment controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // replicas is the total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` - // updatedReplicas is the total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` - // readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` - // unavailableReplicas is the total number of unavailable pods targeted by this deployment. This is the total number of + // Total number of unavailable pods targeted by this deployment. This is the total number of // pods that are still required for the deployment to have 100% available capacity. They may // either be pods that are running but not yet available or pods that still have not been created. // +optional UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` - // Conditions represent the latest available observations of a deployment's current state. + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,9,opt,name=terminatingReplicas"` + + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge // +listType=map diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go index 1381d75dc0..02ea5f7f26 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go @@ -113,13 +113,14 @@ func (DeploymentSpec) SwaggerDoc() map[string]string { var map_DeploymentStatus = map[string]string{ "": "DeploymentStatus is the most recently observed status of the Deployment.", - "observedGeneration": "observedGeneration is the generation observed by the deployment controller.", - "replicas": "replicas is the total number of non-terminated pods targeted by this deployment (their labels match the selector).", - "updatedReplicas": "updatedReplicas is the total number of non-terminated pods targeted by this deployment that have the desired template spec.", - "readyReplicas": "readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition.", - "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", - "unavailableReplicas": "unavailableReplicas is the total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", - "conditions": "Conditions represent the latest available observations of a deployment's current state.", + "observedGeneration": "The generation observed by the deployment controller.", + "replicas": "Total number of non-terminating pods targeted by this deployment (their labels match the selector).", + "updatedReplicas": "Total number of non-terminating pods targeted by this deployment that have the desired template spec.", + "readyReplicas": "Total number of non-terminating pods targeted by this Deployment with a Ready Condition.", + "availableReplicas": "Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment.", + "unavailableReplicas": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "terminatingReplicas": "Total number of terminating pods targeted by this deployment. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", + "conditions": "Represents the latest available observations of a deployment's current state.", "collisionCount": "collisionCount is the count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", } diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go index dd73f1a5a9..e8594766c7 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta1/zz_generated.deepcopy.go @@ -246,6 +246,11 @@ func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]DeploymentCondition, len(*in)) diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/doc.go b/go-controller/vendor/k8s.io/api/apps/v1beta2/doc.go index ac91fddfd5..7d28fe42df 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/doc.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1beta2 // import "k8s.io/api/apps/v1beta2" +package v1beta2 diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.pb.go b/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.pb.go index 1c3d3be5bc..9fcba6feb1 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.pb.go @@ -1017,153 +1017,155 @@ func init() { } var fileDescriptor_c423c016abf485d4 = []byte{ - // 2328 bytes of a gzipped FileDescriptorProto + // 2359 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0xcd, 0x6f, 0x1b, 0xc7, - 0x15, 0xf7, 0xf2, 0x43, 0x26, 0x87, 0x96, 0x64, 0x8f, 0x54, 0x89, 0xb1, 0x5b, 0xd2, 0x58, 0x1b, - 0xb6, 0x12, 0xdb, 0xa4, 0xad, 0x7c, 0x20, 0xb1, 0xdb, 0x04, 0xa2, 0x94, 0xda, 0x0e, 0xf4, 0xc1, - 0x0c, 0x2d, 0x07, 0x0d, 0xfa, 0xe1, 0x11, 0x39, 0xa6, 0x36, 0xde, 0x2f, 0xec, 0x0e, 0x15, 0x13, - 0xbd, 0xf4, 0x5a, 0xa0, 0x40, 0xdb, 0x6b, 0xff, 0x89, 0xa2, 0x97, 0xa2, 0x68, 0xd0, 0x4b, 0x11, - 0x04, 0x3e, 0x06, 0xbd, 0x24, 0x27, 0xa2, 0x66, 0x4e, 0x45, 0xd1, 0x5b, 0x7b, 0x31, 0x50, 0xa0, - 0x98, 0xd9, 0xd9, 0xef, 0x5d, 0x73, 0xa9, 0xd8, 0x4a, 0x13, 0xe4, 0xc6, 0x9d, 0xf7, 0xde, 0x6f, - 0xde, 0xcc, 0xbc, 0x37, 0xef, 0x37, 0x33, 0x04, 0x17, 0x1f, 0xbc, 0x6e, 0x37, 0x14, 0xa3, 0x89, - 0x4d, 0xa5, 0x89, 0x4d, 0xd3, 0x6e, 0x1e, 0x5c, 0xdb, 0x23, 0x14, 0xaf, 0x36, 0xfb, 0x44, 0x27, - 0x16, 0xa6, 0xa4, 0xd7, 0x30, 0x2d, 0x83, 0x1a, 0x70, 0xd9, 0x51, 0x6c, 0x60, 0x53, 0x69, 0x30, - 0xc5, 0x86, 0x50, 0x3c, 0x7d, 0xa5, 0xaf, 0xd0, 0xfd, 0xc1, 0x5e, 0xa3, 0x6b, 0x68, 0xcd, 0xbe, - 0xd1, 0x37, 0x9a, 0x5c, 0x7f, 0x6f, 0x70, 0x9f, 0x7f, 0xf1, 0x0f, 0xfe, 0xcb, 0xc1, 0x39, 0x2d, - 0x07, 0x3a, 0xec, 0x1a, 0x16, 0x69, 0x1e, 0x5c, 0x8b, 0xf6, 0x75, 0xfa, 0x15, 0x5f, 0x47, 0xc3, - 0xdd, 0x7d, 0x45, 0x27, 0xd6, 0xb0, 0x69, 0x3e, 0xe8, 0xb3, 0x06, 0xbb, 0xa9, 0x11, 0x8a, 0x93, - 0xac, 0x9a, 0x69, 0x56, 0xd6, 0x40, 0xa7, 0x8a, 0x46, 0x62, 0x06, 0xaf, 0x4d, 0x32, 0xb0, 0xbb, - 0xfb, 0x44, 0xc3, 0x31, 0xbb, 0x97, 0xd3, 0xec, 0x06, 0x54, 0x51, 0x9b, 0x8a, 0x4e, 0x6d, 0x6a, - 0x45, 0x8d, 0xe4, 0xff, 0x48, 0x00, 0xae, 0x1b, 0x3a, 0xb5, 0x0c, 0x55, 0x25, 0x16, 0x22, 0x07, - 0x8a, 0xad, 0x18, 0x3a, 0xbc, 0x07, 0x4a, 0x6c, 0x3c, 0x3d, 0x4c, 0x71, 0x55, 0x3a, 0x2b, 0xad, - 0x54, 0x56, 0xaf, 0x36, 0xfc, 0x99, 0xf6, 0xe0, 0x1b, 0xe6, 0x83, 0x3e, 0x6b, 0xb0, 0x1b, 0x4c, - 0xbb, 0x71, 0x70, 0xad, 0xb1, 0xb3, 0xf7, 0x01, 0xe9, 0xd2, 0x2d, 0x42, 0x71, 0x0b, 0x3e, 0x1a, - 0xd5, 0x8f, 0x8d, 0x47, 0x75, 0xe0, 0xb7, 0x21, 0x0f, 0x15, 0xee, 0x80, 0x02, 0x47, 0xcf, 0x71, - 0xf4, 0x2b, 0xa9, 0xe8, 0x62, 0xd0, 0x0d, 0x84, 0x3f, 0x7c, 0xfb, 0x21, 0x25, 0x3a, 0x73, 0xaf, - 0x75, 0x42, 0x40, 0x17, 0x36, 0x30, 0xc5, 0x88, 0x03, 0xc1, 0xcb, 0xa0, 0x64, 0x09, 0xf7, 0xab, - 0xf9, 0xb3, 0xd2, 0x4a, 0xbe, 0x75, 0x52, 0x68, 0x95, 0xdc, 0x61, 0x21, 0x4f, 0x43, 0x7e, 0x24, - 0x81, 0xa5, 0xf8, 0xb8, 0x37, 0x15, 0x9b, 0xc2, 0x1f, 0xc7, 0xc6, 0xde, 0xc8, 0x36, 0x76, 0x66, - 0xcd, 0x47, 0xee, 0x75, 0xec, 0xb6, 0x04, 0xc6, 0xdd, 0x06, 0x45, 0x85, 0x12, 0xcd, 0xae, 0xe6, - 0xce, 0xe6, 0x57, 0x2a, 0xab, 0x97, 0x1a, 0x29, 0x01, 0xdc, 0x88, 0x7b, 0xd7, 0x9a, 0x15, 0xb8, - 0xc5, 0xdb, 0x0c, 0x01, 0x39, 0x40, 0xf2, 0x2f, 0x73, 0xa0, 0xbc, 0x81, 0x89, 0x66, 0xe8, 0x1d, - 0x42, 0x8f, 0x60, 0xe5, 0x6e, 0x81, 0x82, 0x6d, 0x92, 0xae, 0x58, 0xb9, 0x0b, 0xa9, 0x03, 0xf0, - 0x7c, 0xea, 0x98, 0xa4, 0xeb, 0x2f, 0x19, 0xfb, 0x42, 0x1c, 0x01, 0xb6, 0xc1, 0x8c, 0x4d, 0x31, - 0x1d, 0xd8, 0x7c, 0xc1, 0x2a, 0xab, 0x2b, 0x19, 0xb0, 0xb8, 0x7e, 0x6b, 0x4e, 0xa0, 0xcd, 0x38, - 0xdf, 0x48, 0xe0, 0xc8, 0xff, 0xc8, 0x01, 0xe8, 0xe9, 0xae, 0x1b, 0x7a, 0x4f, 0xa1, 0x2c, 0x9c, - 0xaf, 0x83, 0x02, 0x1d, 0x9a, 0x84, 0x4f, 0x48, 0xb9, 0x75, 0xc1, 0x75, 0xe5, 0xce, 0xd0, 0x24, - 0x4f, 0x46, 0xf5, 0xa5, 0xb8, 0x05, 0x93, 0x20, 0x6e, 0x03, 0x37, 0x3d, 0x27, 0x73, 0xdc, 0xfa, - 0x95, 0x70, 0xd7, 0x4f, 0x46, 0xf5, 0x84, 0xbd, 0xa3, 0xe1, 0x21, 0x85, 0x1d, 0x84, 0x07, 0x00, - 0xaa, 0xd8, 0xa6, 0x77, 0x2c, 0xac, 0xdb, 0x4e, 0x4f, 0x8a, 0x46, 0xc4, 0xf0, 0x5f, 0xca, 0xb6, - 0x50, 0xcc, 0xa2, 0x75, 0x5a, 0x78, 0x01, 0x37, 0x63, 0x68, 0x28, 0xa1, 0x07, 0x78, 0x01, 0xcc, - 0x58, 0x04, 0xdb, 0x86, 0x5e, 0x2d, 0xf0, 0x51, 0x78, 0x13, 0x88, 0x78, 0x2b, 0x12, 0x52, 0xf8, - 0x22, 0x38, 0xae, 0x11, 0xdb, 0xc6, 0x7d, 0x52, 0x2d, 0x72, 0xc5, 0x79, 0xa1, 0x78, 0x7c, 0xcb, - 0x69, 0x46, 0xae, 0x5c, 0xfe, 0xa3, 0x04, 0x66, 0xbd, 0x99, 0x3b, 0x82, 0xcc, 0xb9, 0x19, 0xce, - 0x1c, 0x79, 0x72, 0xb0, 0xa4, 0x24, 0xcc, 0xc7, 0xf9, 0x80, 0xe3, 0x2c, 0x1c, 0xe1, 0x4f, 0x40, - 0xc9, 0x26, 0x2a, 0xe9, 0x52, 0xc3, 0x12, 0x8e, 0xbf, 0x9c, 0xd1, 0x71, 0xbc, 0x47, 0xd4, 0x8e, - 0x30, 0x6d, 0x9d, 0x60, 0x9e, 0xbb, 0x5f, 0xc8, 0x83, 0x84, 0xef, 0x82, 0x12, 0x25, 0x9a, 0xa9, - 0x62, 0x4a, 0x44, 0xd6, 0x9c, 0x0b, 0x3a, 0xcf, 0x62, 0x86, 0x81, 0xb5, 0x8d, 0xde, 0x1d, 0xa1, - 0xc6, 0x53, 0xc6, 0x9b, 0x0c, 0xb7, 0x15, 0x79, 0x30, 0xd0, 0x04, 0x73, 0x03, 0xb3, 0xc7, 0x34, - 0x29, 0xdb, 0xce, 0xfb, 0x43, 0x11, 0x43, 0x57, 0x27, 0xcf, 0xca, 0x6e, 0xc8, 0xae, 0xb5, 0x24, - 0x7a, 0x99, 0x0b, 0xb7, 0xa3, 0x08, 0x3e, 0x5c, 0x03, 0xf3, 0x9a, 0xa2, 0x23, 0x82, 0x7b, 0xc3, - 0x0e, 0xe9, 0x1a, 0x7a, 0xcf, 0xe6, 0xa1, 0x54, 0x6c, 0x2d, 0x0b, 0x80, 0xf9, 0xad, 0xb0, 0x18, - 0x45, 0xf5, 0xe1, 0x26, 0x58, 0x74, 0x37, 0xe0, 0x5b, 0x8a, 0x4d, 0x0d, 0x6b, 0xb8, 0xa9, 0x68, - 0x0a, 0xad, 0xce, 0x70, 0x9c, 0xea, 0x78, 0x54, 0x5f, 0x44, 0x09, 0x72, 0x94, 0x68, 0x25, 0xff, - 0x76, 0x06, 0xcc, 0x47, 0xf6, 0x05, 0x78, 0x17, 0x2c, 0x75, 0x07, 0x96, 0x45, 0x74, 0xba, 0x3d, - 0xd0, 0xf6, 0x88, 0xd5, 0xe9, 0xee, 0x93, 0xde, 0x40, 0x25, 0x3d, 0xbe, 0xac, 0xc5, 0x56, 0x4d, - 0xf8, 0xba, 0xb4, 0x9e, 0xa8, 0x85, 0x52, 0xac, 0xe1, 0x3b, 0x00, 0xea, 0xbc, 0x69, 0x4b, 0xb1, - 0x6d, 0x0f, 0x33, 0xc7, 0x31, 0xbd, 0x54, 0xdc, 0x8e, 0x69, 0xa0, 0x04, 0x2b, 0xe6, 0x63, 0x8f, - 0xd8, 0x8a, 0x45, 0x7a, 0x51, 0x1f, 0xf3, 0x61, 0x1f, 0x37, 0x12, 0xb5, 0x50, 0x8a, 0x35, 0x7c, - 0x15, 0x54, 0x9c, 0xde, 0xf8, 0x9c, 0x8b, 0xc5, 0x59, 0x10, 0x60, 0x95, 0x6d, 0x5f, 0x84, 0x82, - 0x7a, 0x6c, 0x68, 0xc6, 0x9e, 0x4d, 0xac, 0x03, 0xd2, 0xbb, 0xe9, 0x90, 0x03, 0x56, 0x41, 0x8b, - 0xbc, 0x82, 0x7a, 0x43, 0xdb, 0x89, 0x69, 0xa0, 0x04, 0x2b, 0x36, 0x34, 0x27, 0x6a, 0x62, 0x43, - 0x9b, 0x09, 0x0f, 0x6d, 0x37, 0x51, 0x0b, 0xa5, 0x58, 0xb3, 0xd8, 0x73, 0x5c, 0x5e, 0x3b, 0xc0, - 0x8a, 0x8a, 0xf7, 0x54, 0x52, 0x3d, 0x1e, 0x8e, 0xbd, 0xed, 0xb0, 0x18, 0x45, 0xf5, 0xe1, 0x4d, - 0x70, 0xca, 0x69, 0xda, 0xd5, 0xb1, 0x07, 0x52, 0xe2, 0x20, 0x2f, 0x08, 0x90, 0x53, 0xdb, 0x51, - 0x05, 0x14, 0xb7, 0x81, 0xd7, 0xc1, 0x5c, 0xd7, 0x50, 0x55, 0x1e, 0x8f, 0xeb, 0xc6, 0x40, 0xa7, - 0xd5, 0x32, 0x47, 0x81, 0x2c, 0x87, 0xd6, 0x43, 0x12, 0x14, 0xd1, 0x84, 0x3f, 0x03, 0xa0, 0xeb, - 0x16, 0x06, 0xbb, 0x0a, 0x26, 0x30, 0x80, 0x78, 0x59, 0xf2, 0x2b, 0xb3, 0xd7, 0x64, 0xa3, 0x00, - 0xa4, 0xfc, 0xb1, 0x04, 0x96, 0x53, 0x12, 0x1d, 0xbe, 0x15, 0x2a, 0x82, 0x97, 0x22, 0x45, 0xf0, - 0x4c, 0x8a, 0x59, 0xa0, 0x12, 0xee, 0x83, 0x59, 0x46, 0x48, 0x14, 0xbd, 0xef, 0xa8, 0x88, 0xbd, - 0xac, 0x99, 0x3a, 0x00, 0x14, 0xd4, 0xf6, 0x77, 0xe5, 0x53, 0xe3, 0x51, 0x7d, 0x36, 0x24, 0x43, - 0x61, 0x60, 0xf9, 0x57, 0x39, 0x00, 0x36, 0x88, 0xa9, 0x1a, 0x43, 0x8d, 0xe8, 0x47, 0xc1, 0x69, - 0x6e, 0x87, 0x38, 0xcd, 0xc5, 0xf4, 0x25, 0xf1, 0x9c, 0x4a, 0x25, 0x35, 0xef, 0x46, 0x48, 0xcd, - 0x8b, 0x59, 0xc0, 0x9e, 0xce, 0x6a, 0x3e, 0xcb, 0x83, 0x05, 0x5f, 0xd9, 0xa7, 0x35, 0x37, 0x42, - 0x2b, 0x7a, 0x31, 0xb2, 0xa2, 0xcb, 0x09, 0x26, 0xcf, 0x8d, 0xd7, 0x7c, 0x00, 0xe6, 0x18, 0xeb, - 0x70, 0xd6, 0x8f, 0x73, 0x9a, 0x99, 0xa9, 0x39, 0x8d, 0x57, 0x89, 0x36, 0x43, 0x48, 0x28, 0x82, - 0x9c, 0xc2, 0xa1, 0x8e, 0x7f, 0x1d, 0x39, 0xd4, 0x9f, 0x24, 0x30, 0xe7, 0x2f, 0xd3, 0x11, 0x90, - 0xa8, 0x5b, 0x61, 0x12, 0x75, 0x2e, 0x43, 0x70, 0xa6, 0xb0, 0xa8, 0xcf, 0x0a, 0x41, 0xd7, 0x39, - 0x8d, 0x5a, 0x61, 0x47, 0x30, 0x53, 0x55, 0xba, 0xd8, 0x16, 0xf5, 0xf6, 0x84, 0x73, 0xfc, 0x72, - 0xda, 0x90, 0x27, 0x0d, 0x11, 0xae, 0xdc, 0xf3, 0x25, 0x5c, 0xf9, 0x67, 0x43, 0xb8, 0x7e, 0x04, - 0x4a, 0xb6, 0x4b, 0xb5, 0x0a, 0x1c, 0xf2, 0x52, 0xa6, 0xc4, 0x16, 0x2c, 0xcb, 0x83, 0xf6, 0xf8, - 0x95, 0x07, 0x97, 0xc4, 0xac, 0x8a, 0x5f, 0x25, 0xb3, 0x62, 0x81, 0x6e, 0xe2, 0x81, 0x4d, 0x7a, - 0x3c, 0xa9, 0x4a, 0x7e, 0xa0, 0xb7, 0x79, 0x2b, 0x12, 0x52, 0xb8, 0x0b, 0x96, 0x4d, 0xcb, 0xe8, - 0x5b, 0xc4, 0xb6, 0x37, 0x08, 0xee, 0xa9, 0x8a, 0x4e, 0xdc, 0x01, 0x38, 0x35, 0xf1, 0xcc, 0x78, - 0x54, 0x5f, 0x6e, 0x27, 0xab, 0xa0, 0x34, 0x5b, 0xf9, 0xaf, 0x05, 0x70, 0x32, 0xba, 0x37, 0xa6, - 0xd0, 0x14, 0xe9, 0x50, 0x34, 0xe5, 0x72, 0x20, 0x4e, 0x1d, 0x0e, 0x17, 0xb8, 0x2a, 0x88, 0xc5, - 0xea, 0x1a, 0x98, 0x17, 0xb4, 0xc4, 0x15, 0x0a, 0xa2, 0xe6, 0x2d, 0xcf, 0x6e, 0x58, 0x8c, 0xa2, - 0xfa, 0xf0, 0x06, 0x98, 0xb5, 0x38, 0xf3, 0x72, 0x01, 0x1c, 0xf6, 0xf2, 0x1d, 0x01, 0x30, 0x8b, - 0x82, 0x42, 0x14, 0xd6, 0x65, 0xcc, 0xc5, 0x27, 0x24, 0x2e, 0x40, 0x21, 0xcc, 0x5c, 0xd6, 0xa2, - 0x0a, 0x28, 0x6e, 0x03, 0xb7, 0xc0, 0xc2, 0x40, 0x8f, 0x43, 0x39, 0xb1, 0x76, 0x46, 0x40, 0x2d, - 0xec, 0xc6, 0x55, 0x50, 0x92, 0x1d, 0xbc, 0x17, 0x22, 0x33, 0x33, 0x7c, 0x3f, 0xb9, 0x9c, 0x21, - 0x27, 0x32, 0xb3, 0x99, 0x04, 0xaa, 0x55, 0xca, 0x4a, 0xb5, 0xe4, 0x8f, 0x24, 0x00, 0xe3, 0x79, - 0x38, 0xf1, 0x26, 0x20, 0x66, 0x11, 0xa8, 0x98, 0x4a, 0x32, 0xff, 0xb9, 0x9a, 0x91, 0xff, 0xf8, - 0x1b, 0x6a, 0x36, 0x02, 0x24, 0x26, 0xfa, 0x68, 0x2e, 0x75, 0xb2, 0x12, 0x20, 0xdf, 0xa9, 0x67, - 0x40, 0x80, 0x02, 0x60, 0x4f, 0x27, 0x40, 0xff, 0xcc, 0x81, 0x05, 0x5f, 0x39, 0x33, 0x01, 0x4a, - 0x30, 0xf9, 0xf6, 0x62, 0x27, 0x1b, 0x29, 0xf1, 0xa7, 0xee, 0xff, 0x89, 0x94, 0xf8, 0x5e, 0xa5, - 0x90, 0x92, 0xdf, 0xe7, 0x82, 0xae, 0x4f, 0x49, 0x4a, 0x9e, 0xc1, 0x0d, 0xc7, 0xd7, 0x8e, 0xd7, - 0xc8, 0x9f, 0xe4, 0xc1, 0xc9, 0x68, 0x1e, 0x86, 0x0a, 0xa4, 0x34, 0xb1, 0x40, 0xb6, 0xc1, 0xe2, - 0xfd, 0x81, 0xaa, 0x0e, 0xf9, 0x18, 0x02, 0x55, 0xd2, 0x29, 0xad, 0xdf, 0x15, 0x96, 0x8b, 0x3f, - 0x4c, 0xd0, 0x41, 0x89, 0x96, 0xf1, 0x7a, 0x59, 0xf8, 0xb2, 0xf5, 0xb2, 0x78, 0x88, 0x7a, 0x99, - 0x4c, 0x39, 0xf2, 0x87, 0xa2, 0x1c, 0xd3, 0x15, 0xcb, 0x84, 0x8d, 0x6b, 0xe2, 0xd1, 0x7f, 0x2c, - 0x81, 0xa5, 0xe4, 0x03, 0x37, 0x54, 0xc1, 0x9c, 0x86, 0x1f, 0x06, 0x2f, 0x3e, 0x26, 0x15, 0x91, - 0x01, 0x55, 0xd4, 0x86, 0xf3, 0x64, 0xd4, 0xb8, 0xad, 0xd3, 0x1d, 0xab, 0x43, 0x2d, 0x45, 0xef, - 0x3b, 0x95, 0x77, 0x2b, 0x84, 0x85, 0x22, 0xd8, 0xf0, 0x7d, 0x50, 0xd2, 0xf0, 0xc3, 0xce, 0xc0, - 0xea, 0x27, 0x55, 0xc8, 0x6c, 0xfd, 0xf0, 0x04, 0xd8, 0x12, 0x28, 0xc8, 0xc3, 0x93, 0xbf, 0x90, - 0xc0, 0x72, 0x4a, 0x55, 0xfd, 0x06, 0x8d, 0xf2, 0x2f, 0x12, 0x38, 0x1b, 0x1a, 0x25, 0x4b, 0x4b, - 0x72, 0x7f, 0xa0, 0xf2, 0x0c, 0x15, 0x4c, 0xe6, 0x12, 0x28, 0x9b, 0xd8, 0xa2, 0x8a, 0xc7, 0x83, - 0x8b, 0xad, 0xd9, 0xf1, 0xa8, 0x5e, 0x6e, 0xbb, 0x8d, 0xc8, 0x97, 0x27, 0xcc, 0x4d, 0xee, 0xf9, - 0xcd, 0x8d, 0xfc, 0x5f, 0x09, 0x14, 0x3b, 0x5d, 0xac, 0x92, 0x23, 0x20, 0x2e, 0x1b, 0x21, 0xe2, - 0x92, 0xfe, 0x28, 0xc0, 0xfd, 0x49, 0xe5, 0x2c, 0x9b, 0x11, 0xce, 0x72, 0x7e, 0x02, 0xce, 0xd3, - 0xe9, 0xca, 0x1b, 0xa0, 0xec, 0x75, 0x37, 0xdd, 0x5e, 0x2a, 0xff, 0x2e, 0x07, 0x2a, 0x81, 0x2e, - 0xa6, 0xdc, 0x89, 0xef, 0x85, 0xca, 0x0f, 0xdb, 0x63, 0x56, 0xb3, 0x0c, 0xa4, 0xe1, 0x96, 0x9a, - 0xb7, 0x75, 0x6a, 0x05, 0xcf, 0xaa, 0xf1, 0x0a, 0xf4, 0x26, 0x98, 0xa3, 0xd8, 0xea, 0x13, 0xea, - 0xca, 0xf8, 0x84, 0x95, 0xfd, 0xbb, 0x9b, 0x3b, 0x21, 0x29, 0x8a, 0x68, 0x9f, 0xbe, 0x01, 0x66, - 0x43, 0x9d, 0xc1, 0x93, 0x20, 0xff, 0x80, 0x0c, 0x1d, 0x06, 0x87, 0xd8, 0x4f, 0xb8, 0x08, 0x8a, - 0x07, 0x58, 0x1d, 0x38, 0x21, 0x5a, 0x46, 0xce, 0xc7, 0xf5, 0xdc, 0xeb, 0x92, 0xfc, 0x6b, 0x36, - 0x39, 0x7e, 0x2a, 0x1c, 0x41, 0x74, 0xbd, 0x13, 0x8a, 0xae, 0xf4, 0xf7, 0xc9, 0x60, 0x82, 0xa6, - 0xc5, 0x18, 0x8a, 0xc4, 0xd8, 0x4b, 0x99, 0xd0, 0x9e, 0x1e, 0x69, 0xff, 0xca, 0x81, 0xc5, 0x80, - 0xb6, 0xcf, 0x8c, 0xbf, 0x1f, 0x62, 0xc6, 0x2b, 0x11, 0x66, 0x5c, 0x4d, 0xb2, 0xf9, 0x96, 0x1a, - 0x4f, 0xa6, 0xc6, 0x7f, 0x96, 0xc0, 0x7c, 0x60, 0xee, 0x8e, 0x80, 0x1b, 0xdf, 0x0e, 0x73, 0xe3, - 0xf3, 0x59, 0x82, 0x26, 0x85, 0x1c, 0x5f, 0x07, 0x0b, 0x01, 0xa5, 0x1d, 0xab, 0xa7, 0xe8, 0x58, - 0xb5, 0xe1, 0x39, 0x50, 0xb4, 0x29, 0xb6, 0xa8, 0x5b, 0x44, 0x5c, 0xdb, 0x0e, 0x6b, 0x44, 0x8e, - 0x4c, 0xfe, 0xb7, 0x04, 0x9a, 0x01, 0xe3, 0x36, 0xb1, 0x6c, 0xc5, 0xa6, 0x44, 0xa7, 0x77, 0x0d, - 0x75, 0xa0, 0x91, 0x75, 0x15, 0x2b, 0x1a, 0x22, 0xac, 0x41, 0x31, 0xf4, 0xb6, 0xa1, 0x2a, 0xdd, - 0x21, 0xc4, 0xa0, 0xf2, 0xe1, 0x3e, 0xd1, 0x37, 0x88, 0x4a, 0xa8, 0x78, 0x81, 0x2b, 0xb7, 0xde, - 0x72, 0x1f, 0xa4, 0xde, 0xf3, 0x45, 0x4f, 0x46, 0xf5, 0x95, 0x2c, 0x88, 0x3c, 0x42, 0x83, 0x98, - 0xf0, 0xa7, 0x00, 0xb0, 0x4f, 0xbe, 0x97, 0xf5, 0x44, 0xb0, 0xbe, 0xe9, 0x66, 0xf4, 0x7b, 0x9e, - 0x64, 0xaa, 0x0e, 0x02, 0x88, 0xf2, 0x1f, 0x4a, 0xa1, 0xf5, 0xfe, 0xc6, 0xdf, 0x72, 0xfe, 0x1c, - 0x2c, 0x1e, 0xf8, 0xb3, 0xe3, 0x2a, 0x30, 0xfe, 0x9d, 0x8f, 0x9e, 0xe4, 0x3d, 0xf8, 0xa4, 0x79, - 0xf5, 0x59, 0xff, 0xdd, 0x04, 0x38, 0x94, 0xd8, 0x09, 0x7c, 0x15, 0x54, 0x18, 0x6f, 0x56, 0xba, - 0x64, 0x1b, 0x6b, 0x6e, 0x2e, 0x7a, 0x0f, 0x98, 0x1d, 0x5f, 0x84, 0x82, 0x7a, 0x70, 0x1f, 0x2c, - 0x98, 0x46, 0x6f, 0x0b, 0xeb, 0xb8, 0x4f, 0x18, 0x11, 0x74, 0x96, 0x92, 0x5f, 0x7d, 0x96, 0x5b, - 0xaf, 0xb9, 0xd7, 0x5a, 0xed, 0xb8, 0xca, 0x93, 0x51, 0x7d, 0x39, 0xa1, 0x99, 0x07, 0x41, 0x12, - 0x24, 0xb4, 0x62, 0x8f, 0xee, 0xce, 0xa3, 0xc3, 0x6a, 0x96, 0xa4, 0x3c, 0xe4, 0xb3, 0x7b, 0xda, - 0xcd, 0x6e, 0xe9, 0x50, 0x37, 0xbb, 0x09, 0x47, 0xdc, 0xf2, 0x94, 0x47, 0xdc, 0x4f, 0x24, 0x70, - 0xde, 0xcc, 0x90, 0x4b, 0x55, 0xc0, 0xe7, 0xe6, 0x56, 0x96, 0xb9, 0xc9, 0x92, 0x9b, 0xad, 0x95, - 0xf1, 0xa8, 0x7e, 0x3e, 0x8b, 0x26, 0xca, 0xe4, 0x1f, 0xbc, 0x0b, 0x4a, 0x86, 0xd8, 0x03, 0xab, - 0x15, 0xee, 0xeb, 0xe5, 0x2c, 0xbe, 0xba, 0xfb, 0xa6, 0x93, 0x96, 0xee, 0x17, 0xf2, 0xb0, 0xe4, - 0x8f, 0x8a, 0xe0, 0x54, 0xac, 0x82, 0x7f, 0x85, 0xf7, 0xd7, 0xb1, 0xc3, 0x74, 0x7e, 0x8a, 0xc3, - 0xf4, 0x1a, 0x98, 0x17, 0x7f, 0x89, 0x88, 0x9c, 0xc5, 0xbd, 0x80, 0x59, 0x0f, 0x8b, 0x51, 0x54, - 0x3f, 0xe9, 0xfe, 0xbc, 0x38, 0xe5, 0xfd, 0x79, 0xd0, 0x0b, 0xf1, 0x17, 0x3f, 0x27, 0xbd, 0xe3, - 0x5e, 0x88, 0x7f, 0xfa, 0x45, 0xf5, 0x19, 0x71, 0x75, 0x50, 0x3d, 0x84, 0xe3, 0x61, 0xe2, 0xba, - 0x1b, 0x92, 0xa2, 0x88, 0xf6, 0x97, 0x7a, 0xf6, 0xc7, 0x09, 0xcf, 0xfe, 0x57, 0xb2, 0xc4, 0x5a, - 0xf6, 0xab, 0xf2, 0xc4, 0x4b, 0x8f, 0xca, 0xf4, 0x97, 0x1e, 0xf2, 0xdf, 0x24, 0xf0, 0x42, 0xea, - 0xae, 0x05, 0xd7, 0x42, 0xb4, 0xf2, 0x4a, 0x84, 0x56, 0x7e, 0x2f, 0xd5, 0x30, 0xc0, 0x2d, 0xad, - 0xe4, 0x5b, 0xf4, 0x37, 0xb2, 0xdd, 0xa2, 0x27, 0x9c, 0x84, 0x27, 0x5f, 0xa7, 0xb7, 0x7e, 0xf0, - 0xe8, 0x71, 0xed, 0xd8, 0xa7, 0x8f, 0x6b, 0xc7, 0x3e, 0x7f, 0x5c, 0x3b, 0xf6, 0x8b, 0x71, 0x4d, - 0x7a, 0x34, 0xae, 0x49, 0x9f, 0x8e, 0x6b, 0xd2, 0xe7, 0xe3, 0x9a, 0xf4, 0xf7, 0x71, 0x4d, 0xfa, - 0xcd, 0x17, 0xb5, 0x63, 0xef, 0x2f, 0xa7, 0xfc, 0xe9, 0xf8, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, - 0xa4, 0x79, 0xcd, 0x52, 0x8e, 0x2c, 0x00, 0x00, + 0x15, 0xf7, 0x92, 0xa2, 0x44, 0x0e, 0x2d, 0xc9, 0x1e, 0xa9, 0x22, 0x63, 0xb7, 0xa4, 0xb1, 0x36, + 0x6c, 0x25, 0xb6, 0x49, 0x5b, 0xf9, 0x40, 0x62, 0xb7, 0x09, 0x44, 0x29, 0xb5, 0x1d, 0x48, 0x32, + 0x33, 0xb4, 0x1c, 0x34, 0xe8, 0x87, 0x47, 0xe4, 0x98, 0xda, 0x78, 0xbf, 0xb0, 0x3b, 0x54, 0x4c, + 0xf4, 0xd2, 0x6b, 0x81, 0x16, 0x6d, 0xae, 0xfd, 0x27, 0x8a, 0x5e, 0x8a, 0xa2, 0x41, 0x6f, 0x41, + 0xe1, 0x63, 0xd0, 0x4b, 0x72, 0x22, 0x6a, 0xe6, 0x54, 0x14, 0xbd, 0xb5, 0x17, 0x03, 0x05, 0x8a, + 0x99, 0x9d, 0xfd, 0xde, 0x35, 0x97, 0x8a, 0xad, 0x34, 0x41, 0x6e, 0xdc, 0x79, 0xef, 0xfd, 0xe6, + 0xcd, 0xcc, 0x7b, 0xf3, 0x7e, 0xfb, 0xb8, 0xe0, 0xc2, 0x83, 0xd7, 0xed, 0x86, 0x62, 0x34, 0xb1, + 0xa9, 0x34, 0xb1, 0x69, 0xda, 0xcd, 0x83, 0xab, 0x7b, 0x84, 0xe2, 0xb5, 0x66, 0x9f, 0xe8, 0xc4, + 0xc2, 0x94, 0xf4, 0x1a, 0xa6, 0x65, 0x50, 0x03, 0x56, 0x1c, 0xc5, 0x06, 0x36, 0x95, 0x06, 0x53, + 0x6c, 0x08, 0xc5, 0x53, 0x97, 0xfb, 0x0a, 0xdd, 0x1f, 0xec, 0x35, 0xba, 0x86, 0xd6, 0xec, 0x1b, + 0x7d, 0xa3, 0xc9, 0xf5, 0xf7, 0x06, 0xf7, 0xf9, 0x13, 0x7f, 0xe0, 0xbf, 0x1c, 0x9c, 0x53, 0x72, + 0x60, 0xc2, 0xae, 0x61, 0x91, 0xe6, 0xc1, 0xd5, 0xe8, 0x5c, 0xa7, 0x5e, 0xf1, 0x75, 0x34, 0xdc, + 0xdd, 0x57, 0x74, 0x62, 0x0d, 0x9b, 0xe6, 0x83, 0x3e, 0x1b, 0xb0, 0x9b, 0x1a, 0xa1, 0x38, 0xc9, + 0xaa, 0x99, 0x66, 0x65, 0x0d, 0x74, 0xaa, 0x68, 0x24, 0x66, 0xf0, 0xda, 0x24, 0x03, 0xbb, 0xbb, + 0x4f, 0x34, 0x1c, 0xb3, 0x7b, 0x39, 0xcd, 0x6e, 0x40, 0x15, 0xb5, 0xa9, 0xe8, 0xd4, 0xa6, 0x56, + 0xd4, 0x48, 0xfe, 0x8f, 0x04, 0xe0, 0x86, 0xa1, 0x53, 0xcb, 0x50, 0x55, 0x62, 0x21, 0x72, 0xa0, + 0xd8, 0x8a, 0xa1, 0xc3, 0x7b, 0xa0, 0xc8, 0xd6, 0xd3, 0xc3, 0x14, 0x57, 0xa5, 0x33, 0xd2, 0x6a, + 0x79, 0xed, 0x4a, 0xc3, 0xdf, 0x69, 0x0f, 0xbe, 0x61, 0x3e, 0xe8, 0xb3, 0x01, 0xbb, 0xc1, 0xb4, + 0x1b, 0x07, 0x57, 0x1b, 0xb7, 0xf7, 0x3e, 0x20, 0x5d, 0xba, 0x4d, 0x28, 0x6e, 0xc1, 0x47, 0xa3, + 0xfa, 0xb1, 0xf1, 0xa8, 0x0e, 0xfc, 0x31, 0xe4, 0xa1, 0xc2, 0xdb, 0x60, 0x86, 0xa3, 0xe7, 0x38, + 0xfa, 0xe5, 0x54, 0x74, 0xb1, 0xe8, 0x06, 0xc2, 0x1f, 0xbe, 0xfd, 0x90, 0x12, 0x9d, 0xb9, 0xd7, + 0x3a, 0x2e, 0xa0, 0x67, 0x36, 0x31, 0xc5, 0x88, 0x03, 0xc1, 0x4b, 0xa0, 0x68, 0x09, 0xf7, 0xab, + 0xf9, 0x33, 0xd2, 0x6a, 0xbe, 0x75, 0x42, 0x68, 0x15, 0xdd, 0x65, 0x21, 0x4f, 0x43, 0x7e, 0x24, + 0x81, 0x95, 0xf8, 0xba, 0xb7, 0x14, 0x9b, 0xc2, 0x1f, 0xc7, 0xd6, 0xde, 0xc8, 0xb6, 0x76, 0x66, + 0xcd, 0x57, 0xee, 0x4d, 0xec, 0x8e, 0x04, 0xd6, 0xdd, 0x06, 0x05, 0x85, 0x12, 0xcd, 0xae, 0xe6, + 0xce, 0xe4, 0x57, 0xcb, 0x6b, 0x17, 0x1b, 0x29, 0x01, 0xdc, 0x88, 0x7b, 0xd7, 0x9a, 0x17, 0xb8, + 0x85, 0x5b, 0x0c, 0x01, 0x39, 0x40, 0xf2, 0x2f, 0x73, 0xa0, 0xb4, 0x89, 0x89, 0x66, 0xe8, 0x1d, + 0x42, 0x8f, 0xe0, 0xe4, 0x6e, 0x82, 0x19, 0xdb, 0x24, 0x5d, 0x71, 0x72, 0xe7, 0x53, 0x17, 0xe0, + 0xf9, 0xd4, 0x31, 0x49, 0xd7, 0x3f, 0x32, 0xf6, 0x84, 0x38, 0x02, 0x6c, 0x83, 0x59, 0x9b, 0x62, + 0x3a, 0xb0, 0xf9, 0x81, 0x95, 0xd7, 0x56, 0x33, 0x60, 0x71, 0xfd, 0xd6, 0x82, 0x40, 0x9b, 0x75, + 0x9e, 0x91, 0xc0, 0x91, 0xff, 0x91, 0x03, 0xd0, 0xd3, 0xdd, 0x30, 0xf4, 0x9e, 0x42, 0x59, 0x38, + 0x5f, 0x03, 0x33, 0x74, 0x68, 0x12, 0xbe, 0x21, 0xa5, 0xd6, 0x79, 0xd7, 0x95, 0x3b, 0x43, 0x93, + 0x3c, 0x19, 0xd5, 0x57, 0xe2, 0x16, 0x4c, 0x82, 0xb8, 0x0d, 0xdc, 0xf2, 0x9c, 0xcc, 0x71, 0xeb, + 0x57, 0xc2, 0x53, 0x3f, 0x19, 0xd5, 0x13, 0xee, 0x8e, 0x86, 0x87, 0x14, 0x76, 0x10, 0x1e, 0x00, + 0xa8, 0x62, 0x9b, 0xde, 0xb1, 0xb0, 0x6e, 0x3b, 0x33, 0x29, 0x1a, 0x11, 0xcb, 0x7f, 0x29, 0xdb, + 0x41, 0x31, 0x8b, 0xd6, 0x29, 0xe1, 0x05, 0xdc, 0x8a, 0xa1, 0xa1, 0x84, 0x19, 0xe0, 0x79, 0x30, + 0x6b, 0x11, 0x6c, 0x1b, 0x7a, 0x75, 0x86, 0xaf, 0xc2, 0xdb, 0x40, 0xc4, 0x47, 0x91, 0x90, 0xc2, + 0x17, 0xc1, 0x9c, 0x46, 0x6c, 0x1b, 0xf7, 0x49, 0xb5, 0xc0, 0x15, 0x17, 0x85, 0xe2, 0xdc, 0xb6, + 0x33, 0x8c, 0x5c, 0xb9, 0xfc, 0x47, 0x09, 0xcc, 0x7b, 0x3b, 0x77, 0x04, 0x99, 0x73, 0x23, 0x9c, + 0x39, 0xf2, 0xe4, 0x60, 0x49, 0x49, 0x98, 0x4f, 0xf2, 0x01, 0xc7, 0x59, 0x38, 0xc2, 0x9f, 0x80, + 0xa2, 0x4d, 0x54, 0xd2, 0xa5, 0x86, 0x25, 0x1c, 0x7f, 0x39, 0xa3, 0xe3, 0x78, 0x8f, 0xa8, 0x1d, + 0x61, 0xda, 0x3a, 0xce, 0x3c, 0x77, 0x9f, 0x90, 0x07, 0x09, 0xdf, 0x05, 0x45, 0x4a, 0x34, 0x53, + 0xc5, 0x94, 0x88, 0xac, 0x39, 0x1b, 0x74, 0x9e, 0xc5, 0x0c, 0x03, 0x6b, 0x1b, 0xbd, 0x3b, 0x42, + 0x8d, 0xa7, 0x8c, 0xb7, 0x19, 0xee, 0x28, 0xf2, 0x60, 0xa0, 0x09, 0x16, 0x06, 0x66, 0x8f, 0x69, + 0x52, 0x76, 0x9d, 0xf7, 0x87, 0x22, 0x86, 0xae, 0x4c, 0xde, 0x95, 0xdd, 0x90, 0x5d, 0x6b, 0x45, + 0xcc, 0xb2, 0x10, 0x1e, 0x47, 0x11, 0x7c, 0xb8, 0x0e, 0x16, 0x35, 0x45, 0x47, 0x04, 0xf7, 0x86, + 0x1d, 0xd2, 0x35, 0xf4, 0x9e, 0xcd, 0x43, 0xa9, 0xd0, 0xaa, 0x08, 0x80, 0xc5, 0xed, 0xb0, 0x18, + 0x45, 0xf5, 0xe1, 0x16, 0x58, 0x76, 0x2f, 0xe0, 0x9b, 0x8a, 0x4d, 0x0d, 0x6b, 0xb8, 0xa5, 0x68, + 0x0a, 0xad, 0xce, 0x72, 0x9c, 0xea, 0x78, 0x54, 0x5f, 0x46, 0x09, 0x72, 0x94, 0x68, 0x25, 0x7f, + 0x34, 0x0b, 0x16, 0x23, 0xf7, 0x02, 0xbc, 0x0b, 0x56, 0xba, 0x03, 0xcb, 0x22, 0x3a, 0xdd, 0x19, + 0x68, 0x7b, 0xc4, 0xea, 0x74, 0xf7, 0x49, 0x6f, 0xa0, 0x92, 0x1e, 0x3f, 0xd6, 0x42, 0xab, 0x26, + 0x7c, 0x5d, 0xd9, 0x48, 0xd4, 0x42, 0x29, 0xd6, 0xf0, 0x1d, 0x00, 0x75, 0x3e, 0xb4, 0xad, 0xd8, + 0xb6, 0x87, 0x99, 0xe3, 0x98, 0x5e, 0x2a, 0xee, 0xc4, 0x34, 0x50, 0x82, 0x15, 0xf3, 0xb1, 0x47, + 0x6c, 0xc5, 0x22, 0xbd, 0xa8, 0x8f, 0xf9, 0xb0, 0x8f, 0x9b, 0x89, 0x5a, 0x28, 0xc5, 0x1a, 0xbe, + 0x0a, 0xca, 0xce, 0x6c, 0x7c, 0xcf, 0xc5, 0xe1, 0x2c, 0x09, 0xb0, 0xf2, 0x8e, 0x2f, 0x42, 0x41, + 0x3d, 0xb6, 0x34, 0x63, 0xcf, 0x26, 0xd6, 0x01, 0xe9, 0xdd, 0x70, 0xc8, 0x01, 0xab, 0xa0, 0x05, + 0x5e, 0x41, 0xbd, 0xa5, 0xdd, 0x8e, 0x69, 0xa0, 0x04, 0x2b, 0xb6, 0x34, 0x27, 0x6a, 0x62, 0x4b, + 0x9b, 0x0d, 0x2f, 0x6d, 0x37, 0x51, 0x0b, 0xa5, 0x58, 0xb3, 0xd8, 0x73, 0x5c, 0x5e, 0x3f, 0xc0, + 0x8a, 0x8a, 0xf7, 0x54, 0x52, 0x9d, 0x0b, 0xc7, 0xde, 0x4e, 0x58, 0x8c, 0xa2, 0xfa, 0xf0, 0x06, + 0x38, 0xe9, 0x0c, 0xed, 0xea, 0xd8, 0x03, 0x29, 0x72, 0x90, 0x17, 0x04, 0xc8, 0xc9, 0x9d, 0xa8, + 0x02, 0x8a, 0xdb, 0xc0, 0x6b, 0x60, 0xa1, 0x6b, 0xa8, 0x2a, 0x8f, 0xc7, 0x0d, 0x63, 0xa0, 0xd3, + 0x6a, 0x89, 0xa3, 0x40, 0x96, 0x43, 0x1b, 0x21, 0x09, 0x8a, 0x68, 0xc2, 0x9f, 0x01, 0xd0, 0x75, + 0x0b, 0x83, 0x5d, 0x05, 0x13, 0x18, 0x40, 0xbc, 0x2c, 0xf9, 0x95, 0xd9, 0x1b, 0xb2, 0x51, 0x00, + 0x52, 0xfe, 0x44, 0x02, 0x95, 0x94, 0x44, 0x87, 0x6f, 0x85, 0x8a, 0xe0, 0xc5, 0x48, 0x11, 0x3c, + 0x9d, 0x62, 0x16, 0xa8, 0x84, 0xfb, 0x60, 0x9e, 0x11, 0x12, 0x45, 0xef, 0x3b, 0x2a, 0xe2, 0x2e, + 0x6b, 0xa6, 0x2e, 0x00, 0x05, 0xb5, 0xfd, 0x5b, 0xf9, 0xe4, 0x78, 0x54, 0x9f, 0x0f, 0xc9, 0x50, + 0x18, 0x58, 0xfe, 0x55, 0x0e, 0x80, 0x4d, 0x62, 0xaa, 0xc6, 0x50, 0x23, 0xfa, 0x51, 0x70, 0x9a, + 0x5b, 0x21, 0x4e, 0x73, 0x21, 0xfd, 0x48, 0x3c, 0xa7, 0x52, 0x49, 0xcd, 0xbb, 0x11, 0x52, 0xf3, + 0x62, 0x16, 0xb0, 0xa7, 0xb3, 0x9a, 0xcf, 0xf2, 0x60, 0xc9, 0x57, 0xf6, 0x69, 0xcd, 0xf5, 0xd0, + 0x89, 0x5e, 0x88, 0x9c, 0x68, 0x25, 0xc1, 0xe4, 0xb9, 0xf1, 0x9a, 0x0f, 0xc0, 0x02, 0x63, 0x1d, + 0xce, 0xf9, 0x71, 0x4e, 0x33, 0x3b, 0x35, 0xa7, 0xf1, 0x2a, 0xd1, 0x56, 0x08, 0x09, 0x45, 0x90, + 0x53, 0x38, 0xd4, 0xdc, 0xd7, 0x91, 0x43, 0xfd, 0x49, 0x02, 0x0b, 0xfe, 0x31, 0x1d, 0x01, 0x89, + 0xba, 0x19, 0x26, 0x51, 0x67, 0x33, 0x04, 0x67, 0x0a, 0x8b, 0xfa, 0x6c, 0x26, 0xe8, 0x3a, 0xa7, + 0x51, 0xab, 0xec, 0x15, 0xcc, 0x54, 0x95, 0x2e, 0xb6, 0x45, 0xbd, 0x3d, 0xee, 0xbc, 0x7e, 0x39, + 0x63, 0xc8, 0x93, 0x86, 0x08, 0x57, 0xee, 0xf9, 0x12, 0xae, 0xfc, 0xb3, 0x21, 0x5c, 0x3f, 0x02, + 0x45, 0xdb, 0xa5, 0x5a, 0x33, 0x1c, 0xf2, 0x62, 0xa6, 0xc4, 0x16, 0x2c, 0xcb, 0x83, 0xf6, 0xf8, + 0x95, 0x07, 0x97, 0xc4, 0xac, 0x0a, 0x5f, 0x25, 0xb3, 0x62, 0x81, 0x6e, 0xe2, 0x81, 0x4d, 0x7a, + 0x3c, 0xa9, 0x8a, 0x7e, 0xa0, 0xb7, 0xf9, 0x28, 0x12, 0x52, 0xb8, 0x0b, 0x2a, 0xa6, 0x65, 0xf4, + 0x2d, 0x62, 0xdb, 0x9b, 0x04, 0xf7, 0x54, 0x45, 0x27, 0xee, 0x02, 0x9c, 0x9a, 0x78, 0x7a, 0x3c, + 0xaa, 0x57, 0xda, 0xc9, 0x2a, 0x28, 0xcd, 0x56, 0xfe, 0x75, 0x01, 0x9c, 0x88, 0xde, 0x8d, 0x29, + 0x34, 0x45, 0x3a, 0x14, 0x4d, 0xb9, 0x14, 0x88, 0x53, 0x87, 0xc3, 0x05, 0x5a, 0x05, 0xb1, 0x58, + 0x5d, 0x07, 0x8b, 0x82, 0x96, 0xb8, 0x42, 0x41, 0xd4, 0xbc, 0xe3, 0xd9, 0x0d, 0x8b, 0x51, 0x54, + 0x1f, 0x5e, 0x07, 0xf3, 0x16, 0x67, 0x5e, 0x2e, 0x80, 0xc3, 0x5e, 0xbe, 0x23, 0x00, 0xe6, 0x51, + 0x50, 0x88, 0xc2, 0xba, 0x8c, 0xb9, 0xf8, 0x84, 0xc4, 0x05, 0x98, 0x09, 0x33, 0x97, 0xf5, 0xa8, + 0x02, 0x8a, 0xdb, 0xc0, 0x6d, 0xb0, 0x34, 0xd0, 0xe3, 0x50, 0x4e, 0xac, 0x9d, 0x16, 0x50, 0x4b, + 0xbb, 0x71, 0x15, 0x94, 0x64, 0x07, 0x6f, 0x81, 0x25, 0x4a, 0x2c, 0x4d, 0xd1, 0x31, 0x55, 0xf4, + 0xbe, 0x07, 0xe7, 0x9c, 0x7c, 0x85, 0x41, 0xdd, 0x89, 0x8b, 0x51, 0x92, 0x0d, 0xbc, 0x17, 0xe2, + 0x45, 0xb3, 0xfc, 0x6a, 0xba, 0x94, 0x21, 0xbd, 0x32, 0x13, 0xa3, 0x04, 0xd6, 0x56, 0xcc, 0xca, + 0xda, 0xe4, 0x8f, 0x25, 0x00, 0xe3, 0x29, 0x3d, 0xb1, 0xa9, 0x10, 0xb3, 0x08, 0x14, 0x5f, 0x25, + 0x99, 0x4a, 0x5d, 0xc9, 0x48, 0xa5, 0xfc, 0xbb, 0x39, 0x1b, 0x97, 0x12, 0x1b, 0x7d, 0x34, 0xfd, + 0xa1, 0xac, 0x5c, 0xca, 0x77, 0xea, 0x19, 0x70, 0xa9, 0x00, 0xd8, 0xd3, 0xb9, 0xd4, 0x3f, 0x73, + 0x60, 0xc9, 0x57, 0xce, 0xcc, 0xa5, 0x12, 0x4c, 0xbe, 0xed, 0x11, 0x65, 0xe3, 0x37, 0xfe, 0xd6, + 0xfd, 0x3f, 0xf1, 0x1b, 0xdf, 0xab, 0x14, 0x7e, 0xf3, 0xfb, 0x5c, 0xd0, 0xf5, 0x29, 0xf9, 0xcd, + 0x33, 0x68, 0x96, 0x7c, 0xed, 0x28, 0x92, 0xfc, 0xd1, 0x0c, 0x38, 0x11, 0xcd, 0xc3, 0x50, 0xad, + 0x95, 0x26, 0xd6, 0xda, 0x36, 0x58, 0xbe, 0x3f, 0x50, 0xd5, 0x21, 0x5f, 0x43, 0xa0, 0xe0, 0x3a, + 0x55, 0xfa, 0xbb, 0xc2, 0x72, 0xf9, 0x87, 0x09, 0x3a, 0x28, 0xd1, 0x32, 0x5e, 0x7a, 0x67, 0xbe, + 0x6c, 0xe9, 0x2d, 0x1c, 0xa2, 0xf4, 0xa6, 0xd4, 0xca, 0xb9, 0x43, 0xd4, 0xca, 0x64, 0x22, 0x94, + 0x3f, 0x14, 0x11, 0x9a, 0xae, 0xee, 0x26, 0xdc, 0x81, 0x13, 0x1b, 0x12, 0x63, 0x09, 0xac, 0x24, + 0xb7, 0x01, 0xa0, 0x0a, 0x16, 0x34, 0xfc, 0x30, 0xd8, 0x8e, 0x99, 0x54, 0x8f, 0x06, 0x54, 0x51, + 0x1b, 0xce, 0x1f, 0x59, 0x8d, 0x5b, 0x3a, 0xbd, 0x6d, 0x75, 0xa8, 0xa5, 0xe8, 0x7d, 0xa7, 0x88, + 0x6f, 0x87, 0xb0, 0x50, 0x04, 0x1b, 0xbe, 0x0f, 0x8a, 0x1a, 0x7e, 0xd8, 0x19, 0x58, 0xfd, 0xa4, + 0x62, 0x9b, 0x6d, 0x1e, 0x9e, 0x4b, 0xdb, 0x02, 0x05, 0x79, 0x78, 0xf2, 0x17, 0x12, 0xa8, 0xa4, + 0x14, 0xe8, 0x6f, 0xd0, 0x2a, 0xff, 0x22, 0x81, 0x33, 0xa1, 0x55, 0xb2, 0x0c, 0x27, 0xf7, 0x07, + 0x2a, 0x4f, 0x76, 0x41, 0x8a, 0x2e, 0x82, 0x92, 0x89, 0x2d, 0xaa, 0x78, 0xec, 0xbc, 0xd0, 0x9a, + 0x1f, 0x8f, 0xea, 0xa5, 0xb6, 0x3b, 0x88, 0x7c, 0x79, 0xc2, 0xde, 0xe4, 0x9e, 0xdf, 0xde, 0xc8, + 0xff, 0x95, 0x40, 0xa1, 0xd3, 0xc5, 0x2a, 0x39, 0x02, 0x0e, 0xb4, 0x19, 0xe2, 0x40, 0xe9, 0x7f, + 0x55, 0x70, 0x7f, 0x52, 0xe9, 0xcf, 0x56, 0x84, 0xfe, 0x9c, 0x9b, 0x80, 0xf3, 0x74, 0xe6, 0xf3, + 0x06, 0x28, 0x79, 0xd3, 0x4d, 0x77, 0x2d, 0xcb, 0xbf, 0xcb, 0x81, 0x72, 0x60, 0x8a, 0x29, 0x2f, + 0xf5, 0x7b, 0xa1, 0x4a, 0xc6, 0xee, 0x98, 0xb5, 0x2c, 0x0b, 0x69, 0xb8, 0x55, 0xeb, 0x6d, 0x9d, + 0x5a, 0xc1, 0x37, 0xe8, 0x78, 0x31, 0x7b, 0x13, 0x2c, 0x50, 0x6c, 0xf5, 0x09, 0x75, 0x65, 0x7c, + 0xc3, 0x4a, 0x7e, 0x47, 0xe9, 0x4e, 0x48, 0x8a, 0x22, 0xda, 0xa7, 0xae, 0x83, 0xf9, 0xd0, 0x64, + 0xf0, 0x04, 0xc8, 0x3f, 0x20, 0x43, 0x87, 0x0c, 0x22, 0xf6, 0x13, 0x2e, 0x83, 0xc2, 0x01, 0x56, + 0x07, 0x4e, 0x88, 0x96, 0x90, 0xf3, 0x70, 0x2d, 0xf7, 0xba, 0x24, 0xff, 0x86, 0x6d, 0x8e, 0x9f, + 0x0a, 0x47, 0x10, 0x5d, 0xef, 0x84, 0xa2, 0x2b, 0xfd, 0x5f, 0xd3, 0x60, 0x82, 0xa6, 0xc5, 0x18, + 0x8a, 0xc4, 0xd8, 0x4b, 0x99, 0xd0, 0x9e, 0x1e, 0x69, 0xff, 0xca, 0x81, 0xe5, 0x80, 0xb6, 0x4f, + 0xb2, 0xbf, 0x1f, 0x22, 0xd9, 0xab, 0x11, 0x92, 0x5d, 0x4d, 0xb2, 0xf9, 0x96, 0x65, 0x4f, 0x66, + 0xd9, 0x7f, 0x96, 0xc0, 0x62, 0x60, 0xef, 0x8e, 0x80, 0x66, 0xdf, 0x0a, 0xd3, 0xec, 0x73, 0x59, + 0x82, 0x26, 0x85, 0x67, 0x5f, 0x03, 0x4b, 0x01, 0xa5, 0xdb, 0x56, 0x4f, 0xd1, 0xb1, 0x6a, 0xc3, + 0xb3, 0xa0, 0x60, 0x53, 0x6c, 0x51, 0xb7, 0x88, 0xb8, 0xb6, 0x1d, 0x36, 0x88, 0x1c, 0x99, 0xfc, + 0x6f, 0x09, 0x34, 0x03, 0xc6, 0x6d, 0x62, 0xd9, 0x8a, 0x4d, 0x89, 0x4e, 0xef, 0x1a, 0xea, 0x40, + 0x23, 0x1b, 0x2a, 0x56, 0x34, 0x44, 0xd8, 0x80, 0x62, 0xe8, 0x6d, 0x43, 0x55, 0xba, 0x43, 0x88, + 0x41, 0xf9, 0xc3, 0x7d, 0xa2, 0x6f, 0x12, 0x95, 0x50, 0xf1, 0xbf, 0x60, 0xa9, 0xf5, 0x96, 0xfb, + 0x37, 0xd9, 0x7b, 0xbe, 0xe8, 0xc9, 0xa8, 0xbe, 0x9a, 0x05, 0x91, 0x47, 0x68, 0x10, 0x13, 0xfe, + 0x14, 0x00, 0xf6, 0xc8, 0xef, 0xb2, 0x9e, 0x08, 0xd6, 0x37, 0xdd, 0x8c, 0x7e, 0xcf, 0x93, 0x4c, + 0x35, 0x41, 0x00, 0x51, 0xfe, 0x43, 0x31, 0x74, 0xde, 0xdf, 0xf8, 0xde, 0xeb, 0xcf, 0xc1, 0xf2, + 0x81, 0xbf, 0x3b, 0xae, 0x02, 0xa3, 0xf2, 0xf9, 0x68, 0x53, 0xc0, 0x83, 0x4f, 0xda, 0x57, 0xff, + 0x05, 0xe2, 0x6e, 0x02, 0x1c, 0x4a, 0x9c, 0x04, 0xbe, 0x0a, 0xca, 0x8c, 0x37, 0x2b, 0x5d, 0xb2, + 0x83, 0x35, 0x37, 0x17, 0xbd, 0xbf, 0x55, 0x3b, 0xbe, 0x08, 0x05, 0xf5, 0xe0, 0x3e, 0x58, 0x32, + 0x8d, 0xde, 0x36, 0xd6, 0x71, 0x9f, 0x30, 0x22, 0xe8, 0x1c, 0x25, 0x6f, 0xc8, 0x96, 0x5a, 0xaf, + 0xb9, 0xcd, 0xb6, 0x76, 0x5c, 0xe5, 0xc9, 0xa8, 0x5e, 0x49, 0x18, 0xe6, 0x41, 0x90, 0x04, 0x09, + 0xad, 0xd8, 0xa7, 0x00, 0xce, 0x5f, 0x21, 0x6b, 0x59, 0x92, 0xf2, 0x90, 0x1f, 0x03, 0xa4, 0xf5, + 0x9b, 0x8b, 0x87, 0xea, 0x37, 0x27, 0xbc, 0x2d, 0x97, 0xa6, 0x7c, 0x5b, 0xfe, 0xab, 0x04, 0xce, + 0x99, 0x19, 0x72, 0xa9, 0x0a, 0xf8, 0xde, 0xdc, 0xcc, 0xb2, 0x37, 0x59, 0x72, 0xb3, 0xb5, 0x3a, + 0x1e, 0xd5, 0xcf, 0x65, 0xd1, 0x44, 0x99, 0xfc, 0x83, 0x77, 0x41, 0xd1, 0x10, 0x77, 0x60, 0xb5, + 0xcc, 0x7d, 0xbd, 0x94, 0xc5, 0x57, 0xf7, 0xde, 0x74, 0xd2, 0xd2, 0x7d, 0x42, 0x1e, 0x96, 0xfc, + 0x71, 0x01, 0x9c, 0x8c, 0x55, 0xf0, 0xaf, 0xb0, 0xab, 0x1e, 0x7b, 0x2f, 0xcf, 0x4f, 0xf1, 0x5e, + 0xbe, 0x0e, 0x16, 0xc5, 0x87, 0x1a, 0x91, 0xd7, 0x7a, 0x2f, 0x60, 0x36, 0xc2, 0x62, 0x14, 0xd5, + 0x4f, 0xea, 0xea, 0x17, 0xa6, 0xec, 0xea, 0x07, 0xbd, 0x10, 0x1f, 0x1e, 0x3a, 0xe9, 0x1d, 0xf7, + 0x42, 0x7c, 0x7f, 0x18, 0xd5, 0x67, 0xc4, 0xd5, 0x41, 0xf5, 0x10, 0xe6, 0xc2, 0xc4, 0x75, 0x37, + 0x24, 0x45, 0x11, 0xed, 0x2f, 0xf5, 0x31, 0x02, 0x4e, 0xf8, 0x18, 0xe1, 0x72, 0x96, 0x58, 0xcb, + 0xde, 0x75, 0x4f, 0xec, 0x9f, 0x94, 0xa7, 0xef, 0x9f, 0xc8, 0x7f, 0x93, 0xc0, 0x0b, 0xa9, 0xb7, + 0x16, 0x5c, 0x0f, 0xd1, 0xca, 0xcb, 0x11, 0x5a, 0xf9, 0xbd, 0x54, 0xc3, 0x00, 0xb7, 0xb4, 0x92, + 0x1b, 0xf2, 0x6f, 0x64, 0x6b, 0xc8, 0x27, 0xbc, 0x09, 0x4f, 0xee, 0xcc, 0xb7, 0x7e, 0xf0, 0xe8, + 0x71, 0xed, 0xd8, 0xa7, 0x8f, 0x6b, 0xc7, 0x3e, 0x7f, 0x5c, 0x3b, 0xf6, 0x8b, 0x71, 0x4d, 0x7a, + 0x34, 0xae, 0x49, 0x9f, 0x8e, 0x6b, 0xd2, 0xe7, 0xe3, 0x9a, 0xf4, 0xf7, 0x71, 0x4d, 0xfa, 0xed, + 0x17, 0xb5, 0x63, 0xef, 0x57, 0x52, 0x3e, 0x85, 0xfe, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd4, + 0x01, 0x82, 0xf5, 0x24, 0x2d, 0x00, 0x00, } func (m *ControllerRevision) Marshal() (dAtA []byte, err error) { @@ -1845,6 +1847,11 @@ func (m *DeploymentStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x48 + } if m.CollisionCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) i-- @@ -2151,6 +2158,11 @@ func (m *ReplicaSetStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x38 + } if len(m.Conditions) > 0 { for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { { @@ -3146,6 +3158,9 @@ func (m *DeploymentStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -3251,6 +3266,9 @@ func (m *ReplicaSetStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -3711,6 +3729,7 @@ func (this *DeploymentStatus) String() string { `Conditions:` + repeatedStringForConditions + `,`, `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -3797,6 +3816,7 @@ func (this *ReplicaSetStatus) String() string { `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `AvailableReplicas:` + fmt.Sprintf("%v", this.AvailableReplicas) + `,`, `Conditions:` + repeatedStringForConditions + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -6261,6 +6281,26 @@ func (m *DeploymentStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -7193,6 +7233,26 @@ func (m *ReplicaSetStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.proto b/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.proto index c08a4c78bc..68c463e257 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.proto +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/generated.proto @@ -323,19 +323,19 @@ message DeploymentStatus { // +optional optional int64 observedGeneration = 1; - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional optional int32 replicas = 2; - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional optional int32 updatedReplicas = 3; - // readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional optional int32 readyReplicas = 7; - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional optional int32 availableReplicas = 4; @@ -345,6 +345,13 @@ message DeploymentStatus { // +optional optional int32 unavailableReplicas = 5; + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 9; + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -427,16 +434,16 @@ message ReplicaSetList { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset repeated ReplicaSet items = 2; } // ReplicaSetSpec is the specification of a ReplicaSet. message ReplicaSetSpec { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional optional int32 replicas = 1; @@ -454,29 +461,36 @@ message ReplicaSetSpec { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional optional .k8s.io.api.core.v1.PodTemplateSpec template = 3; } // ReplicaSetStatus represents the current status of a ReplicaSet. message ReplicaSetStatus { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset optional int32 replicas = 1; - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional optional int32 fullyLabeledReplicas = 2; - // readyReplicas is the number of pods targeted by this ReplicaSet controller with a Ready Condition. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional optional int32 readyReplicas = 4; - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional optional int32 availableReplicas = 5; + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 7; + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional optional int64 observedGeneration = 3; @@ -747,6 +761,7 @@ message StatefulSetSpec { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional optional string serviceName = 5; // podManagementPolicy controls how pods are created during initial scale up, diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/types.go b/go-controller/vendor/k8s.io/api/apps/v1beta2/types.go index c2624a941d..491afc59f5 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/types.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/types.go @@ -269,6 +269,7 @@ type StatefulSetSpec struct { // the network identity of the set. Pods get DNS/hostnames that follow the // pattern: pod-specific-string.serviceName.default.svc.cluster.local // where "pod-specific-string" is managed by the StatefulSet controller. + // +optional ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` // podManagementPolicy controls how pods are created during initial scale up, @@ -530,19 +531,19 @@ type DeploymentStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` - // readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` @@ -552,6 +553,13 @@ type DeploymentStatus struct { // +optional UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,9,opt,name=terminatingReplicas"` + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -897,16 +905,16 @@ type ReplicaSetList struct { metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Items []ReplicaSet `json:"items" protobuf:"bytes,2,rep,name=items"` } // ReplicaSetSpec is the specification of a ReplicaSet. type ReplicaSetSpec struct { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` @@ -924,29 +932,36 @@ type ReplicaSetSpec struct { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional Template v1.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,3,opt,name=template"` } // ReplicaSetStatus represents the current status of a ReplicaSet. type ReplicaSetStatus struct { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty" protobuf:"varint,2,opt,name=fullyLabeledReplicas"` - // readyReplicas is the number of pods targeted by this ReplicaSet controller with a Ready Condition. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,4,opt,name=readyReplicas"` - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"` + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,7,opt,name=terminatingReplicas"` + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go index beec4b7555..4089434151 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go @@ -177,11 +177,12 @@ func (DeploymentSpec) SwaggerDoc() map[string]string { var map_DeploymentStatus = map[string]string{ "": "DeploymentStatus is the most recently observed status of the Deployment.", "observedGeneration": "The generation observed by the deployment controller.", - "replicas": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", - "updatedReplicas": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", - "readyReplicas": "readyReplicas is the number of pods targeted by this Deployment controller with a Ready Condition.", - "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "replicas": "Total number of non-terminating pods targeted by this deployment (their labels match the selector).", + "updatedReplicas": "Total number of non-terminating pods targeted by this deployment that have the desired template spec.", + "readyReplicas": "Total number of non-terminating pods targeted by this Deployment with a Ready Condition.", + "availableReplicas": "Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment.", "unavailableReplicas": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "terminatingReplicas": "Total number of terminating pods targeted by this deployment. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "conditions": "Represents the latest available observations of a deployment's current state.", "collisionCount": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", } @@ -227,7 +228,7 @@ func (ReplicaSetCondition) SwaggerDoc() map[string]string { var map_ReplicaSetList = map[string]string{ "": "ReplicaSetList is a collection of ReplicaSets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", + "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", } func (ReplicaSetList) SwaggerDoc() map[string]string { @@ -236,10 +237,10 @@ func (ReplicaSetList) SwaggerDoc() map[string]string { var map_ReplicaSetSpec = map[string]string{ "": "ReplicaSetSpec is the specification of a ReplicaSet.", - "replicas": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "replicas": "Replicas is the number of desired pods. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", "minReadySeconds": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "selector": "Selector is a label query over pods that should match the replica count. Label keys and values that must match in order to be controlled by this replica set. It must match the pod template's labels. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", + "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template", } func (ReplicaSetSpec) SwaggerDoc() map[string]string { @@ -248,10 +249,11 @@ func (ReplicaSetSpec) SwaggerDoc() map[string]string { var map_ReplicaSetStatus = map[string]string{ "": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "replicas": "Replicas is the most recently observed number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", - "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replicaset.", - "readyReplicas": "readyReplicas is the number of pods targeted by this ReplicaSet controller with a Ready Condition.", - "availableReplicas": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", + "replicas": "Replicas is the most recently observed number of non-terminating pods. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", + "fullyLabeledReplicas": "The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset.", + "readyReplicas": "The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition.", + "availableReplicas": "The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set.", + "terminatingReplicas": "The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", "conditions": "Represents the latest available observations of a replica set's current state.", } diff --git a/go-controller/vendor/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go index cd92792db5..917ad4a22f 100644 --- a/go-controller/vendor/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/apps/v1beta2/zz_generated.deepcopy.go @@ -363,6 +363,11 @@ func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]DeploymentCondition, len(*in)) @@ -517,6 +522,11 @@ func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]ReplicaSetCondition, len(*in)) diff --git a/go-controller/vendor/k8s.io/api/authentication/v1/doc.go b/go-controller/vendor/k8s.io/api/authentication/v1/doc.go index 3bdc89badc..dc3aed4e4f 100644 --- a/go-controller/vendor/k8s.io/api/authentication/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/authentication/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1 // import "k8s.io/api/authentication/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/authentication/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/authentication/v1alpha1/doc.go index eb32def904..c199ccd499 100644 --- a/go-controller/vendor/k8s.io/api/authentication/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/authentication/v1alpha1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1alpha1 // import "k8s.io/api/authentication/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/authentication/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/authentication/v1beta1/doc.go index 2a2b176e43..af63dc845b 100644 --- a/go-controller/vendor/k8s.io/api/authentication/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/authentication/v1beta1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1beta1 // import "k8s.io/api/authentication/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/authorization/v1/doc.go b/go-controller/vendor/k8s.io/api/authorization/v1/doc.go index 77e5a19c4c..40bf8006e0 100644 --- a/go-controller/vendor/k8s.io/api/authorization/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/authorization/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=authorization.k8s.io -package v1 // import "k8s.io/api/authorization/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/authorization/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/authorization/v1beta1/doc.go index c996e35ccc..9f7332d493 100644 --- a/go-controller/vendor/k8s.io/api/authorization/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/authorization/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=authorization.k8s.io -package v1beta1 // import "k8s.io/api/authorization/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v1/doc.go b/go-controller/vendor/k8s.io/api/autoscaling/v1/doc.go index d64c9cbc1a..4ee085e165 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1 // import "k8s.io/api/autoscaling/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/doc.go b/go-controller/vendor/k8s.io/api/autoscaling/v2/doc.go index aafa2d4de2..8dea6339df 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/doc.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v2 // import "k8s.io/api/autoscaling/v2" +package v2 diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.pb.go b/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.pb.go index ece6dedadb..40b60ebeca 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.pb.go @@ -751,115 +751,116 @@ func init() { } var fileDescriptor_4d5f2c8767749221 = []byte{ - // 1722 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcb, 0x8f, 0x1b, 0x49, - 0x19, 0x9f, 0xb6, 0x3d, 0xaf, 0xf2, 0x3c, 0x2b, 0x2f, 0x67, 0xa2, 0xd8, 0xa3, 0x26, 0x90, 0x07, - 0xa4, 0x4d, 0x4c, 0x88, 0x22, 0x72, 0x40, 0xd3, 0x13, 0x20, 0xa3, 0xcc, 0x30, 0x4e, 0x39, 0xc9, - 0x00, 0x02, 0x94, 0x72, 0x77, 0x8d, 0xa7, 0x18, 0xbb, 0xdb, 0xea, 0x6e, 0x3b, 0x99, 0x48, 0x48, - 0x5c, 0xb8, 0x23, 0x50, 0x84, 0xf8, 0x1f, 0x22, 0x4e, 0xa0, 0x70, 0x00, 0x09, 0x69, 0xf7, 0x90, - 0xcb, 0x4a, 0x39, 0xec, 0x21, 0x27, 0x6b, 0xe3, 0x95, 0xf6, 0xb8, 0x7f, 0x40, 0x4e, 0xab, 0x7a, - 0xf4, 0xd3, 0xaf, 0x71, 0x76, 0x32, 0xd2, 0xdc, 0x5c, 0x55, 0xdf, 0xf7, 0xfb, 0x1e, 0xf5, 0xbd, - 0xaa, 0x0d, 0xae, 0xee, 0xdf, 0x76, 0x35, 0x6a, 0x17, 0x71, 0x93, 0x16, 0x71, 0xcb, 0xb3, 0x5d, - 0x03, 0xd7, 0xa9, 0x55, 0x2b, 0xb6, 0x4b, 0xc5, 0x1a, 0xb1, 0x88, 0x83, 0x3d, 0x62, 0x6a, 0x4d, - 0xc7, 0xf6, 0x6c, 0x78, 0x5e, 0x90, 0x6a, 0xb8, 0x49, 0xb5, 0x08, 0xa9, 0xd6, 0x2e, 0xad, 0x5c, - 0xaf, 0x51, 0x6f, 0xaf, 0x55, 0xd5, 0x0c, 0xbb, 0x51, 0xac, 0xd9, 0x35, 0xbb, 0xc8, 0x39, 0xaa, - 0xad, 0x5d, 0xbe, 0xe2, 0x0b, 0xfe, 0x4b, 0x20, 0xad, 0xa8, 0x11, 0xa1, 0x86, 0xed, 0x90, 0x62, - 0xfb, 0x46, 0x52, 0xda, 0xca, 0xcd, 0x90, 0xa6, 0x81, 0x8d, 0x3d, 0x6a, 0x11, 0xe7, 0xa0, 0xd8, - 0xdc, 0xaf, 0x71, 0x26, 0x87, 0xb8, 0x76, 0xcb, 0x31, 0xc8, 0x58, 0x5c, 0x6e, 0xb1, 0x41, 0x3c, - 0xdc, 0x4f, 0x56, 0x71, 0x10, 0x97, 0xd3, 0xb2, 0x3c, 0xda, 0xe8, 0x15, 0x73, 0x6b, 0x14, 0x83, - 0x6b, 0xec, 0x91, 0x06, 0x4e, 0xf2, 0xa9, 0x5f, 0x29, 0xe0, 0xe2, 0xba, 0x6d, 0x79, 0x98, 0x71, - 0x20, 0x69, 0xc4, 0x16, 0xf1, 0x1c, 0x6a, 0x54, 0xf8, 0x6f, 0xb8, 0x0e, 0x32, 0x16, 0x6e, 0x90, - 0x9c, 0xb2, 0xaa, 0x5c, 0x99, 0xd5, 0x8b, 0xaf, 0x3b, 0x85, 0x89, 0x6e, 0xa7, 0x90, 0xf9, 0x25, - 0x6e, 0x90, 0xf7, 0x9d, 0x42, 0xa1, 0xd7, 0x71, 0x9a, 0x0f, 0xc3, 0x48, 0x10, 0x67, 0x86, 0xdb, - 0x60, 0xca, 0xc3, 0x4e, 0x8d, 0x78, 0xb9, 0xd4, 0xaa, 0x72, 0x25, 0x5b, 0xba, 0xac, 0x0d, 0xbc, - 0x3a, 0x4d, 0x48, 0x7f, 0xc8, 0xc9, 0xf5, 0x05, 0x29, 0x6f, 0x4a, 0xac, 0x91, 0x84, 0x81, 0x45, - 0x30, 0x6b, 0xf8, 0x6a, 0xe7, 0xd2, 0x5c, 0xb5, 0x65, 0x49, 0x3a, 0x1b, 0xda, 0x13, 0xd2, 0xa8, - 0x5f, 0x0f, 0x31, 0xd4, 0xc3, 0x5e, 0xcb, 0x3d, 0x1a, 0x43, 0x77, 0xc0, 0xb4, 0xd1, 0x72, 0x1c, - 0x62, 0xf9, 0x96, 0xfe, 0x60, 0xa4, 0xa5, 0x8f, 0x71, 0xbd, 0x45, 0x84, 0x0e, 0xfa, 0xa2, 0x94, - 0x3a, 0xbd, 0x2e, 0x40, 0x90, 0x8f, 0x36, 0xbe, 0xc1, 0x2f, 0x14, 0x70, 0x61, 0xdd, 0xb1, 0x5d, - 0xf7, 0x31, 0x71, 0x5c, 0x6a, 0x5b, 0xdb, 0xd5, 0x3f, 0x10, 0xc3, 0x43, 0x64, 0x97, 0x38, 0xc4, - 0x32, 0x08, 0x5c, 0x05, 0x99, 0x7d, 0x6a, 0x99, 0xd2, 0xdc, 0x39, 0xdf, 0xdc, 0xfb, 0xd4, 0x32, - 0x11, 0x3f, 0x61, 0x14, 0xdc, 0x21, 0xa9, 0x38, 0x45, 0xc4, 0xda, 0x12, 0x00, 0xb8, 0x49, 0xa5, - 0x00, 0xa9, 0x15, 0x94, 0x74, 0x60, 0xad, 0xbc, 0x21, 0x4f, 0x50, 0x84, 0x4a, 0xfd, 0xaf, 0x02, - 0x4e, 0xff, 0xec, 0x99, 0x47, 0x1c, 0x0b, 0xd7, 0x63, 0x81, 0x56, 0x01, 0x53, 0x0d, 0xbe, 0xe6, - 0x2a, 0x65, 0x4b, 0xdf, 0x1f, 0xe9, 0xb9, 0x0d, 0x93, 0x58, 0x1e, 0xdd, 0xa5, 0xc4, 0x09, 0xe3, - 0x44, 0x9c, 0x20, 0x09, 0x75, 0xe4, 0x81, 0xa7, 0x7e, 0xda, 0xab, 0xbe, 0x08, 0x9f, 0x8f, 0xa2, - 0xfe, 0xc7, 0x0a, 0x27, 0xf5, 0x9f, 0x0a, 0x58, 0xba, 0x57, 0x5e, 0xab, 0x08, 0xee, 0xb2, 0x5d, - 0xa7, 0xc6, 0x01, 0xbc, 0x0d, 0x32, 0xde, 0x41, 0xd3, 0xcf, 0x80, 0x4b, 0xfe, 0x85, 0x3f, 0x3c, - 0x68, 0xb2, 0x0c, 0x38, 0x9d, 0xa4, 0x67, 0xfb, 0x88, 0x73, 0xc0, 0xef, 0x80, 0xc9, 0x36, 0x93, - 0xcb, 0xb5, 0x9c, 0xd4, 0xe7, 0x25, 0xeb, 0x24, 0x57, 0x06, 0x89, 0x33, 0x78, 0x07, 0xcc, 0x37, - 0x89, 0x43, 0x6d, 0xb3, 0x42, 0x0c, 0xdb, 0x32, 0x5d, 0x1e, 0x30, 0x93, 0xfa, 0x19, 0x49, 0x3c, - 0x5f, 0x8e, 0x1e, 0xa2, 0x38, 0xad, 0xfa, 0x8f, 0x14, 0x58, 0x0c, 0x15, 0x40, 0xad, 0x3a, 0x71, - 0xe1, 0xef, 0xc1, 0x8a, 0xeb, 0xe1, 0x2a, 0xad, 0xd3, 0xe7, 0xd8, 0xa3, 0xb6, 0xb5, 0x43, 0x2d, - 0xd3, 0x7e, 0x1a, 0x47, 0xcf, 0x77, 0x3b, 0x85, 0x95, 0xca, 0x40, 0x2a, 0x34, 0x04, 0x01, 0xde, - 0x07, 0x73, 0x2e, 0xa9, 0x13, 0xc3, 0x13, 0xf6, 0x4a, 0xbf, 0x5c, 0xee, 0x76, 0x0a, 0x73, 0x95, - 0xc8, 0xfe, 0xfb, 0x4e, 0xe1, 0x54, 0xcc, 0x31, 0xe2, 0x10, 0xc5, 0x98, 0xe1, 0xaf, 0xc1, 0x4c, - 0x93, 0xfd, 0xa2, 0xc4, 0xcd, 0xa5, 0x56, 0xd3, 0x23, 0x22, 0x24, 0xe9, 0x6b, 0x7d, 0x49, 0x7a, - 0x69, 0xa6, 0x2c, 0x41, 0x50, 0x00, 0xa7, 0xbe, 0x4a, 0x81, 0x73, 0xf7, 0x6c, 0x87, 0x3e, 0x67, - 0xc9, 0x5f, 0x2f, 0xdb, 0xe6, 0x9a, 0x04, 0x23, 0x0e, 0x7c, 0x02, 0x66, 0x58, 0x93, 0x31, 0xb1, - 0x87, 0x65, 0x60, 0xfe, 0x30, 0x22, 0x36, 0xe8, 0x15, 0x5a, 0x73, 0xbf, 0xc6, 0x36, 0x5c, 0x8d, - 0x51, 0x6b, 0xed, 0x1b, 0x9a, 0xa8, 0x17, 0x5b, 0xc4, 0xc3, 0x61, 0x4a, 0x87, 0x7b, 0x28, 0x40, - 0x85, 0xbf, 0x02, 0x19, 0xb7, 0x49, 0x0c, 0x19, 0xa0, 0xb7, 0x86, 0x19, 0xd5, 0x5f, 0xc7, 0x4a, - 0x93, 0x18, 0x61, 0x79, 0x61, 0x2b, 0xc4, 0x11, 0xe1, 0x13, 0x30, 0xe5, 0xf2, 0x40, 0xe6, 0x77, - 0x99, 0x2d, 0xdd, 0xfe, 0x00, 0x6c, 0x91, 0x08, 0x41, 0x7e, 0x89, 0x35, 0x92, 0xb8, 0xea, 0x67, - 0x0a, 0x28, 0x0c, 0xe0, 0xd4, 0xc9, 0x1e, 0x6e, 0x53, 0xdb, 0x81, 0x0f, 0xc0, 0x34, 0xdf, 0x79, - 0xd4, 0x94, 0x0e, 0xbc, 0x76, 0xa8, 0x7b, 0xe3, 0x21, 0xaa, 0x67, 0x59, 0xf6, 0x55, 0x04, 0x3b, - 0xf2, 0x71, 0xe0, 0x0e, 0x98, 0xe5, 0x3f, 0xef, 0xda, 0x4f, 0x2d, 0xe9, 0xb7, 0x71, 0x40, 0xe7, - 0x59, 0xd1, 0xaf, 0xf8, 0x00, 0x28, 0xc4, 0x52, 0xff, 0x9c, 0x06, 0xab, 0x03, 0xec, 0x59, 0xb7, - 0x2d, 0x93, 0xb2, 0x18, 0x87, 0xf7, 0x62, 0x69, 0x7e, 0x33, 0x91, 0xe6, 0x97, 0x46, 0xf1, 0x47, - 0xd2, 0x7e, 0x33, 0xb8, 0xa0, 0x54, 0x0c, 0x4b, 0xba, 0xf9, 0x7d, 0xa7, 0xd0, 0x67, 0xb0, 0xd2, - 0x02, 0xa4, 0xf8, 0x65, 0xc0, 0x36, 0x80, 0x75, 0xec, 0x7a, 0x0f, 0x1d, 0x6c, 0xb9, 0x42, 0x12, - 0x6d, 0x10, 0x79, 0xf5, 0xd7, 0x0e, 0x17, 0xb4, 0x8c, 0x43, 0x5f, 0x91, 0x5a, 0xc0, 0xcd, 0x1e, - 0x34, 0xd4, 0x47, 0x02, 0xfc, 0x1e, 0x98, 0x72, 0x08, 0x76, 0x6d, 0x2b, 0x97, 0xe1, 0x56, 0x04, - 0xc1, 0x82, 0xf8, 0x2e, 0x92, 0xa7, 0xf0, 0x2a, 0x98, 0x6e, 0x10, 0xd7, 0xc5, 0x35, 0x92, 0x9b, - 0xe4, 0x84, 0x41, 0x79, 0xdd, 0x12, 0xdb, 0xc8, 0x3f, 0x57, 0x3f, 0x57, 0xc0, 0x85, 0x01, 0x7e, - 0xdc, 0xa4, 0xae, 0x07, 0x7f, 0xdb, 0x93, 0x95, 0xda, 0xe1, 0x0c, 0x64, 0xdc, 0x3c, 0x27, 0x83, - 0x7a, 0xe0, 0xef, 0x44, 0x32, 0x72, 0x07, 0x4c, 0x52, 0x8f, 0x34, 0xfc, 0x3a, 0x53, 0x1a, 0x3f, - 0x6d, 0xc2, 0x0a, 0xbe, 0xc1, 0x80, 0x90, 0xc0, 0x53, 0x5f, 0xa5, 0x07, 0x9a, 0xc5, 0xd2, 0x16, - 0xb6, 0xc1, 0x02, 0x5f, 0xc9, 0x9e, 0x49, 0x76, 0xa5, 0x71, 0xc3, 0x8a, 0xc2, 0x90, 0x19, 0x45, - 0x3f, 0x2b, 0xb5, 0x58, 0xa8, 0xc4, 0x50, 0x51, 0x42, 0x0a, 0xbc, 0x01, 0xb2, 0x0d, 0x6a, 0x21, - 0xd2, 0xac, 0x53, 0x03, 0xbb, 0xb2, 0x09, 0x2d, 0x76, 0x3b, 0x85, 0xec, 0x56, 0xb8, 0x8d, 0xa2, - 0x34, 0xf0, 0xc7, 0x20, 0xdb, 0xc0, 0xcf, 0x02, 0x16, 0xd1, 0x2c, 0x4e, 0x49, 0x79, 0xd9, 0xad, - 0xf0, 0x08, 0x45, 0xe9, 0x60, 0x99, 0xc5, 0x00, 0x6b, 0xb3, 0x6e, 0x2e, 0xc3, 0x9d, 0xfb, 0xdd, - 0x91, 0x0d, 0x99, 0x97, 0xb7, 0x48, 0xa8, 0x70, 0x6e, 0xe4, 0xc3, 0x40, 0x13, 0xcc, 0x54, 0x65, - 0xa9, 0xe1, 0x61, 0x95, 0x2d, 0xfd, 0xe4, 0x03, 0xee, 0x4b, 0x22, 0xe8, 0x73, 0x2c, 0x24, 0xfc, - 0x15, 0x0a, 0x90, 0xd5, 0x97, 0x19, 0x70, 0x71, 0x68, 0x89, 0x84, 0x3f, 0x07, 0xd0, 0xae, 0xba, - 0xc4, 0x69, 0x13, 0xf3, 0x17, 0xe2, 0x91, 0xc0, 0x66, 0x3a, 0x76, 0x7f, 0x69, 0xfd, 0x2c, 0xcb, - 0xa6, 0xed, 0x9e, 0x53, 0xd4, 0x87, 0x03, 0x1a, 0x60, 0x9e, 0xe5, 0x98, 0xb8, 0x31, 0x2a, 0xc7, - 0xc7, 0xf1, 0x12, 0x78, 0x99, 0x4d, 0x03, 0x9b, 0x51, 0x10, 0x14, 0xc7, 0x84, 0x6b, 0x60, 0x51, - 0x4e, 0x32, 0x89, 0x1b, 0x3c, 0x27, 0xfd, 0xbc, 0xb8, 0x1e, 0x3f, 0x46, 0x49, 0x7a, 0x06, 0x61, - 0x12, 0x97, 0x3a, 0xc4, 0x0c, 0x20, 0x32, 0x71, 0x88, 0xbb, 0xf1, 0x63, 0x94, 0xa4, 0x87, 0x35, - 0xb0, 0x20, 0x51, 0xe5, 0xad, 0xe6, 0x26, 0x79, 0x4c, 0x8c, 0x1e, 0x32, 0x65, 0x5b, 0x0a, 0xe2, - 0x7b, 0x3d, 0x06, 0x83, 0x12, 0xb0, 0xd0, 0x06, 0xc0, 0xf0, 0x8b, 0xa6, 0x9b, 0x9b, 0xe2, 0x42, - 0xee, 0x8c, 0x1f, 0x25, 0x41, 0xe1, 0x0d, 0x3b, 0x7a, 0xb0, 0xe5, 0xa2, 0x88, 0x08, 0xf5, 0x6f, - 0x0a, 0x58, 0x4a, 0x0e, 0xa9, 0xc1, 0x7b, 0x40, 0x19, 0xf8, 0x1e, 0xf8, 0x1d, 0x98, 0x11, 0x33, - 0x8f, 0xed, 0xc8, 0x6b, 0xff, 0xd1, 0x21, 0xcb, 0x1a, 0xae, 0x92, 0x7a, 0x45, 0xb2, 0x8a, 0x20, - 0xf6, 0x57, 0x28, 0x80, 0x54, 0x5f, 0x64, 0x00, 0x08, 0x73, 0x0a, 0xde, 0x8c, 0xf5, 0xb1, 0xd5, - 0x44, 0x1f, 0x5b, 0x8a, 0x3e, 0x2e, 0x22, 0x3d, 0xeb, 0x01, 0x98, 0xb2, 0x79, 0x99, 0x91, 0x1a, - 0x5e, 0x1f, 0xe2, 0xc7, 0x60, 0xde, 0x09, 0x80, 0x74, 0xc0, 0x1a, 0x83, 0xac, 0x53, 0x12, 0x08, - 0x6e, 0x80, 0x4c, 0xd3, 0x36, 0xfd, 0x29, 0x65, 0xd8, 0x58, 0x57, 0xb6, 0x4d, 0x37, 0x06, 0x37, - 0xc3, 0x34, 0x66, 0xbb, 0x88, 0x43, 0xb0, 0x29, 0xd1, 0xff, 0x94, 0xc0, 0xc3, 0x31, 0x5b, 0x2a, - 0x0e, 0x81, 0xeb, 0xf7, 0x60, 0x17, 0xde, 0xf3, 0x4f, 0x50, 0x00, 0x07, 0xff, 0x08, 0x96, 0x8d, - 0xe4, 0x03, 0x38, 0x37, 0x3d, 0x72, 0xb0, 0x1a, 0xfa, 0x75, 0x40, 0x3f, 0xd3, 0xed, 0x14, 0x96, - 0x7b, 0x48, 0x50, 0xaf, 0x24, 0x66, 0x19, 0x91, 0xef, 0x26, 0x59, 0xe7, 0x86, 0x59, 0xd6, 0xef, - 0x85, 0x28, 0x2c, 0xf3, 0x4f, 0x50, 0x00, 0xa7, 0xfe, 0x3d, 0x03, 0xe6, 0x62, 0x6f, 0xb1, 0x63, - 0x8e, 0x0c, 0x91, 0xcc, 0x47, 0x16, 0x19, 0x02, 0xee, 0x48, 0x23, 0x43, 0x40, 0x1e, 0x53, 0x64, - 0x08, 0x61, 0xc7, 0x14, 0x19, 0x11, 0xcb, 0xfa, 0x44, 0xc6, 0x27, 0x29, 0x3f, 0x32, 0xc4, 0xb0, - 0x70, 0xb8, 0xc8, 0x10, 0xb4, 0x91, 0xc8, 0xd8, 0x8e, 0x3e, 0x6f, 0x47, 0xcc, 0x6a, 0x9a, 0xef, - 0x56, 0xed, 0x41, 0x0b, 0x5b, 0x1e, 0xf5, 0x0e, 0xf4, 0xd9, 0x9e, 0xa7, 0xb0, 0x09, 0xe6, 0x70, - 0x9b, 0x38, 0xb8, 0x46, 0xf8, 0xb6, 0x8c, 0x8f, 0x71, 0x71, 0x97, 0xd8, 0x4b, 0x74, 0x2d, 0x82, - 0x83, 0x62, 0xa8, 0xac, 0xa5, 0xcb, 0xf5, 0x23, 0x2f, 0x78, 0xe2, 0xca, 0x2e, 0xc7, 0x5b, 0xfa, - 0x5a, 0xcf, 0x29, 0xea, 0xc3, 0xa1, 0xfe, 0x35, 0x05, 0x96, 0x7b, 0x3e, 0x2e, 0x84, 0x4e, 0x51, - 0x3e, 0x92, 0x53, 0x52, 0xc7, 0xe8, 0x94, 0xf4, 0xd8, 0x4e, 0xf9, 0x77, 0x0a, 0xc0, 0xde, 0xfe, - 0x00, 0x0f, 0xf8, 0x58, 0x61, 0x38, 0xb4, 0x4a, 0x4c, 0x71, 0xfc, 0x2d, 0x67, 0xe0, 0xe8, 0x38, - 0x12, 0x85, 0x45, 0x49, 0x39, 0x47, 0xff, 0x91, 0x35, 0xfc, 0xa4, 0x95, 0x3e, 0xb2, 0x4f, 0x5a, - 0xea, 0xff, 0x92, 0x7e, 0x3b, 0x81, 0x9f, 0xcf, 0xfa, 0xdd, 0x72, 0xfa, 0x78, 0x6e, 0x59, 0xfd, - 0x8f, 0x02, 0x96, 0x92, 0x63, 0xc4, 0x09, 0xf9, 0x76, 0xfa, 0xff, 0xb8, 0xea, 0x27, 0xf1, 0xbb, - 0xe9, 0x4b, 0x05, 0x9c, 0x3e, 0x39, 0x7f, 0x93, 0xa8, 0xff, 0xea, 0x55, 0xf7, 0x04, 0xfc, 0xd9, - 0xa1, 0xff, 0xf4, 0xf5, 0xbb, 0xfc, 0xc4, 0x9b, 0x77, 0xf9, 0x89, 0xb7, 0xef, 0xf2, 0x13, 0x7f, - 0xea, 0xe6, 0x95, 0xd7, 0xdd, 0xbc, 0xf2, 0xa6, 0x9b, 0x57, 0xde, 0x76, 0xf3, 0xca, 0x17, 0xdd, - 0xbc, 0xf2, 0x97, 0x2f, 0xf3, 0x13, 0xbf, 0x39, 0x3f, 0xf0, 0x9f, 0xc2, 0x6f, 0x02, 0x00, 0x00, - 0xff, 0xff, 0xca, 0x8b, 0x47, 0xba, 0x45, 0x1c, 0x00, 0x00, + // 1742 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xc9, 0x8f, 0x1b, 0x4b, + 0x19, 0x9f, 0xb6, 0x3d, 0x5b, 0x79, 0xd6, 0xca, 0xe6, 0x4c, 0x14, 0x7b, 0xd4, 0x04, 0xb2, 0x40, + 0xda, 0xc4, 0x84, 0x28, 0x22, 0x07, 0x34, 0x3d, 0x01, 0x32, 0xca, 0x0c, 0xe3, 0x94, 0x27, 0x19, + 0x76, 0xa5, 0xdc, 0x5d, 0xe3, 0x29, 0xc6, 0xee, 0xb6, 0xba, 0xdb, 0x4e, 0x26, 0x12, 0x12, 0x17, + 0xee, 0x08, 0x14, 0xf1, 0x4f, 0x44, 0x9c, 0x40, 0xe1, 0x00, 0x12, 0x12, 0x1c, 0x72, 0x41, 0xca, + 0x81, 0x43, 0x4e, 0x16, 0x31, 0xd2, 0x3b, 0xbe, 0xe3, 0x3b, 0xe4, 0xf4, 0x54, 0x4b, 0xaf, 0xde, + 0xc6, 0x79, 0x93, 0x91, 0xe6, 0xe6, 0xaa, 0xfa, 0xbe, 0xdf, 0xb7, 0xd4, 0xb7, 0x55, 0x1b, 0x5c, + 0x3f, 0xb8, 0xeb, 0x6a, 0xd4, 0x2e, 0xe2, 0x26, 0x2d, 0xe2, 0x96, 0x67, 0xbb, 0x06, 0xae, 0x53, + 0xab, 0x56, 0x6c, 0x97, 0x8a, 0x35, 0x62, 0x11, 0x07, 0x7b, 0xc4, 0xd4, 0x9a, 0x8e, 0xed, 0xd9, + 0xf0, 0xa2, 0x20, 0xd5, 0x70, 0x93, 0x6a, 0x11, 0x52, 0xad, 0x5d, 0x5a, 0xb9, 0x59, 0xa3, 0xde, + 0x7e, 0xab, 0xaa, 0x19, 0x76, 0xa3, 0x58, 0xb3, 0x6b, 0x76, 0x91, 0x73, 0x54, 0x5b, 0x7b, 0x7c, + 0xc5, 0x17, 0xfc, 0x97, 0x40, 0x5a, 0x51, 0x23, 0x42, 0x0d, 0xdb, 0x21, 0xc5, 0xf6, 0xad, 0xa4, + 0xb4, 0x95, 0xdb, 0x21, 0x4d, 0x03, 0x1b, 0xfb, 0xd4, 0x22, 0xce, 0x61, 0xb1, 0x79, 0x50, 0xe3, + 0x4c, 0x0e, 0x71, 0xed, 0x96, 0x63, 0x90, 0xb1, 0xb8, 0xdc, 0x62, 0x83, 0x78, 0xb8, 0x9f, 0xac, + 0xe2, 0x20, 0x2e, 0xa7, 0x65, 0x79, 0xb4, 0xd1, 0x2b, 0xe6, 0xce, 0x28, 0x06, 0xd7, 0xd8, 0x27, + 0x0d, 0x9c, 0xe4, 0x53, 0x3f, 0x53, 0xc0, 0xe5, 0x75, 0xdb, 0xf2, 0x30, 0xe3, 0x40, 0xd2, 0x88, + 0x2d, 0xe2, 0x39, 0xd4, 0xa8, 0xf0, 0xdf, 0x70, 0x1d, 0x64, 0x2c, 0xdc, 0x20, 0x39, 0x65, 0x55, + 0xb9, 0x36, 0xab, 0x17, 0xdf, 0x74, 0x0a, 0x13, 0xdd, 0x4e, 0x21, 0xf3, 0x63, 0xdc, 0x20, 0x1f, + 0x3a, 0x85, 0x42, 0xaf, 0xe3, 0x34, 0x1f, 0x86, 0x91, 0x20, 0xce, 0x0c, 0xb7, 0xc1, 0x94, 0x87, + 0x9d, 0x1a, 0xf1, 0x72, 0xa9, 0x55, 0xe5, 0x5a, 0xb6, 0x74, 0x55, 0x1b, 0x78, 0x75, 0x9a, 0x90, + 0xbe, 0xc3, 0xc9, 0xf5, 0x05, 0x29, 0x6f, 0x4a, 0xac, 0x91, 0x84, 0x81, 0x45, 0x30, 0x6b, 0xf8, + 0x6a, 0xe7, 0xd2, 0x5c, 0xb5, 0x65, 0x49, 0x3a, 0x1b, 0xda, 0x13, 0xd2, 0xa8, 0x9f, 0x0f, 0x31, + 0xd4, 0xc3, 0x5e, 0xcb, 0x3d, 0x1e, 0x43, 0x77, 0xc1, 0xb4, 0xd1, 0x72, 0x1c, 0x62, 0xf9, 0x96, + 0x7e, 0x6b, 0xa4, 0xa5, 0x4f, 0x70, 0xbd, 0x45, 0x84, 0x0e, 0xfa, 0xa2, 0x94, 0x3a, 0xbd, 0x2e, + 0x40, 0x90, 0x8f, 0x36, 0xbe, 0xc1, 0x2f, 0x15, 0x70, 0x69, 0xdd, 0xb1, 0x5d, 0xf7, 0x09, 0x71, + 0x5c, 0x6a, 0x5b, 0xdb, 0xd5, 0x5f, 0x13, 0xc3, 0x43, 0x64, 0x8f, 0x38, 0xc4, 0x32, 0x08, 0x5c, + 0x05, 0x99, 0x03, 0x6a, 0x99, 0xd2, 0xdc, 0x39, 0xdf, 0xdc, 0x87, 0xd4, 0x32, 0x11, 0x3f, 0x61, + 0x14, 0xdc, 0x21, 0xa9, 0x38, 0x45, 0xc4, 0xda, 0x12, 0x00, 0xb8, 0x49, 0xa5, 0x00, 0xa9, 0x15, + 0x94, 0x74, 0x60, 0xad, 0xbc, 0x21, 0x4f, 0x50, 0x84, 0x4a, 0xfd, 0xbb, 0x02, 0xce, 0xfe, 0xe0, + 0xb9, 0x47, 0x1c, 0x0b, 0xd7, 0x63, 0x81, 0x56, 0x01, 0x53, 0x0d, 0xbe, 0xe6, 0x2a, 0x65, 0x4b, + 0xdf, 0x1c, 0xe9, 0xb9, 0x0d, 0x93, 0x58, 0x1e, 0xdd, 0xa3, 0xc4, 0x09, 0xe3, 0x44, 0x9c, 0x20, + 0x09, 0x75, 0xec, 0x81, 0xa7, 0xfe, 0xbb, 0x57, 0x7d, 0x11, 0x3e, 0x9f, 0x44, 0xfd, 0x4f, 0x15, + 0x4e, 0xea, 0x9f, 0x15, 0xb0, 0xf4, 0xa0, 0xbc, 0x56, 0x11, 0xdc, 0x65, 0xbb, 0x4e, 0x8d, 0x43, + 0x78, 0x17, 0x64, 0xbc, 0xc3, 0xa6, 0x9f, 0x01, 0x57, 0xfc, 0x0b, 0xdf, 0x39, 0x6c, 0xb2, 0x0c, + 0x38, 0x9b, 0xa4, 0x67, 0xfb, 0x88, 0x73, 0xc0, 0xaf, 0x81, 0xc9, 0x36, 0x93, 0xcb, 0xb5, 0x9c, + 0xd4, 0xe7, 0x25, 0xeb, 0x24, 0x57, 0x06, 0x89, 0x33, 0x78, 0x0f, 0xcc, 0x37, 0x89, 0x43, 0x6d, + 0xb3, 0x42, 0x0c, 0xdb, 0x32, 0x5d, 0x1e, 0x30, 0x93, 0xfa, 0x39, 0x49, 0x3c, 0x5f, 0x8e, 0x1e, + 0xa2, 0x38, 0xad, 0xfa, 0x45, 0x0a, 0x2c, 0x86, 0x0a, 0xa0, 0x56, 0x9d, 0xb8, 0xf0, 0x57, 0x60, + 0xc5, 0xf5, 0x70, 0x95, 0xd6, 0xe9, 0x0b, 0xec, 0x51, 0xdb, 0xda, 0xa5, 0x96, 0x69, 0x3f, 0x8b, + 0xa3, 0xe7, 0xbb, 0x9d, 0xc2, 0x4a, 0x65, 0x20, 0x15, 0x1a, 0x82, 0x00, 0x1f, 0x82, 0x39, 0x97, + 0xd4, 0x89, 0xe1, 0x09, 0x7b, 0xa5, 0x5f, 0xae, 0x76, 0x3b, 0x85, 0xb9, 0x4a, 0x64, 0xff, 0x43, + 0xa7, 0x70, 0x26, 0xe6, 0x18, 0x71, 0x88, 0x62, 0xcc, 0xf0, 0xa7, 0x60, 0xa6, 0xc9, 0x7e, 0x51, + 0xe2, 0xe6, 0x52, 0xab, 0xe9, 0x11, 0x11, 0x92, 0xf4, 0xb5, 0xbe, 0x24, 0xbd, 0x34, 0x53, 0x96, + 0x20, 0x28, 0x80, 0x83, 0x3f, 0x07, 0xb3, 0x9e, 0x5d, 0x27, 0x0e, 0xb6, 0x0c, 0x92, 0xcb, 0xf0, + 0x38, 0xd1, 0x22, 0xd8, 0x41, 0x43, 0xd0, 0x9a, 0x07, 0x35, 0x2e, 0xcc, 0xef, 0x56, 0xda, 0xa3, + 0x16, 0xb6, 0x3c, 0xea, 0x1d, 0xea, 0xf3, 0xac, 0x8e, 0xec, 0xf8, 0x20, 0x28, 0xc4, 0x53, 0x5f, + 0xa7, 0xc0, 0x85, 0x07, 0xb6, 0x43, 0x5f, 0xb0, 0xca, 0x52, 0x2f, 0xdb, 0xe6, 0x9a, 0xd4, 0x94, + 0x38, 0xf0, 0x29, 0x98, 0x61, 0x1d, 0xcc, 0xc4, 0x1e, 0x96, 0x51, 0xff, 0xed, 0x61, 0x72, 0x5d, + 0x8d, 0x51, 0x6b, 0xed, 0x5b, 0x9a, 0x28, 0x46, 0x5b, 0xc4, 0xc3, 0x61, 0xbd, 0x08, 0xf7, 0x50, + 0x80, 0x0a, 0x7f, 0x02, 0x32, 0x6e, 0x93, 0x18, 0x32, 0xfa, 0xef, 0x0c, 0xf3, 0x58, 0x7f, 0x1d, + 0x2b, 0x4d, 0x62, 0x84, 0xb5, 0x8b, 0xad, 0x10, 0x47, 0x84, 0x4f, 0xc1, 0x94, 0xcb, 0xb3, 0x84, + 0x07, 0x4a, 0xb6, 0x74, 0xf7, 0x23, 0xb0, 0x45, 0x96, 0x05, 0xc9, 0x2b, 0xd6, 0x48, 0xe2, 0xaa, + 0xff, 0x51, 0x40, 0x61, 0x00, 0xa7, 0x4e, 0xf6, 0x71, 0x9b, 0xda, 0x0e, 0x7c, 0x04, 0xa6, 0xf9, + 0xce, 0xe3, 0xa6, 0x74, 0xe0, 0x8d, 0x23, 0x05, 0x05, 0x8f, 0x7f, 0x3d, 0xcb, 0x52, 0xbb, 0x22, + 0xd8, 0x91, 0x8f, 0x03, 0x77, 0xc1, 0x2c, 0xff, 0x79, 0xdf, 0x7e, 0x66, 0x49, 0xbf, 0x8d, 0x03, + 0xca, 0x23, 0xa1, 0xe2, 0x03, 0xa0, 0x10, 0x4b, 0xfd, 0x5d, 0x1a, 0xac, 0x0e, 0xb0, 0x67, 0xdd, + 0xb6, 0x4c, 0xca, 0x12, 0x08, 0x3e, 0x88, 0xd5, 0x90, 0xdb, 0x89, 0x1a, 0x72, 0x65, 0x14, 0x7f, + 0xa4, 0xa6, 0x6c, 0x06, 0x17, 0x94, 0x8a, 0x61, 0x49, 0x37, 0x7f, 0xe8, 0x14, 0xfa, 0x4c, 0x6d, + 0x5a, 0x80, 0x14, 0xbf, 0x0c, 0xd8, 0x06, 0xb0, 0x8e, 0x5d, 0x6f, 0xc7, 0xc1, 0x96, 0x2b, 0x24, + 0xd1, 0x06, 0x91, 0x57, 0x7f, 0xe3, 0x68, 0x41, 0xcb, 0x38, 0xf4, 0x15, 0xa9, 0x05, 0xdc, 0xec, + 0x41, 0x43, 0x7d, 0x24, 0xc0, 0x6f, 0x80, 0x29, 0x87, 0x60, 0xd7, 0xb6, 0x78, 0x62, 0xce, 0x86, + 0xc1, 0x82, 0xf8, 0x2e, 0x92, 0xa7, 0xf0, 0x3a, 0x98, 0x6e, 0x10, 0xd7, 0xc5, 0x35, 0x92, 0x9b, + 0xe4, 0x84, 0x41, 0xed, 0xde, 0x12, 0xdb, 0xc8, 0x3f, 0x57, 0xff, 0xab, 0x80, 0x4b, 0x03, 0xfc, + 0xb8, 0x49, 0x5d, 0x0f, 0xfe, 0xa2, 0x27, 0x2b, 0xb5, 0xa3, 0x19, 0xc8, 0xb8, 0x79, 0x4e, 0x06, + 0xc5, 0xc6, 0xdf, 0x89, 0x64, 0xe4, 0x2e, 0x98, 0xa4, 0x1e, 0x69, 0xf8, 0x45, 0xac, 0x34, 0x7e, + 0xda, 0x84, 0xed, 0x61, 0x83, 0x01, 0x21, 0x81, 0xa7, 0xbe, 0x4e, 0x0f, 0x34, 0x8b, 0xa5, 0x2d, + 0x6c, 0x83, 0x05, 0xbe, 0x92, 0x0d, 0x99, 0xec, 0x49, 0xe3, 0x86, 0x15, 0x85, 0x21, 0x03, 0x90, + 0x7e, 0x5e, 0x6a, 0xb1, 0x50, 0x89, 0xa1, 0xa2, 0x84, 0x14, 0x78, 0x0b, 0x64, 0x1b, 0xd4, 0x42, + 0xa4, 0x59, 0xa7, 0x06, 0x76, 0x65, 0x87, 0x5b, 0xec, 0x76, 0x0a, 0xd9, 0xad, 0x70, 0x1b, 0x45, + 0x69, 0xe0, 0x77, 0x41, 0xb6, 0x81, 0x9f, 0x07, 0x2c, 0xa2, 0x13, 0x9d, 0x91, 0xf2, 0xb2, 0x5b, + 0xe1, 0x11, 0x8a, 0xd2, 0xc1, 0x32, 0x8b, 0x01, 0xd6, 0xc3, 0xdd, 0x5c, 0x86, 0x3b, 0xf7, 0xeb, + 0x23, 0xbb, 0x3d, 0x2f, 0x6f, 0x91, 0x50, 0xe1, 0xdc, 0xc8, 0x87, 0x81, 0x26, 0x98, 0xa9, 0xca, + 0x52, 0xc3, 0xc3, 0x2a, 0x5b, 0xfa, 0xde, 0x47, 0xdc, 0x97, 0x44, 0xd0, 0xe7, 0x58, 0x48, 0xf8, + 0x2b, 0x14, 0x20, 0xab, 0xaf, 0x32, 0xe0, 0xf2, 0xd0, 0x12, 0x09, 0x7f, 0x08, 0xa0, 0x5d, 0x75, + 0x89, 0xd3, 0x26, 0xe6, 0x8f, 0xc4, 0x0b, 0x84, 0x0d, 0x8c, 0xec, 0xfe, 0xd2, 0xfa, 0x79, 0x96, + 0x4d, 0xdb, 0x3d, 0xa7, 0xa8, 0x0f, 0x07, 0x34, 0xc0, 0x3c, 0xcb, 0x31, 0x71, 0x63, 0x54, 0xce, + 0xa6, 0xe3, 0x25, 0xf0, 0x32, 0x1b, 0x35, 0x36, 0xa3, 0x20, 0x28, 0x8e, 0x09, 0xd7, 0xc0, 0xa2, + 0x1c, 0x93, 0x12, 0x37, 0x78, 0x41, 0xfa, 0x79, 0x71, 0x3d, 0x7e, 0x8c, 0x92, 0xf4, 0x0c, 0xc2, + 0x24, 0x2e, 0x75, 0x88, 0x19, 0x40, 0x64, 0xe2, 0x10, 0xf7, 0xe3, 0xc7, 0x28, 0x49, 0x0f, 0x6b, + 0x60, 0x41, 0xa2, 0xca, 0x5b, 0xcd, 0x4d, 0xf2, 0x98, 0x18, 0x3d, 0xc1, 0xca, 0xb6, 0x14, 0xc4, + 0xf7, 0x7a, 0x0c, 0x06, 0x25, 0x60, 0xa1, 0x0d, 0x80, 0xe1, 0x17, 0x4d, 0x37, 0x37, 0xc5, 0x85, + 0xdc, 0x1b, 0x3f, 0x4a, 0x82, 0xc2, 0x1b, 0x76, 0xf4, 0x60, 0xcb, 0x45, 0x11, 0x11, 0xea, 0x1f, + 0x15, 0xb0, 0x94, 0x9c, 0x80, 0x83, 0xc7, 0x86, 0x32, 0xf0, 0xb1, 0xf1, 0x4b, 0x30, 0x23, 0x06, + 0x2a, 0xdb, 0x91, 0xd7, 0xfe, 0x9d, 0x23, 0x96, 0x35, 0x5c, 0x25, 0xf5, 0x8a, 0x64, 0x15, 0x41, + 0xec, 0xaf, 0x50, 0x00, 0xa9, 0xbe, 0xcc, 0x00, 0x10, 0xe6, 0x14, 0xbc, 0x1d, 0xeb, 0x63, 0xab, + 0x89, 0x3e, 0xb6, 0x14, 0x7d, 0xb9, 0x44, 0x7a, 0xd6, 0x23, 0x30, 0x65, 0xf3, 0x32, 0x23, 0x35, + 0xbc, 0x39, 0xc4, 0x8f, 0xc1, 0xbc, 0x13, 0x00, 0xe9, 0x80, 0x35, 0x06, 0x59, 0xa7, 0x24, 0x10, + 0xdc, 0x00, 0x99, 0xa6, 0x6d, 0xfa, 0x53, 0xca, 0xb0, 0x99, 0xb1, 0x6c, 0x9b, 0x6e, 0x0c, 0x6e, + 0x86, 0x69, 0xcc, 0x76, 0x11, 0x87, 0x60, 0x23, 0xa8, 0x3f, 0xf9, 0xc9, 0x31, 0xb1, 0x38, 0x04, + 0xae, 0xdf, 0xd7, 0x00, 0xe1, 0x3d, 0xff, 0x04, 0x05, 0x70, 0xf0, 0x37, 0x60, 0xd9, 0x48, 0xbe, + 0xae, 0x73, 0xd3, 0x23, 0x07, 0xab, 0xa1, 0x9f, 0x1e, 0xf4, 0x73, 0xdd, 0x4e, 0x61, 0xb9, 0x87, + 0x04, 0xf5, 0x4a, 0x62, 0x96, 0x11, 0xf9, 0x28, 0x93, 0x75, 0x6e, 0x98, 0x65, 0xfd, 0x9e, 0x9f, + 0xc2, 0x32, 0xff, 0x04, 0x05, 0x70, 0xea, 0x9f, 0x32, 0x60, 0x2e, 0xf6, 0xd0, 0x3b, 0xe1, 0xc8, + 0x10, 0xc9, 0x7c, 0x6c, 0x91, 0x21, 0xe0, 0x8e, 0x35, 0x32, 0x04, 0xe4, 0x09, 0x45, 0x86, 0x10, + 0x76, 0x42, 0x91, 0x11, 0xb1, 0xac, 0x4f, 0x64, 0xfc, 0x2b, 0xe5, 0x47, 0x86, 0x18, 0x16, 0x8e, + 0x16, 0x19, 0x82, 0x36, 0x12, 0x19, 0xdb, 0xd1, 0xb7, 0xf3, 0xf8, 0x2f, 0xb7, 0xd9, 0x9e, 0x77, + 0xb6, 0x09, 0xe6, 0x70, 0x9b, 0x38, 0xb8, 0x46, 0xf8, 0xb6, 0x8c, 0x8f, 0x71, 0x71, 0x97, 0xd8, + 0x33, 0x77, 0x2d, 0x82, 0x83, 0x62, 0xa8, 0xac, 0xa5, 0xcb, 0xf5, 0x63, 0x2f, 0x78, 0x3f, 0xcb, + 0x2e, 0xc7, 0x5b, 0xfa, 0x5a, 0xcf, 0x29, 0xea, 0xc3, 0xa1, 0xfe, 0x21, 0x05, 0x96, 0x7b, 0xbe, + 0x5c, 0x84, 0x4e, 0x51, 0x3e, 0x91, 0x53, 0x52, 0x27, 0xe8, 0x94, 0xf4, 0xd8, 0x4e, 0xf9, 0x6b, + 0x0a, 0xc0, 0xde, 0xfe, 0x00, 0x0f, 0xf9, 0x58, 0x61, 0x38, 0xb4, 0x4a, 0x4c, 0x71, 0xfc, 0x15, + 0x67, 0xe0, 0xe8, 0x38, 0x12, 0x85, 0x45, 0x49, 0x39, 0xc7, 0xff, 0x05, 0x37, 0xfc, 0x5e, 0x96, + 0x3e, 0xb6, 0xef, 0x65, 0xea, 0x3f, 0x92, 0x7e, 0x3b, 0x85, 0xdf, 0xe6, 0xfa, 0xdd, 0x72, 0xfa, + 0x64, 0x6e, 0x59, 0xfd, 0x9b, 0x02, 0x96, 0x92, 0x63, 0xc4, 0x29, 0xf9, 0x30, 0xfb, 0xcf, 0xb8, + 0xea, 0xa7, 0xf1, 0xa3, 0xec, 0x2b, 0x05, 0x9c, 0x3d, 0x3d, 0xff, 0xc1, 0xa8, 0x7f, 0xe9, 0x55, + 0xf7, 0x14, 0xfc, 0x93, 0xa2, 0x7f, 0xff, 0xcd, 0xfb, 0xfc, 0xc4, 0xdb, 0xf7, 0xf9, 0x89, 0x77, + 0xef, 0xf3, 0x13, 0xbf, 0xed, 0xe6, 0x95, 0x37, 0xdd, 0xbc, 0xf2, 0xb6, 0x9b, 0x57, 0xde, 0x75, + 0xf3, 0xca, 0xff, 0xba, 0x79, 0xe5, 0xf7, 0xff, 0xcf, 0x4f, 0xfc, 0xec, 0xe2, 0xc0, 0xbf, 0x21, + 0xbf, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x23, 0xae, 0x54, 0xa2, 0x1c, 0x00, 0x00, } func (m *ContainerResourceMetricSource) Marshal() (dAtA []byte, err error) { @@ -1126,6 +1127,18 @@ func (m *HPAScalingRules) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Tolerance != nil { + { + size, err := m.Tolerance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } if m.StabilizationWindowSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.StabilizationWindowSeconds)) i-- @@ -2203,6 +2216,10 @@ func (m *HPAScalingRules) Size() (n int) { if m.StabilizationWindowSeconds != nil { n += 1 + sovGenerated(uint64(*m.StabilizationWindowSeconds)) } + if m.Tolerance != nil { + l = m.Tolerance.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -2619,6 +2636,7 @@ func (this *HPAScalingRules) String() string { `SelectPolicy:` + valueToStringGenerated(this.SelectPolicy) + `,`, `Policies:` + repeatedStringForPolicies + `,`, `StabilizationWindowSeconds:` + valueToStringGenerated(this.StabilizationWindowSeconds) + `,`, + `Tolerance:` + strings.Replace(fmt.Sprintf("%v", this.Tolerance), "Quantity", "resource.Quantity", 1) + `,`, `}`, }, "") return s @@ -3770,6 +3788,42 @@ func (m *HPAScalingRules) Unmarshal(dAtA []byte) error { } } m.StabilizationWindowSeconds = &v + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tolerance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Tolerance == nil { + m.Tolerance = &resource.Quantity{} + } + if err := m.Tolerance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.proto b/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.proto index 4e6dc0592a..04c34d6e16 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.proto +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/generated.proto @@ -112,12 +112,18 @@ message HPAScalingPolicy { optional int32 periodSeconds = 3; } -// HPAScalingRules configures the scaling behavior for one direction. -// These Rules are applied after calculating DesiredReplicas from metrics for the HPA. +// HPAScalingRules configures the scaling behavior for one direction via +// scaling Policy Rules and a configurable metric tolerance. +// +// Scaling Policy Rules are applied after calculating DesiredReplicas from metrics for the HPA. // They can limit the scaling velocity by specifying scaling policies. // They can prevent flapping by specifying the stabilization window, so that the // number of replicas is not set instantly, instead, the safest value from the stabilization // window is chosen. +// +// The tolerance is applied to the metric values and prevents scaling too +// eagerly for small metric variations. (Note that setting a tolerance requires +// enabling the alpha HPAConfigurableTolerance feature gate.) message HPAScalingRules { // stabilizationWindowSeconds is the number of seconds for which past recommendations should be // considered while scaling up or scaling down. @@ -134,10 +140,28 @@ message HPAScalingRules { optional string selectPolicy = 1; // policies is a list of potential scaling polices which can be used during scaling. - // At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid + // If not set, use the default values: + // - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + // - For scale down: allow all pods to be removed in a 15s window. // +listType=atomic // +optional repeated HPAScalingPolicy policies = 2; + + // tolerance is the tolerance on the ratio between the current and desired + // metric value under which no updates are made to the desired number of + // replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + // set, the default cluster-wide tolerance is applied (by default 10%). + // + // For example, if autoscaling is configured with a memory consumption target of 100Mi, + // and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + // triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + // + // This is an alpha field and requires enabling the HPAConfigurableTolerance + // feature gate. + // + // +featureGate=HPAConfigurableTolerance + // +optional + optional .k8s.io.apimachinery.pkg.api.resource.Quantity tolerance = 4; } // HorizontalPodAutoscaler is the configuration for a horizontal pod diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/types.go b/go-controller/vendor/k8s.io/api/autoscaling/v2/types.go index 99e8db09dc..9ce69b1edc 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/types.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/types.go @@ -171,12 +171,18 @@ const ( DisabledPolicySelect ScalingPolicySelect = "Disabled" ) -// HPAScalingRules configures the scaling behavior for one direction. -// These Rules are applied after calculating DesiredReplicas from metrics for the HPA. +// HPAScalingRules configures the scaling behavior for one direction via +// scaling Policy Rules and a configurable metric tolerance. +// +// Scaling Policy Rules are applied after calculating DesiredReplicas from metrics for the HPA. // They can limit the scaling velocity by specifying scaling policies. // They can prevent flapping by specifying the stabilization window, so that the // number of replicas is not set instantly, instead, the safest value from the stabilization // window is chosen. +// +// The tolerance is applied to the metric values and prevents scaling too +// eagerly for small metric variations. (Note that setting a tolerance requires +// enabling the alpha HPAConfigurableTolerance feature gate.) type HPAScalingRules struct { // stabilizationWindowSeconds is the number of seconds for which past recommendations should be // considered while scaling up or scaling down. @@ -193,10 +199,28 @@ type HPAScalingRules struct { SelectPolicy *ScalingPolicySelect `json:"selectPolicy,omitempty" protobuf:"bytes,1,opt,name=selectPolicy"` // policies is a list of potential scaling polices which can be used during scaling. - // At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid + // If not set, use the default values: + // - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + // - For scale down: allow all pods to be removed in a 15s window. // +listType=atomic // +optional Policies []HPAScalingPolicy `json:"policies,omitempty" listType:"atomic" protobuf:"bytes,2,rep,name=policies"` + + // tolerance is the tolerance on the ratio between the current and desired + // metric value under which no updates are made to the desired number of + // replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + // set, the default cluster-wide tolerance is applied (by default 10%). + // + // For example, if autoscaling is configured with a memory consumption target of 100Mi, + // and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + // triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + // + // This is an alpha field and requires enabling the HPAConfigurableTolerance + // feature gate. + // + // +featureGate=HPAConfigurableTolerance + // +optional + Tolerance *resource.Quantity `json:"tolerance,omitempty" protobuf:"bytes,4,opt,name=tolerance"` } // HPAScalingPolicyType is the type of the policy which could be used while making scaling decisions. diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/autoscaling/v2/types_swagger_doc_generated.go index 649cd04a03..017fefcde7 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/types_swagger_doc_generated.go @@ -92,10 +92,11 @@ func (HPAScalingPolicy) SwaggerDoc() map[string]string { } var map_HPAScalingRules = map[string]string{ - "": "HPAScalingRules configures the scaling behavior for one direction. These Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.", + "": "HPAScalingRules configures the scaling behavior for one direction via scaling Policy Rules and a configurable metric tolerance.\n\nScaling Policy Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.\n\nThe tolerance is applied to the metric values and prevents scaling too eagerly for small metric variations. (Note that setting a tolerance requires enabling the alpha HPAConfigurableTolerance feature gate.)", "stabilizationWindowSeconds": "stabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).", "selectPolicy": "selectPolicy is used to specify which policy should be used. If not set, the default value Max is used.", - "policies": "policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid", + "policies": "policies is a list of potential scaling polices which can be used during scaling. If not set, use the default values: - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. - For scale down: allow all pods to be removed in a 15s window.", + "tolerance": "tolerance is the tolerance on the ratio between the current and desired metric value under which no updates are made to the desired number of replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not set, the default cluster-wide tolerance is applied (by default 10%).\n\nFor example, if autoscaling is configured with a memory consumption target of 100Mi, and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be triggered when the actual consumption falls below 95Mi or exceeds 101Mi.\n\nThis is an alpha field and requires enabling the HPAConfigurableTolerance feature gate.", } func (HPAScalingRules) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/autoscaling/v2/zz_generated.deepcopy.go index 125708d6fd..5fbcf9f807 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2/zz_generated.deepcopy.go @@ -146,6 +146,11 @@ func (in *HPAScalingRules) DeepCopyInto(out *HPAScalingRules) { *out = make([]HPAScalingPolicy, len(*in)) copy(*out, *in) } + if in.Tolerance != nil { + in, out := &in.Tolerance, &out.Tolerance + x := (*in).DeepCopy() + *out = &x + } return } diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2beta1/doc.go b/go-controller/vendor/k8s.io/api/autoscaling/v2beta1/doc.go index 25ca507bba..eac92e86e8 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2beta1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v2beta1 // import "k8s.io/api/autoscaling/v2beta1" +package v2beta1 diff --git a/go-controller/vendor/k8s.io/api/autoscaling/v2beta2/doc.go b/go-controller/vendor/k8s.io/api/autoscaling/v2beta2/doc.go index 76fb0aff87..1500372978 100644 --- a/go-controller/vendor/k8s.io/api/autoscaling/v2beta2/doc.go +++ b/go-controller/vendor/k8s.io/api/autoscaling/v2beta2/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v2beta2 // import "k8s.io/api/autoscaling/v2beta2" +package v2beta2 diff --git a/go-controller/vendor/k8s.io/api/batch/v1/doc.go b/go-controller/vendor/k8s.io/api/batch/v1/doc.go index cb5cbb6002..69088e2c5b 100644 --- a/go-controller/vendor/k8s.io/api/batch/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/batch/v1/doc.go @@ -18,4 +18,4 @@ limitations under the License. // +k8s:protobuf-gen=package // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1 // import "k8s.io/api/batch/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/batch/v1/generated.proto b/go-controller/vendor/k8s.io/api/batch/v1/generated.proto index 361ebdca12..d3aeae0adb 100644 --- a/go-controller/vendor/k8s.io/api/batch/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/batch/v1/generated.proto @@ -222,8 +222,6 @@ message JobSpec { // When the field is specified, it must be immutable and works only for the Indexed Jobs. // Once the Job meets the SuccessPolicy, the lingering pods are terminated. // - // This field is beta-level. To use this field, you must enable the - // `JobSuccessPolicy` feature gate (enabled by default). // +optional optional SuccessPolicy successPolicy = 16; @@ -238,8 +236,6 @@ message JobSpec { // batch.kubernetes.io/job-index-failure-count annotation. It can only // be set when Job's completionMode=Indexed, and the Pod's restart // policy is Never. The field is immutable. - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional optional int32 backoffLimitPerIndex = 12; @@ -251,8 +247,6 @@ message JobSpec { // It can only be specified when backoffLimitPerIndex is set. // It can be null or up to completions. It is required and must be // less than or equal to 10^4 when is completions greater than 10^5. - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional optional int32 maxFailedIndexes = 13; @@ -442,8 +436,6 @@ message JobStatus { // represented as "1,3-5,7". // The set of failed indexes cannot overlap with the set of completed indexes. // - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional optional string failedIndexes = 10; @@ -554,8 +546,6 @@ message PodFailurePolicyRule { // running pods are terminated. // - FailIndex: indicates that the pod's index is marked as Failed and will // not be restarted. - // This value is beta-level. It can be used when the - // `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default). // - Ignore: indicates that the counter towards the .backoffLimit is not // incremented and a replacement pod is created. // - Count: indicates that the pod is handled in the default way - the diff --git a/go-controller/vendor/k8s.io/api/batch/v1/types.go b/go-controller/vendor/k8s.io/api/batch/v1/types.go index 8e9a761b95..6c0007c21e 100644 --- a/go-controller/vendor/k8s.io/api/batch/v1/types.go +++ b/go-controller/vendor/k8s.io/api/batch/v1/types.go @@ -128,7 +128,6 @@ const ( // This is an action which might be taken on a pod failure - mark the // Job's index as failed to avoid restarts within this index. This action // can only be used when backoffLimitPerIndex is set. - // This value is beta-level. PodFailurePolicyActionFailIndex PodFailurePolicyAction = "FailIndex" // This is an action which might be taken on a pod failure - the counter towards @@ -223,8 +222,6 @@ type PodFailurePolicyRule struct { // running pods are terminated. // - FailIndex: indicates that the pod's index is marked as Failed and will // not be restarted. - // This value is beta-level. It can be used when the - // `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default). // - Ignore: indicates that the counter towards the .backoffLimit is not // incremented and a replacement pod is created. // - Count: indicates that the pod is handled in the default way - the @@ -346,8 +343,6 @@ type JobSpec struct { // When the field is specified, it must be immutable and works only for the Indexed Jobs. // Once the Job meets the SuccessPolicy, the lingering pods are terminated. // - // This field is beta-level. To use this field, you must enable the - // `JobSuccessPolicy` feature gate (enabled by default). // +optional SuccessPolicy *SuccessPolicy `json:"successPolicy,omitempty" protobuf:"bytes,16,opt,name=successPolicy"` @@ -362,8 +357,6 @@ type JobSpec struct { // batch.kubernetes.io/job-index-failure-count annotation. It can only // be set when Job's completionMode=Indexed, and the Pod's restart // policy is Never. The field is immutable. - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional BackoffLimitPerIndex *int32 `json:"backoffLimitPerIndex,omitempty" protobuf:"varint,12,opt,name=backoffLimitPerIndex"` @@ -375,8 +368,6 @@ type JobSpec struct { // It can only be specified when backoffLimitPerIndex is set. // It can be null or up to completions. It is required and must be // less than or equal to 10^4 when is completions greater than 10^5. - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional MaxFailedIndexes *int32 `json:"maxFailedIndexes,omitempty" protobuf:"varint,13,opt,name=maxFailedIndexes"` @@ -571,8 +562,6 @@ type JobStatus struct { // represented as "1,3-5,7". // The set of failed indexes cannot overlap with the set of completed indexes. // - // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` - // feature gate is enabled (enabled by default). // +optional FailedIndexes *string `json:"failedIndexes,omitempty" protobuf:"bytes,10,opt,name=failedIndexes"` @@ -647,13 +636,9 @@ const ( JobReasonFailedIndexes string = "FailedIndexes" // JobReasonSuccessPolicy reason indicates a SuccessCriteriaMet condition is added due to // a Job met successPolicy. - // https://kep.k8s.io/3998 - // This is currently a beta field. JobReasonSuccessPolicy string = "SuccessPolicy" // JobReasonCompletionsReached reason indicates a SuccessCriteriaMet condition is added due to // a number of succeeded Job pods met completions. - // - https://kep.k8s.io/3998 - // This is currently a beta field. JobReasonCompletionsReached string = "CompletionsReached" ) diff --git a/go-controller/vendor/k8s.io/api/batch/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/batch/v1/types_swagger_doc_generated.go index 893f3371f0..ffd4e4f5fe 100644 --- a/go-controller/vendor/k8s.io/api/batch/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/batch/v1/types_swagger_doc_generated.go @@ -116,10 +116,10 @@ var map_JobSpec = map[string]string{ "completions": "Specifies the desired number of successfully finished pods the job should be run with. Setting to null means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", "activeDeadlineSeconds": "Specifies the duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it; value must be positive integer. If a Job is suspended (at creation or through an update), this timer will effectively be stopped and reset when the Job is resumed again.", "podFailurePolicy": "Specifies the policy of handling failed pods. In particular, it allows to specify the set of actions and conditions which need to be satisfied to take the associated action. If empty, the default behaviour applies - the counter of failed pods, represented by the jobs's .status.failed field, is incremented and it is checked against the backoffLimit. This field cannot be used in combination with restartPolicy=OnFailure.", - "successPolicy": "successPolicy specifies the policy when the Job can be declared as succeeded. If empty, the default behavior applies - the Job is declared as succeeded only when the number of succeeded pods equals to the completions. When the field is specified, it must be immutable and works only for the Indexed Jobs. Once the Job meets the SuccessPolicy, the lingering pods are terminated.\n\nThis field is beta-level. To use this field, you must enable the `JobSuccessPolicy` feature gate (enabled by default).", + "successPolicy": "successPolicy specifies the policy when the Job can be declared as succeeded. If empty, the default behavior applies - the Job is declared as succeeded only when the number of succeeded pods equals to the completions. When the field is specified, it must be immutable and works only for the Indexed Jobs. Once the Job meets the SuccessPolicy, the lingering pods are terminated.", "backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6", - "backoffLimitPerIndex": "Specifies the limit for the number of retries within an index before marking this index as failed. When enabled the number of failures per index is kept in the pod's batch.kubernetes.io/job-index-failure-count annotation. It can only be set when Job's completionMode=Indexed, and the Pod's restart policy is Never. The field is immutable. This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default).", - "maxFailedIndexes": "Specifies the maximal number of failed indexes before marking the Job as failed, when backoffLimitPerIndex is set. Once the number of failed indexes exceeds this number the entire Job is marked as Failed and its execution is terminated. When left as null the job continues execution of all of its indexes and is marked with the `Complete` Job condition. It can only be specified when backoffLimitPerIndex is set. It can be null or up to completions. It is required and must be less than or equal to 10^4 when is completions greater than 10^5. This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default).", + "backoffLimitPerIndex": "Specifies the limit for the number of retries within an index before marking this index as failed. When enabled the number of failures per index is kept in the pod's batch.kubernetes.io/job-index-failure-count annotation. It can only be set when Job's completionMode=Indexed, and the Pod's restart policy is Never. The field is immutable.", + "maxFailedIndexes": "Specifies the maximal number of failed indexes before marking the Job as failed, when backoffLimitPerIndex is set. Once the number of failed indexes exceeds this number the entire Job is marked as Failed and its execution is terminated. When left as null the job continues execution of all of its indexes and is marked with the `Complete` Job condition. It can only be specified when backoffLimitPerIndex is set. It can be null or up to completions. It is required and must be less than or equal to 10^4 when is completions greater than 10^5.", "selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector", "template": "Describes the pod that will be created when executing a job. The only allowed template.spec.restartPolicy values are \"Never\" or \"OnFailure\". More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", @@ -144,7 +144,7 @@ var map_JobStatus = map[string]string{ "failed": "The number of pods which reached phase Failed. The value increases monotonically.", "terminating": "The number of pods which are terminating (in phase Pending or Running and have a deletionTimestamp).\n\nThis field is beta-level. The job controller populates the field when the feature gate JobPodReplacementPolicy is enabled (enabled by default).", "completedIndexes": "completedIndexes holds the completed indexes when .spec.completionMode = \"Indexed\" in a text format. The indexes are represented as decimal integers separated by commas. The numbers are listed in increasing order. Three or more consecutive numbers are compressed and represented by the first and last element of the series, separated by a hyphen. For example, if the completed indexes are 1, 3, 4, 5 and 7, they are represented as \"1,3-5,7\".", - "failedIndexes": "FailedIndexes holds the failed indexes when spec.backoffLimitPerIndex is set. The indexes are represented in the text format analogous as for the `completedIndexes` field, ie. they are kept as decimal integers separated by commas. The numbers are listed in increasing order. Three or more consecutive numbers are compressed and represented by the first and last element of the series, separated by a hyphen. For example, if the failed indexes are 1, 3, 4, 5 and 7, they are represented as \"1,3-5,7\". The set of failed indexes cannot overlap with the set of completed indexes.\n\nThis field is beta-level. It can be used when the `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default).", + "failedIndexes": "FailedIndexes holds the failed indexes when spec.backoffLimitPerIndex is set. The indexes are represented in the text format analogous as for the `completedIndexes` field, ie. they are kept as decimal integers separated by commas. The numbers are listed in increasing order. Three or more consecutive numbers are compressed and represented by the first and last element of the series, separated by a hyphen. For example, if the failed indexes are 1, 3, 4, 5 and 7, they are represented as \"1,3-5,7\". The set of failed indexes cannot overlap with the set of completed indexes.", "uncountedTerminatedPods": "uncountedTerminatedPods holds the UIDs of Pods that have terminated but the job controller hasn't yet accounted for in the status counters.\n\nThe job controller creates pods with a finalizer. When a pod terminates (succeeded or failed), the controller does three steps to account for it in the job status:\n\n1. Add the pod UID to the arrays in this field. 2. Remove the pod finalizer. 3. Remove the pod UID from the arrays while increasing the corresponding\n counter.\n\nOld jobs might not be tracked using this field, in which case the field remains null. The structure is empty for finished jobs.", "ready": "The number of active pods which have a Ready condition and are not terminating (without a deletionTimestamp).", } @@ -195,7 +195,7 @@ func (PodFailurePolicyOnPodConditionsPattern) SwaggerDoc() map[string]string { var map_PodFailurePolicyRule = map[string]string{ "": "PodFailurePolicyRule describes how a pod failure is handled when the requirements are met. One of onExitCodes and onPodConditions, but not both, can be used in each rule.", - "action": "Specifies the action taken on a pod failure when the requirements are satisfied. Possible values are:\n\n- FailJob: indicates that the pod's job is marked as Failed and all\n running pods are terminated.\n- FailIndex: indicates that the pod's index is marked as Failed and will\n not be restarted.\n This value is beta-level. It can be used when the\n `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default).\n- Ignore: indicates that the counter towards the .backoffLimit is not\n incremented and a replacement pod is created.\n- Count: indicates that the pod is handled in the default way - the\n counter towards the .backoffLimit is incremented.\nAdditional values are considered to be added in the future. Clients should react to an unknown action by skipping the rule.", + "action": "Specifies the action taken on a pod failure when the requirements are satisfied. Possible values are:\n\n- FailJob: indicates that the pod's job is marked as Failed and all\n running pods are terminated.\n- FailIndex: indicates that the pod's index is marked as Failed and will\n not be restarted.\n- Ignore: indicates that the counter towards the .backoffLimit is not\n incremented and a replacement pod is created.\n- Count: indicates that the pod is handled in the default way - the\n counter towards the .backoffLimit is incremented.\nAdditional values are considered to be added in the future. Clients should react to an unknown action by skipping the rule.", "onExitCodes": "Represents the requirement on the container exit codes.", "onPodConditions": "Represents the requirement on the pod conditions. The requirement is represented as a list of pod condition patterns. The requirement is satisfied if at least one pattern matches an actual pod condition. At most 20 elements are allowed.", } diff --git a/go-controller/vendor/k8s.io/api/batch/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/batch/v1beta1/doc.go index cb2572f5da..3430d6939d 100644 --- a/go-controller/vendor/k8s.io/api/batch/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/batch/v1beta1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1beta1 // import "k8s.io/api/batch/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/certificates/v1/doc.go b/go-controller/vendor/k8s.io/api/certificates/v1/doc.go index 78434478e8..6c16fc29b8 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=certificates.k8s.io -package v1 // import "k8s.io/api/certificates/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/certificates/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/certificates/v1alpha1/doc.go index d83d0e8207..01481df8e5 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1alpha1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=certificates.k8s.io -package v1alpha1 // import "k8s.io/api/certificates/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/doc.go index 1165518c67..81608a554c 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=certificates.k8s.io -package v1beta1 // import "k8s.io/api/certificates/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.pb.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.pb.go index b6d8ab3f59..199a54496a 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.pb.go @@ -186,10 +186,94 @@ func (m *CertificateSigningRequestStatus) XXX_DiscardUnknown() { var xxx_messageInfo_CertificateSigningRequestStatus proto.InternalMessageInfo +func (m *ClusterTrustBundle) Reset() { *m = ClusterTrustBundle{} } +func (*ClusterTrustBundle) ProtoMessage() {} +func (*ClusterTrustBundle) Descriptor() ([]byte, []int) { + return fileDescriptor_6529c11a462c48a5, []int{5} +} +func (m *ClusterTrustBundle) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClusterTrustBundle) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ClusterTrustBundle) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClusterTrustBundle.Merge(m, src) +} +func (m *ClusterTrustBundle) XXX_Size() int { + return m.Size() +} +func (m *ClusterTrustBundle) XXX_DiscardUnknown() { + xxx_messageInfo_ClusterTrustBundle.DiscardUnknown(m) +} + +var xxx_messageInfo_ClusterTrustBundle proto.InternalMessageInfo + +func (m *ClusterTrustBundleList) Reset() { *m = ClusterTrustBundleList{} } +func (*ClusterTrustBundleList) ProtoMessage() {} +func (*ClusterTrustBundleList) Descriptor() ([]byte, []int) { + return fileDescriptor_6529c11a462c48a5, []int{6} +} +func (m *ClusterTrustBundleList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClusterTrustBundleList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ClusterTrustBundleList) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClusterTrustBundleList.Merge(m, src) +} +func (m *ClusterTrustBundleList) XXX_Size() int { + return m.Size() +} +func (m *ClusterTrustBundleList) XXX_DiscardUnknown() { + xxx_messageInfo_ClusterTrustBundleList.DiscardUnknown(m) +} + +var xxx_messageInfo_ClusterTrustBundleList proto.InternalMessageInfo + +func (m *ClusterTrustBundleSpec) Reset() { *m = ClusterTrustBundleSpec{} } +func (*ClusterTrustBundleSpec) ProtoMessage() {} +func (*ClusterTrustBundleSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_6529c11a462c48a5, []int{7} +} +func (m *ClusterTrustBundleSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClusterTrustBundleSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ClusterTrustBundleSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClusterTrustBundleSpec.Merge(m, src) +} +func (m *ClusterTrustBundleSpec) XXX_Size() int { + return m.Size() +} +func (m *ClusterTrustBundleSpec) XXX_DiscardUnknown() { + xxx_messageInfo_ClusterTrustBundleSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_ClusterTrustBundleSpec proto.InternalMessageInfo + func (m *ExtraValue) Reset() { *m = ExtraValue{} } func (*ExtraValue) ProtoMessage() {} func (*ExtraValue) Descriptor() ([]byte, []int) { - return fileDescriptor_6529c11a462c48a5, []int{5} + return fileDescriptor_6529c11a462c48a5, []int{8} } func (m *ExtraValue) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -221,6 +305,9 @@ func init() { proto.RegisterType((*CertificateSigningRequestSpec)(nil), "k8s.io.api.certificates.v1beta1.CertificateSigningRequestSpec") proto.RegisterMapType((map[string]ExtraValue)(nil), "k8s.io.api.certificates.v1beta1.CertificateSigningRequestSpec.ExtraEntry") proto.RegisterType((*CertificateSigningRequestStatus)(nil), "k8s.io.api.certificates.v1beta1.CertificateSigningRequestStatus") + proto.RegisterType((*ClusterTrustBundle)(nil), "k8s.io.api.certificates.v1beta1.ClusterTrustBundle") + proto.RegisterType((*ClusterTrustBundleList)(nil), "k8s.io.api.certificates.v1beta1.ClusterTrustBundleList") + proto.RegisterType((*ClusterTrustBundleSpec)(nil), "k8s.io.api.certificates.v1beta1.ClusterTrustBundleSpec") proto.RegisterType((*ExtraValue)(nil), "k8s.io.api.certificates.v1beta1.ExtraValue") } @@ -229,64 +316,69 @@ func init() { } var fileDescriptor_6529c11a462c48a5 = []byte{ - // 901 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4d, 0x6f, 0x1b, 0x45, - 0x18, 0xf6, 0xc6, 0x1f, 0xb1, 0xc7, 0x21, 0x6d, 0x47, 0x50, 0x2d, 0x96, 0xea, 0xb5, 0x56, 0x80, - 0xc2, 0xd7, 0x2c, 0xa9, 0x2a, 0x88, 0x72, 0x40, 0xb0, 0x21, 0x42, 0x11, 0x29, 0x48, 0x93, 0x84, - 0x03, 0x42, 0xa2, 0x93, 0xf5, 0xdb, 0xcd, 0x34, 0xdd, 0x0f, 0x76, 0x66, 0x4d, 0x7d, 0xeb, 0x4f, - 0xe0, 0xc8, 0x91, 0xff, 0xc0, 0x9f, 0x08, 0x07, 0xa4, 0x1e, 0x7b, 0x40, 0x16, 0x71, 0xff, 0x45, - 0x4e, 0x68, 0x66, 0xc7, 0x6b, 0xc7, 0x4e, 0x70, 0x69, 0x6f, 0x3b, 0xcf, 0xbc, 0xcf, 0xf3, 0xbc, - 0xf3, 0xce, 0xfb, 0x8e, 0x8d, 0xbc, 0xd3, 0x2d, 0x41, 0x78, 0xe2, 0xb1, 0x94, 0x7b, 0x01, 0x64, - 0x92, 0x3f, 0xe4, 0x01, 0x93, 0x20, 0xbc, 0xc1, 0xe6, 0x31, 0x48, 0xb6, 0xe9, 0x85, 0x10, 0x43, - 0xc6, 0x24, 0xf4, 0x49, 0x9a, 0x25, 0x32, 0xc1, 0x4e, 0x41, 0x20, 0x2c, 0xe5, 0x64, 0x96, 0x40, - 0x0c, 0xa1, 0xf3, 0x71, 0xc8, 0xe5, 0x49, 0x7e, 0x4c, 0x82, 0x24, 0xf2, 0xc2, 0x24, 0x4c, 0x3c, - 0xcd, 0x3b, 0xce, 0x1f, 0xea, 0x95, 0x5e, 0xe8, 0xaf, 0x42, 0xaf, 0xe3, 0xce, 0x26, 0x90, 0x64, - 0xe0, 0x0d, 0x16, 0x3c, 0x3b, 0xf7, 0xa6, 0x31, 0x11, 0x0b, 0x4e, 0x78, 0x0c, 0xd9, 0xd0, 0x4b, - 0x4f, 0x43, 0x05, 0x08, 0x2f, 0x02, 0xc9, 0xae, 0x62, 0x79, 0xd7, 0xb1, 0xb2, 0x3c, 0x96, 0x3c, - 0x82, 0x05, 0xc2, 0xa7, 0xcb, 0x08, 0x22, 0x38, 0x81, 0x88, 0xcd, 0xf3, 0xdc, 0x3f, 0x57, 0xd0, - 0xdb, 0x3b, 0xd3, 0x52, 0x1c, 0xf0, 0x30, 0xe6, 0x71, 0x48, 0xe1, 0xe7, 0x1c, 0x84, 0xc4, 0x0f, - 0x50, 0x53, 0x65, 0xd8, 0x67, 0x92, 0xd9, 0x56, 0xcf, 0xda, 0x68, 0xdf, 0xfd, 0x84, 0x4c, 0x6b, - 0x58, 0x1a, 0x91, 0xf4, 0x34, 0x54, 0x80, 0x20, 0x2a, 0x9a, 0x0c, 0x36, 0xc9, 0x77, 0xc7, 0x8f, - 0x20, 0x90, 0xf7, 0x41, 0x32, 0x1f, 0x9f, 0x8d, 0x9c, 0xca, 0x78, 0xe4, 0xa0, 0x29, 0x46, 0x4b, - 0x55, 0xfc, 0x00, 0xd5, 0x44, 0x0a, 0x81, 0xbd, 0xa2, 0xd5, 0x3f, 0x27, 0x4b, 0x6e, 0x88, 0x5c, - 0x9b, 0xeb, 0x41, 0x0a, 0x81, 0xbf, 0x66, 0xbc, 0x6a, 0x6a, 0x45, 0xb5, 0x32, 0x3e, 0x41, 0x0d, - 0x21, 0x99, 0xcc, 0x85, 0x5d, 0xd5, 0x1e, 0x5f, 0xbc, 0x86, 0x87, 0xd6, 0xf1, 0xd7, 0x8d, 0x4b, - 0xa3, 0x58, 0x53, 0xa3, 0xef, 0xbe, 0xa8, 0x22, 0xf7, 0x5a, 0xee, 0x4e, 0x12, 0xf7, 0xb9, 0xe4, - 0x49, 0x8c, 0xb7, 0x50, 0x4d, 0x0e, 0x53, 0xd0, 0x05, 0x6d, 0xf9, 0xef, 0x4c, 0x52, 0x3e, 0x1c, - 0xa6, 0x70, 0x31, 0x72, 0xde, 0x9c, 0x8f, 0x57, 0x38, 0xd5, 0x0c, 0xbc, 0x5f, 0x1e, 0xa5, 0xa1, - 0xb9, 0xf7, 0x2e, 0x27, 0x72, 0x31, 0x72, 0xae, 0xe8, 0x48, 0x52, 0x2a, 0x5d, 0x4e, 0x17, 0xbf, - 0x87, 0x1a, 0x19, 0x30, 0x91, 0xc4, 0xba, 0xf8, 0xad, 0xe9, 0xb1, 0xa8, 0x46, 0xa9, 0xd9, 0xc5, - 0xef, 0xa3, 0xd5, 0x08, 0x84, 0x60, 0x21, 0xe8, 0x0a, 0xb6, 0xfc, 0x1b, 0x26, 0x70, 0xf5, 0x7e, - 0x01, 0xd3, 0xc9, 0x3e, 0x7e, 0x84, 0xd6, 0x1f, 0x33, 0x21, 0x8f, 0xd2, 0x3e, 0x93, 0x70, 0xc8, - 0x23, 0xb0, 0x6b, 0xba, 0xe6, 0x1f, 0xbc, 0x5c, 0xd7, 0x28, 0x86, 0x7f, 0xdb, 0xa8, 0xaf, 0xef, - 0x5f, 0x52, 0xa2, 0x73, 0xca, 0x78, 0x80, 0xb0, 0x42, 0x0e, 0x33, 0x16, 0x8b, 0xa2, 0x50, 0xca, - 0xaf, 0xfe, 0xbf, 0xfd, 0x3a, 0xc6, 0x0f, 0xef, 0x2f, 0xa8, 0xd1, 0x2b, 0x1c, 0xdc, 0x91, 0x85, - 0xee, 0x5c, 0x7b, 0xcb, 0xfb, 0x5c, 0x48, 0xfc, 0xe3, 0xc2, 0xd4, 0x90, 0x97, 0xcb, 0x47, 0xb1, - 0xf5, 0xcc, 0xdc, 0x34, 0x39, 0x35, 0x27, 0xc8, 0xcc, 0xc4, 0xfc, 0x84, 0xea, 0x5c, 0x42, 0x24, - 0xec, 0x95, 0x5e, 0x75, 0xa3, 0x7d, 0x77, 0xfb, 0xd5, 0xdb, 0xd9, 0x7f, 0xc3, 0xd8, 0xd4, 0xf7, - 0x94, 0x20, 0x2d, 0x74, 0xdd, 0x3f, 0x6a, 0xff, 0x71, 0x40, 0x35, 0x58, 0xf8, 0x5d, 0xb4, 0x9a, - 0x15, 0x4b, 0x7d, 0xbe, 0x35, 0xbf, 0xad, 0xba, 0xc1, 0x44, 0xd0, 0xc9, 0x1e, 0x26, 0x08, 0x09, - 0x1e, 0xc6, 0x90, 0x7d, 0xcb, 0x22, 0xb0, 0x57, 0x8b, 0x26, 0x53, 0x2f, 0xc1, 0x41, 0x89, 0xd2, - 0x99, 0x08, 0xbc, 0x83, 0x6e, 0xc1, 0x93, 0x94, 0x67, 0x4c, 0x37, 0x2b, 0x04, 0x49, 0xdc, 0x17, - 0x76, 0xb3, 0x67, 0x6d, 0xd4, 0xfd, 0xb7, 0xc6, 0x23, 0xe7, 0xd6, 0xee, 0xfc, 0x26, 0x5d, 0x8c, - 0xc7, 0x04, 0x35, 0x72, 0xd5, 0x8b, 0xc2, 0xae, 0xf7, 0xaa, 0x1b, 0x2d, 0xff, 0xb6, 0xea, 0xe8, - 0x23, 0x8d, 0x5c, 0x8c, 0x9c, 0xe6, 0x37, 0x30, 0xd4, 0x0b, 0x6a, 0xa2, 0xf0, 0x47, 0xa8, 0x99, - 0x0b, 0xc8, 0x62, 0x95, 0x62, 0x31, 0x07, 0x65, 0xf1, 0x8f, 0x0c, 0x4e, 0xcb, 0x08, 0x7c, 0x07, - 0x55, 0x73, 0xde, 0x37, 0x73, 0xd0, 0x36, 0x81, 0xd5, 0xa3, 0xbd, 0xaf, 0xa8, 0xc2, 0xb1, 0x8b, - 0x1a, 0x61, 0x96, 0xe4, 0xa9, 0xb0, 0x6b, 0xda, 0x1c, 0x29, 0xf3, 0xaf, 0x35, 0x42, 0xcd, 0x0e, - 0x8e, 0x51, 0x1d, 0x9e, 0xc8, 0x8c, 0xd9, 0x0d, 0x7d, 0x7f, 0x7b, 0xaf, 0xf7, 0xe4, 0x91, 0x5d, - 0xa5, 0xb5, 0x1b, 0xcb, 0x6c, 0x38, 0xbd, 0x4e, 0x8d, 0xd1, 0xc2, 0xa6, 0x03, 0x08, 0x4d, 0x63, - 0xf0, 0x4d, 0x54, 0x3d, 0x85, 0x61, 0xf1, 0xf6, 0x50, 0xf5, 0x89, 0xbf, 0x44, 0xf5, 0x01, 0x7b, - 0x9c, 0x83, 0x79, 0x82, 0x3f, 0x5c, 0x9a, 0x8f, 0x56, 0xfb, 0x5e, 0x51, 0x68, 0xc1, 0xdc, 0x5e, - 0xd9, 0xb2, 0xdc, 0xbf, 0x2c, 0xe4, 0x2c, 0x79, 0x38, 0xf1, 0x2f, 0x08, 0x05, 0x93, 0xc7, 0x48, - 0xd8, 0x96, 0x3e, 0xff, 0xce, 0xab, 0x9f, 0xbf, 0x7c, 0xd8, 0xa6, 0xbf, 0x31, 0x25, 0x24, 0xe8, - 0x8c, 0x15, 0xde, 0x44, 0xed, 0x19, 0x69, 0x7d, 0xd2, 0x35, 0xff, 0xc6, 0x78, 0xe4, 0xb4, 0x67, - 0xc4, 0xe9, 0x6c, 0x8c, 0xfb, 0x99, 0x29, 0x9b, 0x3e, 0x28, 0x76, 0x26, 0x43, 0x67, 0xe9, 0x7b, - 0x6d, 0xcd, 0x0f, 0xcd, 0x76, 0xf3, 0xb7, 0xdf, 0x9d, 0xca, 0xd3, 0xbf, 0x7b, 0x15, 0x7f, 0xf7, - 0xec, 0xbc, 0x5b, 0x79, 0x76, 0xde, 0xad, 0x3c, 0x3f, 0xef, 0x56, 0x9e, 0x8e, 0xbb, 0xd6, 0xd9, - 0xb8, 0x6b, 0x3d, 0x1b, 0x77, 0xad, 0xe7, 0xe3, 0xae, 0xf5, 0xcf, 0xb8, 0x6b, 0xfd, 0xfa, 0xa2, - 0x5b, 0xf9, 0xc1, 0x59, 0xf2, 0xdf, 0xe5, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x35, 0x2f, 0x11, - 0xe8, 0xdd, 0x08, 0x00, 0x00, + // 991 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0x8f, 0x9b, 0x3f, 0x4d, 0x26, 0xa5, 0xbb, 0x3b, 0x40, 0x65, 0x22, 0x6d, 0x1c, 0x59, 0x80, + 0xca, 0x3f, 0x9b, 0x96, 0x85, 0xad, 0x7a, 0x40, 0xe0, 0x50, 0xa1, 0x8a, 0x2e, 0x48, 0xd3, 0x16, + 0x01, 0x42, 0x62, 0xa7, 0xce, 0x5b, 0xd7, 0xdb, 0xc6, 0x36, 0x9e, 0x71, 0xd8, 0xdc, 0x56, 0xe2, + 0x0b, 0x70, 0xe4, 0xc8, 0x77, 0xe0, 0x4b, 0x94, 0x03, 0x52, 0xb9, 0xed, 0x01, 0x45, 0x34, 0xfb, + 0x2d, 0x7a, 0x42, 0x33, 0x9e, 0x38, 0x4e, 0xd2, 0x90, 0xa5, 0x2b, 0xed, 0x2d, 0xf3, 0xe6, 0xfd, + 0x7e, 0xbf, 0xf7, 0x9e, 0xdf, 0x7b, 0x13, 0x64, 0x9f, 0x6c, 0x31, 0xcb, 0x0f, 0x6d, 0x1a, 0xf9, + 0xb6, 0x0b, 0x31, 0xf7, 0x1f, 0xf8, 0x2e, 0xe5, 0xc0, 0xec, 0xde, 0xc6, 0x11, 0x70, 0xba, 0x61, + 0x7b, 0x10, 0x40, 0x4c, 0x39, 0x74, 0xac, 0x28, 0x0e, 0x79, 0x88, 0x8d, 0x14, 0x60, 0xd1, 0xc8, + 0xb7, 0xf2, 0x00, 0x4b, 0x01, 0x1a, 0xef, 0x79, 0x3e, 0x3f, 0x4e, 0x8e, 0x2c, 0x37, 0xec, 0xda, + 0x5e, 0xe8, 0x85, 0xb6, 0xc4, 0x1d, 0x25, 0x0f, 0xe4, 0x49, 0x1e, 0xe4, 0xaf, 0x94, 0xaf, 0x61, + 0xe6, 0x03, 0x08, 0x63, 0xb0, 0x7b, 0x33, 0x9a, 0x8d, 0x3b, 0x63, 0x9f, 0x2e, 0x75, 0x8f, 0xfd, + 0x00, 0xe2, 0xbe, 0x1d, 0x9d, 0x78, 0xc2, 0xc0, 0xec, 0x2e, 0x70, 0x7a, 0x15, 0xca, 0x9e, 0x87, + 0x8a, 0x93, 0x80, 0xfb, 0x5d, 0x98, 0x01, 0x7c, 0xb4, 0x08, 0xc0, 0xdc, 0x63, 0xe8, 0xd2, 0x69, + 0x9c, 0xf9, 0xc7, 0x12, 0x7a, 0xad, 0x3d, 0x2e, 0xc5, 0xbe, 0xef, 0x05, 0x7e, 0xe0, 0x11, 0xf8, + 0x31, 0x01, 0xc6, 0xf1, 0x7d, 0x54, 0x15, 0x11, 0x76, 0x28, 0xa7, 0xba, 0xd6, 0xd2, 0xd6, 0xeb, + 0x9b, 0xef, 0x5b, 0xe3, 0x1a, 0x66, 0x42, 0x56, 0x74, 0xe2, 0x09, 0x03, 0xb3, 0x84, 0xb7, 0xd5, + 0xdb, 0xb0, 0xbe, 0x3a, 0x7a, 0x08, 0x2e, 0xbf, 0x07, 0x9c, 0x3a, 0xf8, 0x6c, 0x60, 0x14, 0x86, + 0x03, 0x03, 0x8d, 0x6d, 0x24, 0x63, 0xc5, 0xf7, 0x51, 0x89, 0x45, 0xe0, 0xea, 0x4b, 0x92, 0xfd, + 0x63, 0x6b, 0xc1, 0x17, 0xb2, 0xe6, 0xc6, 0xba, 0x1f, 0x81, 0xeb, 0xac, 0x28, 0xad, 0x92, 0x38, + 0x11, 0xc9, 0x8c, 0x8f, 0x51, 0x85, 0x71, 0xca, 0x13, 0xa6, 0x17, 0xa5, 0xc6, 0x27, 0xcf, 0xa1, + 0x21, 0x79, 0x9c, 0x55, 0xa5, 0x52, 0x49, 0xcf, 0x44, 0xf1, 0x9b, 0x4f, 0x8b, 0xc8, 0x9c, 0x8b, + 0x6d, 0x87, 0x41, 0xc7, 0xe7, 0x7e, 0x18, 0xe0, 0x2d, 0x54, 0xe2, 0xfd, 0x08, 0x64, 0x41, 0x6b, + 0xce, 0xeb, 0xa3, 0x90, 0x0f, 0xfa, 0x11, 0x5c, 0x0e, 0x8c, 0x57, 0xa6, 0xfd, 0x85, 0x9d, 0x48, + 0x04, 0xde, 0xcb, 0x52, 0xa9, 0x48, 0xec, 0x9d, 0xc9, 0x40, 0x2e, 0x07, 0xc6, 0x15, 0x1d, 0x69, + 0x65, 0x4c, 0x93, 0xe1, 0xe2, 0x37, 0x51, 0x25, 0x06, 0xca, 0xc2, 0x40, 0x16, 0xbf, 0x36, 0x4e, + 0x8b, 0x48, 0x2b, 0x51, 0xb7, 0xf8, 0x2d, 0xb4, 0xdc, 0x05, 0xc6, 0xa8, 0x07, 0xb2, 0x82, 0x35, + 0xe7, 0x86, 0x72, 0x5c, 0xbe, 0x97, 0x9a, 0xc9, 0xe8, 0x1e, 0x3f, 0x44, 0xab, 0xa7, 0x94, 0xf1, + 0xc3, 0xa8, 0x43, 0x39, 0x1c, 0xf8, 0x5d, 0xd0, 0x4b, 0xb2, 0xe6, 0x6f, 0x3f, 0x5b, 0xd7, 0x08, + 0x84, 0xb3, 0xa6, 0xd8, 0x57, 0xf7, 0x26, 0x98, 0xc8, 0x14, 0x33, 0xee, 0x21, 0x2c, 0x2c, 0x07, + 0x31, 0x0d, 0x58, 0x5a, 0x28, 0xa1, 0x57, 0xfe, 0xdf, 0x7a, 0x0d, 0xa5, 0x87, 0xf7, 0x66, 0xd8, + 0xc8, 0x15, 0x0a, 0xe6, 0x40, 0x43, 0xb7, 0xe7, 0x7e, 0xe5, 0x3d, 0x9f, 0x71, 0xfc, 0xfd, 0xcc, + 0xd4, 0x58, 0xcf, 0x16, 0x8f, 0x40, 0xcb, 0x99, 0xb9, 0xa9, 0x62, 0xaa, 0x8e, 0x2c, 0xb9, 0x89, + 0xf9, 0x01, 0x95, 0x7d, 0x0e, 0x5d, 0xa6, 0x2f, 0xb5, 0x8a, 0xeb, 0xf5, 0xcd, 0xed, 0xeb, 0xb7, + 0xb3, 0xf3, 0x92, 0x92, 0x29, 0xef, 0x0a, 0x42, 0x92, 0xf2, 0x9a, 0xbf, 0x97, 0xfe, 0x23, 0x41, + 0x31, 0x58, 0xf8, 0x0d, 0xb4, 0x1c, 0xa7, 0x47, 0x99, 0xdf, 0x8a, 0x53, 0x17, 0xdd, 0xa0, 0x3c, + 0xc8, 0xe8, 0x0e, 0x5b, 0x08, 0x31, 0xdf, 0x0b, 0x20, 0xfe, 0x92, 0x76, 0x41, 0x5f, 0x4e, 0x9b, + 0x4c, 0x6c, 0x82, 0xfd, 0xcc, 0x4a, 0x72, 0x1e, 0xb8, 0x8d, 0x6e, 0xc1, 0xa3, 0xc8, 0x8f, 0xa9, + 0x6c, 0x56, 0x70, 0xc3, 0xa0, 0xc3, 0xf4, 0x6a, 0x4b, 0x5b, 0x2f, 0x3b, 0xaf, 0x0e, 0x07, 0xc6, + 0xad, 0x9d, 0xe9, 0x4b, 0x32, 0xeb, 0x8f, 0x2d, 0x54, 0x49, 0x44, 0x2f, 0x32, 0xbd, 0xdc, 0x2a, + 0xae, 0xd7, 0x9c, 0x35, 0xd1, 0xd1, 0x87, 0xd2, 0x72, 0x39, 0x30, 0xaa, 0x5f, 0x40, 0x5f, 0x1e, + 0x88, 0xf2, 0xc2, 0xef, 0xa2, 0x6a, 0xc2, 0x20, 0x0e, 0x44, 0x88, 0xe9, 0x1c, 0x64, 0xc5, 0x3f, + 0x54, 0x76, 0x92, 0x79, 0xe0, 0xdb, 0xa8, 0x98, 0xf8, 0x1d, 0x35, 0x07, 0x75, 0xe5, 0x58, 0x3c, + 0xdc, 0xfd, 0x8c, 0x08, 0x3b, 0x36, 0x51, 0xc5, 0x8b, 0xc3, 0x24, 0x62, 0x7a, 0x49, 0x8a, 0x23, + 0x21, 0xfe, 0xb9, 0xb4, 0x10, 0x75, 0x83, 0x03, 0x54, 0x86, 0x47, 0x3c, 0xa6, 0x7a, 0x45, 0x7e, + 0xbf, 0xdd, 0xe7, 0x5b, 0x79, 0xd6, 0x8e, 0xe0, 0xda, 0x09, 0x78, 0xdc, 0x1f, 0x7f, 0x4e, 0x69, + 0x23, 0xa9, 0x4c, 0x03, 0x10, 0x1a, 0xfb, 0xe0, 0x9b, 0xa8, 0x78, 0x02, 0xfd, 0x74, 0xf7, 0x10, + 0xf1, 0x13, 0x7f, 0x8a, 0xca, 0x3d, 0x7a, 0x9a, 0x80, 0x5a, 0xc1, 0xef, 0x2c, 0x8c, 0x47, 0xb2, + 0x7d, 0x2d, 0x20, 0x24, 0x45, 0x6e, 0x2f, 0x6d, 0x69, 0xe6, 0x9f, 0x1a, 0x32, 0x16, 0x2c, 0x4e, + 0xfc, 0x13, 0x42, 0xee, 0x68, 0x19, 0x31, 0x5d, 0x93, 0xf9, 0xb7, 0xaf, 0x9f, 0x7f, 0xb6, 0xd8, + 0xc6, 0x6f, 0x4c, 0x66, 0x62, 0x24, 0x27, 0x85, 0x37, 0x50, 0x3d, 0x47, 0x2d, 0x33, 0x5d, 0x71, + 0x6e, 0x0c, 0x07, 0x46, 0x3d, 0x47, 0x4e, 0xf2, 0x3e, 0xe6, 0x5f, 0x1a, 0xc2, 0xed, 0xd3, 0x84, + 0x71, 0x88, 0x0f, 0xe2, 0x84, 0x71, 0x27, 0x09, 0x3a, 0xa7, 0xf0, 0x02, 0x5e, 0xc4, 0x6f, 0x27, + 0x5e, 0xc4, 0xbb, 0x8b, 0xcb, 0x33, 0x13, 0xe4, 0xbc, 0xa7, 0xd0, 0x3c, 0xd7, 0xd0, 0xda, 0xac, + 0xfb, 0x0b, 0xd8, 0x59, 0xdf, 0x4c, 0xee, 0xac, 0x0f, 0xae, 0x91, 0xd4, 0x9c, 0x65, 0xf5, 0xf3, + 0x95, 0x29, 0xc9, 0x2d, 0xb5, 0x39, 0xb1, 0x7e, 0xd2, 0xd7, 0x36, 0x2b, 0xfd, 0x9c, 0x15, 0xf4, + 0x21, 0xaa, 0xf3, 0x31, 0x8d, 0x5a, 0x08, 0x2f, 0x2b, 0x50, 0x3d, 0xa7, 0x40, 0xf2, 0x7e, 0xe6, + 0x5d, 0x35, 0x63, 0x72, 0x2a, 0xb0, 0x31, 0xca, 0x56, 0x93, 0x4b, 0xa0, 0x36, 0x1d, 0xf4, 0x76, + 0xf5, 0xd7, 0xdf, 0x8c, 0xc2, 0xe3, 0xbf, 0x5b, 0x05, 0x67, 0xe7, 0xec, 0xa2, 0x59, 0x38, 0xbf, + 0x68, 0x16, 0x9e, 0x5c, 0x34, 0x0b, 0x8f, 0x87, 0x4d, 0xed, 0x6c, 0xd8, 0xd4, 0xce, 0x87, 0x4d, + 0xed, 0xc9, 0xb0, 0xa9, 0xfd, 0x33, 0x6c, 0x6a, 0xbf, 0x3c, 0x6d, 0x16, 0xbe, 0x33, 0x16, 0xfc, + 0xd1, 0xfd, 0x37, 0x00, 0x00, 0xff, 0xff, 0x17, 0xbe, 0xe3, 0x02, 0x0a, 0x0b, 0x00, 0x00, } func (m *CertificateSigningRequest) Marshal() (dAtA []byte, err error) { @@ -595,6 +687,129 @@ func (m *CertificateSigningRequestStatus) MarshalToSizedBuffer(dAtA []byte) (int return len(dAtA) - i, nil } +func (m *ClusterTrustBundle) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClusterTrustBundle) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClusterTrustBundle) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ClusterTrustBundleList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClusterTrustBundleList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClusterTrustBundleList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ClusterTrustBundleSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClusterTrustBundleSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClusterTrustBundleSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.TrustBundle) + copy(dAtA[i:], m.TrustBundle) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.TrustBundle))) + i-- + dAtA[i] = 0x12 + i -= len(m.SignerName) + copy(dAtA[i:], m.SignerName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SignerName))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m ExtraValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -755,6 +970,49 @@ func (m *CertificateSigningRequestStatus) Size() (n int) { return n } +func (m *ClusterTrustBundle) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ClusterTrustBundleList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ClusterTrustBundleSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SignerName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.TrustBundle) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m ExtraValue) Size() (n int) { if m == nil { return 0 @@ -862,6 +1120,44 @@ func (this *CertificateSigningRequestStatus) String() string { }, "") return s } +func (this *ClusterTrustBundle) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ClusterTrustBundle{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ClusterTrustBundleSpec", "ClusterTrustBundleSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ClusterTrustBundleList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ClusterTrustBundle{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ClusterTrustBundle", "ClusterTrustBundle", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ClusterTrustBundleList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ClusterTrustBundleSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ClusterTrustBundleSpec{`, + `SignerName:` + fmt.Sprintf("%v", this.SignerName) + `,`, + `TrustBundle:` + fmt.Sprintf("%v", this.TrustBundle) + `,`, + `}`, + }, "") + return s +} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -1892,6 +2188,353 @@ func (m *CertificateSigningRequestStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *ClusterTrustBundle) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClusterTrustBundle: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClusterTrustBundle: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClusterTrustBundleList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClusterTrustBundleList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClusterTrustBundleList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, ClusterTrustBundle{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClusterTrustBundleSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClusterTrustBundleSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClusterTrustBundleSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignerName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignerName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TrustBundle", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TrustBundle = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ExtraValue) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.proto index f3ec4c06e4..7c48270f65 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/generated.proto @@ -190,6 +190,79 @@ message CertificateSigningRequestStatus { optional bytes certificate = 2; } +// ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors +// (root certificates). +// +// ClusterTrustBundle objects are considered to be readable by any authenticated +// user in the cluster, because they can be mounted by pods using the +// `clusterTrustBundle` projection. All service accounts have read access to +// ClusterTrustBundles by default. Users who only have namespace-level access +// to a cluster can read ClusterTrustBundles by impersonating a serviceaccount +// that they have access to. +// +// It can be optionally associated with a particular assigner, in which case it +// contains one valid set of trust anchors for that signer. Signers may have +// multiple associated ClusterTrustBundles; each is an independent set of trust +// anchors for that signer. Admission control is used to enforce that only users +// with permissions on the signer can create or modify the corresponding bundle. +message ClusterTrustBundle { + // metadata contains the object metadata. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec contains the signer (if any) and trust anchors. + optional ClusterTrustBundleSpec spec = 2; +} + +// ClusterTrustBundleList is a collection of ClusterTrustBundle objects +message ClusterTrustBundleList { + // metadata contains the list metadata. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // items is a collection of ClusterTrustBundle objects + repeated ClusterTrustBundle items = 2; +} + +// ClusterTrustBundleSpec contains the signer and trust anchors. +message ClusterTrustBundleSpec { + // signerName indicates the associated signer, if any. + // + // In order to create or update a ClusterTrustBundle that sets signerName, + // you must have the following cluster-scoped permission: + // group=certificates.k8s.io resource=signers resourceName= + // verb=attest. + // + // If signerName is not empty, then the ClusterTrustBundle object must be + // named with the signer name as a prefix (translating slashes to colons). + // For example, for the signer name `example.com/foo`, valid + // ClusterTrustBundle object names include `example.com:foo:abc` and + // `example.com:foo:v1`. + // + // If signerName is empty, then the ClusterTrustBundle object's name must + // not have such a prefix. + // + // List/watch requests for ClusterTrustBundles can filter on this field + // using a `spec.signerName=NAME` field selector. + // + // +optional + optional string signerName = 1; + + // trustBundle contains the individual X.509 trust anchors for this + // bundle, as PEM bundle of PEM-wrapped, DER-formatted X.509 certificates. + // + // The data must consist only of PEM certificate blocks that parse as valid + // X.509 certificates. Each certificate must include a basic constraints + // extension with the CA bit set. The API server will reject objects that + // contain duplicate certificates, or that use PEM block headers. + // + // Users of ClusterTrustBundles, including Kubelet, are free to reorder and + // deduplicate certificate blocks in this file according to their own logic, + // as well as to drop PEM block headers and inter-block data. + optional string trustBundle = 2; +} + // ExtraValue masks the value so protobuf can generate // +protobuf.nullable=true // +protobuf.options.(gogoproto.goproto_stringer)=false diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/register.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/register.go index b4f3af9b9c..800dccd07d 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/register.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/register.go @@ -51,6 +51,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &CertificateSigningRequest{}, &CertificateSigningRequestList{}, + &ClusterTrustBundle{}, + &ClusterTrustBundleList{}, ) // Add the watch version that applies diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/types.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/types.go index 7e5a5c198a..1ce104807d 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/types.go @@ -262,3 +262,88 @@ const ( UsageMicrosoftSGC KeyUsage = "microsoft sgc" UsageNetscapeSGC KeyUsage = "netscape sgc" ) + +// +genclient +// +genclient:nonNamespaced +// +k8s:prerelease-lifecycle-gen:introduced=1.33 +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors +// (root certificates). +// +// ClusterTrustBundle objects are considered to be readable by any authenticated +// user in the cluster, because they can be mounted by pods using the +// `clusterTrustBundle` projection. All service accounts have read access to +// ClusterTrustBundles by default. Users who only have namespace-level access +// to a cluster can read ClusterTrustBundles by impersonating a serviceaccount +// that they have access to. +// +// It can be optionally associated with a particular assigner, in which case it +// contains one valid set of trust anchors for that signer. Signers may have +// multiple associated ClusterTrustBundles; each is an independent set of trust +// anchors for that signer. Admission control is used to enforce that only users +// with permissions on the signer can create or modify the corresponding bundle. +type ClusterTrustBundle struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // spec contains the signer (if any) and trust anchors. + Spec ClusterTrustBundleSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` +} + +// ClusterTrustBundleSpec contains the signer and trust anchors. +type ClusterTrustBundleSpec struct { + // signerName indicates the associated signer, if any. + // + // In order to create or update a ClusterTrustBundle that sets signerName, + // you must have the following cluster-scoped permission: + // group=certificates.k8s.io resource=signers resourceName= + // verb=attest. + // + // If signerName is not empty, then the ClusterTrustBundle object must be + // named with the signer name as a prefix (translating slashes to colons). + // For example, for the signer name `example.com/foo`, valid + // ClusterTrustBundle object names include `example.com:foo:abc` and + // `example.com:foo:v1`. + // + // If signerName is empty, then the ClusterTrustBundle object's name must + // not have such a prefix. + // + // List/watch requests for ClusterTrustBundles can filter on this field + // using a `spec.signerName=NAME` field selector. + // + // +optional + SignerName string `json:"signerName,omitempty" protobuf:"bytes,1,opt,name=signerName"` + + // trustBundle contains the individual X.509 trust anchors for this + // bundle, as PEM bundle of PEM-wrapped, DER-formatted X.509 certificates. + // + // The data must consist only of PEM certificate blocks that parse as valid + // X.509 certificates. Each certificate must include a basic constraints + // extension with the CA bit set. The API server will reject objects that + // contain duplicate certificates, or that use PEM block headers. + // + // Users of ClusterTrustBundles, including Kubelet, are free to reorder and + // deduplicate certificate blocks in this file according to their own logic, + // as well as to drop PEM block headers and inter-block data. + TrustBundle string `json:"trustBundle" protobuf:"bytes,2,opt,name=trustBundle"` +} + +// +k8s:prerelease-lifecycle-gen:introduced=1.33 +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterTrustBundleList is a collection of ClusterTrustBundle objects +type ClusterTrustBundleList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata. + // + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // items is a collection of ClusterTrustBundle objects + Items []ClusterTrustBundle `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go index f9ab1f13de..58c69e54d3 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go @@ -75,4 +75,34 @@ func (CertificateSigningRequestStatus) SwaggerDoc() map[string]string { return map_CertificateSigningRequestStatus } +var map_ClusterTrustBundle = map[string]string{ + "": "ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors (root certificates).\n\nClusterTrustBundle objects are considered to be readable by any authenticated user in the cluster, because they can be mounted by pods using the `clusterTrustBundle` projection. All service accounts have read access to ClusterTrustBundles by default. Users who only have namespace-level access to a cluster can read ClusterTrustBundles by impersonating a serviceaccount that they have access to.\n\nIt can be optionally associated with a particular assigner, in which case it contains one valid set of trust anchors for that signer. Signers may have multiple associated ClusterTrustBundles; each is an independent set of trust anchors for that signer. Admission control is used to enforce that only users with permissions on the signer can create or modify the corresponding bundle.", + "metadata": "metadata contains the object metadata.", + "spec": "spec contains the signer (if any) and trust anchors.", +} + +func (ClusterTrustBundle) SwaggerDoc() map[string]string { + return map_ClusterTrustBundle +} + +var map_ClusterTrustBundleList = map[string]string{ + "": "ClusterTrustBundleList is a collection of ClusterTrustBundle objects", + "metadata": "metadata contains the list metadata.", + "items": "items is a collection of ClusterTrustBundle objects", +} + +func (ClusterTrustBundleList) SwaggerDoc() map[string]string { + return map_ClusterTrustBundleList +} + +var map_ClusterTrustBundleSpec = map[string]string{ + "": "ClusterTrustBundleSpec contains the signer and trust anchors.", + "signerName": "signerName indicates the associated signer, if any.\n\nIn order to create or update a ClusterTrustBundle that sets signerName, you must have the following cluster-scoped permission: group=certificates.k8s.io resource=signers resourceName= verb=attest.\n\nIf signerName is not empty, then the ClusterTrustBundle object must be named with the signer name as a prefix (translating slashes to colons). For example, for the signer name `example.com/foo`, valid ClusterTrustBundle object names include `example.com:foo:abc` and `example.com:foo:v1`.\n\nIf signerName is empty, then the ClusterTrustBundle object's name must not have such a prefix.\n\nList/watch requests for ClusterTrustBundles can filter on this field using a `spec.signerName=NAME` field selector.", + "trustBundle": "trustBundle contains the individual X.509 trust anchors for this bundle, as PEM bundle of PEM-wrapped, DER-formatted X.509 certificates.\n\nThe data must consist only of PEM certificate blocks that parse as valid X.509 certificates. Each certificate must include a basic constraints extension with the CA bit set. The API server will reject objects that contain duplicate certificates, or that use PEM block headers.\n\nUsers of ClusterTrustBundles, including Kubelet, are free to reorder and deduplicate certificate blocks in this file according to their own logic, as well as to drop PEM block headers and inter-block data.", +} + +func (ClusterTrustBundleSpec) SwaggerDoc() map[string]string { + return map_ClusterTrustBundleSpec +} + // AUTO-GENERATED FUNCTIONS END HERE diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go index a315e2ac60..854e834739 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go @@ -188,6 +188,82 @@ func (in *CertificateSigningRequestStatus) DeepCopy() *CertificateSigningRequest return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterTrustBundle) DeepCopyInto(out *ClusterTrustBundle) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundle. +func (in *ClusterTrustBundle) DeepCopy() *ClusterTrustBundle { + if in == nil { + return nil + } + out := new(ClusterTrustBundle) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterTrustBundle) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterTrustBundleList) DeepCopyInto(out *ClusterTrustBundleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterTrustBundle, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundleList. +func (in *ClusterTrustBundleList) DeepCopy() *ClusterTrustBundleList { + if in == nil { + return nil + } + out := new(ClusterTrustBundleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterTrustBundleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterTrustBundleSpec) DeepCopyInto(out *ClusterTrustBundleSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundleSpec. +func (in *ClusterTrustBundleSpec) DeepCopy() *ClusterTrustBundleSpec { + if in == nil { + return nil + } + out := new(ClusterTrustBundleSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in ExtraValue) DeepCopyInto(out *ExtraValue) { { diff --git a/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go b/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go index 480a329361..062b46f164 100644 --- a/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go +++ b/go-controller/vendor/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go @@ -72,3 +72,39 @@ func (in *CertificateSigningRequestList) APILifecycleReplacement() schema.GroupV func (in *CertificateSigningRequestList) APILifecycleRemoved() (major, minor int) { return 1, 22 } + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ClusterTrustBundle) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *ClusterTrustBundle) APILifecycleDeprecated() (major, minor int) { + return 1, 36 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *ClusterTrustBundle) APILifecycleRemoved() (major, minor int) { + return 1, 39 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ClusterTrustBundleList) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *ClusterTrustBundleList) APILifecycleDeprecated() (major, minor int) { + return 1, 36 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *ClusterTrustBundleList) APILifecycleRemoved() (major, minor int) { + return 1, 39 +} diff --git a/go-controller/vendor/k8s.io/api/coordination/v1/doc.go b/go-controller/vendor/k8s.io/api/coordination/v1/doc.go index 9b2fbbda3a..82ae6340c7 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=coordination.k8s.io -package v1 // import "k8s.io/api/coordination/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/doc.go b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/doc.go index 5e6d655302..dff7df47fc 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/doc.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=coordination.k8s.io -package v1alpha2 // import "k8s.io/api/coordination/v1alpha2" +package v1alpha2 diff --git a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/generated.proto b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/generated.proto index 7e56cd7f96..250c6113ec 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/generated.proto +++ b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/generated.proto @@ -92,8 +92,6 @@ message LeaseCandidateSpec { // If multiple candidates for the same Lease return different strategies, the strategy provided // by the candidate with the latest BinaryVersion will be used. If there is still conflict, // this is a user error and coordinated leader election will not operate the Lease until resolved. - // (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled. - // +featureGate=CoordinatedLeaderElection // +required optional string strategy = 6; } diff --git a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types.go b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types.go index 2f53b097a2..13e1deb067 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types.go @@ -73,8 +73,6 @@ type LeaseCandidateSpec struct { // If multiple candidates for the same Lease return different strategies, the strategy provided // by the candidate with the latest BinaryVersion will be used. If there is still conflict, // this is a user error and coordinated leader election will not operate the Lease until resolved. - // (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled. - // +featureGate=CoordinatedLeaderElection // +required Strategy v1.CoordinatedLeaseStrategy `json:"strategy,omitempty" protobuf:"bytes,6,opt,name=strategy"` } diff --git a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types_swagger_doc_generated.go index 39534e6adb..f7e29849e4 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1alpha2/types_swagger_doc_generated.go @@ -54,7 +54,7 @@ var map_LeaseCandidateSpec = map[string]string{ "renewTime": "RenewTime is the time that the LeaseCandidate was last updated. Any time a Lease needs to do leader election, the PingTime field is updated to signal to the LeaseCandidate that they should update the RenewTime. Old LeaseCandidate objects are also garbage collected if it has been hours since the last renew. The PingTime field is updated regularly to prevent garbage collection for still active LeaseCandidates.", "binaryVersion": "BinaryVersion is the binary version. It must be in a semver format without leading `v`. This field is required.", "emulationVersion": "EmulationVersion is the emulation version. It must be in a semver format without leading `v`. EmulationVersion must be less than or equal to BinaryVersion. This field is required when strategy is \"OldestEmulationVersion\"", - "strategy": "Strategy is the strategy that coordinated leader election will use for picking the leader. If multiple candidates for the same Lease return different strategies, the strategy provided by the candidate with the latest BinaryVersion will be used. If there is still conflict, this is a user error and coordinated leader election will not operate the Lease until resolved. (Alpha) Using this field requires the CoordinatedLeaderElection feature gate to be enabled.", + "strategy": "Strategy is the strategy that coordinated leader election will use for picking the leader. If multiple candidates for the same Lease return different strategies, the strategy provided by the candidate with the latest BinaryVersion will be used. If there is still conflict, this is a user error and coordinated leader election will not operate the Lease until resolved.", } func (LeaseCandidateSpec) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/doc.go index e733411aa9..cab8becf67 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=coordination.k8s.io -package v1beta1 // import "k8s.io/api/coordination/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.pb.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.pb.go index bea9b8146a..52fd4167fa 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.pb.go @@ -74,10 +74,94 @@ func (m *Lease) XXX_DiscardUnknown() { var xxx_messageInfo_Lease proto.InternalMessageInfo +func (m *LeaseCandidate) Reset() { *m = LeaseCandidate{} } +func (*LeaseCandidate) ProtoMessage() {} +func (*LeaseCandidate) Descriptor() ([]byte, []int) { + return fileDescriptor_8d4e223b8bb23da3, []int{1} +} +func (m *LeaseCandidate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *LeaseCandidate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *LeaseCandidate) XXX_Merge(src proto.Message) { + xxx_messageInfo_LeaseCandidate.Merge(m, src) +} +func (m *LeaseCandidate) XXX_Size() int { + return m.Size() +} +func (m *LeaseCandidate) XXX_DiscardUnknown() { + xxx_messageInfo_LeaseCandidate.DiscardUnknown(m) +} + +var xxx_messageInfo_LeaseCandidate proto.InternalMessageInfo + +func (m *LeaseCandidateList) Reset() { *m = LeaseCandidateList{} } +func (*LeaseCandidateList) ProtoMessage() {} +func (*LeaseCandidateList) Descriptor() ([]byte, []int) { + return fileDescriptor_8d4e223b8bb23da3, []int{2} +} +func (m *LeaseCandidateList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *LeaseCandidateList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *LeaseCandidateList) XXX_Merge(src proto.Message) { + xxx_messageInfo_LeaseCandidateList.Merge(m, src) +} +func (m *LeaseCandidateList) XXX_Size() int { + return m.Size() +} +func (m *LeaseCandidateList) XXX_DiscardUnknown() { + xxx_messageInfo_LeaseCandidateList.DiscardUnknown(m) +} + +var xxx_messageInfo_LeaseCandidateList proto.InternalMessageInfo + +func (m *LeaseCandidateSpec) Reset() { *m = LeaseCandidateSpec{} } +func (*LeaseCandidateSpec) ProtoMessage() {} +func (*LeaseCandidateSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_8d4e223b8bb23da3, []int{3} +} +func (m *LeaseCandidateSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *LeaseCandidateSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *LeaseCandidateSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_LeaseCandidateSpec.Merge(m, src) +} +func (m *LeaseCandidateSpec) XXX_Size() int { + return m.Size() +} +func (m *LeaseCandidateSpec) XXX_DiscardUnknown() { + xxx_messageInfo_LeaseCandidateSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_LeaseCandidateSpec proto.InternalMessageInfo + func (m *LeaseList) Reset() { *m = LeaseList{} } func (*LeaseList) ProtoMessage() {} func (*LeaseList) Descriptor() ([]byte, []int) { - return fileDescriptor_8d4e223b8bb23da3, []int{1} + return fileDescriptor_8d4e223b8bb23da3, []int{4} } func (m *LeaseList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -105,7 +189,7 @@ var xxx_messageInfo_LeaseList proto.InternalMessageInfo func (m *LeaseSpec) Reset() { *m = LeaseSpec{} } func (*LeaseSpec) ProtoMessage() {} func (*LeaseSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_8d4e223b8bb23da3, []int{2} + return fileDescriptor_8d4e223b8bb23da3, []int{5} } func (m *LeaseSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -132,6 +216,9 @@ var xxx_messageInfo_LeaseSpec proto.InternalMessageInfo func init() { proto.RegisterType((*Lease)(nil), "k8s.io.api.coordination.v1beta1.Lease") + proto.RegisterType((*LeaseCandidate)(nil), "k8s.io.api.coordination.v1beta1.LeaseCandidate") + proto.RegisterType((*LeaseCandidateList)(nil), "k8s.io.api.coordination.v1beta1.LeaseCandidateList") + proto.RegisterType((*LeaseCandidateSpec)(nil), "k8s.io.api.coordination.v1beta1.LeaseCandidateSpec") proto.RegisterType((*LeaseList)(nil), "k8s.io.api.coordination.v1beta1.LeaseList") proto.RegisterType((*LeaseSpec)(nil), "k8s.io.api.coordination.v1beta1.LeaseSpec") } @@ -141,45 +228,54 @@ func init() { } var fileDescriptor_8d4e223b8bb23da3 = []byte{ - // 600 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x4e, 0xd4, 0x4e, - 0x14, 0xc7, 0xb7, 0xb0, 0xfb, 0xfb, 0xb1, 0xb3, 0xf2, 0x27, 0x23, 0x17, 0x0d, 0x17, 0x2d, 0xe1, - 0xc2, 0x10, 0x12, 0xa7, 0x82, 0xc6, 0x18, 0x13, 0x13, 0x2d, 0x9a, 0x48, 0x2c, 0xd1, 0x14, 0xae, - 0x0c, 0x89, 0xce, 0xb6, 0x87, 0xee, 0x08, 0xed, 0xd4, 0x99, 0x59, 0x0c, 0x77, 0x3e, 0x82, 0x4f, - 0xa3, 0xf1, 0x0d, 0xb8, 0xe4, 0x92, 0xab, 0x46, 0xc6, 0xb7, 0xf0, 0xca, 0xcc, 0x6c, 0x61, 0x61, - 0x81, 0xb0, 0xf1, 0x6e, 0xe7, 0x9c, 0xf3, 0xfd, 0x9c, 0xef, 0x9c, 0xb3, 0x53, 0x14, 0xec, 0x3d, - 0x91, 0x84, 0xf1, 0x80, 0x96, 0x2c, 0x48, 0x38, 0x17, 0x29, 0x2b, 0xa8, 0x62, 0xbc, 0x08, 0x0e, - 0x56, 0xbb, 0xa0, 0xe8, 0x6a, 0x90, 0x41, 0x01, 0x82, 0x2a, 0x48, 0x49, 0x29, 0xb8, 0xe2, 0xd8, - 0x1f, 0x08, 0x08, 0x2d, 0x19, 0xb9, 0x28, 0x20, 0xb5, 0x60, 0xe1, 0x7e, 0xc6, 0x54, 0xaf, 0xdf, - 0x25, 0x09, 0xcf, 0x83, 0x8c, 0x67, 0x3c, 0xb0, 0xba, 0x6e, 0x7f, 0xd7, 0x9e, 0xec, 0xc1, 0xfe, - 0x1a, 0xf0, 0x16, 0x56, 0x6e, 0x36, 0x30, 0xda, 0x7b, 0xe1, 0xd1, 0xb0, 0x36, 0xa7, 0x49, 0x8f, - 0x15, 0x20, 0x0e, 0x83, 0x72, 0x2f, 0x33, 0x01, 0x19, 0xe4, 0xa0, 0xe8, 0x75, 0xaa, 0xe0, 0x26, - 0x95, 0xe8, 0x17, 0x8a, 0xe5, 0x70, 0x45, 0xf0, 0xf8, 0x36, 0x81, 0x4c, 0x7a, 0x90, 0xd3, 0x51, - 0xdd, 0xd2, 0x0f, 0x07, 0xb5, 0x22, 0xa0, 0x12, 0xf0, 0x47, 0x34, 0x65, 0xdc, 0xa4, 0x54, 0x51, - 0xd7, 0x59, 0x74, 0x96, 0x3b, 0x6b, 0x0f, 0xc8, 0x70, 0x6e, 0xe7, 0x50, 0x52, 0xee, 0x65, 0x26, - 0x20, 0x89, 0xa9, 0x26, 0x07, 0xab, 0xe4, 0x6d, 0xf7, 0x13, 0x24, 0x6a, 0x13, 0x14, 0x0d, 0xf1, - 0x51, 0xe5, 0x37, 0x74, 0xe5, 0xa3, 0x61, 0x2c, 0x3e, 0xa7, 0xe2, 0x08, 0x35, 0x65, 0x09, 0x89, - 0x3b, 0x61, 0xe9, 0x2b, 0xe4, 0x96, 0xad, 0x10, 0xeb, 0x6b, 0xab, 0x84, 0x24, 0xbc, 0x53, 0x73, - 0x9b, 0xe6, 0x14, 0x5b, 0xca, 0xd2, 0x77, 0x07, 0xb5, 0x6d, 0x45, 0xc4, 0xa4, 0xc2, 0x3b, 0x57, - 0xdc, 0x93, 0xf1, 0xdc, 0x1b, 0xb5, 0xf5, 0x3e, 0x57, 0xf7, 0x98, 0x3a, 0x8b, 0x5c, 0x70, 0xfe, - 0x06, 0xb5, 0x98, 0x82, 0x5c, 0xba, 0x13, 0x8b, 0x93, 0xcb, 0x9d, 0xb5, 0x7b, 0xe3, 0x59, 0x0f, - 0xa7, 0x6b, 0x64, 0x6b, 0xc3, 0x88, 0xe3, 0x01, 0x63, 0xe9, 0x67, 0xb3, 0x36, 0x6e, 0x2e, 0x83, - 0x9f, 0xa2, 0x99, 0x1e, 0xdf, 0x4f, 0x41, 0x6c, 0xa4, 0x50, 0x28, 0xa6, 0x0e, 0xad, 0xfd, 0x76, - 0x88, 0x75, 0xe5, 0xcf, 0xbc, 0xbe, 0x94, 0x89, 0x47, 0x2a, 0x71, 0x84, 0xe6, 0xf7, 0x0d, 0xe8, - 0x65, 0x5f, 0xd8, 0xf6, 0x5b, 0x90, 0xf0, 0x22, 0x95, 0x76, 0xc0, 0xad, 0xd0, 0xd5, 0x95, 0x3f, - 0x1f, 0x5d, 0x93, 0x8f, 0xaf, 0x55, 0xe1, 0x2e, 0xea, 0xd0, 0xe4, 0x73, 0x9f, 0x09, 0xd8, 0x66, - 0x39, 0xb8, 0x93, 0x76, 0x8a, 0xc1, 0x78, 0x53, 0xdc, 0x64, 0x89, 0xe0, 0x46, 0x16, 0xce, 0xea, - 0xca, 0xef, 0xbc, 0x18, 0x72, 0xe2, 0x8b, 0x50, 0xbc, 0x83, 0xda, 0x02, 0x0a, 0xf8, 0x62, 0x3b, - 0x34, 0xff, 0xad, 0xc3, 0xb4, 0xae, 0xfc, 0x76, 0x7c, 0x46, 0x89, 0x87, 0x40, 0xfc, 0x1c, 0xcd, - 0xd9, 0x9b, 0x6d, 0x0b, 0x5a, 0x48, 0x66, 0xee, 0x26, 0xdd, 0x96, 0x9d, 0xc5, 0xbc, 0xae, 0xfc, - 0xb9, 0x68, 0x24, 0x17, 0x5f, 0xa9, 0xc6, 0x1f, 0xd0, 0x94, 0x54, 0xe6, 0x7d, 0x64, 0x87, 0xee, - 0x7f, 0x76, 0x0f, 0xeb, 0xe6, 0x2f, 0xb1, 0x55, 0xc7, 0xfe, 0x54, 0xfe, 0xc3, 0x9b, 0xdf, 0x3e, - 0x59, 0x3f, 0x3b, 0x43, 0x3a, 0x58, 0x70, 0x2d, 0x8b, 0xcf, 0xa1, 0xf8, 0x19, 0x9a, 0x2d, 0x05, - 0xec, 0x82, 0x10, 0x90, 0x0e, 0xb6, 0xeb, 0xfe, 0x6f, 0xfb, 0xdc, 0xd5, 0x95, 0x3f, 0xfb, 0xee, - 0x72, 0x2a, 0x1e, 0xad, 0x0d, 0x5f, 0x1d, 0x9d, 0x7a, 0x8d, 0xe3, 0x53, 0xaf, 0x71, 0x72, 0xea, - 0x35, 0xbe, 0x6a, 0xcf, 0x39, 0xd2, 0x9e, 0x73, 0xac, 0x3d, 0xe7, 0x44, 0x7b, 0xce, 0x2f, 0xed, - 0x39, 0xdf, 0x7e, 0x7b, 0x8d, 0xf7, 0xfe, 0x2d, 0x1f, 0xc8, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, - 0x57, 0x93, 0xf3, 0xef, 0x42, 0x05, 0x00, 0x00, + // 750 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xdd, 0x4e, 0x1b, 0x39, + 0x18, 0xcd, 0x40, 0xb2, 0x9b, 0x38, 0x04, 0xb2, 0x5e, 0x56, 0x1a, 0x71, 0x31, 0x83, 0x72, 0xb1, + 0x42, 0x48, 0xeb, 0x59, 0x60, 0xb5, 0x5a, 0x6d, 0x55, 0xa9, 0x1d, 0x40, 0x2d, 0x6a, 0x68, 0x91, + 0xa1, 0x95, 0x5a, 0x21, 0xb5, 0xce, 0x8c, 0x99, 0xb8, 0x30, 0x3f, 0xf5, 0x38, 0x54, 0xb9, 0xeb, + 0x23, 0xf4, 0x69, 0x5a, 0xf5, 0x0d, 0xd2, 0x3b, 0x2e, 0xb9, 0x8a, 0xca, 0x54, 0xea, 0x43, 0xf4, + 0xaa, 0xb2, 0x33, 0xf9, 0x27, 0x22, 0x6d, 0x11, 0x77, 0xf1, 0xf7, 0x9d, 0x73, 0xfc, 0x1d, 0xfb, + 0x38, 0x1a, 0x60, 0x1d, 0xff, 0x17, 0x23, 0x16, 0x5a, 0x24, 0x62, 0x96, 0x13, 0x86, 0xdc, 0x65, + 0x01, 0x11, 0x2c, 0x0c, 0xac, 0xd3, 0xb5, 0x1a, 0x15, 0x64, 0xcd, 0xf2, 0x68, 0x40, 0x39, 0x11, + 0xd4, 0x45, 0x11, 0x0f, 0x45, 0x08, 0xcd, 0x0e, 0x01, 0x91, 0x88, 0xa1, 0x41, 0x02, 0x4a, 0x09, + 0x4b, 0x7f, 0x79, 0x4c, 0xd4, 0x1b, 0x35, 0xe4, 0x84, 0xbe, 0xe5, 0x85, 0x5e, 0x68, 0x29, 0x5e, + 0xad, 0x71, 0xa4, 0x56, 0x6a, 0xa1, 0x7e, 0x75, 0xf4, 0x96, 0x56, 0x27, 0x0f, 0x30, 0xba, 0xf7, + 0xd2, 0x3f, 0x7d, 0xac, 0x4f, 0x9c, 0x3a, 0x0b, 0x28, 0x6f, 0x5a, 0xd1, 0xb1, 0x27, 0x0b, 0xb1, + 0xe5, 0x53, 0x41, 0x2e, 0x63, 0x59, 0x93, 0x58, 0xbc, 0x11, 0x08, 0xe6, 0xd3, 0x31, 0xc2, 0xbf, + 0x57, 0x11, 0x62, 0xa7, 0x4e, 0x7d, 0x32, 0xca, 0xab, 0xbc, 0xd7, 0x40, 0xae, 0x4a, 0x49, 0x4c, + 0xe1, 0x0b, 0x90, 0x97, 0xd3, 0xb8, 0x44, 0x10, 0x5d, 0x5b, 0xd6, 0x56, 0x8a, 0xeb, 0x7f, 0xa3, + 0xfe, 0xb9, 0xf5, 0x44, 0x51, 0x74, 0xec, 0xc9, 0x42, 0x8c, 0x24, 0x1a, 0x9d, 0xae, 0xa1, 0x47, + 0xb5, 0x97, 0xd4, 0x11, 0xbb, 0x54, 0x10, 0x1b, 0xb6, 0xda, 0x66, 0x26, 0x69, 0x9b, 0xa0, 0x5f, + 0xc3, 0x3d, 0x55, 0x58, 0x05, 0xd9, 0x38, 0xa2, 0x8e, 0x3e, 0xa3, 0xd4, 0x57, 0xd1, 0x15, 0xb7, + 0x82, 0xd4, 0x5c, 0xfb, 0x11, 0x75, 0xec, 0xb9, 0x54, 0x37, 0x2b, 0x57, 0x58, 0xa9, 0x54, 0x3e, + 0x6a, 0x60, 0x5e, 0x21, 0x36, 0x49, 0xe0, 0x32, 0x97, 0x88, 0x9b, 0xb0, 0xf0, 0x78, 0xc8, 0xc2, + 0xc6, 0x74, 0x16, 0x7a, 0x03, 0x4e, 0xf4, 0xd2, 0xd2, 0x00, 0x1c, 0x86, 0x56, 0x59, 0x2c, 0xe0, + 0xe1, 0x98, 0x1f, 0x34, 0x9d, 0x1f, 0xc9, 0x56, 0x6e, 0xca, 0xe9, 0x66, 0xf9, 0x6e, 0x65, 0xc0, + 0xcb, 0x01, 0xc8, 0x31, 0x41, 0xfd, 0x58, 0x9f, 0x59, 0x9e, 0x5d, 0x29, 0xae, 0x5b, 0xdf, 0x69, + 0xc6, 0x2e, 0xa5, 0xda, 0xb9, 0x1d, 0xa9, 0x82, 0x3b, 0x62, 0x95, 0x2f, 0xb3, 0xa3, 0x56, 0xa4, + 0x4f, 0x68, 0x81, 0xc2, 0x89, 0xac, 0x3e, 0x24, 0x3e, 0x55, 0x5e, 0x0a, 0xf6, 0x6f, 0x29, 0xbf, + 0x50, 0xed, 0x36, 0x70, 0x1f, 0x03, 0x9f, 0x82, 0x7c, 0xc4, 0x02, 0xef, 0x80, 0xf9, 0x34, 0x3d, + 0x6d, 0x6b, 0x3a, 0xef, 0xbb, 0xcc, 0xe1, 0xa1, 0xa4, 0xd9, 0x73, 0xd2, 0xf8, 0x5e, 0x2a, 0x82, + 0x7b, 0x72, 0xf0, 0x10, 0x14, 0x38, 0x0d, 0xe8, 0x6b, 0xa5, 0x3d, 0xfb, 0x63, 0xda, 0x25, 0x39, + 0x38, 0xee, 0xaa, 0xe0, 0xbe, 0x20, 0xbc, 0x05, 0x4a, 0x35, 0x16, 0x10, 0xde, 0x7c, 0x42, 0x79, + 0xcc, 0xc2, 0x40, 0xcf, 0x2a, 0xb7, 0x7f, 0xa4, 0x6e, 0x4b, 0xf6, 0x60, 0x13, 0x0f, 0x63, 0xe1, + 0x16, 0x28, 0x53, 0xbf, 0x71, 0xa2, 0xce, 0xbd, 0xcb, 0xcf, 0x29, 0xbe, 0x9e, 0xf2, 0xcb, 0xdb, + 0x23, 0x7d, 0x3c, 0xc6, 0x80, 0x0e, 0xc8, 0xc7, 0x42, 0xbe, 0x72, 0xaf, 0xa9, 0xff, 0xa2, 0xd8, + 0xf7, 0xba, 0x39, 0xd8, 0x4f, 0xeb, 0x5f, 0xdb, 0xe6, 0xc6, 0xe4, 0x7f, 0x31, 0xb4, 0xd9, 0x5d, + 0x53, 0xb7, 0xf3, 0x0a, 0x53, 0x1a, 0xee, 0x09, 0x57, 0xde, 0x69, 0xa0, 0x73, 0x73, 0x37, 0x10, + 0xd5, 0x07, 0xc3, 0x51, 0xfd, 0x73, 0xba, 0xa8, 0x4e, 0x48, 0xe8, 0x87, 0x6c, 0x3a, 0xb8, 0x0a, + 0xe6, 0xff, 0x60, 0xbe, 0x1e, 0x9e, 0xb8, 0x94, 0xef, 0xb8, 0x34, 0x10, 0x4c, 0x34, 0xd3, 0x74, + 0xc2, 0xa4, 0x6d, 0xce, 0xdf, 0x1f, 0xea, 0xe0, 0x11, 0x24, 0xac, 0x82, 0x45, 0x15, 0xd8, 0xad, + 0x06, 0x57, 0xdb, 0xef, 0x53, 0x27, 0x0c, 0xdc, 0x58, 0xe5, 0x35, 0x67, 0xeb, 0x49, 0xdb, 0x5c, + 0xac, 0x5e, 0xd2, 0xc7, 0x97, 0xb2, 0x60, 0x0d, 0x14, 0x89, 0xf3, 0xaa, 0xc1, 0x38, 0xfd, 0x99, + 0x60, 0x2e, 0x24, 0x6d, 0xb3, 0x78, 0xb7, 0xaf, 0x83, 0x07, 0x45, 0x87, 0xa3, 0x9f, 0xbd, 0xee, + 0xe8, 0xdf, 0x01, 0x65, 0xe5, 0xec, 0x80, 0x93, 0x20, 0x66, 0xd2, 0x5b, 0xac, 0xd2, 0x9b, 0xb3, + 0x17, 0x65, 0x72, 0xab, 0x23, 0x3d, 0x3c, 0x86, 0x86, 0xcf, 0xc7, 0x92, 0xbb, 0x79, 0xad, 0xa9, + 0x85, 0xb7, 0xc1, 0x42, 0xc4, 0xe9, 0x11, 0xe5, 0x9c, 0xba, 0x9d, 0xdb, 0xd5, 0x7f, 0x55, 0xfb, + 0xfc, 0x9e, 0xb4, 0xcd, 0x85, 0xbd, 0xe1, 0x16, 0x1e, 0xc5, 0xda, 0xdb, 0xad, 0x0b, 0x23, 0x73, + 0x76, 0x61, 0x64, 0xce, 0x2f, 0x8c, 0xcc, 0x9b, 0xc4, 0xd0, 0x5a, 0x89, 0xa1, 0x9d, 0x25, 0x86, + 0x76, 0x9e, 0x18, 0xda, 0xa7, 0xc4, 0xd0, 0xde, 0x7e, 0x36, 0x32, 0xcf, 0xcc, 0x2b, 0x3e, 0x50, + 0xbe, 0x05, 0x00, 0x00, 0xff, 0xff, 0xff, 0x56, 0x51, 0x57, 0xc2, 0x08, 0x00, 0x00, } func (m *Lease) Marshal() (dAtA []byte, err error) { @@ -225,6 +321,163 @@ func (m *Lease) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *LeaseCandidate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LeaseCandidate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LeaseCandidate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *LeaseCandidateList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LeaseCandidateList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LeaseCandidateList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *LeaseCandidateSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LeaseCandidateSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LeaseCandidateSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Strategy) + copy(dAtA[i:], m.Strategy) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Strategy))) + i-- + dAtA[i] = 0x32 + i -= len(m.EmulationVersion) + copy(dAtA[i:], m.EmulationVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.EmulationVersion))) + i-- + dAtA[i] = 0x2a + i -= len(m.BinaryVersion) + copy(dAtA[i:], m.BinaryVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.BinaryVersion))) + i-- + dAtA[i] = 0x22 + if m.RenewTime != nil { + { + size, err := m.RenewTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.PingTime != nil { + { + size, err := m.PingTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i -= len(m.LeaseName) + copy(dAtA[i:], m.LeaseName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.LeaseName))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *LeaseList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -374,6 +627,61 @@ func (m *Lease) Size() (n int) { return n } +func (m *LeaseCandidate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *LeaseCandidateList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *LeaseCandidateSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.LeaseName) + n += 1 + l + sovGenerated(uint64(l)) + if m.PingTime != nil { + l = m.PingTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.RenewTime != nil { + l = m.RenewTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.BinaryVersion) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.EmulationVersion) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Strategy) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *LeaseList) Size() (n int) { if m == nil { return 0 @@ -443,6 +751,48 @@ func (this *Lease) String() string { }, "") return s } +func (this *LeaseCandidate) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&LeaseCandidate{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "LeaseCandidateSpec", "LeaseCandidateSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *LeaseCandidateList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]LeaseCandidate{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "LeaseCandidate", "LeaseCandidate", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&LeaseCandidateList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *LeaseCandidateSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&LeaseCandidateSpec{`, + `LeaseName:` + fmt.Sprintf("%v", this.LeaseName) + `,`, + `PingTime:` + strings.Replace(fmt.Sprintf("%v", this.PingTime), "MicroTime", "v1.MicroTime", 1) + `,`, + `RenewTime:` + strings.Replace(fmt.Sprintf("%v", this.RenewTime), "MicroTime", "v1.MicroTime", 1) + `,`, + `BinaryVersion:` + fmt.Sprintf("%v", this.BinaryVersion) + `,`, + `EmulationVersion:` + fmt.Sprintf("%v", this.EmulationVersion) + `,`, + `Strategy:` + fmt.Sprintf("%v", this.Strategy) + `,`, + `}`, + }, "") + return s +} func (this *LeaseList) String() string { if this == nil { return "nil" @@ -599,6 +949,489 @@ func (m *Lease) Unmarshal(dAtA []byte) error { } return nil } +func (m *LeaseCandidate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LeaseCandidate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LeaseCandidate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LeaseCandidateList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LeaseCandidateList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LeaseCandidateList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, LeaseCandidate{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LeaseCandidateSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LeaseCandidateSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LeaseCandidateSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LeaseName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LeaseName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PingTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PingTime == nil { + m.PingTime = &v1.MicroTime{} + } + if err := m.PingTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RenewTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RenewTime == nil { + m.RenewTime = &v1.MicroTime{} + } + if err := m.RenewTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BinaryVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BinaryVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EmulationVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EmulationVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Strategy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Strategy = k8s_io_api_coordination_v1.CoordinatedLeaseStrategy(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *LeaseList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.proto index 088811a74b..7ca043f528 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/generated.proto @@ -41,6 +41,75 @@ message Lease { optional LeaseSpec spec = 2; } +// LeaseCandidate defines a candidate for a Lease object. +// Candidates are created such that coordinated leader election will pick the best leader from the list of candidates. +message LeaseCandidate { + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec contains the specification of the Lease. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional LeaseCandidateSpec spec = 2; +} + +// LeaseCandidateList is a list of Lease objects. +message LeaseCandidateList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // items is a list of schema objects. + repeated LeaseCandidate items = 2; +} + +// LeaseCandidateSpec is a specification of a Lease. +message LeaseCandidateSpec { + // LeaseName is the name of the lease for which this candidate is contending. + // The limits on this field are the same as on Lease.name. Multiple lease candidates + // may reference the same Lease.name. + // This field is immutable. + // +required + optional string leaseName = 1; + + // PingTime is the last time that the server has requested the LeaseCandidate + // to renew. It is only done during leader election to check if any + // LeaseCandidates have become ineligible. When PingTime is updated, the + // LeaseCandidate will respond by updating RenewTime. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime pingTime = 2; + + // RenewTime is the time that the LeaseCandidate was last updated. + // Any time a Lease needs to do leader election, the PingTime field + // is updated to signal to the LeaseCandidate that they should update + // the RenewTime. + // Old LeaseCandidate objects are also garbage collected if it has been hours + // since the last renew. The PingTime field is updated regularly to prevent + // garbage collection for still active LeaseCandidates. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime renewTime = 3; + + // BinaryVersion is the binary version. It must be in a semver format without leading `v`. + // This field is required. + // +required + optional string binaryVersion = 4; + + // EmulationVersion is the emulation version. It must be in a semver format without leading `v`. + // EmulationVersion must be less than or equal to BinaryVersion. + // This field is required when strategy is "OldestEmulationVersion" + // +optional + optional string emulationVersion = 5; + + // Strategy is the strategy that coordinated leader election will use for picking the leader. + // If multiple candidates for the same Lease return different strategies, the strategy provided + // by the candidate with the latest BinaryVersion will be used. If there is still conflict, + // this is a user error and coordinated leader election will not operate the Lease until resolved. + // +required + optional string strategy = 6; +} + // LeaseList is a list of Lease objects. message LeaseList { // Standard list metadata. diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/register.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/register.go index 85efaa64e7..bd00164233 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/register.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/register.go @@ -46,6 +46,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Lease{}, &LeaseList{}, + &LeaseCandidate{}, + &LeaseCandidateList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/types.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/types.go index d63fc30a9e..781d29efce 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/types.go @@ -91,3 +91,76 @@ type LeaseList struct { // items is a list of schema objects. Items []Lease `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// LeaseCandidate defines a candidate for a Lease object. +// Candidates are created such that coordinated leader election will pick the best leader from the list of candidates. +type LeaseCandidate struct { + metav1.TypeMeta `json:",inline"` + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // spec contains the specification of the Lease. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec LeaseCandidateSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` +} + +// LeaseCandidateSpec is a specification of a Lease. +type LeaseCandidateSpec struct { + // LeaseName is the name of the lease for which this candidate is contending. + // The limits on this field are the same as on Lease.name. Multiple lease candidates + // may reference the same Lease.name. + // This field is immutable. + // +required + LeaseName string `json:"leaseName" protobuf:"bytes,1,name=leaseName"` + // PingTime is the last time that the server has requested the LeaseCandidate + // to renew. It is only done during leader election to check if any + // LeaseCandidates have become ineligible. When PingTime is updated, the + // LeaseCandidate will respond by updating RenewTime. + // +optional + PingTime *metav1.MicroTime `json:"pingTime,omitempty" protobuf:"bytes,2,opt,name=pingTime"` + // RenewTime is the time that the LeaseCandidate was last updated. + // Any time a Lease needs to do leader election, the PingTime field + // is updated to signal to the LeaseCandidate that they should update + // the RenewTime. + // Old LeaseCandidate objects are also garbage collected if it has been hours + // since the last renew. The PingTime field is updated regularly to prevent + // garbage collection for still active LeaseCandidates. + // +optional + RenewTime *metav1.MicroTime `json:"renewTime,omitempty" protobuf:"bytes,3,opt,name=renewTime"` + // BinaryVersion is the binary version. It must be in a semver format without leading `v`. + // This field is required. + // +required + BinaryVersion string `json:"binaryVersion" protobuf:"bytes,4,name=binaryVersion"` + // EmulationVersion is the emulation version. It must be in a semver format without leading `v`. + // EmulationVersion must be less than or equal to BinaryVersion. + // This field is required when strategy is "OldestEmulationVersion" + // +optional + EmulationVersion string `json:"emulationVersion,omitempty" protobuf:"bytes,5,opt,name=emulationVersion"` + // Strategy is the strategy that coordinated leader election will use for picking the leader. + // If multiple candidates for the same Lease return different strategies, the strategy provided + // by the candidate with the latest BinaryVersion will be used. If there is still conflict, + // this is a user error and coordinated leader election will not operate the Lease until resolved. + // +required + Strategy v1.CoordinatedLeaseStrategy `json:"strategy,omitempty" protobuf:"bytes,6,opt,name=strategy"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// LeaseCandidateList is a list of Lease objects. +type LeaseCandidateList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // items is a list of schema objects. + Items []LeaseCandidate `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/types_swagger_doc_generated.go index 50fe8ea189..35812b77f3 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/types_swagger_doc_generated.go @@ -37,6 +37,40 @@ func (Lease) SwaggerDoc() map[string]string { return map_Lease } +var map_LeaseCandidate = map[string]string{ + "": "LeaseCandidate defines a candidate for a Lease object. Candidates are created such that coordinated leader election will pick the best leader from the list of candidates.", + "metadata": "More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec contains the specification of the Lease. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", +} + +func (LeaseCandidate) SwaggerDoc() map[string]string { + return map_LeaseCandidate +} + +var map_LeaseCandidateList = map[string]string{ + "": "LeaseCandidateList is a list of Lease objects.", + "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "items": "items is a list of schema objects.", +} + +func (LeaseCandidateList) SwaggerDoc() map[string]string { + return map_LeaseCandidateList +} + +var map_LeaseCandidateSpec = map[string]string{ + "": "LeaseCandidateSpec is a specification of a Lease.", + "leaseName": "LeaseName is the name of the lease for which this candidate is contending. The limits on this field are the same as on Lease.name. Multiple lease candidates may reference the same Lease.name. This field is immutable.", + "pingTime": "PingTime is the last time that the server has requested the LeaseCandidate to renew. It is only done during leader election to check if any LeaseCandidates have become ineligible. When PingTime is updated, the LeaseCandidate will respond by updating RenewTime.", + "renewTime": "RenewTime is the time that the LeaseCandidate was last updated. Any time a Lease needs to do leader election, the PingTime field is updated to signal to the LeaseCandidate that they should update the RenewTime. Old LeaseCandidate objects are also garbage collected if it has been hours since the last renew. The PingTime field is updated regularly to prevent garbage collection for still active LeaseCandidates.", + "binaryVersion": "BinaryVersion is the binary version. It must be in a semver format without leading `v`. This field is required.", + "emulationVersion": "EmulationVersion is the emulation version. It must be in a semver format without leading `v`. EmulationVersion must be less than or equal to BinaryVersion. This field is required when strategy is \"OldestEmulationVersion\"", + "strategy": "Strategy is the strategy that coordinated leader election will use for picking the leader. If multiple candidates for the same Lease return different strategies, the strategy provided by the candidate with the latest BinaryVersion will be used. If there is still conflict, this is a user error and coordinated leader election will not operate the Lease until resolved.", +} + +func (LeaseCandidateSpec) SwaggerDoc() map[string]string { + return map_LeaseCandidateSpec +} + var map_LeaseList = map[string]string{ "": "LeaseList is a list of Lease objects.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.deepcopy.go index dcef1e346a..b990ee247f 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.deepcopy.go @@ -53,6 +53,90 @@ func (in *Lease) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaseCandidate) DeepCopyInto(out *LeaseCandidate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidate. +func (in *LeaseCandidate) DeepCopy() *LeaseCandidate { + if in == nil { + return nil + } + out := new(LeaseCandidate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LeaseCandidate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaseCandidateList) DeepCopyInto(out *LeaseCandidateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LeaseCandidate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateList. +func (in *LeaseCandidateList) DeepCopy() *LeaseCandidateList { + if in == nil { + return nil + } + out := new(LeaseCandidateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LeaseCandidateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaseCandidateSpec) DeepCopyInto(out *LeaseCandidateSpec) { + *out = *in + if in.PingTime != nil { + in, out := &in.PingTime, &out.PingTime + *out = (*in).DeepCopy() + } + if in.RenewTime != nil { + in, out := &in.RenewTime, &out.RenewTime + *out = (*in).DeepCopy() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaseCandidateSpec. +func (in *LeaseCandidateSpec) DeepCopy() *LeaseCandidateSpec { + if in == nil { + return nil + } + out := new(LeaseCandidateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeaseList) DeepCopyInto(out *LeaseList) { *out = *in diff --git a/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.prerelease-lifecycle.go b/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.prerelease-lifecycle.go index 18926aa108..73636edfa3 100644 --- a/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.prerelease-lifecycle.go +++ b/go-controller/vendor/k8s.io/api/coordination/v1beta1/zz_generated.prerelease-lifecycle.go @@ -49,6 +49,42 @@ func (in *Lease) APILifecycleRemoved() (major, minor int) { return 1, 22 } +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *LeaseCandidate) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *LeaseCandidate) APILifecycleDeprecated() (major, minor int) { + return 1, 36 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *LeaseCandidate) APILifecycleRemoved() (major, minor int) { + return 1, 39 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *LeaseCandidateList) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor. +func (in *LeaseCandidateList) APILifecycleDeprecated() (major, minor int) { + return 1, 36 +} + +// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor. +func (in *LeaseCandidateList) APILifecycleRemoved() (major, minor int) { + return 1, 39 +} + // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. func (in *LeaseList) APILifecycleIntroduced() (major, minor int) { diff --git a/go-controller/vendor/k8s.io/api/core/v1/doc.go b/go-controller/vendor/k8s.io/api/core/v1/doc.go index bc0041b331..e4e9196aeb 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/core/v1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName= // Package v1 is the v1 version of the core API. -package v1 // import "k8s.io/api/core/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/core/v1/generated.pb.go b/go-controller/vendor/k8s.io/api/core/v1/generated.pb.go index 9d466c6d79..a4b8f58429 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/core/v1/generated.pb.go @@ -3213,10 +3213,38 @@ func (m *NodeStatus) XXX_DiscardUnknown() { var xxx_messageInfo_NodeStatus proto.InternalMessageInfo +func (m *NodeSwapStatus) Reset() { *m = NodeSwapStatus{} } +func (*NodeSwapStatus) ProtoMessage() {} +func (*NodeSwapStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_6c07b07c062484ab, []int{113} +} +func (m *NodeSwapStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *NodeSwapStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *NodeSwapStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_NodeSwapStatus.Merge(m, src) +} +func (m *NodeSwapStatus) XXX_Size() int { + return m.Size() +} +func (m *NodeSwapStatus) XXX_DiscardUnknown() { + xxx_messageInfo_NodeSwapStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_NodeSwapStatus proto.InternalMessageInfo + func (m *NodeSystemInfo) Reset() { *m = NodeSystemInfo{} } func (*NodeSystemInfo) ProtoMessage() {} func (*NodeSystemInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{113} + return fileDescriptor_6c07b07c062484ab, []int{114} } func (m *NodeSystemInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3244,7 +3272,7 @@ var xxx_messageInfo_NodeSystemInfo proto.InternalMessageInfo func (m *ObjectFieldSelector) Reset() { *m = ObjectFieldSelector{} } func (*ObjectFieldSelector) ProtoMessage() {} func (*ObjectFieldSelector) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{114} + return fileDescriptor_6c07b07c062484ab, []int{115} } func (m *ObjectFieldSelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3272,7 +3300,7 @@ var xxx_messageInfo_ObjectFieldSelector proto.InternalMessageInfo func (m *ObjectReference) Reset() { *m = ObjectReference{} } func (*ObjectReference) ProtoMessage() {} func (*ObjectReference) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{115} + return fileDescriptor_6c07b07c062484ab, []int{116} } func (m *ObjectReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3300,7 +3328,7 @@ var xxx_messageInfo_ObjectReference proto.InternalMessageInfo func (m *PersistentVolume) Reset() { *m = PersistentVolume{} } func (*PersistentVolume) ProtoMessage() {} func (*PersistentVolume) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{116} + return fileDescriptor_6c07b07c062484ab, []int{117} } func (m *PersistentVolume) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3328,7 +3356,7 @@ var xxx_messageInfo_PersistentVolume proto.InternalMessageInfo func (m *PersistentVolumeClaim) Reset() { *m = PersistentVolumeClaim{} } func (*PersistentVolumeClaim) ProtoMessage() {} func (*PersistentVolumeClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{117} + return fileDescriptor_6c07b07c062484ab, []int{118} } func (m *PersistentVolumeClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3356,7 +3384,7 @@ var xxx_messageInfo_PersistentVolumeClaim proto.InternalMessageInfo func (m *PersistentVolumeClaimCondition) Reset() { *m = PersistentVolumeClaimCondition{} } func (*PersistentVolumeClaimCondition) ProtoMessage() {} func (*PersistentVolumeClaimCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{118} + return fileDescriptor_6c07b07c062484ab, []int{119} } func (m *PersistentVolumeClaimCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3384,7 +3412,7 @@ var xxx_messageInfo_PersistentVolumeClaimCondition proto.InternalMessageInfo func (m *PersistentVolumeClaimList) Reset() { *m = PersistentVolumeClaimList{} } func (*PersistentVolumeClaimList) ProtoMessage() {} func (*PersistentVolumeClaimList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{119} + return fileDescriptor_6c07b07c062484ab, []int{120} } func (m *PersistentVolumeClaimList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3412,7 +3440,7 @@ var xxx_messageInfo_PersistentVolumeClaimList proto.InternalMessageInfo func (m *PersistentVolumeClaimSpec) Reset() { *m = PersistentVolumeClaimSpec{} } func (*PersistentVolumeClaimSpec) ProtoMessage() {} func (*PersistentVolumeClaimSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{120} + return fileDescriptor_6c07b07c062484ab, []int{121} } func (m *PersistentVolumeClaimSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3440,7 +3468,7 @@ var xxx_messageInfo_PersistentVolumeClaimSpec proto.InternalMessageInfo func (m *PersistentVolumeClaimStatus) Reset() { *m = PersistentVolumeClaimStatus{} } func (*PersistentVolumeClaimStatus) ProtoMessage() {} func (*PersistentVolumeClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{121} + return fileDescriptor_6c07b07c062484ab, []int{122} } func (m *PersistentVolumeClaimStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3468,7 +3496,7 @@ var xxx_messageInfo_PersistentVolumeClaimStatus proto.InternalMessageInfo func (m *PersistentVolumeClaimTemplate) Reset() { *m = PersistentVolumeClaimTemplate{} } func (*PersistentVolumeClaimTemplate) ProtoMessage() {} func (*PersistentVolumeClaimTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{122} + return fileDescriptor_6c07b07c062484ab, []int{123} } func (m *PersistentVolumeClaimTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3496,7 +3524,7 @@ var xxx_messageInfo_PersistentVolumeClaimTemplate proto.InternalMessageInfo func (m *PersistentVolumeClaimVolumeSource) Reset() { *m = PersistentVolumeClaimVolumeSource{} } func (*PersistentVolumeClaimVolumeSource) ProtoMessage() {} func (*PersistentVolumeClaimVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{123} + return fileDescriptor_6c07b07c062484ab, []int{124} } func (m *PersistentVolumeClaimVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3524,7 +3552,7 @@ var xxx_messageInfo_PersistentVolumeClaimVolumeSource proto.InternalMessageInfo func (m *PersistentVolumeList) Reset() { *m = PersistentVolumeList{} } func (*PersistentVolumeList) ProtoMessage() {} func (*PersistentVolumeList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{124} + return fileDescriptor_6c07b07c062484ab, []int{125} } func (m *PersistentVolumeList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3552,7 +3580,7 @@ var xxx_messageInfo_PersistentVolumeList proto.InternalMessageInfo func (m *PersistentVolumeSource) Reset() { *m = PersistentVolumeSource{} } func (*PersistentVolumeSource) ProtoMessage() {} func (*PersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{125} + return fileDescriptor_6c07b07c062484ab, []int{126} } func (m *PersistentVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3580,7 +3608,7 @@ var xxx_messageInfo_PersistentVolumeSource proto.InternalMessageInfo func (m *PersistentVolumeSpec) Reset() { *m = PersistentVolumeSpec{} } func (*PersistentVolumeSpec) ProtoMessage() {} func (*PersistentVolumeSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{126} + return fileDescriptor_6c07b07c062484ab, []int{127} } func (m *PersistentVolumeSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3608,7 +3636,7 @@ var xxx_messageInfo_PersistentVolumeSpec proto.InternalMessageInfo func (m *PersistentVolumeStatus) Reset() { *m = PersistentVolumeStatus{} } func (*PersistentVolumeStatus) ProtoMessage() {} func (*PersistentVolumeStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{127} + return fileDescriptor_6c07b07c062484ab, []int{128} } func (m *PersistentVolumeStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3636,7 +3664,7 @@ var xxx_messageInfo_PersistentVolumeStatus proto.InternalMessageInfo func (m *PhotonPersistentDiskVolumeSource) Reset() { *m = PhotonPersistentDiskVolumeSource{} } func (*PhotonPersistentDiskVolumeSource) ProtoMessage() {} func (*PhotonPersistentDiskVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{128} + return fileDescriptor_6c07b07c062484ab, []int{129} } func (m *PhotonPersistentDiskVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3664,7 +3692,7 @@ var xxx_messageInfo_PhotonPersistentDiskVolumeSource proto.InternalMessageInfo func (m *Pod) Reset() { *m = Pod{} } func (*Pod) ProtoMessage() {} func (*Pod) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{129} + return fileDescriptor_6c07b07c062484ab, []int{130} } func (m *Pod) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3692,7 +3720,7 @@ var xxx_messageInfo_Pod proto.InternalMessageInfo func (m *PodAffinity) Reset() { *m = PodAffinity{} } func (*PodAffinity) ProtoMessage() {} func (*PodAffinity) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{130} + return fileDescriptor_6c07b07c062484ab, []int{131} } func (m *PodAffinity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3720,7 +3748,7 @@ var xxx_messageInfo_PodAffinity proto.InternalMessageInfo func (m *PodAffinityTerm) Reset() { *m = PodAffinityTerm{} } func (*PodAffinityTerm) ProtoMessage() {} func (*PodAffinityTerm) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{131} + return fileDescriptor_6c07b07c062484ab, []int{132} } func (m *PodAffinityTerm) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3748,7 +3776,7 @@ var xxx_messageInfo_PodAffinityTerm proto.InternalMessageInfo func (m *PodAntiAffinity) Reset() { *m = PodAntiAffinity{} } func (*PodAntiAffinity) ProtoMessage() {} func (*PodAntiAffinity) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{132} + return fileDescriptor_6c07b07c062484ab, []int{133} } func (m *PodAntiAffinity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3776,7 +3804,7 @@ var xxx_messageInfo_PodAntiAffinity proto.InternalMessageInfo func (m *PodAttachOptions) Reset() { *m = PodAttachOptions{} } func (*PodAttachOptions) ProtoMessage() {} func (*PodAttachOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{133} + return fileDescriptor_6c07b07c062484ab, []int{134} } func (m *PodAttachOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3804,7 +3832,7 @@ var xxx_messageInfo_PodAttachOptions proto.InternalMessageInfo func (m *PodCondition) Reset() { *m = PodCondition{} } func (*PodCondition) ProtoMessage() {} func (*PodCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{134} + return fileDescriptor_6c07b07c062484ab, []int{135} } func (m *PodCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3832,7 +3860,7 @@ var xxx_messageInfo_PodCondition proto.InternalMessageInfo func (m *PodDNSConfig) Reset() { *m = PodDNSConfig{} } func (*PodDNSConfig) ProtoMessage() {} func (*PodDNSConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{135} + return fileDescriptor_6c07b07c062484ab, []int{136} } func (m *PodDNSConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3860,7 +3888,7 @@ var xxx_messageInfo_PodDNSConfig proto.InternalMessageInfo func (m *PodDNSConfigOption) Reset() { *m = PodDNSConfigOption{} } func (*PodDNSConfigOption) ProtoMessage() {} func (*PodDNSConfigOption) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{136} + return fileDescriptor_6c07b07c062484ab, []int{137} } func (m *PodDNSConfigOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3888,7 +3916,7 @@ var xxx_messageInfo_PodDNSConfigOption proto.InternalMessageInfo func (m *PodExecOptions) Reset() { *m = PodExecOptions{} } func (*PodExecOptions) ProtoMessage() {} func (*PodExecOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{137} + return fileDescriptor_6c07b07c062484ab, []int{138} } func (m *PodExecOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3916,7 +3944,7 @@ var xxx_messageInfo_PodExecOptions proto.InternalMessageInfo func (m *PodIP) Reset() { *m = PodIP{} } func (*PodIP) ProtoMessage() {} func (*PodIP) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{138} + return fileDescriptor_6c07b07c062484ab, []int{139} } func (m *PodIP) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3944,7 +3972,7 @@ var xxx_messageInfo_PodIP proto.InternalMessageInfo func (m *PodList) Reset() { *m = PodList{} } func (*PodList) ProtoMessage() {} func (*PodList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{139} + return fileDescriptor_6c07b07c062484ab, []int{140} } func (m *PodList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3972,7 +4000,7 @@ var xxx_messageInfo_PodList proto.InternalMessageInfo func (m *PodLogOptions) Reset() { *m = PodLogOptions{} } func (*PodLogOptions) ProtoMessage() {} func (*PodLogOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{140} + return fileDescriptor_6c07b07c062484ab, []int{141} } func (m *PodLogOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4000,7 +4028,7 @@ var xxx_messageInfo_PodLogOptions proto.InternalMessageInfo func (m *PodOS) Reset() { *m = PodOS{} } func (*PodOS) ProtoMessage() {} func (*PodOS) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{141} + return fileDescriptor_6c07b07c062484ab, []int{142} } func (m *PodOS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4028,7 +4056,7 @@ var xxx_messageInfo_PodOS proto.InternalMessageInfo func (m *PodPortForwardOptions) Reset() { *m = PodPortForwardOptions{} } func (*PodPortForwardOptions) ProtoMessage() {} func (*PodPortForwardOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{142} + return fileDescriptor_6c07b07c062484ab, []int{143} } func (m *PodPortForwardOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4056,7 +4084,7 @@ var xxx_messageInfo_PodPortForwardOptions proto.InternalMessageInfo func (m *PodProxyOptions) Reset() { *m = PodProxyOptions{} } func (*PodProxyOptions) ProtoMessage() {} func (*PodProxyOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{143} + return fileDescriptor_6c07b07c062484ab, []int{144} } func (m *PodProxyOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4084,7 +4112,7 @@ var xxx_messageInfo_PodProxyOptions proto.InternalMessageInfo func (m *PodReadinessGate) Reset() { *m = PodReadinessGate{} } func (*PodReadinessGate) ProtoMessage() {} func (*PodReadinessGate) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{144} + return fileDescriptor_6c07b07c062484ab, []int{145} } func (m *PodReadinessGate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4112,7 +4140,7 @@ var xxx_messageInfo_PodReadinessGate proto.InternalMessageInfo func (m *PodResourceClaim) Reset() { *m = PodResourceClaim{} } func (*PodResourceClaim) ProtoMessage() {} func (*PodResourceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{145} + return fileDescriptor_6c07b07c062484ab, []int{146} } func (m *PodResourceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4140,7 +4168,7 @@ var xxx_messageInfo_PodResourceClaim proto.InternalMessageInfo func (m *PodResourceClaimStatus) Reset() { *m = PodResourceClaimStatus{} } func (*PodResourceClaimStatus) ProtoMessage() {} func (*PodResourceClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{146} + return fileDescriptor_6c07b07c062484ab, []int{147} } func (m *PodResourceClaimStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4168,7 +4196,7 @@ var xxx_messageInfo_PodResourceClaimStatus proto.InternalMessageInfo func (m *PodSchedulingGate) Reset() { *m = PodSchedulingGate{} } func (*PodSchedulingGate) ProtoMessage() {} func (*PodSchedulingGate) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{147} + return fileDescriptor_6c07b07c062484ab, []int{148} } func (m *PodSchedulingGate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4196,7 +4224,7 @@ var xxx_messageInfo_PodSchedulingGate proto.InternalMessageInfo func (m *PodSecurityContext) Reset() { *m = PodSecurityContext{} } func (*PodSecurityContext) ProtoMessage() {} func (*PodSecurityContext) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{148} + return fileDescriptor_6c07b07c062484ab, []int{149} } func (m *PodSecurityContext) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4224,7 +4252,7 @@ var xxx_messageInfo_PodSecurityContext proto.InternalMessageInfo func (m *PodSignature) Reset() { *m = PodSignature{} } func (*PodSignature) ProtoMessage() {} func (*PodSignature) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{149} + return fileDescriptor_6c07b07c062484ab, []int{150} } func (m *PodSignature) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4252,7 +4280,7 @@ var xxx_messageInfo_PodSignature proto.InternalMessageInfo func (m *PodSpec) Reset() { *m = PodSpec{} } func (*PodSpec) ProtoMessage() {} func (*PodSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{150} + return fileDescriptor_6c07b07c062484ab, []int{151} } func (m *PodSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4280,7 +4308,7 @@ var xxx_messageInfo_PodSpec proto.InternalMessageInfo func (m *PodStatus) Reset() { *m = PodStatus{} } func (*PodStatus) ProtoMessage() {} func (*PodStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{151} + return fileDescriptor_6c07b07c062484ab, []int{152} } func (m *PodStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4308,7 +4336,7 @@ var xxx_messageInfo_PodStatus proto.InternalMessageInfo func (m *PodStatusResult) Reset() { *m = PodStatusResult{} } func (*PodStatusResult) ProtoMessage() {} func (*PodStatusResult) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{152} + return fileDescriptor_6c07b07c062484ab, []int{153} } func (m *PodStatusResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4336,7 +4364,7 @@ var xxx_messageInfo_PodStatusResult proto.InternalMessageInfo func (m *PodTemplate) Reset() { *m = PodTemplate{} } func (*PodTemplate) ProtoMessage() {} func (*PodTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{153} + return fileDescriptor_6c07b07c062484ab, []int{154} } func (m *PodTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4364,7 +4392,7 @@ var xxx_messageInfo_PodTemplate proto.InternalMessageInfo func (m *PodTemplateList) Reset() { *m = PodTemplateList{} } func (*PodTemplateList) ProtoMessage() {} func (*PodTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{154} + return fileDescriptor_6c07b07c062484ab, []int{155} } func (m *PodTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4392,7 +4420,7 @@ var xxx_messageInfo_PodTemplateList proto.InternalMessageInfo func (m *PodTemplateSpec) Reset() { *m = PodTemplateSpec{} } func (*PodTemplateSpec) ProtoMessage() {} func (*PodTemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{155} + return fileDescriptor_6c07b07c062484ab, []int{156} } func (m *PodTemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4420,7 +4448,7 @@ var xxx_messageInfo_PodTemplateSpec proto.InternalMessageInfo func (m *PortStatus) Reset() { *m = PortStatus{} } func (*PortStatus) ProtoMessage() {} func (*PortStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{156} + return fileDescriptor_6c07b07c062484ab, []int{157} } func (m *PortStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4448,7 +4476,7 @@ var xxx_messageInfo_PortStatus proto.InternalMessageInfo func (m *PortworxVolumeSource) Reset() { *m = PortworxVolumeSource{} } func (*PortworxVolumeSource) ProtoMessage() {} func (*PortworxVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{157} + return fileDescriptor_6c07b07c062484ab, []int{158} } func (m *PortworxVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4476,7 +4504,7 @@ var xxx_messageInfo_PortworxVolumeSource proto.InternalMessageInfo func (m *Preconditions) Reset() { *m = Preconditions{} } func (*Preconditions) ProtoMessage() {} func (*Preconditions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{158} + return fileDescriptor_6c07b07c062484ab, []int{159} } func (m *Preconditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4504,7 +4532,7 @@ var xxx_messageInfo_Preconditions proto.InternalMessageInfo func (m *PreferAvoidPodsEntry) Reset() { *m = PreferAvoidPodsEntry{} } func (*PreferAvoidPodsEntry) ProtoMessage() {} func (*PreferAvoidPodsEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{159} + return fileDescriptor_6c07b07c062484ab, []int{160} } func (m *PreferAvoidPodsEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4532,7 +4560,7 @@ var xxx_messageInfo_PreferAvoidPodsEntry proto.InternalMessageInfo func (m *PreferredSchedulingTerm) Reset() { *m = PreferredSchedulingTerm{} } func (*PreferredSchedulingTerm) ProtoMessage() {} func (*PreferredSchedulingTerm) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{160} + return fileDescriptor_6c07b07c062484ab, []int{161} } func (m *PreferredSchedulingTerm) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4560,7 +4588,7 @@ var xxx_messageInfo_PreferredSchedulingTerm proto.InternalMessageInfo func (m *Probe) Reset() { *m = Probe{} } func (*Probe) ProtoMessage() {} func (*Probe) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{161} + return fileDescriptor_6c07b07c062484ab, []int{162} } func (m *Probe) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4588,7 +4616,7 @@ var xxx_messageInfo_Probe proto.InternalMessageInfo func (m *ProbeHandler) Reset() { *m = ProbeHandler{} } func (*ProbeHandler) ProtoMessage() {} func (*ProbeHandler) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{162} + return fileDescriptor_6c07b07c062484ab, []int{163} } func (m *ProbeHandler) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4616,7 +4644,7 @@ var xxx_messageInfo_ProbeHandler proto.InternalMessageInfo func (m *ProjectedVolumeSource) Reset() { *m = ProjectedVolumeSource{} } func (*ProjectedVolumeSource) ProtoMessage() {} func (*ProjectedVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{163} + return fileDescriptor_6c07b07c062484ab, []int{164} } func (m *ProjectedVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4644,7 +4672,7 @@ var xxx_messageInfo_ProjectedVolumeSource proto.InternalMessageInfo func (m *QuobyteVolumeSource) Reset() { *m = QuobyteVolumeSource{} } func (*QuobyteVolumeSource) ProtoMessage() {} func (*QuobyteVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{164} + return fileDescriptor_6c07b07c062484ab, []int{165} } func (m *QuobyteVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4672,7 +4700,7 @@ var xxx_messageInfo_QuobyteVolumeSource proto.InternalMessageInfo func (m *RBDPersistentVolumeSource) Reset() { *m = RBDPersistentVolumeSource{} } func (*RBDPersistentVolumeSource) ProtoMessage() {} func (*RBDPersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{165} + return fileDescriptor_6c07b07c062484ab, []int{166} } func (m *RBDPersistentVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4700,7 +4728,7 @@ var xxx_messageInfo_RBDPersistentVolumeSource proto.InternalMessageInfo func (m *RBDVolumeSource) Reset() { *m = RBDVolumeSource{} } func (*RBDVolumeSource) ProtoMessage() {} func (*RBDVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{166} + return fileDescriptor_6c07b07c062484ab, []int{167} } func (m *RBDVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4728,7 +4756,7 @@ var xxx_messageInfo_RBDVolumeSource proto.InternalMessageInfo func (m *RangeAllocation) Reset() { *m = RangeAllocation{} } func (*RangeAllocation) ProtoMessage() {} func (*RangeAllocation) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{167} + return fileDescriptor_6c07b07c062484ab, []int{168} } func (m *RangeAllocation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4756,7 +4784,7 @@ var xxx_messageInfo_RangeAllocation proto.InternalMessageInfo func (m *ReplicationController) Reset() { *m = ReplicationController{} } func (*ReplicationController) ProtoMessage() {} func (*ReplicationController) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{168} + return fileDescriptor_6c07b07c062484ab, []int{169} } func (m *ReplicationController) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4784,7 +4812,7 @@ var xxx_messageInfo_ReplicationController proto.InternalMessageInfo func (m *ReplicationControllerCondition) Reset() { *m = ReplicationControllerCondition{} } func (*ReplicationControllerCondition) ProtoMessage() {} func (*ReplicationControllerCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{169} + return fileDescriptor_6c07b07c062484ab, []int{170} } func (m *ReplicationControllerCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4812,7 +4840,7 @@ var xxx_messageInfo_ReplicationControllerCondition proto.InternalMessageInfo func (m *ReplicationControllerList) Reset() { *m = ReplicationControllerList{} } func (*ReplicationControllerList) ProtoMessage() {} func (*ReplicationControllerList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{170} + return fileDescriptor_6c07b07c062484ab, []int{171} } func (m *ReplicationControllerList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4840,7 +4868,7 @@ var xxx_messageInfo_ReplicationControllerList proto.InternalMessageInfo func (m *ReplicationControllerSpec) Reset() { *m = ReplicationControllerSpec{} } func (*ReplicationControllerSpec) ProtoMessage() {} func (*ReplicationControllerSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{171} + return fileDescriptor_6c07b07c062484ab, []int{172} } func (m *ReplicationControllerSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4868,7 +4896,7 @@ var xxx_messageInfo_ReplicationControllerSpec proto.InternalMessageInfo func (m *ReplicationControllerStatus) Reset() { *m = ReplicationControllerStatus{} } func (*ReplicationControllerStatus) ProtoMessage() {} func (*ReplicationControllerStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{172} + return fileDescriptor_6c07b07c062484ab, []int{173} } func (m *ReplicationControllerStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4896,7 +4924,7 @@ var xxx_messageInfo_ReplicationControllerStatus proto.InternalMessageInfo func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } func (*ResourceClaim) ProtoMessage() {} func (*ResourceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{173} + return fileDescriptor_6c07b07c062484ab, []int{174} } func (m *ResourceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4924,7 +4952,7 @@ var xxx_messageInfo_ResourceClaim proto.InternalMessageInfo func (m *ResourceFieldSelector) Reset() { *m = ResourceFieldSelector{} } func (*ResourceFieldSelector) ProtoMessage() {} func (*ResourceFieldSelector) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{174} + return fileDescriptor_6c07b07c062484ab, []int{175} } func (m *ResourceFieldSelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4952,7 +4980,7 @@ var xxx_messageInfo_ResourceFieldSelector proto.InternalMessageInfo func (m *ResourceHealth) Reset() { *m = ResourceHealth{} } func (*ResourceHealth) ProtoMessage() {} func (*ResourceHealth) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{175} + return fileDescriptor_6c07b07c062484ab, []int{176} } func (m *ResourceHealth) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4980,7 +5008,7 @@ var xxx_messageInfo_ResourceHealth proto.InternalMessageInfo func (m *ResourceQuota) Reset() { *m = ResourceQuota{} } func (*ResourceQuota) ProtoMessage() {} func (*ResourceQuota) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{176} + return fileDescriptor_6c07b07c062484ab, []int{177} } func (m *ResourceQuota) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5008,7 +5036,7 @@ var xxx_messageInfo_ResourceQuota proto.InternalMessageInfo func (m *ResourceQuotaList) Reset() { *m = ResourceQuotaList{} } func (*ResourceQuotaList) ProtoMessage() {} func (*ResourceQuotaList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{177} + return fileDescriptor_6c07b07c062484ab, []int{178} } func (m *ResourceQuotaList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5036,7 +5064,7 @@ var xxx_messageInfo_ResourceQuotaList proto.InternalMessageInfo func (m *ResourceQuotaSpec) Reset() { *m = ResourceQuotaSpec{} } func (*ResourceQuotaSpec) ProtoMessage() {} func (*ResourceQuotaSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{178} + return fileDescriptor_6c07b07c062484ab, []int{179} } func (m *ResourceQuotaSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5064,7 +5092,7 @@ var xxx_messageInfo_ResourceQuotaSpec proto.InternalMessageInfo func (m *ResourceQuotaStatus) Reset() { *m = ResourceQuotaStatus{} } func (*ResourceQuotaStatus) ProtoMessage() {} func (*ResourceQuotaStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{179} + return fileDescriptor_6c07b07c062484ab, []int{180} } func (m *ResourceQuotaStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5092,7 +5120,7 @@ var xxx_messageInfo_ResourceQuotaStatus proto.InternalMessageInfo func (m *ResourceRequirements) Reset() { *m = ResourceRequirements{} } func (*ResourceRequirements) ProtoMessage() {} func (*ResourceRequirements) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{180} + return fileDescriptor_6c07b07c062484ab, []int{181} } func (m *ResourceRequirements) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5120,7 +5148,7 @@ var xxx_messageInfo_ResourceRequirements proto.InternalMessageInfo func (m *ResourceStatus) Reset() { *m = ResourceStatus{} } func (*ResourceStatus) ProtoMessage() {} func (*ResourceStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{181} + return fileDescriptor_6c07b07c062484ab, []int{182} } func (m *ResourceStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5148,7 +5176,7 @@ var xxx_messageInfo_ResourceStatus proto.InternalMessageInfo func (m *SELinuxOptions) Reset() { *m = SELinuxOptions{} } func (*SELinuxOptions) ProtoMessage() {} func (*SELinuxOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{182} + return fileDescriptor_6c07b07c062484ab, []int{183} } func (m *SELinuxOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5176,7 +5204,7 @@ var xxx_messageInfo_SELinuxOptions proto.InternalMessageInfo func (m *ScaleIOPersistentVolumeSource) Reset() { *m = ScaleIOPersistentVolumeSource{} } func (*ScaleIOPersistentVolumeSource) ProtoMessage() {} func (*ScaleIOPersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{183} + return fileDescriptor_6c07b07c062484ab, []int{184} } func (m *ScaleIOPersistentVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5204,7 +5232,7 @@ var xxx_messageInfo_ScaleIOPersistentVolumeSource proto.InternalMessageInfo func (m *ScaleIOVolumeSource) Reset() { *m = ScaleIOVolumeSource{} } func (*ScaleIOVolumeSource) ProtoMessage() {} func (*ScaleIOVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{184} + return fileDescriptor_6c07b07c062484ab, []int{185} } func (m *ScaleIOVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5232,7 +5260,7 @@ var xxx_messageInfo_ScaleIOVolumeSource proto.InternalMessageInfo func (m *ScopeSelector) Reset() { *m = ScopeSelector{} } func (*ScopeSelector) ProtoMessage() {} func (*ScopeSelector) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{185} + return fileDescriptor_6c07b07c062484ab, []int{186} } func (m *ScopeSelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5260,7 +5288,7 @@ var xxx_messageInfo_ScopeSelector proto.InternalMessageInfo func (m *ScopedResourceSelectorRequirement) Reset() { *m = ScopedResourceSelectorRequirement{} } func (*ScopedResourceSelectorRequirement) ProtoMessage() {} func (*ScopedResourceSelectorRequirement) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{186} + return fileDescriptor_6c07b07c062484ab, []int{187} } func (m *ScopedResourceSelectorRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5288,7 +5316,7 @@ var xxx_messageInfo_ScopedResourceSelectorRequirement proto.InternalMessageInfo func (m *SeccompProfile) Reset() { *m = SeccompProfile{} } func (*SeccompProfile) ProtoMessage() {} func (*SeccompProfile) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{187} + return fileDescriptor_6c07b07c062484ab, []int{188} } func (m *SeccompProfile) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5316,7 +5344,7 @@ var xxx_messageInfo_SeccompProfile proto.InternalMessageInfo func (m *Secret) Reset() { *m = Secret{} } func (*Secret) ProtoMessage() {} func (*Secret) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{188} + return fileDescriptor_6c07b07c062484ab, []int{189} } func (m *Secret) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5344,7 +5372,7 @@ var xxx_messageInfo_Secret proto.InternalMessageInfo func (m *SecretEnvSource) Reset() { *m = SecretEnvSource{} } func (*SecretEnvSource) ProtoMessage() {} func (*SecretEnvSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{189} + return fileDescriptor_6c07b07c062484ab, []int{190} } func (m *SecretEnvSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5372,7 +5400,7 @@ var xxx_messageInfo_SecretEnvSource proto.InternalMessageInfo func (m *SecretKeySelector) Reset() { *m = SecretKeySelector{} } func (*SecretKeySelector) ProtoMessage() {} func (*SecretKeySelector) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{190} + return fileDescriptor_6c07b07c062484ab, []int{191} } func (m *SecretKeySelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5400,7 +5428,7 @@ var xxx_messageInfo_SecretKeySelector proto.InternalMessageInfo func (m *SecretList) Reset() { *m = SecretList{} } func (*SecretList) ProtoMessage() {} func (*SecretList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{191} + return fileDescriptor_6c07b07c062484ab, []int{192} } func (m *SecretList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5428,7 +5456,7 @@ var xxx_messageInfo_SecretList proto.InternalMessageInfo func (m *SecretProjection) Reset() { *m = SecretProjection{} } func (*SecretProjection) ProtoMessage() {} func (*SecretProjection) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{192} + return fileDescriptor_6c07b07c062484ab, []int{193} } func (m *SecretProjection) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5456,7 +5484,7 @@ var xxx_messageInfo_SecretProjection proto.InternalMessageInfo func (m *SecretReference) Reset() { *m = SecretReference{} } func (*SecretReference) ProtoMessage() {} func (*SecretReference) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{193} + return fileDescriptor_6c07b07c062484ab, []int{194} } func (m *SecretReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5484,7 +5512,7 @@ var xxx_messageInfo_SecretReference proto.InternalMessageInfo func (m *SecretVolumeSource) Reset() { *m = SecretVolumeSource{} } func (*SecretVolumeSource) ProtoMessage() {} func (*SecretVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{194} + return fileDescriptor_6c07b07c062484ab, []int{195} } func (m *SecretVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5512,7 +5540,7 @@ var xxx_messageInfo_SecretVolumeSource proto.InternalMessageInfo func (m *SecurityContext) Reset() { *m = SecurityContext{} } func (*SecurityContext) ProtoMessage() {} func (*SecurityContext) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{195} + return fileDescriptor_6c07b07c062484ab, []int{196} } func (m *SecurityContext) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5540,7 +5568,7 @@ var xxx_messageInfo_SecurityContext proto.InternalMessageInfo func (m *SerializedReference) Reset() { *m = SerializedReference{} } func (*SerializedReference) ProtoMessage() {} func (*SerializedReference) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{196} + return fileDescriptor_6c07b07c062484ab, []int{197} } func (m *SerializedReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5568,7 +5596,7 @@ var xxx_messageInfo_SerializedReference proto.InternalMessageInfo func (m *Service) Reset() { *m = Service{} } func (*Service) ProtoMessage() {} func (*Service) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{197} + return fileDescriptor_6c07b07c062484ab, []int{198} } func (m *Service) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5596,7 +5624,7 @@ var xxx_messageInfo_Service proto.InternalMessageInfo func (m *ServiceAccount) Reset() { *m = ServiceAccount{} } func (*ServiceAccount) ProtoMessage() {} func (*ServiceAccount) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{198} + return fileDescriptor_6c07b07c062484ab, []int{199} } func (m *ServiceAccount) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5624,7 +5652,7 @@ var xxx_messageInfo_ServiceAccount proto.InternalMessageInfo func (m *ServiceAccountList) Reset() { *m = ServiceAccountList{} } func (*ServiceAccountList) ProtoMessage() {} func (*ServiceAccountList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{199} + return fileDescriptor_6c07b07c062484ab, []int{200} } func (m *ServiceAccountList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5652,7 +5680,7 @@ var xxx_messageInfo_ServiceAccountList proto.InternalMessageInfo func (m *ServiceAccountTokenProjection) Reset() { *m = ServiceAccountTokenProjection{} } func (*ServiceAccountTokenProjection) ProtoMessage() {} func (*ServiceAccountTokenProjection) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{200} + return fileDescriptor_6c07b07c062484ab, []int{201} } func (m *ServiceAccountTokenProjection) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5680,7 +5708,7 @@ var xxx_messageInfo_ServiceAccountTokenProjection proto.InternalMessageInfo func (m *ServiceList) Reset() { *m = ServiceList{} } func (*ServiceList) ProtoMessage() {} func (*ServiceList) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{201} + return fileDescriptor_6c07b07c062484ab, []int{202} } func (m *ServiceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5708,7 +5736,7 @@ var xxx_messageInfo_ServiceList proto.InternalMessageInfo func (m *ServicePort) Reset() { *m = ServicePort{} } func (*ServicePort) ProtoMessage() {} func (*ServicePort) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{202} + return fileDescriptor_6c07b07c062484ab, []int{203} } func (m *ServicePort) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5736,7 +5764,7 @@ var xxx_messageInfo_ServicePort proto.InternalMessageInfo func (m *ServiceProxyOptions) Reset() { *m = ServiceProxyOptions{} } func (*ServiceProxyOptions) ProtoMessage() {} func (*ServiceProxyOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{203} + return fileDescriptor_6c07b07c062484ab, []int{204} } func (m *ServiceProxyOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5764,7 +5792,7 @@ var xxx_messageInfo_ServiceProxyOptions proto.InternalMessageInfo func (m *ServiceSpec) Reset() { *m = ServiceSpec{} } func (*ServiceSpec) ProtoMessage() {} func (*ServiceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{204} + return fileDescriptor_6c07b07c062484ab, []int{205} } func (m *ServiceSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5792,7 +5820,7 @@ var xxx_messageInfo_ServiceSpec proto.InternalMessageInfo func (m *ServiceStatus) Reset() { *m = ServiceStatus{} } func (*ServiceStatus) ProtoMessage() {} func (*ServiceStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{205} + return fileDescriptor_6c07b07c062484ab, []int{206} } func (m *ServiceStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5820,7 +5848,7 @@ var xxx_messageInfo_ServiceStatus proto.InternalMessageInfo func (m *SessionAffinityConfig) Reset() { *m = SessionAffinityConfig{} } func (*SessionAffinityConfig) ProtoMessage() {} func (*SessionAffinityConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{206} + return fileDescriptor_6c07b07c062484ab, []int{207} } func (m *SessionAffinityConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5848,7 +5876,7 @@ var xxx_messageInfo_SessionAffinityConfig proto.InternalMessageInfo func (m *SleepAction) Reset() { *m = SleepAction{} } func (*SleepAction) ProtoMessage() {} func (*SleepAction) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{207} + return fileDescriptor_6c07b07c062484ab, []int{208} } func (m *SleepAction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5876,7 +5904,7 @@ var xxx_messageInfo_SleepAction proto.InternalMessageInfo func (m *StorageOSPersistentVolumeSource) Reset() { *m = StorageOSPersistentVolumeSource{} } func (*StorageOSPersistentVolumeSource) ProtoMessage() {} func (*StorageOSPersistentVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{208} + return fileDescriptor_6c07b07c062484ab, []int{209} } func (m *StorageOSPersistentVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5904,7 +5932,7 @@ var xxx_messageInfo_StorageOSPersistentVolumeSource proto.InternalMessageInfo func (m *StorageOSVolumeSource) Reset() { *m = StorageOSVolumeSource{} } func (*StorageOSVolumeSource) ProtoMessage() {} func (*StorageOSVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{209} + return fileDescriptor_6c07b07c062484ab, []int{210} } func (m *StorageOSVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5932,7 +5960,7 @@ var xxx_messageInfo_StorageOSVolumeSource proto.InternalMessageInfo func (m *Sysctl) Reset() { *m = Sysctl{} } func (*Sysctl) ProtoMessage() {} func (*Sysctl) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{210} + return fileDescriptor_6c07b07c062484ab, []int{211} } func (m *Sysctl) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5960,7 +5988,7 @@ var xxx_messageInfo_Sysctl proto.InternalMessageInfo func (m *TCPSocketAction) Reset() { *m = TCPSocketAction{} } func (*TCPSocketAction) ProtoMessage() {} func (*TCPSocketAction) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{211} + return fileDescriptor_6c07b07c062484ab, []int{212} } func (m *TCPSocketAction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5988,7 +6016,7 @@ var xxx_messageInfo_TCPSocketAction proto.InternalMessageInfo func (m *Taint) Reset() { *m = Taint{} } func (*Taint) ProtoMessage() {} func (*Taint) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{212} + return fileDescriptor_6c07b07c062484ab, []int{213} } func (m *Taint) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6016,7 +6044,7 @@ var xxx_messageInfo_Taint proto.InternalMessageInfo func (m *Toleration) Reset() { *m = Toleration{} } func (*Toleration) ProtoMessage() {} func (*Toleration) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{213} + return fileDescriptor_6c07b07c062484ab, []int{214} } func (m *Toleration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6044,7 +6072,7 @@ var xxx_messageInfo_Toleration proto.InternalMessageInfo func (m *TopologySelectorLabelRequirement) Reset() { *m = TopologySelectorLabelRequirement{} } func (*TopologySelectorLabelRequirement) ProtoMessage() {} func (*TopologySelectorLabelRequirement) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{214} + return fileDescriptor_6c07b07c062484ab, []int{215} } func (m *TopologySelectorLabelRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6072,7 +6100,7 @@ var xxx_messageInfo_TopologySelectorLabelRequirement proto.InternalMessageInfo func (m *TopologySelectorTerm) Reset() { *m = TopologySelectorTerm{} } func (*TopologySelectorTerm) ProtoMessage() {} func (*TopologySelectorTerm) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{215} + return fileDescriptor_6c07b07c062484ab, []int{216} } func (m *TopologySelectorTerm) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6100,7 +6128,7 @@ var xxx_messageInfo_TopologySelectorTerm proto.InternalMessageInfo func (m *TopologySpreadConstraint) Reset() { *m = TopologySpreadConstraint{} } func (*TopologySpreadConstraint) ProtoMessage() {} func (*TopologySpreadConstraint) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{216} + return fileDescriptor_6c07b07c062484ab, []int{217} } func (m *TopologySpreadConstraint) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6128,7 +6156,7 @@ var xxx_messageInfo_TopologySpreadConstraint proto.InternalMessageInfo func (m *TypedLocalObjectReference) Reset() { *m = TypedLocalObjectReference{} } func (*TypedLocalObjectReference) ProtoMessage() {} func (*TypedLocalObjectReference) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{217} + return fileDescriptor_6c07b07c062484ab, []int{218} } func (m *TypedLocalObjectReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6156,7 +6184,7 @@ var xxx_messageInfo_TypedLocalObjectReference proto.InternalMessageInfo func (m *TypedObjectReference) Reset() { *m = TypedObjectReference{} } func (*TypedObjectReference) ProtoMessage() {} func (*TypedObjectReference) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{218} + return fileDescriptor_6c07b07c062484ab, []int{219} } func (m *TypedObjectReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6184,7 +6212,7 @@ var xxx_messageInfo_TypedObjectReference proto.InternalMessageInfo func (m *Volume) Reset() { *m = Volume{} } func (*Volume) ProtoMessage() {} func (*Volume) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{219} + return fileDescriptor_6c07b07c062484ab, []int{220} } func (m *Volume) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6212,7 +6240,7 @@ var xxx_messageInfo_Volume proto.InternalMessageInfo func (m *VolumeDevice) Reset() { *m = VolumeDevice{} } func (*VolumeDevice) ProtoMessage() {} func (*VolumeDevice) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{220} + return fileDescriptor_6c07b07c062484ab, []int{221} } func (m *VolumeDevice) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6240,7 +6268,7 @@ var xxx_messageInfo_VolumeDevice proto.InternalMessageInfo func (m *VolumeMount) Reset() { *m = VolumeMount{} } func (*VolumeMount) ProtoMessage() {} func (*VolumeMount) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{221} + return fileDescriptor_6c07b07c062484ab, []int{222} } func (m *VolumeMount) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6268,7 +6296,7 @@ var xxx_messageInfo_VolumeMount proto.InternalMessageInfo func (m *VolumeMountStatus) Reset() { *m = VolumeMountStatus{} } func (*VolumeMountStatus) ProtoMessage() {} func (*VolumeMountStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{222} + return fileDescriptor_6c07b07c062484ab, []int{223} } func (m *VolumeMountStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6296,7 +6324,7 @@ var xxx_messageInfo_VolumeMountStatus proto.InternalMessageInfo func (m *VolumeNodeAffinity) Reset() { *m = VolumeNodeAffinity{} } func (*VolumeNodeAffinity) ProtoMessage() {} func (*VolumeNodeAffinity) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{223} + return fileDescriptor_6c07b07c062484ab, []int{224} } func (m *VolumeNodeAffinity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6324,7 +6352,7 @@ var xxx_messageInfo_VolumeNodeAffinity proto.InternalMessageInfo func (m *VolumeProjection) Reset() { *m = VolumeProjection{} } func (*VolumeProjection) ProtoMessage() {} func (*VolumeProjection) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{224} + return fileDescriptor_6c07b07c062484ab, []int{225} } func (m *VolumeProjection) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6352,7 +6380,7 @@ var xxx_messageInfo_VolumeProjection proto.InternalMessageInfo func (m *VolumeResourceRequirements) Reset() { *m = VolumeResourceRequirements{} } func (*VolumeResourceRequirements) ProtoMessage() {} func (*VolumeResourceRequirements) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{225} + return fileDescriptor_6c07b07c062484ab, []int{226} } func (m *VolumeResourceRequirements) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6380,7 +6408,7 @@ var xxx_messageInfo_VolumeResourceRequirements proto.InternalMessageInfo func (m *VolumeSource) Reset() { *m = VolumeSource{} } func (*VolumeSource) ProtoMessage() {} func (*VolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{226} + return fileDescriptor_6c07b07c062484ab, []int{227} } func (m *VolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6408,7 +6436,7 @@ var xxx_messageInfo_VolumeSource proto.InternalMessageInfo func (m *VsphereVirtualDiskVolumeSource) Reset() { *m = VsphereVirtualDiskVolumeSource{} } func (*VsphereVirtualDiskVolumeSource) ProtoMessage() {} func (*VsphereVirtualDiskVolumeSource) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{227} + return fileDescriptor_6c07b07c062484ab, []int{228} } func (m *VsphereVirtualDiskVolumeSource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6436,7 +6464,7 @@ var xxx_messageInfo_VsphereVirtualDiskVolumeSource proto.InternalMessageInfo func (m *WeightedPodAffinityTerm) Reset() { *m = WeightedPodAffinityTerm{} } func (*WeightedPodAffinityTerm) ProtoMessage() {} func (*WeightedPodAffinityTerm) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{228} + return fileDescriptor_6c07b07c062484ab, []int{229} } func (m *WeightedPodAffinityTerm) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6464,7 +6492,7 @@ var xxx_messageInfo_WeightedPodAffinityTerm proto.InternalMessageInfo func (m *WindowsSecurityContextOptions) Reset() { *m = WindowsSecurityContextOptions{} } func (*WindowsSecurityContextOptions) ProtoMessage() {} func (*WindowsSecurityContextOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_6c07b07c062484ab, []int{229} + return fileDescriptor_6c07b07c062484ab, []int{230} } func (m *WindowsSecurityContextOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6617,6 +6645,7 @@ func init() { proto.RegisterType((*NodeStatus)(nil), "k8s.io.api.core.v1.NodeStatus") proto.RegisterMapType((ResourceList)(nil), "k8s.io.api.core.v1.NodeStatus.AllocatableEntry") proto.RegisterMapType((ResourceList)(nil), "k8s.io.api.core.v1.NodeStatus.CapacityEntry") + proto.RegisterType((*NodeSwapStatus)(nil), "k8s.io.api.core.v1.NodeSwapStatus") proto.RegisterType((*NodeSystemInfo)(nil), "k8s.io.api.core.v1.NodeSystemInfo") proto.RegisterType((*ObjectFieldSelector)(nil), "k8s.io.api.core.v1.ObjectFieldSelector") proto.RegisterType((*ObjectReference)(nil), "k8s.io.api.core.v1.ObjectReference") @@ -6758,1015 +6787,1020 @@ func init() { } var fileDescriptor_6c07b07c062484ab = []byte{ - // 16114 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x69, 0x90, 0x64, 0xd9, - 0x59, 0x28, 0xa6, 0x9b, 0x59, 0xeb, 0x57, 0xfb, 0xa9, 0x5e, 0xaa, 0x6b, 0xba, 0x3b, 0x7b, 0xee, - 0xcc, 0xf4, 0xf4, 0x6c, 0xd5, 0xea, 0x59, 0x34, 0xad, 0x99, 0xd1, 0x30, 0xb5, 0x76, 0xd7, 0x74, - 0x57, 0x75, 0xce, 0xc9, 0xaa, 0x6e, 0x69, 0x34, 0x12, 0xba, 0x9d, 0x79, 0xaa, 0xea, 0xaa, 0x32, - 0xef, 0xcd, 0xb9, 0xf7, 0x66, 0x75, 0x57, 0x5b, 0x04, 0x20, 0x8c, 0x40, 0x02, 0x47, 0x28, 0x08, - 0x6c, 0x1c, 0x82, 0xe0, 0x07, 0x60, 0x16, 0xcb, 0x60, 0x64, 0x61, 0xc0, 0x88, 0xcd, 0x36, 0x8e, - 0x00, 0xff, 0xc0, 0x98, 0x08, 0x4b, 0x84, 0x09, 0x17, 0x56, 0xe1, 0x08, 0x82, 0x1f, 0x06, 0x82, - 0xf7, 0x7e, 0xbc, 0x57, 0xc1, 0x7b, 0xbc, 0x38, 0xeb, 0x3d, 0xe7, 0x2e, 0x99, 0x59, 0x3d, 0xdd, - 0xa5, 0x91, 0x62, 0xfe, 0x65, 0x9e, 0xef, 0x3b, 0xdf, 0x39, 0xf7, 0xac, 0xdf, 0xf9, 0x56, 0xb0, - 0xb7, 0x2f, 0x87, 0x33, 0xae, 0x7f, 0xd1, 0x69, 0xba, 0x17, 0xab, 0x7e, 0x40, 0x2e, 0xee, 0x5c, - 0xba, 0xb8, 0x49, 0x3c, 0x12, 0x38, 0x11, 0xa9, 0xcd, 0x34, 0x03, 0x3f, 0xf2, 0x11, 0xe2, 0x38, - 0x33, 0x4e, 0xd3, 0x9d, 0xa1, 0x38, 0x33, 0x3b, 0x97, 0xa6, 0x9f, 0xdb, 0x74, 0xa3, 0xad, 0xd6, - 0xed, 0x99, 0xaa, 0xdf, 0xb8, 0xb8, 0xe9, 0x6f, 0xfa, 0x17, 0x19, 0xea, 0xed, 0xd6, 0x06, 0xfb, - 0xc7, 0xfe, 0xb0, 0x5f, 0x9c, 0xc4, 0xf4, 0x8b, 0x71, 0x33, 0x0d, 0xa7, 0xba, 0xe5, 0x7a, 0x24, - 0xd8, 0xbd, 0xd8, 0xdc, 0xde, 0x64, 0xed, 0x06, 0x24, 0xf4, 0x5b, 0x41, 0x95, 0x24, 0x1b, 0x6e, - 0x5b, 0x2b, 0xbc, 0xd8, 0x20, 0x91, 0x93, 0xd1, 0xdd, 0xe9, 0x8b, 0x79, 0xb5, 0x82, 0x96, 0x17, - 0xb9, 0x8d, 0x74, 0x33, 0x1f, 0xe9, 0x54, 0x21, 0xac, 0x6e, 0x91, 0x86, 0x93, 0xaa, 0xf7, 0x42, - 0x5e, 0xbd, 0x56, 0xe4, 0xd6, 0x2f, 0xba, 0x5e, 0x14, 0x46, 0x41, 0xb2, 0x92, 0xfd, 0x2d, 0x0b, - 0xce, 0xcd, 0xde, 0xaa, 0x2c, 0xd6, 0x9d, 0x30, 0x72, 0xab, 0x73, 0x75, 0xbf, 0xba, 0x5d, 0x89, - 0xfc, 0x80, 0xdc, 0xf4, 0xeb, 0xad, 0x06, 0xa9, 0xb0, 0x81, 0x40, 0xcf, 0xc2, 0xc0, 0x0e, 0xfb, - 0xbf, 0xbc, 0x30, 0x65, 0x9d, 0xb3, 0x2e, 0x0c, 0xce, 0x8d, 0xff, 0xe9, 0x5e, 0xe9, 0x43, 0xfb, - 0x7b, 0xa5, 0x81, 0x9b, 0xa2, 0x1c, 0x2b, 0x0c, 0x74, 0x1e, 0xfa, 0x36, 0xc2, 0xb5, 0xdd, 0x26, - 0x99, 0x2a, 0x30, 0xdc, 0x51, 0x81, 0xdb, 0xb7, 0x54, 0xa1, 0xa5, 0x58, 0x40, 0xd1, 0x45, 0x18, - 0x6c, 0x3a, 0x41, 0xe4, 0x46, 0xae, 0xef, 0x4d, 0x15, 0xcf, 0x59, 0x17, 0x7a, 0xe7, 0x26, 0x04, - 0xea, 0x60, 0x59, 0x02, 0x70, 0x8c, 0x43, 0xbb, 0x11, 0x10, 0xa7, 0x76, 0xc3, 0xab, 0xef, 0x4e, - 0xf5, 0x9c, 0xb3, 0x2e, 0x0c, 0xc4, 0xdd, 0xc0, 0xa2, 0x1c, 0x2b, 0x0c, 0xfb, 0x2b, 0x05, 0x18, - 0x98, 0xdd, 0xd8, 0x70, 0x3d, 0x37, 0xda, 0x45, 0x37, 0x61, 0xd8, 0xf3, 0x6b, 0x44, 0xfe, 0x67, - 0x5f, 0x31, 0xf4, 0xfc, 0xb9, 0x99, 0xf4, 0x52, 0x9a, 0x59, 0xd5, 0xf0, 0xe6, 0xc6, 0xf7, 0xf7, - 0x4a, 0xc3, 0x7a, 0x09, 0x36, 0xe8, 0x20, 0x0c, 0x43, 0x4d, 0xbf, 0xa6, 0xc8, 0x16, 0x18, 0xd9, - 0x52, 0x16, 0xd9, 0x72, 0x8c, 0x36, 0x37, 0xb6, 0xbf, 0x57, 0x1a, 0xd2, 0x0a, 0xb0, 0x4e, 0x04, - 0xdd, 0x86, 0x31, 0xfa, 0xd7, 0x8b, 0x5c, 0x45, 0xb7, 0xc8, 0xe8, 0x3e, 0x96, 0x47, 0x57, 0x43, - 0x9d, 0x9b, 0xdc, 0xdf, 0x2b, 0x8d, 0x25, 0x0a, 0x71, 0x92, 0xa0, 0xfd, 0x93, 0x16, 0x8c, 0xcd, - 0x36, 0x9b, 0xb3, 0x41, 0xc3, 0x0f, 0xca, 0x81, 0xbf, 0xe1, 0xd6, 0x09, 0x7a, 0x19, 0x7a, 0x22, - 0x3a, 0x6b, 0x7c, 0x86, 0x1f, 0x13, 0x43, 0xdb, 0x43, 0xe7, 0xea, 0x60, 0xaf, 0x34, 0x99, 0x40, - 0x67, 0x53, 0xc9, 0x2a, 0xa0, 0x37, 0x60, 0xbc, 0xee, 0x57, 0x9d, 0xfa, 0x96, 0x1f, 0x46, 0x02, - 0x2a, 0xa6, 0xfe, 0xd8, 0xfe, 0x5e, 0x69, 0xfc, 0x7a, 0x02, 0x86, 0x53, 0xd8, 0xf6, 0x3d, 0x18, - 0x9d, 0x8d, 0x22, 0xa7, 0xba, 0x45, 0x6a, 0x7c, 0x41, 0xa1, 0x17, 0xa1, 0xc7, 0x73, 0x1a, 0xb2, - 0x33, 0xe7, 0x64, 0x67, 0x56, 0x9d, 0x06, 0xed, 0xcc, 0xf8, 0xba, 0xe7, 0xbe, 0xdb, 0x12, 0x8b, - 0x94, 0x96, 0x61, 0x86, 0x8d, 0x9e, 0x07, 0xa8, 0x91, 0x1d, 0xb7, 0x4a, 0xca, 0x4e, 0xb4, 0x25, - 0xfa, 0x80, 0x44, 0x5d, 0x58, 0x50, 0x10, 0xac, 0x61, 0xd9, 0x77, 0x61, 0x70, 0x76, 0xc7, 0x77, - 0x6b, 0x65, 0xbf, 0x16, 0xa2, 0x6d, 0x18, 0x6b, 0x06, 0x64, 0x83, 0x04, 0xaa, 0x68, 0xca, 0x3a, - 0x57, 0xbc, 0x30, 0xf4, 0xfc, 0x85, 0xcc, 0xb1, 0x37, 0x51, 0x17, 0xbd, 0x28, 0xd8, 0x9d, 0x3b, - 0x29, 0xda, 0x1b, 0x4b, 0x40, 0x71, 0x92, 0xb2, 0xfd, 0x27, 0x05, 0x38, 0x3e, 0x7b, 0xaf, 0x15, - 0x90, 0x05, 0x37, 0xdc, 0x4e, 0x6e, 0xb8, 0x9a, 0x1b, 0x6e, 0xaf, 0xc6, 0x23, 0xa0, 0x56, 0xfa, - 0x82, 0x28, 0xc7, 0x0a, 0x03, 0x3d, 0x07, 0xfd, 0xf4, 0xf7, 0x3a, 0x5e, 0x16, 0x9f, 0x3c, 0x29, - 0x90, 0x87, 0x16, 0x9c, 0xc8, 0x59, 0xe0, 0x20, 0x2c, 0x71, 0xd0, 0x0a, 0x0c, 0x55, 0xd9, 0xf9, - 0xb0, 0xb9, 0xe2, 0xd7, 0x08, 0x5b, 0x5b, 0x83, 0x73, 0xcf, 0x50, 0xf4, 0xf9, 0xb8, 0xf8, 0x60, - 0xaf, 0x34, 0xc5, 0xfb, 0x26, 0x48, 0x68, 0x30, 0xac, 0xd7, 0x47, 0xb6, 0xda, 0xee, 0x3d, 0x8c, - 0x12, 0x64, 0x6c, 0xf5, 0x0b, 0xda, 0xce, 0xed, 0x65, 0x3b, 0x77, 0x38, 0x7b, 0xd7, 0xa2, 0x4b, - 0xd0, 0xb3, 0xed, 0x7a, 0xb5, 0xa9, 0x3e, 0x46, 0xeb, 0x0c, 0x9d, 0xf3, 0x6b, 0xae, 0x57, 0x3b, - 0xd8, 0x2b, 0x4d, 0x18, 0xdd, 0xa1, 0x85, 0x98, 0xa1, 0xda, 0xff, 0xc6, 0x82, 0x12, 0x83, 0x2d, - 0xb9, 0x75, 0x52, 0x26, 0x41, 0xe8, 0x86, 0x11, 0xf1, 0x22, 0x63, 0x40, 0x9f, 0x07, 0x08, 0x49, - 0x35, 0x20, 0x91, 0x36, 0xa4, 0x6a, 0x61, 0x54, 0x14, 0x04, 0x6b, 0x58, 0xf4, 0x7c, 0x0a, 0xb7, - 0x9c, 0x80, 0xad, 0x2f, 0x31, 0xb0, 0xea, 0x7c, 0xaa, 0x48, 0x00, 0x8e, 0x71, 0x8c, 0xf3, 0xa9, - 0xd8, 0xe9, 0x7c, 0x42, 0x1f, 0x83, 0xb1, 0xb8, 0xb1, 0xb0, 0xe9, 0x54, 0xe5, 0x00, 0xb2, 0x1d, - 0x5c, 0x31, 0x41, 0x38, 0x89, 0x6b, 0xff, 0xb7, 0x96, 0x58, 0x3c, 0xf4, 0xab, 0xdf, 0xe7, 0xdf, - 0x6a, 0xff, 0xae, 0x05, 0xfd, 0x73, 0xae, 0x57, 0x73, 0xbd, 0x4d, 0xf4, 0x19, 0x18, 0xa0, 0x57, - 0x65, 0xcd, 0x89, 0x1c, 0x71, 0x0c, 0x7f, 0x58, 0xdb, 0x5b, 0xea, 0xe6, 0x9a, 0x69, 0x6e, 0x6f, - 0xd2, 0x82, 0x70, 0x86, 0x62, 0xd3, 0xdd, 0x76, 0xe3, 0xf6, 0x67, 0x49, 0x35, 0x5a, 0x21, 0x91, - 0x13, 0x7f, 0x4e, 0x5c, 0x86, 0x15, 0x55, 0x74, 0x0d, 0xfa, 0x22, 0x27, 0xd8, 0x24, 0x91, 0x38, - 0x8f, 0x33, 0xcf, 0x4d, 0x5e, 0x13, 0xd3, 0x1d, 0x49, 0xbc, 0x2a, 0x89, 0x6f, 0xa9, 0x35, 0x56, - 0x15, 0x0b, 0x12, 0xf6, 0x7f, 0xe8, 0x87, 0x53, 0xf3, 0x95, 0xe5, 0x9c, 0x75, 0x75, 0x1e, 0xfa, - 0x6a, 0x81, 0xbb, 0x43, 0x02, 0x31, 0xce, 0x8a, 0xca, 0x02, 0x2b, 0xc5, 0x02, 0x8a, 0x2e, 0xc3, - 0x30, 0xbf, 0x1f, 0xaf, 0x3a, 0x5e, 0x2d, 0x3e, 0x1e, 0x05, 0xf6, 0xf0, 0x4d, 0x0d, 0x86, 0x0d, - 0xcc, 0x43, 0x2e, 0xaa, 0xf3, 0x89, 0xcd, 0x98, 0x77, 0xf7, 0x7e, 0xd1, 0x82, 0x71, 0xde, 0xcc, - 0x6c, 0x14, 0x05, 0xee, 0xed, 0x56, 0x44, 0xc2, 0xa9, 0x5e, 0x76, 0xd2, 0xcd, 0x67, 0x8d, 0x56, - 0xee, 0x08, 0xcc, 0xdc, 0x4c, 0x50, 0xe1, 0x87, 0xe0, 0x94, 0x68, 0x77, 0x3c, 0x09, 0xc6, 0xa9, - 0x66, 0xd1, 0x8f, 0x58, 0x30, 0x5d, 0xf5, 0xbd, 0x28, 0xf0, 0xeb, 0x75, 0x12, 0x94, 0x5b, 0xb7, - 0xeb, 0x6e, 0xb8, 0xc5, 0xd7, 0x29, 0x26, 0x1b, 0xec, 0x24, 0xc8, 0x99, 0x43, 0x85, 0x24, 0xe6, - 0xf0, 0xec, 0xfe, 0x5e, 0x69, 0x7a, 0x3e, 0x97, 0x14, 0x6e, 0xd3, 0x0c, 0xda, 0x06, 0x44, 0x6f, - 0xf6, 0x4a, 0xe4, 0x6c, 0x92, 0xb8, 0xf1, 0xfe, 0xee, 0x1b, 0x3f, 0xb1, 0xbf, 0x57, 0x42, 0xab, - 0x29, 0x12, 0x38, 0x83, 0x2c, 0x7a, 0x17, 0x8e, 0xd1, 0xd2, 0xd4, 0xb7, 0x0e, 0x74, 0xdf, 0xdc, - 0xd4, 0xfe, 0x5e, 0xe9, 0xd8, 0x6a, 0x06, 0x11, 0x9c, 0x49, 0x1a, 0xfd, 0x90, 0x05, 0xa7, 0xe2, - 0xcf, 0x5f, 0xbc, 0xdb, 0x74, 0xbc, 0x5a, 0xdc, 0xf0, 0x60, 0xf7, 0x0d, 0xd3, 0x33, 0xf9, 0xd4, - 0x7c, 0x1e, 0x25, 0x9c, 0xdf, 0x08, 0xf2, 0x60, 0x92, 0x76, 0x2d, 0xd9, 0x36, 0x74, 0xdf, 0xf6, - 0xc9, 0xfd, 0xbd, 0xd2, 0xe4, 0x6a, 0x9a, 0x06, 0xce, 0x22, 0x3c, 0x3d, 0x0f, 0xc7, 0x33, 0x57, - 0x27, 0x1a, 0x87, 0xe2, 0x36, 0xe1, 0x4c, 0xe0, 0x20, 0xa6, 0x3f, 0xd1, 0x31, 0xe8, 0xdd, 0x71, - 0xea, 0x2d, 0xb1, 0x31, 0x31, 0xff, 0xf3, 0x4a, 0xe1, 0xb2, 0x65, 0xff, 0x6f, 0x45, 0x18, 0x9b, - 0xaf, 0x2c, 0xdf, 0xd7, 0xae, 0xd7, 0xaf, 0xbd, 0x42, 0xdb, 0x6b, 0x2f, 0xbe, 0x44, 0x8b, 0xb9, - 0x97, 0xe8, 0x0f, 0x66, 0x6c, 0xd9, 0x1e, 0xb6, 0x65, 0x3f, 0x9a, 0xb3, 0x65, 0x1f, 0xf0, 0x46, - 0xdd, 0xc9, 0x59, 0xb5, 0xbd, 0x6c, 0x02, 0x33, 0x39, 0x24, 0xc6, 0xfb, 0x25, 0x8f, 0xda, 0x43, - 0x2e, 0xdd, 0x07, 0x33, 0x8f, 0x55, 0x18, 0x9e, 0x77, 0x9a, 0xce, 0x6d, 0xb7, 0xee, 0x46, 0x2e, - 0x09, 0xd1, 0x93, 0x50, 0x74, 0x6a, 0x35, 0xc6, 0xdd, 0x0d, 0xce, 0x1d, 0xdf, 0xdf, 0x2b, 0x15, - 0x67, 0x6b, 0x94, 0xcd, 0x00, 0x85, 0xb5, 0x8b, 0x29, 0x06, 0x7a, 0x1a, 0x7a, 0x6a, 0x81, 0xdf, - 0x9c, 0x2a, 0x30, 0x4c, 0xba, 0xcb, 0x7b, 0x16, 0x02, 0xbf, 0x99, 0x40, 0x65, 0x38, 0xf6, 0x1f, - 0x17, 0xe0, 0xf4, 0x3c, 0x69, 0x6e, 0x2d, 0x55, 0x72, 0xee, 0x8b, 0x0b, 0x30, 0xd0, 0xf0, 0x3d, - 0x37, 0xf2, 0x83, 0x50, 0x34, 0xcd, 0x56, 0xc4, 0x8a, 0x28, 0xc3, 0x0a, 0x8a, 0xce, 0x41, 0x4f, - 0x33, 0x66, 0x62, 0x87, 0x25, 0x03, 0xcc, 0xd8, 0x57, 0x06, 0xa1, 0x18, 0xad, 0x90, 0x04, 0x62, - 0xc5, 0x28, 0x8c, 0xf5, 0x90, 0x04, 0x98, 0x41, 0x62, 0x4e, 0x80, 0xf2, 0x08, 0xe2, 0x46, 0x48, - 0x70, 0x02, 0x14, 0x82, 0x35, 0x2c, 0x54, 0x86, 0xc1, 0x30, 0x31, 0xb3, 0x5d, 0x6d, 0xcd, 0x11, - 0xc6, 0x2a, 0xa8, 0x99, 0x8c, 0x89, 0x18, 0x37, 0x58, 0x5f, 0x47, 0x56, 0xe1, 0x1b, 0x05, 0x40, - 0x7c, 0x08, 0xbf, 0xcb, 0x06, 0x6e, 0x3d, 0x3d, 0x70, 0xdd, 0x6f, 0x89, 0x07, 0x35, 0x7a, 0xff, - 0xd6, 0x82, 0xd3, 0xf3, 0xae, 0x57, 0x23, 0x41, 0xce, 0x02, 0x7c, 0x38, 0x4f, 0xf9, 0xc3, 0x31, - 0x29, 0xc6, 0x12, 0xeb, 0x79, 0x00, 0x4b, 0xcc, 0xfe, 0x47, 0x0b, 0x10, 0xff, 0xec, 0xf7, 0xdd, - 0xc7, 0xae, 0xa7, 0x3f, 0xf6, 0x01, 0x2c, 0x0b, 0xfb, 0x3a, 0x8c, 0xce, 0xd7, 0x5d, 0xe2, 0x45, - 0xcb, 0xe5, 0x79, 0xdf, 0xdb, 0x70, 0x37, 0xd1, 0x2b, 0x30, 0x1a, 0xb9, 0x0d, 0xe2, 0xb7, 0xa2, - 0x0a, 0xa9, 0xfa, 0x1e, 0x7b, 0xb9, 0x5a, 0x17, 0x7a, 0xe7, 0xd0, 0xfe, 0x5e, 0x69, 0x74, 0xcd, - 0x80, 0xe0, 0x04, 0xa6, 0xfd, 0xcb, 0xf4, 0xdc, 0xaa, 0xb7, 0xc2, 0x88, 0x04, 0x6b, 0x41, 0x2b, - 0x8c, 0xe6, 0x5a, 0x94, 0xf7, 0x2c, 0x07, 0x3e, 0xed, 0x8e, 0xeb, 0x7b, 0xe8, 0xb4, 0xf1, 0x1c, - 0x1f, 0x90, 0x4f, 0x71, 0xf1, 0xec, 0x9e, 0x01, 0x08, 0xdd, 0x4d, 0x8f, 0x04, 0xda, 0xf3, 0x61, - 0x94, 0x6d, 0x15, 0x55, 0x8a, 0x35, 0x0c, 0x54, 0x87, 0x91, 0xba, 0x73, 0x9b, 0xd4, 0x2b, 0xa4, - 0x4e, 0xaa, 0x91, 0x1f, 0x08, 0xf9, 0xc6, 0x0b, 0xdd, 0xbd, 0x03, 0xae, 0xeb, 0x55, 0xe7, 0x26, - 0xf6, 0xf7, 0x4a, 0x23, 0x46, 0x11, 0x36, 0x89, 0xd3, 0xa3, 0xc3, 0x6f, 0xd2, 0xaf, 0x70, 0xea, - 0xfa, 0xe3, 0xf3, 0x86, 0x28, 0xc3, 0x0a, 0xaa, 0x8e, 0x8e, 0x9e, 0xbc, 0xa3, 0xc3, 0xfe, 0x6b, - 0xba, 0xd0, 0xfc, 0x46, 0xd3, 0xf7, 0x88, 0x17, 0xcd, 0xfb, 0x5e, 0x8d, 0x4b, 0xa6, 0x5e, 0x31, - 0x44, 0x27, 0xe7, 0x13, 0xa2, 0x93, 0x13, 0xe9, 0x1a, 0x9a, 0xf4, 0xe4, 0xa3, 0xd0, 0x17, 0x46, - 0x4e, 0xd4, 0x0a, 0xc5, 0xc0, 0x3d, 0x2a, 0x97, 0x5d, 0x85, 0x95, 0x1e, 0xec, 0x95, 0xc6, 0x54, - 0x35, 0x5e, 0x84, 0x45, 0x05, 0xf4, 0x14, 0xf4, 0x37, 0x48, 0x18, 0x3a, 0x9b, 0x92, 0x6d, 0x18, - 0x13, 0x75, 0xfb, 0x57, 0x78, 0x31, 0x96, 0x70, 0xf4, 0x18, 0xf4, 0x92, 0x20, 0xf0, 0x03, 0xf1, - 0x6d, 0x23, 0x02, 0xb1, 0x77, 0x91, 0x16, 0x62, 0x0e, 0xb3, 0xff, 0x0f, 0x0b, 0xc6, 0x54, 0x5f, - 0x79, 0x5b, 0x47, 0xf0, 0x5c, 0x7b, 0x1b, 0xa0, 0x2a, 0x3f, 0x30, 0x64, 0xd7, 0xec, 0xd0, 0xf3, - 0xe7, 0x33, 0x39, 0x9a, 0xd4, 0x30, 0xc6, 0x94, 0x55, 0x51, 0x88, 0x35, 0x6a, 0xf6, 0x1f, 0x58, - 0x30, 0x99, 0xf8, 0xa2, 0xeb, 0x6e, 0x18, 0xa1, 0x77, 0x52, 0x5f, 0x35, 0xd3, 0xe5, 0xe2, 0x73, - 0x43, 0xfe, 0x4d, 0x6a, 0xcf, 0xcb, 0x12, 0xed, 0x8b, 0xae, 0x42, 0xaf, 0x1b, 0x91, 0x86, 0xfc, - 0x98, 0xc7, 0xda, 0x7e, 0x0c, 0xef, 0x55, 0x3c, 0x23, 0xcb, 0xb4, 0x26, 0xe6, 0x04, 0xec, 0x3f, - 0x2e, 0xc2, 0x20, 0xdf, 0xdf, 0x2b, 0x4e, 0xf3, 0x08, 0xe6, 0xe2, 0x19, 0x18, 0x74, 0x1b, 0x8d, - 0x56, 0xe4, 0xdc, 0x16, 0xf7, 0xde, 0x00, 0x3f, 0x83, 0x96, 0x65, 0x21, 0x8e, 0xe1, 0x68, 0x19, - 0x7a, 0x58, 0x57, 0xf8, 0x57, 0x3e, 0x99, 0xfd, 0x95, 0xa2, 0xef, 0x33, 0x0b, 0x4e, 0xe4, 0x70, - 0x96, 0x53, 0xed, 0x2b, 0x5a, 0x84, 0x19, 0x09, 0xe4, 0x00, 0xdc, 0x76, 0x3d, 0x27, 0xd8, 0xa5, - 0x65, 0x53, 0x45, 0x46, 0xf0, 0xb9, 0xf6, 0x04, 0xe7, 0x14, 0x3e, 0x27, 0xab, 0x3e, 0x2c, 0x06, - 0x60, 0x8d, 0xe8, 0xf4, 0xcb, 0x30, 0xa8, 0x90, 0x0f, 0xc3, 0x39, 0x4e, 0x7f, 0x0c, 0xc6, 0x12, - 0x6d, 0x75, 0xaa, 0x3e, 0xac, 0x33, 0x9e, 0xbf, 0xc7, 0x8e, 0x0c, 0xd1, 0xeb, 0x45, 0x6f, 0x47, - 0xdc, 0x4d, 0xf7, 0xe0, 0x58, 0x3d, 0xe3, 0xc8, 0x17, 0xf3, 0xda, 0xfd, 0x15, 0x71, 0x5a, 0x7c, - 0xf6, 0xb1, 0x2c, 0x28, 0xce, 0x6c, 0xc3, 0x38, 0x11, 0x0b, 0xed, 0x4e, 0x44, 0x7a, 0xde, 0x1d, - 0x53, 0x9d, 0xbf, 0x46, 0x76, 0xd5, 0xa1, 0xfa, 0x9d, 0xec, 0xfe, 0x19, 0x3e, 0xfa, 0xfc, 0xb8, - 0x1c, 0x12, 0x04, 0x8a, 0xd7, 0xc8, 0x2e, 0x9f, 0x0a, 0xfd, 0xeb, 0x8a, 0x6d, 0xbf, 0xee, 0x6b, - 0x16, 0x8c, 0xa8, 0xaf, 0x3b, 0x82, 0x73, 0x61, 0xce, 0x3c, 0x17, 0xce, 0xb4, 0x5d, 0xe0, 0x39, - 0x27, 0xc2, 0x37, 0x0a, 0x70, 0x4a, 0xe1, 0xd0, 0x47, 0x14, 0xff, 0x23, 0x56, 0xd5, 0x45, 0x18, - 0xf4, 0x94, 0x38, 0xd1, 0x32, 0xe5, 0x78, 0xb1, 0x30, 0x31, 0xc6, 0xa1, 0x57, 0x9e, 0x17, 0x5f, - 0xda, 0xc3, 0xba, 0x9c, 0x5d, 0x5c, 0xee, 0x73, 0x50, 0x6c, 0xb9, 0x35, 0x71, 0xc1, 0x7c, 0x58, - 0x8e, 0xf6, 0xfa, 0xf2, 0xc2, 0xc1, 0x5e, 0xe9, 0xd1, 0x3c, 0x95, 0x13, 0xbd, 0xd9, 0xc2, 0x99, - 0xf5, 0xe5, 0x05, 0x4c, 0x2b, 0xa3, 0x59, 0x18, 0x93, 0x5a, 0xb5, 0x9b, 0x94, 0x2f, 0xf5, 0x3d, - 0x71, 0x0f, 0x29, 0x61, 0x39, 0x36, 0xc1, 0x38, 0x89, 0x8f, 0x16, 0x60, 0x7c, 0xbb, 0x75, 0x9b, - 0xd4, 0x49, 0xc4, 0x3f, 0xf8, 0x1a, 0xe1, 0xa2, 0xe4, 0xc1, 0xf8, 0x09, 0x7b, 0x2d, 0x01, 0xc7, - 0xa9, 0x1a, 0xf6, 0xbf, 0xb2, 0xfb, 0x40, 0x8c, 0x9e, 0xc6, 0xdf, 0x7c, 0x27, 0x97, 0x73, 0x37, - 0xab, 0xe2, 0x1a, 0xd9, 0x5d, 0xf3, 0x29, 0x1f, 0x92, 0xbd, 0x2a, 0x8c, 0x35, 0xdf, 0xd3, 0x76, - 0xcd, 0xff, 0x56, 0x01, 0x8e, 0xab, 0x11, 0x30, 0xb8, 0xe5, 0xef, 0xf6, 0x31, 0xb8, 0x04, 0x43, - 0x35, 0xb2, 0xe1, 0xb4, 0xea, 0x91, 0xd2, 0x6b, 0xf4, 0x72, 0x55, 0xdb, 0x42, 0x5c, 0x8c, 0x75, - 0x9c, 0x43, 0x0c, 0xdb, 0xaf, 0x8f, 0xb0, 0x8b, 0x38, 0x72, 0xe8, 0x1a, 0x57, 0xbb, 0xc6, 0xca, - 0xdd, 0x35, 0x8f, 0x41, 0xaf, 0xdb, 0xa0, 0x8c, 0x59, 0xc1, 0xe4, 0xb7, 0x96, 0x69, 0x21, 0xe6, - 0x30, 0xf4, 0x04, 0xf4, 0x57, 0xfd, 0x46, 0xc3, 0xf1, 0x6a, 0xec, 0xca, 0x1b, 0x9c, 0x1b, 0xa2, - 0xbc, 0xdb, 0x3c, 0x2f, 0xc2, 0x12, 0x46, 0x99, 0x6f, 0x27, 0xd8, 0xe4, 0xc2, 0x1e, 0xc1, 0x7c, - 0xcf, 0x06, 0x9b, 0x21, 0x66, 0xa5, 0xf4, 0xad, 0x7a, 0xc7, 0x0f, 0xb6, 0x5d, 0x6f, 0x73, 0xc1, - 0x0d, 0xc4, 0x96, 0x50, 0x77, 0xe1, 0x2d, 0x05, 0xc1, 0x1a, 0x16, 0x5a, 0x82, 0xde, 0xa6, 0x1f, - 0x44, 0xe1, 0x54, 0x1f, 0x1b, 0xee, 0x47, 0x73, 0x0e, 0x22, 0xfe, 0xb5, 0x65, 0x3f, 0x88, 0xe2, - 0x0f, 0xa0, 0xff, 0x42, 0xcc, 0xab, 0xa3, 0xeb, 0xd0, 0x4f, 0xbc, 0x9d, 0xa5, 0xc0, 0x6f, 0x4c, - 0x4d, 0xe6, 0x53, 0x5a, 0xe4, 0x28, 0x7c, 0x99, 0xc5, 0x3c, 0xaa, 0x28, 0xc6, 0x92, 0x04, 0xfa, - 0x28, 0x14, 0x89, 0xb7, 0x33, 0xd5, 0xcf, 0x28, 0x4d, 0xe7, 0x50, 0xba, 0xe9, 0x04, 0xf1, 0x99, - 0xbf, 0xe8, 0xed, 0x60, 0x5a, 0x07, 0x7d, 0x02, 0x06, 0xe5, 0x81, 0x11, 0x0a, 0x29, 0x6a, 0xe6, - 0x82, 0x95, 0xc7, 0x0c, 0x26, 0xef, 0xb6, 0xdc, 0x80, 0x34, 0x88, 0x17, 0x85, 0xf1, 0x09, 0x29, - 0xa1, 0x21, 0x8e, 0xa9, 0xa1, 0x2a, 0x0c, 0x07, 0x24, 0x74, 0xef, 0x91, 0xb2, 0x5f, 0x77, 0xab, - 0xbb, 0x53, 0x27, 0x59, 0xf7, 0x9e, 0x6a, 0x3b, 0x64, 0x58, 0xab, 0x10, 0x4b, 0xf9, 0xf5, 0x52, - 0x6c, 0x10, 0x45, 0x6f, 0xc1, 0x48, 0x40, 0xc2, 0xc8, 0x09, 0x22, 0xd1, 0xca, 0x94, 0xd2, 0xca, - 0x8d, 0x60, 0x1d, 0xc0, 0x9f, 0x13, 0x71, 0x33, 0x31, 0x04, 0x9b, 0x14, 0xd0, 0x27, 0xa4, 0xca, - 0x61, 0xc5, 0x6f, 0x79, 0x51, 0x38, 0x35, 0xc8, 0xfa, 0x9d, 0xa9, 0x9b, 0xbe, 0x19, 0xe3, 0x25, - 0x75, 0x12, 0xbc, 0x32, 0x36, 0x48, 0xa1, 0x4f, 0xc1, 0x08, 0xff, 0xcf, 0x55, 0xaa, 0xe1, 0xd4, - 0x71, 0x46, 0xfb, 0x5c, 0x3e, 0x6d, 0x8e, 0x38, 0x77, 0x5c, 0x10, 0x1f, 0xd1, 0x4b, 0x43, 0x6c, - 0x52, 0x43, 0x18, 0x46, 0xea, 0xee, 0x0e, 0xf1, 0x48, 0x18, 0x96, 0x03, 0xff, 0x36, 0x11, 0x12, - 0xe2, 0x53, 0xd9, 0x2a, 0x58, 0xff, 0x36, 0x11, 0x8f, 0x40, 0xbd, 0x0e, 0x36, 0x49, 0xa0, 0x75, - 0x18, 0xa5, 0x4f, 0x72, 0x37, 0x26, 0x3a, 0xd4, 0x89, 0x28, 0x7b, 0x38, 0x63, 0xa3, 0x12, 0x4e, - 0x10, 0x41, 0x37, 0x60, 0x98, 0x8d, 0x79, 0xab, 0xc9, 0x89, 0x9e, 0xe8, 0x44, 0x94, 0x19, 0x14, - 0x54, 0xb4, 0x2a, 0xd8, 0x20, 0x80, 0xde, 0x84, 0xc1, 0xba, 0xbb, 0x41, 0xaa, 0xbb, 0xd5, 0x3a, - 0x99, 0x1a, 0x66, 0xd4, 0x32, 0x0f, 0xc3, 0xeb, 0x12, 0x89, 0xf3, 0xe7, 0xea, 0x2f, 0x8e, 0xab, - 0xa3, 0x9b, 0x70, 0x22, 0x22, 0x41, 0xc3, 0xf5, 0x1c, 0x7a, 0x88, 0x89, 0x27, 0x21, 0xd3, 0x8c, - 0x8f, 0xb0, 0xd5, 0x75, 0x56, 0xcc, 0xc6, 0x89, 0xb5, 0x4c, 0x2c, 0x9c, 0x53, 0x1b, 0xdd, 0x85, - 0xa9, 0x0c, 0x08, 0x5f, 0xb7, 0xc7, 0x18, 0xe5, 0xd7, 0x04, 0xe5, 0xa9, 0xb5, 0x1c, 0xbc, 0x83, - 0x36, 0x30, 0x9c, 0x4b, 0x1d, 0xdd, 0x80, 0x31, 0x76, 0x72, 0x96, 0x5b, 0xf5, 0xba, 0x68, 0x70, - 0x94, 0x35, 0xf8, 0x84, 0xe4, 0x23, 0x96, 0x4d, 0xf0, 0xc1, 0x5e, 0x09, 0xe2, 0x7f, 0x38, 0x59, - 0x1b, 0xdd, 0x66, 0x4a, 0xd8, 0x56, 0xe0, 0x46, 0xbb, 0x74, 0x57, 0x91, 0xbb, 0xd1, 0xd4, 0x58, - 0x5b, 0x81, 0x94, 0x8e, 0xaa, 0x34, 0xb5, 0x7a, 0x21, 0x4e, 0x12, 0xa4, 0x57, 0x41, 0x18, 0xd5, - 0x5c, 0x6f, 0x6a, 0x9c, 0xbf, 0xa7, 0xe4, 0x49, 0x5a, 0xa1, 0x85, 0x98, 0xc3, 0x98, 0x02, 0x96, - 0xfe, 0xb8, 0x41, 0x6f, 0xdc, 0x09, 0x86, 0x18, 0x2b, 0x60, 0x25, 0x00, 0xc7, 0x38, 0x94, 0x09, - 0x8e, 0xa2, 0xdd, 0x29, 0xc4, 0x50, 0xd5, 0x81, 0xb8, 0xb6, 0xf6, 0x09, 0x4c, 0xcb, 0xed, 0xdb, - 0x30, 0xaa, 0x8e, 0x09, 0x36, 0x26, 0xa8, 0x04, 0xbd, 0x8c, 0xed, 0x13, 0xe2, 0xd3, 0x41, 0xda, - 0x05, 0xc6, 0x12, 0x62, 0x5e, 0xce, 0xba, 0xe0, 0xde, 0x23, 0x73, 0xbb, 0x11, 0xe1, 0xb2, 0x88, - 0xa2, 0xd6, 0x05, 0x09, 0xc0, 0x31, 0x8e, 0xfd, 0x1f, 0x39, 0xfb, 0x1c, 0xdf, 0x12, 0x5d, 0xdc, - 0x8b, 0xcf, 0xc2, 0x00, 0x33, 0xfc, 0xf0, 0x03, 0xae, 0x9d, 0xed, 0x8d, 0x19, 0xe6, 0xab, 0xa2, - 0x1c, 0x2b, 0x0c, 0xf4, 0x2a, 0x8c, 0x54, 0xf5, 0x06, 0xc4, 0xa5, 0xae, 0x8e, 0x11, 0xa3, 0x75, - 0x6c, 0xe2, 0xa2, 0xcb, 0x30, 0xc0, 0x6c, 0x9c, 0xaa, 0x7e, 0x5d, 0x70, 0x9b, 0x92, 0x33, 0x19, - 0x28, 0x8b, 0xf2, 0x03, 0xed, 0x37, 0x56, 0xd8, 0xe8, 0x3c, 0xf4, 0xd1, 0x2e, 0x2c, 0x97, 0xc5, - 0x75, 0xaa, 0x24, 0x81, 0x57, 0x59, 0x29, 0x16, 0x50, 0xfb, 0x0f, 0x2c, 0xc6, 0x4b, 0xa5, 0xcf, - 0x7c, 0x74, 0x95, 0x5d, 0x1a, 0xec, 0x06, 0xd1, 0xb4, 0xf0, 0x8f, 0x6b, 0x37, 0x81, 0x82, 0x1d, - 0x24, 0xfe, 0x63, 0xa3, 0x26, 0x7a, 0x3b, 0x79, 0x33, 0x70, 0x86, 0xe2, 0x45, 0x39, 0x04, 0xc9, - 0xdb, 0xe1, 0x91, 0xf8, 0x8a, 0xa3, 0xfd, 0x69, 0x77, 0x45, 0xd8, 0x3f, 0x55, 0xd0, 0x56, 0x49, - 0x25, 0x72, 0x22, 0x82, 0xca, 0xd0, 0x7f, 0xc7, 0x71, 0x23, 0xd7, 0xdb, 0x14, 0x7c, 0x5f, 0xfb, - 0x8b, 0x8e, 0x55, 0xba, 0xc5, 0x2b, 0x70, 0xee, 0x45, 0xfc, 0xc1, 0x92, 0x0c, 0xa5, 0x18, 0xb4, - 0x3c, 0x8f, 0x52, 0x2c, 0x74, 0x4b, 0x11, 0xf3, 0x0a, 0x9c, 0xa2, 0xf8, 0x83, 0x25, 0x19, 0xf4, - 0x0e, 0x80, 0x3c, 0x21, 0x48, 0x4d, 0xc8, 0x0e, 0x9f, 0xed, 0x4c, 0x74, 0x4d, 0xd5, 0xe1, 0xc2, - 0xc9, 0xf8, 0x3f, 0xd6, 0xe8, 0xd9, 0x91, 0x36, 0xa7, 0x7a, 0x67, 0xd0, 0x27, 0xe9, 0x16, 0x75, - 0x82, 0x88, 0xd4, 0x66, 0x23, 0x31, 0x38, 0x4f, 0x77, 0xf7, 0x38, 0x5c, 0x73, 0x1b, 0x44, 0xdf, - 0xce, 0x82, 0x08, 0x8e, 0xe9, 0xd9, 0xbf, 0x53, 0x84, 0xa9, 0xbc, 0xee, 0xd2, 0x4d, 0x43, 0xee, - 0xba, 0xd1, 0x3c, 0x65, 0x6b, 0x2d, 0x73, 0xd3, 0x2c, 0x8a, 0x72, 0xac, 0x30, 0xe8, 0xea, 0x0d, - 0xdd, 0x4d, 0xf9, 0xb6, 0xef, 0x8d, 0x57, 0x6f, 0x85, 0x95, 0x62, 0x01, 0xa5, 0x78, 0x01, 0x71, - 0x42, 0x61, 0x7c, 0xa7, 0xad, 0x72, 0xcc, 0x4a, 0xb1, 0x80, 0xea, 0x52, 0xc6, 0x9e, 0x0e, 0x52, - 0x46, 0x63, 0x88, 0x7a, 0x1f, 0xec, 0x10, 0xa1, 0x4f, 0x03, 0x6c, 0xb8, 0x9e, 0x1b, 0x6e, 0x31, - 0xea, 0x7d, 0x87, 0xa6, 0xae, 0x98, 0xe2, 0x25, 0x45, 0x05, 0x6b, 0x14, 0xd1, 0x4b, 0x30, 0xa4, - 0x0e, 0x90, 0xe5, 0x05, 0xa6, 0xfa, 0xd7, 0x4c, 0xa9, 0xe2, 0xd3, 0x74, 0x01, 0xeb, 0x78, 0xf6, - 0x67, 0x93, 0xeb, 0x45, 0xec, 0x00, 0x6d, 0x7c, 0xad, 0x6e, 0xc7, 0xb7, 0xd0, 0x7e, 0x7c, 0xed, - 0x9f, 0x19, 0x84, 0x31, 0xa3, 0xb1, 0x56, 0xd8, 0xc5, 0x99, 0x7b, 0x85, 0x5e, 0x40, 0x4e, 0x44, - 0xc4, 0xfe, 0xb3, 0x3b, 0x6f, 0x15, 0xfd, 0x92, 0xa2, 0x3b, 0x80, 0xd7, 0x47, 0x9f, 0x86, 0xc1, - 0xba, 0x13, 0x32, 0x89, 0x25, 0x11, 0xfb, 0xae, 0x1b, 0x62, 0xf1, 0x83, 0xd0, 0x09, 0x23, 0xed, - 0xd6, 0xe7, 0xb4, 0x63, 0x92, 0xf4, 0xa6, 0xa4, 0xfc, 0x95, 0xb4, 0xee, 0x54, 0x9d, 0xa0, 0x4c, - 0xd8, 0x2e, 0xe6, 0x30, 0x74, 0x99, 0x1d, 0xad, 0x74, 0x55, 0xcc, 0x53, 0x6e, 0x94, 0x2d, 0xb3, - 0x5e, 0x83, 0xc9, 0x56, 0x30, 0x6c, 0x60, 0xc6, 0x6f, 0xb2, 0xbe, 0x36, 0x6f, 0xb2, 0xa7, 0xa0, - 0x9f, 0xfd, 0x50, 0x2b, 0x40, 0xcd, 0xc6, 0x32, 0x2f, 0xc6, 0x12, 0x9e, 0x5c, 0x30, 0x03, 0xdd, - 0x2d, 0x18, 0xfa, 0xea, 0x13, 0x8b, 0x9a, 0x99, 0x5d, 0x0c, 0xf0, 0x53, 0x4e, 0x2c, 0x79, 0x2c, - 0x61, 0xe8, 0x57, 0x2c, 0x40, 0x4e, 0x9d, 0xbe, 0x96, 0x69, 0xb1, 0x7a, 0xdc, 0x00, 0x63, 0xb5, - 0x5f, 0xed, 0x38, 0xec, 0xad, 0x70, 0x66, 0x36, 0x55, 0x9b, 0x4b, 0x4a, 0x5f, 0x11, 0x5d, 0x44, - 0x69, 0x04, 0xfd, 0x32, 0xba, 0xee, 0x86, 0xd1, 0xe7, 0xff, 0x26, 0x71, 0x39, 0x65, 0x74, 0x09, - 0xad, 0xeb, 0x8f, 0xaf, 0xa1, 0x43, 0x3e, 0xbe, 0x46, 0x72, 0x1f, 0x5e, 0xdf, 0x9f, 0x78, 0xc0, - 0x0c, 0xb3, 0x2f, 0x7f, 0xa2, 0xc3, 0x03, 0x46, 0x88, 0xd3, 0xbb, 0x79, 0xc6, 0x94, 0x85, 0x1e, - 0x78, 0x84, 0x75, 0xb9, 0xfd, 0x23, 0x78, 0x3d, 0x24, 0xc1, 0xdc, 0x29, 0xa9, 0x26, 0x3e, 0xd0, - 0x79, 0x0f, 0x4d, 0x6f, 0xfc, 0x43, 0x16, 0x4c, 0xa5, 0x07, 0x88, 0x77, 0x69, 0x6a, 0x94, 0xf5, - 0xdf, 0x6e, 0x37, 0x32, 0xa2, 0xf3, 0xd2, 0xdc, 0x75, 0x6a, 0x36, 0x87, 0x16, 0xce, 0x6d, 0x65, - 0xba, 0x05, 0x27, 0x73, 0xe6, 0x3d, 0x43, 0x6a, 0xbd, 0xa0, 0x4b, 0xad, 0x3b, 0xc8, 0x3a, 0x67, - 0xe4, 0xcc, 0xcc, 0xbc, 0xd5, 0x72, 0xbc, 0xc8, 0x8d, 0x76, 0x75, 0x29, 0xb7, 0x07, 0xe6, 0x80, - 0xa0, 0x4f, 0x41, 0x6f, 0xdd, 0xf5, 0x5a, 0x77, 0xc5, 0x4d, 0x79, 0x3e, 0xfb, 0x11, 0xe3, 0xb5, - 0xee, 0x9a, 0x43, 0x5c, 0xa2, 0x1b, 0x92, 0x95, 0x1f, 0xec, 0x95, 0x50, 0x1a, 0x01, 0x73, 0xaa, - 0xf6, 0xd3, 0x30, 0xba, 0xe0, 0x90, 0x86, 0xef, 0x2d, 0x7a, 0xb5, 0xa6, 0xef, 0x7a, 0x11, 0x9a, - 0x82, 0x1e, 0xc6, 0x22, 0xf2, 0x0b, 0xb2, 0x87, 0x0e, 0x21, 0x66, 0x25, 0xf6, 0x26, 0x1c, 0x5f, - 0xf0, 0xef, 0x78, 0x77, 0x9c, 0xa0, 0x36, 0x5b, 0x5e, 0xd6, 0xa4, 0x7e, 0xab, 0x52, 0xea, 0x64, - 0xe5, 0xbf, 0xe9, 0xb5, 0x9a, 0x7c, 0x29, 0x2d, 0xb9, 0x75, 0x92, 0x23, 0x9b, 0xfd, 0x99, 0x82, - 0xd1, 0x52, 0x8c, 0xaf, 0x34, 0x8b, 0x56, 0xae, 0x51, 0xc2, 0x5b, 0x30, 0xb0, 0xe1, 0x92, 0x7a, - 0x0d, 0x93, 0x0d, 0x31, 0x1b, 0x4f, 0xe6, 0x9b, 0x2d, 0x2e, 0x51, 0x4c, 0xa5, 0x02, 0x65, 0x32, - 0xab, 0x25, 0x51, 0x19, 0x2b, 0x32, 0x68, 0x1b, 0xc6, 0xe5, 0x9c, 0x49, 0xa8, 0x38, 0xb5, 0x9f, - 0x6a, 0xb7, 0x08, 0x4d, 0xe2, 0xcc, 0x84, 0x1b, 0x27, 0xc8, 0xe0, 0x14, 0x61, 0x74, 0x1a, 0x7a, - 0x1a, 0x94, 0x3f, 0xe9, 0x61, 0xc3, 0xcf, 0x84, 0x54, 0x4c, 0xde, 0xc6, 0x4a, 0xed, 0x9f, 0xb3, - 0xe0, 0x64, 0x6a, 0x64, 0x84, 0xdc, 0xf1, 0x01, 0xcf, 0x42, 0x52, 0x0e, 0x58, 0xe8, 0x2c, 0x07, - 0xb4, 0xff, 0x3b, 0x0b, 0x8e, 0x2d, 0x36, 0x9a, 0xd1, 0xee, 0x82, 0x6b, 0x5a, 0x10, 0xbc, 0x0c, - 0x7d, 0x0d, 0x52, 0x73, 0x5b, 0x0d, 0x31, 0x73, 0x25, 0x79, 0x87, 0xaf, 0xb0, 0x52, 0x7a, 0x0e, - 0x54, 0x22, 0x3f, 0x70, 0x36, 0x09, 0x2f, 0xc0, 0x02, 0x9d, 0x71, 0x42, 0xee, 0x3d, 0x72, 0xdd, - 0x6d, 0xb8, 0xd1, 0xfd, 0xed, 0x2e, 0xa1, 0xfc, 0x97, 0x44, 0x70, 0x4c, 0xcf, 0xfe, 0x96, 0x05, - 0x63, 0x72, 0xdd, 0xcf, 0xd6, 0x6a, 0x01, 0x09, 0x43, 0x34, 0x0d, 0x05, 0xb7, 0x29, 0x7a, 0x09, - 0xa2, 0x97, 0x85, 0xe5, 0x32, 0x2e, 0xb8, 0x4d, 0xf9, 0xe8, 0x62, 0x6c, 0x42, 0xd1, 0xb4, 0x83, - 0xb8, 0x2a, 0xca, 0xb1, 0xc2, 0x40, 0x17, 0x60, 0xc0, 0xf3, 0x6b, 0xfc, 0xdd, 0x22, 0x34, 0xe1, - 0x14, 0x73, 0x55, 0x94, 0x61, 0x05, 0x45, 0x65, 0x18, 0xe4, 0x56, 0xb2, 0xf1, 0xa2, 0xed, 0xca, - 0xd6, 0x96, 0x7d, 0xd9, 0x9a, 0xac, 0x89, 0x63, 0x22, 0xf6, 0x1f, 0x59, 0x30, 0x2c, 0xbf, 0xac, - 0xcb, 0x17, 0x25, 0xdd, 0x5a, 0xf1, 0x6b, 0x32, 0xde, 0x5a, 0xf4, 0x45, 0xc8, 0x20, 0xc6, 0x43, - 0xb0, 0x78, 0xa8, 0x87, 0xe0, 0x25, 0x18, 0x72, 0x9a, 0xcd, 0xb2, 0xf9, 0x8a, 0x64, 0x4b, 0x69, - 0x36, 0x2e, 0xc6, 0x3a, 0x8e, 0xfd, 0xb3, 0x05, 0x18, 0x95, 0x5f, 0x50, 0x69, 0xdd, 0x0e, 0x49, - 0x84, 0xd6, 0x60, 0xd0, 0xe1, 0xb3, 0x44, 0xe4, 0x22, 0x7f, 0x2c, 0x5b, 0xba, 0x69, 0x4c, 0x69, - 0xcc, 0x0e, 0xcf, 0xca, 0xda, 0x38, 0x26, 0x84, 0xea, 0x30, 0xe1, 0xf9, 0x11, 0x63, 0x8d, 0x14, - 0xbc, 0x9d, 0xc2, 0x39, 0x49, 0xfd, 0x94, 0xa0, 0x3e, 0xb1, 0x9a, 0xa4, 0x82, 0xd3, 0x84, 0xd1, - 0xa2, 0x94, 0x18, 0x17, 0xf3, 0x45, 0x7d, 0xfa, 0xc4, 0x65, 0x0b, 0x8c, 0xed, 0xdf, 0xb7, 0x60, - 0x50, 0xa2, 0x1d, 0x85, 0x6d, 0xc1, 0x0a, 0xf4, 0x87, 0x6c, 0x12, 0xe4, 0xd0, 0xd8, 0xed, 0x3a, - 0xce, 0xe7, 0x2b, 0xe6, 0xf8, 0xf8, 0xff, 0x10, 0x4b, 0x1a, 0x4c, 0x61, 0xa8, 0xba, 0xff, 0x3e, - 0x51, 0x18, 0xaa, 0xfe, 0xe4, 0x5c, 0x4a, 0x7f, 0xc7, 0xfa, 0xac, 0x49, 0xe0, 0xe9, 0xc3, 0xa4, - 0x19, 0x90, 0x0d, 0xf7, 0x6e, 0xf2, 0x61, 0x52, 0x66, 0xa5, 0x58, 0x40, 0xd1, 0x3b, 0x30, 0x5c, - 0x95, 0x9a, 0xa2, 0x78, 0x87, 0x9f, 0x6f, 0xab, 0xb5, 0x54, 0x0a, 0x6e, 0x2e, 0xe9, 0x9c, 0xd7, - 0xea, 0x63, 0x83, 0x9a, 0x69, 0x05, 0x56, 0xec, 0x64, 0x05, 0x16, 0xd3, 0xcd, 0xb7, 0x89, 0xfa, - 0x79, 0x0b, 0xfa, 0xb8, 0x86, 0xa0, 0x3b, 0x05, 0x8d, 0xa6, 0xef, 0x8f, 0xc7, 0xee, 0x26, 0x2d, - 0x14, 0x9c, 0x0d, 0x5a, 0x81, 0x41, 0xf6, 0x83, 0x69, 0x38, 0x8a, 0xf9, 0x3e, 0x63, 0xbc, 0x55, - 0xbd, 0x83, 0x37, 0x65, 0x35, 0x1c, 0x53, 0xb0, 0x7f, 0xba, 0x48, 0x4f, 0xb7, 0x18, 0xd5, 0xb8, - 0xf4, 0xad, 0x87, 0x77, 0xe9, 0x17, 0x1e, 0xd6, 0xa5, 0xbf, 0x09, 0x63, 0x55, 0xcd, 0x3a, 0x20, - 0x9e, 0xc9, 0x0b, 0x6d, 0x17, 0x89, 0x66, 0x48, 0xc0, 0x65, 0xa8, 0xf3, 0x26, 0x11, 0x9c, 0xa4, - 0x8a, 0x3e, 0x09, 0xc3, 0x7c, 0x9e, 0x45, 0x2b, 0xdc, 0x90, 0xee, 0x89, 0xfc, 0xf5, 0xa2, 0x37, - 0xc1, 0x65, 0xee, 0x5a, 0x75, 0x6c, 0x10, 0xb3, 0xff, 0xc9, 0x02, 0xb4, 0xd8, 0xdc, 0x22, 0x0d, - 0x12, 0x38, 0xf5, 0x58, 0xc9, 0xf7, 0x25, 0x0b, 0xa6, 0x48, 0xaa, 0x78, 0xde, 0x6f, 0x34, 0xc4, - 0x93, 0x3e, 0x47, 0xea, 0xb4, 0x98, 0x53, 0x27, 0x66, 0xeb, 0xf3, 0x30, 0x70, 0x6e, 0x7b, 0x68, - 0x05, 0x26, 0xf9, 0x2d, 0xa9, 0x00, 0x9a, 0xad, 0xdd, 0x23, 0x82, 0xf0, 0xe4, 0x5a, 0x1a, 0x05, - 0x67, 0xd5, 0xb3, 0x7f, 0x7f, 0x04, 0x72, 0x7b, 0xf1, 0x81, 0x76, 0xf3, 0x03, 0xed, 0xe6, 0x07, - 0xda, 0xcd, 0x0f, 0xb4, 0x9b, 0x1f, 0x68, 0x37, 0x3f, 0xd0, 0x6e, 0xbe, 0x4f, 0xb5, 0x9b, 0xff, - 0xa5, 0x05, 0xc7, 0xd5, 0xf5, 0x65, 0x3c, 0xd8, 0x3f, 0x07, 0x93, 0x7c, 0xbb, 0xcd, 0xd7, 0x1d, - 0xb7, 0xb1, 0x46, 0x1a, 0xcd, 0xba, 0x13, 0x49, 0x1b, 0xa6, 0x4b, 0x99, 0x2b, 0x37, 0xe1, 0x28, - 0x61, 0x54, 0xe4, 0x1e, 0x67, 0x19, 0x00, 0x9c, 0xd5, 0x8c, 0xfd, 0x3b, 0x03, 0xd0, 0xbb, 0xb8, - 0x43, 0xbc, 0xe8, 0x08, 0x9e, 0x36, 0x55, 0x18, 0x75, 0xbd, 0x1d, 0xbf, 0xbe, 0x43, 0x6a, 0x1c, - 0x7e, 0x98, 0x17, 0xf8, 0x09, 0x41, 0x7a, 0x74, 0xd9, 0x20, 0x81, 0x13, 0x24, 0x1f, 0x86, 0x8e, - 0xe8, 0x0a, 0xf4, 0xf1, 0xcb, 0x47, 0x28, 0x88, 0x32, 0xcf, 0x6c, 0x36, 0x88, 0xe2, 0x4a, 0x8d, - 0xf5, 0x57, 0xfc, 0x72, 0x13, 0xd5, 0xd1, 0x67, 0x61, 0x74, 0xc3, 0x0d, 0xc2, 0x68, 0xcd, 0x6d, - 0xd0, 0xab, 0xa1, 0xd1, 0xbc, 0x0f, 0x9d, 0x90, 0x1a, 0x87, 0x25, 0x83, 0x12, 0x4e, 0x50, 0x46, - 0x9b, 0x30, 0x52, 0x77, 0xf4, 0xa6, 0xfa, 0x0f, 0xdd, 0x94, 0xba, 0x1d, 0xae, 0xeb, 0x84, 0xb0, - 0x49, 0x97, 0x6e, 0xa7, 0x2a, 0x53, 0x6b, 0x0c, 0x30, 0x71, 0x86, 0xda, 0x4e, 0x5c, 0x9f, 0xc1, - 0x61, 0x94, 0x41, 0x63, 0xee, 0x06, 0x83, 0x26, 0x83, 0xa6, 0x39, 0x15, 0x7c, 0x06, 0x06, 0x09, - 0x1d, 0x42, 0x4a, 0x58, 0x5c, 0x30, 0x17, 0xbb, 0xeb, 0xeb, 0x8a, 0x5b, 0x0d, 0x7c, 0x53, 0x1b, - 0xb7, 0x28, 0x29, 0xe1, 0x98, 0x28, 0x9a, 0x87, 0xbe, 0x90, 0x04, 0xae, 0x92, 0xf8, 0xb7, 0x99, - 0x46, 0x86, 0xc6, 0x5d, 0x1a, 0xf9, 0x6f, 0x2c, 0xaa, 0xd2, 0xe5, 0xe5, 0x30, 0x51, 0x2c, 0xbb, - 0x0c, 0xb4, 0xe5, 0x35, 0xcb, 0x4a, 0xb1, 0x80, 0xa2, 0x37, 0xa1, 0x3f, 0x20, 0x75, 0xa6, 0xee, - 0x1d, 0xe9, 0x7e, 0x91, 0x73, 0xed, 0x31, 0xaf, 0x87, 0x25, 0x01, 0x74, 0x0d, 0x50, 0x40, 0x28, - 0x83, 0xe7, 0x7a, 0x9b, 0xca, 0x08, 0x5f, 0x1c, 0xb4, 0x8a, 0x91, 0xc6, 0x31, 0x86, 0xf4, 0x66, - 0xc5, 0x19, 0xd5, 0xd0, 0x15, 0x98, 0x50, 0xa5, 0xcb, 0x5e, 0x18, 0x39, 0xf4, 0x80, 0x1b, 0x63, - 0xb4, 0x94, 0x7c, 0x05, 0x27, 0x11, 0x70, 0xba, 0x8e, 0xfd, 0x6b, 0x16, 0xf0, 0x71, 0x3e, 0x02, - 0xa9, 0xc2, 0xeb, 0xa6, 0x54, 0xe1, 0x54, 0xee, 0xcc, 0xe5, 0x48, 0x14, 0x7e, 0xcd, 0x82, 0x21, - 0x6d, 0x66, 0xe3, 0x35, 0x6b, 0xb5, 0x59, 0xb3, 0x2d, 0x18, 0xa7, 0x2b, 0xfd, 0xc6, 0xed, 0x90, - 0x04, 0x3b, 0xa4, 0xc6, 0x16, 0x66, 0xe1, 0xfe, 0x16, 0xa6, 0x32, 0xf8, 0xbd, 0x9e, 0x20, 0x88, - 0x53, 0x4d, 0xd8, 0x9f, 0x91, 0x5d, 0x55, 0xf6, 0xd1, 0x55, 0x35, 0xe7, 0x09, 0xfb, 0x68, 0x35, - 0xab, 0x38, 0xc6, 0xa1, 0x5b, 0x6d, 0xcb, 0x0f, 0xa3, 0xa4, 0x7d, 0xf4, 0x55, 0x3f, 0x8c, 0x30, - 0x83, 0xd8, 0x2f, 0x00, 0x2c, 0xde, 0x25, 0x55, 0xbe, 0x62, 0xf5, 0x47, 0x8f, 0x95, 0xff, 0xe8, - 0xb1, 0xff, 0xd2, 0x82, 0xd1, 0xa5, 0x79, 0xe3, 0xe6, 0x9a, 0x01, 0xe0, 0x2f, 0xb5, 0x5b, 0xb7, - 0x56, 0xa5, 0x91, 0x0e, 0xb7, 0x53, 0x50, 0xa5, 0x58, 0xc3, 0x40, 0xa7, 0xa0, 0x58, 0x6f, 0x79, - 0x42, 0xec, 0xd9, 0x4f, 0xaf, 0xc7, 0xeb, 0x2d, 0x0f, 0xd3, 0x32, 0xcd, 0x93, 0xad, 0xd8, 0xb5, - 0x27, 0x5b, 0xc7, 0x80, 0x3a, 0xa8, 0x04, 0xbd, 0x77, 0xee, 0xb8, 0x35, 0x1e, 0x27, 0x40, 0x18, - 0x10, 0xdd, 0xba, 0xb5, 0xbc, 0x10, 0x62, 0x5e, 0x6e, 0x7f, 0xb9, 0x08, 0xd3, 0x4b, 0x75, 0x72, - 0xf7, 0x3d, 0xc6, 0x4a, 0xe8, 0xd6, 0x0f, 0xef, 0x70, 0x02, 0xa4, 0xc3, 0xfa, 0x5a, 0x76, 0x1e, - 0x8f, 0x0d, 0xe8, 0xe7, 0xe6, 0xc1, 0x32, 0x72, 0x42, 0xa6, 0x52, 0x36, 0x7f, 0x40, 0x66, 0xb8, - 0x99, 0xb1, 0x50, 0xca, 0xaa, 0x0b, 0x53, 0x94, 0x62, 0x49, 0x7c, 0xfa, 0x15, 0x18, 0xd6, 0x31, - 0x0f, 0xe5, 0xf5, 0xfc, 0xc3, 0x45, 0x18, 0xa7, 0x3d, 0x78, 0xa8, 0x13, 0xb1, 0x9e, 0x9e, 0x88, - 0x07, 0xed, 0xf9, 0xda, 0x79, 0x36, 0xde, 0x49, 0xce, 0xc6, 0xa5, 0xbc, 0xd9, 0x38, 0xea, 0x39, - 0xf8, 0x11, 0x0b, 0x26, 0x97, 0xea, 0x7e, 0x75, 0x3b, 0xe1, 0x9d, 0xfa, 0x12, 0x0c, 0xd1, 0xe3, - 0x38, 0x34, 0x02, 0xb5, 0x18, 0xa1, 0x7b, 0x04, 0x08, 0xeb, 0x78, 0x5a, 0xb5, 0xf5, 0xf5, 0xe5, - 0x85, 0xac, 0x88, 0x3f, 0x02, 0x84, 0x75, 0x3c, 0xfb, 0xcf, 0x2d, 0x38, 0x73, 0x65, 0x7e, 0x31, - 0x5e, 0x8a, 0xa9, 0xa0, 0x43, 0xe7, 0xa1, 0xaf, 0x59, 0xd3, 0xba, 0x12, 0x8b, 0x85, 0x17, 0x58, - 0x2f, 0x04, 0xf4, 0xfd, 0x12, 0xdf, 0x6b, 0x1d, 0xe0, 0x0a, 0x2e, 0xcf, 0x8b, 0x73, 0x57, 0x6a, - 0x81, 0xac, 0x5c, 0x2d, 0xd0, 0x13, 0xd0, 0x4f, 0xef, 0x05, 0xb7, 0x2a, 0xfb, 0xcd, 0xcd, 0x2e, - 0x78, 0x11, 0x96, 0x30, 0xfb, 0x57, 0x2d, 0x98, 0xbc, 0xe2, 0x46, 0xf4, 0xd2, 0x4e, 0x46, 0xd5, - 0xa1, 0xb7, 0x76, 0xe8, 0x46, 0x7e, 0xb0, 0x9b, 0x8c, 0xaa, 0x83, 0x15, 0x04, 0x6b, 0x58, 0xfc, - 0x83, 0x76, 0x5c, 0xe6, 0xef, 0x52, 0x30, 0xf5, 0x6e, 0x58, 0x94, 0x63, 0x85, 0x41, 0xc7, 0xab, - 0xe6, 0x06, 0x4c, 0x64, 0xb9, 0x2b, 0x0e, 0x6e, 0x35, 0x5e, 0x0b, 0x12, 0x80, 0x63, 0x1c, 0xfb, - 0x1f, 0x2c, 0x28, 0x5d, 0xe1, 0x5e, 0xbb, 0x1b, 0x61, 0xce, 0xa1, 0xfb, 0x02, 0x0c, 0x12, 0xa9, - 0x20, 0x10, 0xbd, 0x56, 0x8c, 0xa8, 0xd2, 0x1c, 0xf0, 0xe0, 0x3e, 0x0a, 0xaf, 0x0b, 0x17, 0xfa, - 0xc3, 0xf9, 0x40, 0x2f, 0x01, 0x22, 0x7a, 0x5b, 0x7a, 0xb4, 0x23, 0x16, 0x36, 0x65, 0x31, 0x05, - 0xc5, 0x19, 0x35, 0xec, 0x9f, 0xb3, 0xe0, 0xb8, 0xfa, 0xe0, 0xf7, 0xdd, 0x67, 0xda, 0x5f, 0x2f, - 0xc0, 0xc8, 0xd5, 0xb5, 0xb5, 0xf2, 0x15, 0x12, 0x69, 0xab, 0xb2, 0xbd, 0xda, 0x1f, 0x6b, 0xda, - 0xcb, 0x76, 0x6f, 0xc4, 0x56, 0xe4, 0xd6, 0x67, 0x78, 0x0c, 0xbf, 0x99, 0x65, 0x2f, 0xba, 0x11, - 0x54, 0xa2, 0xc0, 0xf5, 0x36, 0x33, 0x57, 0xba, 0xe4, 0x59, 0x8a, 0x79, 0x3c, 0x0b, 0x7a, 0x01, - 0xfa, 0x58, 0x10, 0x41, 0x39, 0x09, 0x8f, 0xa8, 0x27, 0x16, 0x2b, 0x3d, 0xd8, 0x2b, 0x0d, 0xae, - 0xe3, 0x65, 0xfe, 0x07, 0x0b, 0x54, 0xb4, 0x0e, 0x43, 0x5b, 0x51, 0xd4, 0xbc, 0x4a, 0x9c, 0x1a, - 0x09, 0xe4, 0x29, 0x7b, 0x36, 0xeb, 0x94, 0xa5, 0x83, 0xc0, 0xd1, 0xe2, 0x83, 0x29, 0x2e, 0x0b, - 0xb1, 0x4e, 0xc7, 0xae, 0x00, 0xc4, 0xb0, 0x07, 0xa4, 0xb8, 0xb1, 0xd7, 0x60, 0x90, 0x7e, 0xee, - 0x6c, 0xdd, 0x75, 0xda, 0xab, 0xc6, 0x9f, 0x81, 0x41, 0xa9, 0xf8, 0x0e, 0x45, 0x88, 0x0f, 0x76, - 0x23, 0x49, 0xbd, 0x78, 0x88, 0x63, 0xb8, 0xfd, 0x38, 0x08, 0x0b, 0xe0, 0x76, 0x24, 0xed, 0x0d, - 0x38, 0xc6, 0x4c, 0x99, 0x9d, 0x68, 0xcb, 0x58, 0xa3, 0x9d, 0x17, 0xc3, 0xb3, 0xe2, 0x5d, 0xc7, - 0xbf, 0x6c, 0x4a, 0x73, 0x21, 0x1f, 0x96, 0x14, 0xe3, 0x37, 0x9e, 0xfd, 0xf7, 0x3d, 0xf0, 0xc8, - 0x72, 0x25, 0x3f, 0x36, 0xd5, 0x65, 0x18, 0xe6, 0xec, 0x22, 0x5d, 0x1a, 0x4e, 0x5d, 0xb4, 0xab, - 0x24, 0xa0, 0x6b, 0x1a, 0x0c, 0x1b, 0x98, 0xe8, 0x0c, 0x14, 0xdd, 0x77, 0xbd, 0xa4, 0x83, 0xe5, - 0xf2, 0x5b, 0xab, 0x98, 0x96, 0x53, 0x30, 0xe5, 0x3c, 0xf9, 0x91, 0xae, 0xc0, 0x8a, 0xfb, 0x7c, - 0x1d, 0x46, 0xdd, 0xb0, 0x1a, 0xba, 0xcb, 0x1e, 0xdd, 0xa7, 0xda, 0x4e, 0x57, 0x32, 0x07, 0xda, - 0x69, 0x05, 0xc5, 0x09, 0x6c, 0xed, 0x7e, 0xe9, 0xed, 0x9a, 0x7b, 0xed, 0x18, 0x19, 0x83, 0x1e, - 0xff, 0x4d, 0xf6, 0x75, 0x21, 0x13, 0xc1, 0x8b, 0xe3, 0x9f, 0x7f, 0x70, 0x88, 0x25, 0x8c, 0x3e, - 0xe8, 0xaa, 0x5b, 0x4e, 0x73, 0xb6, 0x15, 0x6d, 0x2d, 0xb8, 0x61, 0xd5, 0xdf, 0x21, 0xc1, 0x2e, - 0x7b, 0x8b, 0x0f, 0xc4, 0x0f, 0x3a, 0x05, 0x98, 0xbf, 0x3a, 0x5b, 0xa6, 0x98, 0x38, 0x5d, 0x07, - 0xcd, 0xc2, 0x98, 0x2c, 0xac, 0x90, 0x90, 0x5d, 0x01, 0x43, 0x8c, 0x8c, 0x72, 0x79, 0x14, 0xc5, - 0x8a, 0x48, 0x12, 0xdf, 0x64, 0x70, 0xe1, 0x41, 0x30, 0xb8, 0x2f, 0xc3, 0x88, 0xeb, 0xb9, 0x91, - 0xeb, 0x44, 0x3e, 0xd7, 0x1f, 0xf1, 0x67, 0x37, 0x13, 0x30, 0x2f, 0xeb, 0x00, 0x6c, 0xe2, 0xd9, - 0xff, 0x5f, 0x0f, 0x4c, 0xb0, 0x69, 0xfb, 0x60, 0x85, 0x7d, 0x2f, 0xad, 0xb0, 0xf5, 0xf4, 0x0a, - 0x7b, 0x10, 0x9c, 0xfb, 0x7d, 0x2f, 0xb3, 0x2f, 0x58, 0x30, 0xc1, 0x64, 0xdc, 0xc6, 0x32, 0xbb, - 0x08, 0x83, 0x81, 0xe1, 0x8d, 0x3a, 0xa8, 0x2b, 0xb5, 0xa4, 0x63, 0x69, 0x8c, 0x83, 0xde, 0x00, - 0x68, 0xc6, 0x32, 0xf4, 0x82, 0x11, 0x42, 0x14, 0x72, 0xc5, 0xe7, 0x5a, 0x1d, 0xfb, 0xb3, 0x30, - 0xa8, 0xdc, 0x4d, 0xa5, 0xbf, 0xb9, 0x95, 0xe3, 0x6f, 0xde, 0x99, 0x8d, 0x90, 0xb6, 0x71, 0xc5, - 0x4c, 0xdb, 0xb8, 0xaf, 0x5a, 0x10, 0x6b, 0x38, 0xd0, 0x5b, 0x30, 0xd8, 0xf4, 0x99, 0x41, 0x74, - 0x20, 0xbd, 0x0c, 0x1e, 0x6f, 0xab, 0x22, 0xe1, 0x71, 0x02, 0x03, 0x3e, 0x1d, 0x65, 0x59, 0x15, - 0xc7, 0x54, 0xd0, 0x35, 0xe8, 0x6f, 0x06, 0xa4, 0x12, 0xb1, 0x20, 0x56, 0xdd, 0x13, 0xe4, 0xcb, - 0x97, 0x57, 0xc4, 0x92, 0x82, 0xfd, 0x1b, 0x05, 0x18, 0x4f, 0xa2, 0xa2, 0xd7, 0xa0, 0x87, 0xdc, - 0x25, 0x55, 0xd1, 0xdf, 0x4c, 0x9e, 0x20, 0x96, 0x91, 0xf0, 0x01, 0xa0, 0xff, 0x31, 0xab, 0x85, - 0xae, 0x42, 0x3f, 0x65, 0x08, 0xae, 0xa8, 0x80, 0x8d, 0x8f, 0xe6, 0x31, 0x15, 0x8a, 0xb3, 0xe2, - 0x9d, 0x13, 0x45, 0x58, 0x56, 0x67, 0x06, 0x69, 0xd5, 0x66, 0x85, 0xbe, 0xb5, 0xa2, 0x76, 0x22, - 0x81, 0xb5, 0xf9, 0x32, 0x47, 0x12, 0xd4, 0xb8, 0x41, 0x9a, 0x2c, 0xc4, 0x31, 0x11, 0xf4, 0x06, - 0xf4, 0x86, 0x75, 0x42, 0x9a, 0xc2, 0xe2, 0x20, 0x53, 0xca, 0x59, 0xa1, 0x08, 0x82, 0x12, 0x93, - 0x8a, 0xb0, 0x02, 0xcc, 0x2b, 0xda, 0xbf, 0x65, 0x01, 0x70, 0x0b, 0x3e, 0xc7, 0xdb, 0x24, 0x47, - 0xa0, 0x18, 0x58, 0x80, 0x9e, 0xb0, 0x49, 0xaa, 0xed, 0xac, 0xfd, 0xe3, 0xfe, 0x54, 0x9a, 0xa4, - 0x1a, 0xaf, 0x59, 0xfa, 0x0f, 0xb3, 0xda, 0xf6, 0x8f, 0x02, 0x8c, 0xc6, 0x68, 0xcb, 0x11, 0x69, - 0xa0, 0xe7, 0x8c, 0x28, 0x37, 0xa7, 0x12, 0x51, 0x6e, 0x06, 0x19, 0xb6, 0x26, 0x83, 0xfe, 0x2c, - 0x14, 0x1b, 0xce, 0x5d, 0x21, 0x64, 0x7c, 0xa6, 0x7d, 0x37, 0x28, 0xfd, 0x99, 0x15, 0xe7, 0x2e, - 0x7f, 0x87, 0x3f, 0x23, 0xf7, 0xd8, 0x8a, 0x73, 0xb7, 0xa3, 0x45, 0x3a, 0x6d, 0x84, 0xb5, 0xe5, - 0x7a, 0xc2, 0x38, 0xad, 0xab, 0xb6, 0x5c, 0x2f, 0xd9, 0x96, 0xeb, 0x75, 0xd1, 0x96, 0xeb, 0xa1, - 0x7b, 0xd0, 0x2f, 0x6c, 0x47, 0x45, 0xf8, 0xbd, 0x8b, 0x5d, 0xb4, 0x27, 0x4c, 0x4f, 0x79, 0x9b, - 0x17, 0xa5, 0x9c, 0x41, 0x94, 0x76, 0x6c, 0x57, 0x36, 0x88, 0xfe, 0x2b, 0x0b, 0x46, 0xc5, 0x6f, - 0x4c, 0xde, 0x6d, 0x91, 0x30, 0x12, 0x7c, 0xf8, 0x47, 0xba, 0xef, 0x83, 0xa8, 0xc8, 0xbb, 0xf2, - 0x11, 0x79, 0x65, 0x9a, 0xc0, 0x8e, 0x3d, 0x4a, 0xf4, 0x02, 0xfd, 0x86, 0x05, 0xc7, 0x1a, 0xce, - 0x5d, 0xde, 0x22, 0x2f, 0xc3, 0x4e, 0xe4, 0xfa, 0xc2, 0x06, 0xe3, 0xb5, 0xee, 0xa6, 0x3f, 0x55, - 0x9d, 0x77, 0x52, 0x2a, 0x5c, 0x8f, 0x65, 0xa1, 0x74, 0xec, 0x6a, 0x66, 0xbf, 0xa6, 0x37, 0x60, - 0x40, 0xae, 0xb7, 0x87, 0x69, 0x18, 0xcf, 0xda, 0x11, 0x6b, 0xed, 0xa1, 0xb6, 0xf3, 0x59, 0x18, - 0xd6, 0xd7, 0xd8, 0x43, 0x6d, 0xeb, 0x5d, 0x98, 0xcc, 0x58, 0x4b, 0x0f, 0xb5, 0xc9, 0x3b, 0x70, - 0x2a, 0x77, 0x7d, 0x3c, 0x54, 0xc7, 0x86, 0xaf, 0x5b, 0xfa, 0x39, 0x78, 0x04, 0xda, 0x99, 0x79, - 0x53, 0x3b, 0x73, 0xb6, 0xfd, 0xce, 0xc9, 0x51, 0xd1, 0xbc, 0xa3, 0x77, 0x9a, 0x9e, 0xea, 0xe8, - 0x4d, 0xe8, 0xab, 0xd3, 0x12, 0x69, 0x81, 0x6c, 0x77, 0xde, 0x91, 0x31, 0x5f, 0xcc, 0xca, 0x43, - 0x2c, 0x28, 0xd8, 0x5f, 0xb1, 0x20, 0xc3, 0x35, 0x83, 0xf2, 0x49, 0x2d, 0xb7, 0xc6, 0x86, 0xa4, - 0x18, 0xf3, 0x49, 0x2a, 0x08, 0xcc, 0x19, 0x28, 0x6e, 0xba, 0x35, 0xe1, 0x59, 0xac, 0xc0, 0x57, - 0x28, 0x78, 0xd3, 0xad, 0xa1, 0x25, 0x40, 0x61, 0xab, 0xd9, 0xac, 0x33, 0xb3, 0x25, 0xa7, 0x7e, - 0x25, 0xf0, 0x5b, 0x4d, 0x6e, 0x6e, 0x5c, 0xe4, 0x42, 0xa2, 0x4a, 0x0a, 0x8a, 0x33, 0x6a, 0xd8, - 0xbf, 0x6b, 0x41, 0xcf, 0x11, 0x4c, 0x13, 0x36, 0xa7, 0xe9, 0xb9, 0x5c, 0xd2, 0x22, 0x6b, 0xc3, - 0x0c, 0x76, 0xee, 0x2c, 0xde, 0x8d, 0x88, 0x17, 0x32, 0x86, 0x23, 0x73, 0xd6, 0xf6, 0x2c, 0x98, - 0xbc, 0xee, 0x3b, 0xb5, 0x39, 0xa7, 0xee, 0x78, 0x55, 0x12, 0x2c, 0x7b, 0x9b, 0x87, 0xb2, 0xed, - 0x2f, 0x74, 0xb4, 0xed, 0xbf, 0x0c, 0x7d, 0x6e, 0x53, 0x0b, 0xfb, 0x7e, 0x8e, 0xce, 0xee, 0x72, - 0x59, 0x44, 0x7c, 0x47, 0x46, 0xe3, 0xac, 0x14, 0x0b, 0x7c, 0xba, 0x2c, 0xb9, 0x51, 0x5d, 0x4f, - 0xfe, 0xb2, 0xa4, 0x6f, 0x9d, 0x64, 0x38, 0x33, 0xc3, 0xfc, 0x7b, 0x0b, 0x8c, 0x26, 0x84, 0x07, - 0x23, 0x86, 0x7e, 0x97, 0x7f, 0xa9, 0x58, 0x9b, 0x4f, 0x66, 0xbf, 0x41, 0x52, 0x03, 0xa3, 0xf9, - 0xe6, 0xf1, 0x02, 0x2c, 0x09, 0xd9, 0x97, 0x21, 0x33, 0xfc, 0x4c, 0x67, 0xf9, 0x92, 0xfd, 0x09, - 0x98, 0x60, 0x35, 0x0f, 0x29, 0xbb, 0xb1, 0x13, 0x52, 0xf1, 0x8c, 0x08, 0xbe, 0xf6, 0xff, 0x6d, - 0x01, 0x5a, 0xf1, 0x6b, 0xee, 0xc6, 0xae, 0x20, 0xce, 0xbf, 0xff, 0x5d, 0x28, 0xf1, 0xc7, 0x71, - 0x32, 0xca, 0xed, 0x7c, 0xdd, 0x09, 0x43, 0x4d, 0x22, 0xff, 0xa4, 0x68, 0xb7, 0xb4, 0xd6, 0x1e, - 0x1d, 0x77, 0xa2, 0x87, 0xde, 0x4a, 0x04, 0x1d, 0xfc, 0x68, 0x2a, 0xe8, 0xe0, 0x93, 0x99, 0x76, - 0x31, 0xe9, 0xde, 0xcb, 0x60, 0x84, 0xf6, 0x17, 0x2d, 0x18, 0x5b, 0x4d, 0x44, 0x6d, 0x3d, 0xcf, - 0x8c, 0x04, 0x32, 0x34, 0x4d, 0x15, 0x56, 0x8a, 0x05, 0xf4, 0x81, 0x4b, 0x62, 0xff, 0xd5, 0x82, - 0x38, 0xdc, 0xd5, 0x11, 0xb0, 0xdc, 0xf3, 0x06, 0xcb, 0x9d, 0xf9, 0x7c, 0x51, 0xdd, 0xc9, 0xe3, - 0xb8, 0xd1, 0x35, 0x35, 0x27, 0x6d, 0x5e, 0x2e, 0x31, 0x19, 0xbe, 0xcf, 0x46, 0xcd, 0x89, 0x53, - 0xb3, 0xf1, 0xcd, 0x02, 0x20, 0x85, 0xdb, 0x75, 0xa0, 0xca, 0x74, 0x8d, 0x07, 0x13, 0xa8, 0x72, - 0x07, 0x10, 0x33, 0x73, 0x09, 0x1c, 0x2f, 0xe4, 0x64, 0x5d, 0x21, 0x7b, 0x3e, 0x9c, 0x0d, 0xcd, - 0xb4, 0xf4, 0x5c, 0xbd, 0x9e, 0xa2, 0x86, 0x33, 0x5a, 0xd0, 0xcc, 0x97, 0x7a, 0xbb, 0x35, 0x5f, - 0xea, 0xeb, 0xe0, 0x82, 0xfd, 0x35, 0x0b, 0x46, 0xd4, 0x30, 0xbd, 0x4f, 0x5c, 0x40, 0x54, 0x7f, - 0x72, 0xee, 0x95, 0xb2, 0xd6, 0x65, 0xc6, 0x0c, 0x7c, 0x1f, 0x73, 0xa5, 0x77, 0xea, 0xee, 0x3d, - 0xa2, 0xe2, 0x29, 0x97, 0x84, 0x6b, 0xbc, 0x28, 0x3d, 0xd8, 0x2b, 0x8d, 0xa8, 0x7f, 0x3c, 0x82, - 0x6b, 0x5c, 0xc5, 0xfe, 0x25, 0xba, 0xd9, 0xcd, 0xa5, 0x88, 0x5e, 0x82, 0xde, 0xe6, 0x96, 0x13, - 0x92, 0x84, 0xab, 0x5c, 0x6f, 0x99, 0x16, 0x1e, 0xec, 0x95, 0x46, 0x55, 0x05, 0x56, 0x82, 0x39, - 0x76, 0xf7, 0xe1, 0x3f, 0xd3, 0x8b, 0xb3, 0x63, 0xf8, 0xcf, 0x7f, 0xb2, 0xa0, 0x67, 0x95, 0xde, - 0x5e, 0x0f, 0xff, 0x08, 0x78, 0xdd, 0x38, 0x02, 0x4e, 0xe7, 0x65, 0x16, 0xca, 0xdd, 0xfd, 0x4b, - 0x89, 0xdd, 0x7f, 0x36, 0x97, 0x42, 0xfb, 0x8d, 0xdf, 0x80, 0x21, 0x96, 0xaf, 0x48, 0xb8, 0x05, - 0xbe, 0x60, 0x6c, 0xf8, 0x52, 0x62, 0xc3, 0x8f, 0x69, 0xa8, 0xda, 0x4e, 0x7f, 0x0a, 0xfa, 0x85, - 0x9f, 0x59, 0x32, 0x22, 0x81, 0xc0, 0xc5, 0x12, 0x6e, 0xff, 0x7c, 0x11, 0x8c, 0xfc, 0x48, 0xe8, - 0xf7, 0x2d, 0x98, 0x09, 0xb8, 0xfd, 0x79, 0x6d, 0xa1, 0x15, 0xb8, 0xde, 0x66, 0xa5, 0xba, 0x45, - 0x6a, 0xad, 0xba, 0xeb, 0x6d, 0x2e, 0x6f, 0x7a, 0xbe, 0x2a, 0x5e, 0xbc, 0x4b, 0xaa, 0x2d, 0xa6, - 0x1b, 0xee, 0x90, 0x8c, 0x49, 0xf9, 0x71, 0x3c, 0xbf, 0xbf, 0x57, 0x9a, 0xc1, 0x87, 0xa2, 0x8d, - 0x0f, 0xd9, 0x17, 0xf4, 0xe7, 0x16, 0x5c, 0xe4, 0x79, 0x7a, 0xba, 0xef, 0x7f, 0x1b, 0x09, 0x47, - 0x59, 0x92, 0x8a, 0x89, 0xac, 0x91, 0xa0, 0x31, 0xf7, 0xb2, 0x18, 0xd0, 0x8b, 0xe5, 0xc3, 0xb5, - 0x85, 0x0f, 0xdb, 0x39, 0xfb, 0x7f, 0x2e, 0xc2, 0x88, 0x08, 0x13, 0x29, 0xee, 0x80, 0x97, 0x8c, - 0x25, 0xf1, 0x68, 0x62, 0x49, 0x4c, 0x18, 0xc8, 0x0f, 0xe6, 0xf8, 0x0f, 0x61, 0x82, 0x1e, 0xce, - 0x57, 0x89, 0x13, 0x44, 0xb7, 0x89, 0xc3, 0xad, 0x12, 0x8b, 0x87, 0x3e, 0xfd, 0x95, 0x78, 0xfc, - 0x7a, 0x92, 0x18, 0x4e, 0xd3, 0xff, 0x5e, 0xba, 0x73, 0x3c, 0x18, 0x4f, 0x45, 0xfa, 0x7c, 0x1b, - 0x06, 0x95, 0x93, 0x94, 0x38, 0x74, 0xda, 0x07, 0xcc, 0x4d, 0x52, 0xe0, 0x42, 0xcf, 0xd8, 0x41, - 0x2f, 0x26, 0x67, 0xff, 0x66, 0xc1, 0x68, 0x90, 0x4f, 0xe2, 0x2a, 0x0c, 0x38, 0x21, 0x0b, 0xe2, - 0x5d, 0x6b, 0x27, 0x97, 0x4e, 0x35, 0xc3, 0x1c, 0xd5, 0x66, 0x45, 0x4d, 0xac, 0x68, 0xa0, 0xab, - 0xdc, 0xf6, 0x73, 0x87, 0xb4, 0x13, 0x4a, 0xa7, 0xa8, 0x81, 0xb4, 0x0e, 0xdd, 0x21, 0x58, 0xd4, - 0x47, 0x9f, 0xe2, 0xc6, 0xb9, 0xd7, 0x3c, 0xff, 0x8e, 0x77, 0xc5, 0xf7, 0x65, 0x48, 0xa0, 0xee, - 0x08, 0x4e, 0x48, 0x93, 0x5c, 0x55, 0x1d, 0x9b, 0xd4, 0xba, 0x0b, 0x9d, 0xfd, 0x39, 0x60, 0x79, - 0x49, 0xcc, 0x98, 0x04, 0x21, 0x22, 0x30, 0x26, 0x62, 0x90, 0xca, 0x32, 0x31, 0x76, 0x99, 0xcf, - 0x6f, 0xb3, 0x76, 0xac, 0xc7, 0xb9, 0x66, 0x92, 0xc0, 0x49, 0x9a, 0xf6, 0x16, 0x3f, 0x84, 0x97, - 0x88, 0x13, 0xb5, 0x02, 0x12, 0xa2, 0x8f, 0xc3, 0x54, 0xfa, 0x65, 0x2c, 0xd4, 0x21, 0x16, 0xe3, - 0x9e, 0x4f, 0xef, 0xef, 0x95, 0xa6, 0x2a, 0x39, 0x38, 0x38, 0xb7, 0xb6, 0xfd, 0x2b, 0x16, 0x30, - 0x4f, 0xf0, 0x23, 0xe0, 0x7c, 0x3e, 0x66, 0x72, 0x3e, 0x53, 0x79, 0xd3, 0x99, 0xc3, 0xf4, 0xbc, - 0xc8, 0xd7, 0x70, 0x39, 0xf0, 0xef, 0xee, 0x0a, 0xdb, 0xad, 0xce, 0xcf, 0x38, 0xfb, 0xcb, 0x16, - 0xb0, 0x24, 0x3e, 0x98, 0xbf, 0xda, 0xa5, 0x82, 0xa3, 0xb3, 0x59, 0xc2, 0xc7, 0x61, 0x60, 0x43, - 0x0c, 0x7f, 0x86, 0xd0, 0xc9, 0xe8, 0xb0, 0x49, 0x5b, 0x4e, 0x9a, 0xf0, 0xe8, 0x14, 0xff, 0xb0, - 0xa2, 0x66, 0xff, 0xf7, 0x16, 0x4c, 0xe7, 0x57, 0x43, 0xeb, 0x70, 0x32, 0x20, 0xd5, 0x56, 0x10, - 0xd2, 0x2d, 0x21, 0x1e, 0x40, 0xc2, 0x29, 0x8a, 0x4f, 0xf5, 0x23, 0xfb, 0x7b, 0xa5, 0x93, 0x38, - 0x1b, 0x05, 0xe7, 0xd5, 0x45, 0xaf, 0xc0, 0x68, 0x2b, 0xe4, 0x9c, 0x1f, 0x63, 0xba, 0x42, 0x11, - 0x29, 0x9a, 0xf9, 0x0d, 0xad, 0x1b, 0x10, 0x9c, 0xc0, 0xb4, 0x7f, 0x80, 0x2f, 0x47, 0x15, 0x2c, - 0xba, 0x01, 0x13, 0x9e, 0xf6, 0x9f, 0xde, 0x80, 0xf2, 0xa9, 0xff, 0x78, 0xa7, 0x5b, 0x9f, 0x5d, - 0x97, 0x9a, 0xaf, 0x7a, 0x82, 0x0c, 0x4e, 0x53, 0xb6, 0x7f, 0xc1, 0x82, 0x93, 0x3a, 0xa2, 0xe6, - 0x0e, 0xd7, 0x49, 0x97, 0xb7, 0x00, 0x03, 0x7e, 0x93, 0x04, 0x4e, 0xe4, 0x07, 0xe2, 0x9a, 0xbb, - 0x20, 0x57, 0xe8, 0x0d, 0x51, 0x7e, 0x20, 0x92, 0xd7, 0x48, 0xea, 0xb2, 0x1c, 0xab, 0x9a, 0xc8, - 0x86, 0x3e, 0x26, 0x40, 0x0c, 0x85, 0xe3, 0x23, 0x3b, 0xb4, 0x98, 0x7d, 0x4a, 0x88, 0x05, 0xc4, - 0xfe, 0x7b, 0x8b, 0xaf, 0x4f, 0xbd, 0xeb, 0xe8, 0x5d, 0x18, 0x6f, 0x38, 0x51, 0x75, 0x6b, 0xf1, - 0x6e, 0x33, 0xe0, 0x2a, 0x5a, 0x39, 0x4e, 0xcf, 0x74, 0x1a, 0x27, 0xed, 0x23, 0x63, 0x03, 0xe9, - 0x95, 0x04, 0x31, 0x9c, 0x22, 0x8f, 0x6e, 0xc3, 0x10, 0x2b, 0x63, 0x3e, 0xbd, 0x61, 0x3b, 0x5e, - 0x26, 0xaf, 0x35, 0x65, 0xe2, 0xb3, 0x12, 0xd3, 0xc1, 0x3a, 0x51, 0xfb, 0xab, 0x45, 0x7e, 0x68, - 0xb0, 0xb7, 0xc7, 0x53, 0xd0, 0xdf, 0xf4, 0x6b, 0xf3, 0xcb, 0x0b, 0x58, 0xcc, 0x82, 0xba, 0xf7, - 0xca, 0xbc, 0x18, 0x4b, 0x38, 0xba, 0x00, 0x03, 0xe2, 0xa7, 0x54, 0xa9, 0xb3, 0x3d, 0x22, 0xf0, - 0x42, 0xac, 0xa0, 0xe8, 0x79, 0x80, 0x66, 0xe0, 0xef, 0xb8, 0x35, 0x16, 0x89, 0xa9, 0x68, 0x5a, - 0xe7, 0x95, 0x15, 0x04, 0x6b, 0x58, 0xe8, 0x55, 0x18, 0x69, 0x79, 0x21, 0xe7, 0x9f, 0xb4, 0x78, - 0xf7, 0xca, 0x6e, 0x6c, 0x5d, 0x07, 0x62, 0x13, 0x17, 0xcd, 0x42, 0x5f, 0xe4, 0x30, 0x6b, 0xb3, - 0xde, 0x7c, 0x23, 0xfa, 0x35, 0x8a, 0xa1, 0x67, 0x96, 0xa3, 0x15, 0xb0, 0xa8, 0x88, 0xde, 0x96, - 0xee, 0xf5, 0xfc, 0x26, 0x12, 0xde, 0x2b, 0xdd, 0xdd, 0x5a, 0x9a, 0x73, 0xbd, 0xf0, 0x8a, 0x31, - 0x68, 0xa1, 0x57, 0x00, 0xc8, 0xdd, 0x88, 0x04, 0x9e, 0x53, 0x57, 0x36, 0xa2, 0x8a, 0x91, 0x59, - 0xf0, 0x57, 0xfd, 0x68, 0x3d, 0x24, 0x8b, 0x0a, 0x03, 0x6b, 0xd8, 0xf6, 0x8f, 0x0e, 0x01, 0xc4, - 0x0f, 0x0d, 0x74, 0x0f, 0x06, 0xaa, 0x4e, 0xd3, 0xa9, 0xf2, 0xb4, 0xa9, 0xc5, 0x3c, 0xaf, 0xe7, - 0xb8, 0xc6, 0xcc, 0xbc, 0x40, 0xe7, 0xca, 0x1b, 0x19, 0x32, 0x7c, 0x40, 0x16, 0x77, 0x54, 0xd8, - 0xa8, 0xf6, 0xd0, 0x17, 0x2c, 0x18, 0x12, 0x91, 0x8e, 0xd8, 0x0c, 0x15, 0xf2, 0xf5, 0x6d, 0x5a, - 0xfb, 0xb3, 0x71, 0x0d, 0xde, 0x85, 0x17, 0xe4, 0x0a, 0xd5, 0x20, 0x1d, 0x7b, 0xa1, 0x37, 0x8c, - 0x3e, 0x2c, 0xdf, 0xb6, 0x45, 0x63, 0x28, 0xd5, 0xdb, 0x76, 0x90, 0x5d, 0x35, 0xfa, 0xb3, 0x76, - 0xdd, 0x78, 0xd6, 0xf6, 0xe4, 0xfb, 0x0f, 0x1b, 0xfc, 0x76, 0xa7, 0x17, 0x2d, 0x2a, 0xeb, 0xb1, - 0x44, 0x7a, 0xf3, 0x9d, 0x5e, 0xb5, 0x87, 0x5d, 0x87, 0x38, 0x22, 0x9f, 0x85, 0xb1, 0x9a, 0xc9, - 0xb5, 0x88, 0x95, 0xf8, 0x64, 0x1e, 0xdd, 0x04, 0x93, 0x13, 0xf3, 0x29, 0x09, 0x00, 0x4e, 0x12, - 0x46, 0x65, 0x1e, 0x5a, 0x66, 0xd9, 0xdb, 0xf0, 0x85, 0x07, 0x95, 0x9d, 0x3b, 0x97, 0xbb, 0x61, - 0x44, 0x1a, 0x14, 0x33, 0x66, 0x12, 0x56, 0x45, 0x5d, 0xac, 0xa8, 0xa0, 0x37, 0xa1, 0x8f, 0x79, - 0x3d, 0x86, 0x53, 0x03, 0xf9, 0x6a, 0x0d, 0x33, 0x12, 0x6a, 0xbc, 0x21, 0xd9, 0xdf, 0x10, 0x0b, - 0x0a, 0xe8, 0xaa, 0xf4, 0x29, 0x0e, 0x97, 0xbd, 0xf5, 0x90, 0x30, 0x9f, 0xe2, 0xc1, 0xb9, 0xc7, - 0x63, 0x77, 0x61, 0x5e, 0x9e, 0x99, 0x7f, 0xd6, 0xa8, 0x49, 0xd9, 0x3e, 0xf1, 0x5f, 0xa6, 0xb5, - 0x15, 0x71, 0xdb, 0x32, 0xbb, 0x67, 0xa6, 0xbe, 0x8d, 0x87, 0xf3, 0xa6, 0x49, 0x02, 0x27, 0x69, - 0x52, 0x16, 0x9a, 0xef, 0x7a, 0xe1, 0x83, 0xd5, 0xe9, 0xec, 0xe0, 0x92, 0x03, 0x76, 0x1b, 0xf1, - 0x12, 0x2c, 0xea, 0x23, 0x17, 0xc6, 0x02, 0x83, 0xbd, 0x90, 0xe1, 0xd6, 0xce, 0x77, 0xc7, 0xc4, - 0x68, 0x81, 0xfc, 0x4d, 0x32, 0x38, 0x49, 0x17, 0xbd, 0xa9, 0x31, 0x4a, 0x23, 0xed, 0x5f, 0xfe, - 0x9d, 0x58, 0xa3, 0xe9, 0x6d, 0x18, 0x31, 0x0e, 0x9b, 0x87, 0xaa, 0x82, 0xf4, 0x60, 0x3c, 0x79, - 0xb2, 0x3c, 0x54, 0xcd, 0xe3, 0xdf, 0xf6, 0xc0, 0xa8, 0xb9, 0x13, 0xd0, 0x45, 0x18, 0x14, 0x44, - 0x54, 0x46, 0x2b, 0xb5, 0xb9, 0x57, 0x24, 0x00, 0xc7, 0x38, 0x2c, 0x91, 0x19, 0xab, 0xae, 0xf9, - 0x0a, 0xc4, 0x89, 0xcc, 0x14, 0x04, 0x6b, 0x58, 0xf4, 0x01, 0x7b, 0xdb, 0xf7, 0x23, 0x75, 0x8f, - 0xaa, 0xed, 0x32, 0xc7, 0x4a, 0xb1, 0x80, 0xd2, 0xfb, 0x73, 0x9b, 0x04, 0x1e, 0xa9, 0x9b, 0x29, - 0x1d, 0xd4, 0xfd, 0x79, 0x4d, 0x07, 0x62, 0x13, 0x97, 0x72, 0x01, 0x7e, 0xc8, 0xf6, 0x9f, 0x78, - 0x26, 0xc7, 0xbe, 0x17, 0x15, 0x1e, 0x45, 0x42, 0xc2, 0xd1, 0x27, 0xe0, 0xa4, 0x0a, 0x9f, 0x28, - 0x56, 0x97, 0x6c, 0xb1, 0xcf, 0x90, 0x6a, 0x9d, 0x9c, 0xcf, 0x46, 0xc3, 0x79, 0xf5, 0xd1, 0xeb, - 0x30, 0x2a, 0x9e, 0x52, 0x92, 0x62, 0xbf, 0x69, 0x48, 0x78, 0xcd, 0x80, 0xe2, 0x04, 0xb6, 0x4c, - 0x4a, 0xc1, 0xde, 0x18, 0x92, 0xc2, 0x40, 0x3a, 0x29, 0x85, 0x0e, 0xc7, 0xa9, 0x1a, 0x68, 0x16, - 0xc6, 0x38, 0xeb, 0xe8, 0x7a, 0x9b, 0x7c, 0x4e, 0x84, 0x67, 0xa7, 0xda, 0x54, 0x37, 0x4c, 0x30, - 0x4e, 0xe2, 0xa3, 0xcb, 0x30, 0xec, 0x04, 0xd5, 0x2d, 0x37, 0x22, 0x55, 0xba, 0x33, 0x98, 0x2d, - 0x9f, 0x66, 0x89, 0x39, 0xab, 0xc1, 0xb0, 0x81, 0x69, 0xdf, 0x83, 0xc9, 0x8c, 0xf0, 0x32, 0x74, - 0xe1, 0x38, 0x4d, 0x57, 0x7e, 0x53, 0xc2, 0xdd, 0x61, 0xb6, 0xbc, 0x2c, 0xbf, 0x46, 0xc3, 0xa2, - 0xab, 0x93, 0x85, 0xa1, 0xd1, 0x92, 0x6f, 0xab, 0xd5, 0xb9, 0x24, 0x01, 0x38, 0xc6, 0xb1, 0xff, - 0xb9, 0x00, 0x63, 0x19, 0x0a, 0x3a, 0x96, 0x00, 0x3a, 0xf1, 0xd2, 0x8a, 0xf3, 0x3d, 0x9b, 0x39, - 0x4e, 0x0a, 0x87, 0xc8, 0x71, 0x52, 0xec, 0x94, 0xe3, 0xa4, 0xe7, 0xbd, 0xe4, 0x38, 0x31, 0x47, - 0xac, 0xb7, 0xab, 0x11, 0xcb, 0xc8, 0x8b, 0xd2, 0x77, 0xc8, 0xbc, 0x28, 0xc6, 0xa0, 0xf7, 0x77, - 0x31, 0xe8, 0x3f, 0x5d, 0x80, 0xf1, 0xa4, 0x6e, 0xef, 0x08, 0xe4, 0xe3, 0x6f, 0x1a, 0xf2, 0xf1, - 0x0b, 0xdd, 0x78, 0xe2, 0xe7, 0xca, 0xca, 0x71, 0x42, 0x56, 0xfe, 0x74, 0x57, 0xd4, 0xda, 0xcb, - 0xcd, 0x7f, 0xb1, 0x00, 0xc7, 0x33, 0x55, 0x9e, 0x47, 0x30, 0x36, 0x37, 0x8c, 0xb1, 0x79, 0xae, - 0xeb, 0x28, 0x05, 0xb9, 0x03, 0x74, 0x2b, 0x31, 0x40, 0x17, 0xbb, 0x27, 0xd9, 0x7e, 0x94, 0xbe, - 0x55, 0x84, 0xb3, 0x99, 0xf5, 0x62, 0xf1, 0xf2, 0x92, 0x21, 0x5e, 0x7e, 0x3e, 0x21, 0x5e, 0xb6, - 0xdb, 0xd7, 0x7e, 0x30, 0xf2, 0x66, 0xe1, 0xad, 0xcf, 0x62, 0x8e, 0xdc, 0xa7, 0xac, 0xd9, 0xf0, - 0xd6, 0x57, 0x84, 0xb0, 0x49, 0xf7, 0x7b, 0x49, 0xc6, 0xfc, 0x67, 0x16, 0x9c, 0xca, 0x9c, 0x9b, - 0x23, 0x90, 0xf4, 0xad, 0x9a, 0x92, 0xbe, 0xa7, 0xba, 0x5e, 0xad, 0x39, 0xa2, 0xbf, 0x2f, 0xf6, - 0xe5, 0x7c, 0x0b, 0x13, 0x40, 0xdc, 0x80, 0x21, 0xa7, 0x5a, 0x25, 0x61, 0xb8, 0xe2, 0xd7, 0x54, - 0x3a, 0x84, 0xe7, 0xd8, 0xf3, 0x30, 0x2e, 0x3e, 0xd8, 0x2b, 0x4d, 0x27, 0x49, 0xc4, 0x60, 0xac, - 0x53, 0x40, 0x9f, 0x82, 0x81, 0x50, 0x66, 0xb2, 0xec, 0xb9, 0xff, 0x4c, 0x96, 0x8c, 0xc9, 0x55, - 0x02, 0x16, 0x45, 0x12, 0x7d, 0xbf, 0x1e, 0xfd, 0xa9, 0x8d, 0x68, 0x91, 0x77, 0xf2, 0x3e, 0x62, - 0x40, 0x3d, 0x0f, 0xb0, 0xa3, 0x5e, 0x32, 0x49, 0xe1, 0x89, 0xf6, 0xc6, 0xd1, 0xb0, 0xd0, 0x1b, - 0x30, 0x1e, 0xf2, 0xc0, 0xa7, 0xb1, 0x91, 0x0a, 0x5f, 0x8b, 0x2c, 0x76, 0x5c, 0x25, 0x01, 0xc3, - 0x29, 0x6c, 0xb4, 0x24, 0x5b, 0x65, 0xe6, 0x48, 0x7c, 0x79, 0x9e, 0x8f, 0x5b, 0x14, 0x26, 0x49, - 0xc7, 0x92, 0x93, 0xc0, 0x86, 0x5f, 0xab, 0x89, 0x3e, 0x05, 0x40, 0x17, 0x91, 0x10, 0xa2, 0xf4, - 0xe7, 0x1f, 0xa1, 0xf4, 0x6c, 0xa9, 0x65, 0x7a, 0x32, 0x30, 0x37, 0xfb, 0x05, 0x45, 0x04, 0x6b, - 0x04, 0x91, 0x03, 0x23, 0xf1, 0xbf, 0x38, 0x47, 0xfb, 0x85, 0xdc, 0x16, 0x92, 0xc4, 0x99, 0x82, - 0x61, 0x41, 0x27, 0x81, 0x4d, 0x8a, 0xe8, 0x93, 0x70, 0x6a, 0x27, 0xd7, 0xf2, 0x87, 0x73, 0x82, - 0x2c, 0xe9, 0x7a, 0xbe, 0xbd, 0x4f, 0x7e, 0x7d, 0xfb, 0x7f, 0x07, 0x78, 0xa4, 0xcd, 0x49, 0x8f, - 0x66, 0x4d, 0xad, 0xfd, 0x33, 0x49, 0xc9, 0xc6, 0x74, 0x66, 0x65, 0x43, 0xd4, 0x91, 0xd8, 0x50, - 0x85, 0xf7, 0xbc, 0xa1, 0x7e, 0xc2, 0xd2, 0x64, 0x4e, 0xdc, 0xa6, 0xfb, 0x63, 0x87, 0xbc, 0xc1, - 0x1e, 0xa0, 0x10, 0x6a, 0x23, 0x43, 0x92, 0xf3, 0x7c, 0xd7, 0xdd, 0xe9, 0x5e, 0xb4, 0xf3, 0xf5, - 0xec, 0x80, 0xef, 0x5c, 0xc8, 0x73, 0xe5, 0xb0, 0xdf, 0x7f, 0x54, 0xc1, 0xdf, 0xbf, 0x69, 0xc1, - 0xa9, 0x54, 0x31, 0xef, 0x03, 0x09, 0x45, 0xb4, 0xbb, 0xd5, 0xf7, 0xdc, 0x79, 0x49, 0x90, 0x7f, - 0xc3, 0x55, 0xf1, 0x0d, 0xa7, 0x72, 0xf1, 0x92, 0x5d, 0xff, 0xd2, 0xdf, 0x94, 0x26, 0x59, 0x03, - 0x26, 0x22, 0xce, 0xef, 0x3a, 0x6a, 0xc2, 0xb9, 0x6a, 0x2b, 0x08, 0xe2, 0xc5, 0x9a, 0xb1, 0x39, - 0xf9, 0x5b, 0xef, 0xf1, 0xfd, 0xbd, 0xd2, 0xb9, 0xf9, 0x0e, 0xb8, 0xb8, 0x23, 0x35, 0xe4, 0x01, - 0x6a, 0xa4, 0xec, 0xeb, 0xd8, 0x01, 0x90, 0x23, 0x87, 0x49, 0x5b, 0xe3, 0x71, 0x4b, 0xd9, 0x0c, - 0x2b, 0xbd, 0x0c, 0xca, 0x47, 0x2b, 0x3d, 0xf9, 0xce, 0xc4, 0xa5, 0x9f, 0xbe, 0x0e, 0x67, 0xdb, - 0x2f, 0xa6, 0x43, 0x85, 0x72, 0xf8, 0x4b, 0x0b, 0xce, 0xb4, 0x8d, 0x17, 0xf6, 0x5d, 0xf8, 0x58, - 0xb0, 0x3f, 0x6f, 0xc1, 0xa3, 0x99, 0x35, 0x92, 0x4e, 0x78, 0x55, 0x5a, 0xa8, 0x99, 0xa3, 0xc6, - 0x91, 0x73, 0x24, 0x00, 0xc7, 0x38, 0x86, 0xc5, 0x66, 0xa1, 0xa3, 0xc5, 0xe6, 0x1f, 0x59, 0x90, - 0xba, 0xea, 0x8f, 0x80, 0xf3, 0x5c, 0x36, 0x39, 0xcf, 0xc7, 0xbb, 0x19, 0xcd, 0x1c, 0xa6, 0xf3, - 0x1f, 0xc7, 0xe0, 0x44, 0x8e, 0x27, 0xf6, 0x0e, 0x4c, 0x6c, 0x56, 0x89, 0x19, 0x7a, 0xa3, 0x5d, - 0x48, 0xba, 0xb6, 0x71, 0x3a, 0xe6, 0x8e, 0xef, 0xef, 0x95, 0x26, 0x52, 0x28, 0x38, 0xdd, 0x04, - 0xfa, 0xbc, 0x05, 0xc7, 0x9c, 0x3b, 0xe1, 0x22, 0x7d, 0x41, 0xb8, 0xd5, 0xb9, 0xba, 0x5f, 0xdd, - 0xa6, 0x8c, 0x99, 0xdc, 0x56, 0x2f, 0x66, 0x0a, 0xa3, 0x6f, 0x55, 0x52, 0xf8, 0x46, 0xf3, 0x53, - 0xfb, 0x7b, 0xa5, 0x63, 0x59, 0x58, 0x38, 0xb3, 0x2d, 0x84, 0x45, 0xc6, 0x2f, 0x27, 0xda, 0x6a, - 0x17, 0x1c, 0x26, 0xcb, 0x65, 0x9e, 0xb3, 0xc4, 0x12, 0x82, 0x15, 0x1d, 0xf4, 0x19, 0x18, 0xdc, - 0x94, 0x71, 0x20, 0x32, 0x58, 0xee, 0x78, 0x20, 0xdb, 0x47, 0xc7, 0xe0, 0x26, 0x30, 0x0a, 0x09, - 0xc7, 0x44, 0xd1, 0xeb, 0x50, 0xf4, 0x36, 0x42, 0x11, 0xa2, 0x2e, 0xdb, 0x12, 0xd7, 0xb4, 0x75, - 0xe6, 0x21, 0x98, 0x56, 0x97, 0x2a, 0x98, 0x56, 0x44, 0x57, 0xa1, 0x18, 0xdc, 0xae, 0x09, 0x4d, - 0x4a, 0xe6, 0x26, 0xc5, 0x73, 0x0b, 0x39, 0xbd, 0x62, 0x94, 0xf0, 0xdc, 0x02, 0xa6, 0x24, 0x50, - 0x19, 0x7a, 0x99, 0xfb, 0xb2, 0x60, 0x6d, 0x33, 0x9f, 0xf2, 0x6d, 0xc2, 0x00, 0x70, 0x8f, 0x44, - 0x86, 0x80, 0x39, 0x21, 0xb4, 0x06, 0x7d, 0x55, 0xd7, 0xab, 0x91, 0x40, 0xf0, 0xb2, 0x1f, 0xce, - 0xd4, 0x99, 0x30, 0x8c, 0x1c, 0x9a, 0x5c, 0x85, 0xc0, 0x30, 0xb0, 0xa0, 0xc5, 0xa8, 0x92, 0xe6, - 0xd6, 0x86, 0xbc, 0xb1, 0xb2, 0xa9, 0x92, 0xe6, 0xd6, 0x52, 0xa5, 0x2d, 0x55, 0x86, 0x81, 0x05, - 0x2d, 0xf4, 0x0a, 0x14, 0x36, 0xaa, 0xc2, 0x35, 0x39, 0x53, 0x79, 0x62, 0x46, 0xd1, 0x9a, 0xeb, - 0xdb, 0xdf, 0x2b, 0x15, 0x96, 0xe6, 0x71, 0x61, 0xa3, 0x8a, 0x56, 0xa1, 0x7f, 0x83, 0xc7, 0xdd, - 0x11, 0xfa, 0x91, 0x27, 0xb3, 0x43, 0x02, 0xa5, 0x42, 0xf3, 0x70, 0xef, 0x52, 0x01, 0xc0, 0x92, - 0x08, 0x4b, 0x40, 0xa5, 0xe2, 0x07, 0x89, 0xf0, 0xa5, 0x33, 0x87, 0x8b, 0xf9, 0xc4, 0x9f, 0x1a, - 0x71, 0x14, 0x22, 0xac, 0x51, 0xa4, 0xab, 0xda, 0xb9, 0xd7, 0x0a, 0x58, 0x6e, 0x0b, 0xa1, 0x1a, - 0xc9, 0x5c, 0xd5, 0xb3, 0x12, 0xa9, 0xdd, 0xaa, 0x56, 0x48, 0x38, 0x26, 0x8a, 0xb6, 0x61, 0x64, - 0x27, 0x6c, 0x6e, 0x11, 0xb9, 0xa5, 0x59, 0xd8, 0xbb, 0x1c, 0x6e, 0xf6, 0xa6, 0x40, 0x74, 0x83, - 0xa8, 0xe5, 0xd4, 0x53, 0xa7, 0x10, 0x7b, 0xd6, 0xdc, 0xd4, 0x89, 0x61, 0x93, 0x36, 0x1d, 0xfe, - 0x77, 0x5b, 0xfe, 0xed, 0xdd, 0x88, 0x88, 0xa8, 0xa3, 0x99, 0xc3, 0xff, 0x16, 0x47, 0x49, 0x0f, - 0xbf, 0x00, 0x60, 0x49, 0x04, 0xdd, 0x14, 0xc3, 0xc3, 0x4e, 0xcf, 0xf1, 0xfc, 0x90, 0xe6, 0xb3, - 0x12, 0x29, 0x67, 0x50, 0xd8, 0x69, 0x19, 0x93, 0x62, 0xa7, 0x64, 0x73, 0xcb, 0x8f, 0x7c, 0x2f, - 0x71, 0x42, 0x4f, 0xe4, 0x9f, 0x92, 0xe5, 0x0c, 0xfc, 0xf4, 0x29, 0x99, 0x85, 0x85, 0x33, 0xdb, - 0x42, 0x35, 0x18, 0x6d, 0xfa, 0x41, 0x74, 0xc7, 0x0f, 0xe4, 0xfa, 0x42, 0x6d, 0x04, 0xa5, 0x06, - 0xa6, 0x68, 0x91, 0x19, 0xe6, 0x98, 0x10, 0x9c, 0xa0, 0x89, 0x3e, 0x0e, 0xfd, 0x61, 0xd5, 0xa9, - 0x93, 0xe5, 0x1b, 0x53, 0x93, 0xf9, 0xd7, 0x4f, 0x85, 0xa3, 0xe4, 0xac, 0x2e, 0x1e, 0x36, 0x89, - 0xa3, 0x60, 0x49, 0x0e, 0x2d, 0x41, 0x2f, 0x4b, 0xec, 0xcc, 0x42, 0xe4, 0xe6, 0x44, 0x66, 0x4f, - 0xb9, 0xd5, 0xf0, 0xb3, 0x89, 0x15, 0x63, 0x5e, 0x9d, 0xee, 0x01, 0x21, 0x29, 0xf0, 0xc3, 0xa9, - 0xe3, 0xf9, 0x7b, 0x40, 0x08, 0x18, 0x6e, 0x54, 0xda, 0xed, 0x01, 0x85, 0x84, 0x63, 0xa2, 0xf4, - 0x64, 0xa6, 0xa7, 0xe9, 0x89, 0x36, 0x26, 0x93, 0xb9, 0x67, 0x29, 0x3b, 0x99, 0xe9, 0x49, 0x4a, - 0x49, 0xd8, 0x7f, 0x30, 0x90, 0xe6, 0x59, 0x98, 0x84, 0xe9, 0x3f, 0xb7, 0x52, 0x36, 0x13, 0x1f, - 0xe9, 0x56, 0xe0, 0xfd, 0x00, 0x1f, 0xae, 0x9f, 0xb7, 0xe0, 0x44, 0x33, 0xf3, 0x43, 0x04, 0x03, - 0xd0, 0x9d, 0xdc, 0x9c, 0x7f, 0xba, 0x0a, 0xa7, 0x9c, 0x0d, 0xc7, 0x39, 0x2d, 0x25, 0x85, 0x03, - 0xc5, 0xf7, 0x2c, 0x1c, 0x58, 0x81, 0x81, 0x2a, 0x7f, 0xc9, 0xc9, 0x34, 0x00, 0x5d, 0x05, 0x03, - 0x65, 0xac, 0x84, 0x78, 0x02, 0x6e, 0x60, 0x45, 0x02, 0xfd, 0xa4, 0x05, 0x67, 0x92, 0x5d, 0xc7, - 0x84, 0x81, 0x85, 0xc1, 0x24, 0x17, 0x6b, 0x2d, 0x89, 0xef, 0x4f, 0xf1, 0xff, 0x06, 0xf2, 0x41, - 0x27, 0x04, 0xdc, 0xbe, 0x31, 0xb4, 0x90, 0x21, 0x57, 0xeb, 0x33, 0x35, 0x8a, 0x5d, 0xc8, 0xd6, - 0x5e, 0x84, 0xe1, 0x86, 0xdf, 0xf2, 0x22, 0x61, 0xf7, 0x28, 0x8c, 0xa7, 0x98, 0xd1, 0xd0, 0x8a, - 0x56, 0x8e, 0x0d, 0xac, 0x84, 0x44, 0x6e, 0xe0, 0xbe, 0x25, 0x72, 0xef, 0xc0, 0xb0, 0xa7, 0xb9, - 0x04, 0xb4, 0x7b, 0xc1, 0x0a, 0xe9, 0xa2, 0x86, 0xcd, 0x7b, 0xa9, 0x97, 0x60, 0x83, 0x5a, 0x7b, - 0x69, 0x19, 0xbc, 0x37, 0x69, 0xd9, 0x91, 0x3e, 0x89, 0xed, 0x5f, 0x2f, 0x64, 0xbc, 0x18, 0xb8, - 0x54, 0xee, 0x35, 0x53, 0x2a, 0x77, 0x3e, 0x29, 0x95, 0x4b, 0xa9, 0xaa, 0x0c, 0x81, 0x5c, 0xf7, - 0x19, 0x25, 0xbb, 0x0e, 0xf0, 0xfc, 0xc3, 0x16, 0x9c, 0x64, 0xba, 0x0f, 0xda, 0xc0, 0x7b, 0xd6, - 0x77, 0x30, 0x93, 0xd4, 0xeb, 0xd9, 0xe4, 0x70, 0x5e, 0x3b, 0x76, 0x1d, 0xce, 0x75, 0xba, 0x77, - 0x99, 0x85, 0x6f, 0x4d, 0x19, 0x47, 0xc4, 0x16, 0xbe, 0xb5, 0xe5, 0x05, 0xcc, 0x20, 0xdd, 0x86, - 0x2f, 0xb4, 0xff, 0x7f, 0x0b, 0x8a, 0x65, 0xbf, 0x76, 0x04, 0x2f, 0xfa, 0x8f, 0x19, 0x2f, 0xfa, - 0x47, 0xb2, 0x6f, 0xfc, 0x5a, 0xae, 0xb2, 0x6f, 0x31, 0xa1, 0xec, 0x3b, 0x93, 0x47, 0xa0, 0xbd, - 0x6a, 0xef, 0x97, 0x8a, 0x30, 0x54, 0xf6, 0x6b, 0x6a, 0x9f, 0xfd, 0xaf, 0xf7, 0xe3, 0xc8, 0x93, - 0x9b, 0x7d, 0x4a, 0xa3, 0xcc, 0x2c, 0x7a, 0x65, 0xdc, 0x89, 0xef, 0x32, 0x7f, 0x9e, 0x5b, 0xc4, - 0xdd, 0xdc, 0x8a, 0x48, 0x2d, 0xf9, 0x39, 0x47, 0xe7, 0xcf, 0xf3, 0xed, 0x22, 0x8c, 0x25, 0x5a, - 0x47, 0x75, 0x18, 0xa9, 0xeb, 0xaa, 0x24, 0xb1, 0x4e, 0xef, 0x4b, 0x0b, 0x25, 0xfc, 0x21, 0xb4, - 0x22, 0x6c, 0x12, 0x47, 0x33, 0x00, 0x9e, 0x6e, 0x15, 0xae, 0x02, 0x15, 0x6b, 0x16, 0xe1, 0x1a, - 0x06, 0x7a, 0x09, 0x86, 0x22, 0xbf, 0xe9, 0xd7, 0xfd, 0xcd, 0xdd, 0x6b, 0x44, 0x46, 0xb6, 0x54, - 0x46, 0xc3, 0x6b, 0x31, 0x08, 0xeb, 0x78, 0xe8, 0x2e, 0x4c, 0x28, 0x22, 0x95, 0x07, 0xa0, 0x5e, - 0x63, 0x62, 0x93, 0xd5, 0x24, 0x45, 0x9c, 0x6e, 0x04, 0xbd, 0x02, 0xa3, 0xcc, 0x7a, 0x99, 0xd5, - 0xbf, 0x46, 0x76, 0x65, 0xc4, 0x63, 0xc6, 0x61, 0xaf, 0x18, 0x10, 0x9c, 0xc0, 0x44, 0xf3, 0x30, - 0xd1, 0x70, 0xc3, 0x44, 0xf5, 0x3e, 0x56, 0x9d, 0x75, 0x60, 0x25, 0x09, 0xc4, 0x69, 0x7c, 0xfb, - 0x57, 0xc5, 0x1c, 0x7b, 0x91, 0xfb, 0xc1, 0x76, 0x7c, 0x7f, 0x6f, 0xc7, 0x6f, 0x59, 0x30, 0x4e, - 0x5b, 0x67, 0x26, 0x99, 0x92, 0x91, 0x52, 0x39, 0x31, 0xac, 0x36, 0x39, 0x31, 0xce, 0xd3, 0x63, - 0xbb, 0xe6, 0xb7, 0x22, 0x21, 0x1d, 0xd5, 0xce, 0x65, 0x5a, 0x8a, 0x05, 0x54, 0xe0, 0x91, 0x20, - 0x10, 0x7e, 0xef, 0x3a, 0x1e, 0x09, 0x02, 0x2c, 0xa0, 0x32, 0x65, 0x46, 0x4f, 0x76, 0xca, 0x0c, - 0x1e, 0xf9, 0x5c, 0x58, 0xc1, 0x09, 0x96, 0x56, 0x8b, 0x7c, 0x2e, 0xcd, 0xe3, 0x62, 0x1c, 0xfb, - 0xeb, 0x45, 0x18, 0x2e, 0xfb, 0xb5, 0xd8, 0xb0, 0xe3, 0x45, 0xc3, 0xb0, 0xe3, 0x5c, 0xc2, 0xb0, - 0x63, 0x5c, 0xc7, 0xfd, 0xc0, 0x8c, 0xe3, 0x3b, 0x65, 0xc6, 0xf1, 0x87, 0x16, 0x9b, 0xb5, 0x85, - 0xd5, 0x0a, 0xb7, 0xf0, 0x45, 0x97, 0x60, 0x88, 0x9d, 0x70, 0x2c, 0xd0, 0x82, 0xb4, 0x76, 0x60, - 0x29, 0x2c, 0x57, 0xe3, 0x62, 0xac, 0xe3, 0xa0, 0x0b, 0x30, 0x10, 0x12, 0x27, 0xa8, 0x6e, 0xa9, - 0xe3, 0x5d, 0x98, 0x26, 0xf0, 0x32, 0xac, 0xa0, 0xe8, 0xad, 0x38, 0xe8, 0x76, 0x31, 0xdf, 0x5c, - 0x58, 0xef, 0x0f, 0xdf, 0x22, 0xf9, 0x91, 0xb6, 0xed, 0x5b, 0x80, 0xd2, 0xf8, 0x5d, 0xf8, 0x5f, - 0x95, 0xcc, 0xb0, 0xb0, 0x83, 0xa9, 0x90, 0xb0, 0xff, 0x62, 0xc1, 0x68, 0xd9, 0xaf, 0xd1, 0xad, - 0xfb, 0xbd, 0xb4, 0x4f, 0xf5, 0x8c, 0x03, 0x7d, 0x6d, 0x32, 0x0e, 0x3c, 0x06, 0xbd, 0x65, 0xbf, - 0xd6, 0x21, 0x74, 0xed, 0x7f, 0x63, 0x41, 0x7f, 0xd9, 0xaf, 0x1d, 0x81, 0xe2, 0xe5, 0x35, 0x53, - 0xf1, 0x72, 0x32, 0x67, 0xdd, 0xe4, 0xe8, 0x5a, 0xfe, 0xa4, 0x07, 0x46, 0x68, 0x3f, 0xfd, 0x4d, - 0x39, 0x95, 0xc6, 0xb0, 0x59, 0x5d, 0x0c, 0x1b, 0x7d, 0x06, 0xf8, 0xf5, 0xba, 0x7f, 0x27, 0x39, - 0xad, 0x4b, 0xac, 0x14, 0x0b, 0x28, 0x7a, 0x16, 0x06, 0x9a, 0x01, 0xd9, 0x71, 0x7d, 0xc1, 0x5f, - 0x6b, 0x6a, 0xac, 0xb2, 0x28, 0xc7, 0x0a, 0x83, 0x3e, 0xbc, 0x43, 0xd7, 0xa3, 0xbc, 0x44, 0xd5, - 0xf7, 0x6a, 0x5c, 0x37, 0x51, 0x14, 0x69, 0xb1, 0xb4, 0x72, 0x6c, 0x60, 0xa1, 0x5b, 0x30, 0xc8, - 0xfe, 0xb3, 0x63, 0xa7, 0xf7, 0xd0, 0xc7, 0x8e, 0x48, 0x14, 0x2c, 0x08, 0xe0, 0x98, 0x16, 0x7a, - 0x1e, 0x20, 0x92, 0xa9, 0x65, 0x42, 0x11, 0xc2, 0x54, 0xbd, 0x45, 0x54, 0xd2, 0x99, 0x10, 0x6b, - 0x58, 0xe8, 0x19, 0x18, 0x8c, 0x1c, 0xb7, 0x7e, 0xdd, 0xf5, 0x98, 0xfe, 0x9e, 0xf6, 0x5f, 0xe4, - 0xeb, 0x15, 0x85, 0x38, 0x86, 0x53, 0x5e, 0x90, 0xc5, 0x84, 0x9a, 0xdb, 0x8d, 0x44, 0x6a, 0xba, - 0x22, 0xe7, 0x05, 0xaf, 0xab, 0x52, 0xac, 0x61, 0xa0, 0x2d, 0x38, 0xed, 0x7a, 0x2c, 0x85, 0x14, - 0xa9, 0x6c, 0xbb, 0xcd, 0xb5, 0xeb, 0x95, 0x9b, 0x24, 0x70, 0x37, 0x76, 0xe7, 0x9c, 0xea, 0x36, - 0xf1, 0x64, 0x42, 0xfc, 0xc7, 0x45, 0x17, 0x4f, 0x2f, 0xb7, 0xc1, 0xc5, 0x6d, 0x29, 0x21, 0x9b, - 0x6e, 0xc7, 0x80, 0x38, 0x0d, 0x21, 0x13, 0xe0, 0xe9, 0x67, 0x58, 0x09, 0x16, 0x10, 0xfb, 0x05, - 0xb6, 0x27, 0x6e, 0x54, 0xd0, 0xd3, 0xc6, 0xf1, 0x72, 0x42, 0x3f, 0x5e, 0x0e, 0xf6, 0x4a, 0x7d, - 0x37, 0x2a, 0x5a, 0x7c, 0xa0, 0xcb, 0x70, 0xbc, 0xec, 0xd7, 0xca, 0x7e, 0x10, 0x2d, 0xf9, 0xc1, - 0x1d, 0x27, 0xa8, 0xc9, 0x25, 0x58, 0x92, 0x11, 0x92, 0xe8, 0x19, 0xdb, 0xcb, 0x4f, 0x20, 0x23, - 0xfa, 0xd1, 0x0b, 0x8c, 0xab, 0x3b, 0xa4, 0x43, 0x6a, 0x95, 0xf1, 0x17, 0x2a, 0x51, 0xdb, 0x15, - 0x27, 0x22, 0xe8, 0x06, 0x8c, 0x54, 0xf5, 0xab, 0x56, 0x54, 0x7f, 0x4a, 0x5e, 0x76, 0xc6, 0x3d, - 0x9c, 0x79, 0x37, 0x9b, 0xf5, 0xed, 0x6f, 0x5a, 0xa2, 0x15, 0x2e, 0xad, 0xe0, 0x76, 0xaf, 0x9d, - 0xcf, 0xdc, 0x79, 0x98, 0x08, 0xf4, 0x2a, 0x9a, 0xfd, 0xd8, 0x71, 0x9e, 0xf9, 0x26, 0x01, 0xc4, - 0x69, 0x7c, 0xf4, 0x49, 0x38, 0x65, 0x14, 0x4a, 0x55, 0xba, 0x96, 0x7f, 0x9a, 0xc9, 0x73, 0x70, - 0x1e, 0x12, 0xce, 0xaf, 0x6f, 0xff, 0x20, 0x9c, 0x48, 0x7e, 0x97, 0x90, 0xb0, 0xdc, 0xe7, 0xd7, - 0x15, 0x0e, 0xf7, 0x75, 0xf6, 0x4b, 0x30, 0x41, 0x9f, 0xde, 0x8a, 0x8d, 0x64, 0xf3, 0xd7, 0x39, - 0x08, 0xd5, 0x6f, 0x0e, 0xb0, 0x6b, 0x30, 0x91, 0x7d, 0x0d, 0x7d, 0x1a, 0x46, 0x43, 0xc2, 0x22, - 0xaf, 0x49, 0xc9, 0x5e, 0x1b, 0x6f, 0xf2, 0xca, 0xa2, 0x8e, 0xc9, 0x5f, 0x2f, 0x66, 0x19, 0x4e, - 0x50, 0x43, 0x0d, 0x18, 0xbd, 0xe3, 0x7a, 0x35, 0xff, 0x4e, 0x28, 0xe9, 0x0f, 0xe4, 0xab, 0x09, - 0x6e, 0x71, 0xcc, 0x44, 0x1f, 0x8d, 0xe6, 0x6e, 0x19, 0xc4, 0x70, 0x82, 0x38, 0x3d, 0x6a, 0x82, - 0x96, 0x37, 0x1b, 0xae, 0x87, 0x24, 0x10, 0x71, 0xe1, 0xd8, 0x51, 0x83, 0x65, 0x21, 0x8e, 0xe1, - 0xf4, 0xa8, 0x61, 0x7f, 0x98, 0x3b, 0x3a, 0x3b, 0xcb, 0xc4, 0x51, 0x83, 0x55, 0x29, 0xd6, 0x30, - 0xe8, 0x51, 0xcc, 0xfe, 0xad, 0xfa, 0x1e, 0xf6, 0xfd, 0x48, 0x1e, 0xde, 0x2c, 0x55, 0xa5, 0x56, - 0x8e, 0x0d, 0xac, 0x9c, 0x28, 0x74, 0x3d, 0x87, 0x8d, 0x42, 0x87, 0xa2, 0x36, 0x1e, 0xf8, 0x3c, - 0x1a, 0xf2, 0xe5, 0x76, 0x1e, 0xf8, 0x07, 0xf7, 0xe5, 0x9d, 0x4f, 0x79, 0x81, 0x0d, 0x31, 0x40, - 0xbd, 0x3c, 0xcc, 0x1e, 0x53, 0x64, 0x56, 0xf8, 0xe8, 0x48, 0x18, 0x5a, 0x84, 0xfe, 0x70, 0x37, - 0xac, 0x46, 0xf5, 0xb0, 0x5d, 0x3a, 0xd2, 0x0a, 0x43, 0xd1, 0xb2, 0x61, 0xf3, 0x2a, 0x58, 0xd6, - 0x45, 0x55, 0x98, 0x14, 0x14, 0xe7, 0xb7, 0x1c, 0x4f, 0x25, 0x49, 0xe4, 0x16, 0x8b, 0x97, 0xf6, - 0xf7, 0x4a, 0x93, 0xa2, 0x65, 0x1d, 0x7c, 0xb0, 0x57, 0xa2, 0x5b, 0x32, 0x03, 0x82, 0xb3, 0xa8, - 0xf1, 0x25, 0x5f, 0xad, 0xfa, 0x8d, 0x66, 0x39, 0xf0, 0x37, 0xdc, 0x3a, 0x69, 0xa7, 0x0c, 0xae, - 0x18, 0x98, 0x62, 0xc9, 0x1b, 0x65, 0x38, 0x41, 0x0d, 0xdd, 0x86, 0x31, 0xa7, 0xd9, 0x9c, 0x0d, - 0x1a, 0x7e, 0x20, 0x1b, 0x18, 0xca, 0xd7, 0x2a, 0xcc, 0x9a, 0xa8, 0x3c, 0x47, 0x62, 0xa2, 0x10, - 0x27, 0x09, 0xd2, 0x81, 0x12, 0x1b, 0xcd, 0x18, 0xa8, 0x91, 0x78, 0xa0, 0xc4, 0xbe, 0xcc, 0x18, - 0xa8, 0x0c, 0x08, 0xce, 0xa2, 0x66, 0xff, 0x00, 0x63, 0xfc, 0x2b, 0xee, 0xa6, 0xc7, 0x9c, 0xe3, - 0x50, 0x03, 0x46, 0x9a, 0xec, 0xd8, 0x17, 0xf9, 0xcb, 0xc4, 0x51, 0xf1, 0x62, 0x97, 0xc2, 0xcb, - 0x3b, 0x2c, 0x03, 0xab, 0x61, 0xc4, 0x5a, 0xd6, 0xc9, 0x61, 0x93, 0xba, 0xfd, 0x8b, 0xd3, 0x8c, - 0x75, 0xac, 0x70, 0x89, 0x64, 0xbf, 0x70, 0x55, 0x14, 0x32, 0x88, 0xe9, 0x7c, 0xd9, 0x7f, 0xbc, - 0xbe, 0x84, 0xbb, 0x23, 0x96, 0x75, 0xd1, 0xa7, 0x60, 0x94, 0x3e, 0xe9, 0x15, 0xfb, 0x16, 0x4e, - 0x1d, 0xcb, 0x8f, 0x81, 0xa5, 0xb0, 0xf4, 0xdc, 0x86, 0x7a, 0x65, 0x9c, 0x20, 0x86, 0xde, 0x62, - 0x76, 0x9d, 0x92, 0x74, 0xa1, 0x1b, 0xd2, 0xba, 0x09, 0xa7, 0x24, 0xab, 0x11, 0x41, 0x2d, 0x98, - 0x4c, 0x67, 0x70, 0x0e, 0xa7, 0xec, 0xfc, 0xb7, 0x51, 0x3a, 0x09, 0x73, 0x9c, 0x84, 0x2e, 0x0d, - 0x0b, 0x71, 0x16, 0x7d, 0x74, 0x3d, 0x99, 0x5f, 0xb7, 0x68, 0x68, 0x0d, 0x52, 0x39, 0x76, 0x47, - 0xda, 0xa6, 0xd6, 0xdd, 0x84, 0x33, 0x5a, 0x8a, 0xd2, 0x2b, 0x81, 0xc3, 0xec, 0x8a, 0x5c, 0x76, - 0x1b, 0x69, 0x4c, 0xed, 0xa3, 0xfb, 0x7b, 0xa5, 0x33, 0x6b, 0xed, 0x10, 0x71, 0x7b, 0x3a, 0xe8, - 0x06, 0x1c, 0xe7, 0x11, 0x5c, 0x16, 0x88, 0x53, 0xab, 0xbb, 0x9e, 0xe2, 0x9a, 0xf9, 0xd9, 0x75, - 0x6a, 0x7f, 0xaf, 0x74, 0x7c, 0x36, 0x0b, 0x01, 0x67, 0xd7, 0x43, 0xaf, 0xc1, 0x60, 0xcd, 0x93, - 0xa7, 0x6c, 0x9f, 0x91, 0x05, 0x76, 0x70, 0x61, 0xb5, 0xa2, 0xbe, 0x3f, 0xfe, 0x83, 0xe3, 0x0a, - 0x68, 0x93, 0xab, 0xad, 0x94, 0xac, 0xb1, 0x3f, 0x15, 0xd8, 0x33, 0x29, 0x8e, 0x37, 0x42, 0x22, - 0x70, 0x7d, 0xad, 0x72, 0xb9, 0x33, 0xa2, 0x25, 0x18, 0x84, 0xd1, 0x9b, 0x80, 0x44, 0xb6, 0xa1, - 0xd9, 0x2a, 0x4b, 0x8e, 0xa7, 0xd9, 0x92, 0x2a, 0x11, 0x42, 0x25, 0x85, 0x81, 0x33, 0x6a, 0xa1, - 0xab, 0xf4, 0x78, 0xd4, 0x4b, 0xc5, 0xf1, 0xab, 0x72, 0x8d, 0x2f, 0x90, 0x66, 0x40, 0x98, 0xf9, - 0xa3, 0x49, 0x11, 0x27, 0xea, 0xa1, 0x1a, 0x9c, 0x76, 0x5a, 0x91, 0xcf, 0x34, 0x82, 0x26, 0xea, - 0x9a, 0xbf, 0x4d, 0x3c, 0xa6, 0x8c, 0x1f, 0x60, 0x01, 0x43, 0x4f, 0xcf, 0xb6, 0xc1, 0xc3, 0x6d, - 0xa9, 0xd0, 0xe7, 0x14, 0x1d, 0x0b, 0x4d, 0x59, 0x67, 0x78, 0x77, 0x73, 0x0d, 0xb6, 0xc4, 0x40, - 0x2f, 0xc1, 0xd0, 0x96, 0x1f, 0x46, 0xab, 0x24, 0xba, 0xe3, 0x07, 0xdb, 0x22, 0xbd, 0x41, 0x9c, - 0x52, 0x26, 0x06, 0x61, 0x1d, 0x0f, 0x3d, 0x05, 0xfd, 0xcc, 0x54, 0x6c, 0x79, 0x81, 0xdd, 0xb5, - 0x03, 0xf1, 0x19, 0x73, 0x95, 0x17, 0x63, 0x09, 0x97, 0xa8, 0xcb, 0xe5, 0x79, 0x76, 0x1c, 0x27, - 0x50, 0x97, 0xcb, 0xf3, 0x58, 0xc2, 0xe9, 0x72, 0x0d, 0xb7, 0x9c, 0x80, 0x94, 0x03, 0xbf, 0x4a, - 0x42, 0x2d, 0x91, 0xd1, 0x23, 0x3c, 0x79, 0x03, 0x5d, 0xae, 0x95, 0x2c, 0x04, 0x9c, 0x5d, 0x0f, - 0x91, 0x74, 0x7a, 0xde, 0xd1, 0x7c, 0x55, 0x69, 0x9a, 0x1d, 0xec, 0x32, 0x43, 0xaf, 0x07, 0xe3, - 0x2a, 0x31, 0x30, 0x4f, 0xd7, 0x10, 0x4e, 0x8d, 0xb1, 0xb5, 0xdd, 0x7d, 0xae, 0x07, 0xa5, 0x7c, - 0x5e, 0x4e, 0x50, 0xc2, 0x29, 0xda, 0x46, 0x44, 0xda, 0xf1, 0x8e, 0x11, 0x69, 0x2f, 0xc2, 0x60, - 0xd8, 0xba, 0x5d, 0xf3, 0x1b, 0x8e, 0xeb, 0x31, 0x8b, 0x1b, 0xed, 0xe1, 0x5e, 0x91, 0x00, 0x1c, - 0xe3, 0xa0, 0x25, 0x18, 0x70, 0xa4, 0x66, 0x19, 0xe5, 0x07, 0xdb, 0x53, 0xfa, 0x64, 0x1e, 0x7f, - 0x4a, 0xea, 0x92, 0x55, 0x5d, 0xf4, 0x2a, 0x8c, 0x88, 0x80, 0x1e, 0x22, 0x97, 0xfe, 0xa4, 0xe9, - 0xbe, 0x5c, 0xd1, 0x81, 0xd8, 0xc4, 0x45, 0xeb, 0x30, 0x14, 0xf9, 0x75, 0xe6, 0x83, 0x4b, 0xb9, - 0xe4, 0x13, 0xf9, 0x31, 0x71, 0xd7, 0x14, 0x9a, 0xae, 0xf3, 0x50, 0x55, 0xb1, 0x4e, 0x07, 0xad, - 0xf1, 0xf5, 0xce, 0xd2, 0x16, 0x91, 0x50, 0x24, 0x63, 0x3f, 0x93, 0x67, 0x2e, 0xc9, 0xd0, 0xcc, - 0xed, 0x20, 0x6a, 0x62, 0x9d, 0x0c, 0xba, 0x02, 0x13, 0xcd, 0xc0, 0xf5, 0xd9, 0x9a, 0x50, 0x9a, - 0xf2, 0x29, 0x33, 0x49, 0x69, 0x39, 0x89, 0x80, 0xd3, 0x75, 0x58, 0x3c, 0x16, 0x51, 0x38, 0x75, - 0x8a, 0x27, 0x5a, 0xe3, 0x72, 0x10, 0x5e, 0x86, 0x15, 0x14, 0xad, 0xb0, 0x93, 0x98, 0x8b, 0xf0, - 0xa6, 0xa6, 0xf3, 0xbd, 0xfc, 0x75, 0x51, 0x1f, 0xe7, 0xfd, 0xd5, 0x5f, 0x1c, 0x53, 0x40, 0x35, - 0x2d, 0xbf, 0x39, 0x7d, 0x41, 0x85, 0x53, 0xa7, 0xdb, 0xd8, 0xeb, 0x26, 0x9e, 0xcb, 0x31, 0x43, - 0x60, 0x14, 0x87, 0x38, 0x41, 0x13, 0xbd, 0x01, 0xe3, 0x22, 0x58, 0x41, 0x3c, 0x4c, 0x67, 0x62, - 0x9f, 0x26, 0x9c, 0x80, 0xe1, 0x14, 0x36, 0x4f, 0x74, 0xe6, 0xdc, 0xae, 0x13, 0x71, 0xf4, 0x5d, - 0x77, 0xbd, 0xed, 0x70, 0xea, 0x2c, 0x3b, 0x1f, 0x44, 0xa2, 0xb3, 0x24, 0x14, 0x67, 0xd4, 0x40, - 0x6b, 0x30, 0xde, 0x0c, 0x08, 0x69, 0xb0, 0x77, 0x92, 0xb8, 0xcf, 0x4a, 0x3c, 0x1c, 0x11, 0xed, - 0x49, 0x39, 0x01, 0x3b, 0xc8, 0x28, 0xc3, 0x29, 0x0a, 0xe8, 0x0e, 0x0c, 0xf8, 0x3b, 0x24, 0xd8, - 0x22, 0x4e, 0x6d, 0xea, 0x5c, 0x1b, 0x4f, 0x3b, 0x71, 0xb9, 0xdd, 0x10, 0xb8, 0x09, 0x43, 0x24, - 0x59, 0xdc, 0xd9, 0x10, 0x49, 0x36, 0x86, 0xfe, 0x0b, 0x0b, 0x4e, 0x49, 0xd5, 0x5e, 0xa5, 0x49, - 0x47, 0x7d, 0xde, 0xf7, 0xc2, 0x28, 0xe0, 0x01, 0x74, 0x1e, 0xcd, 0x0f, 0x2a, 0xb3, 0x96, 0x53, - 0x49, 0x69, 0x11, 0x4e, 0xe5, 0x61, 0x84, 0x38, 0xbf, 0x45, 0xfa, 0xb2, 0x0f, 0x49, 0x24, 0x0f, - 0xa3, 0xd9, 0x70, 0xe9, 0xad, 0x85, 0xd5, 0xa9, 0xc7, 0x78, 0xf4, 0x1f, 0xba, 0x19, 0x2a, 0x49, - 0x20, 0x4e, 0xe3, 0xa3, 0x4b, 0x50, 0xf0, 0xc3, 0xa9, 0xc7, 0xdb, 0xa4, 0xc4, 0xf7, 0x6b, 0x37, - 0x2a, 0xdc, 0x20, 0xf5, 0x46, 0x05, 0x17, 0xfc, 0x50, 0x26, 0x1b, 0xa3, 0xcf, 0xd9, 0x70, 0xea, - 0x09, 0x2e, 0x73, 0x96, 0xc9, 0xc6, 0x58, 0x21, 0x8e, 0xe1, 0x68, 0x0b, 0xc6, 0x42, 0x43, 0x6c, - 0x10, 0x4e, 0x9d, 0x67, 0x23, 0xf5, 0x44, 0xde, 0xa4, 0x19, 0xd8, 0x5a, 0x16, 0x20, 0x93, 0x0a, - 0x4e, 0x92, 0xe5, 0xbb, 0x4b, 0x13, 0x5c, 0x84, 0x53, 0x4f, 0x76, 0xd8, 0x5d, 0x1a, 0xb2, 0xbe, - 0xbb, 0x74, 0x1a, 0x38, 0x41, 0x13, 0xad, 0xeb, 0x6e, 0x8c, 0x17, 0xf2, 0x8d, 0x1b, 0x33, 0x1d, - 0x18, 0x47, 0xf2, 0x9c, 0x17, 0xa7, 0xbf, 0x0f, 0x26, 0x52, 0x5c, 0xd8, 0x61, 0x7c, 0x3a, 0xa6, - 0xb7, 0x61, 0xc4, 0x58, 0xe9, 0x0f, 0xd5, 0xe4, 0xe7, 0xcf, 0x06, 0x61, 0x50, 0x99, 0x62, 0xa0, - 0x8b, 0xa6, 0x95, 0xcf, 0xa9, 0xa4, 0x95, 0xcf, 0x40, 0xd9, 0xaf, 0x19, 0x86, 0x3d, 0x6b, 0x19, - 0xb1, 0x72, 0xf3, 0xce, 0xd5, 0xee, 0x1d, 0xcf, 0x34, 0xf5, 0x52, 0xb1, 0x6b, 0x73, 0xa1, 0x9e, - 0xb6, 0x1a, 0xab, 0x2b, 0x30, 0xe1, 0xf9, 0x8c, 0xf5, 0x27, 0x35, 0xc9, 0xd7, 0x31, 0xf6, 0x6d, - 0x50, 0x8f, 0xe5, 0x96, 0x40, 0xc0, 0xe9, 0x3a, 0xb4, 0x41, 0xce, 0x7f, 0x25, 0x55, 0x64, 0x9c, - 0x3d, 0xc3, 0x02, 0x4a, 0x9f, 0x9c, 0xfc, 0x57, 0x38, 0x35, 0x9e, 0xff, 0xe4, 0xe4, 0x95, 0x92, - 0x3c, 0x5e, 0x28, 0x79, 0x3c, 0xa6, 0x11, 0x6a, 0xfa, 0xb5, 0xe5, 0xb2, 0x78, 0x3d, 0x68, 0x51, - 0xec, 0x6b, 0xcb, 0x65, 0xcc, 0x61, 0x68, 0x16, 0xfa, 0xd8, 0x0f, 0x19, 0x23, 0x27, 0x6f, 0xf7, - 0x2f, 0x97, 0xb5, 0x1c, 0xaa, 0xac, 0x02, 0x16, 0x15, 0x99, 0xc4, 0x9f, 0x3e, 0xb9, 0x98, 0xc4, - 0xbf, 0xff, 0x3e, 0x25, 0xfe, 0x92, 0x00, 0x8e, 0x69, 0xa1, 0xbb, 0x70, 0xdc, 0x78, 0xe6, 0x2a, - 0x4f, 0x3c, 0xc8, 0x37, 0x06, 0x48, 0x20, 0xcf, 0x9d, 0x11, 0x9d, 0x3e, 0xbe, 0x9c, 0x45, 0x09, - 0x67, 0x37, 0x80, 0xea, 0x30, 0x51, 0x4d, 0xb5, 0x3a, 0xd0, 0x7d, 0xab, 0x6a, 0x5d, 0xa4, 0x5b, - 0x4c, 0x13, 0x46, 0xaf, 0xc2, 0xc0, 0xbb, 0x3e, 0x37, 0xdc, 0x13, 0x2f, 0x1e, 0x19, 0x05, 0x66, - 0xe0, 0xad, 0x1b, 0x15, 0x56, 0x7e, 0xb0, 0x57, 0x1a, 0x2a, 0xfb, 0x35, 0xf9, 0x17, 0xab, 0x0a, - 0xe8, 0xc7, 0x2c, 0x98, 0x4e, 0xbf, 0xa3, 0x55, 0xa7, 0x47, 0xba, 0xef, 0xb4, 0x2d, 0x1a, 0x9d, - 0x5e, 0xcc, 0x25, 0x87, 0xdb, 0x34, 0x85, 0x3e, 0x4a, 0xf7, 0x53, 0xe8, 0xde, 0x23, 0x22, 0x01, - 0xfd, 0xa3, 0xf1, 0x7e, 0xa2, 0xa5, 0x07, 0x7b, 0xa5, 0x31, 0x7e, 0xe0, 0xba, 0xf7, 0x54, 0xbc, - 0x7d, 0x5e, 0x01, 0xfd, 0x20, 0x1c, 0x0f, 0xd2, 0x72, 0x6d, 0x22, 0x79, 0xfb, 0xa7, 0xbb, 0x39, - 0xbc, 0x93, 0x13, 0x8e, 0xb3, 0x08, 0xe2, 0xec, 0x76, 0xec, 0xdf, 0xb3, 0x98, 0x3e, 0x43, 0x74, - 0x8b, 0x84, 0xad, 0x7a, 0x74, 0x04, 0xc6, 0x72, 0x8b, 0x86, 0x3d, 0xc1, 0x7d, 0x5b, 0xbb, 0xfd, - 0x2f, 0x16, 0xb3, 0x76, 0x3b, 0x42, 0xbf, 0xbd, 0xb7, 0x60, 0x20, 0x12, 0xad, 0x89, 0xae, 0xe7, - 0x59, 0xe6, 0xc8, 0x4e, 0x31, 0x8b, 0x3f, 0xf5, 0x76, 0x92, 0xa5, 0x58, 0x91, 0xb1, 0xff, 0x47, - 0x3e, 0x03, 0x12, 0x72, 0x04, 0x6a, 0xdb, 0x05, 0x53, 0x6d, 0x5b, 0xea, 0xf0, 0x05, 0x39, 0xea, - 0xdb, 0xff, 0xc1, 0xec, 0x37, 0x93, 0x19, 0xbe, 0xdf, 0xcd, 0x2c, 0xed, 0x2f, 0x5a, 0x00, 0x71, - 0x82, 0x93, 0x2e, 0x12, 0x4e, 0x5f, 0xa6, 0xaf, 0x25, 0x3f, 0xf2, 0xab, 0x7e, 0x5d, 0xa8, 0x8d, - 0x4e, 0xc7, 0x9a, 0x63, 0x5e, 0x7e, 0xa0, 0xfd, 0xc6, 0x0a, 0x1b, 0x95, 0x64, 0xc4, 0xe1, 0x62, - 0x6c, 0xcb, 0x60, 0x44, 0x1b, 0xfe, 0x8a, 0x05, 0xc7, 0xb2, 0x9c, 0x40, 0xe8, 0xdb, 0x9b, 0x4b, - 0x4f, 0x95, 0x09, 0xac, 0x9a, 0xcd, 0x9b, 0xa2, 0x1c, 0x2b, 0x8c, 0xae, 0x33, 0x79, 0x1f, 0x2e, - 0xf9, 0xc6, 0x0d, 0x18, 0x29, 0x07, 0x44, 0xe3, 0x2f, 0x5e, 0x8f, 0xf3, 0x02, 0x0d, 0xce, 0x3d, - 0x7b, 0xe8, 0xc8, 0x4a, 0xf6, 0x57, 0x0b, 0x70, 0x8c, 0x1b, 0x72, 0xcd, 0xee, 0xf8, 0x6e, 0xad, - 0xec, 0xd7, 0x84, 0xeb, 0xee, 0xdb, 0x30, 0xdc, 0xd4, 0x44, 0xde, 0xed, 0x02, 0xc9, 0xeb, 0xa2, - 0xf1, 0x58, 0x48, 0xa7, 0x97, 0x62, 0x83, 0x16, 0xaa, 0xc1, 0x30, 0xd9, 0x71, 0xab, 0xca, 0x1a, - 0xa8, 0x70, 0xe8, 0x4b, 0x5a, 0xb5, 0xb2, 0xa8, 0xd1, 0xc1, 0x06, 0xd5, 0xae, 0xcd, 0xaf, 0x35, - 0x16, 0xad, 0xa7, 0x83, 0x05, 0xd0, 0xcf, 0x5a, 0x70, 0x32, 0x27, 0xec, 0x3c, 0x6d, 0xee, 0x0e, - 0x33, 0x99, 0x13, 0xcb, 0x56, 0x35, 0xc7, 0x0d, 0xe9, 0xb0, 0x80, 0xa2, 0x8f, 0x03, 0x34, 0xe3, - 0x94, 0x9b, 0x1d, 0xe2, 0x73, 0x1b, 0x91, 0x7a, 0xb5, 0xa0, 0xab, 0x2a, 0x33, 0xa7, 0x46, 0xcb, - 0xfe, 0x4a, 0x0f, 0xf4, 0x32, 0xc3, 0x2b, 0x54, 0x86, 0xfe, 0x2d, 0x1e, 0x13, 0xb0, 0xed, 0xbc, - 0x51, 0x5c, 0x19, 0x64, 0x30, 0x9e, 0x37, 0xad, 0x14, 0x4b, 0x32, 0x68, 0x05, 0x26, 0x79, 0x3a, - 0xd1, 0xfa, 0x02, 0xa9, 0x3b, 0xbb, 0x52, 0x9a, 0x5c, 0x60, 0x9f, 0xaa, 0xa4, 0xea, 0xcb, 0x69, - 0x14, 0x9c, 0x55, 0x0f, 0xbd, 0x0e, 0xa3, 0xf4, 0x75, 0xef, 0xb7, 0x22, 0x49, 0x89, 0xe7, 0xef, - 0x54, 0x0f, 0x9e, 0x35, 0x03, 0x8a, 0x13, 0xd8, 0xe8, 0x55, 0x18, 0x69, 0xa6, 0xe4, 0xe6, 0xbd, - 0xb1, 0x80, 0xc9, 0x94, 0x95, 0x9b, 0xb8, 0xcc, 0x0f, 0xa4, 0xc5, 0xbc, 0x5e, 0xd6, 0xb6, 0x02, - 0x12, 0x6e, 0xf9, 0xf5, 0x1a, 0xe3, 0x80, 0x7b, 0x35, 0x3f, 0x90, 0x04, 0x1c, 0xa7, 0x6a, 0x50, - 0x2a, 0x1b, 0x8e, 0x5b, 0x6f, 0x05, 0x24, 0xa6, 0xd2, 0x67, 0x52, 0x59, 0x4a, 0xc0, 0x71, 0xaa, - 0x46, 0x67, 0x85, 0x40, 0xff, 0x83, 0x51, 0x08, 0xd8, 0xbf, 0x5c, 0x00, 0x63, 0x6a, 0xbf, 0x87, - 0xf3, 0x8a, 0xbe, 0x06, 0x3d, 0x9b, 0x41, 0xb3, 0x2a, 0x8c, 0x0c, 0x33, 0xbf, 0xec, 0x0a, 0x2e, - 0xcf, 0xeb, 0x5f, 0x46, 0xff, 0x63, 0x56, 0x8b, 0xee, 0xf1, 0xe3, 0xe5, 0xc0, 0xa7, 0x97, 0x9c, - 0x0c, 0x1b, 0xaa, 0xdc, 0xad, 0xfa, 0xe5, 0x1b, 0xbb, 0x4d, 0x80, 0x6d, 0xe1, 0x33, 0xc2, 0x29, - 0x18, 0xf6, 0x78, 0x15, 0xf1, 0xc2, 0x96, 0x54, 0xd0, 0x25, 0x18, 0x12, 0xa9, 0x1e, 0x99, 0x57, - 0x10, 0xdf, 0x4c, 0xcc, 0x7e, 0x70, 0x21, 0x2e, 0xc6, 0x3a, 0x8e, 0xfd, 0xe3, 0x05, 0x98, 0xcc, - 0x70, 0xeb, 0xe4, 0xd7, 0xc8, 0xa6, 0x1b, 0x46, 0xc1, 0x6e, 0xf2, 0x72, 0xc2, 0xa2, 0x1c, 0x2b, - 0x0c, 0x7a, 0x56, 0xf1, 0x8b, 0x2a, 0x79, 0x39, 0x09, 0xb7, 0x29, 0x01, 0x3d, 0xdc, 0xe5, 0x44, - 0xaf, 0xed, 0x56, 0x48, 0x64, 0x2c, 0x7f, 0x75, 0x6d, 0x33, 0x63, 0x03, 0x06, 0xa1, 0x4f, 0xc0, - 0x4d, 0xa5, 0x41, 0xd7, 0x9e, 0x80, 0x5c, 0x87, 0xce, 0x61, 0xb4, 0x73, 0x11, 0xf1, 0x1c, 0x2f, - 0x12, 0x0f, 0xc5, 0x38, 0xc6, 0x33, 0x2b, 0xc5, 0x02, 0x6a, 0x7f, 0xb9, 0x08, 0xa7, 0x72, 0x1d, - 0xbd, 0x69, 0xd7, 0x1b, 0xbe, 0xe7, 0x46, 0xbe, 0x32, 0xcc, 0xe4, 0x71, 0x9d, 0x49, 0x73, 0x6b, - 0x45, 0x94, 0x63, 0x85, 0x81, 0xce, 0x43, 0x2f, 0x93, 0xb5, 0x27, 0xd3, 0xbc, 0xe1, 0xb9, 0x05, - 0x1e, 0x31, 0x93, 0x83, 0xb5, 0x5b, 0xbd, 0xd8, 0xf6, 0x56, 0x7f, 0x8c, 0x72, 0x30, 0x7e, 0x3d, - 0x79, 0xa1, 0xd0, 0xee, 0xfa, 0x7e, 0x1d, 0x33, 0x20, 0x7a, 0x42, 0x8c, 0x57, 0xc2, 0x12, 0x11, - 0x3b, 0x35, 0x3f, 0xd4, 0x06, 0xed, 0x29, 0xe8, 0xdf, 0x26, 0xbb, 0x81, 0xeb, 0x6d, 0x26, 0x2d, - 0x54, 0xaf, 0xf1, 0x62, 0x2c, 0xe1, 0x66, 0x56, 0xf3, 0xfe, 0x07, 0x91, 0xd5, 0x5c, 0x5f, 0x01, - 0x03, 0x1d, 0xd9, 0x93, 0x9f, 0x28, 0xc2, 0x18, 0x9e, 0x5b, 0xf8, 0x60, 0x22, 0xd6, 0xd3, 0x13, - 0xf1, 0x20, 0x92, 0x7f, 0x1f, 0x6e, 0x36, 0x7e, 0xdb, 0x82, 0x31, 0x96, 0x70, 0x52, 0x44, 0x69, - 0x71, 0x7d, 0xef, 0x08, 0x9e, 0x02, 0x8f, 0x41, 0x6f, 0x40, 0x1b, 0x15, 0x33, 0xa8, 0xf6, 0x38, - 0xeb, 0x09, 0xe6, 0x30, 0x74, 0x1a, 0x7a, 0x58, 0x17, 0xe8, 0xe4, 0x0d, 0xf3, 0x23, 0x78, 0xc1, - 0x89, 0x1c, 0xcc, 0x4a, 0x59, 0xbc, 0x48, 0x4c, 0x9a, 0x75, 0x97, 0x77, 0x3a, 0xb6, 0x84, 0x78, - 0x7f, 0x84, 0x80, 0xc9, 0xec, 0xda, 0x7b, 0x8b, 0x17, 0x99, 0x4d, 0xb2, 0xfd, 0x33, 0xfb, 0x1f, - 0x0a, 0x70, 0x36, 0xb3, 0x5e, 0xd7, 0xf1, 0x22, 0xdb, 0xd7, 0x7e, 0x98, 0xe9, 0xe9, 0x8a, 0x47, - 0x68, 0xff, 0xdf, 0xd3, 0x2d, 0xf7, 0xdf, 0xdb, 0x45, 0x18, 0xc7, 0xcc, 0x21, 0x7b, 0x9f, 0x84, - 0x71, 0xcc, 0xec, 0x5b, 0x8e, 0x98, 0xe0, 0x5f, 0x0b, 0x39, 0xdf, 0xc2, 0x04, 0x06, 0x17, 0xe8, - 0x39, 0xc3, 0x80, 0xa1, 0x7c, 0x84, 0xf3, 0x33, 0x86, 0x97, 0x61, 0x05, 0x45, 0xb3, 0x30, 0xd6, - 0x70, 0x3d, 0x7a, 0xf8, 0xec, 0x9a, 0xac, 0xb8, 0x52, 0x91, 0xac, 0x98, 0x60, 0x9c, 0xc4, 0x47, - 0xae, 0x16, 0xe2, 0x91, 0x7f, 0xdd, 0xab, 0x87, 0xda, 0x75, 0x33, 0xa6, 0x95, 0x88, 0x1a, 0xc5, - 0x8c, 0x70, 0x8f, 0x2b, 0x9a, 0x9c, 0xa8, 0xd8, 0xbd, 0x9c, 0x68, 0x38, 0x5b, 0x46, 0x34, 0xfd, - 0x2a, 0x8c, 0xdc, 0xb7, 0x6e, 0xc4, 0xfe, 0x56, 0x11, 0x1e, 0x69, 0xb3, 0xed, 0xf9, 0x59, 0x6f, - 0xcc, 0x81, 0x76, 0xd6, 0xa7, 0xe6, 0xa1, 0x0c, 0xc7, 0x36, 0x5a, 0xf5, 0xfa, 0x2e, 0x73, 0x74, - 0x23, 0x35, 0x89, 0x21, 0x78, 0x4a, 0x29, 0x1c, 0x39, 0xb6, 0x94, 0x81, 0x83, 0x33, 0x6b, 0xd2, - 0x27, 0x16, 0xbd, 0x49, 0x76, 0x15, 0xa9, 0xc4, 0x13, 0x0b, 0xeb, 0x40, 0x6c, 0xe2, 0xa2, 0x2b, - 0x30, 0xe1, 0xec, 0x38, 0x2e, 0x4f, 0xef, 0x21, 0x09, 0xf0, 0x37, 0x96, 0x92, 0x45, 0xcf, 0x26, - 0x11, 0x70, 0xba, 0x0e, 0x7a, 0x13, 0x90, 0x7f, 0x9b, 0x39, 0xcf, 0xd4, 0xae, 0x10, 0x4f, 0x28, - 0xf3, 0xd9, 0xdc, 0x15, 0xe3, 0x23, 0xe1, 0x46, 0x0a, 0x03, 0x67, 0xd4, 0x4a, 0x04, 0x1b, 0xec, - 0xcb, 0x0f, 0x36, 0xd8, 0xfe, 0x5c, 0xec, 0x98, 0x19, 0xf1, 0x1d, 0x18, 0x39, 0xac, 0xb5, 0xf7, - 0x53, 0xd0, 0x1f, 0x88, 0x9c, 0xf3, 0x09, 0xaf, 0x72, 0x99, 0x91, 0x5b, 0xc2, 0xed, 0xff, 0xc7, - 0x02, 0x25, 0x4b, 0x36, 0xe3, 0x8a, 0xbf, 0xca, 0x4c, 0xd7, 0xb9, 0x14, 0x5c, 0x0b, 0x25, 0x76, - 0x5c, 0x33, 0x5d, 0x8f, 0x81, 0xd8, 0xc4, 0xe5, 0xcb, 0x2d, 0x8c, 0x23, 0x58, 0x18, 0x0f, 0x08, - 0xa1, 0x35, 0x54, 0x18, 0xe8, 0x13, 0xd0, 0x5f, 0x73, 0x77, 0xdc, 0x50, 0xc8, 0xd1, 0x0e, 0xad, - 0xb7, 0x8b, 0xbf, 0x6f, 0x81, 0x93, 0xc1, 0x92, 0x9e, 0xfd, 0x53, 0x16, 0x28, 0x75, 0xe7, 0x55, - 0xe2, 0xd4, 0xa3, 0x2d, 0xf4, 0x06, 0x80, 0xa4, 0xa0, 0x64, 0x6f, 0xd2, 0x08, 0x0b, 0xb0, 0x82, - 0x1c, 0x18, 0xff, 0xb0, 0x56, 0x07, 0xbd, 0x0e, 0x7d, 0x5b, 0x8c, 0x96, 0xf8, 0xb6, 0xf3, 0x4a, - 0xd5, 0xc5, 0x4a, 0x0f, 0xf6, 0x4a, 0xc7, 0xcc, 0x36, 0xe5, 0x2d, 0xc6, 0x6b, 0xd9, 0x3f, 0x51, - 0x88, 0xe7, 0xf4, 0xad, 0x96, 0x1f, 0x39, 0x47, 0xc0, 0x89, 0x5c, 0x31, 0x38, 0x91, 0x27, 0xda, - 0xe9, 0x73, 0x59, 0x97, 0x72, 0x39, 0x90, 0x1b, 0x09, 0x0e, 0xe4, 0xc9, 0xce, 0xa4, 0xda, 0x73, - 0x1e, 0xff, 0x93, 0x05, 0x13, 0x06, 0xfe, 0x11, 0x5c, 0x80, 0x4b, 0xe6, 0x05, 0xf8, 0x68, 0xc7, - 0x6f, 0xc8, 0xb9, 0xf8, 0x7e, 0xb4, 0x98, 0xe8, 0x3b, 0xbb, 0xf0, 0xde, 0x85, 0x9e, 0x2d, 0x27, - 0xa8, 0x89, 0x77, 0xfd, 0xc5, 0xae, 0xc6, 0x7a, 0xe6, 0xaa, 0x13, 0x08, 0x03, 0x8e, 0x67, 0xe5, - 0xa8, 0xd3, 0xa2, 0x8e, 0xc6, 0x1b, 0xac, 0x29, 0x74, 0x19, 0xfa, 0xc2, 0xaa, 0xdf, 0x54, 0x7e, - 0x80, 0x2c, 0x5d, 0x78, 0x85, 0x95, 0x1c, 0xec, 0x95, 0x90, 0xd9, 0x1c, 0x2d, 0xc6, 0x02, 0x1f, - 0xbd, 0x0d, 0x23, 0xec, 0x97, 0xb2, 0xa6, 0x2c, 0xe6, 0x4b, 0x60, 0x2a, 0x3a, 0x22, 0x37, 0x35, - 0x36, 0x8a, 0xb0, 0x49, 0x6a, 0x7a, 0x13, 0x06, 0xd5, 0x67, 0x3d, 0x54, 0x6d, 0xfd, 0xff, 0x59, - 0x84, 0xc9, 0x8c, 0x35, 0x87, 0x42, 0x63, 0x26, 0x2e, 0x75, 0xb9, 0x54, 0xdf, 0xe3, 0x5c, 0x84, - 0xec, 0x01, 0x58, 0x13, 0x6b, 0xab, 0xeb, 0x46, 0xd7, 0x43, 0x92, 0x6c, 0x94, 0x16, 0x75, 0x6e, - 0x94, 0x36, 0x76, 0x64, 0x43, 0x4d, 0x1b, 0x52, 0x3d, 0x7d, 0xa8, 0x73, 0xfa, 0x87, 0x3d, 0x70, - 0x2c, 0xcb, 0xc4, 0x04, 0x7d, 0x0e, 0xfa, 0x98, 0xa3, 0x9a, 0x14, 0x9c, 0xbd, 0xd8, 0xad, 0x71, - 0xca, 0x0c, 0xf3, 0x75, 0x13, 0xa1, 0x69, 0x67, 0xe4, 0x71, 0xc4, 0x0b, 0x3b, 0x0e, 0xb3, 0x68, - 0x93, 0x85, 0x8c, 0x12, 0xb7, 0xa7, 0x3c, 0x3e, 0x3e, 0xd2, 0x75, 0x07, 0xc4, 0xfd, 0x1b, 0x26, - 0x2c, 0xb5, 0x64, 0x71, 0x67, 0x4b, 0x2d, 0xd9, 0x32, 0x5a, 0x86, 0xbe, 0x2a, 0x37, 0x01, 0x2a, - 0x76, 0x3e, 0xc2, 0xb8, 0xfd, 0x8f, 0x3a, 0x80, 0x85, 0xdd, 0x8f, 0x20, 0x30, 0xed, 0xc2, 0x90, - 0x36, 0x30, 0x0f, 0x75, 0xf1, 0x6c, 0xd3, 0x8b, 0x4f, 0x1b, 0x82, 0x87, 0xba, 0x80, 0x7e, 0x46, - 0xbb, 0xfb, 0xc5, 0x79, 0xf0, 0x61, 0x83, 0x77, 0x3a, 0x9d, 0x70, 0x1f, 0x4c, 0xec, 0x2b, 0xc6, - 0x4b, 0x55, 0xcc, 0x98, 0xee, 0xb9, 0xa9, 0xa1, 0xcc, 0x0b, 0xbf, 0x7d, 0x1c, 0x77, 0xfb, 0x67, - 0x2d, 0x48, 0x38, 0x78, 0x29, 0x71, 0xa7, 0x95, 0x2b, 0xee, 0x3c, 0x07, 0x3d, 0x81, 0x5f, 0x27, - 0xc9, 0xd4, 0xfb, 0xd8, 0xaf, 0x13, 0xcc, 0x20, 0x14, 0x23, 0x8a, 0x85, 0x58, 0xc3, 0xfa, 0x03, - 0x5d, 0x3c, 0xbd, 0x1f, 0x83, 0xde, 0x3a, 0xd9, 0x21, 0xf5, 0x64, 0x86, 0xd4, 0xeb, 0xb4, 0x10, - 0x73, 0x98, 0xfd, 0xdb, 0x3d, 0x70, 0xa6, 0x6d, 0x64, 0x39, 0xca, 0x60, 0x6e, 0x3a, 0x11, 0xb9, - 0xe3, 0xec, 0x26, 0x33, 0x03, 0x5e, 0xe1, 0xc5, 0x58, 0xc2, 0x99, 0xb3, 0x35, 0xcf, 0x94, 0x93, - 0x10, 0x0e, 0x8b, 0x04, 0x39, 0x02, 0x6a, 0x0a, 0x1b, 0x8b, 0x0f, 0x42, 0xd8, 0xf8, 0x3c, 0x40, - 0x18, 0xd6, 0xb9, 0x1d, 0x67, 0x4d, 0x78, 0x71, 0xc7, 0x19, 0x95, 0x2a, 0xd7, 0x05, 0x04, 0x6b, - 0x58, 0x68, 0x01, 0xc6, 0x9b, 0x81, 0x1f, 0x71, 0x59, 0xfb, 0x02, 0x37, 0x75, 0xee, 0x35, 0x83, - 0x7a, 0x95, 0x13, 0x70, 0x9c, 0xaa, 0x81, 0x5e, 0x82, 0x21, 0x11, 0xe8, 0xab, 0xec, 0xfb, 0x75, - 0x21, 0xde, 0x53, 0xd6, 0xbf, 0x95, 0x18, 0x84, 0x75, 0x3c, 0xad, 0x1a, 0x13, 0xe0, 0xf7, 0x67, - 0x56, 0xe3, 0x42, 0x7c, 0x0d, 0x2f, 0x91, 0x14, 0x60, 0xa0, 0xab, 0xa4, 0x00, 0xb1, 0xc0, 0x73, - 0xb0, 0x6b, 0x7d, 0x32, 0x74, 0x14, 0x11, 0x7e, 0xad, 0x07, 0x26, 0xc5, 0xc2, 0x79, 0xd8, 0xcb, - 0x65, 0x3d, 0xbd, 0x5c, 0x1e, 0x84, 0x48, 0xf4, 0x83, 0x35, 0x73, 0xd4, 0x6b, 0xe6, 0x27, 0x2d, - 0x30, 0x79, 0x48, 0xf4, 0x9f, 0xe5, 0xa6, 0x56, 0x7d, 0x29, 0x97, 0x27, 0x8d, 0x23, 0x86, 0xbf, - 0xb7, 0x24, 0xab, 0xf6, 0xff, 0x65, 0xc1, 0xa3, 0x1d, 0x29, 0xa2, 0x45, 0x18, 0x64, 0x8c, 0xae, - 0xf6, 0x2e, 0x7e, 0x52, 0xb9, 0x42, 0x48, 0x40, 0x0e, 0xdf, 0x1d, 0xd7, 0x44, 0x8b, 0xa9, 0x1c, - 0xb6, 0x4f, 0x65, 0xe4, 0xb0, 0x3d, 0x6e, 0x0c, 0xcf, 0x7d, 0x26, 0xb1, 0xfd, 0x12, 0xbd, 0x71, - 0x4c, 0x7f, 0xca, 0x8f, 0x18, 0xe2, 0x5c, 0x3b, 0x21, 0xce, 0x45, 0x26, 0xb6, 0x76, 0x87, 0xbc, - 0x01, 0xe3, 0x2c, 0x02, 0x28, 0x73, 0xcc, 0x11, 0x8e, 0x98, 0x85, 0xd8, 0xf8, 0xfe, 0x7a, 0x02, - 0x86, 0x53, 0xd8, 0xf6, 0xdf, 0x15, 0xa1, 0x8f, 0x6f, 0xbf, 0x23, 0x78, 0xf8, 0x3e, 0x03, 0x83, - 0x6e, 0xa3, 0xd1, 0xe2, 0x69, 0x49, 0x7b, 0x63, 0x53, 0xee, 0x65, 0x59, 0x88, 0x63, 0x38, 0x5a, - 0x12, 0x9a, 0x84, 0x36, 0x41, 0xc6, 0x79, 0xc7, 0x67, 0x16, 0x9c, 0xc8, 0xe1, 0x5c, 0x9c, 0xba, - 0x67, 0x63, 0x9d, 0x03, 0xfa, 0x34, 0x40, 0x18, 0x05, 0xae, 0xb7, 0x49, 0xcb, 0x44, 0x26, 0x8a, - 0xa7, 0xdb, 0x50, 0xab, 0x28, 0x64, 0x4e, 0x33, 0x3e, 0x73, 0x14, 0x00, 0x6b, 0x14, 0xd1, 0x8c, - 0x71, 0xd3, 0x4f, 0x27, 0xe6, 0x0e, 0x38, 0xd5, 0x78, 0xce, 0xa6, 0x5f, 0x86, 0x41, 0x45, 0xbc, - 0x93, 0x5c, 0x71, 0x58, 0x67, 0xd8, 0x3e, 0x06, 0x63, 0x89, 0xbe, 0x1d, 0x4a, 0x2c, 0xf9, 0x3b, - 0x16, 0x8c, 0xf1, 0xce, 0x2c, 0x7a, 0x3b, 0xe2, 0x36, 0xb8, 0x07, 0xc7, 0xea, 0x19, 0xa7, 0xb2, - 0x98, 0xfe, 0xee, 0x4f, 0x71, 0x25, 0x86, 0xcc, 0x82, 0xe2, 0xcc, 0x36, 0xd0, 0x05, 0xba, 0xe3, - 0xe8, 0xa9, 0xeb, 0xd4, 0x45, 0x34, 0x91, 0x61, 0xbe, 0xdb, 0x78, 0x19, 0x56, 0x50, 0xfb, 0xaf, - 0x2c, 0x98, 0xe0, 0x3d, 0xbf, 0x46, 0x76, 0xd5, 0xd9, 0xf4, 0x9d, 0xec, 0xbb, 0x48, 0x88, 0x5d, - 0xc8, 0x49, 0x88, 0xad, 0x7f, 0x5a, 0xb1, 0xed, 0xa7, 0x7d, 0xd5, 0x02, 0xb1, 0x42, 0x8e, 0x40, - 0xd2, 0xf2, 0x7d, 0xa6, 0xa4, 0x65, 0x3a, 0x7f, 0x13, 0xe4, 0x88, 0x58, 0xfe, 0xc5, 0x82, 0x71, - 0x8e, 0x10, 0x5b, 0x41, 0x7c, 0x47, 0xe7, 0x61, 0xce, 0xfc, 0xa2, 0x4c, 0xb3, 0xd6, 0x6b, 0x64, - 0x77, 0xcd, 0x2f, 0x3b, 0xd1, 0x56, 0xf6, 0x47, 0x19, 0x93, 0xd5, 0xd3, 0x76, 0xb2, 0x6a, 0x72, - 0x03, 0x19, 0x89, 0x17, 0x3b, 0x08, 0x80, 0x0f, 0x9b, 0x78, 0xd1, 0xfe, 0x7b, 0x0b, 0x10, 0x6f, - 0xc6, 0x60, 0xdc, 0x28, 0x3b, 0xc4, 0x4a, 0xb5, 0x8b, 0x2e, 0x3e, 0x9a, 0x14, 0x04, 0x6b, 0x58, - 0x0f, 0x64, 0x78, 0x12, 0xa6, 0x2c, 0xc5, 0xce, 0xa6, 0x2c, 0x87, 0x18, 0xd1, 0xaf, 0xf6, 0x43, - 0xd2, 0x15, 0x13, 0xdd, 0x84, 0xe1, 0xaa, 0xd3, 0x74, 0x6e, 0xbb, 0x75, 0x37, 0x72, 0x49, 0xd8, - 0xce, 0xce, 0x6d, 0x5e, 0xc3, 0x13, 0xc6, 0x07, 0x5a, 0x09, 0x36, 0xe8, 0xa0, 0x19, 0x80, 0x66, - 0xe0, 0xee, 0xb8, 0x75, 0xb2, 0xc9, 0x04, 0x42, 0x2c, 0x7e, 0x11, 0x37, 0xba, 0x93, 0xa5, 0x58, - 0xc3, 0xc8, 0x08, 0x1b, 0x52, 0x7c, 0xc8, 0x61, 0x43, 0xe0, 0xc8, 0xc2, 0x86, 0xf4, 0x1c, 0x2a, - 0x6c, 0xc8, 0xc0, 0xa1, 0xc3, 0x86, 0xf4, 0x76, 0x15, 0x36, 0x04, 0xc3, 0x09, 0xc9, 0x7b, 0xd2, - 0xff, 0x4b, 0x6e, 0x9d, 0x88, 0x07, 0x07, 0x0f, 0xba, 0x34, 0xbd, 0xbf, 0x57, 0x3a, 0x81, 0x33, - 0x31, 0x70, 0x4e, 0x4d, 0xf4, 0x71, 0x98, 0x72, 0xea, 0x75, 0xff, 0x8e, 0x9a, 0xd4, 0xc5, 0xb0, - 0xea, 0xd4, 0xb9, 0x72, 0xa9, 0x9f, 0x51, 0x3d, 0xbd, 0xbf, 0x57, 0x9a, 0x9a, 0xcd, 0xc1, 0xc1, - 0xb9, 0xb5, 0xd1, 0x6b, 0x30, 0xd8, 0x0c, 0xfc, 0xea, 0x8a, 0xe6, 0x2f, 0x7e, 0x96, 0x0e, 0x60, - 0x59, 0x16, 0x1e, 0xec, 0x95, 0x46, 0xd4, 0x1f, 0x76, 0xe1, 0xc7, 0x15, 0x32, 0x22, 0x72, 0x0c, - 0x3d, 0xec, 0x88, 0x1c, 0xc3, 0x0f, 0x38, 0x22, 0x87, 0xbd, 0x0d, 0x93, 0x15, 0x12, 0xb8, 0x4e, - 0xdd, 0xbd, 0x47, 0x79, 0x72, 0x79, 0x06, 0xae, 0xc1, 0x60, 0x90, 0x38, 0xf5, 0xbb, 0x0a, 0x2e, - 0xae, 0xc9, 0x65, 0xe4, 0x29, 0x1f, 0x13, 0xb2, 0xff, 0xbd, 0x05, 0xfd, 0xc2, 0xbd, 0xf3, 0x08, - 0x38, 0xd3, 0x59, 0x43, 0x25, 0x53, 0xca, 0x9e, 0x14, 0xd6, 0x99, 0x5c, 0x65, 0xcc, 0x72, 0x42, - 0x19, 0xf3, 0x68, 0x3b, 0x22, 0xed, 0xd5, 0x30, 0xff, 0x75, 0x91, 0xbe, 0x10, 0x8c, 0x40, 0x03, - 0x0f, 0x7f, 0x08, 0x56, 0xa1, 0x3f, 0x14, 0x8e, 0xee, 0x85, 0x7c, 0x5f, 0x9e, 0xe4, 0x24, 0xc6, - 0x36, 0x90, 0xc2, 0xb5, 0x5d, 0x12, 0xc9, 0xf4, 0xa0, 0x2f, 0x3e, 0x44, 0x0f, 0xfa, 0x4e, 0xa1, - 0x18, 0x7a, 0x1e, 0x44, 0x28, 0x06, 0xfb, 0x1b, 0xec, 0x76, 0xd6, 0xcb, 0x8f, 0x80, 0x71, 0xbb, - 0x62, 0xde, 0xe3, 0x76, 0x9b, 0x95, 0x25, 0x3a, 0x95, 0xc3, 0xc0, 0xfd, 0x96, 0x05, 0x67, 0x32, - 0xbe, 0x4a, 0xe3, 0xe6, 0x9e, 0x85, 0x01, 0xa7, 0x55, 0x73, 0xd5, 0x5e, 0xd6, 0xb4, 0xc5, 0xb3, - 0xa2, 0x1c, 0x2b, 0x0c, 0x34, 0x0f, 0x13, 0xe4, 0x6e, 0xd3, 0xe5, 0x6a, 0x78, 0xdd, 0x74, 0xbc, - 0xc8, 0x7d, 0x82, 0x17, 0x93, 0x40, 0x9c, 0xc6, 0x57, 0xe1, 0xdc, 0x8a, 0xb9, 0xe1, 0xdc, 0x7e, - 0xdd, 0x82, 0x21, 0xe5, 0xea, 0xfd, 0xd0, 0x47, 0xfb, 0x0d, 0x73, 0xb4, 0x1f, 0x69, 0x33, 0xda, - 0x39, 0xc3, 0xfc, 0x97, 0x05, 0xd5, 0xdf, 0xb2, 0x1f, 0x44, 0x5d, 0x70, 0x89, 0xf7, 0xef, 0xf6, - 0x72, 0x09, 0x86, 0x9c, 0x66, 0x53, 0x02, 0xa4, 0xfd, 0x22, 0x4b, 0x15, 0x11, 0x17, 0x63, 0x1d, - 0x47, 0x79, 0xe1, 0x14, 0x73, 0xbd, 0x70, 0x6a, 0x00, 0x91, 0x13, 0x6c, 0x92, 0x88, 0x96, 0x09, - 0x73, 0xeb, 0xfc, 0xf3, 0xa6, 0x15, 0xb9, 0xf5, 0x19, 0xd7, 0x8b, 0xc2, 0x28, 0x98, 0x59, 0xf6, - 0xa2, 0x1b, 0x01, 0x7f, 0xa6, 0x6a, 0x41, 0x13, 0x15, 0x2d, 0xac, 0xd1, 0x95, 0x61, 0x4d, 0x58, - 0x1b, 0xbd, 0xa6, 0x21, 0xcc, 0xaa, 0x28, 0xc7, 0x0a, 0xc3, 0x7e, 0x99, 0xdd, 0x3e, 0x6c, 0x4c, - 0x0f, 0x17, 0x0c, 0xf0, 0x1f, 0x86, 0xd5, 0x6c, 0x30, 0x95, 0xf0, 0x82, 0x1e, 0x72, 0xb0, 0xfd, - 0x61, 0x4f, 0x1b, 0xd6, 0xfd, 0x59, 0xe3, 0xb8, 0x84, 0xe8, 0x93, 0x29, 0xe3, 0xa6, 0xe7, 0x3a, - 0xdc, 0x1a, 0x87, 0x30, 0x67, 0x62, 0x79, 0xe3, 0x58, 0x56, 0xad, 0xe5, 0xb2, 0xd8, 0x17, 0x5a, - 0xde, 0x38, 0x01, 0xc0, 0x31, 0x0e, 0x65, 0xd8, 0xd4, 0x9f, 0x70, 0x0a, 0xc5, 0xe1, 0xc5, 0x15, - 0x76, 0x88, 0x35, 0x0c, 0x74, 0x51, 0x08, 0x2d, 0xb8, 0xee, 0xe1, 0x91, 0x84, 0xd0, 0x42, 0x0e, - 0x97, 0x26, 0x69, 0xba, 0x04, 0x43, 0xe4, 0x6e, 0x44, 0x02, 0xcf, 0xa9, 0xd3, 0x16, 0x7a, 0xe3, - 0x88, 0xb8, 0x8b, 0x71, 0x31, 0xd6, 0x71, 0xd0, 0x1a, 0x8c, 0x85, 0x5c, 0x96, 0xa7, 0x92, 0x5a, - 0x70, 0x99, 0xe8, 0xd3, 0xca, 0xc9, 0xde, 0x04, 0x1f, 0xb0, 0x22, 0x7e, 0x3a, 0xc9, 0xd0, 0x23, - 0x49, 0x12, 0xe8, 0x75, 0x18, 0xad, 0xfb, 0x4e, 0x6d, 0xce, 0xa9, 0x3b, 0x5e, 0x95, 0x8d, 0xcf, - 0x80, 0x11, 0x7f, 0x72, 0xf4, 0xba, 0x01, 0xc5, 0x09, 0x6c, 0xca, 0x20, 0xea, 0x25, 0x22, 0x11, - 0x8b, 0xe3, 0x6d, 0x92, 0x70, 0x6a, 0x90, 0x7d, 0x15, 0x63, 0x10, 0xaf, 0xe7, 0xe0, 0xe0, 0xdc, - 0xda, 0xe8, 0x32, 0x0c, 0xcb, 0xcf, 0xd7, 0x22, 0xf5, 0xc4, 0x0e, 0x4d, 0x1a, 0x0c, 0x1b, 0x98, - 0x28, 0x84, 0xe3, 0xf2, 0xff, 0x5a, 0xe0, 0x6c, 0x6c, 0xb8, 0x55, 0x11, 0xbe, 0x82, 0x3b, 0x7f, - 0x7f, 0x4c, 0x7a, 0x9a, 0x2e, 0x66, 0x21, 0x1d, 0xec, 0x95, 0x4e, 0x8b, 0x51, 0xcb, 0x84, 0xe3, - 0x6c, 0xda, 0x68, 0x05, 0x26, 0xb9, 0x0d, 0xcc, 0xfc, 0x16, 0xa9, 0x6e, 0xcb, 0x0d, 0xc7, 0xb8, - 0x46, 0xcd, 0xf1, 0xe7, 0x6a, 0x1a, 0x05, 0x67, 0xd5, 0x43, 0xef, 0xc0, 0x54, 0xb3, 0x75, 0xbb, - 0xee, 0x86, 0x5b, 0xab, 0x7e, 0xc4, 0x4c, 0xc8, 0x66, 0x6b, 0xb5, 0x80, 0x84, 0xdc, 0x37, 0x98, - 0x5d, 0xbd, 0x32, 0xba, 0x52, 0x39, 0x07, 0x0f, 0xe7, 0x52, 0x40, 0xf7, 0xe0, 0x78, 0x62, 0x21, - 0x88, 0x30, 0x29, 0xa3, 0xf9, 0x29, 0xad, 0x2a, 0x59, 0x15, 0x44, 0xc4, 0xa1, 0x2c, 0x10, 0xce, - 0x6e, 0x02, 0xbd, 0x02, 0xe0, 0x36, 0x97, 0x9c, 0x86, 0x5b, 0xa7, 0xcf, 0xd1, 0x49, 0xb6, 0x46, - 0xe8, 0xd3, 0x04, 0x96, 0xcb, 0xb2, 0x94, 0x9e, 0xcd, 0xe2, 0xdf, 0x2e, 0xd6, 0xb0, 0xd1, 0x75, - 0x18, 0x15, 0xff, 0x76, 0xc5, 0x94, 0x4e, 0xa8, 0xec, 0xa7, 0xa3, 0xb2, 0x86, 0x9a, 0xc7, 0x44, - 0x09, 0x4e, 0xd4, 0x45, 0x9b, 0x70, 0x46, 0xa6, 0x5e, 0xd5, 0xd7, 0xa7, 0x9c, 0x83, 0x90, 0xe5, - 0x91, 0x1a, 0xe0, 0x3e, 0x45, 0xb3, 0xed, 0x10, 0x71, 0x7b, 0x3a, 0xf4, 0x5e, 0xd7, 0x97, 0x39, - 0xf7, 0x18, 0x3f, 0x1e, 0x47, 0xf1, 0xbc, 0x9e, 0x04, 0xe2, 0x34, 0x3e, 0xf2, 0xe1, 0xb8, 0xeb, - 0x65, 0xad, 0xea, 0x13, 0x8c, 0xd0, 0x47, 0xb9, 0xb3, 0x7c, 0xfb, 0x15, 0x9d, 0x09, 0xc7, 0xd9, - 0x74, 0xd1, 0x32, 0x4c, 0x46, 0xbc, 0x60, 0xc1, 0x0d, 0x79, 0x9a, 0x1a, 0xfa, 0xec, 0x3b, 0xc9, - 0x9a, 0x3b, 0x49, 0x57, 0xf3, 0x5a, 0x1a, 0x8c, 0xb3, 0xea, 0xbc, 0x37, 0x03, 0xd0, 0x6f, 0x5a, - 0xb4, 0xb6, 0xc6, 0xe8, 0xa3, 0xcf, 0xc0, 0xb0, 0x3e, 0x3e, 0x82, 0x69, 0x39, 0x9f, 0xcd, 0x07, - 0x6b, 0xc7, 0x0b, 0x7f, 0x26, 0xa8, 0x23, 0x44, 0x87, 0x61, 0x83, 0x22, 0xaa, 0x66, 0x04, 0xb9, - 0xb8, 0xd8, 0x1d, 0x53, 0xd4, 0xbd, 0xfd, 0x23, 0x81, 0xec, 0x9d, 0x83, 0xae, 0xc3, 0x40, 0xb5, - 0xee, 0x12, 0x2f, 0x5a, 0x2e, 0xb7, 0x0b, 0xae, 0x3a, 0x2f, 0x70, 0xc4, 0x56, 0x14, 0xd9, 0xa5, - 0x78, 0x19, 0x56, 0x14, 0xec, 0xcb, 0x30, 0x54, 0xa9, 0x13, 0xd2, 0xe4, 0x7e, 0x5c, 0xe8, 0x29, - 0xf6, 0x30, 0x61, 0xac, 0xa5, 0xc5, 0x58, 0x4b, 0xfd, 0xcd, 0xc1, 0x98, 0x4a, 0x09, 0xb7, 0xff, - 0xb8, 0x00, 0xa5, 0x0e, 0x49, 0xce, 0x12, 0xfa, 0x36, 0xab, 0x2b, 0x7d, 0xdb, 0x2c, 0x8c, 0xc5, - 0xff, 0x74, 0x51, 0x9e, 0x32, 0x86, 0xbe, 0x69, 0x82, 0x71, 0x12, 0xbf, 0x6b, 0xbf, 0x16, 0x5d, - 0x65, 0xd7, 0xd3, 0xd1, 0x33, 0xcb, 0x50, 0xd5, 0xf7, 0x76, 0xff, 0xf6, 0xce, 0x55, 0xbb, 0xda, - 0xdf, 0x28, 0xc0, 0x71, 0x35, 0x84, 0xdf, 0xbb, 0x03, 0xb7, 0x9e, 0x1e, 0xb8, 0x07, 0xa0, 0xb4, - 0xb6, 0x6f, 0x40, 0x1f, 0x8f, 0xf8, 0xda, 0x05, 0xcf, 0xff, 0x98, 0x19, 0x7c, 0x5f, 0xb1, 0x99, - 0x46, 0x00, 0xfe, 0x1f, 0xb3, 0x60, 0x2c, 0xe1, 0x20, 0x89, 0xb0, 0xe6, 0x45, 0x7f, 0x3f, 0x7c, - 0x79, 0x16, 0xc7, 0x7f, 0x0e, 0x7a, 0xb6, 0x7c, 0x65, 0xa4, 0xac, 0x30, 0xae, 0xfa, 0x61, 0x84, - 0x19, 0xc4, 0xfe, 0x6b, 0x0b, 0x7a, 0xd7, 0x1c, 0xd7, 0x8b, 0xa4, 0xf6, 0xc3, 0xca, 0xd1, 0x7e, - 0x74, 0xf3, 0x5d, 0xe8, 0x25, 0xe8, 0x23, 0x1b, 0x1b, 0xa4, 0x1a, 0x89, 0x59, 0x95, 0xd1, 0x34, - 0xfa, 0x16, 0x59, 0x29, 0x65, 0x42, 0x59, 0x63, 0xfc, 0x2f, 0x16, 0xc8, 0xe8, 0x16, 0x0c, 0x46, - 0x6e, 0x83, 0xcc, 0xd6, 0x6a, 0xc2, 0x26, 0xe0, 0x3e, 0x42, 0xc0, 0xac, 0x49, 0x02, 0x38, 0xa6, - 0x65, 0x7f, 0xb9, 0x00, 0x10, 0x47, 0x98, 0xeb, 0xf4, 0x89, 0x73, 0x29, 0x6d, 0xf1, 0xf9, 0x0c, - 0x6d, 0x31, 0x8a, 0x09, 0x66, 0xa8, 0x8a, 0xd5, 0x30, 0x15, 0xbb, 0x1a, 0xa6, 0x9e, 0xc3, 0x0c, - 0xd3, 0x3c, 0x4c, 0xc4, 0x11, 0xf2, 0xcc, 0x00, 0xa1, 0xec, 0xfe, 0x5e, 0x4b, 0x02, 0x71, 0x1a, - 0xdf, 0x26, 0x70, 0x4e, 0x05, 0x0a, 0x13, 0x77, 0x21, 0x73, 0x25, 0xd0, 0xb5, 0xef, 0x1d, 0xc6, - 0x29, 0x56, 0x87, 0x17, 0x72, 0xd5, 0xe1, 0xbf, 0x60, 0xc1, 0xb1, 0x64, 0x3b, 0xcc, 0xef, 0xfe, - 0x8b, 0x16, 0x1c, 0x8f, 0x73, 0xfc, 0xa4, 0x4d, 0x10, 0x5e, 0x6c, 0x1b, 0xfc, 0x2c, 0xa7, 0xc7, - 0x71, 0xd8, 0x96, 0x95, 0x2c, 0xd2, 0x38, 0xbb, 0x45, 0xfb, 0xdf, 0xf5, 0xc0, 0x54, 0x5e, 0xd4, - 0x34, 0xe6, 0x69, 0xe4, 0xdc, 0xad, 0x6c, 0x93, 0x3b, 0xc2, 0x9f, 0x23, 0xf6, 0x34, 0xe2, 0xc5, - 0x58, 0xc2, 0x93, 0x69, 0x9d, 0x0a, 0x5d, 0xa6, 0x75, 0xda, 0x82, 0x89, 0x3b, 0x5b, 0xc4, 0x5b, - 0xf7, 0x42, 0x27, 0x72, 0xc3, 0x0d, 0x97, 0x29, 0xd0, 0xf9, 0xba, 0x79, 0x45, 0x7a, 0x5d, 0xdc, - 0x4a, 0x22, 0x1c, 0xec, 0x95, 0xce, 0x18, 0x05, 0x71, 0x97, 0xf9, 0x41, 0x82, 0xd3, 0x44, 0xd3, - 0x59, 0xb1, 0x7a, 0x1e, 0x72, 0x56, 0xac, 0x86, 0x2b, 0xcc, 0x6e, 0xa4, 0x1b, 0x09, 0x7b, 0xb6, - 0xae, 0xa8, 0x52, 0xac, 0x61, 0xa0, 0x4f, 0x01, 0xd2, 0xd3, 0x1a, 0x1a, 0x41, 0x6b, 0x9f, 0xdb, - 0xdf, 0x2b, 0xa1, 0xd5, 0x14, 0xf4, 0x60, 0xaf, 0x34, 0x49, 0x4b, 0x97, 0x3d, 0xfa, 0xfc, 0x8d, - 0x23, 0xfd, 0x65, 0x10, 0x42, 0xb7, 0x60, 0x9c, 0x96, 0xb2, 0x1d, 0x25, 0x23, 0xe2, 0xf2, 0x27, - 0xeb, 0x33, 0xfb, 0x7b, 0xa5, 0xf1, 0xd5, 0x04, 0x2c, 0x8f, 0x74, 0x8a, 0x48, 0x46, 0x72, 0xac, - 0x81, 0x6e, 0x93, 0x63, 0xd9, 0x5f, 0xb4, 0xe0, 0x14, 0xbd, 0xe0, 0x6a, 0xd7, 0x73, 0xb4, 0xe8, - 0x4e, 0xd3, 0xe5, 0x7a, 0x1a, 0x71, 0xd5, 0x30, 0x59, 0x5d, 0x79, 0x99, 0x6b, 0x69, 0x14, 0x94, - 0x9e, 0xf0, 0xdb, 0xae, 0x57, 0x4b, 0x9e, 0xf0, 0xd7, 0x5c, 0xaf, 0x86, 0x19, 0x44, 0x5d, 0x59, - 0xc5, 0xdc, 0x08, 0xfb, 0x5f, 0xa3, 0x7b, 0x95, 0xf6, 0xe5, 0x3b, 0xda, 0x0d, 0xf4, 0x8c, 0xae, - 0x53, 0x15, 0xe6, 0x93, 0xb9, 0xfa, 0xd4, 0x2f, 0x58, 0x20, 0xbc, 0xdf, 0xbb, 0xb8, 0x93, 0xdf, - 0x86, 0xe1, 0x9d, 0x74, 0xca, 0xd7, 0x73, 0xf9, 0xe1, 0x00, 0x44, 0xa2, 0x57, 0xc5, 0xa2, 0x1b, - 0xe9, 0x5d, 0x0d, 0x5a, 0x76, 0x0d, 0x04, 0x74, 0x81, 0x30, 0xad, 0x46, 0xe7, 0xde, 0x3c, 0x0f, - 0x50, 0x63, 0xb8, 0x2c, 0x0f, 0x7c, 0xc1, 0xe4, 0xb8, 0x16, 0x14, 0x04, 0x6b, 0x58, 0xf6, 0xaf, - 0x16, 0x61, 0x48, 0xa6, 0x18, 0x6d, 0x79, 0xdd, 0xc8, 0x1e, 0x75, 0xc6, 0xa9, 0xd0, 0x91, 0x71, - 0x7a, 0x07, 0x26, 0x02, 0x52, 0x6d, 0x05, 0xa1, 0xbb, 0x43, 0x24, 0x58, 0x6c, 0x92, 0x19, 0x9e, - 0xe0, 0x21, 0x01, 0x3c, 0x60, 0x21, 0xb2, 0x12, 0x85, 0x4c, 0x69, 0x9c, 0x26, 0x84, 0x2e, 0xc2, - 0x20, 0x13, 0xbd, 0x97, 0x63, 0x81, 0xb0, 0x12, 0x7c, 0xad, 0x48, 0x00, 0x8e, 0x71, 0xd8, 0xe3, - 0xa0, 0x75, 0x9b, 0xa1, 0x27, 0x3c, 0xc1, 0x2b, 0xbc, 0x18, 0x4b, 0x38, 0xfa, 0x38, 0x8c, 0xf3, - 0x7a, 0x81, 0xdf, 0x74, 0x36, 0xb9, 0x4a, 0xb0, 0x57, 0x85, 0xd7, 0x19, 0x5f, 0x49, 0xc0, 0x0e, - 0xf6, 0x4a, 0xc7, 0x92, 0x65, 0xac, 0xdb, 0x29, 0x2a, 0xcc, 0xf2, 0x8f, 0x37, 0x42, 0xef, 0x8c, - 0x94, 0xc1, 0x60, 0x0c, 0xc2, 0x3a, 0x9e, 0xfd, 0xcf, 0x16, 0x4c, 0x68, 0x53, 0xd5, 0x75, 0x8e, - 0x0d, 0x63, 0x90, 0x0a, 0x5d, 0x0c, 0xd2, 0xe1, 0xa2, 0x3d, 0x64, 0xce, 0x70, 0xcf, 0x03, 0x9a, - 0x61, 0xfb, 0x33, 0x80, 0xd2, 0xf9, 0x6b, 0xd1, 0x9b, 0xdc, 0x90, 0xdf, 0x0d, 0x48, 0xad, 0x9d, - 0xc2, 0x5f, 0x8f, 0x9c, 0x23, 0x3d, 0x57, 0x79, 0x2d, 0xac, 0xea, 0xdb, 0x3f, 0xde, 0x03, 0xe3, - 0xc9, 0x58, 0x1d, 0xe8, 0x2a, 0xf4, 0x71, 0x2e, 0x5d, 0x90, 0x6f, 0x63, 0x4f, 0xa6, 0x45, 0xf8, - 0xe0, 0xf9, 0x6f, 0x38, 0x77, 0x2f, 0xea, 0xa3, 0x77, 0x60, 0xa8, 0xe6, 0xdf, 0xf1, 0xee, 0x38, - 0x41, 0x6d, 0xb6, 0xbc, 0x2c, 0x4e, 0x88, 0x4c, 0x01, 0xd4, 0x42, 0x8c, 0xa6, 0x47, 0x0d, 0x61, - 0xb6, 0x13, 0x31, 0x08, 0xeb, 0xe4, 0xd0, 0x1a, 0x4b, 0xc9, 0xb4, 0xe1, 0x6e, 0xae, 0x38, 0xcd, - 0x76, 0x5e, 0x5d, 0xf3, 0x12, 0x49, 0xa3, 0x3c, 0x22, 0xf2, 0x36, 0x71, 0x00, 0x8e, 0x09, 0xa1, - 0xcf, 0xc1, 0x64, 0x98, 0xa3, 0x12, 0xcb, 0x4b, 0x67, 0xde, 0x4e, 0x4b, 0xc4, 0x85, 0x29, 0x59, - 0xca, 0xb3, 0xac, 0x66, 0xd0, 0x5d, 0x40, 0x42, 0xf4, 0xbc, 0x16, 0xb4, 0xc2, 0x68, 0xae, 0xe5, - 0xd5, 0xea, 0x32, 0x65, 0xd3, 0x87, 0xb3, 0xe5, 0x04, 0x49, 0x6c, 0xad, 0x6d, 0x16, 0x12, 0x38, - 0x8d, 0x81, 0x33, 0xda, 0xb0, 0xbf, 0xd0, 0x03, 0xd3, 0x32, 0x61, 0x74, 0x86, 0xf7, 0xca, 0xe7, - 0xad, 0x84, 0xfb, 0xca, 0x2b, 0xf9, 0x07, 0xfd, 0x43, 0x73, 0x62, 0xf9, 0x52, 0xda, 0x89, 0xe5, - 0xb5, 0x43, 0x76, 0xe3, 0x81, 0xb9, 0xb2, 0x7c, 0xcf, 0xfa, 0x9f, 0xec, 0x1f, 0x03, 0xe3, 0x6a, - 0x46, 0x98, 0xc7, 0x5b, 0x2f, 0x4b, 0xd5, 0x51, 0xce, 0xf3, 0xff, 0xaa, 0xc0, 0x31, 0x2e, 0xfb, - 0x61, 0x19, 0x95, 0x9d, 0x9d, 0xb3, 0x8a, 0x0e, 0xa5, 0x49, 0x1a, 0xcd, 0x68, 0x77, 0xc1, 0x0d, - 0x44, 0x8f, 0x33, 0x69, 0x2e, 0x0a, 0x9c, 0x34, 0x4d, 0x09, 0xc1, 0x8a, 0x0e, 0xda, 0x81, 0x89, - 0x4d, 0x16, 0xf1, 0x49, 0xcb, 0xdd, 0x2c, 0xce, 0x85, 0xcc, 0x7d, 0x7b, 0x65, 0x7e, 0x31, 0x3f, - 0xd1, 0x33, 0x7f, 0xfc, 0xa5, 0x50, 0x70, 0xba, 0x09, 0xba, 0x35, 0x8e, 0x39, 0x77, 0xc2, 0xc5, - 0xba, 0x13, 0x46, 0x6e, 0x75, 0xae, 0xee, 0x57, 0xb7, 0x2b, 0x91, 0x1f, 0xc8, 0x04, 0x8f, 0x99, - 0x6f, 0xaf, 0xd9, 0x5b, 0x95, 0x14, 0xbe, 0xd1, 0xfc, 0xd4, 0xfe, 0x5e, 0xe9, 0x58, 0x16, 0x16, - 0xce, 0x6c, 0x0b, 0xad, 0x42, 0xff, 0xa6, 0x1b, 0x61, 0xd2, 0xf4, 0xc5, 0x69, 0x91, 0x79, 0x14, - 0x5e, 0xe1, 0x28, 0x46, 0x4b, 0x2c, 0x22, 0x95, 0x00, 0x60, 0x49, 0x04, 0xbd, 0xa9, 0x2e, 0x81, - 0xbe, 0x7c, 0x01, 0x6c, 0xda, 0xf6, 0x2e, 0xf3, 0x1a, 0x78, 0x1d, 0x8a, 0xde, 0x46, 0xd8, 0x2e, - 0x16, 0xcf, 0xea, 0x92, 0x21, 0x3f, 0x9b, 0xeb, 0xa7, 0x4f, 0xe3, 0xd5, 0xa5, 0x0a, 0xa6, 0x15, - 0x99, 0xdb, 0x6b, 0x58, 0x0d, 0x5d, 0x91, 0x2c, 0x2a, 0xd3, 0x0b, 0x78, 0xb9, 0x32, 0x5f, 0x59, - 0x36, 0x68, 0xb0, 0xa8, 0x86, 0xac, 0x18, 0xf3, 0xea, 0xe8, 0x26, 0x0c, 0x6e, 0xf2, 0x83, 0x6f, - 0x23, 0x14, 0x49, 0xe3, 0x33, 0x2f, 0xa3, 0x2b, 0x12, 0xc9, 0xa0, 0xc7, 0xae, 0x0c, 0x05, 0xc2, - 0x31, 0x29, 0xf4, 0x05, 0x0b, 0x8e, 0x27, 0xb3, 0xee, 0x33, 0x67, 0x35, 0x61, 0xa6, 0x96, 0xe9, - 0x00, 0x50, 0xce, 0xaa, 0x60, 0x34, 0xc8, 0xd4, 0x2f, 0x99, 0x68, 0x38, 0xbb, 0x39, 0x3a, 0xd0, - 0xc1, 0xed, 0x5a, 0xbb, 0xfc, 0x42, 0x89, 0xc0, 0x44, 0x7c, 0xa0, 0xf1, 0xdc, 0x02, 0xa6, 0x15, - 0xd1, 0x1a, 0xc0, 0x46, 0x9d, 0x88, 0x88, 0x8f, 0xc2, 0x28, 0x2a, 0xf3, 0xf6, 0x5f, 0x52, 0x58, - 0x82, 0x0e, 0x7b, 0x89, 0xc6, 0xa5, 0x58, 0xa3, 0x43, 0x97, 0x52, 0xd5, 0xf5, 0x6a, 0x24, 0x60, - 0xca, 0xad, 0x9c, 0xa5, 0x34, 0xcf, 0x30, 0xd2, 0x4b, 0x89, 0x97, 0x63, 0x41, 0x81, 0xd1, 0x22, - 0xcd, 0xad, 0x8d, 0xb0, 0x5d, 0x26, 0x8b, 0x79, 0xd2, 0xdc, 0x4a, 0x2c, 0x28, 0x4e, 0x8b, 0x95, - 0x63, 0x41, 0x81, 0x6e, 0x99, 0x0d, 0xba, 0x81, 0x48, 0x30, 0x35, 0x96, 0xbf, 0x65, 0x96, 0x38, - 0x4a, 0x7a, 0xcb, 0x08, 0x00, 0x96, 0x44, 0xd0, 0xa7, 0x4d, 0x6e, 0x67, 0x9c, 0xd1, 0x7c, 0xa6, - 0x03, 0xb7, 0x63, 0xd0, 0x6d, 0xcf, 0xef, 0xbc, 0x02, 0x85, 0x8d, 0x2a, 0x53, 0x8a, 0xe5, 0xe8, - 0x0c, 0x96, 0xe6, 0x0d, 0x6a, 0x2c, 0x32, 0xfc, 0xd2, 0x3c, 0x2e, 0x6c, 0x54, 0xe9, 0xd2, 0x77, - 0xee, 0xb5, 0x02, 0xb2, 0xe4, 0xd6, 0x89, 0xc8, 0x6a, 0x91, 0xb9, 0xf4, 0x67, 0x25, 0x52, 0x7a, - 0xe9, 0x2b, 0x10, 0x8e, 0x49, 0x51, 0xba, 0x31, 0x0f, 0x36, 0x99, 0x4f, 0x57, 0xb1, 0x5a, 0x69, - 0xba, 0x99, 0x5c, 0xd8, 0x36, 0x8c, 0xec, 0x84, 0xcd, 0x2d, 0x22, 0x4f, 0x45, 0xa6, 0xae, 0xcb, - 0x89, 0x54, 0x71, 0x53, 0x20, 0xba, 0x41, 0xd4, 0x72, 0xea, 0xa9, 0x83, 0x9c, 0x89, 0x56, 0x6e, - 0xea, 0xc4, 0xb0, 0x49, 0x9b, 0x2e, 0x84, 0x77, 0x79, 0x38, 0x39, 0xa6, 0xb8, 0xcb, 0x59, 0x08, - 0x19, 0x11, 0xe7, 0xf8, 0x42, 0x10, 0x00, 0x2c, 0x89, 0xa8, 0xc1, 0x66, 0x17, 0xd0, 0x89, 0x0e, - 0x83, 0x9d, 0xea, 0x6f, 0x3c, 0xd8, 0xec, 0xc2, 0x89, 0x49, 0xb1, 0x8b, 0xa6, 0xb9, 0xe5, 0x47, - 0xbe, 0x97, 0xb8, 0xe4, 0x4e, 0xe6, 0x5f, 0x34, 0xe5, 0x0c, 0xfc, 0xf4, 0x45, 0x93, 0x85, 0x85, - 0x33, 0xdb, 0xa2, 0x1f, 0xd7, 0x94, 0x91, 0x01, 0x45, 0xe6, 0x8d, 0xa7, 0x72, 0x02, 0x6b, 0xa6, - 0xc3, 0x07, 0xf2, 0x8f, 0x53, 0x20, 0x1c, 0x93, 0x42, 0x35, 0x18, 0x6d, 0x1a, 0x11, 0x67, 0x59, - 0x06, 0x91, 0x1c, 0xbe, 0x20, 0x2b, 0x36, 0x2d, 0x97, 0x10, 0x99, 0x10, 0x9c, 0xa0, 0xc9, 0x2c, - 0xf7, 0xb8, 0xab, 0x1f, 0x4b, 0x30, 0x92, 0x33, 0xd5, 0x19, 0xde, 0x80, 0x7c, 0xaa, 0x05, 0x00, - 0x4b, 0x22, 0x74, 0x34, 0x84, 0x83, 0x9a, 0x1f, 0xb2, 0x3c, 0x3d, 0x79, 0x0a, 0xf6, 0x2c, 0x35, - 0x91, 0x0c, 0xb3, 0x2e, 0x40, 0x38, 0x26, 0x45, 0x4f, 0x72, 0x7a, 0xe1, 0x9d, 0xce, 0x3f, 0xc9, - 0x93, 0xd7, 0x1d, 0x3b, 0xc9, 0xe9, 0x65, 0x57, 0x14, 0x57, 0x9d, 0x8a, 0x0a, 0xce, 0x72, 0x8c, - 0xe4, 0xf4, 0x4b, 0x85, 0x15, 0x4f, 0xf7, 0x4b, 0x81, 0x70, 0x4c, 0x8a, 0x5d, 0xc5, 0x2c, 0x34, - 0xdd, 0xd9, 0x36, 0x57, 0x31, 0x45, 0xc8, 0xb8, 0x8a, 0xb5, 0xd0, 0x75, 0xf6, 0x8f, 0x17, 0xe0, - 0x6c, 0xfb, 0x7d, 0x1b, 0xeb, 0xd0, 0xca, 0xb1, 0xcd, 0x52, 0x42, 0x87, 0xc6, 0x25, 0x3a, 0x31, - 0x56, 0xd7, 0x01, 0x87, 0xaf, 0xc0, 0x84, 0x72, 0x47, 0xac, 0xbb, 0xd5, 0x5d, 0x2d, 0xb1, 0xa8, - 0x0a, 0xcd, 0x53, 0x49, 0x22, 0xe0, 0x74, 0x1d, 0x34, 0x0b, 0x63, 0x46, 0xe1, 0xf2, 0x82, 0x78, - 0xfe, 0xc7, 0xd9, 0x31, 0x4c, 0x30, 0x4e, 0xe2, 0xdb, 0xbf, 0x66, 0xc1, 0xc9, 0x9c, 0x3c, 0xf3, - 0x5d, 0xc7, 0xd3, 0xdd, 0x80, 0xb1, 0xa6, 0x59, 0xb5, 0x43, 0x08, 0x70, 0x23, 0x9b, 0xbd, 0xea, - 0x6b, 0x02, 0x80, 0x93, 0x44, 0xed, 0x5f, 0x29, 0xc0, 0x99, 0xb6, 0xf6, 0xf5, 0x08, 0xc3, 0x89, - 0xcd, 0x46, 0xe8, 0xcc, 0x07, 0xa4, 0x46, 0xbc, 0xc8, 0x75, 0xea, 0x95, 0x26, 0xa9, 0x6a, 0x5a, - 0x50, 0x66, 0xa8, 0x7e, 0x65, 0xa5, 0x32, 0x9b, 0xc6, 0xc0, 0x39, 0x35, 0xd1, 0x12, 0xa0, 0x34, - 0x44, 0xcc, 0x30, 0x7b, 0xe2, 0xa6, 0xe9, 0xe1, 0x8c, 0x1a, 0xe8, 0x65, 0x18, 0x51, 0x76, 0xfb, - 0xda, 0x8c, 0xb3, 0x0b, 0x02, 0xeb, 0x00, 0x6c, 0xe2, 0xa1, 0x4b, 0x3c, 0x6d, 0x92, 0x48, 0xb0, - 0x25, 0x54, 0xa6, 0x63, 0x32, 0x27, 0x92, 0x28, 0xc6, 0x3a, 0xce, 0xdc, 0xe5, 0x3f, 0xfd, 0xf6, - 0xd9, 0x0f, 0xfd, 0xc5, 0xb7, 0xcf, 0x7e, 0xe8, 0xaf, 0xbe, 0x7d, 0xf6, 0x43, 0x3f, 0xb4, 0x7f, - 0xd6, 0xfa, 0xd3, 0xfd, 0xb3, 0xd6, 0x5f, 0xec, 0x9f, 0xb5, 0xfe, 0x6a, 0xff, 0xac, 0xf5, 0xff, - 0xee, 0x9f, 0xb5, 0xbe, 0xfc, 0xb7, 0x67, 0x3f, 0xf4, 0x36, 0x8a, 0x23, 0x54, 0x5f, 0xa4, 0xb3, - 0x73, 0x71, 0xe7, 0xd2, 0x7f, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x60, 0x45, 0x7a, 0xd6, 0xa3, 0x24, - 0x01, 0x00, + // 16206 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x69, 0x90, 0x1c, 0xc9, + 0x75, 0x30, 0xc6, 0xea, 0x9e, 0xf3, 0xcd, 0x9d, 0xb8, 0x06, 0xb3, 0x00, 0x1a, 0x5b, 0xbb, 0x8b, + 0xc5, 0x5e, 0x03, 0x62, 0x0f, 0x2e, 0xb8, 0xbb, 0x5c, 0xed, 0x9c, 0x40, 0x2f, 0x30, 0x83, 0xde, + 0xec, 0x01, 0x40, 0x2e, 0x97, 0x14, 0x0b, 0xdd, 0x39, 0x33, 0xc5, 0xe9, 0xae, 0xea, 0xad, 0xaa, + 0x1e, 0x60, 0x60, 0x2a, 0x24, 0x51, 0x16, 0x25, 0x52, 0x72, 0x04, 0x43, 0x21, 0x59, 0x0e, 0x4a, + 0xa1, 0x1f, 0xba, 0x65, 0x5a, 0xb2, 0x68, 0xc9, 0x92, 0x2c, 0xea, 0xb2, 0x2d, 0x47, 0xc8, 0xfe, + 0x21, 0x4b, 0x8a, 0x30, 0xa9, 0xb0, 0xc2, 0x23, 0x73, 0x6c, 0x87, 0x42, 0x3f, 0x2c, 0x29, 0x64, + 0xff, 0xb0, 0x27, 0xf4, 0x7d, 0xfc, 0x22, 0xcf, 0xca, 0xac, 0xa3, 0xbb, 0x07, 0x0b, 0x0c, 0x97, + 0x8c, 0xfd, 0xd7, 0x9d, 0xef, 0xe5, 0xcb, 0xac, 0x3c, 0x5f, 0xbe, 0x13, 0xec, 0xad, 0x4b, 0xe1, + 0xac, 0xeb, 0x5f, 0x70, 0x5a, 0xee, 0x85, 0x9a, 0x1f, 0x90, 0x0b, 0xdb, 0x17, 0x2f, 0x6c, 0x10, + 0x8f, 0x04, 0x4e, 0x44, 0xea, 0xb3, 0xad, 0xc0, 0x8f, 0x7c, 0x84, 0x38, 0xce, 0xac, 0xd3, 0x72, + 0x67, 0x29, 0xce, 0xec, 0xf6, 0xc5, 0x99, 0xe7, 0x36, 0xdc, 0x68, 0xb3, 0x7d, 0x7b, 0xb6, 0xe6, + 0x37, 0x2f, 0x6c, 0xf8, 0x1b, 0xfe, 0x05, 0x86, 0x7a, 0xbb, 0xbd, 0xce, 0xfe, 0xb1, 0x3f, 0xec, + 0x17, 0x27, 0x31, 0xf3, 0x62, 0xdc, 0x4c, 0xd3, 0xa9, 0x6d, 0xba, 0x1e, 0x09, 0x76, 0x2e, 0xb4, + 0xb6, 0x36, 0x58, 0xbb, 0x01, 0x09, 0xfd, 0x76, 0x50, 0x23, 0xc9, 0x86, 0x3b, 0xd6, 0x0a, 0x2f, + 0x34, 0x49, 0xe4, 0x64, 0x74, 0x77, 0xe6, 0x42, 0x5e, 0xad, 0xa0, 0xed, 0x45, 0x6e, 0x33, 0xdd, + 0xcc, 0x47, 0xba, 0x55, 0x08, 0x6b, 0x9b, 0xa4, 0xe9, 0xa4, 0xea, 0xbd, 0x90, 0x57, 0xaf, 0x1d, + 0xb9, 0x8d, 0x0b, 0xae, 0x17, 0x85, 0x51, 0x90, 0xac, 0x64, 0x7f, 0xd3, 0x82, 0xb3, 0x73, 0xb7, + 0xaa, 0x4b, 0x0d, 0x27, 0x8c, 0xdc, 0xda, 0x7c, 0xc3, 0xaf, 0x6d, 0x55, 0x23, 0x3f, 0x20, 0x37, + 0xfd, 0x46, 0xbb, 0x49, 0xaa, 0x6c, 0x20, 0xd0, 0xb3, 0x30, 0xb4, 0xcd, 0xfe, 0x97, 0x17, 0xa7, + 0xad, 0xb3, 0xd6, 0xf9, 0xe1, 0xf9, 0xc9, 0xbf, 0xd8, 0x2d, 0x7d, 0x68, 0x6f, 0xb7, 0x34, 0x74, + 0x53, 0x94, 0x63, 0x85, 0x81, 0xce, 0xc1, 0xc0, 0x7a, 0xb8, 0xb6, 0xd3, 0x22, 0xd3, 0x05, 0x86, + 0x3b, 0x2e, 0x70, 0x07, 0x96, 0xab, 0xb4, 0x14, 0x0b, 0x28, 0xba, 0x00, 0xc3, 0x2d, 0x27, 0x88, + 0xdc, 0xc8, 0xf5, 0xbd, 0xe9, 0xe2, 0x59, 0xeb, 0x7c, 0xff, 0xfc, 0x94, 0x40, 0x1d, 0xae, 0x48, + 0x00, 0x8e, 0x71, 0x68, 0x37, 0x02, 0xe2, 0xd4, 0xaf, 0x7b, 0x8d, 0x9d, 0xe9, 0xbe, 0xb3, 0xd6, + 0xf9, 0xa1, 0xb8, 0x1b, 0x58, 0x94, 0x63, 0x85, 0x61, 0x7f, 0xa5, 0x00, 0x43, 0x73, 0xeb, 0xeb, + 0xae, 0xe7, 0x46, 0x3b, 0xe8, 0x26, 0x8c, 0x7a, 0x7e, 0x9d, 0xc8, 0xff, 0xec, 0x2b, 0x46, 0x9e, + 0x3f, 0x3b, 0x9b, 0x5e, 0x4a, 0xb3, 0xab, 0x1a, 0xde, 0xfc, 0xe4, 0xde, 0x6e, 0x69, 0x54, 0x2f, + 0xc1, 0x06, 0x1d, 0x84, 0x61, 0xa4, 0xe5, 0xd7, 0x15, 0xd9, 0x02, 0x23, 0x5b, 0xca, 0x22, 0x5b, + 0x89, 0xd1, 0xe6, 0x27, 0xf6, 0x76, 0x4b, 0x23, 0x5a, 0x01, 0xd6, 0x89, 0xa0, 0xdb, 0x30, 0x41, + 0xff, 0x7a, 0x91, 0xab, 0xe8, 0x16, 0x19, 0xdd, 0xc7, 0xf2, 0xe8, 0x6a, 0xa8, 0xf3, 0x47, 0xf6, + 0x76, 0x4b, 0x13, 0x89, 0x42, 0x9c, 0x24, 0x68, 0xff, 0xa4, 0x05, 0x13, 0x73, 0xad, 0xd6, 0x5c, + 0xd0, 0xf4, 0x83, 0x4a, 0xe0, 0xaf, 0xbb, 0x0d, 0x82, 0x5e, 0x86, 0xbe, 0x88, 0xce, 0x1a, 0x9f, + 0xe1, 0xc7, 0xc4, 0xd0, 0xf6, 0xd1, 0xb9, 0xda, 0xdf, 0x2d, 0x1d, 0x49, 0xa0, 0xb3, 0xa9, 0x64, + 0x15, 0xd0, 0x1b, 0x30, 0xd9, 0xf0, 0x6b, 0x4e, 0x63, 0xd3, 0x0f, 0x23, 0x01, 0x15, 0x53, 0x7f, + 0x74, 0x6f, 0xb7, 0x34, 0x79, 0x2d, 0x01, 0xc3, 0x29, 0x6c, 0xfb, 0x1e, 0x8c, 0xcf, 0x45, 0x91, + 0x53, 0xdb, 0x24, 0x75, 0xbe, 0xa0, 0xd0, 0x8b, 0xd0, 0xe7, 0x39, 0x4d, 0xd9, 0x99, 0xb3, 0xb2, + 0x33, 0xab, 0x4e, 0x93, 0x76, 0x66, 0xf2, 0x86, 0xe7, 0xbe, 0xdb, 0x16, 0x8b, 0x94, 0x96, 0x61, + 0x86, 0x8d, 0x9e, 0x07, 0xa8, 0x93, 0x6d, 0xb7, 0x46, 0x2a, 0x4e, 0xb4, 0x29, 0xfa, 0x80, 0x44, + 0x5d, 0x58, 0x54, 0x10, 0xac, 0x61, 0xd9, 0x77, 0x61, 0x78, 0x6e, 0xdb, 0x77, 0xeb, 0x15, 0xbf, + 0x1e, 0xa2, 0x2d, 0x98, 0x68, 0x05, 0x64, 0x9d, 0x04, 0xaa, 0x68, 0xda, 0x3a, 0x5b, 0x3c, 0x3f, + 0xf2, 0xfc, 0xf9, 0xcc, 0xb1, 0x37, 0x51, 0x97, 0xbc, 0x28, 0xd8, 0x99, 0x3f, 0x21, 0xda, 0x9b, + 0x48, 0x40, 0x71, 0x92, 0xb2, 0xfd, 0xe7, 0x05, 0x38, 0x36, 0x77, 0xaf, 0x1d, 0x90, 0x45, 0x37, + 0xdc, 0x4a, 0x6e, 0xb8, 0xba, 0x1b, 0x6e, 0xad, 0xc6, 0x23, 0xa0, 0x56, 0xfa, 0xa2, 0x28, 0xc7, + 0x0a, 0x03, 0x3d, 0x07, 0x83, 0xf4, 0xf7, 0x0d, 0x5c, 0x16, 0x9f, 0x7c, 0x44, 0x20, 0x8f, 0x2c, + 0x3a, 0x91, 0xb3, 0xc8, 0x41, 0x58, 0xe2, 0xa0, 0x15, 0x18, 0xa9, 0xb1, 0xf3, 0x61, 0x63, 0xc5, + 0xaf, 0x13, 0xb6, 0xb6, 0x86, 0xe7, 0x9f, 0xa1, 0xe8, 0x0b, 0x71, 0xf1, 0xfe, 0x6e, 0x69, 0x9a, + 0xf7, 0x4d, 0x90, 0xd0, 0x60, 0x58, 0xaf, 0x8f, 0x6c, 0xb5, 0xdd, 0xfb, 0x18, 0x25, 0xc8, 0xd8, + 0xea, 0xe7, 0xb5, 0x9d, 0xdb, 0xcf, 0x76, 0xee, 0x68, 0xf6, 0xae, 0x45, 0x17, 0xa1, 0x6f, 0xcb, + 0xf5, 0xea, 0xd3, 0x03, 0x8c, 0xd6, 0x69, 0x3a, 0xe7, 0x57, 0x5d, 0xaf, 0xbe, 0xbf, 0x5b, 0x9a, + 0x32, 0xba, 0x43, 0x0b, 0x31, 0x43, 0xb5, 0xff, 0x1f, 0x0b, 0x4a, 0x0c, 0xb6, 0xec, 0x36, 0x48, + 0x85, 0x04, 0xa1, 0x1b, 0x46, 0xc4, 0x8b, 0x8c, 0x01, 0x7d, 0x1e, 0x20, 0x24, 0xb5, 0x80, 0x44, + 0xda, 0x90, 0xaa, 0x85, 0x51, 0x55, 0x10, 0xac, 0x61, 0xd1, 0xf3, 0x29, 0xdc, 0x74, 0x02, 0xb6, + 0xbe, 0xc4, 0xc0, 0xaa, 0xf3, 0xa9, 0x2a, 0x01, 0x38, 0xc6, 0x31, 0xce, 0xa7, 0x62, 0xb7, 0xf3, + 0x09, 0x7d, 0x0c, 0x26, 0xe2, 0xc6, 0xc2, 0x96, 0x53, 0x93, 0x03, 0xc8, 0x76, 0x70, 0xd5, 0x04, + 0xe1, 0x24, 0xae, 0xfd, 0x9f, 0x5b, 0x62, 0xf1, 0xd0, 0xaf, 0x7e, 0x9f, 0x7f, 0xab, 0xfd, 0x07, + 0x16, 0x0c, 0xce, 0xbb, 0x5e, 0xdd, 0xf5, 0x36, 0xd0, 0x67, 0x60, 0x88, 0x5e, 0x95, 0x75, 0x27, + 0x72, 0xc4, 0x31, 0xfc, 0x61, 0x6d, 0x6f, 0xa9, 0x9b, 0x6b, 0xb6, 0xb5, 0xb5, 0x41, 0x0b, 0xc2, + 0x59, 0x8a, 0x4d, 0x77, 0xdb, 0xf5, 0xdb, 0x9f, 0x25, 0xb5, 0x68, 0x85, 0x44, 0x4e, 0xfc, 0x39, + 0x71, 0x19, 0x56, 0x54, 0xd1, 0x55, 0x18, 0x88, 0x9c, 0x60, 0x83, 0x44, 0xe2, 0x3c, 0xce, 0x3c, + 0x37, 0x79, 0x4d, 0x4c, 0x77, 0x24, 0xf1, 0x6a, 0x24, 0xbe, 0xa5, 0xd6, 0x58, 0x55, 0x2c, 0x48, + 0xd8, 0xff, 0x6e, 0x10, 0x4e, 0x2e, 0x54, 0xcb, 0x39, 0xeb, 0xea, 0x1c, 0x0c, 0xd4, 0x03, 0x77, + 0x9b, 0x04, 0x62, 0x9c, 0x15, 0x95, 0x45, 0x56, 0x8a, 0x05, 0x14, 0x5d, 0x82, 0x51, 0x7e, 0x3f, + 0x5e, 0x71, 0xbc, 0x7a, 0x7c, 0x3c, 0x0a, 0xec, 0xd1, 0x9b, 0x1a, 0x0c, 0x1b, 0x98, 0x07, 0x5c, + 0x54, 0xe7, 0x12, 0x9b, 0x31, 0xef, 0xee, 0xfd, 0xa2, 0x05, 0x93, 0xbc, 0x99, 0xb9, 0x28, 0x0a, + 0xdc, 0xdb, 0xed, 0x88, 0x84, 0xd3, 0xfd, 0xec, 0xa4, 0x5b, 0xc8, 0x1a, 0xad, 0xdc, 0x11, 0x98, + 0xbd, 0x99, 0xa0, 0xc2, 0x0f, 0xc1, 0x69, 0xd1, 0xee, 0x64, 0x12, 0x8c, 0x53, 0xcd, 0xa2, 0x1f, + 0xb1, 0x60, 0xa6, 0xe6, 0x7b, 0x51, 0xe0, 0x37, 0x1a, 0x24, 0xa8, 0xb4, 0x6f, 0x37, 0xdc, 0x70, + 0x93, 0xaf, 0x53, 0x4c, 0xd6, 0xd9, 0x49, 0x90, 0x33, 0x87, 0x0a, 0x49, 0xcc, 0xe1, 0x99, 0xbd, + 0xdd, 0xd2, 0xcc, 0x42, 0x2e, 0x29, 0xdc, 0xa1, 0x19, 0xb4, 0x05, 0x88, 0xde, 0xec, 0xd5, 0xc8, + 0xd9, 0x20, 0x71, 0xe3, 0x83, 0xbd, 0x37, 0x7e, 0x7c, 0x6f, 0xb7, 0x84, 0x56, 0x53, 0x24, 0x70, + 0x06, 0x59, 0xf4, 0x2e, 0x1c, 0xa5, 0xa5, 0xa9, 0x6f, 0x1d, 0xea, 0xbd, 0xb9, 0xe9, 0xbd, 0xdd, + 0xd2, 0xd1, 0xd5, 0x0c, 0x22, 0x38, 0x93, 0x34, 0xfa, 0x21, 0x0b, 0x4e, 0xc6, 0x9f, 0xbf, 0x74, + 0xb7, 0xe5, 0x78, 0xf5, 0xb8, 0xe1, 0xe1, 0xde, 0x1b, 0xa6, 0x67, 0xf2, 0xc9, 0x85, 0x3c, 0x4a, + 0x38, 0xbf, 0x11, 0xe4, 0xc1, 0x11, 0xda, 0xb5, 0x64, 0xdb, 0xd0, 0x7b, 0xdb, 0x27, 0xf6, 0x76, + 0x4b, 0x47, 0x56, 0xd3, 0x34, 0x70, 0x16, 0xe1, 0x99, 0x05, 0x38, 0x96, 0xb9, 0x3a, 0xd1, 0x24, + 0x14, 0xb7, 0x08, 0x67, 0x02, 0x87, 0x31, 0xfd, 0x89, 0x8e, 0x42, 0xff, 0xb6, 0xd3, 0x68, 0x8b, + 0x8d, 0x89, 0xf9, 0x9f, 0x57, 0x0a, 0x97, 0x2c, 0xfb, 0x7f, 0x28, 0xc2, 0xc4, 0x42, 0xb5, 0x7c, + 0x5f, 0xbb, 0x5e, 0xbf, 0xf6, 0x0a, 0x1d, 0xaf, 0xbd, 0xf8, 0x12, 0x2d, 0xe6, 0x5e, 0xa2, 0x3f, + 0x98, 0xb1, 0x65, 0xfb, 0xd8, 0x96, 0xfd, 0x68, 0xce, 0x96, 0x7d, 0xc0, 0x1b, 0x75, 0x3b, 0x67, + 0xd5, 0xf6, 0xb3, 0x09, 0xcc, 0xe4, 0x90, 0x18, 0xef, 0x97, 0x3c, 0x6a, 0x0f, 0xb8, 0x74, 0x1f, + 0xcc, 0x3c, 0xd6, 0x60, 0x74, 0xc1, 0x69, 0x39, 0xb7, 0xdd, 0x86, 0x1b, 0xb9, 0x24, 0x44, 0x4f, + 0x42, 0xd1, 0xa9, 0xd7, 0x19, 0x77, 0x37, 0x3c, 0x7f, 0x6c, 0x6f, 0xb7, 0x54, 0x9c, 0xab, 0x53, + 0x36, 0x03, 0x14, 0xd6, 0x0e, 0xa6, 0x18, 0xe8, 0x69, 0xe8, 0xab, 0x07, 0x7e, 0x6b, 0xba, 0xc0, + 0x30, 0xe9, 0x2e, 0xef, 0x5b, 0x0c, 0xfc, 0x56, 0x02, 0x95, 0xe1, 0xd8, 0x7f, 0x56, 0x80, 0x53, + 0x0b, 0xa4, 0xb5, 0xb9, 0x5c, 0xcd, 0xb9, 0x2f, 0xce, 0xc3, 0x50, 0xd3, 0xf7, 0xdc, 0xc8, 0x0f, + 0x42, 0xd1, 0x34, 0x5b, 0x11, 0x2b, 0xa2, 0x0c, 0x2b, 0x28, 0x3a, 0x0b, 0x7d, 0xad, 0x98, 0x89, + 0x1d, 0x95, 0x0c, 0x30, 0x63, 0x5f, 0x19, 0x84, 0x62, 0xb4, 0x43, 0x12, 0x88, 0x15, 0xa3, 0x30, + 0x6e, 0x84, 0x24, 0xc0, 0x0c, 0x12, 0x73, 0x02, 0x94, 0x47, 0x10, 0x37, 0x42, 0x82, 0x13, 0xa0, + 0x10, 0xac, 0x61, 0xa1, 0x0a, 0x0c, 0x87, 0x89, 0x99, 0xed, 0x69, 0x6b, 0x8e, 0x31, 0x56, 0x41, + 0xcd, 0x64, 0x4c, 0xc4, 0xb8, 0xc1, 0x06, 0xba, 0xb2, 0x0a, 0x5f, 0x2f, 0x00, 0xe2, 0x43, 0xf8, + 0x5d, 0x36, 0x70, 0x37, 0xd2, 0x03, 0xd7, 0xfb, 0x96, 0x78, 0x50, 0xa3, 0xf7, 0xff, 0x5a, 0x70, + 0x6a, 0xc1, 0xf5, 0xea, 0x24, 0xc8, 0x59, 0x80, 0x0f, 0xe7, 0x29, 0x7f, 0x30, 0x26, 0xc5, 0x58, + 0x62, 0x7d, 0x0f, 0x60, 0x89, 0xd9, 0xff, 0x6c, 0x01, 0xe2, 0x9f, 0xfd, 0xbe, 0xfb, 0xd8, 0x1b, + 0xe9, 0x8f, 0x7d, 0x00, 0xcb, 0xc2, 0xbe, 0x06, 0xe3, 0x0b, 0x0d, 0x97, 0x78, 0x51, 0xb9, 0xb2, + 0xe0, 0x7b, 0xeb, 0xee, 0x06, 0x7a, 0x05, 0xc6, 0x23, 0xb7, 0x49, 0xfc, 0x76, 0x54, 0x25, 0x35, + 0xdf, 0x63, 0x2f, 0x57, 0xeb, 0x7c, 0xff, 0x3c, 0xda, 0xdb, 0x2d, 0x8d, 0xaf, 0x19, 0x10, 0x9c, + 0xc0, 0xb4, 0x7f, 0x95, 0x9e, 0x5b, 0x8d, 0x76, 0x18, 0x91, 0x60, 0x2d, 0x68, 0x87, 0xd1, 0x7c, + 0x9b, 0xf2, 0x9e, 0x95, 0xc0, 0xa7, 0xdd, 0x71, 0x7d, 0x0f, 0x9d, 0x32, 0x9e, 0xe3, 0x43, 0xf2, + 0x29, 0x2e, 0x9e, 0xdd, 0xb3, 0x00, 0xa1, 0xbb, 0xe1, 0x91, 0x40, 0x7b, 0x3e, 0x8c, 0xb3, 0xad, + 0xa2, 0x4a, 0xb1, 0x86, 0x81, 0x1a, 0x30, 0xd6, 0x70, 0x6e, 0x93, 0x46, 0x95, 0x34, 0x48, 0x2d, + 0xf2, 0x03, 0x21, 0xdf, 0x78, 0xa1, 0xb7, 0x77, 0xc0, 0x35, 0xbd, 0xea, 0xfc, 0xd4, 0xde, 0x6e, + 0x69, 0xcc, 0x28, 0xc2, 0x26, 0x71, 0x7a, 0x74, 0xf8, 0x2d, 0xfa, 0x15, 0x4e, 0x43, 0x7f, 0x7c, + 0x5e, 0x17, 0x65, 0x58, 0x41, 0xd5, 0xd1, 0xd1, 0x97, 0x77, 0x74, 0xd8, 0x7f, 0x47, 0x17, 0x9a, + 0xdf, 0x6c, 0xf9, 0x1e, 0xf1, 0xa2, 0x05, 0xdf, 0xab, 0x73, 0xc9, 0xd4, 0x2b, 0x86, 0xe8, 0xe4, + 0x5c, 0x42, 0x74, 0x72, 0x3c, 0x5d, 0x43, 0x93, 0x9e, 0x7c, 0x14, 0x06, 0xc2, 0xc8, 0x89, 0xda, + 0xa1, 0x18, 0xb8, 0x47, 0xe5, 0xb2, 0xab, 0xb2, 0xd2, 0xfd, 0xdd, 0xd2, 0x84, 0xaa, 0xc6, 0x8b, + 0xb0, 0xa8, 0x80, 0x9e, 0x82, 0xc1, 0x26, 0x09, 0x43, 0x67, 0x43, 0xb2, 0x0d, 0x13, 0xa2, 0xee, + 0xe0, 0x0a, 0x2f, 0xc6, 0x12, 0x8e, 0x1e, 0x83, 0x7e, 0x12, 0x04, 0x7e, 0x20, 0xbe, 0x6d, 0x4c, + 0x20, 0xf6, 0x2f, 0xd1, 0x42, 0xcc, 0x61, 0xf6, 0xff, 0x6c, 0xc1, 0x84, 0xea, 0x2b, 0x6f, 0xeb, + 0x10, 0x9e, 0x6b, 0x6f, 0x03, 0xd4, 0xe4, 0x07, 0x86, 0xec, 0x9a, 0x1d, 0x79, 0xfe, 0x5c, 0x26, + 0x47, 0x93, 0x1a, 0xc6, 0x98, 0xb2, 0x2a, 0x0a, 0xb1, 0x46, 0xcd, 0xfe, 0x63, 0x0b, 0x8e, 0x24, + 0xbe, 0xe8, 0x9a, 0x1b, 0x46, 0xe8, 0x9d, 0xd4, 0x57, 0xcd, 0xf6, 0xb8, 0xf8, 0xdc, 0x90, 0x7f, + 0x93, 0xda, 0xf3, 0xb2, 0x44, 0xfb, 0xa2, 0x2b, 0xd0, 0xef, 0x46, 0xa4, 0x29, 0x3f, 0xe6, 0xb1, + 0x8e, 0x1f, 0xc3, 0x7b, 0x15, 0xcf, 0x48, 0x99, 0xd6, 0xc4, 0x9c, 0x80, 0xfd, 0x67, 0x45, 0x18, + 0xe6, 0xfb, 0x7b, 0xc5, 0x69, 0x1d, 0xc2, 0x5c, 0x3c, 0x03, 0xc3, 0x6e, 0xb3, 0xd9, 0x8e, 0x9c, + 0xdb, 0xe2, 0xde, 0x1b, 0xe2, 0x67, 0x50, 0x59, 0x16, 0xe2, 0x18, 0x8e, 0xca, 0xd0, 0xc7, 0xba, + 0xc2, 0xbf, 0xf2, 0xc9, 0xec, 0xaf, 0x14, 0x7d, 0x9f, 0x5d, 0x74, 0x22, 0x87, 0xb3, 0x9c, 0x6a, + 0x5f, 0xd1, 0x22, 0xcc, 0x48, 0x20, 0x07, 0xe0, 0xb6, 0xeb, 0x39, 0xc1, 0x0e, 0x2d, 0x9b, 0x2e, + 0x32, 0x82, 0xcf, 0x75, 0x26, 0x38, 0xaf, 0xf0, 0x39, 0x59, 0xf5, 0x61, 0x31, 0x00, 0x6b, 0x44, + 0x67, 0x5e, 0x86, 0x61, 0x85, 0x7c, 0x10, 0xce, 0x71, 0xe6, 0x63, 0x30, 0x91, 0x68, 0xab, 0x5b, + 0xf5, 0x51, 0x9d, 0xf1, 0xfc, 0x43, 0x76, 0x64, 0x88, 0x5e, 0x2f, 0x79, 0xdb, 0xe2, 0x6e, 0xba, + 0x07, 0x47, 0x1b, 0x19, 0x47, 0xbe, 0x98, 0xd7, 0xde, 0xaf, 0x88, 0x53, 0xe2, 0xb3, 0x8f, 0x66, + 0x41, 0x71, 0x66, 0x1b, 0xc6, 0x89, 0x58, 0xe8, 0x74, 0x22, 0xd2, 0xf3, 0xee, 0xa8, 0xea, 0xfc, + 0x55, 0xb2, 0xa3, 0x0e, 0xd5, 0xef, 0x64, 0xf7, 0x4f, 0xf3, 0xd1, 0xe7, 0xc7, 0xe5, 0x88, 0x20, + 0x50, 0xbc, 0x4a, 0x76, 0xf8, 0x54, 0xe8, 0x5f, 0x57, 0xec, 0xf8, 0x75, 0x5f, 0xb3, 0x60, 0x4c, + 0x7d, 0xdd, 0x21, 0x9c, 0x0b, 0xf3, 0xe6, 0xb9, 0x70, 0xba, 0xe3, 0x02, 0xcf, 0x39, 0x11, 0xbe, + 0x5e, 0x80, 0x93, 0x0a, 0x87, 0x3e, 0xa2, 0xf8, 0x1f, 0xb1, 0xaa, 0x2e, 0xc0, 0xb0, 0xa7, 0xc4, + 0x89, 0x96, 0x29, 0xc7, 0x8b, 0x85, 0x89, 0x31, 0x0e, 0xbd, 0xf2, 0xbc, 0xf8, 0xd2, 0x1e, 0xd5, + 0xe5, 0xec, 0xe2, 0x72, 0x9f, 0x87, 0x62, 0xdb, 0xad, 0x8b, 0x0b, 0xe6, 0xc3, 0x72, 0xb4, 0x6f, + 0x94, 0x17, 0xf7, 0x77, 0x4b, 0x8f, 0xe6, 0xa9, 0x9c, 0xe8, 0xcd, 0x16, 0xce, 0xde, 0x28, 0x2f, + 0x62, 0x5a, 0x19, 0xcd, 0xc1, 0x84, 0xd4, 0xaa, 0xdd, 0xa4, 0x7c, 0xa9, 0xef, 0x89, 0x7b, 0x48, + 0x09, 0xcb, 0xb1, 0x09, 0xc6, 0x49, 0x7c, 0xb4, 0x08, 0x93, 0x5b, 0xed, 0xdb, 0xa4, 0x41, 0x22, + 0xfe, 0xc1, 0x57, 0x09, 0x17, 0x25, 0x0f, 0xc7, 0x4f, 0xd8, 0xab, 0x09, 0x38, 0x4e, 0xd5, 0xb0, + 0xbf, 0xcd, 0xee, 0x03, 0x31, 0x7a, 0x1a, 0x7f, 0xf3, 0x9d, 0x5c, 0xce, 0xbd, 0xac, 0x8a, 0xab, + 0x64, 0x67, 0xcd, 0xa7, 0x7c, 0x48, 0xf6, 0xaa, 0x30, 0xd6, 0x7c, 0x5f, 0xc7, 0x35, 0xff, 0xbb, + 0x05, 0x38, 0xa6, 0x46, 0xc0, 0xe0, 0x96, 0xbf, 0xdb, 0xc7, 0xe0, 0x22, 0x8c, 0xd4, 0xc9, 0xba, + 0xd3, 0x6e, 0x44, 0x4a, 0xaf, 0xd1, 0xcf, 0x55, 0x6d, 0x8b, 0x71, 0x31, 0xd6, 0x71, 0x0e, 0x30, + 0x6c, 0xbf, 0x39, 0xc6, 0x2e, 0xe2, 0xc8, 0xa1, 0x6b, 0x5c, 0xed, 0x1a, 0x2b, 0x77, 0xd7, 0x3c, + 0x06, 0xfd, 0x6e, 0x93, 0x32, 0x66, 0x05, 0x93, 0xdf, 0x2a, 0xd3, 0x42, 0xcc, 0x61, 0xe8, 0x09, + 0x18, 0xac, 0xf9, 0xcd, 0xa6, 0xe3, 0xd5, 0xd9, 0x95, 0x37, 0x3c, 0x3f, 0x42, 0x79, 0xb7, 0x05, + 0x5e, 0x84, 0x25, 0x8c, 0x32, 0xdf, 0x4e, 0xb0, 0xc1, 0x85, 0x3d, 0x82, 0xf9, 0x9e, 0x0b, 0x36, + 0x42, 0xcc, 0x4a, 0xe9, 0x5b, 0xf5, 0x8e, 0x1f, 0x6c, 0xb9, 0xde, 0xc6, 0xa2, 0x1b, 0x88, 0x2d, + 0xa1, 0xee, 0xc2, 0x5b, 0x0a, 0x82, 0x35, 0x2c, 0xb4, 0x0c, 0xfd, 0x2d, 0x3f, 0x88, 0xc2, 0xe9, + 0x01, 0x36, 0xdc, 0x8f, 0xe6, 0x1c, 0x44, 0xfc, 0x6b, 0x2b, 0x7e, 0x10, 0xc5, 0x1f, 0x40, 0xff, + 0x85, 0x98, 0x57, 0x47, 0xd7, 0x60, 0x90, 0x78, 0xdb, 0xcb, 0x81, 0xdf, 0x9c, 0x3e, 0x92, 0x4f, + 0x69, 0x89, 0xa3, 0xf0, 0x65, 0x16, 0xf3, 0xa8, 0xa2, 0x18, 0x4b, 0x12, 0xe8, 0xa3, 0x50, 0x24, + 0xde, 0xf6, 0xf4, 0x20, 0xa3, 0x34, 0x93, 0x43, 0xe9, 0xa6, 0x13, 0xc4, 0x67, 0xfe, 0x92, 0xb7, + 0x8d, 0x69, 0x1d, 0xf4, 0x09, 0x18, 0x96, 0x07, 0x46, 0x28, 0xa4, 0xa8, 0x99, 0x0b, 0x56, 0x1e, + 0x33, 0x98, 0xbc, 0xdb, 0x76, 0x03, 0xd2, 0x24, 0x5e, 0x14, 0xc6, 0x27, 0xa4, 0x84, 0x86, 0x38, + 0xa6, 0x86, 0x6a, 0x30, 0x1a, 0x90, 0xd0, 0xbd, 0x47, 0x2a, 0x7e, 0xc3, 0xad, 0xed, 0x4c, 0x9f, + 0x60, 0xdd, 0x7b, 0xaa, 0xe3, 0x90, 0x61, 0xad, 0x42, 0x2c, 0xe5, 0xd7, 0x4b, 0xb1, 0x41, 0x14, + 0xbd, 0x05, 0x63, 0x01, 0x09, 0x23, 0x27, 0x88, 0x44, 0x2b, 0xd3, 0x4a, 0x2b, 0x37, 0x86, 0x75, + 0x00, 0x7f, 0x4e, 0xc4, 0xcd, 0xc4, 0x10, 0x6c, 0x52, 0x40, 0x9f, 0x90, 0x2a, 0x87, 0x15, 0xbf, + 0xed, 0x45, 0xe1, 0xf4, 0x30, 0xeb, 0x77, 0xa6, 0x6e, 0xfa, 0x66, 0x8c, 0x97, 0xd4, 0x49, 0xf0, + 0xca, 0xd8, 0x20, 0x85, 0x3e, 0x05, 0x63, 0xfc, 0x3f, 0x57, 0xa9, 0x86, 0xd3, 0xc7, 0x18, 0xed, + 0xb3, 0xf9, 0xb4, 0x39, 0xe2, 0xfc, 0x31, 0x41, 0x7c, 0x4c, 0x2f, 0x0d, 0xb1, 0x49, 0x0d, 0x61, + 0x18, 0x6b, 0xb8, 0xdb, 0xc4, 0x23, 0x61, 0x58, 0x09, 0xfc, 0xdb, 0x44, 0x48, 0x88, 0x4f, 0x66, + 0xab, 0x60, 0xfd, 0xdb, 0x44, 0x3c, 0x02, 0xf5, 0x3a, 0xd8, 0x24, 0x81, 0x6e, 0xc0, 0x38, 0x7d, + 0x92, 0xbb, 0x31, 0xd1, 0x91, 0x6e, 0x44, 0xd9, 0xc3, 0x19, 0x1b, 0x95, 0x70, 0x82, 0x08, 0xba, + 0x0e, 0xa3, 0x6c, 0xcc, 0xdb, 0x2d, 0x4e, 0xf4, 0x78, 0x37, 0xa2, 0xcc, 0xa0, 0xa0, 0xaa, 0x55, + 0xc1, 0x06, 0x01, 0xf4, 0x26, 0x0c, 0x37, 0xdc, 0x75, 0x52, 0xdb, 0xa9, 0x35, 0xc8, 0xf4, 0x28, + 0xa3, 0x96, 0x79, 0x18, 0x5e, 0x93, 0x48, 0x9c, 0x3f, 0x57, 0x7f, 0x71, 0x5c, 0x1d, 0xdd, 0x84, + 0xe3, 0x11, 0x09, 0x9a, 0xae, 0xe7, 0xd0, 0x43, 0x4c, 0x3c, 0x09, 0x99, 0x66, 0x7c, 0x8c, 0xad, + 0xae, 0x33, 0x62, 0x36, 0x8e, 0xaf, 0x65, 0x62, 0xe1, 0x9c, 0xda, 0xe8, 0x2e, 0x4c, 0x67, 0x40, + 0xf8, 0xba, 0x3d, 0xca, 0x28, 0xbf, 0x26, 0x28, 0x4f, 0xaf, 0xe5, 0xe0, 0xed, 0x77, 0x80, 0xe1, + 0x5c, 0xea, 0xe8, 0x3a, 0x4c, 0xb0, 0x93, 0xb3, 0xd2, 0x6e, 0x34, 0x44, 0x83, 0xe3, 0xac, 0xc1, + 0x27, 0x24, 0x1f, 0x51, 0x36, 0xc1, 0xfb, 0xbb, 0x25, 0x88, 0xff, 0xe1, 0x64, 0x6d, 0x74, 0x9b, + 0x29, 0x61, 0xdb, 0x81, 0x1b, 0xed, 0xd0, 0x5d, 0x45, 0xee, 0x46, 0xd3, 0x13, 0x1d, 0x05, 0x52, + 0x3a, 0xaa, 0xd2, 0xd4, 0xea, 0x85, 0x38, 0x49, 0x90, 0x5e, 0x05, 0x61, 0x54, 0x77, 0xbd, 0xe9, + 0x49, 0xfe, 0x9e, 0x92, 0x27, 0x69, 0x95, 0x16, 0x62, 0x0e, 0x63, 0x0a, 0x58, 0xfa, 0xe3, 0x3a, + 0xbd, 0x71, 0xa7, 0x18, 0x62, 0xac, 0x80, 0x95, 0x00, 0x1c, 0xe3, 0x50, 0x26, 0x38, 0x8a, 0x76, + 0xa6, 0x11, 0x43, 0x55, 0x07, 0xe2, 0xda, 0xda, 0x27, 0x30, 0x2d, 0xb7, 0x6f, 0xc3, 0xb8, 0x3a, + 0x26, 0xd8, 0x98, 0xa0, 0x12, 0xf4, 0x33, 0xb6, 0x4f, 0x88, 0x4f, 0x87, 0x69, 0x17, 0x18, 0x4b, + 0x88, 0x79, 0x39, 0xeb, 0x82, 0x7b, 0x8f, 0xcc, 0xef, 0x44, 0x84, 0xcb, 0x22, 0x8a, 0x5a, 0x17, + 0x24, 0x00, 0xc7, 0x38, 0xf6, 0xbf, 0xe7, 0xec, 0x73, 0x7c, 0x4b, 0xf4, 0x70, 0x2f, 0x3e, 0x0b, + 0x43, 0xcc, 0xf0, 0xc3, 0x0f, 0xb8, 0x76, 0xb6, 0x3f, 0x66, 0x98, 0xaf, 0x88, 0x72, 0xac, 0x30, + 0xd0, 0xab, 0x30, 0x56, 0xd3, 0x1b, 0x10, 0x97, 0xba, 0x3a, 0x46, 0x8c, 0xd6, 0xb1, 0x89, 0x8b, + 0x2e, 0xc1, 0x10, 0xb3, 0x71, 0xaa, 0xf9, 0x0d, 0xc1, 0x6d, 0x4a, 0xce, 0x64, 0xa8, 0x22, 0xca, + 0xf7, 0xb5, 0xdf, 0x58, 0x61, 0xa3, 0x73, 0x30, 0x40, 0xbb, 0x50, 0xae, 0x88, 0xeb, 0x54, 0x49, + 0x02, 0xaf, 0xb0, 0x52, 0x2c, 0xa0, 0xf6, 0x1f, 0x5b, 0x8c, 0x97, 0x4a, 0x9f, 0xf9, 0xe8, 0x0a, + 0xbb, 0x34, 0xd8, 0x0d, 0xa2, 0x69, 0xe1, 0x1f, 0xd7, 0x6e, 0x02, 0x05, 0xdb, 0x4f, 0xfc, 0xc7, + 0x46, 0x4d, 0xf4, 0x76, 0xf2, 0x66, 0xe0, 0x0c, 0xc5, 0x8b, 0x72, 0x08, 0x92, 0xb7, 0xc3, 0x23, + 0xf1, 0x15, 0x47, 0xfb, 0xd3, 0xe9, 0x8a, 0xb0, 0x7f, 0xaa, 0xa0, 0xad, 0x92, 0x6a, 0xe4, 0x44, + 0x04, 0x55, 0x60, 0xf0, 0x8e, 0xe3, 0x46, 0xae, 0xb7, 0x21, 0xf8, 0xbe, 0xce, 0x17, 0x1d, 0xab, + 0x74, 0x8b, 0x57, 0xe0, 0xdc, 0x8b, 0xf8, 0x83, 0x25, 0x19, 0x4a, 0x31, 0x68, 0x7b, 0x1e, 0xa5, + 0x58, 0xe8, 0x95, 0x22, 0xe6, 0x15, 0x38, 0x45, 0xf1, 0x07, 0x4b, 0x32, 0xe8, 0x1d, 0x00, 0x79, + 0x42, 0x90, 0xba, 0x90, 0x1d, 0x3e, 0xdb, 0x9d, 0xe8, 0x9a, 0xaa, 0xc3, 0x85, 0x93, 0xf1, 0x7f, + 0xac, 0xd1, 0xb3, 0x23, 0x6d, 0x4e, 0xf5, 0xce, 0xa0, 0x4f, 0xd2, 0x2d, 0xea, 0x04, 0x11, 0xa9, + 0xcf, 0x45, 0x62, 0x70, 0x9e, 0xee, 0xed, 0x71, 0xb8, 0xe6, 0x36, 0x89, 0xbe, 0x9d, 0x05, 0x11, + 0x1c, 0xd3, 0xb3, 0x7f, 0xbf, 0x08, 0xd3, 0x79, 0xdd, 0xa5, 0x9b, 0x86, 0xdc, 0x75, 0xa3, 0x05, + 0xca, 0xd6, 0x5a, 0xe6, 0xa6, 0x59, 0x12, 0xe5, 0x58, 0x61, 0xd0, 0xd5, 0x1b, 0xba, 0x1b, 0xf2, + 0x6d, 0xdf, 0x1f, 0xaf, 0xde, 0x2a, 0x2b, 0xc5, 0x02, 0x4a, 0xf1, 0x02, 0xe2, 0x84, 0xc2, 0xf8, + 0x4e, 0x5b, 0xe5, 0x98, 0x95, 0x62, 0x01, 0xd5, 0xa5, 0x8c, 0x7d, 0x5d, 0xa4, 0x8c, 0xc6, 0x10, + 0xf5, 0x3f, 0xd8, 0x21, 0x42, 0x9f, 0x06, 0x58, 0x77, 0x3d, 0x37, 0xdc, 0x64, 0xd4, 0x07, 0x0e, + 0x4c, 0x5d, 0x31, 0xc5, 0xcb, 0x8a, 0x0a, 0xd6, 0x28, 0xa2, 0x97, 0x60, 0x44, 0x1d, 0x20, 0xe5, + 0x45, 0xa6, 0xfa, 0xd7, 0x4c, 0xa9, 0xe2, 0xd3, 0x74, 0x11, 0xeb, 0x78, 0xf6, 0x67, 0x93, 0xeb, + 0x45, 0xec, 0x00, 0x6d, 0x7c, 0xad, 0x5e, 0xc7, 0xb7, 0xd0, 0x79, 0x7c, 0xed, 0xbf, 0x1e, 0x86, + 0x09, 0xa3, 0xb1, 0x76, 0xd8, 0xc3, 0x99, 0x7b, 0x99, 0x5e, 0x40, 0x4e, 0x44, 0xc4, 0xfe, 0xb3, + 0xbb, 0x6f, 0x15, 0xfd, 0x92, 0xa2, 0x3b, 0x80, 0xd7, 0x47, 0x9f, 0x86, 0xe1, 0x86, 0x13, 0x32, + 0x89, 0x25, 0x11, 0xfb, 0xae, 0x17, 0x62, 0xf1, 0x83, 0xd0, 0x09, 0x23, 0xed, 0xd6, 0xe7, 0xb4, + 0x63, 0x92, 0xf4, 0xa6, 0xa4, 0xfc, 0x95, 0xb4, 0xee, 0x54, 0x9d, 0xa0, 0x4c, 0xd8, 0x0e, 0xe6, + 0x30, 0x74, 0x89, 0x1d, 0xad, 0x74, 0x55, 0x2c, 0x50, 0x6e, 0x94, 0x2d, 0xb3, 0x7e, 0x83, 0xc9, + 0x56, 0x30, 0x6c, 0x60, 0xc6, 0x6f, 0xb2, 0x81, 0x0e, 0x6f, 0xb2, 0xa7, 0x60, 0x90, 0xfd, 0x50, + 0x2b, 0x40, 0xcd, 0x46, 0x99, 0x17, 0x63, 0x09, 0x4f, 0x2e, 0x98, 0xa1, 0xde, 0x16, 0x0c, 0x7d, + 0xf5, 0x89, 0x45, 0xcd, 0xcc, 0x2e, 0x86, 0xf8, 0x29, 0x27, 0x96, 0x3c, 0x96, 0x30, 0xf4, 0x6b, + 0x16, 0x20, 0xa7, 0x41, 0x5f, 0xcb, 0xb4, 0x58, 0x3d, 0x6e, 0x80, 0xb1, 0xda, 0xaf, 0x76, 0x1d, + 0xf6, 0x76, 0x38, 0x3b, 0x97, 0xaa, 0xcd, 0x25, 0xa5, 0xaf, 0x88, 0x2e, 0xa2, 0x34, 0x82, 0x7e, + 0x19, 0x5d, 0x73, 0xc3, 0xe8, 0xf3, 0x7f, 0x9f, 0xb8, 0x9c, 0x32, 0xba, 0x84, 0x6e, 0xe8, 0x8f, + 0xaf, 0x91, 0x03, 0x3e, 0xbe, 0xc6, 0x72, 0x1f, 0x5e, 0xdf, 0x9f, 0x78, 0xc0, 0x8c, 0xb2, 0x2f, + 0x7f, 0xa2, 0xcb, 0x03, 0x46, 0x88, 0xd3, 0x7b, 0x79, 0xc6, 0x54, 0x84, 0x1e, 0x78, 0x8c, 0x75, + 0xb9, 0xf3, 0x23, 0xf8, 0x46, 0x48, 0x82, 0xf9, 0x93, 0x52, 0x4d, 0xbc, 0xaf, 0xf3, 0x1e, 0x9a, + 0xde, 0xf8, 0x87, 0x2c, 0x98, 0x4e, 0x0f, 0x10, 0xef, 0xd2, 0xf4, 0x38, 0xeb, 0xbf, 0xdd, 0x69, + 0x64, 0x44, 0xe7, 0xa5, 0xb9, 0xeb, 0xf4, 0x5c, 0x0e, 0x2d, 0x9c, 0xdb, 0x0a, 0xba, 0x04, 0x10, + 0x46, 0x7e, 0x8b, 0x9f, 0xf5, 0x8c, 0x99, 0x1d, 0x66, 0x06, 0x17, 0x50, 0x55, 0xa5, 0xfb, 0xf1, + 0x5d, 0xa0, 0xe1, 0xce, 0xb4, 0xe1, 0x44, 0xce, 0x8a, 0xc9, 0x90, 0x77, 0x2f, 0xea, 0xf2, 0xee, + 0x2e, 0x52, 0xd2, 0x59, 0x39, 0xa7, 0xb3, 0x6f, 0xb5, 0x1d, 0x2f, 0x72, 0xa3, 0x1d, 0x5d, 0x3e, + 0xee, 0x81, 0x39, 0x94, 0xe8, 0x53, 0xd0, 0xdf, 0x70, 0xbd, 0xf6, 0x5d, 0x71, 0xc7, 0x9e, 0xcb, + 0x7e, 0xfe, 0x78, 0xed, 0xbb, 0xe6, 0xe4, 0x94, 0xe8, 0x56, 0x66, 0xe5, 0xfb, 0xbb, 0x25, 0x94, + 0x46, 0xc0, 0x9c, 0xaa, 0xfd, 0x34, 0x8c, 0x2f, 0x3a, 0xa4, 0xe9, 0x7b, 0x4b, 0x5e, 0xbd, 0xe5, + 0xbb, 0x5e, 0x84, 0xa6, 0xa1, 0x8f, 0x31, 0x97, 0xfc, 0x6a, 0xed, 0xa3, 0x83, 0x8f, 0x59, 0x89, + 0xbd, 0x01, 0xc7, 0x16, 0xfd, 0x3b, 0xde, 0x1d, 0x27, 0xa8, 0xcf, 0x55, 0xca, 0x9a, 0xbc, 0x70, + 0x55, 0xca, 0xab, 0xac, 0x7c, 0x69, 0x80, 0x56, 0x93, 0x2f, 0xc2, 0x65, 0xb7, 0x41, 0x72, 0xa4, + 0xba, 0x3f, 0x5b, 0x30, 0x5a, 0x8a, 0xf1, 0x95, 0x4e, 0xd2, 0xca, 0x35, 0x67, 0x78, 0x0b, 0x86, + 0xd6, 0x5d, 0xd2, 0xa8, 0x63, 0xb2, 0x2e, 0x66, 0xe3, 0xc9, 0x7c, 0x83, 0xc7, 0x65, 0x8a, 0xa9, + 0x94, 0xa7, 0x4c, 0xda, 0xb5, 0x2c, 0x2a, 0x63, 0x45, 0x06, 0x6d, 0xc1, 0xa4, 0x9c, 0x33, 0x09, + 0x15, 0xe7, 0xfd, 0x53, 0x9d, 0x96, 0xaf, 0x49, 0x9c, 0x19, 0x7f, 0xe3, 0x04, 0x19, 0x9c, 0x22, + 0x8c, 0x4e, 0x41, 0x5f, 0x93, 0x72, 0x36, 0x7d, 0x6c, 0xf8, 0x99, 0x78, 0x8b, 0x49, 0xea, 0x58, + 0xa9, 0xfd, 0xf3, 0x16, 0x9c, 0x48, 0x8d, 0x8c, 0x90, 0x58, 0x3e, 0xe0, 0x59, 0x48, 0x4a, 0x10, + 0x0b, 0xdd, 0x25, 0x88, 0xf6, 0x7f, 0x61, 0xc1, 0xd1, 0xa5, 0x66, 0x2b, 0xda, 0x59, 0x74, 0x4d, + 0xdb, 0x83, 0x97, 0x61, 0xa0, 0x49, 0xea, 0x6e, 0xbb, 0x29, 0x66, 0xae, 0x24, 0x6f, 0xff, 0x15, + 0x56, 0x4a, 0x4f, 0x90, 0x6a, 0xe4, 0x07, 0xce, 0x06, 0xe1, 0x05, 0x58, 0xa0, 0x33, 0x1e, 0xca, + 0xbd, 0x47, 0xae, 0xb9, 0x4d, 0x37, 0xba, 0xbf, 0xdd, 0x25, 0xcc, 0x06, 0x24, 0x11, 0x1c, 0xd3, + 0xb3, 0xbf, 0x69, 0xc1, 0x84, 0x5c, 0xf7, 0x73, 0xf5, 0x7a, 0x40, 0xc2, 0x10, 0xcd, 0x40, 0xc1, + 0x6d, 0x89, 0x5e, 0x82, 0xe8, 0x65, 0xa1, 0x5c, 0xc1, 0x05, 0xb7, 0x25, 0x9f, 0x6b, 0x8c, 0xc1, + 0x28, 0x9a, 0x16, 0x14, 0x57, 0x44, 0x39, 0x56, 0x18, 0xe8, 0x3c, 0x0c, 0x79, 0x7e, 0x9d, 0xbf, + 0x78, 0x84, 0x0e, 0x9d, 0x62, 0xae, 0x8a, 0x32, 0xac, 0xa0, 0xa8, 0x02, 0xc3, 0xdc, 0xbe, 0x36, + 0x5e, 0xb4, 0x3d, 0x59, 0xe9, 0xb2, 0x2f, 0x5b, 0x93, 0x35, 0x71, 0x4c, 0xc4, 0xfe, 0x53, 0x0b, + 0x46, 0xe5, 0x97, 0xf5, 0xf8, 0x16, 0xa5, 0x5b, 0x2b, 0x7e, 0x87, 0xc6, 0x5b, 0x8b, 0xbe, 0x25, + 0x19, 0xc4, 0x78, 0x42, 0x16, 0x0f, 0xf4, 0x84, 0xbc, 0x08, 0x23, 0x4e, 0xab, 0x55, 0x31, 0xdf, + 0x9f, 0x6c, 0x29, 0xcd, 0xc5, 0xc5, 0x58, 0xc7, 0xb1, 0x7f, 0xae, 0x00, 0xe3, 0xf2, 0x0b, 0xaa, + 0xed, 0xdb, 0x21, 0x89, 0xd0, 0x1a, 0x0c, 0x3b, 0x7c, 0x96, 0x88, 0x5c, 0xe4, 0x8f, 0x65, 0xcb, + 0x45, 0x8d, 0x29, 0x8d, 0x19, 0xe9, 0x39, 0x59, 0x1b, 0xc7, 0x84, 0x50, 0x03, 0xa6, 0x3c, 0x3f, + 0x62, 0x4c, 0x95, 0x82, 0x77, 0x52, 0x55, 0x27, 0xa9, 0x9f, 0x14, 0xd4, 0xa7, 0x56, 0x93, 0x54, + 0x70, 0x9a, 0x30, 0x5a, 0x92, 0xb2, 0xe6, 0x62, 0xbe, 0x90, 0x50, 0x9f, 0xb8, 0x6c, 0x51, 0xb3, + 0xfd, 0x47, 0x16, 0x0c, 0x4b, 0xb4, 0xc3, 0xb0, 0x4a, 0x58, 0x81, 0xc1, 0x90, 0x4d, 0x82, 0x1c, + 0x1a, 0xbb, 0x53, 0xc7, 0xf9, 0x7c, 0xc5, 0xbc, 0x22, 0xff, 0x1f, 0x62, 0x49, 0x83, 0xa9, 0x1a, + 0x55, 0xf7, 0xdf, 0x27, 0xaa, 0x46, 0xd5, 0x9f, 0x9c, 0x4b, 0xe9, 0x1f, 0x58, 0x9f, 0x35, 0xd9, + 0x3d, 0x7d, 0xd2, 0xb4, 0x02, 0xb2, 0xee, 0xde, 0x4d, 0x3e, 0x69, 0x2a, 0xac, 0x14, 0x0b, 0x28, + 0x7a, 0x07, 0x46, 0x6b, 0x52, 0xc7, 0x14, 0xef, 0xf0, 0x73, 0x1d, 0xf5, 0x9d, 0x4a, 0x35, 0xce, + 0x65, 0xa4, 0x0b, 0x5a, 0x7d, 0x6c, 0x50, 0x33, 0xed, 0xc7, 0x8a, 0xdd, 0xec, 0xc7, 0x62, 0xba, + 0xf9, 0xd6, 0x54, 0xbf, 0x60, 0xc1, 0x00, 0xd7, 0x2d, 0xf4, 0xa6, 0xda, 0xd1, 0x2c, 0x05, 0xe2, + 0xb1, 0xbb, 0x49, 0x0b, 0x05, 0x67, 0x83, 0x56, 0x60, 0x98, 0xfd, 0x60, 0xba, 0x91, 0x62, 0xbe, + 0xb7, 0x19, 0x6f, 0x55, 0xef, 0xe0, 0x4d, 0x59, 0x0d, 0xc7, 0x14, 0xec, 0x9f, 0x2e, 0xd2, 0xd3, + 0x2d, 0x46, 0x35, 0x2e, 0x7d, 0xeb, 0xe1, 0x5d, 0xfa, 0x85, 0x87, 0x75, 0xe9, 0x6f, 0xc0, 0x44, + 0x4d, 0xb3, 0x2b, 0x88, 0x67, 0xf2, 0x7c, 0xc7, 0x45, 0xa2, 0x99, 0x20, 0x70, 0xe9, 0xeb, 0x82, + 0x49, 0x04, 0x27, 0xa9, 0xa2, 0x4f, 0xc2, 0x28, 0x9f, 0x67, 0xd1, 0x0a, 0x37, 0xc1, 0x7b, 0x22, + 0x7f, 0xbd, 0xe8, 0x4d, 0x70, 0x69, 0xbd, 0x56, 0x1d, 0x1b, 0xc4, 0xec, 0x7f, 0xb1, 0x00, 0x2d, + 0xb5, 0x36, 0x49, 0x93, 0x04, 0x4e, 0x23, 0x56, 0x0f, 0x7e, 0xc9, 0x82, 0x69, 0x92, 0x2a, 0x5e, + 0xf0, 0x9b, 0x4d, 0x21, 0x0c, 0xc8, 0x91, 0x57, 0x2d, 0xe5, 0xd4, 0x89, 0x1f, 0x04, 0x79, 0x18, + 0x38, 0xb7, 0x3d, 0xb4, 0x02, 0x47, 0xf8, 0x2d, 0xa9, 0x00, 0x9a, 0x95, 0xde, 0x23, 0x82, 0xf0, + 0x91, 0xb5, 0x34, 0x0a, 0xce, 0xaa, 0x67, 0xff, 0xd1, 0x18, 0xe4, 0xf6, 0xe2, 0x03, 0xbd, 0xe8, + 0x07, 0x7a, 0xd1, 0x0f, 0xf4, 0xa2, 0x1f, 0xe8, 0x45, 0x3f, 0xd0, 0x8b, 0x7e, 0xa0, 0x17, 0x7d, + 0x9f, 0xea, 0x45, 0x7f, 0xc6, 0x82, 0x63, 0xea, 0xfa, 0x32, 0x1e, 0xec, 0x9f, 0x83, 0x23, 0x7c, + 0xbb, 0x2d, 0x34, 0x1c, 0xb7, 0xb9, 0x46, 0x9a, 0xad, 0x86, 0x13, 0x49, 0xeb, 0xa7, 0x8b, 0x99, + 0x2b, 0x37, 0xe1, 0x62, 0x61, 0x54, 0xe4, 0xbe, 0x6a, 0x19, 0x00, 0x9c, 0xd5, 0x8c, 0xfd, 0xfb, + 0x43, 0xd0, 0xbf, 0xb4, 0x4d, 0xbc, 0xe8, 0x10, 0x9e, 0x36, 0x35, 0x18, 0x77, 0xbd, 0x6d, 0xbf, + 0xb1, 0x4d, 0xea, 0x1c, 0x7e, 0x90, 0x17, 0xf8, 0x71, 0x41, 0x7a, 0xbc, 0x6c, 0x90, 0xc0, 0x09, + 0x92, 0x0f, 0x43, 0xbb, 0x74, 0x19, 0x06, 0xf8, 0xe5, 0x23, 0x54, 0x4b, 0x99, 0x67, 0x36, 0x1b, + 0x44, 0x71, 0xa5, 0xc6, 0x9a, 0x2f, 0x7e, 0xb9, 0x89, 0xea, 0xe8, 0xb3, 0x30, 0xbe, 0xee, 0x06, + 0x61, 0xb4, 0xe6, 0x36, 0xe9, 0xd5, 0xd0, 0x6c, 0xdd, 0x87, 0x36, 0x49, 0x8d, 0xc3, 0xb2, 0x41, + 0x09, 0x27, 0x28, 0xa3, 0x0d, 0x18, 0x6b, 0x38, 0x7a, 0x53, 0x83, 0x07, 0x6e, 0x4a, 0xdd, 0x0e, + 0xd7, 0x74, 0x42, 0xd8, 0xa4, 0x4b, 0xb7, 0x53, 0x8d, 0x29, 0x44, 0x86, 0x98, 0x38, 0x43, 0x6d, + 0x27, 0xae, 0x09, 0xe1, 0x30, 0xca, 0xa0, 0x31, 0x47, 0x85, 0x61, 0x93, 0x41, 0xd3, 0xdc, 0x11, + 0x3e, 0x03, 0xc3, 0x84, 0x0e, 0x21, 0x25, 0x2c, 0x2e, 0x98, 0x0b, 0xbd, 0xf5, 0x75, 0xc5, 0xad, + 0x05, 0xbe, 0xa9, 0xc7, 0x5b, 0x92, 0x94, 0x70, 0x4c, 0x14, 0x2d, 0xc0, 0x40, 0x48, 0x02, 0x57, + 0xe9, 0x0a, 0x3a, 0x4c, 0x23, 0x43, 0xe3, 0xce, 0x90, 0xfc, 0x37, 0x16, 0x55, 0xe9, 0xf2, 0x72, + 0x98, 0x28, 0x96, 0x5d, 0x06, 0xda, 0xf2, 0x9a, 0x63, 0xa5, 0x58, 0x40, 0xd1, 0x9b, 0x30, 0x18, + 0x90, 0x06, 0x53, 0x14, 0x8f, 0xf5, 0xbe, 0xc8, 0xb9, 0xde, 0x99, 0xd7, 0xc3, 0x92, 0x00, 0xba, + 0x0a, 0x28, 0x20, 0x94, 0xc1, 0x73, 0xbd, 0x0d, 0x65, 0xbe, 0x2f, 0x0e, 0x5a, 0xc5, 0x48, 0xe3, + 0x18, 0x43, 0xfa, 0xc1, 0xe2, 0x8c, 0x6a, 0xe8, 0x32, 0x4c, 0xa9, 0xd2, 0xb2, 0x17, 0x46, 0x0e, + 0x3d, 0xe0, 0xb8, 0xb8, 0x5e, 0xc9, 0x57, 0x70, 0x12, 0x01, 0xa7, 0xeb, 0xd8, 0xbf, 0x61, 0x01, + 0x1f, 0xe7, 0x43, 0x90, 0x2a, 0xbc, 0x6e, 0x4a, 0x15, 0x4e, 0xe6, 0xce, 0x5c, 0x8e, 0x44, 0xe1, + 0x37, 0x2c, 0x18, 0xd1, 0x66, 0x36, 0x5e, 0xb3, 0x56, 0x87, 0x35, 0xdb, 0x86, 0x49, 0xba, 0xd2, + 0xaf, 0xdf, 0x0e, 0x49, 0xb0, 0x4d, 0xea, 0x6c, 0x61, 0x16, 0xee, 0x6f, 0x61, 0x2a, 0x53, 0xe1, + 0x6b, 0x09, 0x82, 0x38, 0xd5, 0x84, 0xfd, 0x19, 0xd9, 0x55, 0x65, 0x59, 0x5d, 0x53, 0x73, 0x9e, + 0xb0, 0xac, 0x56, 0xb3, 0x8a, 0x63, 0x1c, 0xba, 0xd5, 0x36, 0xfd, 0x30, 0x4a, 0x5a, 0x56, 0x5f, + 0xf1, 0xc3, 0x08, 0x33, 0x88, 0xfd, 0x02, 0xc0, 0xd2, 0x5d, 0x52, 0xe3, 0x2b, 0x56, 0x7f, 0xf4, + 0x58, 0xf9, 0x8f, 0x1e, 0xfb, 0x6f, 0x2c, 0x18, 0x5f, 0x5e, 0x30, 0x6e, 0xae, 0x59, 0x00, 0xfe, + 0x52, 0xbb, 0x75, 0x6b, 0x55, 0x9a, 0xf7, 0x70, 0x0b, 0x07, 0x55, 0x8a, 0x35, 0x0c, 0x74, 0x12, + 0x8a, 0x8d, 0xb6, 0x27, 0xc4, 0x9e, 0x83, 0xf4, 0x7a, 0xbc, 0xd6, 0xf6, 0x30, 0x2d, 0xd3, 0x7c, + 0xe0, 0x8a, 0x3d, 0xfb, 0xc0, 0x75, 0x0d, 0xc5, 0x83, 0x4a, 0xd0, 0x7f, 0xe7, 0x8e, 0x5b, 0xe7, + 0x11, 0x06, 0x84, 0xe9, 0xd1, 0xad, 0x5b, 0xe5, 0xc5, 0x10, 0xf3, 0x72, 0xfb, 0xcb, 0x45, 0x98, + 0x59, 0x6e, 0x90, 0xbb, 0xef, 0x31, 0xca, 0x42, 0xaf, 0x1e, 0x7c, 0x07, 0x13, 0x20, 0x1d, 0xd4, + 0x4b, 0xb3, 0xfb, 0x78, 0xac, 0xc3, 0x20, 0x37, 0x2c, 0x96, 0x31, 0x17, 0x32, 0xd5, 0xb9, 0xf9, + 0x03, 0x32, 0xcb, 0x0d, 0x94, 0x85, 0x3a, 0x57, 0x5d, 0x98, 0xa2, 0x14, 0x4b, 0xe2, 0x33, 0xaf, + 0xc0, 0xa8, 0x8e, 0x79, 0x20, 0x7f, 0xe9, 0x1f, 0x2e, 0xc2, 0x24, 0xed, 0xc1, 0x43, 0x9d, 0x88, + 0x1b, 0xe9, 0x89, 0x78, 0xd0, 0x3e, 0xb3, 0xdd, 0x67, 0xe3, 0x9d, 0xe4, 0x6c, 0x5c, 0xcc, 0x9b, + 0x8d, 0xc3, 0x9e, 0x83, 0x1f, 0xb1, 0xe0, 0xc8, 0x72, 0xc3, 0xaf, 0x6d, 0x25, 0xfc, 0x5a, 0x5f, + 0x82, 0x11, 0x7a, 0x1c, 0x87, 0x46, 0x88, 0x17, 0x23, 0xe8, 0x8f, 0x00, 0x61, 0x1d, 0x4f, 0xab, + 0x76, 0xe3, 0x46, 0x79, 0x31, 0x2b, 0x56, 0x90, 0x00, 0x61, 0x1d, 0xcf, 0xfe, 0x4b, 0x0b, 0x4e, + 0x5f, 0x5e, 0x58, 0x8a, 0x97, 0x62, 0x2a, 0x5c, 0xd1, 0x39, 0x18, 0x68, 0xd5, 0xb5, 0xae, 0xc4, + 0x62, 0xe1, 0x45, 0xd6, 0x0b, 0x01, 0x7d, 0xbf, 0x44, 0x06, 0xbb, 0x01, 0x70, 0x19, 0x57, 0x16, + 0xc4, 0xb9, 0x2b, 0xb5, 0x40, 0x56, 0xae, 0x16, 0xe8, 0x09, 0x18, 0xa4, 0xf7, 0x82, 0x5b, 0x93, + 0xfd, 0xe6, 0x06, 0x1b, 0xbc, 0x08, 0x4b, 0x98, 0xfd, 0xeb, 0x16, 0x1c, 0xb9, 0xec, 0x46, 0xf4, + 0xd2, 0x4e, 0xc6, 0xe3, 0xa1, 0xb7, 0x76, 0xe8, 0x46, 0x7e, 0xb0, 0x93, 0x8c, 0xc7, 0x83, 0x15, + 0x04, 0x6b, 0x58, 0xfc, 0x83, 0xb6, 0x5d, 0xe6, 0x29, 0x53, 0x30, 0xf5, 0x6e, 0x58, 0x94, 0x63, + 0x85, 0x41, 0xc7, 0xab, 0xee, 0x06, 0x4c, 0x64, 0xb9, 0x23, 0x0e, 0x6e, 0x35, 0x5e, 0x8b, 0x12, + 0x80, 0x63, 0x1c, 0xfb, 0x9f, 0x2c, 0x28, 0x5d, 0xe6, 0xfe, 0xbe, 0xeb, 0x61, 0xce, 0xa1, 0xfb, + 0x02, 0x0c, 0x13, 0xa9, 0x20, 0x10, 0xbd, 0x56, 0x8c, 0xa8, 0xd2, 0x1c, 0xf0, 0xb0, 0x40, 0x0a, + 0xaf, 0x07, 0xe7, 0xfb, 0x83, 0x79, 0x4f, 0x2f, 0x03, 0x22, 0x7a, 0x5b, 0x7a, 0x9c, 0x24, 0x16, + 0x70, 0x65, 0x29, 0x05, 0xc5, 0x19, 0x35, 0xec, 0x9f, 0xb7, 0xe0, 0x98, 0xfa, 0xe0, 0xf7, 0xdd, + 0x67, 0xda, 0xbf, 0x53, 0x80, 0xb1, 0x2b, 0x6b, 0x6b, 0x95, 0xcb, 0x24, 0xd2, 0x56, 0x65, 0x67, + 0xb5, 0x3f, 0xd6, 0xb4, 0x97, 0x9d, 0xde, 0x88, 0xed, 0xc8, 0x6d, 0xcc, 0xf2, 0xe8, 0x7f, 0xb3, + 0x65, 0x2f, 0xba, 0x1e, 0x54, 0xa3, 0xc0, 0xf5, 0x36, 0x32, 0x57, 0xba, 0xe4, 0x59, 0x8a, 0x79, + 0x3c, 0x0b, 0x7a, 0x01, 0x06, 0x58, 0xf8, 0x41, 0x39, 0x09, 0x8f, 0xa8, 0x27, 0x16, 0x2b, 0xdd, + 0xdf, 0x2d, 0x0d, 0xdf, 0xc0, 0x65, 0xfe, 0x07, 0x0b, 0x54, 0x74, 0x03, 0x46, 0x36, 0xa3, 0xa8, + 0x75, 0x85, 0x38, 0x75, 0x12, 0xc8, 0x53, 0xf6, 0x4c, 0xd6, 0x29, 0x4b, 0x07, 0x81, 0xa3, 0xc5, + 0x07, 0x53, 0x5c, 0x16, 0x62, 0x9d, 0x8e, 0x5d, 0x05, 0x88, 0x61, 0x0f, 0x48, 0x71, 0x63, 0xaf, + 0xc1, 0x30, 0xfd, 0xdc, 0xb9, 0x86, 0xeb, 0x74, 0x56, 0x8d, 0x3f, 0x03, 0xc3, 0x52, 0xf1, 0x1d, + 0x8a, 0xe0, 0x20, 0xec, 0x46, 0x92, 0x7a, 0xf1, 0x10, 0xc7, 0x70, 0xfb, 0x71, 0x10, 0xb6, 0xc3, + 0x9d, 0x48, 0xda, 0xeb, 0x70, 0x94, 0x19, 0x41, 0x3b, 0xd1, 0xa6, 0xb1, 0x46, 0xbb, 0x2f, 0x86, + 0x67, 0xc5, 0xbb, 0xae, 0xa0, 0xec, 0x7d, 0xa4, 0xf3, 0xf9, 0xa8, 0xa4, 0x18, 0xbf, 0xf1, 0xec, + 0x7f, 0xec, 0x83, 0x47, 0xca, 0xd5, 0xfc, 0xa8, 0x56, 0x97, 0x60, 0x94, 0xb3, 0x8b, 0x74, 0x69, + 0x38, 0x0d, 0xd1, 0xae, 0x92, 0x80, 0xae, 0x69, 0x30, 0x6c, 0x60, 0xa2, 0xd3, 0x50, 0x74, 0xdf, + 0xf5, 0x92, 0xae, 0x99, 0xe5, 0xb7, 0x56, 0x31, 0x2d, 0xa7, 0x60, 0xca, 0x79, 0xf2, 0x23, 0x5d, + 0x81, 0x15, 0xf7, 0xf9, 0x3a, 0x8c, 0xbb, 0x61, 0x2d, 0x74, 0xcb, 0x1e, 0xdd, 0xa7, 0xda, 0x4e, + 0x57, 0x32, 0x07, 0xda, 0x69, 0x05, 0xc5, 0x09, 0x6c, 0xed, 0x7e, 0xe9, 0xef, 0x99, 0x7b, 0xed, + 0x1a, 0x53, 0x83, 0x1e, 0xff, 0x2d, 0xf6, 0x75, 0x21, 0x13, 0xc1, 0x8b, 0xe3, 0x9f, 0x7f, 0x70, + 0x88, 0x25, 0x8c, 0x3e, 0xe8, 0x6a, 0x9b, 0x4e, 0x6b, 0xae, 0x1d, 0x6d, 0x2e, 0xba, 0x61, 0xcd, + 0xdf, 0x26, 0xc1, 0x0e, 0x7b, 0x8b, 0x0f, 0xc5, 0x0f, 0x3a, 0x05, 0x58, 0xb8, 0x32, 0x57, 0xa1, + 0x98, 0x38, 0x5d, 0x07, 0xcd, 0xc1, 0x84, 0x2c, 0xac, 0x92, 0x90, 0x5d, 0x01, 0x23, 0x8c, 0x8c, + 0x72, 0x96, 0x14, 0xc5, 0x8a, 0x48, 0x12, 0xdf, 0x64, 0x70, 0xe1, 0x41, 0x30, 0xb8, 0x2f, 0xc3, + 0x98, 0xeb, 0xb9, 0x91, 0xeb, 0x44, 0x3e, 0xd7, 0x1f, 0xf1, 0x67, 0x37, 0x13, 0x30, 0x97, 0x75, + 0x00, 0x36, 0xf1, 0xec, 0xff, 0xb3, 0x0f, 0xa6, 0xd8, 0xb4, 0x7d, 0xb0, 0xc2, 0xbe, 0x97, 0x56, + 0xd8, 0x8d, 0xf4, 0x0a, 0x7b, 0x10, 0x9c, 0xfb, 0x7d, 0x2f, 0xb3, 0x2f, 0x58, 0x30, 0xc5, 0x64, + 0xdc, 0xc6, 0x32, 0xbb, 0x00, 0xc3, 0x81, 0xe1, 0xc7, 0x3a, 0xac, 0x2b, 0xb5, 0xa4, 0x4b, 0x6a, + 0x8c, 0x83, 0xde, 0x00, 0x68, 0xc5, 0x32, 0xf4, 0x82, 0x11, 0x7c, 0x14, 0x72, 0xc5, 0xe7, 0x5a, + 0x1d, 0xfb, 0xb3, 0x30, 0xac, 0x1c, 0x55, 0xa5, 0xa7, 0xba, 0x95, 0xe3, 0xa9, 0xde, 0x9d, 0x8d, + 0x90, 0xb6, 0x71, 0xc5, 0x4c, 0xdb, 0xb8, 0xff, 0xcb, 0x82, 0x58, 0xc3, 0x81, 0xde, 0x82, 0xe1, + 0x96, 0xcf, 0x4c, 0xa9, 0x03, 0xe9, 0x9f, 0xf0, 0x78, 0x47, 0x15, 0x09, 0x8f, 0x30, 0x18, 0xf0, + 0xe9, 0xa8, 0xc8, 0xaa, 0x38, 0xa6, 0x82, 0xae, 0xc2, 0x60, 0x2b, 0x20, 0xd5, 0x88, 0x85, 0xbf, + 0xea, 0x9d, 0x20, 0x5f, 0xbe, 0xbc, 0x22, 0x96, 0x14, 0x12, 0x96, 0xa9, 0xc5, 0xde, 0x2d, 0x53, + 0xed, 0xdf, 0x2a, 0xc0, 0x64, 0xb2, 0x11, 0xf4, 0x1a, 0xf4, 0x91, 0xbb, 0xa4, 0x26, 0xbe, 0x34, + 0x93, 0x9b, 0x88, 0xa5, 0x2b, 0x7c, 0xe8, 0xe8, 0x7f, 0xcc, 0x6a, 0xa1, 0x2b, 0x30, 0x48, 0x59, + 0x89, 0xcb, 0x2a, 0x48, 0xe4, 0xa3, 0x79, 0xec, 0x88, 0xe2, 0xc9, 0xf8, 0x67, 0x89, 0x22, 0x2c, + 0xab, 0x33, 0x53, 0xb6, 0x5a, 0xab, 0x4a, 0x5f, 0x69, 0x51, 0x27, 0x61, 0xc2, 0xda, 0x42, 0x85, + 0x23, 0x09, 0x6a, 0xdc, 0x94, 0x4d, 0x16, 0xe2, 0x98, 0x08, 0x7a, 0x03, 0xfa, 0xc3, 0x06, 0x21, + 0x2d, 0x61, 0xab, 0x90, 0x29, 0x1f, 0xad, 0x52, 0x04, 0x41, 0x89, 0xc9, 0x53, 0x58, 0x01, 0xe6, + 0x15, 0xed, 0xdf, 0xb5, 0x00, 0xb8, 0xed, 0x9f, 0xe3, 0x6d, 0x90, 0x43, 0x50, 0x29, 0x2c, 0x42, + 0x5f, 0xd8, 0x22, 0xb5, 0x4e, 0x1e, 0x06, 0x71, 0x7f, 0xaa, 0x2d, 0x52, 0x8b, 0x57, 0x3b, 0xfd, + 0x87, 0x59, 0x6d, 0xfb, 0x47, 0x01, 0xc6, 0x63, 0xb4, 0x72, 0x44, 0x9a, 0xe8, 0x39, 0x23, 0xb2, + 0xce, 0xc9, 0x44, 0x64, 0x9d, 0x61, 0x86, 0xad, 0x49, 0xaf, 0x3f, 0x0b, 0xc5, 0xa6, 0x73, 0x57, + 0x88, 0x27, 0x9f, 0xe9, 0xdc, 0x0d, 0x4a, 0x7f, 0x76, 0xc5, 0xb9, 0xcb, 0x5f, 0xf0, 0xcf, 0xc8, + 0xdd, 0xb9, 0xe2, 0xdc, 0xed, 0x6a, 0x05, 0x4f, 0x1b, 0x61, 0x6d, 0xb9, 0x9e, 0x30, 0x6b, 0xeb, + 0xa9, 0x2d, 0xd7, 0x4b, 0xb6, 0xe5, 0x7a, 0x3d, 0xb4, 0xe5, 0x7a, 0xe8, 0x1e, 0x0c, 0x0a, 0xab, + 0x53, 0x11, 0xf2, 0xef, 0x42, 0x0f, 0xed, 0x09, 0xa3, 0x55, 0xde, 0xe6, 0x05, 0x29, 0xa1, 0x10, + 0xa5, 0x5d, 0xdb, 0x95, 0x0d, 0xa2, 0xff, 0xd4, 0x82, 0x71, 0xf1, 0x1b, 0x93, 0x77, 0xdb, 0x24, + 0x8c, 0x04, 0x07, 0xff, 0x91, 0xde, 0xfb, 0x20, 0x2a, 0xf2, 0xae, 0x7c, 0x44, 0x5e, 0xb6, 0x26, + 0xb0, 0x6b, 0x8f, 0x12, 0xbd, 0x40, 0xbf, 0x65, 0xc1, 0xd1, 0xa6, 0x73, 0x97, 0xb7, 0xc8, 0xcb, + 0xb0, 0x13, 0xb9, 0xbe, 0xb0, 0xde, 0x78, 0xad, 0xb7, 0xe9, 0x4f, 0x55, 0xe7, 0x9d, 0x94, 0xaa, + 0xda, 0xa3, 0x59, 0x28, 0x5d, 0xbb, 0x9a, 0xd9, 0xaf, 0x99, 0x75, 0x18, 0x92, 0xeb, 0xed, 0x61, + 0x9a, 0xd4, 0xb3, 0x76, 0xc4, 0x5a, 0x7b, 0xa8, 0xed, 0x7c, 0x16, 0x46, 0xf5, 0x35, 0xf6, 0x50, + 0xdb, 0x7a, 0x17, 0x8e, 0x64, 0xac, 0xa5, 0x87, 0xda, 0xe4, 0x1d, 0x38, 0x99, 0xbb, 0x3e, 0x1e, + 0xaa, 0x4b, 0xc4, 0xef, 0x58, 0xfa, 0x39, 0x78, 0x08, 0x7a, 0x9d, 0x05, 0x53, 0xaf, 0x73, 0xa6, + 0xf3, 0xce, 0xc9, 0x51, 0xee, 0xbc, 0xa3, 0x77, 0x9a, 0x9e, 0xea, 0xe8, 0x4d, 0x18, 0x68, 0xd0, + 0x12, 0x69, 0xbb, 0x6c, 0x77, 0xdf, 0x91, 0x31, 0x47, 0xcd, 0xca, 0x43, 0x2c, 0x28, 0xd8, 0x5f, + 0xb1, 0x20, 0xc3, 0xa9, 0x83, 0x72, 0x58, 0x6d, 0xb7, 0xce, 0x86, 0xa4, 0x18, 0x73, 0x58, 0x2a, + 0xf0, 0xcc, 0x69, 0x28, 0x6e, 0xb8, 0x75, 0xe1, 0xcd, 0xac, 0xc0, 0x97, 0x29, 0x78, 0xc3, 0xad, + 0xa3, 0x65, 0x40, 0x61, 0xbb, 0xd5, 0x6a, 0x30, 0x83, 0x27, 0xa7, 0x71, 0x39, 0xf0, 0xdb, 0x2d, + 0x6e, 0xa8, 0x5c, 0xe4, 0xe2, 0xa5, 0x6a, 0x0a, 0x8a, 0x33, 0x6a, 0xd8, 0x7f, 0x60, 0x41, 0xdf, + 0x21, 0x4c, 0x13, 0x36, 0xa7, 0xe9, 0xb9, 0x5c, 0xd2, 0x22, 0x53, 0xc4, 0x2c, 0x76, 0xee, 0x2c, + 0xdd, 0x8d, 0x88, 0x17, 0x32, 0x86, 0x23, 0x73, 0xd6, 0x76, 0x2d, 0x38, 0x72, 0xcd, 0x77, 0xea, + 0xf3, 0x4e, 0xc3, 0xf1, 0x6a, 0x24, 0x28, 0x7b, 0x1b, 0x07, 0xf2, 0x0a, 0x28, 0x74, 0xf5, 0x0a, + 0xb8, 0x04, 0x03, 0x6e, 0x4b, 0x0b, 0x35, 0x7f, 0x96, 0xce, 0x6e, 0xb9, 0x22, 0xa2, 0xcc, 0x23, + 0xa3, 0x71, 0x56, 0x8a, 0x05, 0x3e, 0x5d, 0x96, 0xdc, 0x1c, 0xaf, 0x2f, 0x7f, 0x59, 0xd2, 0x57, + 0x52, 0x32, 0x84, 0x9a, 0x61, 0x38, 0xbe, 0x09, 0x46, 0x13, 0xc2, 0x4d, 0x0a, 0xc3, 0xa0, 0xcb, + 0xbf, 0x54, 0xac, 0xcd, 0x27, 0xb3, 0x5f, 0x2f, 0xa9, 0x81, 0xd1, 0xfc, 0x01, 0x79, 0x01, 0x96, + 0x84, 0xec, 0x4b, 0x90, 0x19, 0xf2, 0xa6, 0xbb, 0x64, 0xca, 0xfe, 0x04, 0x4c, 0xb1, 0x9a, 0x07, + 0x94, 0xfa, 0xd8, 0x09, 0x79, 0x7a, 0x46, 0xd4, 0x60, 0xfb, 0x7f, 0xb5, 0x00, 0xad, 0xf8, 0x75, + 0x77, 0x7d, 0x47, 0x10, 0xe7, 0xdf, 0xff, 0x2e, 0x94, 0xf8, 0xb3, 0x3a, 0x19, 0x59, 0x77, 0xa1, + 0xe1, 0x84, 0xa1, 0x26, 0xcb, 0x7f, 0x52, 0xb4, 0x5b, 0x5a, 0xeb, 0x8c, 0x8e, 0xbb, 0xd1, 0x43, + 0x6f, 0x25, 0x02, 0x1d, 0x7e, 0x34, 0x15, 0xe8, 0xf0, 0xc9, 0x4c, 0x8b, 0x9a, 0x74, 0xef, 0x65, + 0x00, 0x44, 0xfb, 0x8b, 0x16, 0x4c, 0xac, 0x26, 0x22, 0xc5, 0x9e, 0x63, 0xe6, 0x05, 0x19, 0x3a, + 0xaa, 0x2a, 0x2b, 0xc5, 0x02, 0xfa, 0xc0, 0x65, 0xb8, 0xdf, 0xb6, 0x20, 0x0e, 0xb1, 0x75, 0x08, + 0x2c, 0xf7, 0x82, 0xc1, 0x72, 0x67, 0x3e, 0x5f, 0x54, 0x77, 0xf2, 0x38, 0x6e, 0x74, 0x55, 0xcd, + 0x49, 0x87, 0x97, 0x4b, 0x4c, 0x86, 0xef, 0xb3, 0x71, 0x73, 0xe2, 0xd4, 0x6c, 0x7c, 0xa3, 0x00, + 0x48, 0xe1, 0xf6, 0x1c, 0x1c, 0x33, 0x5d, 0xe3, 0xc1, 0x04, 0xc7, 0xdc, 0x06, 0xc4, 0x0c, 0x64, + 0x02, 0xc7, 0x0b, 0x39, 0x59, 0x57, 0x48, 0xad, 0x0f, 0x66, 0x7d, 0x33, 0x23, 0xbd, 0x65, 0xaf, + 0xa5, 0xa8, 0xe1, 0x8c, 0x16, 0x34, 0xc3, 0xa7, 0xfe, 0x5e, 0x0d, 0x9f, 0x06, 0xba, 0xb8, 0x7d, + 0x7f, 0xcd, 0x82, 0x31, 0x35, 0x4c, 0xef, 0x13, 0xe7, 0x11, 0xd5, 0x9f, 0x9c, 0x7b, 0xa5, 0xa2, + 0x75, 0x99, 0x31, 0x03, 0xdf, 0xc7, 0xdc, 0xf7, 0x9d, 0x86, 0x7b, 0x8f, 0xa8, 0x18, 0xce, 0x25, + 0xe1, 0x8e, 0x2f, 0x4a, 0xf7, 0x77, 0x4b, 0x63, 0xea, 0x1f, 0x8f, 0x1a, 0x1b, 0x57, 0xb1, 0x7f, + 0x99, 0x6e, 0x76, 0x73, 0x29, 0xa2, 0x97, 0xa0, 0xbf, 0xb5, 0xe9, 0x84, 0x24, 0xe1, 0x64, 0xd7, + 0x5f, 0xa1, 0x85, 0xfb, 0xbb, 0xa5, 0x71, 0x55, 0x81, 0x95, 0x60, 0x8e, 0xdd, 0x7b, 0xc8, 0xd1, + 0xf4, 0xe2, 0xec, 0x1a, 0x72, 0xf4, 0x5f, 0x2c, 0xe8, 0x5b, 0xa5, 0xb7, 0xd7, 0xc3, 0x3f, 0x02, + 0x5e, 0x37, 0x8e, 0x80, 0x53, 0x79, 0xd9, 0x8c, 0x72, 0x77, 0xff, 0x72, 0x62, 0xf7, 0x9f, 0xc9, + 0xa5, 0xd0, 0x79, 0xe3, 0x37, 0x61, 0x84, 0xe5, 0x48, 0x12, 0x0e, 0x85, 0x2f, 0x18, 0x1b, 0xbe, + 0x94, 0xd8, 0xf0, 0x13, 0x1a, 0xaa, 0xb6, 0xd3, 0x9f, 0x82, 0x41, 0xe1, 0xa1, 0x96, 0x8c, 0x82, + 0x20, 0x70, 0xb1, 0x84, 0xdb, 0xbf, 0x50, 0x04, 0x23, 0x27, 0x13, 0xfa, 0x23, 0x0b, 0x66, 0x03, + 0x6e, 0xb9, 0x5e, 0x5f, 0x6c, 0x07, 0xae, 0xb7, 0x51, 0xad, 0x6d, 0x92, 0x7a, 0xbb, 0xe1, 0x7a, + 0x1b, 0xe5, 0x0d, 0xcf, 0x57, 0xc5, 0x4b, 0x77, 0x49, 0xad, 0xcd, 0xb4, 0xca, 0x5d, 0x12, 0x40, + 0x29, 0x0f, 0x90, 0xe7, 0xf7, 0x76, 0x4b, 0xb3, 0xf8, 0x40, 0xb4, 0xf1, 0x01, 0xfb, 0x82, 0xfe, + 0xd2, 0x82, 0x0b, 0x3c, 0x37, 0x50, 0xef, 0xfd, 0xef, 0x20, 0xe1, 0xa8, 0x48, 0x52, 0x31, 0x91, + 0x35, 0x12, 0x34, 0xe7, 0x5f, 0x16, 0x03, 0x7a, 0xa1, 0x72, 0xb0, 0xb6, 0xf0, 0x41, 0x3b, 0x67, + 0xff, 0xb7, 0x45, 0x18, 0x13, 0xa1, 0x29, 0xc5, 0x1d, 0xf0, 0x92, 0xb1, 0x24, 0x1e, 0x4d, 0x2c, + 0x89, 0x29, 0x03, 0xf9, 0xc1, 0x1c, 0xff, 0x21, 0x4c, 0xd1, 0xc3, 0xf9, 0x0a, 0x71, 0x82, 0xe8, + 0x36, 0x71, 0xb8, 0x3d, 0x63, 0xf1, 0xc0, 0xa7, 0xbf, 0x12, 0xac, 0x5f, 0x4b, 0x12, 0xc3, 0x69, + 0xfa, 0xdf, 0x4b, 0x77, 0x8e, 0x07, 0x93, 0xa9, 0xe8, 0xa2, 0x6f, 0xc3, 0xb0, 0x72, 0xaf, 0x12, + 0x87, 0x4e, 0xe7, 0x20, 0xbd, 0x49, 0x0a, 0x5c, 0xe8, 0x19, 0xbb, 0xf6, 0xc5, 0xe4, 0xec, 0xdf, + 0x2e, 0x18, 0x0d, 0xf2, 0x49, 0x5c, 0x85, 0x21, 0x27, 0x64, 0x81, 0xc3, 0xeb, 0x9d, 0x24, 0xda, + 0xa9, 0x66, 0x98, 0x8b, 0xdb, 0x9c, 0xa8, 0x89, 0x15, 0x0d, 0x74, 0x85, 0x5b, 0x8d, 0x6e, 0x93, + 0x4e, 0xe2, 0xec, 0x14, 0x35, 0x90, 0x76, 0xa5, 0xdb, 0x04, 0x8b, 0xfa, 0xe8, 0x53, 0xdc, 0xac, + 0xf7, 0xaa, 0xe7, 0xdf, 0xf1, 0x2e, 0xfb, 0xbe, 0x0c, 0x43, 0xd4, 0x1b, 0xc1, 0x29, 0x69, 0xcc, + 0xab, 0xaa, 0x63, 0x93, 0x5a, 0x6f, 0xe1, 0xba, 0x3f, 0x07, 0x2c, 0x17, 0x8a, 0x19, 0xcd, 0x20, + 0x44, 0x04, 0x26, 0x44, 0xdc, 0x53, 0x59, 0x26, 0xc6, 0x2e, 0xf3, 0xf9, 0x6d, 0xd6, 0x8e, 0x35, + 0x40, 0x57, 0x4d, 0x12, 0x38, 0x49, 0xd3, 0xde, 0xe4, 0x87, 0xf0, 0x32, 0x71, 0xa2, 0x76, 0x40, + 0x42, 0xf4, 0x71, 0x98, 0x4e, 0xbf, 0x8c, 0x85, 0x22, 0xc5, 0x62, 0xdc, 0xf3, 0xa9, 0xbd, 0xdd, + 0xd2, 0x74, 0x35, 0x07, 0x07, 0xe7, 0xd6, 0xb6, 0x7f, 0xcd, 0x02, 0xe6, 0x43, 0x7e, 0x08, 0x9c, + 0xcf, 0xc7, 0x4c, 0xce, 0x67, 0x3a, 0x6f, 0x3a, 0x73, 0x98, 0x9e, 0x17, 0xf9, 0x1a, 0xae, 0x04, + 0xfe, 0xdd, 0x1d, 0x61, 0xf5, 0xd5, 0xfd, 0x19, 0x67, 0x7f, 0xd9, 0x02, 0x96, 0x38, 0x08, 0xf3, + 0x57, 0xbb, 0x54, 0x70, 0x74, 0x37, 0x68, 0xf8, 0x38, 0x0c, 0xad, 0x8b, 0xe1, 0xcf, 0x10, 0x3a, + 0x19, 0x1d, 0x36, 0x69, 0xcb, 0x49, 0x13, 0xbe, 0xa0, 0xe2, 0x1f, 0x56, 0xd4, 0xec, 0xff, 0xd2, + 0x82, 0x99, 0xfc, 0x6a, 0xe8, 0x06, 0x9c, 0x08, 0x48, 0xad, 0x1d, 0x84, 0x74, 0x4b, 0x88, 0x07, + 0x90, 0x70, 0xa7, 0xe2, 0x53, 0xfd, 0xc8, 0xde, 0x6e, 0xe9, 0x04, 0xce, 0x46, 0xc1, 0x79, 0x75, + 0xd1, 0x2b, 0x30, 0xde, 0x0e, 0x39, 0xe7, 0xc7, 0x98, 0xae, 0x50, 0x44, 0xa7, 0x66, 0x1e, 0x47, + 0x37, 0x0c, 0x08, 0x4e, 0x60, 0xda, 0x3f, 0xc0, 0x97, 0xa3, 0x0a, 0x50, 0xdd, 0x84, 0x29, 0x4f, + 0xfb, 0x4f, 0x6f, 0x40, 0xf9, 0xd4, 0x7f, 0xbc, 0xdb, 0xad, 0xcf, 0xae, 0x4b, 0xcd, 0xcb, 0x3d, + 0x41, 0x06, 0xa7, 0x29, 0xdb, 0xbf, 0x68, 0xc1, 0x09, 0x1d, 0x51, 0x73, 0xa4, 0xeb, 0xa6, 0x05, + 0x5c, 0x84, 0x21, 0xbf, 0x45, 0x02, 0x27, 0xf2, 0x03, 0x71, 0xcd, 0x9d, 0x97, 0x2b, 0xf4, 0xba, + 0x28, 0xdf, 0x17, 0x09, 0x73, 0x24, 0x75, 0x59, 0x8e, 0x55, 0x4d, 0x64, 0xc3, 0x00, 0x13, 0x20, + 0x86, 0xc2, 0x65, 0x92, 0x1d, 0x5a, 0xcc, 0xb2, 0x25, 0xc4, 0x02, 0x62, 0xff, 0xa3, 0xc5, 0xd7, + 0xa7, 0xde, 0x75, 0xf4, 0x2e, 0x4c, 0x36, 0x9d, 0xa8, 0xb6, 0xb9, 0x74, 0xb7, 0x15, 0x70, 0xe5, + 0xae, 0x1c, 0xa7, 0x67, 0xba, 0x8d, 0x93, 0xf6, 0x91, 0xb1, 0x69, 0xf5, 0x4a, 0x82, 0x18, 0x4e, + 0x91, 0x47, 0xb7, 0x61, 0x84, 0x95, 0x31, 0x6f, 0xe0, 0xb0, 0x13, 0x2f, 0x93, 0xd7, 0x9a, 0x32, + 0x0e, 0x5a, 0x89, 0xe9, 0x60, 0x9d, 0xa8, 0xfd, 0xd5, 0x22, 0x3f, 0x34, 0xd8, 0xdb, 0xe3, 0x29, + 0x18, 0x6c, 0xf9, 0xf5, 0x85, 0xf2, 0x22, 0x16, 0xb3, 0xa0, 0xee, 0xbd, 0x0a, 0x2f, 0xc6, 0x12, + 0x8e, 0xce, 0xc3, 0x90, 0xf8, 0x29, 0x95, 0xf1, 0x6c, 0x8f, 0x08, 0xbc, 0x10, 0x2b, 0x28, 0x7a, + 0x1e, 0xa0, 0x15, 0xf8, 0xdb, 0x6e, 0x9d, 0x45, 0x7f, 0x2a, 0x9a, 0x76, 0x7d, 0x15, 0x05, 0xc1, + 0x1a, 0x16, 0x7a, 0x15, 0xc6, 0xda, 0x5e, 0xc8, 0xf9, 0x27, 0x2d, 0xc6, 0xbe, 0xb2, 0x38, 0xbb, + 0xa1, 0x03, 0xb1, 0x89, 0x8b, 0xe6, 0x60, 0x20, 0x72, 0x98, 0x9d, 0x5a, 0x7f, 0xbe, 0xf9, 0xfd, + 0x1a, 0xc5, 0xd0, 0xb3, 0xd9, 0xd1, 0x0a, 0x58, 0x54, 0x44, 0x6f, 0x4b, 0xc7, 0x7c, 0x7e, 0x13, + 0x09, 0xbf, 0x97, 0xde, 0x6e, 0x2d, 0xcd, 0x2d, 0x5f, 0xf8, 0xd3, 0x18, 0xb4, 0xd0, 0x2b, 0x00, + 0xe4, 0x6e, 0x44, 0x02, 0xcf, 0x69, 0x28, 0xeb, 0x52, 0xc5, 0xc8, 0x2c, 0xfa, 0xab, 0x7e, 0x74, + 0x23, 0x24, 0x4b, 0x0a, 0x03, 0x6b, 0xd8, 0xf6, 0x8f, 0x8e, 0x00, 0xc4, 0x0f, 0x0d, 0x74, 0x0f, + 0x86, 0x6a, 0x4e, 0xcb, 0xa9, 0xf1, 0x54, 0xad, 0xc5, 0x3c, 0x7f, 0xe9, 0xb8, 0xc6, 0xec, 0x82, + 0x40, 0xe7, 0xca, 0x1b, 0x19, 0xa6, 0x7c, 0x48, 0x16, 0x77, 0x55, 0xd8, 0xa8, 0xf6, 0xd0, 0x17, + 0x2c, 0x18, 0x11, 0xd1, 0x95, 0xd8, 0x0c, 0x15, 0xf2, 0xf5, 0x6d, 0x5a, 0xfb, 0x73, 0x71, 0x0d, + 0xde, 0x85, 0x17, 0xe4, 0x0a, 0xd5, 0x20, 0x5d, 0x7b, 0xa1, 0x37, 0x8c, 0x3e, 0x2c, 0xdf, 0xb6, + 0x45, 0x63, 0x28, 0xd5, 0xdb, 0x76, 0x98, 0x5d, 0x35, 0xfa, 0xb3, 0xf6, 0x86, 0xf1, 0xac, 0xed, + 0xcb, 0xf7, 0x3c, 0x36, 0xf8, 0xed, 0x6e, 0x2f, 0x5a, 0x54, 0xd1, 0xa3, 0x90, 0xf4, 0xe7, 0xbb, + 0xcb, 0x6a, 0x0f, 0xbb, 0x2e, 0x11, 0x48, 0x3e, 0x0b, 0x13, 0x75, 0x93, 0x6b, 0x11, 0x2b, 0xf1, + 0xc9, 0x3c, 0xba, 0x09, 0x26, 0x27, 0xe6, 0x53, 0x12, 0x00, 0x9c, 0x24, 0x8c, 0x2a, 0x3c, 0x28, + 0x4d, 0xd9, 0x5b, 0xf7, 0x85, 0xef, 0x95, 0x9d, 0x3b, 0x97, 0x3b, 0x61, 0x44, 0x9a, 0x14, 0x33, + 0x66, 0x12, 0x56, 0x45, 0x5d, 0xac, 0xa8, 0xa0, 0x37, 0x61, 0x80, 0xf9, 0x4b, 0x86, 0xd3, 0x43, + 0xf9, 0x6a, 0x0d, 0x33, 0xfa, 0x6a, 0xbc, 0x21, 0xd9, 0xdf, 0x10, 0x0b, 0x0a, 0xe8, 0x8a, 0xf4, + 0x46, 0x0e, 0xcb, 0xde, 0x8d, 0x90, 0x30, 0x6f, 0xe4, 0xe1, 0xf9, 0xc7, 0x63, 0x47, 0x63, 0x5e, + 0x9e, 0x99, 0xf3, 0xd6, 0xa8, 0x49, 0xd9, 0x3e, 0xf1, 0x5f, 0xa6, 0xd2, 0x15, 0xb1, 0xe2, 0x32, + 0xbb, 0x67, 0xa6, 0xdb, 0x8d, 0x87, 0xf3, 0xa6, 0x49, 0x02, 0x27, 0x69, 0x52, 0x16, 0x9a, 0xef, + 0x7a, 0xe1, 0xbd, 0xd5, 0xed, 0xec, 0xe0, 0x92, 0x03, 0x76, 0x1b, 0xf1, 0x12, 0x2c, 0xea, 0x23, + 0x17, 0x26, 0x02, 0x83, 0xbd, 0x90, 0x21, 0xde, 0xce, 0xf5, 0xc6, 0xc4, 0x68, 0xc9, 0x03, 0x4c, + 0x32, 0x38, 0x49, 0x17, 0xbd, 0xa9, 0x31, 0x4a, 0x63, 0x9d, 0x5f, 0xfe, 0xdd, 0x58, 0xa3, 0x99, + 0x2d, 0x18, 0x33, 0x0e, 0x9b, 0x87, 0xaa, 0x82, 0xf4, 0x60, 0x32, 0x79, 0xb2, 0x3c, 0x54, 0xcd, + 0xe3, 0x2b, 0x30, 0xce, 0x36, 0xc2, 0x1d, 0xa7, 0x25, 0x8e, 0xe2, 0xf3, 0xc6, 0x51, 0x6c, 0x9d, + 0x2f, 0xf2, 0x81, 0x91, 0x43, 0x10, 0x1f, 0x9c, 0xf6, 0xaf, 0xf4, 0x8b, 0xca, 0x6a, 0x17, 0xa1, + 0x0b, 0x30, 0x2c, 0x3a, 0xa0, 0x32, 0x70, 0xa9, 0x83, 0x61, 0x45, 0x02, 0x70, 0x8c, 0xc3, 0x12, + 0xaf, 0xb1, 0xea, 0x9a, 0x87, 0x42, 0x9c, 0x78, 0x4d, 0x41, 0xb0, 0x86, 0x45, 0x1f, 0xbf, 0xb7, + 0x7d, 0x3f, 0x52, 0x77, 0xb0, 0xda, 0x6a, 0xf3, 0xac, 0x14, 0x0b, 0x28, 0xbd, 0x7b, 0xb7, 0x48, + 0xe0, 0x91, 0x86, 0x99, 0x82, 0x42, 0xdd, 0xbd, 0x57, 0x75, 0x20, 0x36, 0x71, 0x29, 0x07, 0xe1, + 0x87, 0x6c, 0xef, 0x8a, 0x27, 0x76, 0xec, 0xf1, 0x51, 0xe5, 0xb1, 0x2b, 0x24, 0x1c, 0x7d, 0x02, + 0x4e, 0xa8, 0x70, 0x8f, 0x62, 0x65, 0xca, 0x16, 0x07, 0x0c, 0x89, 0xd8, 0x89, 0x85, 0x6c, 0x34, + 0x9c, 0x57, 0x1f, 0xbd, 0x0e, 0xe3, 0xe2, 0x19, 0x26, 0x29, 0x0e, 0x9a, 0xe6, 0x8b, 0x57, 0x0d, + 0x28, 0x4e, 0x60, 0xcb, 0x24, 0x1a, 0xec, 0x7d, 0x22, 0x29, 0x0c, 0xa5, 0x93, 0x68, 0xe8, 0x70, + 0x9c, 0xaa, 0x81, 0xe6, 0x60, 0x82, 0xb3, 0x9d, 0xae, 0xb7, 0xc1, 0xe7, 0x44, 0xf8, 0x93, 0xaa, + 0x0d, 0x79, 0xdd, 0x04, 0xe3, 0x24, 0x3e, 0xba, 0x04, 0xa3, 0x4e, 0x50, 0xdb, 0x74, 0x23, 0x52, + 0xa3, 0xbb, 0x8a, 0x59, 0x10, 0x6a, 0xf6, 0x9f, 0x73, 0x1a, 0x0c, 0x1b, 0x98, 0xe8, 0x0d, 0xe8, + 0x0b, 0xef, 0x38, 0x2d, 0x71, 0xfa, 0xe4, 0x1f, 0xe5, 0x6a, 0x05, 0x73, 0xd3, 0x2f, 0xfa, 0x1f, + 0xb3, 0x9a, 0xf6, 0x3d, 0x38, 0x92, 0x11, 0x16, 0x87, 0x2e, 0x3d, 0xa7, 0xe5, 0xca, 0x51, 0x49, + 0xb8, 0x69, 0xcc, 0x55, 0xca, 0x72, 0x3c, 0x34, 0x2c, 0xba, 0xbe, 0x59, 0xf8, 0x1c, 0x2d, 0xdd, + 0xb8, 0x5a, 0xdf, 0xcb, 0x12, 0x80, 0x63, 0x1c, 0xfb, 0x5f, 0x0b, 0x30, 0x91, 0xa1, 0x1e, 0x64, + 0x29, 0xaf, 0x13, 0xef, 0xbc, 0x38, 0xc3, 0xb5, 0x99, 0xd5, 0xa5, 0x70, 0x80, 0xac, 0x2e, 0xc5, + 0x6e, 0x59, 0x5d, 0xfa, 0xde, 0x4b, 0x56, 0x17, 0x73, 0xc4, 0xfa, 0x7b, 0x1a, 0xb1, 0x8c, 0x4c, + 0x30, 0x03, 0x07, 0xcc, 0x04, 0x63, 0x0c, 0xfa, 0x60, 0x0f, 0x83, 0xfe, 0xd3, 0x05, 0x98, 0x4c, + 0x6a, 0x16, 0x0f, 0x41, 0x3a, 0xff, 0xa6, 0x21, 0x9d, 0x3f, 0xdf, 0x4b, 0x04, 0x81, 0x5c, 0x49, + 0x3d, 0x4e, 0x48, 0xea, 0x9f, 0xee, 0x89, 0x5a, 0x67, 0xa9, 0xfd, 0x2f, 0x15, 0xe0, 0x58, 0xa6, + 0xc2, 0xf5, 0x10, 0xc6, 0xe6, 0xba, 0x31, 0x36, 0xcf, 0xf5, 0x1c, 0x5d, 0x21, 0x77, 0x80, 0x6e, + 0x25, 0x06, 0xe8, 0x42, 0xef, 0x24, 0x3b, 0x8f, 0xd2, 0x37, 0x8b, 0x70, 0x26, 0xb3, 0x5e, 0x2c, + 0xdc, 0x5e, 0x36, 0x84, 0xdb, 0xcf, 0x27, 0x84, 0xdb, 0x76, 0xe7, 0xda, 0x0f, 0x46, 0xda, 0x2d, + 0xa2, 0x0c, 0xb0, 0x58, 0x29, 0xf7, 0x29, 0xe9, 0x36, 0xa2, 0x0c, 0x28, 0x42, 0xd8, 0xa4, 0xfb, + 0xbd, 0x24, 0xe1, 0xfe, 0x1f, 0x2d, 0x38, 0x99, 0x39, 0x37, 0x87, 0x20, 0x67, 0x5c, 0x35, 0xe5, + 0x8c, 0x4f, 0xf5, 0xbc, 0x5a, 0x73, 0x04, 0x8f, 0x5f, 0x1c, 0xc8, 0xf9, 0x16, 0x26, 0xfe, 0xb8, + 0x0e, 0x23, 0x4e, 0xad, 0x46, 0xc2, 0x70, 0xc5, 0xaf, 0xab, 0x04, 0x10, 0xcf, 0xb1, 0xc7, 0x69, + 0x5c, 0xbc, 0xbf, 0x5b, 0x9a, 0x49, 0x92, 0x88, 0xc1, 0x58, 0xa7, 0x80, 0x3e, 0x05, 0x43, 0xa1, + 0xcc, 0xdd, 0xd9, 0x77, 0xff, 0xb9, 0x3b, 0x19, 0x27, 0xa9, 0xc4, 0x3b, 0x8a, 0x24, 0xfa, 0x7e, + 0x3d, 0x6a, 0x55, 0x07, 0xc1, 0x26, 0xef, 0xe4, 0x7d, 0xc4, 0xae, 0x7a, 0x1e, 0x60, 0x5b, 0xbd, + 0xa3, 0x92, 0xa2, 0x1b, 0xed, 0x85, 0xa5, 0x61, 0xa1, 0x37, 0x60, 0x32, 0xe4, 0x01, 0x5b, 0x63, + 0x13, 0x19, 0xbe, 0x16, 0x59, 0xcc, 0xbb, 0x6a, 0x02, 0x86, 0x53, 0xd8, 0x68, 0x59, 0xb6, 0xca, + 0x8c, 0xa1, 0xf8, 0xf2, 0x3c, 0x17, 0xb7, 0x28, 0x0c, 0xa2, 0x8e, 0x26, 0x27, 0x81, 0x0d, 0xbf, + 0x56, 0x13, 0x7d, 0x0a, 0x80, 0x2e, 0x22, 0x21, 0xc2, 0x19, 0xcc, 0x3f, 0x42, 0xe9, 0xd9, 0x52, + 0xcf, 0xf4, 0xc0, 0x60, 0xe1, 0x01, 0x16, 0x15, 0x11, 0xac, 0x11, 0x44, 0x0e, 0x8c, 0xc5, 0xff, + 0xe2, 0xac, 0xf4, 0xe7, 0x73, 0x5b, 0x48, 0x12, 0x67, 0xea, 0x8d, 0x45, 0x9d, 0x04, 0x36, 0x29, + 0xa2, 0x4f, 0xc2, 0xc9, 0xed, 0x5c, 0xbb, 0x23, 0xce, 0x4b, 0xb2, 0x34, 0xf3, 0xf9, 0xd6, 0x46, + 0xf9, 0xf5, 0xed, 0xff, 0x09, 0xe0, 0x91, 0x0e, 0x27, 0x3d, 0x9a, 0x33, 0x6d, 0x06, 0x9e, 0x49, + 0xca, 0x55, 0x66, 0x32, 0x2b, 0x1b, 0x82, 0x96, 0xc4, 0x86, 0x2a, 0xbc, 0xe7, 0x0d, 0xf5, 0x13, + 0x96, 0xf6, 0xcc, 0xe2, 0x16, 0xe5, 0x1f, 0x3b, 0xe0, 0x0d, 0xf6, 0x00, 0x45, 0x60, 0xeb, 0x19, + 0x72, 0xa4, 0xe7, 0x7b, 0xee, 0x4e, 0xef, 0x82, 0xa5, 0xdf, 0xc9, 0x0e, 0x71, 0xcf, 0x45, 0x4c, + 0x97, 0x0f, 0xfa, 0xfd, 0x87, 0x15, 0xee, 0xfe, 0x1b, 0x16, 0x9c, 0x4c, 0x15, 0xf3, 0x3e, 0x90, + 0x50, 0x44, 0xe9, 0x5b, 0x7d, 0xcf, 0x9d, 0x97, 0x04, 0xf9, 0x37, 0x5c, 0x11, 0xdf, 0x70, 0x32, + 0x17, 0x2f, 0xd9, 0xf5, 0x2f, 0xfd, 0x7d, 0xe9, 0x08, 0x6b, 0xc0, 0x44, 0xc4, 0xf9, 0x5d, 0x47, + 0x2d, 0x38, 0x5b, 0x6b, 0x07, 0x41, 0xbc, 0x58, 0x33, 0x36, 0x27, 0x7f, 0x2d, 0x3e, 0xbe, 0xb7, + 0x5b, 0x3a, 0xbb, 0xd0, 0x05, 0x17, 0x77, 0xa5, 0x86, 0x3c, 0x40, 0xcd, 0x94, 0x75, 0x1f, 0x3b, + 0x00, 0x72, 0xa4, 0x40, 0x69, 0x5b, 0x40, 0x6e, 0xa7, 0x9b, 0x61, 0x23, 0x98, 0x41, 0xf9, 0x70, + 0x65, 0x37, 0xdf, 0x99, 0x78, 0xfa, 0x33, 0xd7, 0xe0, 0x4c, 0xe7, 0xc5, 0x74, 0xa0, 0x10, 0x14, + 0x7f, 0x63, 0xc1, 0xe9, 0x8e, 0x71, 0xce, 0xbe, 0x0b, 0x1f, 0x0b, 0xf6, 0xe7, 0x2d, 0x78, 0x34, + 0xb3, 0x46, 0xd2, 0x79, 0xb0, 0x46, 0x0b, 0x35, 0x63, 0xd8, 0x38, 0xe2, 0x8f, 0x04, 0xe0, 0x18, + 0xc7, 0xb0, 0x17, 0x2d, 0x74, 0xb5, 0x17, 0xfd, 0x53, 0x0b, 0x52, 0x57, 0xfd, 0x21, 0x70, 0x9e, + 0x65, 0x93, 0xf3, 0x7c, 0xbc, 0x97, 0xd1, 0xcc, 0x61, 0x3a, 0xff, 0x79, 0x02, 0x8e, 0xe7, 0x78, + 0x90, 0x6f, 0xc3, 0xd4, 0x46, 0x8d, 0x98, 0x21, 0x43, 0x3a, 0x85, 0xd2, 0xeb, 0x18, 0x5f, 0x64, + 0xfe, 0xd8, 0xde, 0x6e, 0x69, 0x2a, 0x85, 0x82, 0xd3, 0x4d, 0xa0, 0xcf, 0x5b, 0x70, 0xd4, 0xb9, + 0x13, 0x2e, 0xd1, 0x17, 0x84, 0x5b, 0x9b, 0x6f, 0xf8, 0xb5, 0x2d, 0xca, 0x98, 0xc9, 0x6d, 0xf5, + 0x62, 0xa6, 0x28, 0xfc, 0x56, 0x35, 0x85, 0x6f, 0x34, 0x3f, 0xbd, 0xb7, 0x5b, 0x3a, 0x9a, 0x85, + 0x85, 0x33, 0xdb, 0x42, 0x58, 0xe4, 0x38, 0x73, 0xa2, 0xcd, 0x4e, 0x41, 0x6d, 0xb2, 0x5c, 0xfd, + 0x39, 0x4b, 0x2c, 0x21, 0x58, 0xd1, 0x41, 0x9f, 0x81, 0xe1, 0x0d, 0x19, 0xbf, 0x22, 0x83, 0xe5, + 0x8e, 0x07, 0xb2, 0x73, 0x54, 0x0f, 0x6e, 0x80, 0xa3, 0x90, 0x70, 0x4c, 0x14, 0xbd, 0x0e, 0x45, + 0x6f, 0x3d, 0x14, 0xa1, 0xf5, 0xb2, 0xed, 0x80, 0x4d, 0x4b, 0x6b, 0x1e, 0x3a, 0x6a, 0x75, 0xb9, + 0x8a, 0x69, 0x45, 0x74, 0x05, 0x8a, 0xc1, 0xed, 0xba, 0xd0, 0xe3, 0x64, 0x6e, 0x52, 0x3c, 0xbf, + 0x98, 0xd3, 0x2b, 0x46, 0x09, 0xcf, 0x2f, 0x62, 0x4a, 0x02, 0x55, 0xa0, 0x9f, 0xb9, 0x5d, 0x0b, + 0xd6, 0x36, 0xf3, 0x29, 0xdf, 0x21, 0x7c, 0x01, 0xf7, 0x87, 0x64, 0x08, 0x98, 0x13, 0x42, 0x6b, + 0x30, 0x50, 0x73, 0xbd, 0x3a, 0x09, 0x04, 0x2f, 0xfb, 0xe1, 0x4c, 0x8d, 0x0d, 0xc3, 0xc8, 0xa1, + 0xc9, 0x15, 0x18, 0x0c, 0x03, 0x0b, 0x5a, 0x8c, 0x2a, 0x69, 0x6d, 0xae, 0xcb, 0x1b, 0x2b, 0x9b, + 0x2a, 0x69, 0x6d, 0x2e, 0x57, 0x3b, 0x52, 0x65, 0x18, 0x58, 0xd0, 0x42, 0xaf, 0x40, 0x61, 0xbd, + 0x26, 0x5c, 0xaa, 0x33, 0xc5, 0x9b, 0x66, 0xf4, 0xaf, 0xf9, 0x81, 0xbd, 0xdd, 0x52, 0x61, 0x79, + 0x01, 0x17, 0xd6, 0x6b, 0x68, 0x15, 0x06, 0xd7, 0x79, 0xbc, 0x20, 0x21, 0x1f, 0x7d, 0x32, 0x3b, + 0x94, 0x51, 0x2a, 0xa4, 0x10, 0xf7, 0x6d, 0x15, 0x00, 0x2c, 0x89, 0xb0, 0x94, 0x5b, 0x2a, 0xee, + 0x91, 0x08, 0xbb, 0x3a, 0x7b, 0xb0, 0x58, 0x55, 0xfc, 0xa9, 0x11, 0x47, 0x4f, 0xc2, 0x1a, 0x45, + 0xba, 0xaa, 0x9d, 0x7b, 0xed, 0x80, 0xe5, 0xe4, 0x10, 0x8a, 0x99, 0xcc, 0x55, 0x3d, 0x27, 0x91, + 0x3a, 0xad, 0x6a, 0x85, 0x84, 0x63, 0xa2, 0x68, 0x0b, 0xc6, 0xb6, 0xc3, 0xd6, 0x26, 0x91, 0x5b, + 0x9a, 0x85, 0xeb, 0xcb, 0xe1, 0x66, 0x6f, 0x0a, 0x44, 0x37, 0x88, 0xda, 0x4e, 0x23, 0x75, 0x0a, + 0xb1, 0x67, 0xcd, 0x4d, 0x9d, 0x18, 0x36, 0x69, 0xd3, 0xe1, 0x7f, 0xb7, 0xed, 0xdf, 0xde, 0x89, + 0x88, 0x88, 0x96, 0x9a, 0x39, 0xfc, 0x6f, 0x71, 0x94, 0xf4, 0xf0, 0x0b, 0x00, 0x96, 0x44, 0xd0, + 0x4d, 0x31, 0x3c, 0xec, 0xf4, 0x9c, 0xcc, 0x0f, 0xc5, 0x3e, 0x27, 0x91, 0x72, 0x06, 0x85, 0x9d, + 0x96, 0x31, 0x29, 0x76, 0x4a, 0xb6, 0x36, 0xfd, 0xc8, 0xf7, 0x12, 0x27, 0xf4, 0x54, 0xfe, 0x29, + 0x59, 0xc9, 0xc0, 0x4f, 0x9f, 0x92, 0x59, 0x58, 0x38, 0xb3, 0x2d, 0x54, 0x87, 0xf1, 0x96, 0x1f, + 0x44, 0x77, 0xfc, 0x40, 0xae, 0x2f, 0xd4, 0x41, 0x50, 0x6a, 0x60, 0x8a, 0x16, 0x99, 0x59, 0x90, + 0x09, 0xc1, 0x09, 0x9a, 0xe8, 0xe3, 0x30, 0x18, 0xd6, 0x9c, 0x06, 0x29, 0x5f, 0x9f, 0x3e, 0x92, + 0x7f, 0xfd, 0x54, 0x39, 0x4a, 0xce, 0xea, 0xe2, 0xe1, 0x9e, 0x38, 0x0a, 0x96, 0xe4, 0xd0, 0x32, + 0xf4, 0xb3, 0x54, 0xd6, 0x2c, 0xb4, 0x6f, 0x4e, 0x44, 0xf9, 0x94, 0x53, 0x0f, 0x3f, 0x9b, 0x58, + 0x31, 0xe6, 0xd5, 0xe9, 0x1e, 0x10, 0x92, 0x02, 0x3f, 0x9c, 0x3e, 0x96, 0xbf, 0x07, 0x84, 0x80, + 0xe1, 0x7a, 0xb5, 0xd3, 0x1e, 0x50, 0x48, 0x38, 0x26, 0x4a, 0x4f, 0x66, 0x7a, 0x9a, 0x1e, 0xef, + 0x60, 0xb0, 0x99, 0x7b, 0x96, 0xb2, 0x93, 0x99, 0x9e, 0xa4, 0x94, 0x84, 0xfd, 0xc7, 0x43, 0x69, + 0x9e, 0x85, 0x49, 0x98, 0xfe, 0x63, 0x2b, 0x65, 0xb1, 0xf1, 0x91, 0x5e, 0x05, 0xde, 0x0f, 0xf0, + 0xe1, 0xfa, 0x79, 0x0b, 0x8e, 0xb7, 0x32, 0x3f, 0x44, 0x30, 0x00, 0xbd, 0xc9, 0xcd, 0xf9, 0xa7, + 0xab, 0x30, 0xd0, 0xd9, 0x70, 0x9c, 0xd3, 0x52, 0x52, 0x38, 0x50, 0x7c, 0xcf, 0xc2, 0x81, 0x15, + 0x18, 0xaa, 0xf1, 0x97, 0x9c, 0x4c, 0x5f, 0xd0, 0x53, 0x10, 0x53, 0xae, 0xa7, 0x15, 0x15, 0xb1, + 0x22, 0x81, 0x7e, 0xd2, 0x82, 0xd3, 0xc9, 0xae, 0x63, 0xc2, 0xc0, 0xc2, 0x5c, 0x93, 0x8b, 0xb5, + 0x96, 0xc5, 0xf7, 0xa7, 0xf8, 0x7f, 0x03, 0x79, 0xbf, 0x1b, 0x02, 0xee, 0xdc, 0x18, 0x5a, 0xcc, + 0x90, 0xab, 0x0d, 0x98, 0x3a, 0xc9, 0x1e, 0x64, 0x6b, 0x2f, 0xc2, 0x68, 0xd3, 0x6f, 0x7b, 0x91, + 0xb0, 0xba, 0x14, 0xa6, 0x5b, 0xcc, 0x64, 0x69, 0x45, 0x2b, 0xc7, 0x06, 0x56, 0x42, 0x22, 0x37, + 0x74, 0xdf, 0x12, 0xb9, 0x77, 0x60, 0xd4, 0xd3, 0x1c, 0x12, 0x3a, 0xbd, 0x60, 0x85, 0x74, 0x51, + 0xc3, 0xe6, 0xbd, 0xd4, 0x4b, 0xb0, 0x41, 0xad, 0xb3, 0xb4, 0x0c, 0xde, 0x9b, 0xb4, 0xec, 0x50, + 0x9f, 0xc4, 0xf6, 0x6f, 0x16, 0x32, 0x5e, 0x0c, 0x5c, 0x2a, 0xf7, 0x9a, 0x29, 0x95, 0x3b, 0x97, + 0x94, 0xca, 0xa5, 0x54, 0x55, 0x86, 0x40, 0xae, 0xf7, 0x1c, 0x9a, 0x3d, 0x07, 0xa6, 0xfe, 0x61, + 0x0b, 0x4e, 0x30, 0xdd, 0x07, 0x6d, 0xe0, 0x3d, 0xeb, 0x3b, 0x98, 0x41, 0xec, 0xb5, 0x6c, 0x72, + 0x38, 0xaf, 0x1d, 0xbb, 0x01, 0x67, 0xbb, 0xdd, 0xbb, 0xcc, 0xbe, 0xb8, 0xae, 0xcc, 0x2b, 0x62, + 0xfb, 0xe2, 0x7a, 0x79, 0x11, 0x33, 0x48, 0xaf, 0x61, 0x17, 0xed, 0xff, 0xdb, 0x82, 0x62, 0xc5, + 0xaf, 0x1f, 0xc2, 0x8b, 0xfe, 0x63, 0xc6, 0x8b, 0xfe, 0x91, 0xec, 0x1b, 0xbf, 0x9e, 0xab, 0xec, + 0x5b, 0x4a, 0x28, 0xfb, 0x4e, 0xe7, 0x11, 0xe8, 0xac, 0xda, 0xfb, 0xe5, 0x22, 0x8c, 0x54, 0xfc, + 0xba, 0xda, 0x67, 0xff, 0xfd, 0xfd, 0xb8, 0x11, 0xe5, 0x66, 0xcd, 0xd2, 0x28, 0x33, 0x7b, 0x62, + 0x19, 0xf5, 0xe2, 0xbb, 0xcc, 0x9b, 0xe8, 0x16, 0x71, 0x37, 0x36, 0x23, 0x52, 0x4f, 0x7e, 0xce, + 0xe1, 0x79, 0x13, 0x7d, 0xab, 0x08, 0x13, 0x89, 0xd6, 0x51, 0x03, 0xc6, 0x1a, 0xba, 0x2a, 0x49, + 0xac, 0xd3, 0xfb, 0xd2, 0x42, 0x09, 0x6f, 0x0c, 0xad, 0x08, 0x9b, 0xc4, 0xd1, 0x2c, 0x80, 0xa7, + 0xdb, 0xa4, 0xab, 0x00, 0xcb, 0x9a, 0x3d, 0xba, 0x86, 0x81, 0x5e, 0x82, 0x91, 0xc8, 0x6f, 0xf9, + 0x0d, 0x7f, 0x63, 0xe7, 0x2a, 0x91, 0x11, 0x39, 0x95, 0xc9, 0xf2, 0x5a, 0x0c, 0xc2, 0x3a, 0x1e, + 0xba, 0x0b, 0x53, 0x8a, 0x48, 0xf5, 0x01, 0xa8, 0xd7, 0x98, 0xd8, 0x64, 0x35, 0x49, 0x11, 0xa7, + 0x1b, 0x41, 0xaf, 0xc0, 0x38, 0xb3, 0x9d, 0x66, 0xf5, 0xaf, 0x92, 0x1d, 0x19, 0xa9, 0x99, 0x71, + 0xd8, 0x2b, 0x06, 0x04, 0x27, 0x30, 0xd1, 0x02, 0x4c, 0x35, 0xdd, 0x30, 0x51, 0x7d, 0x80, 0x55, + 0x67, 0x1d, 0x58, 0x49, 0x02, 0x71, 0x1a, 0xdf, 0xfe, 0x75, 0x31, 0xc7, 0x5e, 0xe4, 0x7e, 0xb0, + 0x1d, 0xdf, 0xdf, 0xdb, 0xf1, 0x9b, 0x16, 0x4c, 0xd2, 0xd6, 0x99, 0x41, 0xa8, 0x64, 0xa4, 0x54, + 0x2e, 0x0f, 0xab, 0x43, 0x2e, 0x8f, 0x73, 0xf4, 0xd8, 0xae, 0xfb, 0xed, 0x48, 0x48, 0x47, 0xb5, + 0x73, 0x99, 0x96, 0x62, 0x01, 0x15, 0x78, 0x24, 0x08, 0x84, 0xd7, 0xbd, 0x8e, 0x47, 0x82, 0x00, + 0x0b, 0xa8, 0x4c, 0xf5, 0xd1, 0x97, 0x9d, 0xea, 0x83, 0x47, 0x6c, 0x17, 0x76, 0x74, 0x82, 0xa5, + 0xd5, 0x22, 0xb6, 0x4b, 0x03, 0xbb, 0x18, 0xc7, 0xfe, 0x76, 0x11, 0x46, 0x2b, 0x7e, 0x3d, 0x36, + 0xec, 0x78, 0xd1, 0x30, 0xec, 0x38, 0x9b, 0x30, 0xec, 0x98, 0xd4, 0x71, 0x35, 0x33, 0x8e, 0x37, + 0x01, 0xf9, 0x22, 0x90, 0xfc, 0x65, 0xe2, 0x31, 0xbb, 0x37, 0x61, 0xa8, 0x57, 0x8c, 0xcd, 0x1e, + 0xae, 0xa7, 0x30, 0x70, 0x46, 0xad, 0x0f, 0x4c, 0x42, 0x0e, 0xd7, 0x24, 0xe4, 0x4f, 0x2c, 0xb6, + 0x02, 0x16, 0x57, 0xab, 0xdc, 0x56, 0x19, 0x5d, 0x84, 0x11, 0x76, 0x5a, 0xb2, 0x90, 0x11, 0xd2, + 0x72, 0x82, 0xa5, 0xf1, 0x5c, 0x8d, 0x8b, 0xb1, 0x8e, 0x83, 0xce, 0xc3, 0x50, 0x48, 0x9c, 0xa0, + 0xb6, 0xa9, 0xae, 0x0a, 0x61, 0xe6, 0xc0, 0xcb, 0xb0, 0x82, 0xa2, 0xb7, 0xe2, 0xc0, 0xe3, 0xc5, + 0x7c, 0xc3, 0x67, 0xbd, 0x3f, 0x7c, 0xbb, 0xe5, 0x47, 0x1b, 0xb7, 0x6f, 0x01, 0x4a, 0xe3, 0xf7, + 0xe0, 0x49, 0x56, 0x32, 0x43, 0xe3, 0x0e, 0xa7, 0xc2, 0xe2, 0xfe, 0x9b, 0x05, 0xe3, 0x15, 0xbf, + 0x4e, 0x8f, 0x81, 0xef, 0xa5, 0x3d, 0xaf, 0x67, 0x5d, 0x18, 0xe8, 0x90, 0x75, 0xe1, 0x31, 0xe8, + 0xaf, 0xf8, 0xf5, 0x2e, 0xe1, 0x7b, 0x7f, 0xc5, 0x82, 0xc1, 0x8a, 0x5f, 0x3f, 0x04, 0x25, 0xce, + 0x6b, 0xa6, 0x12, 0xe7, 0x44, 0xce, 0xba, 0xc9, 0xd1, 0xdb, 0xfc, 0x79, 0x1f, 0x8c, 0xd1, 0x7e, + 0xfa, 0x1b, 0x72, 0x2a, 0x8d, 0x61, 0xb3, 0x7a, 0x18, 0x36, 0xfa, 0xa4, 0xf0, 0x1b, 0x0d, 0xff, + 0x4e, 0x72, 0x5a, 0x97, 0x59, 0x29, 0x16, 0x50, 0xf4, 0x2c, 0x0c, 0xb5, 0x02, 0xb2, 0xed, 0xfa, + 0x82, 0x57, 0xd7, 0x54, 0x62, 0x15, 0x51, 0x8e, 0x15, 0x06, 0x7d, 0xc4, 0x87, 0xae, 0x47, 0xf9, + 0x92, 0x9a, 0xef, 0xd5, 0xb9, 0x9e, 0xa3, 0x28, 0x52, 0x83, 0x69, 0xe5, 0xd8, 0xc0, 0x42, 0xb7, + 0x60, 0x98, 0xfd, 0x67, 0xc7, 0x4e, 0xff, 0x81, 0x8f, 0x1d, 0x91, 0x2c, 0x59, 0x10, 0xc0, 0x31, + 0x2d, 0xf4, 0x3c, 0x40, 0x24, 0xd3, 0xeb, 0x84, 0x22, 0x8c, 0xab, 0x7a, 0xd7, 0xa8, 0xc4, 0x3b, + 0x21, 0xd6, 0xb0, 0xd0, 0x33, 0x30, 0x1c, 0x39, 0x6e, 0xe3, 0x9a, 0xeb, 0x31, 0x5b, 0x00, 0xda, + 0x7f, 0x91, 0xb3, 0x58, 0x14, 0xe2, 0x18, 0x4e, 0xf9, 0x4a, 0x16, 0xdd, 0x6a, 0x7e, 0x27, 0x12, + 0xe9, 0xf9, 0x8a, 0x9c, 0xaf, 0xbc, 0xa6, 0x4a, 0xb1, 0x86, 0x81, 0x36, 0xe1, 0x94, 0xeb, 0xb1, + 0x34, 0x5a, 0xa4, 0xba, 0xe5, 0xb6, 0xd6, 0xae, 0x55, 0x6f, 0x92, 0xc0, 0x5d, 0xdf, 0x99, 0x77, + 0x6a, 0x5b, 0xc4, 0xab, 0x33, 0xb1, 0xc3, 0xd0, 0xfc, 0xe3, 0xa2, 0x8b, 0xa7, 0xca, 0x1d, 0x70, + 0x71, 0x47, 0x4a, 0xc8, 0xa6, 0xdb, 0x31, 0x20, 0x4e, 0x53, 0xc8, 0x17, 0x78, 0x0a, 0x1e, 0x56, + 0x82, 0x05, 0xc4, 0x7e, 0x81, 0xed, 0x89, 0xeb, 0x55, 0xf4, 0xb4, 0x71, 0xbc, 0x1c, 0xd7, 0x8f, + 0x97, 0xfd, 0xdd, 0xd2, 0xc0, 0xf5, 0xaa, 0x16, 0xe9, 0xe8, 0x12, 0x1c, 0xab, 0xf8, 0xf5, 0x8a, + 0x1f, 0x44, 0xcb, 0x7e, 0x70, 0xc7, 0x09, 0xea, 0x72, 0x09, 0x96, 0x64, 0xac, 0x27, 0x7a, 0xc6, + 0xf6, 0xf3, 0x13, 0xc8, 0x88, 0xe3, 0xf4, 0x02, 0xe3, 0x10, 0x0f, 0xe8, 0x5a, 0x5b, 0x63, 0xbc, + 0x8a, 0x4a, 0x56, 0x77, 0xd9, 0x89, 0x08, 0xba, 0x0e, 0x63, 0x35, 0xfd, 0xda, 0x16, 0xd5, 0x9f, + 0x92, 0x97, 0x9d, 0x71, 0xa7, 0x67, 0xde, 0xf3, 0x66, 0x7d, 0xfb, 0x1b, 0x96, 0x68, 0x85, 0x4b, + 0x3e, 0xb8, 0x0d, 0x6d, 0xf7, 0x33, 0x77, 0x01, 0xa6, 0x02, 0xbd, 0x8a, 0x66, 0x8b, 0x76, 0x8c, + 0x67, 0xff, 0x49, 0x00, 0x71, 0x1a, 0x1f, 0x7d, 0x12, 0x4e, 0x1a, 0x85, 0x52, 0x2d, 0xaf, 0xe5, + 0xe0, 0x66, 0xb2, 0x21, 0x9c, 0x87, 0x84, 0xf3, 0xeb, 0xdb, 0x3f, 0x08, 0xc7, 0x93, 0xdf, 0x25, + 0xa4, 0x35, 0xf7, 0xf9, 0x75, 0x85, 0x83, 0x7d, 0x9d, 0xfd, 0x12, 0x4c, 0xd1, 0x67, 0xbc, 0x62, + 0x49, 0xd9, 0xfc, 0x75, 0x0f, 0xa7, 0xf5, 0xdb, 0x43, 0xec, 0x1a, 0x4c, 0x64, 0xa0, 0x43, 0x9f, + 0x86, 0xf1, 0x90, 0xb0, 0x18, 0x72, 0x52, 0x4a, 0xd8, 0xc1, 0x2f, 0xbe, 0xba, 0xa4, 0x63, 0xf2, + 0x97, 0x90, 0x59, 0x86, 0x13, 0xd4, 0x50, 0x13, 0xc6, 0xef, 0xb8, 0x5e, 0xdd, 0xbf, 0x13, 0x4a, + 0xfa, 0x43, 0xf9, 0x2a, 0x87, 0x5b, 0x1c, 0x33, 0xd1, 0x47, 0xa3, 0xb9, 0x5b, 0x06, 0x31, 0x9c, + 0x20, 0x4e, 0x8f, 0x9a, 0xa0, 0xed, 0xcd, 0x85, 0x37, 0x42, 0x12, 0x88, 0x08, 0x77, 0xec, 0xa8, + 0xc1, 0xb2, 0x10, 0xc7, 0x70, 0x7a, 0xd4, 0xb0, 0x3f, 0xcc, 0xb1, 0x9e, 0x9d, 0x65, 0xe2, 0xa8, + 0xc1, 0xaa, 0x14, 0x6b, 0x18, 0xf4, 0x28, 0x66, 0xff, 0x56, 0x7d, 0x0f, 0xfb, 0x7e, 0x24, 0x0f, + 0x6f, 0x96, 0xae, 0x53, 0x2b, 0xc7, 0x06, 0x56, 0x4e, 0x3c, 0xbd, 0xbe, 0x83, 0xc6, 0xd3, 0x43, + 0x51, 0x87, 0x58, 0x02, 0x3c, 0x22, 0xf4, 0xa5, 0x4e, 0xb1, 0x04, 0xf6, 0xef, 0x2b, 0xce, 0x00, + 0xe5, 0x05, 0xd6, 0xc5, 0x00, 0xf5, 0xf3, 0x80, 0x81, 0x4c, 0x29, 0x5a, 0xe5, 0xa3, 0x23, 0x61, + 0x68, 0x09, 0x06, 0xc3, 0x9d, 0xb0, 0x16, 0x35, 0xc2, 0x4e, 0x29, 0x59, 0xab, 0x0c, 0x45, 0xcb, + 0x08, 0xce, 0xab, 0x60, 0x59, 0x17, 0xd5, 0xe0, 0x88, 0xa0, 0xb8, 0xb0, 0xe9, 0x78, 0x2a, 0x51, + 0x24, 0xb7, 0x7e, 0xbc, 0xb8, 0xb7, 0x5b, 0x3a, 0x22, 0x5a, 0xd6, 0xc1, 0xfb, 0xbb, 0x25, 0xba, + 0x25, 0x33, 0x20, 0x38, 0x8b, 0x1a, 0x5f, 0xf2, 0xb5, 0x9a, 0xdf, 0x6c, 0x55, 0x02, 0x7f, 0xdd, + 0x6d, 0x90, 0x4e, 0x8a, 0xe5, 0xaa, 0x81, 0x29, 0x96, 0xbc, 0x51, 0x86, 0x13, 0xd4, 0xd0, 0x6d, + 0x98, 0x70, 0x5a, 0xad, 0xb9, 0xa0, 0xe9, 0x07, 0xb2, 0x81, 0x91, 0x7c, 0x0d, 0xc5, 0x9c, 0x89, + 0xca, 0xf3, 0x44, 0x26, 0x0a, 0x71, 0x92, 0x20, 0x1d, 0x28, 0xb1, 0xd1, 0x8c, 0x81, 0x1a, 0x8b, + 0x07, 0x4a, 0xec, 0xcb, 0x8c, 0x81, 0xca, 0x80, 0xe0, 0x2c, 0x6a, 0xf6, 0x0f, 0x30, 0xc6, 0x9f, + 0xc5, 0x9b, 0x66, 0x6e, 0x46, 0x4d, 0x18, 0x6b, 0xb1, 0x63, 0x5f, 0xe4, 0x70, 0x13, 0x47, 0xc5, + 0x8b, 0x3d, 0x0a, 0x42, 0xef, 0xb0, 0x2c, 0xb4, 0x86, 0x41, 0x6c, 0x45, 0x27, 0x87, 0x4d, 0xea, + 0xf6, 0x2f, 0xcd, 0x30, 0xd6, 0xb1, 0xca, 0xa5, 0x9b, 0x83, 0xc2, 0xe9, 0x52, 0xc8, 0x33, 0x66, + 0xf2, 0xf5, 0x08, 0xf1, 0xfa, 0x12, 0x8e, 0x9b, 0x58, 0xd6, 0x45, 0x9f, 0x82, 0x71, 0xd7, 0x73, + 0xe3, 0xec, 0xcd, 0xe1, 0xf4, 0xd1, 0xfc, 0x68, 0x5e, 0x0a, 0x4b, 0xcf, 0xef, 0xa8, 0x57, 0xc6, + 0x09, 0x62, 0xe8, 0x2d, 0x66, 0x23, 0x2a, 0x49, 0x17, 0x7a, 0x21, 0xad, 0x9b, 0x83, 0x4a, 0xb2, + 0x1a, 0x11, 0xd4, 0x86, 0x23, 0xe9, 0x2c, 0xd6, 0xe1, 0xb4, 0x9d, 0xff, 0x36, 0x4a, 0x27, 0xa2, + 0x8e, 0x13, 0xf1, 0xa5, 0x61, 0x21, 0xce, 0xa2, 0x8f, 0xae, 0x25, 0x73, 0x0c, 0x17, 0x0d, 0x0d, + 0x44, 0x2a, 0xcf, 0xf0, 0x58, 0xc7, 0xf4, 0xc2, 0x1b, 0x70, 0x5a, 0x4b, 0xd3, 0x7a, 0x39, 0x70, + 0x98, 0x8d, 0x92, 0xcb, 0x6e, 0x23, 0x8d, 0xa9, 0x7d, 0x74, 0x6f, 0xb7, 0x74, 0x7a, 0xad, 0x13, + 0x22, 0xee, 0x4c, 0x07, 0x5d, 0x87, 0x63, 0x3c, 0x16, 0xcd, 0x22, 0x71, 0xea, 0x0d, 0xd7, 0x53, + 0x5c, 0x33, 0x3f, 0xbb, 0x4e, 0xee, 0xed, 0x96, 0x8e, 0xcd, 0x65, 0x21, 0xe0, 0xec, 0x7a, 0xe8, + 0x35, 0x18, 0xae, 0x7b, 0xf2, 0x94, 0x1d, 0x30, 0x32, 0xe1, 0x0e, 0x2f, 0xae, 0x56, 0xd5, 0xf7, + 0xc7, 0x7f, 0x70, 0x5c, 0x01, 0x6d, 0x70, 0x15, 0x98, 0x92, 0x5b, 0x0e, 0xa6, 0x42, 0x94, 0x26, + 0x45, 0xfb, 0x46, 0x70, 0x07, 0xae, 0xfb, 0x55, 0x0e, 0x80, 0x46, 0xdc, 0x07, 0x83, 0x30, 0x7a, + 0x13, 0x90, 0xc8, 0xb8, 0x34, 0x57, 0x63, 0x09, 0x02, 0x35, 0xbb, 0x54, 0x25, 0x42, 0xa8, 0xa6, + 0x30, 0x70, 0x46, 0x2d, 0x74, 0x85, 0x1e, 0x8f, 0x7a, 0xa9, 0x38, 0x7e, 0x55, 0xbe, 0xf5, 0x45, + 0xd2, 0x0a, 0x08, 0x33, 0xa5, 0x34, 0x29, 0xe2, 0x44, 0x3d, 0x54, 0x87, 0x53, 0x4e, 0x3b, 0xf2, + 0x99, 0x76, 0xd1, 0x44, 0x5d, 0xf3, 0xb7, 0x88, 0xc7, 0x14, 0xfb, 0x43, 0x2c, 0xf4, 0xe9, 0xa9, + 0xb9, 0x0e, 0x78, 0xb8, 0x23, 0x15, 0xfa, 0x9c, 0xa2, 0x63, 0xa1, 0x29, 0xfe, 0x0c, 0x3f, 0x75, + 0xae, 0x0d, 0x97, 0x18, 0xe8, 0x25, 0x18, 0xd9, 0xf4, 0xc3, 0x68, 0x95, 0x44, 0x77, 0xfc, 0x60, + 0x4b, 0xa4, 0x78, 0x88, 0xd3, 0xea, 0xc4, 0x20, 0xac, 0xe3, 0xa1, 0xa7, 0x60, 0x90, 0x99, 0x9d, + 0x95, 0x17, 0xd9, 0x5d, 0x3b, 0x14, 0x9f, 0x31, 0x57, 0x78, 0x31, 0x96, 0x70, 0x89, 0x5a, 0xae, + 0x2c, 0xb0, 0xe3, 0x38, 0x81, 0x5a, 0xae, 0x2c, 0x60, 0x09, 0xa7, 0xcb, 0x35, 0xdc, 0x74, 0x02, + 0x52, 0x09, 0xfc, 0x1a, 0x09, 0xb5, 0x64, 0x4e, 0x8f, 0xf0, 0x04, 0x16, 0x74, 0xb9, 0x56, 0xb3, + 0x10, 0x70, 0x76, 0x3d, 0x44, 0xd2, 0x29, 0x8a, 0xc7, 0xf3, 0xd5, 0xae, 0x69, 0x76, 0xb0, 0xc7, + 0x2c, 0xc5, 0x1e, 0x4c, 0xaa, 0xe4, 0xc8, 0x3c, 0x65, 0x45, 0x38, 0x3d, 0xc1, 0xd6, 0x76, 0xef, + 0xf9, 0x2e, 0x94, 0x22, 0xbb, 0x9c, 0xa0, 0x84, 0x53, 0xb4, 0x8d, 0xd8, 0xba, 0x93, 0x5d, 0x63, + 0xeb, 0x5e, 0x80, 0xe1, 0xb0, 0x7d, 0xbb, 0xee, 0x37, 0x1d, 0xd7, 0x63, 0xd6, 0x3b, 0xda, 0xc3, + 0xbd, 0x2a, 0x01, 0x38, 0xc6, 0x41, 0xcb, 0x30, 0xe4, 0x48, 0x2d, 0x35, 0xca, 0x0f, 0x1b, 0xa8, + 0x74, 0xd3, 0x3c, 0x92, 0x96, 0xd4, 0x4b, 0xab, 0xba, 0xe8, 0x55, 0x18, 0x13, 0xa1, 0x49, 0x78, + 0x14, 0x1e, 0x66, 0x5d, 0xa3, 0x39, 0x53, 0x57, 0x75, 0x20, 0x36, 0x71, 0xd1, 0x0d, 0x18, 0x89, + 0xfc, 0x86, 0x90, 0x71, 0x86, 0xd3, 0xc7, 0xf3, 0xa3, 0xfb, 0xae, 0x29, 0x34, 0x5d, 0x7f, 0xa2, + 0xaa, 0x62, 0x9d, 0x0e, 0x5a, 0xe3, 0xeb, 0x9d, 0xa5, 0x6e, 0x22, 0xa1, 0x48, 0x48, 0x7f, 0x3a, + 0xcf, 0xf4, 0x92, 0xa1, 0x99, 0xdb, 0x41, 0xd4, 0xc4, 0x3a, 0x19, 0x74, 0x19, 0xa6, 0x5a, 0x81, + 0xeb, 0xb3, 0x35, 0xa1, 0xb4, 0xee, 0xd3, 0x66, 0xa2, 0xd6, 0x4a, 0x12, 0x01, 0xa7, 0xeb, 0xb0, + 0xc8, 0x32, 0xa2, 0x70, 0xfa, 0x24, 0x4f, 0x36, 0xc7, 0xe5, 0x20, 0xbc, 0x0c, 0x2b, 0x28, 0x5a, + 0x61, 0x27, 0x31, 0x17, 0xe1, 0x4d, 0xcf, 0xe4, 0xc7, 0x2b, 0xd0, 0x45, 0x7d, 0x9c, 0xf7, 0x57, + 0x7f, 0x71, 0x4c, 0x01, 0xd5, 0xb5, 0x1c, 0xef, 0xf4, 0x05, 0x15, 0x4e, 0x9f, 0xea, 0x60, 0xfb, + 0x9b, 0x78, 0x2e, 0xc7, 0x0c, 0x81, 0x51, 0x1c, 0xe2, 0x04, 0x4d, 0xf4, 0x06, 0x4c, 0x8a, 0xb0, + 0x0b, 0xf1, 0x30, 0x9d, 0x8e, 0xfd, 0xa3, 0x70, 0x02, 0x86, 0x53, 0xd8, 0x3c, 0xd9, 0x9b, 0x73, + 0xbb, 0x41, 0xc4, 0xd1, 0x77, 0xcd, 0xf5, 0xb6, 0xc2, 0xe9, 0x33, 0xec, 0x7c, 0x10, 0xc9, 0xde, + 0x92, 0x50, 0x9c, 0x51, 0x03, 0xad, 0xc1, 0x64, 0x2b, 0x20, 0xa4, 0xc9, 0xde, 0x49, 0xe2, 0x3e, + 0x2b, 0xf1, 0xc0, 0x4a, 0xb4, 0x27, 0x95, 0x04, 0x6c, 0x3f, 0xa3, 0x0c, 0xa7, 0x28, 0xa0, 0x3b, + 0x30, 0xe4, 0x6f, 0x93, 0x60, 0x93, 0x38, 0xf5, 0xe9, 0xb3, 0x1d, 0xbc, 0xf6, 0xc4, 0xe5, 0x76, + 0x5d, 0xe0, 0x26, 0x8c, 0x9a, 0x64, 0x71, 0x77, 0xa3, 0x26, 0xd9, 0x18, 0xfa, 0x4f, 0x2c, 0x38, + 0x29, 0xd5, 0x84, 0xd5, 0x16, 0x1d, 0xf5, 0x05, 0xdf, 0x0b, 0xa3, 0x80, 0x87, 0x02, 0x7a, 0x34, + 0x3f, 0x3c, 0xce, 0x5a, 0x4e, 0x25, 0xa5, 0x45, 0x38, 0x99, 0x87, 0x11, 0xe2, 0xfc, 0x16, 0xe9, + 0xcb, 0x3e, 0x24, 0x91, 0x3c, 0x8c, 0xe6, 0xc2, 0xe5, 0xb7, 0x16, 0x57, 0xa7, 0x1f, 0xe3, 0x71, + 0x8c, 0xe8, 0x66, 0xa8, 0x26, 0x81, 0x38, 0x8d, 0x8f, 0x2e, 0x42, 0xc1, 0x0f, 0xa7, 0x1f, 0x67, + 0x6b, 0xfb, 0x64, 0xce, 0x38, 0x5e, 0xaf, 0x72, 0xe3, 0xd6, 0xeb, 0x55, 0x5c, 0xf0, 0x43, 0x99, + 0x70, 0x8d, 0x3e, 0x67, 0xc3, 0xe9, 0x27, 0xb8, 0xcc, 0x59, 0x26, 0x5c, 0x63, 0x85, 0x38, 0x86, + 0xa3, 0x4d, 0x98, 0x08, 0x0d, 0xb1, 0x41, 0x38, 0x7d, 0x8e, 0x8d, 0xd4, 0x13, 0x79, 0x93, 0x66, + 0x60, 0x6b, 0x99, 0x90, 0x4c, 0x2a, 0x38, 0x49, 0x96, 0xef, 0x2e, 0x4d, 0x70, 0x11, 0x4e, 0x3f, + 0xd9, 0x65, 0x77, 0x69, 0xc8, 0xfa, 0xee, 0xd2, 0x69, 0xe0, 0x04, 0x4d, 0x74, 0x43, 0x77, 0x89, + 0x3c, 0x9f, 0x6f, 0x28, 0x99, 0xe9, 0x0c, 0x39, 0x96, 0xe7, 0x08, 0x39, 0xf3, 0x7d, 0x30, 0x95, + 0xe2, 0xc2, 0x0e, 0xe2, 0x1f, 0x32, 0xb3, 0x05, 0x63, 0xc6, 0x4a, 0x7f, 0xa8, 0xe6, 0x43, 0x3f, + 0x03, 0x30, 0xac, 0xcc, 0x3a, 0x72, 0xf4, 0x6c, 0x53, 0xf7, 0xa5, 0x67, 0xbb, 0x60, 0x5a, 0x1f, + 0x9d, 0x4c, 0x5a, 0x1f, 0x0d, 0x55, 0xfc, 0xba, 0x61, 0x70, 0xb4, 0x96, 0x11, 0x41, 0x38, 0xef, + 0x8c, 0xee, 0xdd, 0x21, 0x4e, 0x53, 0x55, 0x15, 0x7b, 0x36, 0x63, 0xea, 0xeb, 0xa8, 0xfd, 0xba, + 0x0c, 0x53, 0x9e, 0xcf, 0x9e, 0x11, 0xa4, 0x2e, 0x79, 0x44, 0xc6, 0x0a, 0x0e, 0xeb, 0x11, 0xee, + 0x12, 0x08, 0x38, 0x5d, 0x87, 0x36, 0xc8, 0x79, 0xb9, 0xa4, 0xba, 0x8d, 0xb3, 0x7a, 0x58, 0x40, + 0xe9, 0xf3, 0x95, 0xff, 0x0a, 0xa7, 0x27, 0xf3, 0x9f, 0xaf, 0xbc, 0x52, 0x92, 0x5f, 0x0c, 0x25, + 0xbf, 0xc8, 0xb4, 0x4b, 0x2d, 0xbf, 0x5e, 0xae, 0x88, 0x97, 0x88, 0x16, 0xdb, 0xbf, 0x5e, 0xae, + 0x60, 0x0e, 0x43, 0x73, 0x30, 0xc0, 0x7e, 0xc8, 0xc8, 0x41, 0x79, 0x27, 0x49, 0xb9, 0xa2, 0xe5, + 0xa4, 0x65, 0x15, 0xb0, 0xa8, 0xc8, 0xb4, 0x07, 0xf4, 0xf9, 0xc6, 0xb4, 0x07, 0x83, 0xf7, 0xa9, + 0x3d, 0x90, 0x04, 0x70, 0x4c, 0x0b, 0xdd, 0x85, 0x63, 0xc6, 0x93, 0x59, 0x79, 0x08, 0x42, 0xbe, + 0x91, 0x42, 0x02, 0x79, 0xfe, 0xb4, 0xe8, 0xf4, 0xb1, 0x72, 0x16, 0x25, 0x9c, 0xdd, 0x00, 0x6a, + 0xc0, 0x54, 0x2d, 0xd5, 0xea, 0x50, 0xef, 0xad, 0xaa, 0x75, 0x91, 0x6e, 0x31, 0x4d, 0x18, 0xbd, + 0x0a, 0x43, 0xef, 0xfa, 0xdc, 0xa0, 0x50, 0xbc, 0x9e, 0x64, 0x7c, 0x9b, 0xa1, 0xb7, 0xae, 0x57, + 0x59, 0xf9, 0xfe, 0x6e, 0x69, 0xa4, 0xe2, 0xd7, 0xe5, 0x5f, 0xac, 0x2a, 0xa0, 0x1f, 0xb3, 0x60, + 0x26, 0xfd, 0x26, 0x57, 0x9d, 0x1e, 0xeb, 0xbd, 0xd3, 0xb6, 0x68, 0x74, 0x66, 0x29, 0x97, 0x1c, + 0xee, 0xd0, 0x14, 0xfa, 0x28, 0xdd, 0x4f, 0xa1, 0x7b, 0x8f, 0x88, 0x84, 0xfe, 0x8f, 0xc6, 0xfb, + 0x89, 0x96, 0xee, 0xef, 0x96, 0x26, 0xf8, 0xe1, 0xed, 0xde, 0x53, 0x59, 0x08, 0x78, 0x05, 0xf4, + 0x83, 0x70, 0x2c, 0x48, 0xcb, 0xc8, 0x89, 0x7c, 0x27, 0x3c, 0xdd, 0xcb, 0x45, 0x90, 0x9c, 0x70, + 0x9c, 0x45, 0x10, 0x67, 0xb7, 0x63, 0xff, 0xa1, 0xc5, 0x74, 0x23, 0xa2, 0x5b, 0x24, 0x6c, 0x37, + 0xa2, 0x43, 0x30, 0xe2, 0x5b, 0x32, 0x6c, 0x13, 0xee, 0xdb, 0x0a, 0xef, 0xbf, 0xb3, 0x98, 0x15, + 0xde, 0x21, 0xfa, 0x13, 0xbe, 0x05, 0x43, 0x91, 0x68, 0x4d, 0x74, 0x3d, 0xcf, 0x62, 0x48, 0x76, + 0x8a, 0x59, 0x22, 0xaa, 0x77, 0x98, 0x2c, 0xc5, 0x8a, 0x8c, 0xfd, 0x5f, 0xf3, 0x19, 0x90, 0x90, + 0x43, 0x50, 0x01, 0x2f, 0x9a, 0x2a, 0xe0, 0x52, 0x97, 0x2f, 0xc8, 0x51, 0x05, 0xff, 0x57, 0x66, + 0xbf, 0x99, 0xfc, 0xf1, 0xfd, 0x6e, 0xfe, 0x69, 0x7f, 0xd1, 0x02, 0x88, 0xd3, 0xbe, 0xf4, 0x90, + 0xc0, 0xfb, 0x12, 0x7d, 0x79, 0xf9, 0x91, 0x5f, 0xf3, 0x1b, 0x42, 0x05, 0x75, 0x2a, 0xd6, 0x42, + 0xf3, 0xf2, 0x7d, 0xed, 0x37, 0x56, 0xd8, 0xa8, 0x24, 0xe3, 0x30, 0x17, 0x63, 0xbb, 0x08, 0x23, + 0x06, 0xf3, 0x57, 0x2c, 0x38, 0x9a, 0xe5, 0x9c, 0x42, 0xdf, 0xf1, 0x5c, 0x12, 0xab, 0x4c, 0x73, + 0xd5, 0x6c, 0xde, 0x14, 0xe5, 0x58, 0x61, 0xf4, 0x9c, 0x19, 0xfd, 0x60, 0x29, 0x49, 0xae, 0xc3, + 0x58, 0x25, 0x20, 0x1a, 0x7f, 0xf1, 0x7a, 0x9c, 0x2d, 0x69, 0x78, 0xfe, 0xd9, 0x03, 0x47, 0x7c, + 0xb2, 0xbf, 0x5a, 0x80, 0xa3, 0xdc, 0xc0, 0x6c, 0x6e, 0xdb, 0x77, 0xeb, 0x15, 0xbf, 0x2e, 0x5c, + 0x8a, 0xdf, 0x86, 0xd1, 0x96, 0x26, 0x3e, 0xef, 0x14, 0x5e, 0x5f, 0x17, 0xb3, 0xc7, 0x02, 0x3f, + 0xbd, 0x14, 0x1b, 0xb4, 0x50, 0x1d, 0x46, 0xc9, 0xb6, 0x5b, 0x53, 0x96, 0x45, 0x85, 0x03, 0x5f, + 0xd2, 0xaa, 0x95, 0x25, 0x8d, 0x0e, 0x36, 0xa8, 0xf6, 0x6c, 0x16, 0xae, 0xb1, 0x68, 0x7d, 0x5d, + 0xac, 0x89, 0x7e, 0xce, 0x82, 0x13, 0x39, 0xc1, 0xf8, 0x69, 0x73, 0x77, 0x98, 0x29, 0x9f, 0x58, + 0xb6, 0xaa, 0x39, 0x6e, 0xe0, 0x87, 0x05, 0x14, 0x7d, 0x1c, 0xa0, 0x15, 0xa7, 0x30, 0xed, 0x12, + 0xb5, 0xdc, 0x88, 0x5f, 0xac, 0x85, 0xa2, 0x55, 0x99, 0x4e, 0x35, 0x5a, 0xf6, 0x57, 0xfa, 0xa0, + 0x9f, 0x19, 0x71, 0xa1, 0x0a, 0x0c, 0x6e, 0xf2, 0x48, 0x89, 0x1d, 0xe7, 0x8d, 0xe2, 0xca, 0xd0, + 0x8b, 0xf1, 0xbc, 0x69, 0xa5, 0x58, 0x92, 0x41, 0x2b, 0x70, 0x84, 0xa7, 0x67, 0x6d, 0x2c, 0x92, + 0x86, 0xb3, 0x23, 0x25, 0xd3, 0x05, 0xf6, 0xa9, 0x4a, 0x42, 0x5f, 0x4e, 0xa3, 0xe0, 0xac, 0x7a, + 0xe8, 0x75, 0x18, 0x8f, 0xdc, 0x26, 0xf1, 0xdb, 0x91, 0xa4, 0xc4, 0xf3, 0xa1, 0xaa, 0xc7, 0xd3, + 0x9a, 0x01, 0xc5, 0x09, 0x6c, 0xf4, 0x2a, 0x8c, 0xb5, 0x52, 0x32, 0xf8, 0xfe, 0x58, 0x58, 0x65, + 0xca, 0xdd, 0x4d, 0x5c, 0xe6, 0x9f, 0xd2, 0x66, 0xde, 0x38, 0x6b, 0x9b, 0x01, 0x09, 0x37, 0xfd, + 0x46, 0x9d, 0x71, 0xc0, 0xfd, 0x9a, 0x7f, 0x4a, 0x02, 0x8e, 0x53, 0x35, 0x28, 0x95, 0x75, 0xc7, + 0x6d, 0xb4, 0x03, 0x12, 0x53, 0x19, 0x30, 0xa9, 0x2c, 0x27, 0xe0, 0x38, 0x55, 0xa3, 0xbb, 0x72, + 0x61, 0xf0, 0xc1, 0x28, 0x17, 0xec, 0x5f, 0x2d, 0x80, 0x31, 0xb5, 0xdf, 0xc3, 0xd9, 0x56, 0x5f, + 0x83, 0xbe, 0x8d, 0xa0, 0x55, 0x13, 0x06, 0x8b, 0x99, 0x5f, 0x76, 0x19, 0x57, 0x16, 0xf4, 0x2f, + 0xa3, 0xff, 0x31, 0xab, 0x45, 0xf7, 0xf8, 0xb1, 0x4a, 0xe0, 0xd3, 0x4b, 0x4e, 0x06, 0x53, 0x55, + 0x6e, 0x60, 0x83, 0xf2, 0xbd, 0xde, 0x21, 0xec, 0xb8, 0xf0, 0x65, 0xe1, 0x14, 0x0c, 0xdb, 0xbe, + 0xaa, 0x78, 0xad, 0x4b, 0x2a, 0xe8, 0x22, 0x8c, 0x88, 0x04, 0x98, 0xcc, 0x5b, 0x89, 0x6f, 0x26, + 0x66, 0x8b, 0xb8, 0x18, 0x17, 0x63, 0x1d, 0xc7, 0xfe, 0xf1, 0x02, 0x1c, 0xc9, 0x70, 0x37, 0xe5, + 0xd7, 0xc8, 0x86, 0x1b, 0x46, 0xc1, 0x4e, 0xf2, 0x72, 0xc2, 0xa2, 0x1c, 0x2b, 0x0c, 0x7a, 0x56, + 0xf1, 0x8b, 0x2a, 0x79, 0x39, 0x09, 0x77, 0x2e, 0x01, 0x3d, 0xd8, 0xe5, 0x44, 0xaf, 0xed, 0x76, + 0x48, 0x64, 0x86, 0x03, 0x75, 0x6d, 0x33, 0xc3, 0x05, 0x06, 0xa1, 0x4f, 0xc0, 0x0d, 0xa5, 0x8d, + 0xd7, 0x9e, 0x80, 0x5c, 0x1f, 0xcf, 0x61, 0xb4, 0x73, 0x11, 0xf1, 0x1c, 0x2f, 0x12, 0x0f, 0xc5, + 0x38, 0xf2, 0x35, 0x2b, 0xc5, 0x02, 0x6a, 0x7f, 0xb9, 0x08, 0x27, 0x73, 0x1d, 0xd0, 0x69, 0xd7, + 0x9b, 0xbe, 0xe7, 0x46, 0xbe, 0x32, 0xf2, 0xe4, 0xd1, 0xae, 0x49, 0x6b, 0x73, 0x45, 0x94, 0x63, + 0x85, 0x81, 0xce, 0x41, 0x3f, 0x93, 0xdb, 0x27, 0x93, 0xdf, 0xe1, 0xf9, 0x45, 0x1e, 0x0b, 0x94, + 0x83, 0xb5, 0x5b, 0xbd, 0xd8, 0xf1, 0x56, 0x7f, 0x8c, 0x72, 0x30, 0x7e, 0x23, 0x79, 0xa1, 0xd0, + 0xee, 0xfa, 0x7e, 0x03, 0x33, 0x20, 0x7a, 0x42, 0x8c, 0x57, 0xc2, 0xaa, 0x11, 0x3b, 0x75, 0x3f, + 0xd4, 0x06, 0xed, 0x29, 0x18, 0xdc, 0x22, 0x3b, 0x81, 0xeb, 0x6d, 0x24, 0xad, 0x5d, 0xaf, 0xf2, + 0x62, 0x2c, 0xe1, 0x66, 0x96, 0xf8, 0xc1, 0x07, 0x91, 0x25, 0x5e, 0x5f, 0x01, 0x43, 0x5d, 0xd9, + 0x93, 0x9f, 0x28, 0xc2, 0x04, 0x9e, 0x5f, 0xfc, 0x60, 0x22, 0x6e, 0xa4, 0x27, 0xe2, 0x41, 0x24, + 0x53, 0x3f, 0xd8, 0x6c, 0xfc, 0x9e, 0x05, 0x13, 0x2c, 0x0d, 0xa7, 0x88, 0x1e, 0xe3, 0xfa, 0xde, + 0x21, 0x3c, 0x05, 0x1e, 0x83, 0xfe, 0x80, 0x36, 0x2a, 0x66, 0x50, 0xed, 0x71, 0xd6, 0x13, 0xcc, + 0x61, 0xe8, 0x14, 0xf4, 0xb1, 0x2e, 0xd0, 0xc9, 0x1b, 0xe5, 0x47, 0xf0, 0xa2, 0x13, 0x39, 0x98, + 0x95, 0xb2, 0x38, 0x96, 0x98, 0xb4, 0x1a, 0x2e, 0xef, 0x74, 0x6c, 0x55, 0xf1, 0xfe, 0x08, 0x4d, + 0x93, 0xd9, 0xb5, 0xf7, 0x16, 0xc7, 0x32, 0x9b, 0x64, 0xe7, 0x67, 0xf6, 0x3f, 0x15, 0xe0, 0x4c, + 0x66, 0xbd, 0x9e, 0xe3, 0x58, 0x76, 0xae, 0xfd, 0x30, 0x93, 0xf6, 0x15, 0x0f, 0xd1, 0x97, 0xa0, + 0xaf, 0x57, 0xee, 0xbf, 0xbf, 0x87, 0xf0, 0x92, 0x99, 0x43, 0xf6, 0x3e, 0x09, 0x2f, 0x99, 0xd9, + 0xb7, 0x1c, 0x31, 0xc1, 0xb7, 0x0b, 0x39, 0xdf, 0xc2, 0x04, 0x06, 0xe7, 0xe9, 0x39, 0xc3, 0x80, + 0xa1, 0x7c, 0x84, 0xf3, 0x33, 0x86, 0x97, 0x61, 0x05, 0x45, 0x73, 0x30, 0xd1, 0x74, 0x3d, 0x7a, + 0xf8, 0xec, 0x98, 0xac, 0xb8, 0x52, 0xb7, 0xac, 0x98, 0x60, 0x9c, 0xc4, 0x47, 0xae, 0x16, 0x7a, + 0x92, 0x7f, 0xdd, 0xab, 0x07, 0xda, 0x75, 0xb3, 0xa6, 0xc5, 0x89, 0x1a, 0xc5, 0x8c, 0x30, 0x94, + 0x2b, 0x9a, 0x9c, 0xa8, 0xd8, 0xbb, 0x9c, 0x68, 0x34, 0x5b, 0x46, 0x34, 0xf3, 0x2a, 0x8c, 0xdd, + 0xb7, 0x9e, 0xc5, 0xfe, 0x66, 0x11, 0x1e, 0xe9, 0xb0, 0xed, 0xf9, 0x59, 0x6f, 0xcc, 0x81, 0x76, + 0xd6, 0xa7, 0xe6, 0xa1, 0x02, 0x47, 0xd7, 0xdb, 0x8d, 0xc6, 0x0e, 0x73, 0xc0, 0x23, 0x75, 0x89, + 0x21, 0x78, 0x4a, 0x29, 0x1c, 0x39, 0xba, 0x9c, 0x81, 0x83, 0x33, 0x6b, 0xd2, 0x27, 0x16, 0xbd, + 0x49, 0x76, 0x14, 0xa9, 0xc4, 0x13, 0x0b, 0xeb, 0x40, 0x6c, 0xe2, 0xa2, 0xcb, 0x30, 0xe5, 0x6c, + 0x3b, 0x2e, 0x4f, 0x7a, 0x22, 0x09, 0xf0, 0x37, 0x96, 0x92, 0x45, 0xcf, 0x25, 0x11, 0x70, 0xba, + 0x4e, 0x8e, 0x4a, 0xa8, 0x78, 0x5f, 0x2a, 0x21, 0x33, 0x08, 0xe2, 0x40, 0x7e, 0x10, 0xc4, 0xce, + 0xe7, 0x62, 0xd7, 0x7c, 0x91, 0xef, 0xc0, 0xd8, 0x41, 0x2d, 0xc7, 0x9f, 0x82, 0xc1, 0x40, 0x64, + 0xe2, 0x4f, 0x78, 0xbb, 0xcb, 0x3c, 0xe5, 0x12, 0x6e, 0xff, 0x6f, 0x16, 0x28, 0x59, 0xb2, 0x19, + 0xef, 0xfc, 0x55, 0x66, 0x06, 0xcf, 0xa5, 0xe0, 0x5a, 0x88, 0xb3, 0x63, 0x9a, 0x19, 0x7c, 0x0c, + 0xc4, 0x26, 0x2e, 0x5f, 0x6e, 0x61, 0x1c, 0x59, 0xc3, 0x78, 0x40, 0x08, 0x0d, 0xa4, 0xc2, 0x40, + 0x9f, 0x80, 0xc1, 0xba, 0xbb, 0xed, 0x86, 0x42, 0x8e, 0x76, 0x60, 0x1d, 0x60, 0xfc, 0x7d, 0x8b, + 0x9c, 0x0c, 0x96, 0xf4, 0xec, 0x9f, 0xb2, 0x40, 0xa9, 0x4e, 0xaf, 0x10, 0xa7, 0x11, 0x6d, 0xa2, + 0x37, 0x00, 0x24, 0x05, 0x25, 0x7b, 0x93, 0x06, 0x5d, 0x80, 0x15, 0x64, 0xdf, 0xf8, 0x87, 0xb5, + 0x3a, 0xe8, 0x75, 0x18, 0xd8, 0x64, 0xb4, 0xc4, 0xb7, 0x9d, 0x53, 0xaa, 0x2e, 0x56, 0xba, 0xbf, + 0x5b, 0x3a, 0x6a, 0xb6, 0x29, 0x6f, 0x31, 0x5e, 0xcb, 0xfe, 0x89, 0x42, 0x3c, 0xa7, 0x6f, 0xb5, + 0xfd, 0xc8, 0x39, 0x04, 0x4e, 0xe4, 0xb2, 0xc1, 0x89, 0x3c, 0xd1, 0x49, 0x37, 0xcc, 0xba, 0x94, + 0xcb, 0x81, 0x5c, 0x4f, 0x70, 0x20, 0x4f, 0x76, 0x27, 0xd5, 0x99, 0xf3, 0xf8, 0x6f, 0x2c, 0x98, + 0x32, 0xf0, 0x0f, 0xe1, 0x02, 0x5c, 0x36, 0x2f, 0xc0, 0x47, 0xbb, 0x7e, 0x43, 0xce, 0xc5, 0xf7, + 0xa3, 0xc5, 0x44, 0xdf, 0xd9, 0x85, 0xf7, 0x2e, 0xf4, 0x6d, 0x3a, 0x41, 0x5d, 0xbc, 0xeb, 0x2f, + 0xf4, 0x34, 0xd6, 0xb3, 0x57, 0x9c, 0x40, 0x18, 0x83, 0x3c, 0x2b, 0x47, 0x9d, 0x16, 0x75, 0x35, + 0x04, 0x61, 0x4d, 0xa1, 0x4b, 0x30, 0x10, 0xd6, 0xfc, 0x96, 0xf2, 0x29, 0x64, 0x49, 0xd4, 0xab, + 0xac, 0x64, 0x7f, 0xb7, 0x84, 0xcc, 0xe6, 0x68, 0x31, 0x16, 0xf8, 0xe8, 0x6d, 0x18, 0x63, 0xbf, + 0x94, 0x65, 0x66, 0x31, 0x5f, 0x02, 0x53, 0xd5, 0x11, 0xb9, 0xd9, 0xb2, 0x51, 0x84, 0x4d, 0x52, + 0x33, 0x1b, 0x30, 0xac, 0x3e, 0xeb, 0xa1, 0x6a, 0xfe, 0xff, 0xba, 0x08, 0x47, 0x32, 0xd6, 0x1c, + 0x0a, 0x8d, 0x99, 0xb8, 0xd8, 0xe3, 0x52, 0x7d, 0x8f, 0x73, 0x11, 0xb2, 0x07, 0x60, 0x5d, 0xac, + 0xad, 0x9e, 0x1b, 0xbd, 0x11, 0x92, 0x64, 0xa3, 0xb4, 0xa8, 0x7b, 0xa3, 0xb4, 0xb1, 0x43, 0x1b, + 0x6a, 0xda, 0x90, 0xea, 0xe9, 0x43, 0x9d, 0xd3, 0x3f, 0xe9, 0x83, 0xa3, 0x59, 0xe6, 0x2a, 0xe8, + 0x73, 0x30, 0xc0, 0x9c, 0xde, 0xa4, 0xe0, 0xec, 0xc5, 0x5e, 0x0d, 0x5d, 0x66, 0x99, 0xdf, 0x9c, + 0x08, 0x99, 0x3b, 0x2b, 0x8f, 0x23, 0x5e, 0xd8, 0x75, 0x98, 0x45, 0x9b, 0x2c, 0x94, 0x95, 0xb8, + 0x3d, 0xe5, 0xf1, 0xf1, 0x91, 0x9e, 0x3b, 0x20, 0xee, 0xdf, 0x30, 0x61, 0xf5, 0x25, 0x8b, 0xbb, + 0x5b, 0x7d, 0xc9, 0x96, 0x51, 0x19, 0x06, 0x6a, 0xdc, 0x9c, 0xa8, 0xd8, 0xfd, 0x08, 0xe3, 0xb6, + 0x44, 0xea, 0x00, 0x16, 0x36, 0x44, 0x82, 0xc0, 0x8c, 0x0b, 0x23, 0xda, 0xc0, 0x3c, 0xd4, 0xc5, + 0xb3, 0x45, 0x2f, 0x3e, 0x6d, 0x08, 0x1e, 0xea, 0x02, 0xfa, 0x59, 0xed, 0xee, 0x17, 0xe7, 0xc1, + 0x87, 0x0d, 0xde, 0xe9, 0x54, 0xc2, 0x15, 0x31, 0xb1, 0xaf, 0x18, 0x2f, 0x55, 0x35, 0x63, 0xcd, + 0xe7, 0x26, 0xcc, 0x32, 0x2f, 0xfc, 0xce, 0xf1, 0xe5, 0xed, 0x9f, 0xb3, 0x20, 0xe1, 0x2c, 0xa6, + 0xc4, 0x9d, 0x56, 0xae, 0xb8, 0xf3, 0x2c, 0xf4, 0x05, 0x7e, 0x43, 0xf2, 0x53, 0x0a, 0x03, 0xfb, + 0x0d, 0x82, 0x19, 0x84, 0x62, 0x44, 0xb1, 0x10, 0x6b, 0x54, 0x7f, 0xa0, 0x8b, 0xa7, 0xf7, 0x63, + 0xd0, 0xdf, 0x20, 0xdb, 0xa4, 0x91, 0xcc, 0x1b, 0x7b, 0x8d, 0x16, 0x62, 0x0e, 0xb3, 0x7f, 0xaf, + 0x0f, 0x4e, 0x77, 0x8c, 0x78, 0x47, 0x19, 0xcc, 0x0d, 0x27, 0x22, 0x77, 0x9c, 0x9d, 0x64, 0xbe, + 0xc4, 0xcb, 0xbc, 0x18, 0x4b, 0x38, 0x73, 0xdc, 0xe6, 0x39, 0x80, 0x12, 0xc2, 0x61, 0x91, 0xfa, + 0x47, 0x40, 0x4d, 0x61, 0x63, 0xf1, 0x41, 0x08, 0x1b, 0x9f, 0x07, 0x08, 0xc3, 0x06, 0xb7, 0x09, + 0xad, 0x0b, 0x8f, 0xf0, 0x38, 0x57, 0x54, 0xf5, 0x9a, 0x80, 0x60, 0x0d, 0x0b, 0x2d, 0xc2, 0x64, + 0x2b, 0xf0, 0x23, 0x2e, 0x6b, 0x5f, 0xe4, 0x66, 0xd3, 0xfd, 0x66, 0xb0, 0xb1, 0x4a, 0x02, 0x8e, + 0x53, 0x35, 0xd0, 0x4b, 0x30, 0x22, 0x02, 0x90, 0x55, 0x7c, 0xbf, 0x21, 0xc4, 0x7b, 0xca, 0x92, + 0xb8, 0x1a, 0x83, 0xb0, 0x8e, 0xa7, 0x55, 0x63, 0x02, 0xfc, 0xc1, 0xcc, 0x6a, 0x5c, 0x88, 0xaf, + 0xe1, 0x25, 0x92, 0x15, 0x0c, 0xf5, 0x94, 0xac, 0x20, 0x16, 0x78, 0x0e, 0xf7, 0xac, 0x4f, 0x86, + 0xae, 0x22, 0xc2, 0xaf, 0xf5, 0xc1, 0x11, 0xb1, 0x70, 0x1e, 0xf6, 0x72, 0xb9, 0x91, 0x5e, 0x2e, + 0x0f, 0x42, 0x24, 0xfa, 0xc1, 0x9a, 0x39, 0xec, 0x35, 0xf3, 0x93, 0x16, 0x98, 0x3c, 0x24, 0xfa, + 0x8f, 0x72, 0x13, 0xce, 0xbe, 0x94, 0xcb, 0x93, 0xc6, 0x91, 0xcc, 0xdf, 0x5b, 0xea, 0x59, 0xfb, + 0x7f, 0xb1, 0xe0, 0xd1, 0xae, 0x14, 0xd1, 0x12, 0x0c, 0x33, 0x46, 0x57, 0x7b, 0x17, 0x3f, 0xa9, + 0xdc, 0x2a, 0x24, 0x20, 0x87, 0xef, 0x8e, 0x6b, 0xa2, 0xa5, 0x54, 0x66, 0xdf, 0xa7, 0x32, 0x32, + 0xfb, 0x1e, 0x33, 0x86, 0xe7, 0x3e, 0x53, 0xfb, 0x7e, 0x89, 0xde, 0x38, 0xa6, 0x6f, 0xe6, 0x47, + 0x0c, 0x71, 0xae, 0x9d, 0x10, 0xe7, 0x22, 0x13, 0x5b, 0xbb, 0x43, 0xde, 0x80, 0x49, 0x16, 0x99, + 0x94, 0x39, 0xf9, 0x08, 0xa7, 0xce, 0x42, 0x6c, 0xc8, 0x7f, 0x2d, 0x01, 0xc3, 0x29, 0x6c, 0xfb, + 0x1f, 0x8a, 0x30, 0xc0, 0xb7, 0xdf, 0x21, 0x3c, 0x7c, 0x9f, 0x81, 0x61, 0xb7, 0xd9, 0x6c, 0xf3, + 0x64, 0xad, 0xfd, 0xb1, 0x59, 0x78, 0x59, 0x16, 0xe2, 0x18, 0x8e, 0x96, 0x85, 0x26, 0xa1, 0x43, + 0xf0, 0x73, 0xde, 0xf1, 0xd9, 0x45, 0x27, 0x72, 0x38, 0x17, 0xa7, 0xee, 0xd9, 0x58, 0xe7, 0x80, + 0x3e, 0x0d, 0x10, 0x46, 0x81, 0xeb, 0x6d, 0xd0, 0x32, 0x91, 0x21, 0xe3, 0xe9, 0x0e, 0xd4, 0xaa, + 0x0a, 0x99, 0xd3, 0x8c, 0xcf, 0x1c, 0x05, 0xc0, 0x1a, 0x45, 0x34, 0x6b, 0xdc, 0xf4, 0x33, 0x89, + 0xb9, 0x03, 0x4e, 0x35, 0x9e, 0xb3, 0x99, 0x97, 0x61, 0x58, 0x11, 0xef, 0x26, 0x57, 0x1c, 0xd5, + 0x19, 0xb6, 0x8f, 0xc1, 0x44, 0xa2, 0x6f, 0x07, 0x12, 0x4b, 0xfe, 0xbe, 0x05, 0x13, 0xbc, 0x33, + 0x4b, 0xde, 0xb6, 0xb8, 0x0d, 0xee, 0xc1, 0xd1, 0x46, 0xc6, 0xa9, 0x2c, 0xa6, 0xbf, 0xf7, 0x53, + 0x5c, 0x89, 0x21, 0xb3, 0xa0, 0x38, 0xb3, 0x0d, 0x74, 0x9e, 0xee, 0x38, 0x7a, 0xea, 0x3a, 0x0d, + 0x11, 0x99, 0x64, 0x94, 0xef, 0x36, 0x5e, 0x86, 0x15, 0xd4, 0xfe, 0x5b, 0x0b, 0xa6, 0x78, 0xcf, + 0xaf, 0x92, 0x1d, 0x75, 0x36, 0x7d, 0x27, 0xfb, 0x2e, 0xd2, 0x84, 0x17, 0x72, 0xd2, 0x84, 0xeb, + 0x9f, 0x56, 0xec, 0xf8, 0x69, 0x5f, 0xb5, 0x40, 0xac, 0x90, 0x43, 0x90, 0xb4, 0x7c, 0x9f, 0x29, + 0x69, 0x99, 0xc9, 0xdf, 0x04, 0x39, 0x22, 0x96, 0x7f, 0xb3, 0x60, 0x92, 0x23, 0xc4, 0x56, 0x10, + 0xdf, 0xd1, 0x79, 0x98, 0x37, 0xbf, 0x28, 0xd3, 0xac, 0xf5, 0x2a, 0xd9, 0x59, 0xf3, 0x2b, 0x4e, + 0xb4, 0x99, 0xfd, 0x51, 0xc6, 0x64, 0xf5, 0x75, 0x9c, 0xac, 0xba, 0xdc, 0x40, 0x46, 0x42, 0xc8, + 0x2e, 0x02, 0xe0, 0x83, 0x26, 0x84, 0xb4, 0xff, 0xd1, 0x02, 0xc4, 0x9b, 0x31, 0x18, 0x37, 0xca, + 0x0e, 0xb1, 0x52, 0xed, 0xa2, 0x8b, 0x8f, 0x26, 0x05, 0xc1, 0x1a, 0xd6, 0x03, 0x19, 0x9e, 0x84, + 0x29, 0x4b, 0xb1, 0xbb, 0x29, 0xcb, 0x01, 0x46, 0xf4, 0xab, 0x83, 0x90, 0x74, 0xeb, 0x44, 0x37, + 0x61, 0xb4, 0xe6, 0xb4, 0x9c, 0xdb, 0x6e, 0xc3, 0x8d, 0x5c, 0x12, 0x76, 0xb2, 0x73, 0x5b, 0xd0, + 0xf0, 0x84, 0xf1, 0x81, 0x56, 0x82, 0x0d, 0x3a, 0x68, 0x16, 0xa0, 0x15, 0xb8, 0xdb, 0x6e, 0x83, + 0x6c, 0x30, 0x81, 0x10, 0x8b, 0x85, 0xc4, 0x8d, 0xee, 0x64, 0x29, 0xd6, 0x30, 0x32, 0x42, 0x90, + 0x14, 0x1f, 0x72, 0x08, 0x12, 0x38, 0xb4, 0x10, 0x24, 0x7d, 0x07, 0x0a, 0x41, 0x32, 0x74, 0xe0, + 0x10, 0x24, 0xfd, 0x3d, 0x85, 0x20, 0xc1, 0x70, 0x5c, 0xf2, 0x9e, 0xf4, 0xff, 0xb2, 0xdb, 0x20, + 0xe2, 0xc1, 0xc1, 0x03, 0x38, 0xcd, 0xec, 0xed, 0x96, 0x8e, 0xe3, 0x4c, 0x0c, 0x9c, 0x53, 0x13, + 0x7d, 0x1c, 0xa6, 0x9d, 0x46, 0xc3, 0xbf, 0xa3, 0x26, 0x75, 0x29, 0xac, 0x39, 0x8d, 0x38, 0xae, + 0xdf, 0xd0, 0xfc, 0xa9, 0xbd, 0xdd, 0xd2, 0xf4, 0x5c, 0x0e, 0x0e, 0xce, 0xad, 0x8d, 0x5e, 0x83, + 0xe1, 0x56, 0xe0, 0xd7, 0x56, 0x34, 0xdf, 0xf3, 0x33, 0x74, 0x00, 0x2b, 0xb2, 0x70, 0x7f, 0xb7, + 0x34, 0xa6, 0xfe, 0xb0, 0x0b, 0x3f, 0xae, 0x90, 0x11, 0xdd, 0x63, 0xe4, 0x61, 0x47, 0xf7, 0x18, + 0x7d, 0xc0, 0xd1, 0x3d, 0xec, 0x2d, 0x38, 0x52, 0x25, 0x81, 0xeb, 0x34, 0xdc, 0x7b, 0x94, 0x27, + 0x97, 0x67, 0xe0, 0x1a, 0x0c, 0x07, 0x89, 0x53, 0xbf, 0xa7, 0xa0, 0xe7, 0x9a, 0x5c, 0x46, 0x9e, + 0xf2, 0x31, 0x21, 0xfb, 0xff, 0xb7, 0x60, 0x50, 0xb8, 0x8a, 0x1e, 0x02, 0x67, 0x3a, 0x67, 0xa8, + 0x64, 0x4a, 0xd9, 0x93, 0xc2, 0x3a, 0x93, 0xab, 0x8c, 0x29, 0x27, 0x94, 0x31, 0x8f, 0x76, 0x22, + 0xd2, 0x59, 0x0d, 0xf3, 0x9f, 0x15, 0xe9, 0x0b, 0xc1, 0x08, 0x5a, 0xf0, 0xf0, 0x87, 0x60, 0x15, + 0x06, 0x43, 0xe1, 0x34, 0x5f, 0xc8, 0xf7, 0xe5, 0x49, 0x4e, 0x62, 0x6c, 0x03, 0x29, 0xdc, 0xe4, + 0x25, 0x91, 0x4c, 0x6f, 0xfc, 0xe2, 0x43, 0xf4, 0xc6, 0xef, 0x16, 0xd6, 0xa1, 0xef, 0x41, 0x84, + 0x75, 0xb0, 0xbf, 0xce, 0x6e, 0x67, 0xbd, 0xfc, 0x10, 0x18, 0xb7, 0xcb, 0xe6, 0x3d, 0x6e, 0x77, + 0x58, 0x59, 0xa2, 0x53, 0x39, 0x0c, 0xdc, 0xef, 0x5a, 0x70, 0x3a, 0xe3, 0xab, 0x34, 0x6e, 0xee, + 0x59, 0x18, 0x72, 0xda, 0x75, 0x57, 0xed, 0x65, 0x4d, 0x5b, 0x3c, 0x27, 0xca, 0xb1, 0xc2, 0x40, + 0x0b, 0x30, 0x45, 0xee, 0xb6, 0x5c, 0xae, 0x86, 0xd7, 0x4d, 0xc7, 0x8b, 0xdc, 0xbf, 0x78, 0x29, + 0x09, 0xc4, 0x69, 0x7c, 0x15, 0x1a, 0xae, 0x98, 0x1b, 0x1a, 0xee, 0x37, 0x2d, 0x18, 0x51, 0x6e, + 0xe3, 0x0f, 0x7d, 0xb4, 0xdf, 0x30, 0x47, 0xfb, 0x91, 0x0e, 0xa3, 0x9d, 0x33, 0xcc, 0x7f, 0x53, + 0x50, 0xfd, 0xad, 0xf8, 0x41, 0xd4, 0x03, 0x97, 0x78, 0xff, 0x6e, 0x2f, 0x17, 0x61, 0xc4, 0x69, + 0xb5, 0x24, 0x40, 0xda, 0x2f, 0xb2, 0x14, 0x16, 0x71, 0x31, 0xd6, 0x71, 0x94, 0x17, 0x4e, 0x31, + 0xd7, 0x0b, 0xa7, 0x0e, 0x10, 0x39, 0xc1, 0x06, 0x89, 0x68, 0x99, 0x30, 0xb7, 0xce, 0x3f, 0x6f, + 0xda, 0x91, 0xdb, 0x98, 0x75, 0xbd, 0x28, 0x8c, 0x82, 0xd9, 0xb2, 0x17, 0x5d, 0x0f, 0xf8, 0x33, + 0x55, 0x0b, 0xc0, 0xa8, 0x68, 0x61, 0x8d, 0xae, 0x0c, 0x91, 0xc2, 0xda, 0xe8, 0x37, 0x0d, 0x61, + 0x56, 0x45, 0x39, 0x56, 0x18, 0xf6, 0xcb, 0xec, 0xf6, 0x61, 0x63, 0x7a, 0xb0, 0xc0, 0x82, 0xff, + 0x34, 0xaa, 0x66, 0x83, 0xa9, 0x84, 0x17, 0xf5, 0xf0, 0x85, 0x9d, 0x0f, 0x7b, 0xda, 0xb0, 0xee, + 0xcf, 0x1a, 0xc7, 0x38, 0x44, 0x9f, 0x4c, 0x19, 0x37, 0x3d, 0xd7, 0xe5, 0xd6, 0x38, 0x80, 0x39, + 0x13, 0xcb, 0x67, 0xc7, 0xb2, 0x7d, 0x95, 0x2b, 0x62, 0x5f, 0x68, 0xf9, 0xec, 0x04, 0x00, 0xc7, + 0x38, 0x94, 0x61, 0x53, 0x7f, 0xc2, 0x69, 0x14, 0x87, 0x3d, 0x57, 0xd8, 0x21, 0xd6, 0x30, 0xd0, + 0x05, 0x21, 0xb4, 0xe0, 0xba, 0x87, 0x47, 0x12, 0x42, 0x0b, 0x39, 0x5c, 0x9a, 0xa4, 0xe9, 0x22, + 0x8c, 0x90, 0xbb, 0x11, 0x09, 0x3c, 0xa7, 0x41, 0x5b, 0xe8, 0x8f, 0xa3, 0xeb, 0x2e, 0xc5, 0xc5, + 0x58, 0xc7, 0x41, 0x6b, 0x30, 0x11, 0x72, 0x59, 0x9e, 0x4a, 0xb6, 0xc1, 0x65, 0xa2, 0x4f, 0x2b, + 0x87, 0x7d, 0x13, 0xbc, 0xcf, 0x8a, 0xf8, 0xe9, 0x24, 0xc3, 0x98, 0x24, 0x49, 0xa0, 0xd7, 0x61, + 0xbc, 0xe1, 0x3b, 0xf5, 0x79, 0xa7, 0xe1, 0x78, 0x35, 0x36, 0x3e, 0x43, 0x46, 0x2c, 0xcb, 0xf1, + 0x6b, 0x06, 0x14, 0x27, 0xb0, 0x29, 0x83, 0xa8, 0x97, 0x88, 0x04, 0x31, 0x8e, 0xb7, 0x41, 0xc2, + 0xe9, 0x61, 0xf6, 0x55, 0x8c, 0x41, 0xbc, 0x96, 0x83, 0x83, 0x73, 0x6b, 0xa3, 0x4b, 0x30, 0x2a, + 0x3f, 0x5f, 0x8b, 0xfa, 0x13, 0x3b, 0x34, 0x69, 0x30, 0x6c, 0x60, 0xa2, 0x10, 0x8e, 0xc9, 0xff, + 0x6b, 0x81, 0xb3, 0xbe, 0xee, 0xd6, 0x44, 0x28, 0x0c, 0xee, 0xfc, 0xfd, 0x31, 0xe9, 0x69, 0xba, + 0x94, 0x85, 0xb4, 0xbf, 0x5b, 0x3a, 0x25, 0x46, 0x2d, 0x13, 0x8e, 0xb3, 0x69, 0xa3, 0x15, 0x38, + 0xc2, 0x6d, 0x60, 0x16, 0x36, 0x49, 0x6d, 0x4b, 0x6e, 0x38, 0xc6, 0x35, 0x6a, 0x8e, 0x3f, 0x57, + 0xd2, 0x28, 0x38, 0xab, 0x1e, 0x7a, 0x07, 0xa6, 0x5b, 0xed, 0xdb, 0x0d, 0x37, 0xdc, 0x5c, 0xf5, + 0x23, 0x66, 0x42, 0x36, 0x57, 0xaf, 0x07, 0x24, 0xe4, 0xbe, 0xc1, 0xec, 0xea, 0x95, 0x91, 0x9a, + 0x2a, 0x39, 0x78, 0x38, 0x97, 0x02, 0xba, 0x07, 0xc7, 0x12, 0x0b, 0x41, 0x84, 0x5c, 0x19, 0xcf, + 0x4f, 0xb5, 0x55, 0xcd, 0xaa, 0x20, 0xa2, 0x17, 0x65, 0x81, 0x70, 0x76, 0x13, 0xe8, 0x15, 0x00, + 0xb7, 0xb5, 0xec, 0x34, 0xdd, 0x06, 0x7d, 0x8e, 0x1e, 0x61, 0x6b, 0x84, 0x3e, 0x4d, 0xa0, 0x5c, + 0x91, 0xa5, 0xf4, 0x6c, 0x16, 0xff, 0x76, 0xb0, 0x86, 0x8d, 0xae, 0xc1, 0xb8, 0xf8, 0xb7, 0x23, + 0xa6, 0x74, 0x4a, 0x65, 0x65, 0x1d, 0x97, 0x35, 0xd4, 0x3c, 0x26, 0x4a, 0x70, 0xa2, 0x2e, 0xda, + 0x80, 0xd3, 0x32, 0x25, 0xac, 0xbe, 0x3e, 0xe5, 0x1c, 0x84, 0x2c, 0xbf, 0xd5, 0x10, 0xf7, 0x29, + 0x9a, 0xeb, 0x84, 0x88, 0x3b, 0xd3, 0xa1, 0xf7, 0xba, 0xbe, 0xcc, 0xb9, 0xc7, 0xf8, 0xb1, 0x38, + 0x22, 0xe8, 0xb5, 0x24, 0x10, 0xa7, 0xf1, 0x91, 0x0f, 0xc7, 0x5c, 0x2f, 0x6b, 0x55, 0x1f, 0x67, + 0x84, 0x3e, 0xca, 0x9d, 0xe5, 0x3b, 0xaf, 0xe8, 0x4c, 0x38, 0xce, 0xa6, 0x8b, 0xca, 0x70, 0x24, + 0xe2, 0x05, 0x8b, 0x6e, 0xc8, 0xd3, 0xe7, 0xd0, 0x67, 0xdf, 0x09, 0xd6, 0xdc, 0x09, 0xba, 0x9a, + 0xd7, 0xd2, 0x60, 0x9c, 0x55, 0xe7, 0xbd, 0x19, 0x80, 0x7e, 0xc3, 0xa2, 0xb5, 0x35, 0x46, 0x1f, + 0x7d, 0x06, 0x46, 0xf5, 0xf1, 0x11, 0x4c, 0xcb, 0xb9, 0x6c, 0x3e, 0x58, 0x3b, 0x5e, 0xf8, 0x33, + 0x41, 0x1d, 0x21, 0x3a, 0x0c, 0x1b, 0x14, 0x51, 0x2d, 0x23, 0xc8, 0xc5, 0x85, 0xde, 0x98, 0xa2, + 0xde, 0xed, 0x1f, 0x09, 0x64, 0xef, 0x1c, 0x74, 0x0d, 0x86, 0x6a, 0x0d, 0x97, 0x78, 0x51, 0xb9, + 0xd2, 0x29, 0x50, 0xeb, 0x82, 0xc0, 0x11, 0x5b, 0x51, 0x64, 0xbd, 0xe2, 0x65, 0x58, 0x51, 0xb0, + 0x2f, 0xc1, 0x48, 0xb5, 0x41, 0x48, 0x8b, 0xfb, 0x71, 0xa1, 0xa7, 0xd8, 0xc3, 0x84, 0xb1, 0x96, + 0x16, 0x63, 0x2d, 0xf5, 0x37, 0x07, 0x63, 0x2a, 0x25, 0xdc, 0xfe, 0xb3, 0x02, 0x94, 0xba, 0x24, + 0x5f, 0x4b, 0xe8, 0xdb, 0xac, 0x9e, 0xf4, 0x6d, 0x73, 0x30, 0x11, 0xff, 0xd3, 0x45, 0x79, 0xca, + 0x18, 0xfa, 0xa6, 0x09, 0xc6, 0x49, 0xfc, 0x9e, 0xfd, 0x5a, 0x74, 0x95, 0x5d, 0x5f, 0x57, 0xcf, + 0x2c, 0x43, 0x55, 0xdf, 0xdf, 0xfb, 0xdb, 0x3b, 0x57, 0xed, 0x6a, 0x7f, 0xbd, 0x00, 0xc7, 0xd4, + 0x10, 0x7e, 0xef, 0x0e, 0xdc, 0x8d, 0xf4, 0xc0, 0x3d, 0x00, 0xa5, 0xb5, 0x7d, 0x1d, 0x06, 0x78, + 0xf4, 0xd8, 0x1e, 0x78, 0xfe, 0xc7, 0xcc, 0x40, 0xfe, 0x8a, 0xcd, 0x34, 0x82, 0xf9, 0xff, 0x98, + 0x05, 0x13, 0x09, 0x07, 0x49, 0x84, 0x35, 0x2f, 0xfa, 0xfb, 0xe1, 0xcb, 0xb3, 0x38, 0xfe, 0xb3, + 0xd0, 0xb7, 0xe9, 0x2b, 0x23, 0x65, 0x85, 0x71, 0xc5, 0x0f, 0x23, 0xcc, 0x20, 0xf6, 0xdf, 0x59, + 0xd0, 0xbf, 0xe6, 0xb8, 0x5e, 0x24, 0xb5, 0x1f, 0x56, 0x8e, 0xf6, 0xa3, 0x97, 0xef, 0x42, 0x2f, + 0xc1, 0x00, 0x59, 0x5f, 0x27, 0xb5, 0x48, 0xcc, 0xaa, 0x8c, 0xa6, 0x31, 0xb0, 0xc4, 0x4a, 0x29, + 0x13, 0xca, 0x1a, 0xe3, 0x7f, 0xb1, 0x40, 0x46, 0xb7, 0x60, 0x38, 0x72, 0x9b, 0x64, 0xae, 0x5e, + 0x17, 0x36, 0x01, 0xf7, 0x11, 0x02, 0x66, 0x4d, 0x12, 0xc0, 0x31, 0x2d, 0xfb, 0xcb, 0x05, 0x80, + 0x38, 0x5a, 0x5d, 0xb7, 0x4f, 0x9c, 0x4f, 0x69, 0x8b, 0xcf, 0x65, 0x68, 0x8b, 0x51, 0x4c, 0x30, + 0x43, 0x55, 0xac, 0x86, 0xa9, 0xd8, 0xd3, 0x30, 0xf5, 0x1d, 0x64, 0x98, 0x16, 0x60, 0x2a, 0x8e, + 0xb6, 0x67, 0x06, 0x1b, 0x65, 0xf7, 0xf7, 0x5a, 0x12, 0x88, 0xd3, 0xf8, 0x36, 0x81, 0xb3, 0x2a, + 0xe8, 0x98, 0xb8, 0x0b, 0x99, 0x2b, 0x81, 0xae, 0x7d, 0xef, 0x32, 0x4e, 0xb1, 0x3a, 0xbc, 0x90, + 0xab, 0x0e, 0xff, 0x45, 0x0b, 0x8e, 0x26, 0xdb, 0x61, 0x7e, 0xf7, 0x5f, 0xb4, 0xe0, 0x58, 0x9c, + 0x7b, 0x28, 0x6d, 0x82, 0xf0, 0x62, 0xc7, 0x40, 0x6a, 0x39, 0x3d, 0x8e, 0xc3, 0xb6, 0xac, 0x64, + 0x91, 0xc6, 0xd9, 0x2d, 0xda, 0xff, 0x5f, 0x1f, 0x4c, 0xe7, 0x45, 0x60, 0x63, 0x9e, 0x46, 0xce, + 0xdd, 0xea, 0x16, 0xb9, 0x23, 0xfc, 0x39, 0x62, 0x4f, 0x23, 0x5e, 0x8c, 0x25, 0x3c, 0x99, 0x6e, + 0xaa, 0xd0, 0x63, 0xba, 0xa9, 0x4d, 0x98, 0xba, 0xb3, 0x49, 0xbc, 0x1b, 0x5e, 0xe8, 0x44, 0x6e, + 0xb8, 0xee, 0x32, 0x05, 0x3a, 0x5f, 0x37, 0xaf, 0x48, 0xaf, 0x8b, 0x5b, 0x49, 0x84, 0xfd, 0xdd, + 0xd2, 0x69, 0xa3, 0x20, 0xee, 0x32, 0x3f, 0x48, 0x70, 0x9a, 0x68, 0x3a, 0x5b, 0x57, 0xdf, 0x43, + 0xce, 0xd6, 0xd5, 0x74, 0x85, 0xd9, 0x8d, 0x74, 0x23, 0x61, 0xcf, 0xd6, 0x15, 0x55, 0x8a, 0x35, + 0x0c, 0xf4, 0x29, 0x40, 0x7a, 0xba, 0x45, 0x23, 0x00, 0xee, 0x73, 0x7b, 0xbb, 0x25, 0xb4, 0x9a, + 0x82, 0xee, 0xef, 0x96, 0x8e, 0xd0, 0xd2, 0xb2, 0x47, 0x9f, 0xbf, 0x71, 0xd4, 0xc0, 0x0c, 0x42, + 0xe8, 0x16, 0x4c, 0xd2, 0x52, 0xb6, 0xa3, 0x64, 0x74, 0x5d, 0xfe, 0x64, 0x7d, 0x66, 0x6f, 0xb7, + 0x34, 0xb9, 0x9a, 0x80, 0xe5, 0x91, 0x4e, 0x11, 0xc9, 0x48, 0xda, 0x35, 0xd4, 0x6b, 0xd2, 0x2e, + 0xfb, 0x8b, 0x16, 0x9c, 0xa4, 0x17, 0x5c, 0xfd, 0x5a, 0x8e, 0x16, 0xdd, 0x69, 0xb9, 0x5c, 0x4f, + 0x23, 0xae, 0x1a, 0x26, 0xab, 0xab, 0x94, 0xb9, 0x96, 0x46, 0x41, 0xe9, 0x09, 0xbf, 0xe5, 0x7a, + 0xf5, 0xe4, 0x09, 0x7f, 0xd5, 0xf5, 0xea, 0x98, 0x41, 0xd4, 0x95, 0x55, 0xcc, 0x8d, 0xd6, 0xff, + 0x35, 0xba, 0x57, 0x69, 0x5f, 0xbe, 0xa3, 0xdd, 0x40, 0xcf, 0xe8, 0x3a, 0x55, 0x61, 0x3e, 0x99, + 0xab, 0x4f, 0xfd, 0x82, 0x05, 0xc2, 0xfb, 0xbd, 0x87, 0x3b, 0xf9, 0x6d, 0x18, 0xdd, 0x4e, 0xa7, + 0xa2, 0x3d, 0x9b, 0x1f, 0x0e, 0x40, 0x24, 0xa0, 0x55, 0x2c, 0xba, 0x91, 0x76, 0xd6, 0xa0, 0x65, + 0xd7, 0x41, 0x40, 0x17, 0x09, 0xd3, 0x6a, 0x74, 0xef, 0xcd, 0xf3, 0x00, 0x75, 0x86, 0xcb, 0xf2, + 0xd3, 0x17, 0x4c, 0x8e, 0x6b, 0x51, 0x41, 0xb0, 0x86, 0x65, 0xff, 0x7a, 0x11, 0x46, 0x64, 0xea, + 0xd3, 0xb6, 0xd7, 0x8b, 0xec, 0x51, 0x67, 0x9c, 0x0a, 0x5d, 0x19, 0xa7, 0x77, 0x60, 0x2a, 0x20, + 0xb5, 0x76, 0x10, 0xba, 0xdb, 0x44, 0x82, 0xc5, 0x26, 0x99, 0xe5, 0xc9, 0x22, 0x12, 0xc0, 0x7d, + 0x16, 0x22, 0x2b, 0x51, 0xc8, 0x94, 0xc6, 0x69, 0x42, 0xe8, 0x02, 0x0c, 0x33, 0xd1, 0x7b, 0x25, + 0x16, 0x08, 0x2b, 0xc1, 0xd7, 0x8a, 0x04, 0xe0, 0x18, 0x87, 0x3d, 0x0e, 0xda, 0xb7, 0x19, 0x7a, + 0xc2, 0x13, 0xbc, 0xca, 0x8b, 0xb1, 0x84, 0xa3, 0x8f, 0xc3, 0x24, 0xaf, 0x17, 0xf8, 0x2d, 0x67, + 0x83, 0xab, 0x04, 0xfb, 0x55, 0x78, 0x9d, 0xc9, 0x95, 0x04, 0x6c, 0x7f, 0xb7, 0x74, 0x34, 0x59, + 0xc6, 0xba, 0x9d, 0xa2, 0xc2, 0x2c, 0xff, 0x78, 0x23, 0xf4, 0xce, 0x48, 0x19, 0x0c, 0xc6, 0x20, + 0xac, 0xe3, 0xd9, 0xff, 0x6a, 0xc1, 0x94, 0x36, 0x55, 0x3d, 0xe7, 0xeb, 0x30, 0x06, 0xa9, 0xd0, + 0xc3, 0x20, 0x1d, 0x2c, 0xda, 0x43, 0xe6, 0x0c, 0xf7, 0x3d, 0xa0, 0x19, 0xb6, 0x3f, 0x03, 0x28, + 0x9d, 0x57, 0x17, 0xbd, 0xc9, 0x0d, 0xf9, 0xdd, 0x80, 0xd4, 0x3b, 0x29, 0xfc, 0xf5, 0xc8, 0x39, + 0xd2, 0x73, 0x95, 0xd7, 0xc2, 0xaa, 0xbe, 0xfd, 0xe3, 0x7d, 0x30, 0x99, 0x8c, 0xd5, 0x81, 0xae, + 0xc0, 0x00, 0xe7, 0xd2, 0x05, 0xf9, 0x0e, 0xf6, 0x64, 0x5a, 0x84, 0x0f, 0x9e, 0x4b, 0x87, 0x73, + 0xf7, 0xa2, 0x3e, 0x7a, 0x07, 0x46, 0xea, 0xfe, 0x1d, 0xef, 0x8e, 0x13, 0xd4, 0xe7, 0x2a, 0x65, + 0x71, 0x42, 0x64, 0x0a, 0xa0, 0x16, 0x63, 0x34, 0x3d, 0x6a, 0x08, 0xb3, 0x9d, 0x88, 0x41, 0x58, + 0x27, 0x87, 0xd6, 0x58, 0x7a, 0xa7, 0x75, 0x77, 0x63, 0xc5, 0x69, 0x75, 0xf2, 0xea, 0x5a, 0x90, + 0x48, 0x1a, 0xe5, 0x31, 0x91, 0x03, 0x8a, 0x03, 0x70, 0x4c, 0x08, 0x7d, 0x0e, 0x8e, 0x84, 0x39, + 0x2a, 0xb1, 0xbc, 0x34, 0xeb, 0x9d, 0xb4, 0x44, 0x5c, 0x98, 0x92, 0xa5, 0x3c, 0xcb, 0x6a, 0x06, + 0xdd, 0x05, 0x24, 0x44, 0xcf, 0x6b, 0x41, 0x3b, 0x8c, 0xe6, 0xdb, 0x5e, 0xbd, 0x21, 0xd3, 0x3f, + 0x7d, 0x38, 0x5b, 0x4e, 0x90, 0xc4, 0xd6, 0xda, 0x66, 0xe1, 0x85, 0xd3, 0x18, 0x38, 0xa3, 0x0d, + 0xfb, 0x0b, 0x7d, 0x30, 0x23, 0x13, 0x59, 0x67, 0x78, 0xaf, 0x7c, 0xde, 0x4a, 0xb8, 0xaf, 0xbc, + 0x92, 0x7f, 0xd0, 0x3f, 0x34, 0x27, 0x96, 0x2f, 0xa5, 0x9d, 0x58, 0x5e, 0x3b, 0x60, 0x37, 0x1e, + 0x98, 0x2b, 0xcb, 0xf7, 0xac, 0xff, 0xc9, 0xde, 0x51, 0x30, 0xae, 0x66, 0x84, 0x79, 0xec, 0xf6, + 0x8a, 0x54, 0x1d, 0xe5, 0x3c, 0xff, 0xaf, 0x08, 0x1c, 0xe3, 0xb2, 0x1f, 0x95, 0x11, 0xde, 0xd9, + 0x39, 0xab, 0xe8, 0x50, 0x9a, 0xa4, 0xd9, 0x8a, 0x76, 0x16, 0xdd, 0x40, 0xf4, 0x38, 0x93, 0xe6, + 0x92, 0xc0, 0x49, 0xd3, 0x94, 0x10, 0xac, 0xe8, 0xa0, 0x6d, 0x98, 0xda, 0x60, 0x11, 0x9f, 0xb4, + 0x9c, 0xd2, 0xe2, 0x5c, 0xc8, 0xdc, 0xb7, 0x97, 0x17, 0x96, 0xf2, 0x13, 0x50, 0xf3, 0xc7, 0x5f, + 0x0a, 0x05, 0xa7, 0x9b, 0xa0, 0x5b, 0xe3, 0xa8, 0x73, 0x27, 0x5c, 0x6a, 0x38, 0x61, 0xe4, 0xd6, + 0xe6, 0x1b, 0x7e, 0x6d, 0xab, 0x1a, 0xf9, 0x81, 0x4c, 0x16, 0x99, 0xf9, 0xf6, 0x9a, 0xbb, 0x55, + 0x4d, 0xe1, 0x1b, 0xcd, 0x4f, 0xef, 0xed, 0x96, 0x8e, 0x66, 0x61, 0xe1, 0xcc, 0xb6, 0xd0, 0x2a, + 0x0c, 0x6e, 0xb8, 0x11, 0x26, 0x2d, 0x5f, 0x9c, 0x16, 0x99, 0x47, 0xe1, 0x65, 0x8e, 0x62, 0xb4, + 0xc4, 0x22, 0x52, 0x09, 0x00, 0x96, 0x44, 0xd0, 0x9b, 0xea, 0x12, 0x18, 0xc8, 0x17, 0xc0, 0xa6, + 0x6d, 0xef, 0x32, 0xaf, 0x81, 0xd7, 0xa1, 0xe8, 0xad, 0x87, 0x9d, 0x62, 0xf1, 0xac, 0x2e, 0x1b, + 0xf2, 0xb3, 0xf9, 0x41, 0xfa, 0x34, 0x5e, 0x5d, 0xae, 0x62, 0x5a, 0x91, 0xb9, 0xbd, 0x86, 0xb5, + 0xd0, 0x15, 0x89, 0xa7, 0x32, 0xbd, 0x80, 0xcb, 0xd5, 0x85, 0x6a, 0xd9, 0xa0, 0xc1, 0xa2, 0x1a, + 0xb2, 0x62, 0xcc, 0xab, 0xa3, 0x9b, 0x30, 0xbc, 0xc1, 0x0f, 0xbe, 0xf5, 0x50, 0x24, 0xb3, 0xcf, + 0xbc, 0x8c, 0x2e, 0x4b, 0x24, 0x83, 0x1e, 0xbb, 0x32, 0x14, 0x08, 0xc7, 0xa4, 0xd0, 0x17, 0x2c, + 0x38, 0xd6, 0x4a, 0x48, 0x50, 0x99, 0xb3, 0x9a, 0x30, 0x53, 0xcb, 0x74, 0x00, 0xa8, 0x64, 0x55, + 0x30, 0x1a, 0x64, 0xea, 0x97, 0x4c, 0x34, 0x9c, 0xdd, 0x1c, 0x1d, 0xe8, 0xe0, 0x76, 0xbd, 0x53, + 0xae, 0xa2, 0x44, 0x60, 0x22, 0x3e, 0xd0, 0x78, 0x7e, 0x11, 0xd3, 0x8a, 0x68, 0x0d, 0x60, 0xbd, + 0x41, 0x44, 0xc4, 0x47, 0x61, 0x14, 0x95, 0x79, 0xfb, 0x2f, 0x2b, 0x2c, 0x41, 0x87, 0xbd, 0x44, + 0xe3, 0x52, 0xac, 0xd1, 0xa1, 0x4b, 0xa9, 0xe6, 0x7a, 0x75, 0x12, 0x30, 0xe5, 0x56, 0xce, 0x52, + 0x5a, 0x60, 0x18, 0xe9, 0xa5, 0xc4, 0xcb, 0xb1, 0xa0, 0xc0, 0x68, 0x91, 0xd6, 0xe6, 0x7a, 0xd8, + 0x29, 0x2b, 0xc6, 0x02, 0x69, 0x6d, 0x26, 0x16, 0x14, 0xa7, 0xc5, 0xca, 0xb1, 0xa0, 0x40, 0xb7, + 0xcc, 0x3a, 0xdd, 0x40, 0x24, 0x98, 0x9e, 0xc8, 0xdf, 0x32, 0xcb, 0x1c, 0x25, 0xbd, 0x65, 0x04, + 0x00, 0x4b, 0x22, 0xe8, 0xd3, 0x26, 0xb7, 0x33, 0xc9, 0x68, 0x3e, 0xd3, 0x85, 0xdb, 0x31, 0xe8, + 0x76, 0xe6, 0x77, 0x5e, 0x81, 0xc2, 0x7a, 0x8d, 0x29, 0xc5, 0x72, 0x74, 0x06, 0xcb, 0x0b, 0x06, + 0x35, 0x16, 0x65, 0x7e, 0x79, 0x01, 0x17, 0xd6, 0x6b, 0x74, 0xe9, 0x3b, 0xf7, 0xda, 0x01, 0x59, + 0x76, 0x1b, 0x44, 0x64, 0xc8, 0xc8, 0x5c, 0xfa, 0x73, 0x12, 0x29, 0xbd, 0xf4, 0x15, 0x08, 0xc7, + 0xa4, 0x28, 0xdd, 0x98, 0x07, 0x3b, 0x92, 0x4f, 0x57, 0xb1, 0x5a, 0x69, 0xba, 0x99, 0x5c, 0xd8, + 0x16, 0x8c, 0x6d, 0x87, 0xad, 0x4d, 0x22, 0x4f, 0x45, 0xa6, 0xae, 0xcb, 0x89, 0x54, 0x71, 0x53, + 0x20, 0xba, 0x41, 0xd4, 0x76, 0x1a, 0xa9, 0x83, 0x9c, 0x89, 0x56, 0x6e, 0xea, 0xc4, 0xb0, 0x49, + 0x9b, 0x2e, 0x84, 0x77, 0x79, 0x38, 0x39, 0xa6, 0xb8, 0xcb, 0x59, 0x08, 0x19, 0x11, 0xe7, 0xf8, + 0x42, 0x10, 0x00, 0x2c, 0x89, 0xa8, 0xc1, 0x66, 0x17, 0xd0, 0xf1, 0x2e, 0x83, 0x9d, 0xea, 0x6f, + 0x3c, 0xd8, 0xec, 0xc2, 0x89, 0x49, 0xb1, 0x8b, 0xa6, 0xb5, 0xe9, 0x47, 0xbe, 0x97, 0xb8, 0xe4, + 0x4e, 0xe4, 0x5f, 0x34, 0x95, 0x0c, 0xfc, 0xf4, 0x45, 0x93, 0x85, 0x85, 0x33, 0xdb, 0xa2, 0x1f, + 0xd7, 0x92, 0x91, 0x01, 0x45, 0x16, 0x8f, 0xa7, 0x72, 0x02, 0x6b, 0xa6, 0xc3, 0x07, 0xf2, 0x8f, + 0x53, 0x20, 0x1c, 0x93, 0x42, 0x75, 0x18, 0x6f, 0x19, 0x11, 0x67, 0x59, 0x36, 0x92, 0x1c, 0xbe, + 0x20, 0x2b, 0x36, 0x2d, 0x97, 0x10, 0x99, 0x10, 0x9c, 0xa0, 0xc9, 0x2c, 0xf7, 0xb8, 0xab, 0x1f, + 0x4b, 0x56, 0x92, 0x33, 0xd5, 0x19, 0xde, 0x80, 0x7c, 0xaa, 0x05, 0x00, 0x4b, 0x22, 0x74, 0x34, + 0x84, 0x83, 0x9a, 0x1f, 0xb2, 0x9c, 0x3f, 0x79, 0x0a, 0xf6, 0x2c, 0x35, 0x91, 0x0c, 0xb3, 0x2e, + 0x40, 0x38, 0x26, 0x45, 0x4f, 0x72, 0x7a, 0xe1, 0x9d, 0xca, 0x3f, 0xc9, 0x93, 0xd7, 0x1d, 0x3b, + 0xc9, 0xe9, 0x65, 0x57, 0x14, 0x57, 0x9d, 0x8a, 0x0a, 0xce, 0xf2, 0x95, 0xe4, 0xf4, 0x4b, 0x85, + 0x15, 0x4f, 0xf7, 0x4b, 0x81, 0x70, 0x4c, 0x8a, 0x5d, 0xc5, 0x2c, 0x34, 0xdd, 0x99, 0x0e, 0x57, + 0x31, 0x45, 0xc8, 0xb8, 0x8a, 0xb5, 0xd0, 0x75, 0xf6, 0x8f, 0x17, 0xe0, 0x4c, 0xe7, 0x7d, 0x1b, + 0xeb, 0xd0, 0x2a, 0xb1, 0xcd, 0x52, 0x42, 0x87, 0xc6, 0x25, 0x3a, 0x31, 0x56, 0xcf, 0x01, 0x87, + 0x2f, 0xc3, 0x94, 0x72, 0x47, 0x6c, 0xb8, 0xb5, 0x1d, 0x2d, 0x49, 0xa9, 0x0a, 0xcd, 0x53, 0x4d, + 0x22, 0xe0, 0x74, 0x1d, 0x34, 0x07, 0x13, 0x46, 0x61, 0x79, 0x51, 0x3c, 0xff, 0xe3, 0x4c, 0x1b, + 0x26, 0x18, 0x27, 0xf1, 0xed, 0xdf, 0xb0, 0xe0, 0x44, 0x4e, 0xfe, 0xfb, 0x9e, 0xe3, 0xe9, 0xae, + 0xc3, 0x44, 0xcb, 0xac, 0xda, 0x25, 0x04, 0xb8, 0x91, 0x65, 0x5f, 0xf5, 0x35, 0x01, 0xc0, 0x49, + 0xa2, 0xf6, 0xaf, 0x15, 0xe0, 0x74, 0x47, 0xfb, 0x7a, 0x84, 0xe1, 0xf8, 0x46, 0x33, 0x74, 0x16, + 0x02, 0x52, 0x27, 0x5e, 0xe4, 0x3a, 0x8d, 0x6a, 0x8b, 0xd4, 0x34, 0x2d, 0x28, 0x33, 0x54, 0xbf, + 0xbc, 0x52, 0x9d, 0x4b, 0x63, 0xe0, 0x9c, 0x9a, 0x68, 0x19, 0x50, 0x1a, 0x22, 0x66, 0x98, 0x3d, + 0x71, 0xd3, 0xf4, 0x70, 0x46, 0x0d, 0xf4, 0x32, 0x8c, 0x29, 0xbb, 0x7d, 0x6d, 0xc6, 0xd9, 0x05, + 0x81, 0x75, 0x00, 0x36, 0xf1, 0xd0, 0x45, 0x9e, 0x82, 0x49, 0x24, 0xeb, 0x12, 0x2a, 0xd3, 0x09, + 0x99, 0x5f, 0x49, 0x14, 0x63, 0x1d, 0x67, 0xfe, 0xd2, 0x5f, 0x7c, 0xeb, 0xcc, 0x87, 0xfe, 0xea, + 0x5b, 0x67, 0x3e, 0xf4, 0xb7, 0xdf, 0x3a, 0xf3, 0xa1, 0x1f, 0xda, 0x3b, 0x63, 0xfd, 0xc5, 0xde, + 0x19, 0xeb, 0xaf, 0xf6, 0xce, 0x58, 0x7f, 0xbb, 0x77, 0xc6, 0xfa, 0xdf, 0xf7, 0xce, 0x58, 0x5f, + 0xfe, 0x3f, 0xce, 0x7c, 0xe8, 0x6d, 0x14, 0x47, 0xa8, 0xbe, 0x40, 0x67, 0xe7, 0xc2, 0xf6, 0xc5, + 0xff, 0x10, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xf1, 0x8c, 0x4c, 0x2d, 0x26, 0x01, 0x00, } func (m *AWSElasticBlockStoreVolumeSource) Marshal() (dAtA []byte, err error) { @@ -9887,6 +9921,13 @@ func (m *ContainerStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.StopSignal != nil { + i -= len(*m.StopSignal) + copy(dAtA[i:], *m.StopSignal) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.StopSignal))) + i-- + dAtA[i] = 0x7a + } if len(m.AllocatedResourcesStatus) > 0 { for iNdEx := len(m.AllocatedResourcesStatus) - 1; iNdEx >= 0; iNdEx-- { { @@ -12258,6 +12299,13 @@ func (m *Lifecycle) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.StopSignal != nil { + i -= len(*m.StopSignal) + copy(dAtA[i:], *m.StopSignal) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.StopSignal))) + i-- + dAtA[i] = 0x1a + } if m.PreStop != nil { { size, err := m.PreStop.MarshalToSizedBuffer(dAtA[:i]) @@ -14135,6 +14183,34 @@ func (m *NodeStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *NodeSwapStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeSwapStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *NodeSwapStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Capacity != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Capacity)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *NodeSystemInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14155,6 +14231,18 @@ func (m *NodeSystemInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Swap != nil { + { + size, err := m.Swap.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } i -= len(m.Architecture) copy(dAtA[i:], m.Architecture) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Architecture))) @@ -15723,6 +15811,9 @@ func (m *PodCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + i-- + dAtA[i] = 0x38 i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) @@ -16994,6 +17085,11 @@ func (m *PodStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x88 if len(m.HostIPs) > 0 { for iNdEx := len(m.HostIPs) - 1; iNdEx >= 0; iNdEx-- { { @@ -22542,6 +22638,10 @@ func (m *ContainerStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.StopSignal != nil { + l = len(*m.StopSignal) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -23382,6 +23482,10 @@ func (m *Lifecycle) Size() (n int) { l = m.PreStop.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.StopSignal != nil { + l = len(*m.StopSignal) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -24067,6 +24171,18 @@ func (m *NodeStatus) Size() (n int) { return n } +func (m *NodeSwapStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Capacity != nil { + n += 1 + sovGenerated(uint64(*m.Capacity)) + } + return n +} + func (m *NodeSystemInfo) Size() (n int) { if m == nil { return 0 @@ -24093,6 +24209,10 @@ func (m *NodeSystemInfo) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.Architecture) n += 1 + l + sovGenerated(uint64(l)) + if m.Swap != nil { + l = m.Swap.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -24650,6 +24770,7 @@ func (m *PodCondition) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) return n } @@ -25174,6 +25295,7 @@ func (m *PodStatus) Size() (n int) { n += 2 + l + sovGenerated(uint64(l)) } } + n += 2 + sovGenerated(uint64(m.ObservedGeneration)) return n } @@ -27457,6 +27579,7 @@ func (this *ContainerStatus) String() string { `VolumeMounts:` + repeatedStringForVolumeMounts + `,`, `User:` + strings.Replace(this.User.String(), "ContainerUser", "ContainerUser", 1) + `,`, `AllocatedResourcesStatus:` + repeatedStringForAllocatedResourcesStatus + `,`, + `StopSignal:` + valueToStringGenerated(this.StopSignal) + `,`, `}`, }, "") return s @@ -28080,6 +28203,7 @@ func (this *Lifecycle) String() string { s := strings.Join([]string{`&Lifecycle{`, `PostStart:` + strings.Replace(this.PostStart.String(), "LifecycleHandler", "LifecycleHandler", 1) + `,`, `PreStop:` + strings.Replace(this.PreStop.String(), "LifecycleHandler", "LifecycleHandler", 1) + `,`, + `StopSignal:` + valueToStringGenerated(this.StopSignal) + `,`, `}`, }, "") return s @@ -28658,6 +28782,16 @@ func (this *NodeStatus) String() string { }, "") return s } +func (this *NodeSwapStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&NodeSwapStatus{`, + `Capacity:` + valueToStringGenerated(this.Capacity) + `,`, + `}`, + }, "") + return s +} func (this *NodeSystemInfo) String() string { if this == nil { return "nil" @@ -28673,6 +28807,7 @@ func (this *NodeSystemInfo) String() string { `KubeProxyVersion:` + fmt.Sprintf("%v", this.KubeProxyVersion) + `,`, `OperatingSystem:` + fmt.Sprintf("%v", this.OperatingSystem) + `,`, `Architecture:` + fmt.Sprintf("%v", this.Architecture) + `,`, + `Swap:` + strings.Replace(this.Swap.String(), "NodeSwapStatus", "NodeSwapStatus", 1) + `,`, `}`, }, "") return s @@ -29045,6 +29180,7 @@ func (this *PodCondition) String() string { `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, `}`, }, "") return s @@ -29427,6 +29563,7 @@ func (this *PodStatus) String() string { `Resize:` + fmt.Sprintf("%v", this.Resize) + `,`, `ResourceClaimStatuses:` + repeatedStringForResourceClaimStatuses + `,`, `HostIPs:` + repeatedStringForHostIPs + `,`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, `}`, }, "") return s @@ -37794,88 +37931,122 @@ func (m *ContainerStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Resources == nil { - m.Resources = &ResourceRequirements{} - } - if err := m.Resources.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 12: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VolumeMounts", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.VolumeMounts = append(m.VolumeMounts, VolumeMountStatus{}) - if err := m.VolumeMounts[len(m.VolumeMounts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 13: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.User == nil { - m.User = &ContainerUser{} - } - if err := m.User.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.Resources == nil { + m.Resources = &ResourceRequirements{} + } + if err := m.Resources.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VolumeMounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VolumeMounts = append(m.VolumeMounts, VolumeMountStatus{}) + if err := m.VolumeMounts[len(m.VolumeMounts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.User == nil { + m.User = &ContainerUser{} + } + if err := m.User.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocatedResourcesStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllocatedResourcesStatus = append(m.AllocatedResourcesStatus, ResourceStatus{}) + if err := m.AllocatedResourcesStatus[len(m.AllocatedResourcesStatus)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 14: + case 15: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AllocatedResourcesStatus", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StopSignal", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -37885,25 +38056,24 @@ func (m *ContainerStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.AllocatedResourcesStatus = append(m.AllocatedResourcesStatus, ResourceStatus{}) - if err := m.AllocatedResourcesStatus[len(m.AllocatedResourcesStatus)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := Signal(dAtA[iNdEx:postIndex]) + m.StopSignal = &s iNdEx = postIndex default: iNdEx = preIndex @@ -45056,6 +45226,39 @@ func (m *Lifecycle) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StopSignal", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := Signal(dAtA[iNdEx:postIndex]) + m.StopSignal = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -50743,6 +50946,76 @@ func (m *NodeStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *NodeSwapStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeSwapStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeSwapStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Capacity", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Capacity = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *NodeSystemInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -51092,6 +51365,42 @@ func (m *NodeSystemInfo) Unmarshal(dAtA []byte) error { } m.Architecture = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Swap", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Swap == nil { + m.Swap = &NodeSwapStatus{} + } + if err := m.Swap.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -56087,6 +56396,25 @@ func (m *PodCondition) Unmarshal(dAtA []byte) error { } m.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -60340,6 +60668,25 @@ func (m *PodStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 17: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/core/v1/generated.proto b/go-controller/vendor/k8s.io/api/core/v1/generated.proto index 08706987c5..9b48fb1c39 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/core/v1/generated.proto @@ -1103,6 +1103,11 @@ message ContainerStatus { // +listType=map // +listMapKey=name repeated ResourceStatus allocatedResourcesStatus = 14; + + // StopSignal reports the effective stop signal for this container + // +featureGate=ContainerStopSignals + // +optional + optional string stopSignal = 15; } // ContainerUser represents user identity information @@ -1194,6 +1199,7 @@ message EmptyDirVolumeSource { } // EndpointAddress is a tuple that describes single IP address. +// Deprecated: This API is deprecated in v1.33+. // +structType=atomic message EndpointAddress { // The IP of this endpoint. @@ -1215,6 +1221,7 @@ message EndpointAddress { } // EndpointPort is a tuple that describes a single port. +// Deprecated: This API is deprecated in v1.33+. // +structType=atomic message EndpointPort { // The name of this port. This must match the 'name' field in the @@ -1265,6 +1272,8 @@ message EndpointPort { // // a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], // b: [ 10.10.1.1:309, 10.10.2.2:309 ] +// +// Deprecated: This API is deprecated in v1.33+. message EndpointSubset { // IP addresses which offer the related ports that are marked as ready. These endpoints // should be considered safe for load balancers and clients to utilize. @@ -1298,6 +1307,11 @@ message EndpointSubset { // Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] // }, // ] +// +// Endpoints is a legacy API and does not contain information about all Service features. +// Use discoveryv1.EndpointSlice for complete information about Service endpoints. +// +// Deprecated: This API is deprecated in v1.33+. Use discoveryv1.EndpointSlice. message Endpoints { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata @@ -1317,6 +1331,7 @@ message Endpoints { } // EndpointsList is a list of endpoints. +// Deprecated: This API is deprecated in v1.33+. message EndpointsList { // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds @@ -1327,9 +1342,9 @@ message EndpointsList { repeated Endpoints items = 2; } -// EnvFromSource represents the source of a set of ConfigMaps +// EnvFromSource represents the source of a set of ConfigMaps or Secrets message EnvFromSource { - // An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + // Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. // +optional optional string prefix = 1; @@ -2198,6 +2213,12 @@ message Lifecycle { // More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks // +optional optional LifecycleHandler preStop = 2; + + // StopSignal defines which signal will be sent to a container when it is being stopped. + // If not specified, the default is defined by the container runtime in use. + // StopSignal can only be set for Pods with a non-empty .spec.os.name + // +optional + optional string stopSignal = 3; } // LifecycleHandler defines a specific action that should be taken in a lifecycle @@ -2862,6 +2883,13 @@ message NodeStatus { optional NodeFeatures features = 13; } +// NodeSwapStatus represents swap memory information. +message NodeSwapStatus { + // Total amount of swap memory in bytes. + // +optional + optional int64 capacity = 1; +} + // NodeSystemInfo is a set of ids/uuids to uniquely identify the node. message NodeSystemInfo { // MachineID reported by the node. For unique machine identification @@ -2897,6 +2925,9 @@ message NodeSystemInfo { // The Architecture reported by the node optional string architecture = 10; + + // Swap Info reported by the node. + optional NodeSwapStatus swap = 11; } // ObjectFieldSelector selects an APIVersioned field of an object. @@ -3615,7 +3646,6 @@ message PodAffinityTerm { // pod labels will be ignored. The default value is empty. // The same key is forbidden to exist in both matchLabelKeys and labelSelector. // Also, matchLabelKeys cannot be set when labelSelector isn't set. - // This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). // // +listType=atomic // +optional @@ -3629,7 +3659,6 @@ message PodAffinityTerm { // pod labels will be ignored. The default value is empty. // The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. // Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - // This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). // // +listType=atomic // +optional @@ -3702,6 +3731,12 @@ message PodCondition { // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions optional string type = 1; + // If set, this represents the .metadata.generation that the pod condition was set based upon. + // This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field. + // +featureGate=PodObservedGenerationTracking + // +optional + optional int64 observedGeneration = 7; + // Status is the status of the condition. // Can be True, False, Unknown. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions @@ -4138,7 +4173,7 @@ message PodSpec { // Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. // The resourceRequirements of an init container are taken into account during scheduling // by finding the highest request/limit for each resource type, and then using the max of - // of that value or the sum of the normal containers. Limits are applied to init containers + // that value or the sum of the normal containers. Limits are applied to init containers // in a similar fashion. // Init containers cannot currently be added or removed. // Cannot be updated. @@ -4487,6 +4522,12 @@ message PodSpec { // state of a system, especially if the node that hosts the pod cannot contact the control // plane. message PodStatus { + // If set, this represents the .metadata.generation that the pod status was set based upon. + // This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field. + // +featureGate=PodObservedGenerationTracking + // +optional + optional int64 observedGeneration = 17; + // The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. // The conditions array, the reason and message fields, and the individual container status // arrays contain more detail about the pod's status. @@ -4618,6 +4659,9 @@ message PodStatus { // Status of resources resize desired for pod's containers. // It is empty if no resources resize is pending. // Any changes to container resources will automatically set this to "Proposed" + // Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. + // PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. + // PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources. // +featureGate=InPlacePodVerticalScaling // +optional optional string resize = 14; @@ -5063,12 +5107,18 @@ message ReplicationControllerSpec { // Defaults to 1. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller // +optional + // +k8s:optional + // +default=1 + // +k8s:minimum=0 optional int32 replicas = 1; // Minimum number of seconds for which a newly created pod should be ready // without any of its container crashing, for it to be considered available. // Defaults to 0 (pod will be considered available as soon as it is ready) // +optional + // +k8s:optional + // +default=0 + // +k8s:minimum=0 optional int32 minReadySeconds = 4; // Selector is a label query over pods that should match the Replicas count. @@ -6110,13 +6160,12 @@ message ServiceSpec { // +optional optional string internalTrafficPolicy = 22; - // TrafficDistribution offers a way to express preferences for how traffic is - // distributed to Service endpoints. Implementations can use this field as a - // hint, but are not required to guarantee strict adherence. If the field is - // not set, the implementation will apply its default routing strategy. If set - // to "PreferClose", implementations should prioritize endpoints that are - // topologically close (e.g., same zone). - // This is a beta field and requires enabling ServiceTrafficDistribution feature. + // TrafficDistribution offers a way to express preferences for how traffic + // is distributed to Service endpoints. Implementations can use this field + // as a hint, but are not required to guarantee strict adherence. If the + // field is not set, the implementation will apply its default routing + // strategy. If set to "PreferClose", implementations should prioritize + // endpoints that are in the same zone. // +featureGate=ServiceTrafficDistribution // +optional optional string trafficDistribution = 23; @@ -6411,7 +6460,6 @@ message TopologySpreadConstraint { // - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. // // If this value is nil, the behavior is equivalent to the Honor policy. - // This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // +optional optional string nodeAffinityPolicy = 6; @@ -6422,7 +6470,6 @@ message TopologySpreadConstraint { // - Ignore: node taints are ignored. All nodes are included. // // If this value is nil, the behavior is equivalent to the Ignore policy. - // This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // +optional optional string nodeTaintsPolicy = 7; @@ -6854,7 +6901,7 @@ message VolumeSource { // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. // The volume will be mounted read-only (ro) and non-executable files (noexec). - // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). + // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. // +featureGate=ImageVolume // +optional diff --git a/go-controller/vendor/k8s.io/api/core/v1/lifecycle.go b/go-controller/vendor/k8s.io/api/core/v1/lifecycle.go index 21ca90e815..21b931b67a 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/lifecycle.go +++ b/go-controller/vendor/k8s.io/api/core/v1/lifecycle.go @@ -16,6 +16,10 @@ limitations under the License. package v1 +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + // APILifecycleIntroduced returns the release in which the API struct was introduced as int versions of major and minor for comparison. func (in *ComponentStatus) APILifecycleIntroduced() (major, minor int) { return 1, 0 @@ -35,3 +39,23 @@ func (in *ComponentStatusList) APILifecycleIntroduced() (major, minor int) { func (in *ComponentStatusList) APILifecycleDeprecated() (major, minor int) { return 1, 19 } + +// APILifecycleDeprecated returns the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +func (in *Endpoints) APILifecycleDeprecated() (major, minor int) { + return 1, 33 +} + +// APILifecycleReplacement returns the GVK of the replacement for the given API +func (in *Endpoints) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "discovery.k8s.io", Version: "v1", Kind: "EndpointSlice"} +} + +// APILifecycleDeprecated returns the release in which the API struct was or will be deprecated as int versions of major and minor for comparison. +func (in *EndpointsList) APILifecycleDeprecated() (major, minor int) { + return 1, 33 +} + +// APILifecycleReplacement returns the GVK of the replacement for the given API +func (in *EndpointsList) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "discovery.k8s.io", Version: "v1", Kind: "EndpointSliceList"} +} diff --git a/go-controller/vendor/k8s.io/api/core/v1/types.go b/go-controller/vendor/k8s.io/api/core/v1/types.go index fb2c1c7453..f7641e485a 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/types.go +++ b/go-controller/vendor/k8s.io/api/core/v1/types.go @@ -217,7 +217,7 @@ type VolumeSource struct { // The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. // The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. // The volume will be mounted read-only (ro) and non-executable files (noexec). - // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). + // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. // +featureGate=ImageVolume // +optional @@ -2437,9 +2437,9 @@ type SecretKeySelector struct { Optional *bool `json:"optional,omitempty" protobuf:"varint,3,opt,name=optional"` } -// EnvFromSource represents the source of a set of ConfigMaps +// EnvFromSource represents the source of a set of ConfigMaps or Secrets type EnvFromSource struct { - // An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + // Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER. // +optional Prefix string `json:"prefix,omitempty" protobuf:"bytes,1,opt,name=prefix"` // The ConfigMap to select from @@ -2980,6 +2980,78 @@ type LifecycleHandler struct { Sleep *SleepAction `json:"sleep,omitempty" protobuf:"bytes,4,opt,name=sleep"` } +// Signal defines the stop signal of containers +// +enum +type Signal string + +const ( + SIGABRT Signal = "SIGABRT" + SIGALRM Signal = "SIGALRM" + SIGBUS Signal = "SIGBUS" + SIGCHLD Signal = "SIGCHLD" + SIGCLD Signal = "SIGCLD" + SIGCONT Signal = "SIGCONT" + SIGFPE Signal = "SIGFPE" + SIGHUP Signal = "SIGHUP" + SIGILL Signal = "SIGILL" + SIGINT Signal = "SIGINT" + SIGIO Signal = "SIGIO" + SIGIOT Signal = "SIGIOT" + SIGKILL Signal = "SIGKILL" + SIGPIPE Signal = "SIGPIPE" + SIGPOLL Signal = "SIGPOLL" + SIGPROF Signal = "SIGPROF" + SIGPWR Signal = "SIGPWR" + SIGQUIT Signal = "SIGQUIT" + SIGSEGV Signal = "SIGSEGV" + SIGSTKFLT Signal = "SIGSTKFLT" + SIGSTOP Signal = "SIGSTOP" + SIGSYS Signal = "SIGSYS" + SIGTERM Signal = "SIGTERM" + SIGTRAP Signal = "SIGTRAP" + SIGTSTP Signal = "SIGTSTP" + SIGTTIN Signal = "SIGTTIN" + SIGTTOU Signal = "SIGTTOU" + SIGURG Signal = "SIGURG" + SIGUSR1 Signal = "SIGUSR1" + SIGUSR2 Signal = "SIGUSR2" + SIGVTALRM Signal = "SIGVTALRM" + SIGWINCH Signal = "SIGWINCH" + SIGXCPU Signal = "SIGXCPU" + SIGXFSZ Signal = "SIGXFSZ" + SIGRTMIN Signal = "SIGRTMIN" + SIGRTMINPLUS1 Signal = "SIGRTMIN+1" + SIGRTMINPLUS2 Signal = "SIGRTMIN+2" + SIGRTMINPLUS3 Signal = "SIGRTMIN+3" + SIGRTMINPLUS4 Signal = "SIGRTMIN+4" + SIGRTMINPLUS5 Signal = "SIGRTMIN+5" + SIGRTMINPLUS6 Signal = "SIGRTMIN+6" + SIGRTMINPLUS7 Signal = "SIGRTMIN+7" + SIGRTMINPLUS8 Signal = "SIGRTMIN+8" + SIGRTMINPLUS9 Signal = "SIGRTMIN+9" + SIGRTMINPLUS10 Signal = "SIGRTMIN+10" + SIGRTMINPLUS11 Signal = "SIGRTMIN+11" + SIGRTMINPLUS12 Signal = "SIGRTMIN+12" + SIGRTMINPLUS13 Signal = "SIGRTMIN+13" + SIGRTMINPLUS14 Signal = "SIGRTMIN+14" + SIGRTMINPLUS15 Signal = "SIGRTMIN+15" + SIGRTMAXMINUS14 Signal = "SIGRTMAX-14" + SIGRTMAXMINUS13 Signal = "SIGRTMAX-13" + SIGRTMAXMINUS12 Signal = "SIGRTMAX-12" + SIGRTMAXMINUS11 Signal = "SIGRTMAX-11" + SIGRTMAXMINUS10 Signal = "SIGRTMAX-10" + SIGRTMAXMINUS9 Signal = "SIGRTMAX-9" + SIGRTMAXMINUS8 Signal = "SIGRTMAX-8" + SIGRTMAXMINUS7 Signal = "SIGRTMAX-7" + SIGRTMAXMINUS6 Signal = "SIGRTMAX-6" + SIGRTMAXMINUS5 Signal = "SIGRTMAX-5" + SIGRTMAXMINUS4 Signal = "SIGRTMAX-4" + SIGRTMAXMINUS3 Signal = "SIGRTMAX-3" + SIGRTMAXMINUS2 Signal = "SIGRTMAX-2" + SIGRTMAXMINUS1 Signal = "SIGRTMAX-1" + SIGRTMAX Signal = "SIGRTMAX" +) + // Lifecycle describes actions that the management system should take in response to container lifecycle // events. For the PostStart and PreStop lifecycle handlers, management of the container blocks // until the action is complete, unless the container process fails, in which case the handler is aborted. @@ -3001,6 +3073,11 @@ type Lifecycle struct { // More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks // +optional PreStop *LifecycleHandler `json:"preStop,omitempty" protobuf:"bytes,2,opt,name=preStop"` + // StopSignal defines which signal will be sent to a container when it is being stopped. + // If not specified, the default is defined by the container runtime in use. + // StopSignal can only be set for Pods with a non-empty .spec.os.name + // +optional + StopSignal *Signal `json:"stopSignal,omitempty" protobuf:"bytes,3,opt,name=stopSignal"` } type ConditionStatus string @@ -3154,6 +3231,10 @@ type ContainerStatus struct { // +listType=map // +listMapKey=name AllocatedResourcesStatus []ResourceStatus `json:"allocatedResourcesStatus,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,14,rep,name=allocatedResourcesStatus"` + // StopSignal reports the effective stop signal for this container + // +featureGate=ContainerStopSignals + // +optional + StopSignal *Signal `json:"stopSignal,omitempty" protobuf:"bytes,15,opt,name=stopSignal"` } // ResourceStatus represents the status of a single resource allocated to a Pod. @@ -3278,6 +3359,17 @@ const ( // PodReadyToStartContainers pod sandbox is successfully configured and // the pod is ready to launch containers. PodReadyToStartContainers PodConditionType = "PodReadyToStartContainers" + // PodResizePending indicates that the pod has been resized, but kubelet has not + // yet allocated the resources. If both PodResizePending and PodResizeInProgress + // are set, it means that a new resize was requested in the middle of a previous + // pod resize that is still in progress. + PodResizePending PodConditionType = "PodResizePending" + // PodResizeInProgress indicates that a resize is in progress, and is present whenever + // the Kubelet has allocated resources for the resize, but has not yet actuated all of + // the required changes. + // If both PodResizePending and PodResizeInProgress are set, it means that a new resize was + // requested in the middle of a previous pod resize that is still in progress. + PodResizeInProgress PodConditionType = "PodResizeInProgress" ) // These are reasons for a pod's transition to a condition. @@ -3301,6 +3393,18 @@ const ( // PodReasonPreemptionByScheduler reason in DisruptionTarget pod condition indicates that the // disruption was initiated by scheduler's preemption. PodReasonPreemptionByScheduler = "PreemptionByScheduler" + + // PodReasonDeferred reason in PodResizePending pod condition indicates the proposed resize is feasible in + // theory (it fits on this node) but is not possible right now. + PodReasonDeferred = "Deferred" + + // PodReasonInfeasible reason in PodResizePending pod condition indicates the proposed resize is not + // feasible and is rejected; it may not be re-evaluated + PodReasonInfeasible = "Infeasible" + + // PodReasonError reason in PodResizeInProgress pod condition indicates that an error occurred while + // actuating the resize. + PodReasonError = "Error" ) // PodCondition contains details for the current condition of this pod. @@ -3308,6 +3412,11 @@ type PodCondition struct { // Type is the type of the condition. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions Type PodConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=PodConditionType"` + // If set, this represents the .metadata.generation that the pod condition was set based upon. + // This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field. + // +featureGate=PodObservedGenerationTracking + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,7,opt,name=observedGeneration"` // Status is the status of the condition. // Can be True, False, Unknown. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions @@ -3326,12 +3435,10 @@ type PodCondition struct { Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` } -// PodResizeStatus shows status of desired resize of a pod's containers. +// Deprecated: PodResizeStatus shows status of desired resize of a pod's containers. type PodResizeStatus string const ( - // Pod resources resize has been requested and will be evaluated by node. - PodResizeStatusProposed PodResizeStatus = "Proposed" // Pod resources resize has been accepted by node and is being actuated. PodResizeStatusInProgress PodResizeStatus = "InProgress" // Node cannot resize the pod at this time and will keep retrying. @@ -3627,7 +3734,6 @@ type PodAffinityTerm struct { // pod labels will be ignored. The default value is empty. // The same key is forbidden to exist in both matchLabelKeys and labelSelector. // Also, matchLabelKeys cannot be set when labelSelector isn't set. - // This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). // // +listType=atomic // +optional @@ -3640,7 +3746,6 @@ type PodAffinityTerm struct { // pod labels will be ignored. The default value is empty. // The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. // Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - // This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). // // +listType=atomic // +optional @@ -3792,7 +3897,7 @@ type PodSpec struct { // Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. // The resourceRequirements of an init container are taken into account during scheduling // by finding the highest request/limit for each resource type, and then using the max of - // of that value or the sum of the normal containers. Limits are applied to init containers + // that value or the sum of the normal containers. Limits are applied to init containers // in a similar fashion. // Init containers cannot currently be added or removed. // Cannot be updated. @@ -4301,7 +4406,6 @@ type TopologySpreadConstraint struct { // - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. // // If this value is nil, the behavior is equivalent to the Honor policy. - // This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // +optional NodeAffinityPolicy *NodeInclusionPolicy `json:"nodeAffinityPolicy,omitempty" protobuf:"bytes,6,opt,name=nodeAffinityPolicy"` // NodeTaintsPolicy indicates how we will treat node taints when calculating @@ -4311,7 +4415,6 @@ type TopologySpreadConstraint struct { // - Ignore: node taints are ignored. All nodes are included. // // If this value is nil, the behavior is equivalent to the Ignore policy. - // This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // +optional NodeTaintsPolicy *NodeInclusionPolicy `json:"nodeTaintsPolicy,omitempty" protobuf:"bytes,7,opt,name=nodeTaintsPolicy"` // MatchLabelKeys is a set of pod label keys to select the pods over which @@ -4841,6 +4944,11 @@ type EphemeralContainer struct { // state of a system, especially if the node that hosts the pod cannot contact the control // plane. type PodStatus struct { + // If set, this represents the .metadata.generation that the pod status was set based upon. + // This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field. + // +featureGate=PodObservedGenerationTracking + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,17,opt,name=observedGeneration"` // The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. // The conditions array, the reason and message fields, and the individual container status // arrays contain more detail about the pod's status. @@ -4968,6 +5076,9 @@ type PodStatus struct { // Status of resources resize desired for pod's containers. // It is empty if no resources resize is pending. // Any changes to container resources will automatically set this to "Proposed" + // Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. + // PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. + // PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources. // +featureGate=InPlacePodVerticalScaling // +optional Resize PodResizeStatus `json:"resize,omitempty" protobuf:"bytes,14,opt,name=resize,casttype=PodResizeStatus"` @@ -5099,12 +5210,18 @@ type ReplicationControllerSpec struct { // Defaults to 1. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller // +optional + // +k8s:optional + // +default=1 + // +k8s:minimum=0 Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` // Minimum number of seconds for which a newly created pod should be ready // without any of its container crashing, for it to be considered available. // Defaults to 0 (pod will be considered available as soon as it is ready) // +optional + // +k8s:optional + // +default=0 + // +k8s:minimum=0 MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` // Selector is a label query over pods that should match the Replicas count. @@ -5334,14 +5451,27 @@ const ( // These are valid values for the TrafficDistribution field of a Service. const ( - // Indicates a preference for routing traffic to endpoints that are - // topologically proximate to the client. The interpretation of "topologically - // proximate" may vary across implementations and could encompass endpoints - // within the same node, rack, zone, or even region. Setting this value gives - // implementations permission to make different tradeoffs, e.g. optimizing for - // proximity rather than equal distribution of load. Users should not set this - // value if such tradeoffs are not acceptable. + // Indicates a preference for routing traffic to endpoints that are in the same + // zone as the client. Users should not set this value unless they have ensured + // that clients and endpoints are distributed in such a way that the "same zone" + // preference will not result in endpoints getting overloaded. ServiceTrafficDistributionPreferClose = "PreferClose" + + // Indicates a preference for routing traffic to endpoints that are in the same + // zone as the client. Users should not set this value unless they have ensured + // that clients and endpoints are distributed in such a way that the "same zone" + // preference will not result in endpoints getting overloaded. + // This is an alias for "PreferClose", but it is an Alpha feature and is only + // recognized if the PreferSameTrafficDistribution feature gate is enabled. + ServiceTrafficDistributionPreferSameZone = "PreferSameZone" + + // Indicates a preference for routing traffic to endpoints that are on the same + // node as the client. Users should not set this value unless they have ensured + // that clients and endpoints are distributed in such a way that the "same node" + // preference will not result in endpoints getting overloaded. + // This is an Alpha feature and is only recognized if the + // PreferSameTrafficDistribution feature gate is enabled. + ServiceTrafficDistributionPreferSameNode = "PreferSameNode" ) // These are the valid conditions of a service. @@ -5689,13 +5819,12 @@ type ServiceSpec struct { // +optional InternalTrafficPolicy *ServiceInternalTrafficPolicy `json:"internalTrafficPolicy,omitempty" protobuf:"bytes,22,opt,name=internalTrafficPolicy"` - // TrafficDistribution offers a way to express preferences for how traffic is - // distributed to Service endpoints. Implementations can use this field as a - // hint, but are not required to guarantee strict adherence. If the field is - // not set, the implementation will apply its default routing strategy. If set - // to "PreferClose", implementations should prioritize endpoints that are - // topologically close (e.g., same zone). - // This is a beta field and requires enabling ServiceTrafficDistribution feature. + // TrafficDistribution offers a way to express preferences for how traffic + // is distributed to Service endpoints. Implementations can use this field + // as a hint, but are not required to guarantee strict adherence. If the + // field is not set, the implementation will apply its default routing + // strategy. If set to "PreferClose", implementations should prioritize + // endpoints that are in the same zone. // +featureGate=ServiceTrafficDistribution // +optional TrafficDistribution *string `json:"trafficDistribution,omitempty" protobuf:"bytes,23,opt,name=trafficDistribution"` @@ -5888,6 +6017,11 @@ type ServiceAccountList struct { // Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] // }, // ] +// +// Endpoints is a legacy API and does not contain information about all Service features. +// Use discoveryv1.EndpointSlice for complete information about Service endpoints. +// +// Deprecated: This API is deprecated in v1.33+. Use discoveryv1.EndpointSlice. type Endpoints struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -5920,6 +6054,8 @@ type Endpoints struct { // // a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], // b: [ 10.10.1.1:309, 10.10.2.2:309 ] +// +// Deprecated: This API is deprecated in v1.33+. type EndpointSubset struct { // IP addresses which offer the related ports that are marked as ready. These endpoints // should be considered safe for load balancers and clients to utilize. @@ -5939,6 +6075,7 @@ type EndpointSubset struct { } // EndpointAddress is a tuple that describes single IP address. +// Deprecated: This API is deprecated in v1.33+. // +structType=atomic type EndpointAddress struct { // The IP of this endpoint. @@ -5957,6 +6094,7 @@ type EndpointAddress struct { } // EndpointPort is a tuple that describes a single port. +// Deprecated: This API is deprecated in v1.33+. // +structType=atomic type EndpointPort struct { // The name of this port. This must match the 'name' field in the @@ -5998,6 +6136,7 @@ type EndpointPort struct { // +k8s:prerelease-lifecycle-gen:introduced=1.0 // EndpointsList is a list of endpoints. +// Deprecated: This API is deprecated in v1.33+. type EndpointsList struct { metav1.TypeMeta `json:",inline"` // Standard list metadata. @@ -6166,6 +6305,15 @@ type NodeSystemInfo struct { OperatingSystem string `json:"operatingSystem" protobuf:"bytes,9,opt,name=operatingSystem"` // The Architecture reported by the node Architecture string `json:"architecture" protobuf:"bytes,10,opt,name=architecture"` + // Swap Info reported by the node. + Swap *NodeSwapStatus `json:"swap,omitempty" protobuf:"bytes,11,opt,name=swap"` +} + +// NodeSwapStatus represents swap memory information. +type NodeSwapStatus struct { + // Total amount of swap memory in bytes. + // +optional + Capacity *int64 `json:"capacity,omitempty" protobuf:"varint,1,opt,name=capacity"` } // NodeConfigStatus describes the status of the config assigned by Node.Spec.ConfigSource. @@ -7267,6 +7415,9 @@ const ( ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass" // Match all pod objects that have cross-namespace pod (anti)affinity mentioned. ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity" + + // Match all pvc objects that have volume attributes class mentioned. + ResourceQuotaScopeVolumeAttributesClass ResourceQuotaScope = "VolumeAttributesClass" ) // ResourceQuotaSpec defines the desired hard limits to enforce for Quota. diff --git a/go-controller/vendor/k8s.io/api/core/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/core/v1/types_swagger_doc_generated.go index 89ce3d2303..9e987eefdd 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/core/v1/types_swagger_doc_generated.go @@ -474,6 +474,7 @@ var map_ContainerStatus = map[string]string{ "volumeMounts": "Status of volume mounts.", "user": "User represents user identity information initially attached to the first process of the container", "allocatedResourcesStatus": "AllocatedResourcesStatus represents the status of various resources allocated for this Pod.", + "stopSignal": "StopSignal reports the effective stop signal for this container", } func (ContainerStatus) SwaggerDoc() map[string]string { @@ -540,7 +541,7 @@ func (EmptyDirVolumeSource) SwaggerDoc() map[string]string { } var map_EndpointAddress = map[string]string{ - "": "EndpointAddress is a tuple that describes single IP address.", + "": "EndpointAddress is a tuple that describes single IP address. Deprecated: This API is deprecated in v1.33+.", "ip": "The IP of this endpoint. May not be loopback (127.0.0.0/8 or ::1), link-local (169.254.0.0/16 or fe80::/10), or link-local multicast (224.0.0.0/24 or ff02::/16).", "hostname": "The Hostname of this endpoint", "nodeName": "Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node.", @@ -552,7 +553,7 @@ func (EndpointAddress) SwaggerDoc() map[string]string { } var map_EndpointPort = map[string]string{ - "": "EndpointPort is a tuple that describes a single port.", + "": "EndpointPort is a tuple that describes a single port. Deprecated: This API is deprecated in v1.33+.", "name": "The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.", "port": "The port number of the endpoint.", "protocol": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.", @@ -564,7 +565,7 @@ func (EndpointPort) SwaggerDoc() map[string]string { } var map_EndpointSubset = map[string]string{ - "": "EndpointSubset is a group of addresses with a common set of ports. The expanded set of endpoints is the Cartesian product of Addresses x Ports. For example, given:\n\n\t{\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t}\n\nThe resulting set of endpoints can be viewed as:\n\n\ta: [ 10.10.1.1:8675, 10.10.2.2:8675 ],\n\tb: [ 10.10.1.1:309, 10.10.2.2:309 ]", + "": "EndpointSubset is a group of addresses with a common set of ports. The expanded set of endpoints is the Cartesian product of Addresses x Ports. For example, given:\n\n\t{\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t}\n\nThe resulting set of endpoints can be viewed as:\n\n\ta: [ 10.10.1.1:8675, 10.10.2.2:8675 ],\n\tb: [ 10.10.1.1:309, 10.10.2.2:309 ]\n\nDeprecated: This API is deprecated in v1.33+.", "addresses": "IP addresses which offer the related ports that are marked as ready. These endpoints should be considered safe for load balancers and clients to utilize.", "notReadyAddresses": "IP addresses which offer the related ports but are not currently marked as ready because they have not yet finished starting, have recently failed a readiness check, or have recently failed a liveness check.", "ports": "Port numbers available on the related IP addresses.", @@ -575,7 +576,7 @@ func (EndpointSubset) SwaggerDoc() map[string]string { } var map_Endpoints = map[string]string{ - "": "Endpoints is a collection of endpoints that implement the actual service. Example:\n\n\t Name: \"mysvc\",\n\t Subsets: [\n\t {\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t },\n\t {\n\t Addresses: [{\"ip\": \"10.10.3.3\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 93}, {\"name\": \"b\", \"port\": 76}]\n\t },\n\t]", + "": "Endpoints is a collection of endpoints that implement the actual service. Example:\n\n\t Name: \"mysvc\",\n\t Subsets: [\n\t {\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t },\n\t {\n\t Addresses: [{\"ip\": \"10.10.3.3\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 93}, {\"name\": \"b\", \"port\": 76}]\n\t },\n\t]\n\nEndpoints is a legacy API and does not contain information about all Service features. Use discoveryv1.EndpointSlice for complete information about Service endpoints.\n\nDeprecated: This API is deprecated in v1.33+. Use discoveryv1.EndpointSlice.", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "subsets": "The set of all endpoints is the union of all subsets. Addresses are placed into subsets according to the IPs they share. A single address with multiple ports, some of which are ready and some of which are not (because they come from different containers) will result in the address being displayed in different subsets for the different ports. No address will appear in both Addresses and NotReadyAddresses in the same subset. Sets of addresses and ports that comprise a service.", } @@ -585,7 +586,7 @@ func (Endpoints) SwaggerDoc() map[string]string { } var map_EndpointsList = map[string]string{ - "": "EndpointsList is a list of endpoints.", + "": "EndpointsList is a list of endpoints. Deprecated: This API is deprecated in v1.33+.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of endpoints.", } @@ -595,8 +596,8 @@ func (EndpointsList) SwaggerDoc() map[string]string { } var map_EnvFromSource = map[string]string{ - "": "EnvFromSource represents the source of a set of ConfigMaps", - "prefix": "An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.", + "": "EnvFromSource represents the source of a set of ConfigMaps or Secrets", + "prefix": "Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER.", "configMapRef": "The ConfigMap to select from", "secretRef": "The Secret to select from", } @@ -957,9 +958,10 @@ func (KeyToPath) SwaggerDoc() map[string]string { } var map_Lifecycle = map[string]string{ - "": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", - "postStart": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", - "preStop": "PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod's termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod's termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", + "": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", + "postStart": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", + "preStop": "PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod's termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod's termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", + "stopSignal": "StopSignal defines which signal will be sent to a container when it is being stopped. If not specified, the default is defined by the container runtime in use. StopSignal can only be set for Pods with a non-empty .spec.os.name", } func (Lifecycle) SwaggerDoc() map[string]string { @@ -1335,6 +1337,15 @@ func (NodeStatus) SwaggerDoc() map[string]string { return map_NodeStatus } +var map_NodeSwapStatus = map[string]string{ + "": "NodeSwapStatus represents swap memory information.", + "capacity": "Total amount of swap memory in bytes.", +} + +func (NodeSwapStatus) SwaggerDoc() map[string]string { + return map_NodeSwapStatus +} + var map_NodeSystemInfo = map[string]string{ "": "NodeSystemInfo is a set of ids/uuids to uniquely identify the node.", "machineID": "MachineID reported by the node. For unique machine identification in the cluster this field is preferred. Learn more from man(5) machine-id: http://man7.org/linux/man-pages/man5/machine-id.5.html", @@ -1347,6 +1358,7 @@ var map_NodeSystemInfo = map[string]string{ "kubeProxyVersion": "Deprecated: KubeProxy Version reported by the node.", "operatingSystem": "The Operating System reported by the node", "architecture": "The Architecture reported by the node", + "swap": "Swap Info reported by the node.", } func (NodeSystemInfo) SwaggerDoc() map[string]string { @@ -1583,8 +1595,8 @@ var map_PodAffinityTerm = map[string]string{ "namespaces": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", "topologyKey": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", "namespaceSelector": "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces.", - "matchLabelKeys": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).", - "mismatchLabelKeys": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).", + "matchLabelKeys": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + "mismatchLabelKeys": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", } func (PodAffinityTerm) SwaggerDoc() map[string]string { @@ -1617,6 +1629,7 @@ func (PodAttachOptions) SwaggerDoc() map[string]string { var map_PodCondition = map[string]string{ "": "PodCondition contains details for the current condition of this pod.", "type": "Type is the type of the condition. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + "observedGeneration": "If set, this represents the .metadata.generation that the pod condition was set based upon. This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.", "status": "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "lastProbeTime": "Last time we probed the condition.", "lastTransitionTime": "Last time the condition transitioned from one status to another.", @@ -1799,7 +1812,7 @@ func (PodSignature) SwaggerDoc() map[string]string { var map_PodSpec = map[string]string{ "": "PodSpec is a description of a pod.", "volumes": "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes", - "initContainers": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", + "initContainers": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", "containers": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.", "ephemeralContainers": "List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.", "restartPolicy": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. In some contexts, only a subset of those values may be permitted. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy", @@ -1846,6 +1859,7 @@ func (PodSpec) SwaggerDoc() map[string]string { var map_PodStatus = map[string]string{ "": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", + "observedGeneration": "If set, this represents the .metadata.generation that the pod status was set based upon. This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.", "phase": "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase", "conditions": "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "message": "A human readable message indicating details about why the pod is in this condition.", @@ -1860,7 +1874,7 @@ var map_PodStatus = map[string]string{ "containerStatuses": "Statuses of containers in this pod. Each container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", "qosClass": "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes", "ephemeralContainerStatuses": "Statuses for any ephemeral containers that have run in this pod. Each ephemeral container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", - "resize": "Status of resources resize desired for pod's containers. It is empty if no resources resize is pending. Any changes to container resources will automatically set this to \"Proposed\"", + "resize": "Status of resources resize desired for pod's containers. It is empty if no resources resize is pending. Any changes to container resources will automatically set this to \"Proposed\" Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.", "resourceClaimStatuses": "Status of resource claims.", } @@ -2487,7 +2501,7 @@ var map_ServiceSpec = map[string]string{ "allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type.", "loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.", "internalTrafficPolicy": "InternalTrafficPolicy describes how nodes distribute service traffic they receive on the ClusterIP. If set to \"Local\", the proxy will assume that pods only want to talk to endpoints of the service on the same node as the pod, dropping the traffic if there are no local endpoints. The default value, \"Cluster\", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features).", - "trafficDistribution": "TrafficDistribution offers a way to express preferences for how traffic is distributed to Service endpoints. Implementations can use this field as a hint, but are not required to guarantee strict adherence. If the field is not set, the implementation will apply its default routing strategy. If set to \"PreferClose\", implementations should prioritize endpoints that are topologically close (e.g., same zone). This is a beta field and requires enabling ServiceTrafficDistribution feature.", + "trafficDistribution": "TrafficDistribution offers a way to express preferences for how traffic is distributed to Service endpoints. Implementations can use this field as a hint, but are not required to guarantee strict adherence. If the field is not set, the implementation will apply its default routing strategy. If set to \"PreferClose\", implementations should prioritize endpoints that are in the same zone.", } func (ServiceSpec) SwaggerDoc() map[string]string { @@ -2619,8 +2633,8 @@ var map_TopologySpreadConstraint = map[string]string{ "whenUnsatisfiable": "WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it. - ScheduleAnyway tells the scheduler to schedule the pod in any location,\n but giving higher precedence to topologies that would help reduce the\n skew.\nA constraint is considered \"Unsatisfiable\" for an incoming pod if and only if every possible node assignment for that pod would violate \"MaxSkew\" on some topology. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: ", "labelSelector": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain.", "minDomains": "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule.\n\nFor example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: ", - "nodeAffinityPolicy": "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.\n\nIf this value is nil, the behavior is equivalent to the Honor policy. This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.", - "nodeTaintsPolicy": "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included.\n\nIf this value is nil, the behavior is equivalent to the Ignore policy. This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.", + "nodeAffinityPolicy": "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.\n\nIf this value is nil, the behavior is equivalent to the Honor policy.", + "nodeTaintsPolicy": "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included.\n\nIf this value is nil, the behavior is equivalent to the Ignore policy.", "matchLabelKeys": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot be set when LabelSelector isn't set. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.\n\nThis is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default).", } @@ -2760,7 +2774,7 @@ var map_VolumeSource = map[string]string{ "storageos": "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported.", "csi": "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.", "ephemeral": "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.", - "image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + "image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", } func (VolumeSource) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go index 3f669092ef..619c525427 100644 --- a/go-controller/vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go @@ -1055,6 +1055,11 @@ func (in *ContainerStatus) DeepCopyInto(out *ContainerStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.StopSignal != nil { + in, out := &in.StopSignal, &out.StopSignal + *out = new(Signal) + **out = **in + } return } @@ -2101,6 +2106,11 @@ func (in *Lifecycle) DeepCopyInto(out *Lifecycle) { *out = new(LifecycleHandler) (*in).DeepCopyInto(*out) } + if in.StopSignal != nil { + in, out := &in.StopSignal, &out.StopSignal + *out = new(Signal) + **out = **in + } return } @@ -3002,7 +3012,7 @@ func (in *NodeStatus) DeepCopyInto(out *NodeStatus) { copy(*out, *in) } out.DaemonEndpoints = in.DaemonEndpoints - out.NodeInfo = in.NodeInfo + in.NodeInfo.DeepCopyInto(&out.NodeInfo) if in.Images != nil { in, out := &in.Images, &out.Images *out = make([]ContainerImage, len(*in)) @@ -3050,9 +3060,35 @@ func (in *NodeStatus) DeepCopy() *NodeStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSwapStatus) DeepCopyInto(out *NodeSwapStatus) { + *out = *in + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = new(int64) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSwapStatus. +func (in *NodeSwapStatus) DeepCopy() *NodeSwapStatus { + if in == nil { + return nil + } + out := new(NodeSwapStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeSystemInfo) DeepCopyInto(out *NodeSystemInfo) { *out = *in + if in.Swap != nil { + in, out := &in.Swap, &out.Swap + *out = new(NodeSwapStatus) + (*in).DeepCopyInto(*out) + } return } diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/doc.go b/go-controller/vendor/k8s.io/api/discovery/v1/doc.go index 01913669ff..43e30b7f43 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=discovery.k8s.io -package v1 // import "k8s.io/api/discovery/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/generated.pb.go b/go-controller/vendor/k8s.io/api/discovery/v1/generated.pb.go index 5792481dc1..443ff8f8f3 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1/generated.pb.go @@ -214,10 +214,38 @@ func (m *EndpointSliceList) XXX_DiscardUnknown() { var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo +func (m *ForNode) Reset() { *m = ForNode{} } +func (*ForNode) ProtoMessage() {} +func (*ForNode) Descriptor() ([]byte, []int) { + return fileDescriptor_2237b452324cf77e, []int{6} +} +func (m *ForNode) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ForNode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ForNode) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForNode.Merge(m, src) +} +func (m *ForNode) XXX_Size() int { + return m.Size() +} +func (m *ForNode) XXX_DiscardUnknown() { + xxx_messageInfo_ForNode.DiscardUnknown(m) +} + +var xxx_messageInfo_ForNode proto.InternalMessageInfo + func (m *ForZone) Reset() { *m = ForZone{} } func (*ForZone) ProtoMessage() {} func (*ForZone) Descriptor() ([]byte, []int) { - return fileDescriptor_2237b452324cf77e, []int{6} + return fileDescriptor_2237b452324cf77e, []int{7} } func (m *ForZone) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -250,6 +278,7 @@ func init() { proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1.EndpointPort") proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1.EndpointSlice") proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1.EndpointSliceList") + proto.RegisterType((*ForNode)(nil), "k8s.io.api.discovery.v1.ForNode") proto.RegisterType((*ForZone)(nil), "k8s.io.api.discovery.v1.ForZone") } @@ -258,62 +287,64 @@ func init() { } var fileDescriptor_2237b452324cf77e = []byte{ - // 877 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0x4d, 0x6f, 0xdc, 0x44, - 0x18, 0x5e, 0x67, 0x63, 0x62, 0x8f, 0x13, 0xd1, 0x8e, 0x90, 0x62, 0x2d, 0xc8, 0x5e, 0x8c, 0x0a, - 0x2b, 0x45, 0x78, 0x49, 0x84, 0x50, 0x41, 0xe2, 0x10, 0xd3, 0xd0, 0xf2, 0x15, 0xa2, 0x69, 0x4e, - 0x15, 0x52, 0x71, 0xec, 0x37, 0x5e, 0x93, 0xd8, 0x63, 0x79, 0x26, 0x2b, 0x2d, 0x27, 0x2e, 0x9c, - 0xe1, 0x17, 0x71, 0x44, 0x39, 0xf6, 0x46, 0x4f, 0x16, 0x31, 0x7f, 0x81, 0x53, 0x4f, 0x68, 0xc6, - 0x9f, 0x61, 0xb3, 0xda, 0xde, 0x3c, 0xcf, 0x3c, 0xcf, 0xfb, 0xf1, 0xcc, 0xcc, 0x6b, 0xf4, 0xc1, - 0xc5, 0x43, 0xe6, 0xc6, 0x74, 0xea, 0x67, 0xf1, 0x34, 0x8c, 0x59, 0x40, 0xe7, 0x90, 0x2f, 0xa6, - 0xf3, 0xfd, 0x69, 0x04, 0x29, 0xe4, 0x3e, 0x87, 0xd0, 0xcd, 0x72, 0xca, 0x29, 0xde, 0xad, 0x88, - 0xae, 0x9f, 0xc5, 0x6e, 0x4b, 0x74, 0xe7, 0xfb, 0xa3, 0x0f, 0xa3, 0x98, 0xcf, 0xae, 0xce, 0xdc, - 0x80, 0x26, 0xd3, 0x88, 0x46, 0x74, 0x2a, 0xf9, 0x67, 0x57, 0xe7, 0x72, 0x25, 0x17, 0xf2, 0xab, - 0x8a, 0x33, 0x72, 0x7a, 0x09, 0x03, 0x9a, 0xc3, 0x1d, 0xb9, 0x46, 0x1f, 0x77, 0x9c, 0xc4, 0x0f, - 0x66, 0x71, 0x2a, 0x6a, 0xca, 0x2e, 0x22, 0x01, 0xb0, 0x69, 0x02, 0xdc, 0xbf, 0x4b, 0x35, 0x5d, - 0xa5, 0xca, 0xaf, 0x52, 0x1e, 0x27, 0xb0, 0x24, 0xf8, 0x64, 0x9d, 0x80, 0x05, 0x33, 0x48, 0xfc, - 0xff, 0xeb, 0x9c, 0x7f, 0x37, 0x91, 0x76, 0x94, 0x86, 0x19, 0x8d, 0x53, 0x8e, 0xf7, 0x90, 0xee, - 0x87, 0x61, 0x0e, 0x8c, 0x01, 0x33, 0x95, 0xf1, 0x70, 0xa2, 0x7b, 0x3b, 0x65, 0x61, 0xeb, 0x87, - 0x0d, 0x48, 0xba, 0x7d, 0xfc, 0x1c, 0xa1, 0x80, 0xa6, 0x61, 0xcc, 0x63, 0x9a, 0x32, 0x73, 0x63, - 0xac, 0x4c, 0x8c, 0x83, 0x3d, 0x77, 0x85, 0xb3, 0x6e, 0x93, 0xe3, 0x8b, 0x56, 0xe2, 0xe1, 0xeb, - 0xc2, 0x1e, 0x94, 0x85, 0x8d, 0x3a, 0x8c, 0xf4, 0x42, 0xe2, 0x09, 0xd2, 0x66, 0x94, 0xf1, 0xd4, - 0x4f, 0xc0, 0x1c, 0x8e, 0x95, 0x89, 0xee, 0x6d, 0x97, 0x85, 0xad, 0x3d, 0xa9, 0x31, 0xd2, 0xee, - 0xe2, 0x13, 0xa4, 0x73, 0x3f, 0x8f, 0x80, 0x13, 0x38, 0x37, 0x37, 0x65, 0x25, 0xef, 0xf5, 0x2b, - 0x11, 0x67, 0x23, 0x8a, 0xf8, 0xfe, 0xec, 0x27, 0x08, 0x04, 0x09, 0x72, 0x48, 0x03, 0xa8, 0x9a, - 0x3b, 0x6d, 0x94, 0xa4, 0x0b, 0x82, 0x7f, 0x55, 0x10, 0x0e, 0x21, 0xcb, 0x21, 0x10, 0x5e, 0x9d, - 0xd2, 0x8c, 0x5e, 0xd2, 0x68, 0x61, 0xaa, 0xe3, 0xe1, 0xc4, 0x38, 0xf8, 0x74, 0x6d, 0x97, 0xee, - 0xa3, 0x25, 0xed, 0x51, 0xca, 0xf3, 0x85, 0x37, 0xaa, 0x7b, 0xc6, 0xcb, 0x04, 0x72, 0x47, 0x42, - 0xe1, 0x41, 0x4a, 0x43, 0x38, 0x16, 0x1e, 0xbc, 0xd1, 0x79, 0x70, 0x5c, 0x63, 0xa4, 0xdd, 0xc5, - 0xef, 0xa0, 0xcd, 0x9f, 0x69, 0x0a, 0xe6, 0x96, 0x64, 0x69, 0x65, 0x61, 0x6f, 0x3e, 0xa3, 0x29, - 0x10, 0x89, 0xe2, 0xc7, 0x48, 0x9d, 0xc5, 0x29, 0x67, 0xa6, 0x26, 0xdd, 0x79, 0x7f, 0x6d, 0x07, - 0x4f, 0x04, 0xdb, 0xd3, 0xcb, 0xc2, 0x56, 0xe5, 0x27, 0xa9, 0xf4, 0xa3, 0x23, 0xb4, 0xbb, 0xa2, - 0x37, 0x7c, 0x0f, 0x0d, 0x2f, 0x60, 0x61, 0x2a, 0xa2, 0x00, 0x22, 0x3e, 0xf1, 0x5b, 0x48, 0x9d, - 0xfb, 0x97, 0x57, 0x20, 0x6f, 0x87, 0x4e, 0xaa, 0xc5, 0x67, 0x1b, 0x0f, 0x15, 0xe7, 0x37, 0x05, - 0xe1, 0xe5, 0x2b, 0x81, 0x6d, 0xa4, 0xe6, 0xe0, 0x87, 0x55, 0x10, 0xad, 0x4a, 0x4f, 0x04, 0x40, - 0x2a, 0x1c, 0x3f, 0x40, 0x5b, 0x0c, 0xf2, 0x79, 0x9c, 0x46, 0x32, 0xa6, 0xe6, 0x19, 0x65, 0x61, - 0x6f, 0x3d, 0xad, 0x20, 0xd2, 0xec, 0xe1, 0x7d, 0x64, 0x70, 0xc8, 0x93, 0x38, 0xf5, 0xb9, 0xa0, - 0x0e, 0x25, 0xf5, 0xcd, 0xb2, 0xb0, 0x8d, 0xd3, 0x0e, 0x26, 0x7d, 0x8e, 0xf3, 0x1c, 0xed, 0xdc, - 0xea, 0x1d, 0x1f, 0x23, 0xed, 0x9c, 0xe6, 0xc2, 0xc3, 0xea, 0x2d, 0x18, 0x07, 0xe3, 0x95, 0xae, - 0x7d, 0x59, 0x11, 0xbd, 0x7b, 0xf5, 0xf1, 0x6a, 0x35, 0xc0, 0x48, 0x1b, 0xc3, 0xf9, 0x53, 0x41, - 0xdb, 0x4d, 0x86, 0x13, 0x9a, 0x73, 0x71, 0x62, 0xf2, 0x6e, 0x2b, 0xdd, 0x89, 0xc9, 0x33, 0x95, - 0x28, 0x7e, 0x8c, 0x34, 0xf9, 0x42, 0x03, 0x7a, 0x59, 0xd9, 0xe7, 0xed, 0x89, 0xc0, 0x27, 0x35, - 0xf6, 0xaa, 0xb0, 0xdf, 0x5e, 0x9e, 0x3e, 0x6e, 0xb3, 0x4d, 0x5a, 0xb1, 0x48, 0x93, 0xd1, 0x9c, - 0x4b, 0x13, 0xd4, 0x2a, 0x8d, 0x48, 0x4f, 0x24, 0x2a, 0x9c, 0xf2, 0xb3, 0xac, 0x91, 0xc9, 0xc7, - 0xa3, 0x57, 0x4e, 0x1d, 0x76, 0x30, 0xe9, 0x73, 0x9c, 0xbf, 0x36, 0x3a, 0xab, 0x9e, 0x5e, 0xc6, - 0x01, 0xe0, 0x1f, 0x91, 0x26, 0x06, 0x59, 0xe8, 0x73, 0x5f, 0x76, 0x63, 0x1c, 0x7c, 0xd4, 0xb3, - 0xaa, 0x9d, 0x47, 0x6e, 0x76, 0x11, 0x09, 0x80, 0xb9, 0x82, 0xdd, 0x3d, 0xc8, 0xef, 0x80, 0xfb, - 0xdd, 0x34, 0xe8, 0x30, 0xd2, 0x46, 0xc5, 0x8f, 0x90, 0x51, 0x4f, 0x9e, 0xd3, 0x45, 0x06, 0x75, - 0x99, 0x4e, 0x2d, 0x31, 0x0e, 0xbb, 0xad, 0x57, 0xb7, 0x97, 0xa4, 0x2f, 0xc3, 0x04, 0xe9, 0x50, - 0x17, 0x2e, 0x26, 0x96, 0x38, 0xd3, 0x77, 0xd7, 0xbe, 0x04, 0xef, 0x7e, 0x9d, 0x46, 0x6f, 0x10, - 0x46, 0xba, 0x30, 0xf8, 0x6b, 0xa4, 0x0a, 0x23, 0x99, 0x39, 0x94, 0xf1, 0x1e, 0xac, 0x8d, 0x27, - 0xcc, 0xf7, 0x76, 0xea, 0x98, 0xaa, 0x58, 0x31, 0x52, 0x85, 0x70, 0xfe, 0x50, 0xd0, 0xfd, 0x5b, - 0xce, 0x7e, 0x1b, 0x33, 0x8e, 0x7f, 0x58, 0x72, 0xd7, 0x7d, 0x3d, 0x77, 0x85, 0x5a, 0x7a, 0xdb, - 0x5e, 0xcb, 0x06, 0xe9, 0x39, 0xfb, 0x0d, 0x52, 0x63, 0x0e, 0x49, 0xe3, 0xc7, 0xfa, 0xc9, 0x20, - 0x0b, 0xeb, 0x1a, 0xf8, 0x4a, 0x88, 0x49, 0x15, 0xc3, 0xd9, 0x43, 0x5b, 0xf5, 0xcd, 0xc7, 0xe3, - 0x5b, 0xb7, 0x7b, 0xbb, 0xa6, 0xf7, 0x6e, 0xb8, 0xf7, 0xf9, 0xf5, 0x8d, 0x35, 0x78, 0x71, 0x63, - 0x0d, 0x5e, 0xde, 0x58, 0x83, 0x5f, 0x4a, 0x4b, 0xb9, 0x2e, 0x2d, 0xe5, 0x45, 0x69, 0x29, 0x2f, - 0x4b, 0x4b, 0xf9, 0xbb, 0xb4, 0x94, 0xdf, 0xff, 0xb1, 0x06, 0xcf, 0x76, 0x57, 0xfc, 0xd4, 0xff, - 0x0b, 0x00, 0x00, 0xff, 0xff, 0x76, 0x4b, 0x26, 0xe3, 0xee, 0x07, 0x00, 0x00, + // 902 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xcf, 0x6f, 0xe3, 0x44, + 0x14, 0x8e, 0x9b, 0x9a, 0xda, 0xe3, 0x56, 0xec, 0x8e, 0x90, 0x6a, 0x05, 0x64, 0x07, 0xa3, 0x85, + 0x48, 0x15, 0x0e, 0xad, 0x10, 0x5a, 0x90, 0x38, 0xd4, 0x6c, 0xd9, 0xe5, 0x57, 0xa9, 0x66, 0x7b, + 0x5a, 0x21, 0x81, 0x6b, 0xbf, 0x3a, 0xa6, 0x8d, 0xc7, 0xf2, 0x4c, 0x22, 0x85, 0x13, 0x17, 0xce, + 0xf0, 0x9f, 0xf0, 0x1f, 0x70, 0x44, 0x3d, 0xee, 0x8d, 0x3d, 0x59, 0xd4, 0xfc, 0x0b, 0x9c, 0xf6, + 0x84, 0x66, 0xfc, 0x33, 0xa4, 0x51, 0xf6, 0xe6, 0xf9, 0xe6, 0x7b, 0xdf, 0x7b, 0xf3, 0xcd, 0x7b, + 0x23, 0xa3, 0xf7, 0xae, 0x1e, 0x32, 0x37, 0xa6, 0x63, 0x3f, 0x8d, 0xc7, 0x61, 0xcc, 0x02, 0x3a, + 0x87, 0x6c, 0x31, 0x9e, 0x1f, 0x8e, 0x23, 0x48, 0x20, 0xf3, 0x39, 0x84, 0x6e, 0x9a, 0x51, 0x4e, + 0xf1, 0x7e, 0x49, 0x74, 0xfd, 0x34, 0x76, 0x1b, 0xa2, 0x3b, 0x3f, 0x1c, 0xbc, 0x1f, 0xc5, 0x7c, + 0x32, 0xbb, 0x70, 0x03, 0x3a, 0x1d, 0x47, 0x34, 0xa2, 0x63, 0xc9, 0xbf, 0x98, 0x5d, 0xca, 0x95, + 0x5c, 0xc8, 0xaf, 0x52, 0x67, 0xe0, 0x74, 0x12, 0x06, 0x34, 0x83, 0x3b, 0x72, 0x0d, 0x3e, 0x6c, + 0x39, 0x53, 0x3f, 0x98, 0xc4, 0x89, 0xa8, 0x29, 0xbd, 0x8a, 0x04, 0xc0, 0xc6, 0x53, 0xe0, 0xfe, + 0x5d, 0x51, 0xe3, 0x75, 0x51, 0xd9, 0x2c, 0xe1, 0xf1, 0x14, 0x56, 0x02, 0x3e, 0xda, 0x14, 0xc0, + 0x82, 0x09, 0x4c, 0xfd, 0xff, 0xc7, 0x39, 0xff, 0x6e, 0x23, 0xed, 0x24, 0x09, 0x53, 0x1a, 0x27, + 0x1c, 0x1f, 0x20, 0xdd, 0x0f, 0xc3, 0x0c, 0x18, 0x03, 0x66, 0x2a, 0xc3, 0xfe, 0x48, 0xf7, 0xf6, + 0x8a, 0xdc, 0xd6, 0x8f, 0x6b, 0x90, 0xb4, 0xfb, 0xf8, 0x7b, 0x84, 0x02, 0x9a, 0x84, 0x31, 0x8f, + 0x69, 0xc2, 0xcc, 0xad, 0xa1, 0x32, 0x32, 0x8e, 0x0e, 0xdc, 0x35, 0xce, 0xba, 0x75, 0x8e, 0xcf, + 0x9a, 0x10, 0x0f, 0xdf, 0xe4, 0x76, 0xaf, 0xc8, 0x6d, 0xd4, 0x62, 0xa4, 0x23, 0x89, 0x47, 0x48, + 0x9b, 0x50, 0xc6, 0x13, 0x7f, 0x0a, 0x66, 0x7f, 0xa8, 0x8c, 0x74, 0x6f, 0xb7, 0xc8, 0x6d, 0xed, + 0x49, 0x85, 0x91, 0x66, 0x17, 0x9f, 0x21, 0x9d, 0xfb, 0x59, 0x04, 0x9c, 0xc0, 0xa5, 0xb9, 0x2d, + 0x2b, 0x79, 0xa7, 0x5b, 0x89, 0xb8, 0x1b, 0x51, 0xc4, 0xb7, 0x17, 0x3f, 0x42, 0x20, 0x48, 0x90, + 0x41, 0x12, 0x40, 0x79, 0xb8, 0xf3, 0x3a, 0x92, 0xb4, 0x22, 0xf8, 0x17, 0x05, 0xe1, 0x10, 0xd2, + 0x0c, 0x02, 0xe1, 0xd5, 0x39, 0x4d, 0xe9, 0x35, 0x8d, 0x16, 0xa6, 0x3a, 0xec, 0x8f, 0x8c, 0xa3, + 0x8f, 0x37, 0x9e, 0xd2, 0x7d, 0xb4, 0x12, 0x7b, 0x92, 0xf0, 0x6c, 0xe1, 0x0d, 0xaa, 0x33, 0xe3, + 0x55, 0x02, 0xb9, 0x23, 0xa1, 0xf0, 0x20, 0xa1, 0x21, 0x9c, 0x0a, 0x0f, 0x5e, 0x6b, 0x3d, 0x38, + 0xad, 0x30, 0xd2, 0xec, 0xe2, 0xb7, 0xd0, 0xf6, 0x4f, 0x34, 0x01, 0x73, 0x47, 0xb2, 0xb4, 0x22, + 0xb7, 0xb7, 0x9f, 0xd1, 0x04, 0x88, 0x44, 0xf1, 0x63, 0xa4, 0x4e, 0xe2, 0x84, 0x33, 0x53, 0x93, + 0xee, 0xbc, 0xbb, 0xf1, 0x04, 0x4f, 0x04, 0xdb, 0xd3, 0x8b, 0xdc, 0x56, 0xe5, 0x27, 0x29, 0xe3, + 0x07, 0x27, 0x68, 0x7f, 0xcd, 0xd9, 0xf0, 0x3d, 0xd4, 0xbf, 0x82, 0x85, 0xa9, 0x88, 0x02, 0x88, + 0xf8, 0xc4, 0x6f, 0x20, 0x75, 0xee, 0x5f, 0xcf, 0x40, 0x76, 0x87, 0x4e, 0xca, 0xc5, 0x27, 0x5b, + 0x0f, 0x15, 0xe7, 0x57, 0x05, 0xe1, 0xd5, 0x96, 0xc0, 0x36, 0x52, 0x33, 0xf0, 0xc3, 0x52, 0x44, + 0x2b, 0xd3, 0x13, 0x01, 0x90, 0x12, 0xc7, 0x0f, 0xd0, 0x0e, 0x83, 0x6c, 0x1e, 0x27, 0x91, 0xd4, + 0xd4, 0x3c, 0xa3, 0xc8, 0xed, 0x9d, 0xa7, 0x25, 0x44, 0xea, 0x3d, 0x7c, 0x88, 0x0c, 0x0e, 0xd9, + 0x34, 0x4e, 0x7c, 0x2e, 0xa8, 0x7d, 0x49, 0x7d, 0xbd, 0xc8, 0x6d, 0xe3, 0xbc, 0x85, 0x49, 0x97, + 0xe3, 0xfc, 0xae, 0xa0, 0xbd, 0xa5, 0xc3, 0xe3, 0x53, 0xa4, 0x5d, 0xd2, 0x4c, 0x98, 0x58, 0x0e, + 0x83, 0x71, 0x34, 0x5c, 0x6b, 0xdb, 0xe7, 0x25, 0xd1, 0xbb, 0x57, 0xdd, 0xaf, 0x56, 0x01, 0x8c, + 0x34, 0x1a, 0x95, 0x9e, 0xb8, 0x3a, 0x31, 0x2e, 0x1b, 0xf5, 0x04, 0x71, 0x49, 0x4f, 0x46, 0x92, + 0x46, 0xc3, 0xf9, 0x53, 0x41, 0xbb, 0x75, 0xc5, 0x67, 0x34, 0xe3, 0xa2, 0x05, 0xe4, 0xb0, 0x28, + 0x6d, 0x0b, 0xc8, 0x26, 0x91, 0x28, 0x7e, 0x8c, 0x34, 0x39, 0xf2, 0x01, 0xbd, 0x2e, 0xef, 0xc3, + 0x3b, 0x10, 0xc2, 0x67, 0x15, 0xf6, 0x32, 0xb7, 0xdf, 0x5c, 0x7d, 0xce, 0xdc, 0x7a, 0x9b, 0x34, + 0xc1, 0x22, 0x4d, 0x4a, 0x33, 0x2e, 0x5d, 0x55, 0xcb, 0x34, 0x22, 0x3d, 0x91, 0xa8, 0xb0, 0xde, + 0x4f, 0xd3, 0x3a, 0x4c, 0x4e, 0xa3, 0x5e, 0x5a, 0x7f, 0xdc, 0xc2, 0xa4, 0xcb, 0x71, 0xfe, 0xda, + 0x6a, 0xad, 0x7f, 0x7a, 0x1d, 0x07, 0x80, 0x7f, 0x40, 0x9a, 0x78, 0x19, 0x43, 0x9f, 0xfb, 0xf2, + 0x34, 0xc6, 0xd1, 0x07, 0x1d, 0xab, 0x9a, 0x07, 0xce, 0x4d, 0xaf, 0x22, 0x01, 0x30, 0x57, 0xb0, + 0xdb, 0x09, 0xff, 0x06, 0xb8, 0xdf, 0x3e, 0x2f, 0x2d, 0x46, 0x1a, 0x55, 0xfc, 0x08, 0x19, 0xd5, + 0x53, 0x76, 0xbe, 0x48, 0xa1, 0x2a, 0xd3, 0xa9, 0x42, 0x8c, 0xe3, 0x76, 0xeb, 0xe5, 0xf2, 0x92, + 0x74, 0xc3, 0x30, 0x41, 0x3a, 0x54, 0x85, 0xd7, 0x77, 0xfa, 0xf6, 0xc6, 0xd1, 0xf2, 0xee, 0x57, + 0x69, 0xf4, 0x1a, 0x61, 0xa4, 0x95, 0xc1, 0x5f, 0x22, 0x55, 0x18, 0xc9, 0xcc, 0xbe, 0xd4, 0x7b, + 0xb0, 0x51, 0x4f, 0x98, 0xef, 0xed, 0x55, 0x9a, 0xaa, 0x58, 0x31, 0x52, 0x4a, 0x38, 0x7f, 0x28, + 0xe8, 0xfe, 0x92, 0xb3, 0x5f, 0xc7, 0x8c, 0xe3, 0xef, 0x56, 0xdc, 0x75, 0x5f, 0xcd, 0x5d, 0x11, + 0x2d, 0xbd, 0x6d, 0xda, 0xb2, 0x46, 0x3a, 0xce, 0x7e, 0x85, 0xd4, 0x98, 0xc3, 0xb4, 0xf6, 0x63, + 0xf3, 0x53, 0x23, 0x0b, 0x6b, 0x0f, 0xf0, 0x85, 0x08, 0x26, 0xa5, 0x86, 0x73, 0x80, 0x76, 0xaa, + 0xce, 0xc7, 0xc3, 0xa5, 0xee, 0xde, 0xad, 0xe8, 0x9d, 0x0e, 0xaf, 0xc8, 0x62, 0xd8, 0x36, 0x93, + 0xbd, 0x4f, 0x6f, 0x6e, 0xad, 0xde, 0xf3, 0x5b, 0xab, 0xf7, 0xe2, 0xd6, 0xea, 0xfd, 0x5c, 0x58, + 0xca, 0x4d, 0x61, 0x29, 0xcf, 0x0b, 0x4b, 0x79, 0x51, 0x58, 0xca, 0xdf, 0x85, 0xa5, 0xfc, 0xf6, + 0x8f, 0xd5, 0x7b, 0xb6, 0xbf, 0xe6, 0x97, 0xe2, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf4, 0xfc, + 0xbe, 0xad, 0x6c, 0x08, 0x00, 0x00, } func (m *Endpoint) Marshal() (dAtA []byte, err error) { @@ -500,6 +531,20 @@ func (m *EndpointHints) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ForNodes) > 0 { + for iNdEx := len(m.ForNodes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ForNodes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } if len(m.ForZones) > 0 { for iNdEx := len(m.ForZones) - 1; iNdEx >= 0; iNdEx-- { { @@ -679,6 +724,34 @@ func (m *EndpointSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ForNode) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ForNode) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ForNode) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *ForZone) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -793,6 +866,12 @@ func (m *EndpointHints) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if len(m.ForNodes) > 0 { + for _, e := range m.ForNodes { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -862,6 +941,17 @@ func (m *EndpointSliceList) Size() (n int) { return n } +func (m *ForNode) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *ForZone) Size() (n int) { if m == nil { return 0 @@ -927,8 +1017,14 @@ func (this *EndpointHints) String() string { repeatedStringForForZones += strings.Replace(strings.Replace(f.String(), "ForZone", "ForZone", 1), `&`, ``, 1) + "," } repeatedStringForForZones += "}" + repeatedStringForForNodes := "[]ForNode{" + for _, f := range this.ForNodes { + repeatedStringForForNodes += strings.Replace(strings.Replace(f.String(), "ForNode", "ForNode", 1), `&`, ``, 1) + "," + } + repeatedStringForForNodes += "}" s := strings.Join([]string{`&EndpointHints{`, `ForZones:` + repeatedStringForForZones + `,`, + `ForNodes:` + repeatedStringForForNodes + `,`, `}`, }, "") return s @@ -985,6 +1081,16 @@ func (this *EndpointSliceList) String() string { }, "") return s } +func (this *ForNode) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ForNode{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `}`, + }, "") + return s +} func (this *ForZone) String() string { if this == nil { return "nil" @@ -1592,6 +1698,40 @@ func (m *EndpointHints) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ForNodes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ForNodes = append(m.ForNodes, ForNode{}) + if err := m.ForNodes[len(m.ForNodes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2082,6 +2222,88 @@ func (m *EndpointSliceList) Unmarshal(dAtA []byte) error { } return nil } +func (m *ForNode) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ForNode: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ForNode: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ForZone) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/generated.proto b/go-controller/vendor/k8s.io/api/discovery/v1/generated.proto index 8ddf0dc5d3..569d8a916e 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/discovery/v1/generated.proto @@ -31,12 +31,12 @@ option go_package = "k8s.io/api/discovery/v1"; // Endpoint represents a single logical "backend" implementing a service. message Endpoint { - // addresses of this endpoint. The contents of this field are interpreted - // according to the corresponding EndpointSlice addressType field. Consumers - // must handle different types of addresses in the context of their own - // capabilities. This must contain at least one address but no more than - // 100. These are all assumed to be fungible and clients may choose to only - // use the first element. Refer to: https://issue.k8s.io/106267 + // addresses of this endpoint. For EndpointSlices of addressType "IPv4" or "IPv6", + // the values are IP addresses in canonical form. The syntax and semantics of + // other addressType values are not defined. This must contain at least one + // address but no more than 100. EndpointSlices generated by the EndpointSlice + // controller will always have exactly 1 address. No semantics are defined for + // additional addresses beyond the first, and kube-proxy does not look at them. // +listType=set repeated string addresses = 1; @@ -82,36 +82,42 @@ message Endpoint { // EndpointConditions represents the current condition of an endpoint. message EndpointConditions { - // ready indicates that this endpoint is prepared to receive traffic, + // ready indicates that this endpoint is ready to receive traffic, // according to whatever system is managing the endpoint. A nil value - // indicates an unknown state. In most cases consumers should interpret this - // unknown state as ready. For compatibility reasons, ready should never be - // "true" for terminating endpoints, except when the normal readiness - // behavior is being explicitly overridden, for example when the associated - // Service has set the publishNotReadyAddresses flag. + // should be interpreted as "true". In general, an endpoint should be + // marked ready if it is serving and not terminating, though this can + // be overridden in some cases, such as when the associated Service has + // set the publishNotReadyAddresses flag. // +optional optional bool ready = 1; - // serving is identical to ready except that it is set regardless of the - // terminating state of endpoints. This condition should be set to true for - // a ready endpoint that is terminating. If nil, consumers should defer to - // the ready condition. + // serving indicates that this endpoint is able to receive traffic, + // according to whatever system is managing the endpoint. For endpoints + // backed by pods, the EndpointSlice controller will mark the endpoint + // as serving if the pod's Ready condition is True. A nil value should be + // interpreted as "true". // +optional optional bool serving = 2; // terminating indicates that this endpoint is terminating. A nil value - // indicates an unknown state. Consumers should interpret this unknown state - // to mean that the endpoint is not terminating. + // should be interpreted as "false". // +optional optional bool terminating = 3; } // EndpointHints provides hints describing how an endpoint should be consumed. message EndpointHints { - // forZones indicates the zone(s) this endpoint should be consumed by to - // enable topology aware routing. + // forZones indicates the zone(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. // +listType=atomic repeated ForZone forZones = 1; + + // forNodes indicates the node(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. + // This is an Alpha feature and is only used when the PreferSameTrafficDistribution + // feature gate is enabled. + // +listType=atomic + repeated ForNode forNodes = 2; } // EndpointPort represents a Port used by an EndpointSlice @@ -132,8 +138,9 @@ message EndpointPort { optional string protocol = 2; // port represents the port number of the endpoint. - // If this is not specified, ports are not restricted and must be - // interpreted in the context of the specific consumer. + // If the EndpointSlice is derived from a Kubernetes service, this must be set + // to the service's target port. EndpointSlices used for other purposes may have + // a nil port. optional int32 port = 3; // The application protocol for this port. @@ -155,9 +162,12 @@ message EndpointPort { optional string appProtocol = 4; } -// EndpointSlice represents a subset of the endpoints that implement a service. -// For a given service there may be multiple EndpointSlice objects, selected by -// labels, which must be joined to produce the full set of endpoints. +// EndpointSlice represents a set of service endpoints. Most EndpointSlices are created by +// the EndpointSlice controller to represent the Pods selected by Service objects. For a +// given service there may be multiple EndpointSlice objects which must be joined to +// produce the full set of endpoints; you can find all of the slices for a given service +// by listing EndpointSlices in the service's namespace whose `kubernetes.io/service-name` +// label contains the service's name. message EndpointSlice { // Standard object's metadata. // +optional @@ -169,7 +179,10 @@ message EndpointSlice { // supported: // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. - // * FQDN: Represents a Fully Qualified Domain Name. + // * FQDN: Represents a Fully Qualified Domain Name. (Deprecated) + // The EndpointSlice controller only generates, and kube-proxy only processes, + // slices of addressType "IPv4" and "IPv6". No semantics are defined for + // the "FQDN" type. optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may @@ -178,10 +191,11 @@ message EndpointSlice { repeated Endpoint endpoints = 2; // ports specifies the list of network ports exposed by each endpoint in - // this slice. Each port must have a unique name. When ports is empty, it - // indicates that there are no defined ports. When a port is defined with a - // nil port value, it indicates "all ports". Each slice may include a + // this slice. Each port must have a unique name. Each slice may include a // maximum of 100 ports. + // Services always have at least 1 port, so EndpointSlices generated by the + // EndpointSlice controller will likewise always have at least 1 port. + // EndpointSlices used for other purposes may have an empty ports list. // +optional // +listType=atomic repeated EndpointPort ports = 3; @@ -197,6 +211,12 @@ message EndpointSliceList { repeated EndpointSlice items = 2; } +// ForNode provides information about which nodes should consume this endpoint. +message ForNode { + // name represents the name of the node. + optional string name = 1; +} + // ForZone provides information about which zones should consume this endpoint. message ForZone { // name represents the name of the zone. diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/types.go b/go-controller/vendor/k8s.io/api/discovery/v1/types.go index d6a9d0fced..6f26953169 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/types.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1/types.go @@ -25,9 +25,12 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:prerelease-lifecycle-gen:introduced=1.21 -// EndpointSlice represents a subset of the endpoints that implement a service. -// For a given service there may be multiple EndpointSlice objects, selected by -// labels, which must be joined to produce the full set of endpoints. +// EndpointSlice represents a set of service endpoints. Most EndpointSlices are created by +// the EndpointSlice controller to represent the Pods selected by Service objects. For a +// given service there may be multiple EndpointSlice objects which must be joined to +// produce the full set of endpoints; you can find all of the slices for a given service +// by listing EndpointSlices in the service's namespace whose `kubernetes.io/service-name` +// label contains the service's name. type EndpointSlice struct { metav1.TypeMeta `json:",inline"` @@ -41,7 +44,10 @@ type EndpointSlice struct { // supported: // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. - // * FQDN: Represents a Fully Qualified Domain Name. + // * FQDN: Represents a Fully Qualified Domain Name. (Deprecated) + // The EndpointSlice controller only generates, and kube-proxy only processes, + // slices of addressType "IPv4" and "IPv6". No semantics are defined for + // the "FQDN" type. AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may @@ -50,10 +56,11 @@ type EndpointSlice struct { Endpoints []Endpoint `json:"endpoints" protobuf:"bytes,2,rep,name=endpoints"` // ports specifies the list of network ports exposed by each endpoint in - // this slice. Each port must have a unique name. When ports is empty, it - // indicates that there are no defined ports. When a port is defined with a - // nil port value, it indicates "all ports". Each slice may include a + // this slice. Each port must have a unique name. Each slice may include a // maximum of 100 ports. + // Services always have at least 1 port, so EndpointSlices generated by the + // EndpointSlice controller will likewise always have at least 1 port. + // EndpointSlices used for other purposes may have an empty ports list. // +optional // +listType=atomic Ports []EndpointPort `json:"ports" protobuf:"bytes,3,rep,name=ports"` @@ -76,12 +83,12 @@ const ( // Endpoint represents a single logical "backend" implementing a service. type Endpoint struct { - // addresses of this endpoint. The contents of this field are interpreted - // according to the corresponding EndpointSlice addressType field. Consumers - // must handle different types of addresses in the context of their own - // capabilities. This must contain at least one address but no more than - // 100. These are all assumed to be fungible and clients may choose to only - // use the first element. Refer to: https://issue.k8s.io/106267 + // addresses of this endpoint. For EndpointSlices of addressType "IPv4" or "IPv6", + // the values are IP addresses in canonical form. The syntax and semantics of + // other addressType values are not defined. This must contain at least one + // address but no more than 100. EndpointSlices generated by the EndpointSlice + // controller will always have exactly 1 address. No semantics are defined for + // additional addresses beyond the first, and kube-proxy does not look at them. // +listType=set Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"` @@ -127,36 +134,42 @@ type Endpoint struct { // EndpointConditions represents the current condition of an endpoint. type EndpointConditions struct { - // ready indicates that this endpoint is prepared to receive traffic, + // ready indicates that this endpoint is ready to receive traffic, // according to whatever system is managing the endpoint. A nil value - // indicates an unknown state. In most cases consumers should interpret this - // unknown state as ready. For compatibility reasons, ready should never be - // "true" for terminating endpoints, except when the normal readiness - // behavior is being explicitly overridden, for example when the associated - // Service has set the publishNotReadyAddresses flag. + // should be interpreted as "true". In general, an endpoint should be + // marked ready if it is serving and not terminating, though this can + // be overridden in some cases, such as when the associated Service has + // set the publishNotReadyAddresses flag. // +optional Ready *bool `json:"ready,omitempty" protobuf:"bytes,1,name=ready"` - // serving is identical to ready except that it is set regardless of the - // terminating state of endpoints. This condition should be set to true for - // a ready endpoint that is terminating. If nil, consumers should defer to - // the ready condition. + // serving indicates that this endpoint is able to receive traffic, + // according to whatever system is managing the endpoint. For endpoints + // backed by pods, the EndpointSlice controller will mark the endpoint + // as serving if the pod's Ready condition is True. A nil value should be + // interpreted as "true". // +optional Serving *bool `json:"serving,omitempty" protobuf:"bytes,2,name=serving"` // terminating indicates that this endpoint is terminating. A nil value - // indicates an unknown state. Consumers should interpret this unknown state - // to mean that the endpoint is not terminating. + // should be interpreted as "false". // +optional Terminating *bool `json:"terminating,omitempty" protobuf:"bytes,3,name=terminating"` } // EndpointHints provides hints describing how an endpoint should be consumed. type EndpointHints struct { - // forZones indicates the zone(s) this endpoint should be consumed by to - // enable topology aware routing. + // forZones indicates the zone(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. // +listType=atomic ForZones []ForZone `json:"forZones,omitempty" protobuf:"bytes,1,name=forZones"` + + // forNodes indicates the node(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. + // This is an Alpha feature and is only used when the PreferSameTrafficDistribution + // feature gate is enabled. + // +listType=atomic + ForNodes []ForNode `json:"forNodes,omitempty" protobuf:"bytes,2,name=forNodes"` } // ForZone provides information about which zones should consume this endpoint. @@ -165,6 +178,12 @@ type ForZone struct { Name string `json:"name" protobuf:"bytes,1,name=name"` } +// ForNode provides information about which nodes should consume this endpoint. +type ForNode struct { + // name represents the name of the node. + Name string `json:"name" protobuf:"bytes,1,name=name"` +} + // EndpointPort represents a Port used by an EndpointSlice // +structType=atomic type EndpointPort struct { @@ -183,8 +202,9 @@ type EndpointPort struct { Protocol *v1.Protocol `json:"protocol,omitempty" protobuf:"bytes,2,name=protocol"` // port represents the port number of the endpoint. - // If this is not specified, ports are not restricted and must be - // interpreted in the context of the specific consumer. + // If the EndpointSlice is derived from a Kubernetes service, this must be set + // to the service's target port. EndpointSlices used for other purposes may have + // a nil port. Port *int32 `json:"port,omitempty" protobuf:"bytes,3,opt,name=port"` // The application protocol for this port. diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/discovery/v1/types_swagger_doc_generated.go index 41c3060568..ac5b853b9e 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1/types_swagger_doc_generated.go @@ -29,7 +29,7 @@ package v1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_Endpoint = map[string]string{ "": "Endpoint represents a single logical \"backend\" implementing a service.", - "addresses": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. Consumers must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100. These are all assumed to be fungible and clients may choose to only use the first element. Refer to: https://issue.k8s.io/106267", + "addresses": "addresses of this endpoint. For EndpointSlices of addressType \"IPv4\" or \"IPv6\", the values are IP addresses in canonical form. The syntax and semantics of other addressType values are not defined. This must contain at least one address but no more than 100. EndpointSlices generated by the EndpointSlice controller will always have exactly 1 address. No semantics are defined for additional addresses beyond the first, and kube-proxy does not look at them.", "conditions": "conditions contains information about the current status of the endpoint.", "hostname": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must be lowercase and pass DNS Label (RFC 1123) validation.", "targetRef": "targetRef is a reference to a Kubernetes object that represents this endpoint.", @@ -45,9 +45,9 @@ func (Endpoint) SwaggerDoc() map[string]string { var map_EndpointConditions = map[string]string{ "": "EndpointConditions represents the current condition of an endpoint.", - "ready": "ready indicates that this endpoint is prepared to receive traffic, according to whatever system is managing the endpoint. A nil value indicates an unknown state. In most cases consumers should interpret this unknown state as ready. For compatibility reasons, ready should never be \"true\" for terminating endpoints, except when the normal readiness behavior is being explicitly overridden, for example when the associated Service has set the publishNotReadyAddresses flag.", - "serving": "serving is identical to ready except that it is set regardless of the terminating state of endpoints. This condition should be set to true for a ready endpoint that is terminating. If nil, consumers should defer to the ready condition.", - "terminating": "terminating indicates that this endpoint is terminating. A nil value indicates an unknown state. Consumers should interpret this unknown state to mean that the endpoint is not terminating.", + "ready": "ready indicates that this endpoint is ready to receive traffic, according to whatever system is managing the endpoint. A nil value should be interpreted as \"true\". In general, an endpoint should be marked ready if it is serving and not terminating, though this can be overridden in some cases, such as when the associated Service has set the publishNotReadyAddresses flag.", + "serving": "serving indicates that this endpoint is able to receive traffic, according to whatever system is managing the endpoint. For endpoints backed by pods, the EndpointSlice controller will mark the endpoint as serving if the pod's Ready condition is True. A nil value should be interpreted as \"true\".", + "terminating": "terminating indicates that this endpoint is terminating. A nil value should be interpreted as \"false\".", } func (EndpointConditions) SwaggerDoc() map[string]string { @@ -56,7 +56,8 @@ func (EndpointConditions) SwaggerDoc() map[string]string { var map_EndpointHints = map[string]string{ "": "EndpointHints provides hints describing how an endpoint should be consumed.", - "forZones": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing.", + "forZones": "forZones indicates the zone(s) this endpoint should be consumed by when using topology aware routing. May contain a maximum of 8 entries.", + "forNodes": "forNodes indicates the node(s) this endpoint should be consumed by when using topology aware routing. May contain a maximum of 8 entries. This is an Alpha feature and is only used when the PreferSameTrafficDistribution feature gate is enabled.", } func (EndpointHints) SwaggerDoc() map[string]string { @@ -67,7 +68,7 @@ var map_EndpointPort = map[string]string{ "": "EndpointPort represents a Port used by an EndpointSlice", "name": "name represents the name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is derived from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.", "protocol": "protocol represents the IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.", - "port": "port represents the port number of the endpoint. If this is not specified, ports are not restricted and must be interpreted in the context of the specific consumer.", + "port": "port represents the port number of the endpoint. If the EndpointSlice is derived from a Kubernetes service, this must be set to the service's target port. EndpointSlices used for other purposes may have a nil port.", "appProtocol": "The application protocol for this port. This is used as a hint for implementations to offer richer behavior for protocols that they understand. This field follows standard Kubernetes label syntax. Valid values are either:\n\n* Un-prefixed protocol names - reserved for IANA standard service names (as per RFC-6335 and https://www.iana.org/assignments/service-names).\n\n* Kubernetes-defined prefixed names:\n * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior-\n * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455\n * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455\n\n* Other protocols should use implementation-defined prefixed names such as mycompany.com/my-custom-protocol.", } @@ -76,11 +77,11 @@ func (EndpointPort) SwaggerDoc() map[string]string { } var map_EndpointSlice = map[string]string{ - "": "EndpointSlice represents a subset of the endpoints that implement a service. For a given service there may be multiple EndpointSlice objects, selected by labels, which must be joined to produce the full set of endpoints.", + "": "EndpointSlice represents a set of service endpoints. Most EndpointSlices are created by the EndpointSlice controller to represent the Pods selected by Service objects. For a given service there may be multiple EndpointSlice objects which must be joined to produce the full set of endpoints; you can find all of the slices for a given service by listing EndpointSlices in the service's namespace whose `kubernetes.io/service-name` label contains the service's name.", "metadata": "Standard object's metadata.", - "addressType": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name.", + "addressType": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name. (Deprecated) The EndpointSlice controller only generates, and kube-proxy only processes, slices of addressType \"IPv4\" and \"IPv6\". No semantics are defined for the \"FQDN\" type.", "endpoints": "endpoints is a list of unique endpoints in this slice. Each slice may include a maximum of 1000 endpoints.", - "ports": "ports specifies the list of network ports exposed by each endpoint in this slice. Each port must have a unique name. When ports is empty, it indicates that there are no defined ports. When a port is defined with a nil port value, it indicates \"all ports\". Each slice may include a maximum of 100 ports.", + "ports": "ports specifies the list of network ports exposed by each endpoint in this slice. Each port must have a unique name. Each slice may include a maximum of 100 ports. Services always have at least 1 port, so EndpointSlices generated by the EndpointSlice controller will likewise always have at least 1 port. EndpointSlices used for other purposes may have an empty ports list.", } func (EndpointSlice) SwaggerDoc() map[string]string { @@ -97,6 +98,15 @@ func (EndpointSliceList) SwaggerDoc() map[string]string { return map_EndpointSliceList } +var map_ForNode = map[string]string{ + "": "ForNode provides information about which nodes should consume this endpoint.", + "name": "name represents the name of the node.", +} + +func (ForNode) SwaggerDoc() map[string]string { + return map_ForNode +} + var map_ForZone = map[string]string{ "": "ForZone provides information about which zones should consume this endpoint.", "name": "name represents the name of the zone.", diff --git a/go-controller/vendor/k8s.io/api/discovery/v1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/discovery/v1/zz_generated.deepcopy.go index caa872af00..60eada3b9f 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1/zz_generated.deepcopy.go @@ -119,6 +119,11 @@ func (in *EndpointHints) DeepCopyInto(out *EndpointHints) { *out = make([]ForZone, len(*in)) copy(*out, *in) } + if in.ForNodes != nil { + in, out := &in.ForNodes, &out.ForNodes + *out = make([]ForNode, len(*in)) + copy(*out, *in) + } return } @@ -241,6 +246,22 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForNode) DeepCopyInto(out *ForNode) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForNode. +func (in *ForNode) DeepCopy() *ForNode { + if in == nil { + return nil + } + out := new(ForNode) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ForZone) DeepCopyInto(out *ForZone) { *out = *in diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/discovery/v1beta1/doc.go index 7d7084802d..f12087eff1 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=discovery.k8s.io -package v1beta1 // import "k8s.io/api/discovery/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.pb.go b/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.pb.go index 46935574bf..de32577864 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.pb.go @@ -214,10 +214,38 @@ func (m *EndpointSliceList) XXX_DiscardUnknown() { var xxx_messageInfo_EndpointSliceList proto.InternalMessageInfo +func (m *ForNode) Reset() { *m = ForNode{} } +func (*ForNode) ProtoMessage() {} +func (*ForNode) Descriptor() ([]byte, []int) { + return fileDescriptor_6555bad15de200e0, []int{6} +} +func (m *ForNode) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ForNode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ForNode) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForNode.Merge(m, src) +} +func (m *ForNode) XXX_Size() int { + return m.Size() +} +func (m *ForNode) XXX_DiscardUnknown() { + xxx_messageInfo_ForNode.DiscardUnknown(m) +} + +var xxx_messageInfo_ForNode proto.InternalMessageInfo + func (m *ForZone) Reset() { *m = ForZone{} } func (*ForZone) ProtoMessage() {} func (*ForZone) Descriptor() ([]byte, []int) { - return fileDescriptor_6555bad15de200e0, []int{6} + return fileDescriptor_6555bad15de200e0, []int{7} } func (m *ForZone) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -250,6 +278,7 @@ func init() { proto.RegisterType((*EndpointPort)(nil), "k8s.io.api.discovery.v1beta1.EndpointPort") proto.RegisterType((*EndpointSlice)(nil), "k8s.io.api.discovery.v1beta1.EndpointSlice") proto.RegisterType((*EndpointSliceList)(nil), "k8s.io.api.discovery.v1beta1.EndpointSliceList") + proto.RegisterType((*ForNode)(nil), "k8s.io.api.discovery.v1beta1.ForNode") proto.RegisterType((*ForZone)(nil), "k8s.io.api.discovery.v1beta1.ForZone") } @@ -258,61 +287,62 @@ func init() { } var fileDescriptor_6555bad15de200e0 = []byte{ - // 857 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0x4f, 0x6f, 0xe4, 0x34, - 0x14, 0x9f, 0x74, 0x1a, 0x9a, 0x78, 0x5a, 0xb1, 0x6b, 0x71, 0x18, 0x95, 0x2a, 0x19, 0x05, 0x2d, + // 877 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0x4f, 0x6f, 0xe4, 0x34, + 0x1c, 0x9d, 0x74, 0x1a, 0x9a, 0x78, 0x5a, 0xb1, 0x6b, 0x71, 0x18, 0x95, 0x2a, 0x19, 0x05, 0x2d, 0x1a, 0x51, 0x48, 0x68, 0xb5, 0x42, 0x2b, 0x38, 0x35, 0xb0, 0xb0, 0x48, 0xcb, 0x6e, 0xe5, 0x56, 0x42, 0x5a, 0x71, 0xc0, 0x93, 0xb8, 0x19, 0xd3, 0x26, 0x8e, 0x62, 0x77, 0xa4, 0xb9, 0xf1, 0x0d, - 0xe0, 0xb3, 0xf0, 0x15, 0x90, 0x50, 0x8f, 0x7b, 0xdc, 0x53, 0xc4, 0x84, 0x6f, 0xb1, 0x27, 0x64, - 0xc7, 0xf9, 0x33, 0x0c, 0x94, 0xb9, 0xc5, 0x3f, 0xbf, 0xdf, 0xef, 0xbd, 0xf7, 0x7b, 0xb6, 0x03, - 0x3e, 0xbe, 0x7e, 0xc2, 0x7d, 0xca, 0x02, 0x9c, 0xd3, 0x20, 0xa6, 0x3c, 0x62, 0x0b, 0x52, 0x2c, - 0x83, 0xc5, 0xc9, 0x8c, 0x08, 0x7c, 0x12, 0x24, 0x24, 0x23, 0x05, 0x16, 0x24, 0xf6, 0xf3, 0x82, - 0x09, 0x06, 0x8f, 0xea, 0x68, 0x1f, 0xe7, 0xd4, 0x6f, 0xa3, 0x7d, 0x1d, 0x7d, 0xf8, 0x49, 0x42, - 0xc5, 0xfc, 0x76, 0xe6, 0x47, 0x2c, 0x0d, 0x12, 0x96, 0xb0, 0x40, 0x91, 0x66, 0xb7, 0x57, 0x6a, - 0xa5, 0x16, 0xea, 0xab, 0x16, 0x3b, 0xf4, 0x7a, 0xa9, 0x23, 0x56, 0x90, 0x60, 0xb1, 0x91, 0xf0, - 0xf0, 0x71, 0x17, 0x93, 0xe2, 0x68, 0x4e, 0x33, 0x59, 0x5d, 0x7e, 0x9d, 0x48, 0x80, 0x07, 0x29, - 0x11, 0xf8, 0xdf, 0x58, 0xc1, 0x7f, 0xb1, 0x8a, 0xdb, 0x4c, 0xd0, 0x94, 0x6c, 0x10, 0x3e, 0xfb, - 0x3f, 0x02, 0x8f, 0xe6, 0x24, 0xc5, 0xff, 0xe4, 0x79, 0xbf, 0xed, 0x02, 0xeb, 0x69, 0x16, 0xe7, - 0x8c, 0x66, 0x02, 0x1e, 0x03, 0x1b, 0xc7, 0x71, 0x41, 0x38, 0x27, 0x7c, 0x6c, 0x4c, 0x86, 0x53, - 0x3b, 0x3c, 0xa8, 0x4a, 0xd7, 0x3e, 0x6b, 0x40, 0xd4, 0xed, 0xc3, 0x18, 0x80, 0x88, 0x65, 0x31, - 0x15, 0x94, 0x65, 0x7c, 0xbc, 0x33, 0x31, 0xa6, 0xa3, 0xd3, 0x4f, 0xfd, 0xfb, 0xec, 0xf5, 0x9b, - 0x44, 0x5f, 0xb6, 0xbc, 0x10, 0xde, 0x95, 0xee, 0xa0, 0x2a, 0x5d, 0xd0, 0x61, 0xa8, 0xa7, 0x0b, - 0xa7, 0xc0, 0x9a, 0x33, 0x2e, 0x32, 0x9c, 0x92, 0xf1, 0x70, 0x62, 0x4c, 0xed, 0x70, 0xbf, 0x2a, - 0x5d, 0xeb, 0x99, 0xc6, 0x50, 0xbb, 0x0b, 0xcf, 0x81, 0x2d, 0x70, 0x91, 0x10, 0x81, 0xc8, 0xd5, - 0x78, 0x57, 0x95, 0xf3, 0x41, 0xbf, 0x1c, 0x39, 0x20, 0x7f, 0x71, 0xe2, 0xbf, 0x9c, 0xfd, 0x44, - 0x22, 0x19, 0x44, 0x0a, 0x92, 0x45, 0xa4, 0xee, 0xf0, 0xb2, 0x61, 0xa2, 0x4e, 0x04, 0xce, 0x80, - 0x25, 0x58, 0xce, 0x6e, 0x58, 0xb2, 0x1c, 0x9b, 0x93, 0xe1, 0x74, 0x74, 0xfa, 0x78, 0xbb, 0xfe, - 0xfc, 0x4b, 0x4d, 0x7b, 0x9a, 0x89, 0x62, 0x19, 0x3e, 0xd0, 0x3d, 0x5a, 0x0d, 0x8c, 0x5a, 0x5d, - 0xd9, 0x5f, 0xc6, 0x62, 0xf2, 0x42, 0xf6, 0xf7, 0x4e, 0xd7, 0xdf, 0x0b, 0x8d, 0xa1, 0x76, 0x17, - 0x3e, 0x07, 0xe6, 0x9c, 0x66, 0x82, 0x8f, 0xf7, 0x54, 0x6f, 0xc7, 0xdb, 0x95, 0xf2, 0x4c, 0x52, - 0x42, 0xbb, 0x2a, 0x5d, 0x53, 0x7d, 0xa2, 0x5a, 0xe4, 0xf0, 0x0b, 0x70, 0xb0, 0x56, 0x24, 0x7c, - 0x00, 0x86, 0xd7, 0x64, 0x39, 0x36, 0x64, 0x0d, 0x48, 0x7e, 0xc2, 0xf7, 0x80, 0xb9, 0xc0, 0x37, - 0xb7, 0x44, 0xcd, 0xd6, 0x46, 0xf5, 0xe2, 0xf3, 0x9d, 0x27, 0x86, 0xf7, 0x8b, 0x01, 0xe0, 0xe6, - 0x2c, 0xa1, 0x0b, 0xcc, 0x82, 0xe0, 0xb8, 0x16, 0xb1, 0xea, 0xa4, 0x48, 0x02, 0xa8, 0xc6, 0xe1, - 0x23, 0xb0, 0xc7, 0x49, 0xb1, 0xa0, 0x59, 0xa2, 0x34, 0xad, 0x70, 0x54, 0x95, 0xee, 0xde, 0x45, - 0x0d, 0xa1, 0x66, 0x0f, 0x9e, 0x80, 0x91, 0x20, 0x45, 0x4a, 0x33, 0x2c, 0x64, 0xe8, 0x50, 0x85, - 0xbe, 0x5b, 0x95, 0xee, 0xe8, 0xb2, 0x83, 0x51, 0x3f, 0xc6, 0x8b, 0xc1, 0xc1, 0x5a, 0xc7, 0xf0, - 0x02, 0x58, 0x57, 0xac, 0x78, 0xc5, 0x32, 0x7d, 0x92, 0x47, 0xa7, 0x8f, 0xee, 0x37, 0xec, 0xeb, - 0x3a, 0xba, 0x1b, 0x96, 0x06, 0x38, 0x6a, 0x85, 0xbc, 0x3f, 0x0c, 0xb0, 0xdf, 0xa4, 0x39, 0x67, - 0x85, 0x80, 0x47, 0x60, 0x57, 0x9d, 0x4c, 0xe5, 0x5a, 0x68, 0x55, 0xa5, 0xbb, 0xab, 0xa6, 0xa6, - 0x50, 0xf8, 0x0d, 0xb0, 0xd4, 0x25, 0x8b, 0xd8, 0x4d, 0xed, 0x61, 0x78, 0x2c, 0x85, 0xcf, 0x35, - 0xf6, 0xb6, 0x74, 0xdf, 0xdf, 0x7c, 0x40, 0xfc, 0x66, 0x1b, 0xb5, 0x64, 0x99, 0x26, 0x67, 0x85, - 0x50, 0x4e, 0x98, 0x75, 0x1a, 0x99, 0x1e, 0x29, 0x54, 0xda, 0x85, 0xf3, 0xbc, 0xa1, 0xa9, 0xa3, - 0x6f, 0xd7, 0x76, 0x9d, 0x75, 0x30, 0xea, 0xc7, 0x78, 0xab, 0x9d, 0xce, 0xaf, 0x8b, 0x1b, 0x1a, - 0x11, 0xf8, 0x23, 0xb0, 0xe4, 0x5b, 0x14, 0x63, 0x81, 0x55, 0x37, 0xeb, 0x77, 0xb9, 0x7d, 0x52, - 0xfc, 0xfc, 0x3a, 0x91, 0x00, 0xf7, 0x65, 0x74, 0x77, 0x9d, 0xbe, 0x23, 0x02, 0x77, 0x77, 0xb9, - 0xc3, 0x50, 0xab, 0x0a, 0xbf, 0x02, 0x23, 0xfd, 0x78, 0x5c, 0x2e, 0x73, 0xa2, 0xcb, 0xf4, 0x34, - 0x65, 0x74, 0xd6, 0x6d, 0xbd, 0x5d, 0x5f, 0xa2, 0x3e, 0x0d, 0x7e, 0x0f, 0x6c, 0xa2, 0x0b, 0x97, - 0x8f, 0x8e, 0x1c, 0xec, 0x87, 0xdb, 0xdd, 0x84, 0xf0, 0xa1, 0xce, 0x65, 0x37, 0x08, 0x47, 0x9d, - 0x16, 0x7c, 0x09, 0x4c, 0xe9, 0x26, 0x1f, 0x0f, 0x95, 0xe8, 0x47, 0xdb, 0x89, 0xca, 0x31, 0x84, - 0x07, 0x5a, 0xd8, 0x94, 0x2b, 0x8e, 0x6a, 0x1d, 0xef, 0x77, 0x03, 0x3c, 0x5c, 0xf3, 0xf8, 0x39, - 0xe5, 0x02, 0xfe, 0xb0, 0xe1, 0xb3, 0xbf, 0x9d, 0xcf, 0x92, 0xad, 0x5c, 0x6e, 0x0f, 0x68, 0x83, - 0xf4, 0x3c, 0x3e, 0x07, 0x26, 0x15, 0x24, 0x6d, 0x9c, 0xd9, 0xf2, 0x8d, 0x50, 0xd5, 0x75, 0x5d, - 0x7c, 0x2b, 0x15, 0x50, 0x2d, 0xe4, 0x1d, 0x83, 0x3d, 0x7d, 0x11, 0xe0, 0x64, 0xed, 0xb0, 0xef, - 0xeb, 0xf0, 0xde, 0x81, 0x0f, 0xc3, 0xbb, 0x95, 0x33, 0x78, 0xbd, 0x72, 0x06, 0x6f, 0x56, 0xce, - 0xe0, 0xe7, 0xca, 0x31, 0xee, 0x2a, 0xc7, 0x78, 0x5d, 0x39, 0xc6, 0x9b, 0xca, 0x31, 0xfe, 0xac, - 0x1c, 0xe3, 0xd7, 0xbf, 0x9c, 0xc1, 0xab, 0xa3, 0xfb, 0x7e, 0xd8, 0x7f, 0x07, 0x00, 0x00, 0xff, - 0xff, 0x1c, 0xe6, 0x20, 0x06, 0xcf, 0x07, 0x00, 0x00, + 0xe0, 0xb3, 0x70, 0xe3, 0x8c, 0x84, 0x7a, 0xdc, 0xe3, 0x9e, 0x22, 0x1a, 0xbe, 0xc5, 0x9e, 0x90, + 0x1d, 0xe7, 0xcf, 0x30, 0xd0, 0xce, 0x2d, 0x7e, 0x7e, 0xef, 0xfd, 0xfe, 0xd9, 0x56, 0xc0, 0xc7, + 0x97, 0x4f, 0xb8, 0x4f, 0x59, 0x80, 0x73, 0x1a, 0xc4, 0x94, 0x47, 0x6c, 0x41, 0x8a, 0x65, 0xb0, + 0x38, 0x9a, 0x11, 0x81, 0x8f, 0x82, 0x84, 0x64, 0xa4, 0xc0, 0x82, 0xc4, 0x7e, 0x5e, 0x30, 0xc1, + 0xe0, 0x41, 0xcd, 0xf6, 0x71, 0x4e, 0xfd, 0x96, 0xed, 0x6b, 0xf6, 0xfe, 0x27, 0x09, 0x15, 0xf3, + 0xeb, 0x99, 0x1f, 0xb1, 0x34, 0x48, 0x58, 0xc2, 0x02, 0x25, 0x9a, 0x5d, 0x5f, 0xa8, 0x95, 0x5a, + 0xa8, 0xaf, 0xda, 0x6c, 0xdf, 0xeb, 0x85, 0x8e, 0x58, 0x41, 0x82, 0xc5, 0x5a, 0xc0, 0xfd, 0xc7, + 0x1d, 0x27, 0xc5, 0xd1, 0x9c, 0x66, 0x32, 0xbb, 0xfc, 0x32, 0x91, 0x00, 0x0f, 0x52, 0x22, 0xf0, + 0x7f, 0xa9, 0x82, 0xff, 0x53, 0x15, 0xd7, 0x99, 0xa0, 0x29, 0x59, 0x13, 0x7c, 0x76, 0x9f, 0x80, + 0x47, 0x73, 0x92, 0xe2, 0x7f, 0xeb, 0xbc, 0xdf, 0xb6, 0x81, 0xf5, 0x34, 0x8b, 0x73, 0x46, 0x33, + 0x01, 0x0f, 0x81, 0x8d, 0xe3, 0xb8, 0x20, 0x9c, 0x13, 0x3e, 0x36, 0x26, 0xc3, 0xa9, 0x1d, 0xee, + 0x55, 0xa5, 0x6b, 0x9f, 0x34, 0x20, 0xea, 0xf6, 0x61, 0x0c, 0x40, 0xc4, 0xb2, 0x98, 0x0a, 0xca, + 0x32, 0x3e, 0xde, 0x9a, 0x18, 0xd3, 0xd1, 0xf1, 0xa7, 0xfe, 0x5d, 0xed, 0xf5, 0x9b, 0x40, 0x5f, + 0xb6, 0xba, 0x10, 0xde, 0x94, 0xee, 0xa0, 0x2a, 0x5d, 0xd0, 0x61, 0xa8, 0xe7, 0x0b, 0xa7, 0xc0, + 0x9a, 0x33, 0x2e, 0x32, 0x9c, 0x92, 0xf1, 0x70, 0x62, 0x4c, 0xed, 0x70, 0xb7, 0x2a, 0x5d, 0xeb, + 0x99, 0xc6, 0x50, 0xbb, 0x0b, 0x4f, 0x81, 0x2d, 0x70, 0x91, 0x10, 0x81, 0xc8, 0xc5, 0x78, 0x5b, + 0xa5, 0xf3, 0x41, 0x3f, 0x1d, 0x39, 0x20, 0x7f, 0x71, 0xe4, 0xbf, 0x9c, 0xfd, 0x44, 0x22, 0x49, + 0x22, 0x05, 0xc9, 0x22, 0x52, 0x57, 0x78, 0xde, 0x28, 0x51, 0x67, 0x02, 0x67, 0xc0, 0x12, 0x2c, + 0x67, 0x57, 0x2c, 0x59, 0x8e, 0xcd, 0xc9, 0x70, 0x3a, 0x3a, 0x7e, 0xbc, 0x59, 0x7d, 0xfe, 0xb9, + 0x96, 0x3d, 0xcd, 0x44, 0xb1, 0x0c, 0x1f, 0xe8, 0x1a, 0xad, 0x06, 0x46, 0xad, 0xaf, 0xac, 0x2f, + 0x63, 0x31, 0x79, 0x21, 0xeb, 0x7b, 0xa7, 0xab, 0xef, 0x85, 0xc6, 0x50, 0xbb, 0x0b, 0x9f, 0x03, + 0x73, 0x4e, 0x33, 0xc1, 0xc7, 0x3b, 0xaa, 0xb6, 0xc3, 0xcd, 0x52, 0x79, 0x26, 0x25, 0xa1, 0x5d, + 0x95, 0xae, 0xa9, 0x3e, 0x51, 0x6d, 0xb2, 0xff, 0x05, 0xd8, 0x5b, 0x49, 0x12, 0x3e, 0x00, 0xc3, + 0x4b, 0xb2, 0x1c, 0x1b, 0x32, 0x07, 0x24, 0x3f, 0xe1, 0x7b, 0xc0, 0x5c, 0xe0, 0xab, 0x6b, 0xa2, + 0x66, 0x6b, 0xa3, 0x7a, 0xf1, 0xf9, 0xd6, 0x13, 0xc3, 0xfb, 0xc5, 0x00, 0x70, 0x7d, 0x96, 0xd0, + 0x05, 0x66, 0x41, 0x70, 0x5c, 0x9b, 0x58, 0x75, 0x50, 0x24, 0x01, 0x54, 0xe3, 0xf0, 0x11, 0xd8, + 0xe1, 0xa4, 0x58, 0xd0, 0x2c, 0x51, 0x9e, 0x56, 0x38, 0xaa, 0x4a, 0x77, 0xe7, 0xac, 0x86, 0x50, + 0xb3, 0x07, 0x8f, 0xc0, 0x48, 0x90, 0x22, 0xa5, 0x19, 0x16, 0x92, 0x3a, 0x54, 0xd4, 0x77, 0xab, + 0xd2, 0x1d, 0x9d, 0x77, 0x30, 0xea, 0x73, 0xbc, 0xdf, 0x0d, 0xb0, 0xb7, 0x52, 0x32, 0x3c, 0x03, + 0xd6, 0x05, 0x2b, 0x5e, 0xb1, 0x4c, 0x1f, 0xe5, 0xd1, 0xf1, 0xa3, 0xbb, 0x3b, 0xf6, 0x75, 0xcd, + 0xee, 0xa6, 0xa5, 0x01, 0x8e, 0x5a, 0x23, 0x6d, 0x2a, 0x87, 0x23, 0x4f, 0xfc, 0x66, 0xa6, 0x92, + 0xbd, 0x62, 0xaa, 0xe4, 0xa8, 0x35, 0xf2, 0xfe, 0x34, 0xc0, 0x6e, 0x93, 0xfb, 0x29, 0x2b, 0x04, + 0x3c, 0x00, 0xdb, 0xea, 0xbc, 0xab, 0x59, 0x84, 0x56, 0x55, 0xba, 0xdb, 0xea, 0x2c, 0x28, 0x14, + 0x7e, 0x03, 0x2c, 0x75, 0x75, 0x23, 0x76, 0x55, 0x4f, 0x26, 0x3c, 0x94, 0xc6, 0xa7, 0x1a, 0x7b, + 0x5b, 0xba, 0xef, 0xaf, 0x3f, 0x4b, 0x7e, 0xb3, 0x8d, 0x5a, 0xb1, 0x0c, 0x93, 0xb3, 0x42, 0xa8, + 0xfe, 0x9a, 0x75, 0x18, 0x19, 0x1e, 0x29, 0x54, 0x0e, 0x01, 0xe7, 0x79, 0x23, 0x53, 0x17, 0xca, + 0xae, 0x87, 0x70, 0xd2, 0xc1, 0xa8, 0xcf, 0xf1, 0x6e, 0xb7, 0xba, 0x21, 0x9c, 0x5d, 0xd1, 0x88, + 0xc0, 0x1f, 0x81, 0x25, 0x5f, 0xb8, 0x18, 0x0b, 0xac, 0xaa, 0x59, 0x7d, 0x21, 0xda, 0x87, 0xca, + 0xcf, 0x2f, 0x13, 0x09, 0x70, 0x5f, 0xb2, 0xbb, 0x4b, 0xfa, 0x1d, 0x11, 0xb8, 0x7b, 0x21, 0x3a, + 0x0c, 0xb5, 0xae, 0xf0, 0x2b, 0x30, 0xd2, 0x4f, 0xd2, 0xf9, 0x32, 0x27, 0x3a, 0x4d, 0x4f, 0x4b, + 0x46, 0x27, 0xdd, 0xd6, 0xdb, 0xd5, 0x25, 0xea, 0xcb, 0xe0, 0xf7, 0xc0, 0x26, 0x3a, 0xf1, 0x66, + 0xb0, 0x1f, 0x6e, 0x76, 0xbf, 0xc2, 0x87, 0x3a, 0x96, 0xdd, 0x20, 0x1c, 0x75, 0x5e, 0xf0, 0x25, + 0x30, 0x65, 0x37, 0xf9, 0x78, 0xa8, 0x4c, 0x3f, 0xda, 0xcc, 0x54, 0x8e, 0x21, 0xdc, 0xd3, 0xc6, + 0xa6, 0x5c, 0x71, 0x54, 0xfb, 0x78, 0x7f, 0x18, 0xe0, 0xe1, 0x4a, 0x8f, 0x9f, 0x53, 0x2e, 0xe0, + 0x0f, 0x6b, 0x7d, 0xf6, 0x37, 0xeb, 0xb3, 0x54, 0xab, 0x2e, 0xb7, 0x07, 0xb4, 0x41, 0x7a, 0x3d, + 0x3e, 0x05, 0x26, 0x15, 0x24, 0x6d, 0x3a, 0xb3, 0xe1, 0xcb, 0xa3, 0xb2, 0xeb, 0xaa, 0xf8, 0x56, + 0x3a, 0xa0, 0xda, 0xc8, 0x3b, 0x04, 0x3b, 0xfa, 0x22, 0xc0, 0xc9, 0xca, 0x61, 0xdf, 0xd5, 0xf4, + 0xde, 0x81, 0xd7, 0x64, 0x79, 0x01, 0xef, 0x27, 0x87, 0xe1, 0xcd, 0xad, 0x33, 0x78, 0x7d, 0xeb, + 0x0c, 0xde, 0xdc, 0x3a, 0x83, 0x9f, 0x2b, 0xc7, 0xb8, 0xa9, 0x1c, 0xe3, 0x75, 0xe5, 0x18, 0x6f, + 0x2a, 0xc7, 0xf8, 0xab, 0x72, 0x8c, 0x5f, 0xff, 0x76, 0x06, 0xaf, 0x0e, 0xee, 0xfa, 0x67, 0xf8, + 0x27, 0x00, 0x00, 0xff, 0xff, 0x76, 0x8e, 0x48, 0x7e, 0x52, 0x08, 0x00, 0x00, } func (m *Endpoint) Marshal() (dAtA []byte, err error) { @@ -492,6 +522,20 @@ func (m *EndpointHints) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ForNodes) > 0 { + for iNdEx := len(m.ForNodes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ForNodes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } if len(m.ForZones) > 0 { for iNdEx := len(m.ForZones) - 1; iNdEx >= 0; iNdEx-- { { @@ -671,6 +715,34 @@ func (m *EndpointSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ForNode) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ForNode) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ForNode) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *ForZone) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -781,6 +853,12 @@ func (m *EndpointHints) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if len(m.ForNodes) > 0 { + for _, e := range m.ForNodes { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -850,6 +928,17 @@ func (m *EndpointSliceList) Size() (n int) { return n } +func (m *ForNode) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *ForZone) Size() (n int) { if m == nil { return 0 @@ -914,8 +1003,14 @@ func (this *EndpointHints) String() string { repeatedStringForForZones += strings.Replace(strings.Replace(f.String(), "ForZone", "ForZone", 1), `&`, ``, 1) + "," } repeatedStringForForZones += "}" + repeatedStringForForNodes := "[]ForNode{" + for _, f := range this.ForNodes { + repeatedStringForForNodes += strings.Replace(strings.Replace(f.String(), "ForNode", "ForNode", 1), `&`, ``, 1) + "," + } + repeatedStringForForNodes += "}" s := strings.Join([]string{`&EndpointHints{`, `ForZones:` + repeatedStringForForZones + `,`, + `ForNodes:` + repeatedStringForForNodes + `,`, `}`, }, "") return s @@ -972,6 +1067,16 @@ func (this *EndpointSliceList) String() string { }, "") return s } +func (this *ForNode) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ForNode{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `}`, + }, "") + return s +} func (this *ForZone) String() string { if this == nil { return "nil" @@ -1546,6 +1651,40 @@ func (m *EndpointHints) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ForNodes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ForNodes = append(m.ForNodes, ForNode{}) + if err := m.ForNodes[len(m.ForNodes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2036,6 +2175,88 @@ func (m *EndpointSliceList) Unmarshal(dAtA []byte) error { } return nil } +func (m *ForNode) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ForNode: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ForNode: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ForZone) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.proto index 55828dd97d..907050da1c 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/generated.proto @@ -114,6 +114,13 @@ message EndpointHints { // enable topology aware routing. May contain a maximum of 8 entries. // +listType=atomic repeated ForZone forZones = 1; + + // forNodes indicates the node(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. + // This is an Alpha feature and is only used when the PreferSameTrafficDistribution + // feature gate is enabled. + // +listType=atomic + repeated ForNode forNodes = 2; } // EndpointPort represents a Port used by an EndpointSlice @@ -189,6 +196,12 @@ message EndpointSliceList { repeated EndpointSlice items = 2; } +// ForNode provides information about which nodes should consume this endpoint. +message ForNode { + // name represents the name of the node. + optional string name = 1; +} + // ForZone provides information about which zones should consume this endpoint. message ForZone { // name represents the name of the zone. diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/types.go b/go-controller/vendor/k8s.io/api/discovery/v1beta1/types.go index defd8e2ce6..fa9d1eae43 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/types.go @@ -161,6 +161,13 @@ type EndpointHints struct { // enable topology aware routing. May contain a maximum of 8 entries. // +listType=atomic ForZones []ForZone `json:"forZones,omitempty" protobuf:"bytes,1,name=forZones"` + + // forNodes indicates the node(s) this endpoint should be consumed by when + // using topology aware routing. May contain a maximum of 8 entries. + // This is an Alpha feature and is only used when the PreferSameTrafficDistribution + // feature gate is enabled. + // +listType=atomic + ForNodes []ForNode `json:"forNodes,omitempty" protobuf:"bytes,2,name=forNodes"` } // ForZone provides information about which zones should consume this endpoint. @@ -169,6 +176,12 @@ type ForZone struct { Name string `json:"name" protobuf:"bytes,1,name=name"` } +// ForNode provides information about which nodes should consume this endpoint. +type ForNode struct { + // name represents the name of the node. + Name string `json:"name" protobuf:"bytes,1,name=name"` +} + // EndpointPort represents a Port used by an EndpointSlice type EndpointPort struct { // name represents the name of this port. All ports in an EndpointSlice must have a unique name. diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/discovery/v1beta1/types_swagger_doc_generated.go index 847d4d58e0..72aa0cb9b2 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/types_swagger_doc_generated.go @@ -56,6 +56,7 @@ func (EndpointConditions) SwaggerDoc() map[string]string { var map_EndpointHints = map[string]string{ "": "EndpointHints provides hints describing how an endpoint should be consumed.", "forZones": "forZones indicates the zone(s) this endpoint should be consumed by to enable topology aware routing. May contain a maximum of 8 entries.", + "forNodes": "forNodes indicates the node(s) this endpoint should be consumed by when using topology aware routing. May contain a maximum of 8 entries. This is an Alpha feature and is only used when the PreferSameTrafficDistribution feature gate is enabled.", } func (EndpointHints) SwaggerDoc() map[string]string { @@ -96,6 +97,15 @@ func (EndpointSliceList) SwaggerDoc() map[string]string { return map_EndpointSliceList } +var map_ForNode = map[string]string{ + "": "ForNode provides information about which nodes should consume this endpoint.", + "name": "name represents the name of the node.", +} + +func (ForNode) SwaggerDoc() map[string]string { + return map_ForNode +} + var map_ForZone = map[string]string{ "": "ForZone provides information about which zones should consume this endpoint.", "name": "name represents the name of the zone.", diff --git a/go-controller/vendor/k8s.io/api/discovery/v1beta1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/discovery/v1beta1/zz_generated.deepcopy.go index 13b9544b0c..72490d6adf 100644 --- a/go-controller/vendor/k8s.io/api/discovery/v1beta1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/discovery/v1beta1/zz_generated.deepcopy.go @@ -114,6 +114,11 @@ func (in *EndpointHints) DeepCopyInto(out *EndpointHints) { *out = make([]ForZone, len(*in)) copy(*out, *in) } + if in.ForNodes != nil { + in, out := &in.ForNodes, &out.ForNodes + *out = make([]ForNode, len(*in)) + copy(*out, *in) + } return } @@ -236,6 +241,22 @@ func (in *EndpointSliceList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForNode) DeepCopyInto(out *ForNode) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForNode. +func (in *ForNode) DeepCopy() *ForNode { + if in == nil { + return nil + } + out := new(ForNode) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ForZone) DeepCopyInto(out *ForZone) { *out = *in diff --git a/go-controller/vendor/k8s.io/api/events/v1/doc.go b/go-controller/vendor/k8s.io/api/events/v1/doc.go index 5fe700ffcf..911639044f 100644 --- a/go-controller/vendor/k8s.io/api/events/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/events/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=events.k8s.io -package v1 // import "k8s.io/api/events/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/events/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/events/v1beta1/doc.go index 46048a65b4..e4864294fd 100644 --- a/go-controller/vendor/k8s.io/api/events/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/events/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=events.k8s.io -package v1beta1 // import "k8s.io/api/events/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/extensions/v1beta1/doc.go index c9af49d55c..7770fab5d2 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/doc.go @@ -19,4 +19,4 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:prerelease-lifecycle-gen=true -package v1beta1 // import "k8s.io/api/extensions/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.pb.go b/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.pb.go index 818486f39d..35b9a4ff2a 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.pb.go @@ -1364,185 +1364,187 @@ func init() { } var fileDescriptor_90a532284de28347 = []byte{ - // 2842 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcd, 0x6f, 0x24, 0x47, - 0x15, 0xdf, 0x9e, 0xf1, 0xd8, 0xe3, 0xe7, 0xb5, 0xbd, 0x5b, 0xeb, 0xac, 0x1d, 0x2f, 0xb1, 0xa3, - 0x46, 0x84, 0x4d, 0xd8, 0x9d, 0x61, 0x37, 0xc9, 0x92, 0x0f, 0x29, 0x61, 0xc7, 0xbb, 0xc9, 0x3a, - 0xb1, 0xc7, 0x93, 0x9a, 0x71, 0x82, 0x22, 0x02, 0xb4, 0x7b, 0xca, 0xe3, 0x8e, 0x7b, 0xba, 0x47, - 0xdd, 0x35, 0x66, 0x7d, 0x03, 0xc1, 0x25, 0x27, 0xb8, 0x04, 0x38, 0x22, 0x21, 0x71, 0xe5, 0xca, - 0x21, 0x44, 0x20, 0x82, 0xb4, 0x42, 0x1c, 0x22, 0x71, 0x20, 0x27, 0x8b, 0x38, 0x27, 0xc4, 0x3f, - 0x80, 0xf6, 0x84, 0xea, 0xa3, 0xab, 0xbf, 0xed, 0x1e, 0xe3, 0x58, 0x04, 0x71, 0x5a, 0x4f, 0xbd, - 0xf7, 0x7e, 0xf5, 0xaa, 0xea, 0xd5, 0x7b, 0xbf, 0xaa, 0xea, 0x85, 0xeb, 0xbb, 0xcf, 0xf9, 0x35, - 0xcb, 0xad, 0x1b, 0x03, 0xab, 0x4e, 0xee, 0x53, 0xe2, 0xf8, 0x96, 0xeb, 0xf8, 0xf5, 0xbd, 0x1b, - 0x5b, 0x84, 0x1a, 0x37, 0xea, 0x3d, 0xe2, 0x10, 0xcf, 0xa0, 0xa4, 0x5b, 0x1b, 0x78, 0x2e, 0x75, - 0xd1, 0x63, 0x42, 0xbd, 0x66, 0x0c, 0xac, 0x5a, 0xa8, 0x5e, 0x93, 0xea, 0x8b, 0xd7, 0x7b, 0x16, - 0xdd, 0x19, 0x6e, 0xd5, 0x4c, 0xb7, 0x5f, 0xef, 0xb9, 0x3d, 0xb7, 0xce, 0xad, 0xb6, 0x86, 0xdb, - 0xfc, 0x17, 0xff, 0xc1, 0xff, 0x12, 0x68, 0x8b, 0x7a, 0xa4, 0x73, 0xd3, 0xf5, 0x48, 0x7d, 0x2f, - 0xd5, 0xe3, 0xe2, 0x33, 0xa1, 0x4e, 0xdf, 0x30, 0x77, 0x2c, 0x87, 0x78, 0xfb, 0xf5, 0xc1, 0x6e, - 0x8f, 0x35, 0xf8, 0xf5, 0x3e, 0xa1, 0x46, 0x96, 0x55, 0x3d, 0xcf, 0xca, 0x1b, 0x3a, 0xd4, 0xea, - 0x93, 0x94, 0xc1, 0xad, 0xe3, 0x0c, 0x7c, 0x73, 0x87, 0xf4, 0x8d, 0x94, 0xdd, 0xd3, 0x79, 0x76, - 0x43, 0x6a, 0xd9, 0x75, 0xcb, 0xa1, 0x3e, 0xf5, 0x92, 0x46, 0xfa, 0xfb, 0x25, 0x98, 0xbc, 0x63, - 0x90, 0xbe, 0xeb, 0xb4, 0x09, 0x45, 0xdf, 0x83, 0x2a, 0x1b, 0x46, 0xd7, 0xa0, 0xc6, 0x82, 0xf6, - 0xb8, 0x76, 0x75, 0xea, 0xe6, 0xd7, 0x6b, 0xe1, 0x34, 0x2b, 0xd4, 0xda, 0x60, 0xb7, 0xc7, 0x1a, - 0xfc, 0x1a, 0xd3, 0xae, 0xed, 0xdd, 0xa8, 0x6d, 0x6c, 0xbd, 0x4b, 0x4c, 0xba, 0x4e, 0xa8, 0xd1, - 0x40, 0x0f, 0x0e, 0x96, 0xcf, 0x1d, 0x1e, 0x2c, 0x43, 0xd8, 0x86, 0x15, 0x2a, 0x6a, 0xc2, 0x98, - 0x3f, 0x20, 0xe6, 0x42, 0x89, 0xa3, 0x5f, 0xab, 0x1d, 0xb9, 0x88, 0x35, 0xe5, 0x59, 0x7b, 0x40, - 0xcc, 0xc6, 0x79, 0x89, 0x3c, 0xc6, 0x7e, 0x61, 0x8e, 0x83, 0xde, 0x84, 0x71, 0x9f, 0x1a, 0x74, - 0xe8, 0x2f, 0x94, 0x39, 0x62, 0xad, 0x30, 0x22, 0xb7, 0x6a, 0xcc, 0x48, 0xcc, 0x71, 0xf1, 0x1b, - 0x4b, 0x34, 0xfd, 0x1f, 0x25, 0x40, 0x4a, 0x77, 0xc5, 0x75, 0xba, 0x16, 0xb5, 0x5c, 0x07, 0xbd, - 0x00, 0x63, 0x74, 0x7f, 0x40, 0xf8, 0xe4, 0x4c, 0x36, 0x9e, 0x08, 0x1c, 0xea, 0xec, 0x0f, 0xc8, - 0xc3, 0x83, 0xe5, 0xcb, 0x69, 0x0b, 0x26, 0xc1, 0xdc, 0x06, 0xad, 0x29, 0x57, 0x4b, 0xdc, 0xfa, - 0x99, 0x78, 0xd7, 0x0f, 0x0f, 0x96, 0x33, 0x82, 0xb0, 0xa6, 0x90, 0xe2, 0x0e, 0xa2, 0x3d, 0x40, - 0xb6, 0xe1, 0xd3, 0x8e, 0x67, 0x38, 0xbe, 0xe8, 0xc9, 0xea, 0x13, 0x39, 0x09, 0x4f, 0x15, 0x5b, - 0x34, 0x66, 0xd1, 0x58, 0x94, 0x5e, 0xa0, 0xb5, 0x14, 0x1a, 0xce, 0xe8, 0x01, 0x3d, 0x01, 0xe3, - 0x1e, 0x31, 0x7c, 0xd7, 0x59, 0x18, 0xe3, 0xa3, 0x50, 0x13, 0x88, 0x79, 0x2b, 0x96, 0x52, 0xf4, - 0x24, 0x4c, 0xf4, 0x89, 0xef, 0x1b, 0x3d, 0xb2, 0x50, 0xe1, 0x8a, 0xb3, 0x52, 0x71, 0x62, 0x5d, - 0x34, 0xe3, 0x40, 0xae, 0x7f, 0xa0, 0xc1, 0xb4, 0x9a, 0xb9, 0x35, 0xcb, 0xa7, 0xe8, 0xdb, 0xa9, - 0x38, 0xac, 0x15, 0x1b, 0x12, 0xb3, 0xe6, 0x51, 0x78, 0x41, 0xf6, 0x56, 0x0d, 0x5a, 0x22, 0x31, - 0xb8, 0x0e, 0x15, 0x8b, 0x92, 0x3e, 0x5b, 0x87, 0xf2, 0xd5, 0xa9, 0x9b, 0x57, 0x8b, 0x86, 0x4c, - 0x63, 0x5a, 0x82, 0x56, 0x56, 0x99, 0x39, 0x16, 0x28, 0xfa, 0xcf, 0xc6, 0x22, 0xee, 0xb3, 0xd0, - 0x44, 0xef, 0x40, 0xd5, 0x27, 0x36, 0x31, 0xa9, 0xeb, 0x49, 0xf7, 0x9f, 0x2e, 0xe8, 0xbe, 0xb1, - 0x45, 0xec, 0xb6, 0x34, 0x6d, 0x9c, 0x67, 0xfe, 0x07, 0xbf, 0xb0, 0x82, 0x44, 0x6f, 0x40, 0x95, - 0x92, 0xfe, 0xc0, 0x36, 0x28, 0x91, 0xfb, 0xe8, 0xcb, 0xd1, 0x21, 0xb0, 0xc8, 0x61, 0x60, 0x2d, - 0xb7, 0xdb, 0x91, 0x6a, 0x7c, 0xfb, 0xa8, 0x29, 0x09, 0x5a, 0xb1, 0x82, 0x41, 0x7b, 0x30, 0x33, - 0x1c, 0x74, 0x99, 0x26, 0x65, 0xd9, 0xa1, 0xb7, 0x2f, 0x23, 0xe9, 0x56, 0xd1, 0xb9, 0xd9, 0x8c, - 0x59, 0x37, 0x2e, 0xcb, 0xbe, 0x66, 0xe2, 0xed, 0x38, 0xd1, 0x0b, 0xba, 0x0d, 0xb3, 0x7d, 0xcb, - 0xc1, 0xc4, 0xe8, 0xee, 0xb7, 0x89, 0xe9, 0x3a, 0x5d, 0x9f, 0x87, 0x55, 0xa5, 0x31, 0x2f, 0x01, - 0x66, 0xd7, 0xe3, 0x62, 0x9c, 0xd4, 0x47, 0xaf, 0x01, 0x0a, 0x86, 0xf1, 0xaa, 0x48, 0x6e, 0x96, - 0xeb, 0xf0, 0x98, 0x2b, 0x87, 0xc1, 0xdd, 0x49, 0x69, 0xe0, 0x0c, 0x2b, 0xb4, 0x06, 0x73, 0x1e, - 0xd9, 0xb3, 0xd8, 0x18, 0xef, 0x59, 0x3e, 0x75, 0xbd, 0xfd, 0x35, 0xab, 0x6f, 0xd1, 0x85, 0x71, - 0xee, 0xd3, 0xc2, 0xe1, 0xc1, 0xf2, 0x1c, 0xce, 0x90, 0xe3, 0x4c, 0x2b, 0xfd, 0xe7, 0xe3, 0x30, - 0x9b, 0xc8, 0x37, 0xe8, 0x4d, 0xb8, 0x6c, 0x0e, 0x3d, 0x8f, 0x38, 0xb4, 0x39, 0xec, 0x6f, 0x11, - 0xaf, 0x6d, 0xee, 0x90, 0xee, 0xd0, 0x26, 0x5d, 0x1e, 0x28, 0x95, 0xc6, 0x92, 0xf4, 0xf8, 0xf2, - 0x4a, 0xa6, 0x16, 0xce, 0xb1, 0x66, 0xb3, 0xe0, 0xf0, 0xa6, 0x75, 0xcb, 0xf7, 0x15, 0x66, 0x89, - 0x63, 0xaa, 0x59, 0x68, 0xa6, 0x34, 0x70, 0x86, 0x15, 0xf3, 0xb1, 0x4b, 0x7c, 0xcb, 0x23, 0xdd, - 0xa4, 0x8f, 0xe5, 0xb8, 0x8f, 0x77, 0x32, 0xb5, 0x70, 0x8e, 0x35, 0x7a, 0x16, 0xa6, 0x44, 0x6f, - 0x7c, 0xfd, 0xe4, 0x42, 0x5f, 0x92, 0x60, 0x53, 0xcd, 0x50, 0x84, 0xa3, 0x7a, 0x6c, 0x68, 0xee, - 0x96, 0x4f, 0xbc, 0x3d, 0xd2, 0xcd, 0x5f, 0xe0, 0x8d, 0x94, 0x06, 0xce, 0xb0, 0x62, 0x43, 0x13, - 0x11, 0x98, 0x1a, 0xda, 0x78, 0x7c, 0x68, 0x9b, 0x99, 0x5a, 0x38, 0xc7, 0x9a, 0xc5, 0xb1, 0x70, - 0xf9, 0xf6, 0x9e, 0x61, 0xd9, 0xc6, 0x96, 0x4d, 0x16, 0x26, 0xe2, 0x71, 0xdc, 0x8c, 0x8b, 0x71, - 0x52, 0x1f, 0xbd, 0x0a, 0x17, 0x45, 0xd3, 0xa6, 0x63, 0x28, 0x90, 0x2a, 0x07, 0x79, 0x54, 0x82, - 0x5c, 0x6c, 0x26, 0x15, 0x70, 0xda, 0x06, 0xbd, 0x00, 0x33, 0xa6, 0x6b, 0xdb, 0x3c, 0x1e, 0x57, - 0xdc, 0xa1, 0x43, 0x17, 0x26, 0x39, 0x0a, 0x62, 0xfb, 0x71, 0x25, 0x26, 0xc1, 0x09, 0x4d, 0x44, - 0x00, 0xcc, 0xa0, 0xe0, 0xf8, 0x0b, 0xc0, 0xf3, 0xe3, 0x8d, 0xa2, 0x39, 0x40, 0x95, 0xaa, 0x90, - 0x03, 0xa8, 0x26, 0x1f, 0x47, 0x80, 0xf5, 0x3f, 0x6b, 0x30, 0x9f, 0x93, 0x3a, 0xd0, 0xcb, 0xb1, - 0x12, 0xfb, 0xb5, 0x44, 0x89, 0xbd, 0x92, 0x63, 0x16, 0xa9, 0xb3, 0x0e, 0x4c, 0x7b, 0x6c, 0x54, - 0x4e, 0x4f, 0xa8, 0xc8, 0x1c, 0xf9, 0xec, 0x31, 0xc3, 0xc0, 0x51, 0x9b, 0x30, 0xe7, 0x5f, 0x3c, - 0x3c, 0x58, 0x9e, 0x8e, 0xc9, 0x70, 0x1c, 0x5e, 0xff, 0x45, 0x09, 0xe0, 0x0e, 0x19, 0xd8, 0xee, - 0x7e, 0x9f, 0x38, 0x67, 0xc1, 0xa1, 0x36, 0x62, 0x1c, 0xea, 0xfa, 0x71, 0xcb, 0xa3, 0x5c, 0xcb, - 0x25, 0x51, 0x6f, 0x25, 0x48, 0x54, 0xbd, 0x38, 0xe4, 0xd1, 0x2c, 0xea, 0x6f, 0x65, 0xb8, 0x14, - 0x2a, 0x87, 0x34, 0xea, 0xc5, 0xd8, 0x1a, 0x7f, 0x35, 0xb1, 0xc6, 0xf3, 0x19, 0x26, 0x9f, 0x1b, - 0x8f, 0x7a, 0x17, 0x66, 0x18, 0xcb, 0x11, 0x6b, 0xc9, 0x39, 0xd4, 0xf8, 0xc8, 0x1c, 0x4a, 0x55, - 0xbb, 0xb5, 0x18, 0x12, 0x4e, 0x20, 0xe7, 0x70, 0xb6, 0x89, 0x2f, 0x22, 0x67, 0xfb, 0x50, 0x83, - 0x99, 0x70, 0x99, 0xce, 0x80, 0xb4, 0x35, 0xe3, 0xa4, 0xed, 0xc9, 0xc2, 0x21, 0x9a, 0xc3, 0xda, - 0xfe, 0xc5, 0x08, 0xbe, 0x52, 0x62, 0x1b, 0x7c, 0xcb, 0x30, 0x77, 0xd1, 0xe3, 0x30, 0xe6, 0x18, - 0xfd, 0x20, 0x32, 0xd5, 0x66, 0x69, 0x1a, 0x7d, 0x82, 0xb9, 0x04, 0xbd, 0xaf, 0x01, 0x92, 0x55, - 0xe0, 0xb6, 0xe3, 0xb8, 0xd4, 0x10, 0xb9, 0x52, 0xb8, 0xb5, 0x5a, 0xd8, 0xad, 0xa0, 0xc7, 0xda, - 0x66, 0x0a, 0xeb, 0xae, 0x43, 0xbd, 0xfd, 0x70, 0x91, 0xd3, 0x0a, 0x38, 0xc3, 0x01, 0x64, 0x00, - 0x78, 0x12, 0xb3, 0xe3, 0xca, 0x8d, 0x7c, 0xbd, 0x40, 0xce, 0x63, 0x06, 0x2b, 0xae, 0xb3, 0x6d, - 0xf5, 0xc2, 0xb4, 0x83, 0x15, 0x10, 0x8e, 0x80, 0x2e, 0xde, 0x85, 0xf9, 0x1c, 0x6f, 0xd1, 0x05, - 0x28, 0xef, 0x92, 0x7d, 0x31, 0x6d, 0x98, 0xfd, 0x89, 0xe6, 0xa0, 0xb2, 0x67, 0xd8, 0x43, 0x91, - 0x7e, 0x27, 0xb1, 0xf8, 0xf1, 0x42, 0xe9, 0x39, 0x4d, 0xff, 0xa0, 0x12, 0x8d, 0x1d, 0xce, 0x98, - 0xaf, 0x42, 0xd5, 0x23, 0x03, 0xdb, 0x32, 0x0d, 0x5f, 0x12, 0x21, 0x4e, 0x7e, 0xb1, 0x6c, 0xc3, - 0x4a, 0x1a, 0xe3, 0xd6, 0xa5, 0xcf, 0x97, 0x5b, 0x97, 0x4f, 0x87, 0x5b, 0x7f, 0x17, 0xaa, 0x7e, - 0xc0, 0xaa, 0xc7, 0x38, 0xe4, 0x8d, 0x11, 0xf2, 0xab, 0x24, 0xd4, 0xaa, 0x03, 0x45, 0xa5, 0x15, - 0x68, 0x16, 0x89, 0xae, 0x8c, 0x48, 0xa2, 0x4f, 0x95, 0xf8, 0xb2, 0x7c, 0x33, 0x30, 0x86, 0x3e, - 0xe9, 0xf2, 0xdc, 0x56, 0x0d, 0xf3, 0x4d, 0x8b, 0xb7, 0x62, 0x29, 0x45, 0xef, 0xc4, 0x42, 0xb6, - 0x7a, 0x92, 0x90, 0x9d, 0xc9, 0x0f, 0x57, 0xb4, 0x09, 0xf3, 0x03, 0xcf, 0xed, 0x79, 0xc4, 0xf7, - 0xef, 0x10, 0xa3, 0x6b, 0x5b, 0x0e, 0x09, 0xe6, 0x47, 0x30, 0xa2, 0x2b, 0x87, 0x07, 0xcb, 0xf3, - 0xad, 0x6c, 0x15, 0x9c, 0x67, 0xab, 0x3f, 0x18, 0x83, 0x0b, 0xc9, 0x0a, 0x98, 0x43, 0x52, 0xb5, - 0x13, 0x91, 0xd4, 0x6b, 0x91, 0xcd, 0x20, 0x18, 0xbc, 0x5a, 0xfd, 0x8c, 0x0d, 0x71, 0x1b, 0x66, - 0x65, 0x36, 0x08, 0x84, 0x92, 0xa6, 0xab, 0xd5, 0xdf, 0x8c, 0x8b, 0x71, 0x52, 0x1f, 0xbd, 0x08, - 0xd3, 0x1e, 0xe7, 0xdd, 0x01, 0x80, 0xe0, 0xae, 0x8f, 0x48, 0x80, 0x69, 0x1c, 0x15, 0xe2, 0xb8, - 0x2e, 0xe3, 0xad, 0x21, 0x1d, 0x0d, 0x00, 0xc6, 0xe2, 0xbc, 0xf5, 0x76, 0x52, 0x01, 0xa7, 0x6d, - 0xd0, 0x3a, 0x5c, 0x1a, 0x3a, 0x69, 0x28, 0x11, 0xca, 0x57, 0x24, 0xd4, 0xa5, 0xcd, 0xb4, 0x0a, - 0xce, 0xb2, 0x43, 0xdb, 0x31, 0x2a, 0x3b, 0xce, 0xd3, 0xf3, 0xcd, 0xc2, 0x1b, 0xaf, 0x30, 0x97, - 0xcd, 0xa0, 0xdb, 0xd5, 0xa2, 0x74, 0x5b, 0xff, 0x83, 0x16, 0x2d, 0x42, 0x8a, 0x02, 0x1f, 0x77, - 0xcb, 0x94, 0xb2, 0x88, 0xb0, 0x23, 0x37, 0x9b, 0xfd, 0xde, 0x1a, 0x89, 0xfd, 0x86, 0xc5, 0xf3, - 0x78, 0xfa, 0xfb, 0x47, 0x0d, 0x66, 0xef, 0x75, 0x3a, 0xad, 0x55, 0x87, 0xef, 0x96, 0x96, 0x41, - 0x77, 0x58, 0x15, 0x1d, 0x18, 0x74, 0x27, 0x59, 0x45, 0x99, 0x0c, 0x73, 0x09, 0x7a, 0x06, 0xaa, - 0xec, 0x5f, 0xe6, 0x38, 0x0f, 0xd7, 0x49, 0x9e, 0x64, 0xaa, 0x2d, 0xd9, 0xf6, 0x30, 0xf2, 0x37, - 0x56, 0x9a, 0xe8, 0x5b, 0x30, 0xc1, 0xf6, 0x36, 0x71, 0xba, 0x05, 0xc9, 0xaf, 0x74, 0xaa, 0x21, - 0x8c, 0x42, 0x3e, 0x23, 0x1b, 0x70, 0x00, 0xa7, 0xef, 0xc2, 0x5c, 0x64, 0x10, 0x78, 0x68, 0x93, - 0x37, 0x59, 0xbd, 0x42, 0x6d, 0xa8, 0xb0, 0xde, 0x59, 0x55, 0x2a, 0x17, 0xb8, 0x5e, 0x4c, 0x4c, - 0x44, 0xc8, 0x3d, 0xd8, 0x2f, 0x1f, 0x0b, 0x2c, 0x7d, 0x03, 0x26, 0x56, 0x5b, 0x0d, 0xdb, 0x15, - 0x7c, 0xc3, 0xb4, 0xba, 0x5e, 0x72, 0xa6, 0x56, 0x56, 0xef, 0x60, 0xcc, 0x25, 0x48, 0x87, 0x71, - 0x72, 0xdf, 0x24, 0x03, 0xca, 0x29, 0xc6, 0x64, 0x03, 0x58, 0x22, 0xbd, 0xcb, 0x5b, 0xb0, 0x94, - 0xe8, 0x3f, 0x29, 0xc1, 0x84, 0xec, 0xf6, 0x0c, 0xce, 0x1f, 0x6b, 0xb1, 0xf3, 0xc7, 0x53, 0xc5, - 0x96, 0x20, 0xf7, 0xf0, 0xd1, 0x49, 0x1c, 0x3e, 0xae, 0x15, 0xc4, 0x3b, 0xfa, 0xe4, 0xf1, 0x5e, - 0x09, 0x66, 0xe2, 0x8b, 0x8f, 0x9e, 0x85, 0x29, 0x96, 0x6a, 0x2d, 0x93, 0x34, 0x43, 0x86, 0xa7, - 0xae, 0x1f, 0xda, 0xa1, 0x08, 0x47, 0xf5, 0x50, 0x4f, 0x99, 0xb5, 0x5c, 0x8f, 0xca, 0x41, 0xe7, - 0x4f, 0xe9, 0x90, 0x5a, 0x76, 0x4d, 0x5c, 0xb6, 0xd7, 0x56, 0x1d, 0xba, 0xe1, 0xb5, 0xa9, 0x67, - 0x39, 0xbd, 0x54, 0x47, 0x0c, 0x0c, 0x47, 0x91, 0xd1, 0x5b, 0x2c, 0xed, 0xfb, 0xee, 0xd0, 0x33, - 0x49, 0x16, 0x7d, 0x0b, 0xa8, 0x07, 0xdb, 0x08, 0xdd, 0x35, 0xd7, 0x34, 0x6c, 0xb1, 0x38, 0x98, - 0x6c, 0x13, 0x8f, 0x38, 0x26, 0x09, 0x28, 0x93, 0x80, 0xc0, 0x0a, 0x4c, 0xff, 0xad, 0x06, 0x53, - 0x72, 0x2e, 0xce, 0x80, 0xa8, 0xbf, 0x1e, 0x27, 0xea, 0x4f, 0x14, 0xdc, 0xa1, 0xd9, 0x2c, 0xfd, - 0x77, 0x1a, 0x2c, 0x06, 0xae, 0xbb, 0x46, 0xb7, 0x61, 0xd8, 0x86, 0x63, 0x12, 0x2f, 0x88, 0xf5, - 0x45, 0x28, 0x59, 0x03, 0xb9, 0x92, 0x20, 0x01, 0x4a, 0xab, 0x2d, 0x5c, 0xb2, 0x06, 0xac, 0x8a, - 0xee, 0xb8, 0x3e, 0xe5, 0x6c, 0x5e, 0x1c, 0x14, 0x95, 0xd7, 0xf7, 0x64, 0x3b, 0x56, 0x1a, 0x68, - 0x13, 0x2a, 0x03, 0xd7, 0xa3, 0xac, 0x72, 0x95, 0x13, 0xeb, 0x7b, 0x84, 0xd7, 0x6c, 0xdd, 0x64, - 0x20, 0x86, 0x3b, 0x9d, 0xc1, 0x60, 0x81, 0xa6, 0xff, 0x50, 0x83, 0x47, 0x33, 0xfc, 0x97, 0xa4, - 0xa1, 0x0b, 0x13, 0x96, 0x10, 0xca, 0xf4, 0xf2, 0x7c, 0xb1, 0x6e, 0x33, 0xa6, 0x22, 0x4c, 0x6d, - 0x41, 0x0a, 0x0b, 0xa0, 0xf5, 0x5f, 0x69, 0x70, 0x31, 0xe5, 0x2f, 0x4f, 0xd1, 0x2c, 0x9e, 0x25, - 0xdb, 0x56, 0x29, 0x9a, 0x85, 0x25, 0x97, 0xa0, 0xd7, 0xa1, 0xca, 0xdf, 0x88, 0x4c, 0xd7, 0x96, - 0x13, 0x58, 0x0f, 0x26, 0xb0, 0x25, 0xdb, 0x1f, 0x1e, 0x2c, 0x5f, 0xc9, 0x38, 0x6b, 0x07, 0x62, - 0xac, 0x00, 0xd0, 0x32, 0x54, 0x88, 0xe7, 0xb9, 0x9e, 0x4c, 0xf6, 0x93, 0x6c, 0xa6, 0xee, 0xb2, - 0x06, 0x2c, 0xda, 0xf5, 0x5f, 0x87, 0x41, 0xca, 0xb2, 0x2f, 0xf3, 0x8f, 0x2d, 0x4e, 0x32, 0x31, - 0xb2, 0xa5, 0xc3, 0x5c, 0x82, 0x86, 0x70, 0xc1, 0x4a, 0xa4, 0x6b, 0xb9, 0x3b, 0xeb, 0xc5, 0xa6, - 0x51, 0x99, 0x35, 0x16, 0x24, 0xfc, 0x85, 0xa4, 0x04, 0xa7, 0xba, 0xd0, 0x09, 0xa4, 0xb4, 0xd0, - 0x1b, 0x30, 0xb6, 0x43, 0xe9, 0x20, 0xe3, 0xb2, 0xff, 0x98, 0x22, 0x11, 0xba, 0x50, 0xe5, 0xa3, - 0xeb, 0x74, 0x5a, 0x98, 0x43, 0xe9, 0xbf, 0x2f, 0xa9, 0xf9, 0xe0, 0x27, 0xa4, 0x6f, 0xaa, 0xd1, - 0xae, 0xd8, 0x86, 0xef, 0xf3, 0x14, 0x26, 0x4e, 0xf3, 0x73, 0x11, 0xc7, 0x95, 0x0c, 0xa7, 0xb4, - 0x51, 0x27, 0x2c, 0x9e, 0xda, 0x49, 0x8a, 0xe7, 0x54, 0x56, 0xe1, 0x44, 0xf7, 0xa0, 0x4c, 0xed, - 0xa2, 0xa7, 0x72, 0x89, 0xd8, 0x59, 0x6b, 0x37, 0xa6, 0xe4, 0x94, 0x97, 0x3b, 0x6b, 0x6d, 0xcc, - 0x20, 0xd0, 0x06, 0x54, 0xbc, 0xa1, 0x4d, 0x58, 0x1d, 0x28, 0x17, 0xaf, 0x2b, 0x6c, 0x06, 0xc3, - 0xcd, 0xc7, 0x7e, 0xf9, 0x58, 0xe0, 0xe8, 0x3f, 0xd2, 0x60, 0x3a, 0x56, 0x2d, 0x90, 0x07, 0xe7, - 0xed, 0xc8, 0xde, 0x91, 0xf3, 0xf0, 0xdc, 0xe8, 0xbb, 0x4e, 0x6e, 0xfa, 0x39, 0xd9, 0xef, 0xf9, - 0xa8, 0x0c, 0xc7, 0xfa, 0xd0, 0x0d, 0x80, 0x70, 0xd8, 0x6c, 0x1f, 0xb0, 0xe0, 0x15, 0x1b, 0x5e, - 0xee, 0x03, 0x16, 0xd3, 0x3e, 0x16, 0xed, 0xe8, 0x26, 0x80, 0x4f, 0x4c, 0x8f, 0xd0, 0x66, 0x98, - 0xb8, 0x54, 0x39, 0x6e, 0x2b, 0x09, 0x8e, 0x68, 0xe9, 0x7f, 0xd2, 0x60, 0xba, 0x49, 0xe8, 0xf7, - 0x5d, 0x6f, 0xb7, 0xe5, 0xda, 0x96, 0xb9, 0x7f, 0x06, 0x24, 0x00, 0xc7, 0x48, 0xc0, 0x71, 0xf9, - 0x32, 0xe6, 0x5d, 0x1e, 0x15, 0xd0, 0x3f, 0xd4, 0x60, 0x3e, 0xa6, 0x79, 0x37, 0xcc, 0x07, 0x2a, - 0x41, 0x6b, 0x85, 0x12, 0x74, 0x0c, 0x86, 0x25, 0xb5, 0xec, 0x04, 0x8d, 0xd6, 0xa0, 0x44, 0x5d, - 0x19, 0xbd, 0xa3, 0x61, 0x12, 0xe2, 0x85, 0x35, 0xa7, 0xe3, 0xe2, 0x12, 0x75, 0xd9, 0x42, 0x2c, - 0xc4, 0xb4, 0xa2, 0x19, 0xed, 0x73, 0x1a, 0x01, 0x86, 0xb1, 0x6d, 0xcf, 0xed, 0x9f, 0x78, 0x0c, - 0x6a, 0x21, 0x5e, 0xf1, 0xdc, 0x3e, 0xe6, 0x58, 0xfa, 0x47, 0x1a, 0x5c, 0x8c, 0x69, 0x9e, 0x01, - 0x6f, 0x78, 0x23, 0xce, 0x1b, 0xae, 0x8d, 0x32, 0x90, 0x1c, 0xf6, 0xf0, 0x51, 0x29, 0x31, 0x0c, - 0x36, 0x60, 0xb4, 0x0d, 0x53, 0x03, 0xb7, 0xdb, 0x3e, 0x85, 0x07, 0xda, 0x59, 0xc6, 0xe7, 0x5a, - 0x21, 0x16, 0x8e, 0x02, 0xa3, 0xfb, 0x70, 0x91, 0x51, 0x0b, 0x7f, 0x60, 0x98, 0xa4, 0x7d, 0x0a, - 0x57, 0x56, 0x8f, 0xf0, 0x17, 0xa0, 0x24, 0x22, 0x4e, 0x77, 0x82, 0xd6, 0x61, 0xc2, 0x1a, 0xf0, - 0xf3, 0x85, 0x24, 0x92, 0xc7, 0x92, 0x30, 0x71, 0x1a, 0x11, 0x29, 0x5e, 0xfe, 0xc0, 0x01, 0x86, - 0xfe, 0xd7, 0x64, 0x34, 0x70, 0xba, 0xfa, 0x6a, 0x84, 0x1e, 0xc8, 0xb7, 0x9a, 0x93, 0x51, 0x83, - 0xa6, 0x64, 0x22, 0x27, 0x65, 0xd6, 0xd5, 0x04, 0x6f, 0xf9, 0x0a, 0x4c, 0x10, 0xa7, 0xcb, 0xc9, - 0xba, 0xb8, 0x08, 0xe1, 0xa3, 0xba, 0x2b, 0x9a, 0x70, 0x20, 0xd3, 0x7f, 0x5c, 0x4e, 0x8c, 0x8a, - 0x97, 0xd9, 0x77, 0x4f, 0x2d, 0x38, 0x14, 0xe1, 0xcf, 0x0d, 0x90, 0xad, 0x90, 0xfe, 0x89, 0x98, - 0xff, 0xc6, 0x28, 0x31, 0x1f, 0xad, 0x7f, 0xb9, 0xe4, 0x0f, 0x7d, 0x07, 0xc6, 0x89, 0xe8, 0x42, - 0x54, 0xd5, 0x5b, 0xa3, 0x74, 0x11, 0xa6, 0xdf, 0xf0, 0x9c, 0x25, 0xdb, 0x24, 0x2a, 0x7a, 0x99, - 0xcd, 0x17, 0xd3, 0x65, 0xc7, 0x12, 0xc1, 0x9e, 0x27, 0x1b, 0x8f, 0x89, 0x61, 0xab, 0xe6, 0x87, - 0x07, 0xcb, 0x10, 0xfe, 0xc4, 0x51, 0x0b, 0xfe, 0x7a, 0x26, 0xef, 0x6c, 0xce, 0xe6, 0x0b, 0xa4, - 0xd1, 0x5e, 0xcf, 0x42, 0xd7, 0x4e, 0xed, 0xf5, 0x2c, 0x02, 0x79, 0xf4, 0x19, 0xf6, 0x9f, 0x25, - 0xb8, 0x14, 0x2a, 0x17, 0x7e, 0x3d, 0xcb, 0x30, 0xf9, 0xff, 0x57, 0x48, 0xc5, 0x5e, 0xb4, 0xc2, - 0xa9, 0xfb, 0xef, 0x7b, 0xd1, 0x0a, 0x7d, 0xcb, 0xa9, 0x76, 0xbf, 0x29, 0x45, 0x07, 0x30, 0xe2, - 0xb3, 0xca, 0x29, 0x7c, 0x88, 0xf3, 0x85, 0x7b, 0x99, 0xd1, 0xff, 0x52, 0x86, 0x0b, 0xc9, 0xdd, - 0x18, 0xbb, 0x7d, 0xd7, 0x8e, 0xbd, 0x7d, 0x6f, 0xc1, 0xdc, 0xf6, 0xd0, 0xb6, 0xf7, 0xf9, 0x18, - 0x22, 0x57, 0xf0, 0xe2, 0xde, 0xfe, 0x4b, 0xd2, 0x72, 0xee, 0x95, 0x0c, 0x1d, 0x9c, 0x69, 0x99, - 0xbe, 0x8c, 0x1f, 0xfb, 0x4f, 0x2f, 0xe3, 0x2b, 0x27, 0xb8, 0x8c, 0xcf, 0x7e, 0xcf, 0x28, 0x9f, - 0xe8, 0x3d, 0xe3, 0x24, 0x37, 0xf1, 0x19, 0x49, 0xec, 0xd8, 0xaf, 0x4a, 0x5e, 0x82, 0x99, 0xf8, - 0xeb, 0x90, 0x58, 0x4b, 0xf1, 0x40, 0x25, 0xdf, 0x62, 0x22, 0x6b, 0x29, 0xda, 0xb1, 0xd2, 0xd0, - 0x0f, 0x35, 0xb8, 0x9c, 0xfd, 0x15, 0x08, 0xb2, 0x61, 0xa6, 0x6f, 0xdc, 0x8f, 0x7e, 0x99, 0xa3, - 0x9d, 0x90, 0xad, 0xf0, 0x67, 0x81, 0xf5, 0x18, 0x16, 0x4e, 0x60, 0xa3, 0xb7, 0xa1, 0xda, 0x37, - 0xee, 0xb7, 0x87, 0x5e, 0x8f, 0x9c, 0x98, 0x15, 0xf1, 0x6d, 0xb4, 0x2e, 0x51, 0xb0, 0xc2, 0xd3, - 0x3f, 0xd3, 0x60, 0x3e, 0xe7, 0xb2, 0xff, 0x7f, 0x68, 0x94, 0xef, 0x95, 0xa0, 0xd2, 0x36, 0x0d, - 0x9b, 0x9c, 0x01, 0xa1, 0x78, 0x2d, 0x46, 0x28, 0x8e, 0xfb, 0x9a, 0x94, 0x7b, 0x95, 0xcb, 0x25, - 0x70, 0x82, 0x4b, 0x3c, 0x55, 0x08, 0xed, 0x68, 0x1a, 0xf1, 0x3c, 0x4c, 0xaa, 0x4e, 0x47, 0xcb, - 0x6e, 0xfa, 0x2f, 0x4b, 0x30, 0x15, 0xe9, 0x62, 0xc4, 0xdc, 0xb8, 0x1d, 0x2b, 0x08, 0xe5, 0x02, - 0x37, 0x2d, 0x91, 0xbe, 0x6a, 0x41, 0x09, 0x10, 0x5f, 0x43, 0x84, 0xef, 0xdf, 0xe9, 0xca, 0xf0, - 0x12, 0xcc, 0x50, 0xc3, 0xeb, 0x11, 0xaa, 0x68, 0xbb, 0xb8, 0x64, 0x54, 0x9f, 0xe5, 0x74, 0x62, - 0x52, 0x9c, 0xd0, 0x5e, 0x7c, 0x11, 0xa6, 0x63, 0x9d, 0x8d, 0xf2, 0x31, 0x43, 0x63, 0xe5, 0xc1, - 0xa7, 0x4b, 0xe7, 0x3e, 0xfe, 0x74, 0xe9, 0xdc, 0x27, 0x9f, 0x2e, 0x9d, 0xfb, 0xc1, 0xe1, 0x92, - 0xf6, 0xe0, 0x70, 0x49, 0xfb, 0xf8, 0x70, 0x49, 0xfb, 0xe4, 0x70, 0x49, 0xfb, 0xfb, 0xe1, 0x92, - 0xf6, 0xd3, 0xcf, 0x96, 0xce, 0xbd, 0xfd, 0xd8, 0x91, 0xff, 0xb7, 0xe1, 0xdf, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x5f, 0xd8, 0x14, 0x50, 0xfb, 0x30, 0x00, 0x00, + // 2875 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcf, 0x6f, 0x24, 0x47, + 0xf5, 0xdf, 0x9e, 0xf1, 0xd8, 0xe3, 0xe7, 0xb5, 0xbd, 0x5b, 0xeb, 0xac, 0x1d, 0xef, 0x37, 0x76, + 0xd4, 0x5f, 0x11, 0x36, 0x61, 0x77, 0x86, 0xdd, 0x24, 0x4b, 0x7e, 0x48, 0x09, 0x3b, 0xde, 0x4d, + 0xd6, 0x89, 0x7f, 0x4c, 0x6a, 0xc6, 0x09, 0x8a, 0x08, 0xd0, 0xee, 0x29, 0x8f, 0x3b, 0xee, 0xe9, + 0x1e, 0x75, 0xd7, 0x98, 0xf5, 0x0d, 0x04, 0x97, 0x9c, 0x40, 0x42, 0x21, 0x1c, 0x91, 0x90, 0xb8, + 0x72, 0xe5, 0x10, 0x22, 0x10, 0x41, 0x8a, 0x38, 0x45, 0xe2, 0x40, 0x4e, 0x16, 0x71, 0x4e, 0x88, + 0x7f, 0x00, 0xed, 0x09, 0xd5, 0x8f, 0xae, 0xfe, 0x6d, 0xf7, 0x0c, 0x5e, 0x8b, 0x20, 0x4e, 0xeb, + 0xa9, 0xf7, 0xde, 0xa7, 0x5e, 0x55, 0xbd, 0x7a, 0xef, 0x53, 0x55, 0xbd, 0x70, 0x7d, 0xef, 0x39, + 0xbf, 0x66, 0xb9, 0x75, 0xa3, 0x6f, 0xd5, 0xc9, 0x7d, 0x4a, 0x1c, 0xdf, 0x72, 0x1d, 0xbf, 0xbe, + 0x7f, 0x63, 0x9b, 0x50, 0xe3, 0x46, 0xbd, 0x4b, 0x1c, 0xe2, 0x19, 0x94, 0x74, 0x6a, 0x7d, 0xcf, + 0xa5, 0x2e, 0x7a, 0x4c, 0xa8, 0xd7, 0x8c, 0xbe, 0x55, 0x0b, 0xd5, 0x6b, 0x52, 0x7d, 0xf1, 0x7a, + 0xd7, 0xa2, 0xbb, 0x83, 0xed, 0x9a, 0xe9, 0xf6, 0xea, 0x5d, 0xb7, 0xeb, 0xd6, 0xb9, 0xd5, 0xf6, + 0x60, 0x87, 0xff, 0xe2, 0x3f, 0xf8, 0x5f, 0x02, 0x6d, 0x51, 0x8f, 0x74, 0x6e, 0xba, 0x1e, 0xa9, + 0xef, 0xa7, 0x7a, 0x5c, 0x7c, 0x26, 0xd4, 0xe9, 0x19, 0xe6, 0xae, 0xe5, 0x10, 0xef, 0xa0, 0xde, + 0xdf, 0xeb, 0xb2, 0x06, 0xbf, 0xde, 0x23, 0xd4, 0xc8, 0xb2, 0xaa, 0xe7, 0x59, 0x79, 0x03, 0x87, + 0x5a, 0x3d, 0x92, 0x32, 0xb8, 0x75, 0x92, 0x81, 0x6f, 0xee, 0x92, 0x9e, 0x91, 0xb2, 0x7b, 0x3a, + 0xcf, 0x6e, 0x40, 0x2d, 0xbb, 0x6e, 0x39, 0xd4, 0xa7, 0x5e, 0xd2, 0x48, 0x7f, 0xbf, 0x04, 0x93, + 0x77, 0x0c, 0xd2, 0x73, 0x9d, 0x16, 0xa1, 0xe8, 0x7b, 0x50, 0x65, 0xc3, 0xe8, 0x18, 0xd4, 0x58, + 0xd0, 0x1e, 0xd7, 0xae, 0x4e, 0xdd, 0xfc, 0x7a, 0x2d, 0x9c, 0x66, 0x85, 0x5a, 0xeb, 0xef, 0x75, + 0x59, 0x83, 0x5f, 0x63, 0xda, 0xb5, 0xfd, 0x1b, 0xb5, 0xcd, 0xed, 0x77, 0x89, 0x49, 0xd7, 0x09, + 0x35, 0x1a, 0xe8, 0x93, 0xc3, 0xe5, 0x73, 0x47, 0x87, 0xcb, 0x10, 0xb6, 0x61, 0x85, 0x8a, 0x36, + 0x60, 0xcc, 0xef, 0x13, 0x73, 0xa1, 0xc4, 0xd1, 0xaf, 0xd5, 0x8e, 0x5d, 0xc4, 0x9a, 0xf2, 0xac, + 0xd5, 0x27, 0x66, 0xe3, 0xbc, 0x44, 0x1e, 0x63, 0xbf, 0x30, 0xc7, 0x41, 0x6f, 0xc2, 0xb8, 0x4f, + 0x0d, 0x3a, 0xf0, 0x17, 0xca, 0x1c, 0xb1, 0x56, 0x18, 0x91, 0x5b, 0x35, 0x66, 0x24, 0xe6, 0xb8, + 0xf8, 0x8d, 0x25, 0x9a, 0xfe, 0xf7, 0x12, 0x20, 0xa5, 0xbb, 0xe2, 0x3a, 0x1d, 0x8b, 0x5a, 0xae, + 0x83, 0x5e, 0x80, 0x31, 0x7a, 0xd0, 0x27, 0x7c, 0x72, 0x26, 0x1b, 0x4f, 0x04, 0x0e, 0xb5, 0x0f, + 0xfa, 0xe4, 0xc1, 0xe1, 0xf2, 0xe5, 0xb4, 0x05, 0x93, 0x60, 0x6e, 0x83, 0xd6, 0x94, 0xab, 0x25, + 0x6e, 0xfd, 0x4c, 0xbc, 0xeb, 0x07, 0x87, 0xcb, 0x19, 0x41, 0x58, 0x53, 0x48, 0x71, 0x07, 0xd1, + 0x3e, 0x20, 0xdb, 0xf0, 0x69, 0xdb, 0x33, 0x1c, 0x5f, 0xf4, 0x64, 0xf5, 0x88, 0x9c, 0x84, 0xa7, + 0x8a, 0x2d, 0x1a, 0xb3, 0x68, 0x2c, 0x4a, 0x2f, 0xd0, 0x5a, 0x0a, 0x0d, 0x67, 0xf4, 0x80, 0x9e, + 0x80, 0x71, 0x8f, 0x18, 0xbe, 0xeb, 0x2c, 0x8c, 0xf1, 0x51, 0xa8, 0x09, 0xc4, 0xbc, 0x15, 0x4b, + 0x29, 0x7a, 0x12, 0x26, 0x7a, 0xc4, 0xf7, 0x8d, 0x2e, 0x59, 0xa8, 0x70, 0xc5, 0x59, 0xa9, 0x38, + 0xb1, 0x2e, 0x9a, 0x71, 0x20, 0xd7, 0x3f, 0xd4, 0x60, 0x5a, 0xcd, 0xdc, 0x9a, 0xe5, 0x53, 0xf4, + 0xed, 0x54, 0x1c, 0xd6, 0x8a, 0x0d, 0x89, 0x59, 0xf3, 0x28, 0xbc, 0x20, 0x7b, 0xab, 0x06, 0x2d, + 0x91, 0x18, 0x5c, 0x87, 0x8a, 0x45, 0x49, 0x8f, 0xad, 0x43, 0xf9, 0xea, 0xd4, 0xcd, 0xab, 0x45, + 0x43, 0xa6, 0x31, 0x2d, 0x41, 0x2b, 0xab, 0xcc, 0x1c, 0x0b, 0x14, 0xfd, 0xe7, 0x63, 0x11, 0xf7, + 0x59, 0x68, 0xa2, 0x77, 0xa0, 0xea, 0x13, 0x9b, 0x98, 0xd4, 0xf5, 0xa4, 0xfb, 0x4f, 0x17, 0x74, + 0xdf, 0xd8, 0x26, 0x76, 0x4b, 0x9a, 0x36, 0xce, 0x33, 0xff, 0x83, 0x5f, 0x58, 0x41, 0xa2, 0x37, + 0xa0, 0x4a, 0x49, 0xaf, 0x6f, 0x1b, 0x94, 0xc8, 0x7d, 0xf4, 0xff, 0xd1, 0x21, 0xb0, 0xc8, 0x61, + 0x60, 0x4d, 0xb7, 0xd3, 0x96, 0x6a, 0x7c, 0xfb, 0xa8, 0x29, 0x09, 0x5a, 0xb1, 0x82, 0x41, 0xfb, + 0x30, 0x33, 0xe8, 0x77, 0x98, 0x26, 0x65, 0xd9, 0xa1, 0x7b, 0x20, 0x23, 0xe9, 0x56, 0xd1, 0xb9, + 0xd9, 0x8a, 0x59, 0x37, 0x2e, 0xcb, 0xbe, 0x66, 0xe2, 0xed, 0x38, 0xd1, 0x0b, 0xba, 0x0d, 0xb3, + 0x3d, 0xcb, 0xc1, 0xc4, 0xe8, 0x1c, 0xb4, 0x88, 0xe9, 0x3a, 0x1d, 0x9f, 0x87, 0x55, 0xa5, 0x31, + 0x2f, 0x01, 0x66, 0xd7, 0xe3, 0x62, 0x9c, 0xd4, 0x47, 0xaf, 0x01, 0x0a, 0x86, 0xf1, 0xaa, 0x48, + 0x6e, 0x96, 0xeb, 0xf0, 0x98, 0x2b, 0x87, 0xc1, 0xdd, 0x4e, 0x69, 0xe0, 0x0c, 0x2b, 0xb4, 0x06, + 0x73, 0x1e, 0xd9, 0xb7, 0xd8, 0x18, 0xef, 0x59, 0x3e, 0x75, 0xbd, 0x83, 0x35, 0xab, 0x67, 0xd1, + 0x85, 0x71, 0xee, 0xd3, 0xc2, 0xd1, 0xe1, 0xf2, 0x1c, 0xce, 0x90, 0xe3, 0x4c, 0x2b, 0xfd, 0x83, + 0x71, 0x98, 0x4d, 0xe4, 0x1b, 0xf4, 0x26, 0x5c, 0x36, 0x07, 0x9e, 0x47, 0x1c, 0xba, 0x31, 0xe8, + 0x6d, 0x13, 0xaf, 0x65, 0xee, 0x92, 0xce, 0xc0, 0x26, 0x1d, 0x1e, 0x28, 0x95, 0xc6, 0x92, 0xf4, + 0xf8, 0xf2, 0x4a, 0xa6, 0x16, 0xce, 0xb1, 0x66, 0xb3, 0xe0, 0xf0, 0xa6, 0x75, 0xcb, 0xf7, 0x15, + 0x66, 0x89, 0x63, 0xaa, 0x59, 0xd8, 0x48, 0x69, 0xe0, 0x0c, 0x2b, 0xe6, 0x63, 0x87, 0xf8, 0x96, + 0x47, 0x3a, 0x49, 0x1f, 0xcb, 0x71, 0x1f, 0xef, 0x64, 0x6a, 0xe1, 0x1c, 0x6b, 0xf4, 0x2c, 0x4c, + 0x89, 0xde, 0xf8, 0xfa, 0xc9, 0x85, 0xbe, 0x24, 0xc1, 0xa6, 0x36, 0x42, 0x11, 0x8e, 0xea, 0xb1, + 0xa1, 0xb9, 0xdb, 0x3e, 0xf1, 0xf6, 0x49, 0x27, 0x7f, 0x81, 0x37, 0x53, 0x1a, 0x38, 0xc3, 0x8a, + 0x0d, 0x4d, 0x44, 0x60, 0x6a, 0x68, 0xe3, 0xf1, 0xa1, 0x6d, 0x65, 0x6a, 0xe1, 0x1c, 0x6b, 0x16, + 0xc7, 0xc2, 0xe5, 0xdb, 0xfb, 0x86, 0x65, 0x1b, 0xdb, 0x36, 0x59, 0x98, 0x88, 0xc7, 0xf1, 0x46, + 0x5c, 0x8c, 0x93, 0xfa, 0xe8, 0x55, 0xb8, 0x28, 0x9a, 0xb6, 0x1c, 0x43, 0x81, 0x54, 0x39, 0xc8, + 0xa3, 0x12, 0xe4, 0xe2, 0x46, 0x52, 0x01, 0xa7, 0x6d, 0xd0, 0x0b, 0x30, 0x63, 0xba, 0xb6, 0xcd, + 0xe3, 0x71, 0xc5, 0x1d, 0x38, 0x74, 0x61, 0x92, 0xa3, 0x20, 0xb6, 0x1f, 0x57, 0x62, 0x12, 0x9c, + 0xd0, 0x44, 0x04, 0xc0, 0x0c, 0x0a, 0x8e, 0xbf, 0x00, 0x3c, 0x3f, 0xde, 0x28, 0x9a, 0x03, 0x54, + 0xa9, 0x0a, 0x39, 0x80, 0x6a, 0xf2, 0x71, 0x04, 0x58, 0xff, 0xb3, 0x06, 0xf3, 0x39, 0xa9, 0x03, + 0xbd, 0x1c, 0x2b, 0xb1, 0x5f, 0x4b, 0x94, 0xd8, 0x2b, 0x39, 0x66, 0x91, 0x3a, 0xeb, 0xc0, 0xb4, + 0xc7, 0x46, 0xe5, 0x74, 0x85, 0x8a, 0xcc, 0x91, 0xcf, 0x9e, 0x30, 0x0c, 0x1c, 0xb5, 0x09, 0x73, + 0xfe, 0xc5, 0xa3, 0xc3, 0xe5, 0xe9, 0x98, 0x0c, 0xc7, 0xe1, 0xf5, 0x5f, 0x94, 0x00, 0xee, 0x90, + 0xbe, 0xed, 0x1e, 0xf4, 0x88, 0x73, 0x16, 0x1c, 0x6a, 0x33, 0xc6, 0xa1, 0xae, 0x9f, 0xb4, 0x3c, + 0xca, 0xb5, 0x5c, 0x12, 0xf5, 0x56, 0x82, 0x44, 0xd5, 0x8b, 0x43, 0x1e, 0xcf, 0xa2, 0xfe, 0x5a, + 0x86, 0x4b, 0xa1, 0x72, 0x48, 0xa3, 0x5e, 0x8c, 0xad, 0xf1, 0x57, 0x13, 0x6b, 0x3c, 0x9f, 0x61, + 0xf2, 0xd0, 0x78, 0xd4, 0xbb, 0x30, 0xc3, 0x58, 0x8e, 0x58, 0x4b, 0xce, 0xa1, 0xc6, 0x87, 0xe6, + 0x50, 0xaa, 0xda, 0xad, 0xc5, 0x90, 0x70, 0x02, 0x39, 0x87, 0xb3, 0x4d, 0x7c, 0x19, 0x39, 0xdb, + 0x47, 0x1a, 0xcc, 0x84, 0xcb, 0x74, 0x06, 0xa4, 0x6d, 0x23, 0x4e, 0xda, 0x9e, 0x2c, 0x1c, 0xa2, + 0x39, 0xac, 0xed, 0x9f, 0x8c, 0xe0, 0x2b, 0x25, 0xb6, 0xc1, 0xb7, 0x0d, 0x73, 0x0f, 0x3d, 0x0e, + 0x63, 0x8e, 0xd1, 0x0b, 0x22, 0x53, 0x6d, 0x96, 0x0d, 0xa3, 0x47, 0x30, 0x97, 0xa0, 0xf7, 0x35, + 0x40, 0xb2, 0x0a, 0xdc, 0x76, 0x1c, 0x97, 0x1a, 0x22, 0x57, 0x0a, 0xb7, 0x56, 0x0b, 0xbb, 0x15, + 0xf4, 0x58, 0xdb, 0x4a, 0x61, 0xdd, 0x75, 0xa8, 0x77, 0x10, 0x2e, 0x72, 0x5a, 0x01, 0x67, 0x38, + 0x80, 0x0c, 0x00, 0x4f, 0x62, 0xb6, 0x5d, 0xb9, 0x91, 0xaf, 0x17, 0xc8, 0x79, 0xcc, 0x60, 0xc5, + 0x75, 0x76, 0xac, 0x6e, 0x98, 0x76, 0xb0, 0x02, 0xc2, 0x11, 0xd0, 0xc5, 0xbb, 0x30, 0x9f, 0xe3, + 0x2d, 0xba, 0x00, 0xe5, 0x3d, 0x72, 0x20, 0xa6, 0x0d, 0xb3, 0x3f, 0xd1, 0x1c, 0x54, 0xf6, 0x0d, + 0x7b, 0x20, 0xd2, 0xef, 0x24, 0x16, 0x3f, 0x5e, 0x28, 0x3d, 0xa7, 0xe9, 0x1f, 0x56, 0xa2, 0xb1, + 0xc3, 0x19, 0xf3, 0x55, 0xa8, 0x7a, 0xa4, 0x6f, 0x5b, 0xa6, 0xe1, 0x4b, 0x22, 0xc4, 0xc9, 0x2f, + 0x96, 0x6d, 0x58, 0x49, 0x63, 0xdc, 0xba, 0xf4, 0x70, 0xb9, 0x75, 0xf9, 0x74, 0xb8, 0xf5, 0x77, + 0xa1, 0xea, 0x07, 0xac, 0x7a, 0x8c, 0x43, 0xde, 0x18, 0x22, 0xbf, 0x4a, 0x42, 0xad, 0x3a, 0x50, + 0x54, 0x5a, 0x81, 0x66, 0x91, 0xe8, 0xca, 0x90, 0x24, 0xfa, 0x54, 0x89, 0x2f, 0xcb, 0x37, 0x7d, + 0x63, 0xe0, 0x93, 0x0e, 0xcf, 0x6d, 0xd5, 0x30, 0xdf, 0x34, 0x79, 0x2b, 0x96, 0x52, 0xf4, 0x4e, + 0x2c, 0x64, 0xab, 0xa3, 0x84, 0xec, 0x4c, 0x7e, 0xb8, 0xa2, 0x2d, 0x98, 0xef, 0x7b, 0x6e, 0xd7, + 0x23, 0xbe, 0x7f, 0x87, 0x18, 0x1d, 0xdb, 0x72, 0x48, 0x30, 0x3f, 0x82, 0x11, 0x5d, 0x39, 0x3a, + 0x5c, 0x9e, 0x6f, 0x66, 0xab, 0xe0, 0x3c, 0x5b, 0xfd, 0x67, 0x15, 0xb8, 0x90, 0xac, 0x80, 0x39, + 0x24, 0x55, 0x1b, 0x89, 0xa4, 0x5e, 0x8b, 0x6c, 0x06, 0xc1, 0xe0, 0xd5, 0xea, 0x67, 0x6c, 0x88, + 0xdb, 0x30, 0x2b, 0xb3, 0x41, 0x20, 0x94, 0x34, 0x5d, 0xad, 0xfe, 0x56, 0x5c, 0x8c, 0x93, 0xfa, + 0xe8, 0x45, 0x98, 0xf6, 0x38, 0xef, 0x0e, 0x00, 0x04, 0x77, 0x7d, 0x44, 0x02, 0x4c, 0xe3, 0xa8, + 0x10, 0xc7, 0x75, 0x19, 0x6f, 0x0d, 0xe9, 0x68, 0x00, 0x30, 0x16, 0xe7, 0xad, 0xb7, 0x93, 0x0a, + 0x38, 0x6d, 0x83, 0xd6, 0xe1, 0xd2, 0xc0, 0x49, 0x43, 0x89, 0x50, 0xbe, 0x22, 0xa1, 0x2e, 0x6d, + 0xa5, 0x55, 0x70, 0x96, 0x1d, 0x5a, 0x85, 0x4b, 0x94, 0x78, 0x3d, 0xcb, 0x31, 0xa8, 0xe5, 0x74, + 0x15, 0x9c, 0x58, 0xf9, 0x79, 0x06, 0xd5, 0x4e, 0x8b, 0x71, 0x96, 0x0d, 0xda, 0x89, 0xb1, 0xe2, + 0x71, 0x9e, 0xe9, 0x6f, 0x16, 0xde, 0xc3, 0x85, 0x69, 0x71, 0x06, 0x73, 0xaf, 0x16, 0x65, 0xee, + 0xfa, 0x1f, 0xb4, 0x68, 0x3d, 0x53, 0x6c, 0xfa, 0xa4, 0x0b, 0xab, 0x94, 0x45, 0x84, 0x68, 0xb9, + 0xd9, 0x44, 0xfa, 0xd6, 0x50, 0x44, 0x3a, 0xac, 0xc3, 0x27, 0x33, 0xe9, 0x3f, 0x6a, 0x30, 0x7b, + 0xaf, 0xdd, 0x6e, 0xae, 0x3a, 0x7c, 0xe3, 0x35, 0x0d, 0xba, 0xcb, 0x0a, 0x72, 0xdf, 0xa0, 0xbb, + 0xc9, 0x82, 0xcc, 0x64, 0x98, 0x4b, 0xd0, 0x33, 0x50, 0x65, 0xff, 0x32, 0xc7, 0x79, 0xe4, 0x4f, + 0xf2, 0x7c, 0x55, 0x6d, 0xca, 0xb6, 0x07, 0x91, 0xbf, 0xb1, 0xd2, 0x44, 0xdf, 0x82, 0x09, 0x96, + 0x26, 0x88, 0xd3, 0x29, 0xc8, 0xa3, 0xa5, 0x53, 0x0d, 0x61, 0x14, 0x52, 0x23, 0xd9, 0x80, 0x03, + 0x38, 0x7d, 0x0f, 0xe6, 0x22, 0x83, 0xc0, 0x03, 0x9b, 0xbc, 0xc9, 0x4a, 0x1f, 0x6a, 0x41, 0x85, + 0xf5, 0xce, 0x0a, 0x5c, 0xb9, 0xc0, 0x4d, 0x65, 0x62, 0x22, 0x42, 0x1a, 0xc3, 0x7e, 0xf9, 0x58, + 0x60, 0xe9, 0x9b, 0x30, 0xb1, 0xda, 0x6c, 0xd8, 0xae, 0xa0, 0x2e, 0xa6, 0xd5, 0xf1, 0x92, 0x33, + 0xb5, 0xb2, 0x7a, 0x07, 0x63, 0x2e, 0x41, 0x3a, 0x8c, 0x93, 0xfb, 0x26, 0xe9, 0x53, 0xce, 0x56, + 0x26, 0x1b, 0xc0, 0x72, 0xf2, 0x5d, 0xde, 0x82, 0xa5, 0x44, 0xff, 0x49, 0x09, 0x26, 0x64, 0xb7, + 0x67, 0x70, 0x94, 0x59, 0x8b, 0x1d, 0x65, 0x9e, 0x2a, 0xb6, 0x04, 0xb9, 0xe7, 0x98, 0x76, 0xe2, + 0x1c, 0x73, 0xad, 0x20, 0xde, 0xf1, 0x87, 0x98, 0xf7, 0x4a, 0x30, 0x13, 0x5f, 0x7c, 0xf4, 0x2c, + 0x4c, 0xb1, 0xac, 0x6d, 0x99, 0x64, 0x23, 0x24, 0x8b, 0xea, 0x26, 0xa3, 0x15, 0x8a, 0x70, 0x54, + 0x0f, 0x75, 0x95, 0x59, 0xd3, 0xf5, 0xa8, 0x1c, 0x74, 0xfe, 0x94, 0x0e, 0xa8, 0x65, 0xd7, 0xc4, + 0xbd, 0x7d, 0x6d, 0xd5, 0xa1, 0x9b, 0x5e, 0x8b, 0x7a, 0x96, 0xd3, 0x4d, 0x75, 0xc4, 0xc0, 0x70, + 0x14, 0x19, 0xbd, 0xc5, 0x2a, 0x88, 0xef, 0x0e, 0x3c, 0x93, 0x64, 0x31, 0xc1, 0x80, 0xc5, 0xb0, + 0x8d, 0xd0, 0x59, 0x73, 0x4d, 0xc3, 0x16, 0x8b, 0x83, 0xc9, 0x0e, 0xf1, 0x88, 0x63, 0x92, 0x80, + 0x7d, 0x09, 0x08, 0xac, 0xc0, 0xf4, 0xdf, 0x6a, 0x30, 0x25, 0xe7, 0xe2, 0x0c, 0x38, 0xff, 0xeb, + 0x71, 0xce, 0xff, 0x44, 0xc1, 0x1d, 0x9a, 0x4d, 0xf8, 0x7f, 0xa7, 0xc1, 0x62, 0xe0, 0xba, 0x6b, + 0x74, 0x1a, 0x86, 0x6d, 0x38, 0x26, 0xf1, 0x82, 0x58, 0x5f, 0x84, 0x92, 0xd5, 0x97, 0x2b, 0x09, + 0x12, 0xa0, 0xb4, 0xda, 0xc4, 0x25, 0xab, 0xcf, 0x0a, 0xf2, 0xae, 0xeb, 0x53, 0x7e, 0x30, 0x10, + 0x67, 0x4e, 0xe5, 0xf5, 0x3d, 0xd9, 0x8e, 0x95, 0x06, 0xda, 0x82, 0x4a, 0xdf, 0xf5, 0x28, 0x2b, + 0x82, 0xe5, 0xc4, 0xfa, 0x1e, 0xe3, 0x35, 0x5b, 0x37, 0x19, 0x88, 0xe1, 0x4e, 0x67, 0x30, 0x58, + 0xa0, 0xe9, 0x3f, 0xd4, 0xe0, 0xd1, 0x0c, 0xff, 0x25, 0xff, 0xe8, 0xc0, 0x84, 0x25, 0x84, 0x32, + 0xbd, 0x3c, 0x5f, 0xac, 0xdb, 0x8c, 0xa9, 0x08, 0x53, 0x5b, 0x90, 0xc2, 0x02, 0x68, 0xfd, 0x57, + 0x1a, 0x5c, 0x4c, 0xf9, 0xcb, 0x53, 0x34, 0x8b, 0x67, 0x49, 0xdc, 0x55, 0x8a, 0x66, 0x61, 0xc9, + 0x25, 0xe8, 0x75, 0xa8, 0xf2, 0xe7, 0x26, 0xd3, 0xb5, 0xe5, 0x04, 0xd6, 0x83, 0x09, 0x6c, 0xca, + 0xf6, 0x07, 0x87, 0xcb, 0x57, 0x32, 0x8e, 0xed, 0x81, 0x18, 0x2b, 0x00, 0xb4, 0x0c, 0x15, 0xe2, + 0x79, 0xae, 0x27, 0x93, 0xfd, 0x24, 0x9b, 0xa9, 0xbb, 0xac, 0x01, 0x8b, 0x76, 0xfd, 0xd7, 0x61, + 0x90, 0xb2, 0xec, 0xcb, 0xfc, 0x63, 0x8b, 0x93, 0x4c, 0x8c, 0x6c, 0xe9, 0x30, 0x97, 0xa0, 0x01, + 0x5c, 0xb0, 0x12, 0xe9, 0x5a, 0xee, 0xce, 0x7a, 0xb1, 0x69, 0x54, 0x66, 0x8d, 0x05, 0x09, 0x7f, + 0x21, 0x29, 0xc1, 0xa9, 0x2e, 0x74, 0x02, 0x29, 0x2d, 0xf4, 0x06, 0x8c, 0xed, 0x52, 0xda, 0xcf, + 0x78, 0x37, 0x38, 0xa1, 0x48, 0x84, 0x2e, 0x54, 0xf9, 0xe8, 0xda, 0xed, 0x26, 0xe6, 0x50, 0xfa, + 0xef, 0x4b, 0x6a, 0x3e, 0xf8, 0x61, 0xeb, 0x9b, 0x6a, 0xb4, 0x2b, 0xb6, 0xe1, 0xfb, 0x3c, 0x85, + 0x89, 0x8b, 0x81, 0xb9, 0x88, 0xe3, 0x4a, 0x86, 0x53, 0xda, 0xa8, 0x1d, 0x16, 0x4f, 0x6d, 0x94, + 0xe2, 0x39, 0x95, 0x55, 0x38, 0xd1, 0x3d, 0x28, 0x53, 0xbb, 0xe8, 0x01, 0x5f, 0x22, 0xb6, 0xd7, + 0x5a, 0x8d, 0x29, 0x39, 0xe5, 0xe5, 0xf6, 0x5a, 0x0b, 0x33, 0x08, 0xb4, 0x09, 0x15, 0x6f, 0x60, + 0x13, 0x56, 0x07, 0xca, 0xc5, 0xeb, 0x0a, 0x9b, 0xc1, 0x70, 0xf3, 0xb1, 0x5f, 0x3e, 0x16, 0x38, + 0xfa, 0x8f, 0x34, 0x98, 0x8e, 0x55, 0x0b, 0xe4, 0xc1, 0x79, 0x3b, 0xb2, 0x77, 0xe4, 0x3c, 0x3c, + 0x37, 0xfc, 0xae, 0x93, 0x9b, 0x7e, 0x4e, 0xf6, 0x7b, 0x3e, 0x2a, 0xc3, 0xb1, 0x3e, 0x74, 0x03, + 0x20, 0x1c, 0x36, 0xdb, 0x07, 0x2c, 0x78, 0xc5, 0x86, 0x97, 0xfb, 0x80, 0xc5, 0xb4, 0x8f, 0x45, + 0x3b, 0xba, 0x09, 0xe0, 0x13, 0xd3, 0x23, 0x74, 0x23, 0x4c, 0x5c, 0xaa, 0x1c, 0xb7, 0x94, 0x04, + 0x47, 0xb4, 0xf4, 0x3f, 0x69, 0x30, 0xbd, 0x41, 0xe8, 0xf7, 0x5d, 0x6f, 0xaf, 0xe9, 0xda, 0x96, + 0x79, 0x70, 0x06, 0x24, 0x00, 0xc7, 0x48, 0xc0, 0x49, 0xf9, 0x32, 0xe6, 0x5d, 0x1e, 0x15, 0xd0, + 0x3f, 0xd2, 0x60, 0x3e, 0xa6, 0x79, 0x37, 0xcc, 0x07, 0x2a, 0x41, 0x6b, 0x85, 0x12, 0x74, 0x0c, + 0x86, 0x25, 0xb5, 0xec, 0x04, 0x8d, 0xd6, 0xa0, 0x44, 0x5d, 0x19, 0xbd, 0xc3, 0x61, 0x12, 0xe2, + 0x85, 0x35, 0xa7, 0xed, 0xe2, 0x12, 0x75, 0xd9, 0x42, 0x2c, 0xc4, 0xb4, 0xa2, 0x19, 0xed, 0x21, + 0x8d, 0x00, 0xc3, 0xd8, 0x8e, 0xe7, 0xf6, 0x46, 0x1e, 0x83, 0x5a, 0x88, 0x57, 0x3c, 0xb7, 0x87, + 0x39, 0x96, 0xfe, 0xb1, 0x06, 0x17, 0x63, 0x9a, 0x67, 0xc0, 0x1b, 0xde, 0x88, 0xf3, 0x86, 0x6b, + 0xc3, 0x0c, 0x24, 0x87, 0x3d, 0x7c, 0x5c, 0x4a, 0x0c, 0x83, 0x0d, 0x18, 0xed, 0xc0, 0x54, 0xdf, + 0xed, 0xb4, 0x4e, 0xe1, 0xad, 0x77, 0x96, 0xf1, 0xb9, 0x66, 0x88, 0x85, 0xa3, 0xc0, 0xe8, 0x3e, + 0x5c, 0x64, 0xd4, 0xc2, 0xef, 0x1b, 0x26, 0x69, 0x9d, 0xc2, 0xed, 0xd7, 0x23, 0xfc, 0x31, 0x29, + 0x89, 0x88, 0xd3, 0x9d, 0xa0, 0x75, 0x98, 0xb0, 0xfa, 0xfc, 0x7c, 0x21, 0x89, 0xe4, 0x89, 0x24, + 0x4c, 0x9c, 0x46, 0x44, 0x8a, 0x97, 0x3f, 0x70, 0x80, 0xa1, 0xff, 0x25, 0x19, 0x0d, 0x9c, 0xae, + 0xbe, 0x1a, 0xa1, 0x07, 0xf2, 0xd9, 0x67, 0x34, 0x6a, 0xb0, 0x21, 0x99, 0xc8, 0xa8, 0xcc, 0xba, + 0x9a, 0xe0, 0x2d, 0x5f, 0x81, 0x09, 0xe2, 0x74, 0x38, 0x59, 0x17, 0x77, 0x2a, 0x7c, 0x54, 0x77, + 0x45, 0x13, 0x0e, 0x64, 0xfa, 0x8f, 0xcb, 0x89, 0x51, 0xf1, 0x32, 0xfb, 0xee, 0xa9, 0x05, 0x87, + 0x22, 0xfc, 0xb9, 0x01, 0xb2, 0x1d, 0xd2, 0x3f, 0x11, 0xf3, 0xdf, 0x18, 0x26, 0xe6, 0xa3, 0xf5, + 0x2f, 0x97, 0xfc, 0xa1, 0xef, 0xc0, 0x38, 0x11, 0x5d, 0x88, 0xaa, 0x7a, 0x6b, 0x98, 0x2e, 0xc2, + 0xf4, 0x1b, 0x9e, 0xb3, 0x64, 0x9b, 0x44, 0x45, 0x2f, 0xb3, 0xf9, 0x62, 0xba, 0xec, 0x58, 0x22, + 0xd8, 0xf3, 0x64, 0xe3, 0x31, 0x31, 0x6c, 0xd5, 0xfc, 0xe0, 0x70, 0x19, 0xc2, 0x9f, 0x38, 0x6a, + 0xc1, 0x1f, 0xe2, 0xe4, 0x9d, 0xcd, 0xd9, 0x7c, 0xcc, 0x34, 0xdc, 0x43, 0x5c, 0xe8, 0xda, 0xa9, + 0x3d, 0xc4, 0x45, 0x20, 0x8f, 0x3f, 0xc3, 0xfe, 0xa3, 0x04, 0x97, 0x42, 0xe5, 0xc2, 0x0f, 0x71, + 0x19, 0x26, 0xff, 0xfb, 0xa0, 0xa9, 0xd8, 0xe3, 0x58, 0x38, 0x75, 0xff, 0x79, 0x8f, 0x63, 0xa1, + 0x6f, 0x39, 0xd5, 0xee, 0x37, 0xa5, 0xe8, 0x00, 0x86, 0x7c, 0xa1, 0x39, 0x85, 0x6f, 0x7a, 0xbe, + 0x74, 0x8f, 0x3c, 0xfa, 0x07, 0x63, 0x70, 0x21, 0xb9, 0x1b, 0x63, 0x17, 0xf9, 0xda, 0x89, 0x17, + 0xf9, 0x4d, 0x98, 0xdb, 0x19, 0xd8, 0xf6, 0x01, 0x1f, 0x43, 0xe4, 0x36, 0x5f, 0x3c, 0x01, 0xfc, + 0x9f, 0xb4, 0x9c, 0x7b, 0x25, 0x43, 0x07, 0x67, 0x5a, 0xa6, 0xef, 0xf5, 0xc7, 0xfe, 0xdd, 0x7b, + 0xfd, 0xca, 0x08, 0xf7, 0xfa, 0x39, 0x17, 0xf1, 0x13, 0x23, 0x5c, 0xc4, 0x67, 0xbf, 0xb2, 0x94, + 0x47, 0x7a, 0x65, 0x19, 0xe5, 0x52, 0x3f, 0x23, 0x1f, 0x9e, 0xf8, 0xad, 0xcb, 0x4b, 0x30, 0x13, + 0x7f, 0xb3, 0x12, 0x61, 0x21, 0x9e, 0xcd, 0xe4, 0x0b, 0x51, 0x24, 0x2c, 0x44, 0x3b, 0x56, 0x1a, + 0xfa, 0x91, 0x06, 0x97, 0xb3, 0xbf, 0x4d, 0x41, 0x36, 0xcc, 0xf4, 0x8c, 0xfb, 0xd1, 0xef, 0x85, + 0xb4, 0x11, 0x89, 0x0f, 0x7f, 0x61, 0x58, 0x8f, 0x61, 0xe1, 0x04, 0x36, 0x7a, 0x1b, 0xaa, 0x3d, + 0xe3, 0x7e, 0x6b, 0xe0, 0x75, 0xc9, 0xc8, 0x04, 0x8b, 0xef, 0xc8, 0x75, 0x89, 0x82, 0x15, 0x9e, + 0xfe, 0x85, 0x06, 0xf3, 0x39, 0xef, 0x06, 0xff, 0x45, 0xa3, 0x7c, 0xaf, 0x04, 0x95, 0x96, 0x69, + 0xd8, 0xe4, 0x0c, 0xb8, 0xc9, 0x6b, 0x31, 0x6e, 0x72, 0xd2, 0x37, 0xae, 0xdc, 0xab, 0x5c, 0x5a, + 0x82, 0x13, 0xb4, 0xe4, 0xa9, 0x42, 0x68, 0xc7, 0x33, 0x92, 0xe7, 0x61, 0x52, 0x75, 0x3a, 0x5c, + 0xa2, 0xd4, 0x7f, 0x59, 0x82, 0xa9, 0x48, 0x17, 0x43, 0xa6, 0xd9, 0x9d, 0x58, 0x6d, 0x29, 0x17, + 0xb8, 0xb4, 0x89, 0xf4, 0x55, 0x0b, 0xaa, 0x89, 0xf8, 0x46, 0x23, 0x7c, 0x95, 0x4f, 0x17, 0x99, + 0x97, 0x60, 0x86, 0x1a, 0x5e, 0x97, 0x50, 0x75, 0x02, 0x10, 0xf7, 0x95, 0xea, 0x63, 0xa1, 0x76, + 0x4c, 0x8a, 0x13, 0xda, 0x8b, 0x2f, 0xc2, 0x74, 0xac, 0xb3, 0x61, 0x3e, 0xb1, 0x68, 0xac, 0x7c, + 0xf2, 0xf9, 0xd2, 0xb9, 0x4f, 0x3f, 0x5f, 0x3a, 0xf7, 0xd9, 0xe7, 0x4b, 0xe7, 0x7e, 0x70, 0xb4, + 0xa4, 0x7d, 0x72, 0xb4, 0xa4, 0x7d, 0x7a, 0xb4, 0xa4, 0x7d, 0x76, 0xb4, 0xa4, 0xfd, 0xed, 0x68, + 0x49, 0xfb, 0xe9, 0x17, 0x4b, 0xe7, 0xde, 0x7e, 0xec, 0xd8, 0xff, 0x71, 0xf1, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x6a, 0x79, 0xb9, 0xab, 0x91, 0x31, 0x00, 0x00, } func (m *DaemonSet) Marshal() (dAtA []byte, err error) { @@ -2208,6 +2210,11 @@ func (m *DeploymentStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x48 + } if m.CollisionCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.CollisionCount)) i-- @@ -3486,6 +3493,11 @@ func (m *ReplicaSetStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TerminatingReplicas != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminatingReplicas)) + i-- + dAtA[i] = 0x38 + } if len(m.Conditions) > 0 { for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { { @@ -4024,6 +4036,9 @@ func (m *DeploymentStatus) Size() (n int) { if m.CollisionCount != nil { n += 1 + sovGenerated(uint64(*m.CollisionCount)) } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -4502,6 +4517,9 @@ func (m *ReplicaSetStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.TerminatingReplicas != nil { + n += 1 + sovGenerated(uint64(*m.TerminatingReplicas)) + } return n } @@ -4793,6 +4811,7 @@ func (this *DeploymentStatus) String() string { `Conditions:` + repeatedStringForConditions + `,`, `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `CollisionCount:` + valueToStringGenerated(this.CollisionCount) + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -5182,6 +5201,7 @@ func (this *ReplicaSetStatus) String() string { `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, `AvailableReplicas:` + fmt.Sprintf("%v", this.AvailableReplicas) + `,`, `Conditions:` + repeatedStringForConditions + `,`, + `TerminatingReplicas:` + valueToStringGenerated(this.TerminatingReplicas) + `,`, `}`, }, "") return s @@ -7567,6 +7587,26 @@ func (m *DeploymentStatus) Unmarshal(dAtA []byte) error { } } m.CollisionCount = &v + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -11162,6 +11202,26 @@ func (m *ReplicaSetStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminatingReplicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminatingReplicas = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.proto index 9bbcaa0e26..70fcec0cc5 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/generated.proto @@ -320,19 +320,19 @@ message DeploymentStatus { // +optional optional int64 observedGeneration = 1; - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional optional int32 replicas = 2; - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional optional int32 updatedReplicas = 3; - // Total number of ready pods targeted by this deployment. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional optional int32 readyReplicas = 7; - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional optional int32 availableReplicas = 4; @@ -342,6 +342,13 @@ message DeploymentStatus { // +optional optional int32 unavailableReplicas = 5; + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 9; + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -863,16 +870,16 @@ message ReplicaSetList { optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset repeated ReplicaSet items = 2; } // ReplicaSetSpec is the specification of a ReplicaSet. message ReplicaSetSpec { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional optional int32 replicas = 1; @@ -891,29 +898,36 @@ message ReplicaSetSpec { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional optional .k8s.io.api.core.v1.PodTemplateSpec template = 3; } // ReplicaSetStatus represents the current status of a ReplicaSet. message ReplicaSetStatus { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset optional int32 replicas = 1; - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional optional int32 fullyLabeledReplicas = 2; - // The number of ready replicas for this replica set. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional optional int32 readyReplicas = 4; - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional optional int32 availableReplicas = 5; + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + optional int32 terminatingReplicas = 7; + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional optional int64 observedGeneration = 3; diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/types.go b/go-controller/vendor/k8s.io/api/extensions/v1beta1/types.go index 09f58692f4..b80a7a7e16 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/types.go @@ -245,19 +245,19 @@ type DeploymentStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` - // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // Total number of non-terminating pods targeted by this deployment (their labels match the selector). // +optional Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` - // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // Total number of non-terminating pods targeted by this deployment that have the desired template spec. // +optional UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` - // Total number of ready pods targeted by this deployment. + // Total number of non-terminating pods targeted by this Deployment with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` - // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` @@ -267,6 +267,13 @@ type DeploymentStatus struct { // +optional UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` + // Total number of terminating pods targeted by this deployment. Terminating pods have a non-null + // .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,9,opt,name=terminatingReplicas"` + // Represents the latest available observations of a deployment's current state. // +patchMergeKey=type // +patchStrategy=merge @@ -941,16 +948,16 @@ type ReplicaSetList struct { metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // List of ReplicaSets. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Items []ReplicaSet `json:"items" protobuf:"bytes,2,rep,name=items"` } // ReplicaSetSpec is the specification of a ReplicaSet. type ReplicaSetSpec struct { - // Replicas is the number of desired replicas. + // Replicas is the number of desired pods. // This is a pointer to distinguish between explicit zero and unspecified. // Defaults to 1. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset // +optional Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` @@ -969,29 +976,36 @@ type ReplicaSetSpec struct { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template // +optional Template v1.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,3,opt,name=template"` } // ReplicaSetStatus represents the current status of a ReplicaSet. type ReplicaSetStatus struct { - // Replicas is the most recently observed number of replicas. - // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // Replicas is the most recently observed number of non-terminating pods. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` - // The number of pods that have labels matching the labels of the pod template of the replicaset. + // The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset. // +optional FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty" protobuf:"varint,2,opt,name=fullyLabeledReplicas"` - // The number of ready replicas for this replica set. + // The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition. // +optional ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,4,opt,name=readyReplicas"` - // The number of available replicas (ready for at least minReadySeconds) for this replica set. + // The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set. // +optional AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"` + // The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp + // and have not yet reached the Failed or Succeeded .status.phase. + // + // This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field. + // +optional + TerminatingReplicas *int32 `json:"terminatingReplicas,omitempty" protobuf:"varint,7,opt,name=terminatingReplicas"` + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go index 408022c9d8..923fab3aa1 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go @@ -169,11 +169,12 @@ func (DeploymentSpec) SwaggerDoc() map[string]string { var map_DeploymentStatus = map[string]string{ "": "DeploymentStatus is the most recently observed status of the Deployment.", "observedGeneration": "The generation observed by the deployment controller.", - "replicas": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", - "updatedReplicas": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", - "readyReplicas": "Total number of ready pods targeted by this deployment.", - "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "replicas": "Total number of non-terminating pods targeted by this deployment (their labels match the selector).", + "updatedReplicas": "Total number of non-terminating pods targeted by this deployment that have the desired template spec.", + "readyReplicas": "Total number of non-terminating pods targeted by this Deployment with a Ready Condition.", + "availableReplicas": "Total number of available non-terminating pods (ready for at least minReadySeconds) targeted by this deployment.", "unavailableReplicas": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "terminatingReplicas": "Total number of terminating pods targeted by this deployment. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "conditions": "Represents the latest available observations of a deployment's current state.", "collisionCount": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", } @@ -435,7 +436,7 @@ func (ReplicaSetCondition) SwaggerDoc() map[string]string { var map_ReplicaSetList = map[string]string{ "": "ReplicaSetList is a collection of ReplicaSets.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", + "items": "List of ReplicaSets. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", } func (ReplicaSetList) SwaggerDoc() map[string]string { @@ -444,10 +445,10 @@ func (ReplicaSetList) SwaggerDoc() map[string]string { var map_ReplicaSetSpec = map[string]string{ "": "ReplicaSetSpec is the specification of a ReplicaSet.", - "replicas": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", + "replicas": "Replicas is the number of desired pods. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", "minReadySeconds": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "selector": "Selector is a label query over pods that should match the replica count. If the selector is empty, it is defaulted to the labels present on the pod template. Label keys and values that must match in order to be controlled by this replica set. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", + "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#pod-template", } func (ReplicaSetSpec) SwaggerDoc() map[string]string { @@ -456,10 +457,11 @@ func (ReplicaSetSpec) SwaggerDoc() map[string]string { var map_ReplicaSetStatus = map[string]string{ "": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "replicas": "Replicas is the most recently observed number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller", - "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replicaset.", - "readyReplicas": "The number of ready replicas for this replica set.", - "availableReplicas": "The number of available replicas (ready for at least minReadySeconds) for this replica set.", + "replicas": "Replicas is the most recently observed number of non-terminating pods. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset", + "fullyLabeledReplicas": "The number of non-terminating pods that have labels matching the labels of the pod template of the replicaset.", + "readyReplicas": "The number of non-terminating pods targeted by this ReplicaSet with a Ready Condition.", + "availableReplicas": "The number of available non-terminating pods (ready for at least minReadySeconds) for this replica set.", + "terminatingReplicas": "The number of terminating pods for this replica set. Terminating pods have a non-null .metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.\n\nThis is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.", "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", "conditions": "Represents the latest available observations of a replica set's current state.", } diff --git a/go-controller/vendor/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go index 6b474ae483..2c7a8524ea 100644 --- a/go-controller/vendor/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go @@ -341,6 +341,11 @@ func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]DeploymentCondition, len(*in)) @@ -1045,6 +1050,11 @@ func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { *out = *in + if in.TerminatingReplicas != nil { + in, out := &in.TerminatingReplicas, &out.TerminatingReplicas + *out = new(int32) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]ReplicaSetCondition, len(*in)) diff --git a/go-controller/vendor/k8s.io/api/flowcontrol/v1/doc.go b/go-controller/vendor/k8s.io/api/flowcontrol/v1/doc.go index c9e7db1589..ad5f457919 100644 --- a/go-controller/vendor/k8s.io/api/flowcontrol/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/flowcontrol/v1/doc.go @@ -22,4 +22,4 @@ limitations under the License. // +groupName=flowcontrol.apiserver.k8s.io // Package v1 holds api types of version v1 for group "flowcontrol.apiserver.k8s.io". -package v1 // import "k8s.io/api/flowcontrol/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta1/doc.go index 50897b7eb5..20268c1f2d 100644 --- a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta1/doc.go @@ -22,4 +22,4 @@ limitations under the License. // +groupName=flowcontrol.apiserver.k8s.io // Package v1beta1 holds api types of version v1alpha1 for group "flowcontrol.apiserver.k8s.io". -package v1beta1 // import "k8s.io/api/flowcontrol/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta2/doc.go b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta2/doc.go index 53b460d374..2dcad11ad9 100644 --- a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta2/doc.go +++ b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta2/doc.go @@ -22,4 +22,4 @@ limitations under the License. // +groupName=flowcontrol.apiserver.k8s.io // Package v1beta2 holds api types of version v1alpha1 for group "flowcontrol.apiserver.k8s.io". -package v1beta2 // import "k8s.io/api/flowcontrol/v1beta2" +package v1beta2 diff --git a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta3/doc.go b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta3/doc.go index cd60cfef7f..95f4430d38 100644 --- a/go-controller/vendor/k8s.io/api/flowcontrol/v1beta3/doc.go +++ b/go-controller/vendor/k8s.io/api/flowcontrol/v1beta3/doc.go @@ -22,4 +22,4 @@ limitations under the License. // +groupName=flowcontrol.apiserver.k8s.io // Package v1beta3 holds api types of version v1beta3 for group "flowcontrol.apiserver.k8s.io". -package v1beta3 // import "k8s.io/api/flowcontrol/v1beta3" +package v1beta3 diff --git a/go-controller/vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go index 5db6d52d47..f5fbbdbf0c 100644 --- a/go-controller/vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +groupName=imagepolicy.k8s.io -package v1alpha1 // import "k8s.io/api/imagepolicy/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/networking/v1/doc.go b/go-controller/vendor/k8s.io/api/networking/v1/doc.go index 1d13e7bab3..e2093b7df6 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=networking.k8s.io -package v1 // import "k8s.io/api/networking/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/networking/v1/generated.pb.go b/go-controller/vendor/k8s.io/api/networking/v1/generated.pb.go index 7c023e6903..062382b633 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/generated.pb.go @@ -104,10 +104,94 @@ func (m *HTTPIngressRuleValue) XXX_DiscardUnknown() { var xxx_messageInfo_HTTPIngressRuleValue proto.InternalMessageInfo +func (m *IPAddress) Reset() { *m = IPAddress{} } +func (*IPAddress) ProtoMessage() {} +func (*IPAddress) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{2} +} +func (m *IPAddress) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IPAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *IPAddress) XXX_Merge(src proto.Message) { + xxx_messageInfo_IPAddress.Merge(m, src) +} +func (m *IPAddress) XXX_Size() int { + return m.Size() +} +func (m *IPAddress) XXX_DiscardUnknown() { + xxx_messageInfo_IPAddress.DiscardUnknown(m) +} + +var xxx_messageInfo_IPAddress proto.InternalMessageInfo + +func (m *IPAddressList) Reset() { *m = IPAddressList{} } +func (*IPAddressList) ProtoMessage() {} +func (*IPAddressList) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{3} +} +func (m *IPAddressList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IPAddressList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *IPAddressList) XXX_Merge(src proto.Message) { + xxx_messageInfo_IPAddressList.Merge(m, src) +} +func (m *IPAddressList) XXX_Size() int { + return m.Size() +} +func (m *IPAddressList) XXX_DiscardUnknown() { + xxx_messageInfo_IPAddressList.DiscardUnknown(m) +} + +var xxx_messageInfo_IPAddressList proto.InternalMessageInfo + +func (m *IPAddressSpec) Reset() { *m = IPAddressSpec{} } +func (*IPAddressSpec) ProtoMessage() {} +func (*IPAddressSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{4} +} +func (m *IPAddressSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IPAddressSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *IPAddressSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_IPAddressSpec.Merge(m, src) +} +func (m *IPAddressSpec) XXX_Size() int { + return m.Size() +} +func (m *IPAddressSpec) XXX_DiscardUnknown() { + xxx_messageInfo_IPAddressSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_IPAddressSpec proto.InternalMessageInfo + func (m *IPBlock) Reset() { *m = IPBlock{} } func (*IPBlock) ProtoMessage() {} func (*IPBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{2} + return fileDescriptor_2c41434372fec1d7, []int{5} } func (m *IPBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -135,7 +219,7 @@ var xxx_messageInfo_IPBlock proto.InternalMessageInfo func (m *Ingress) Reset() { *m = Ingress{} } func (*Ingress) ProtoMessage() {} func (*Ingress) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{3} + return fileDescriptor_2c41434372fec1d7, []int{6} } func (m *Ingress) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -163,7 +247,7 @@ var xxx_messageInfo_Ingress proto.InternalMessageInfo func (m *IngressBackend) Reset() { *m = IngressBackend{} } func (*IngressBackend) ProtoMessage() {} func (*IngressBackend) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{4} + return fileDescriptor_2c41434372fec1d7, []int{7} } func (m *IngressBackend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -191,7 +275,7 @@ var xxx_messageInfo_IngressBackend proto.InternalMessageInfo func (m *IngressClass) Reset() { *m = IngressClass{} } func (*IngressClass) ProtoMessage() {} func (*IngressClass) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{5} + return fileDescriptor_2c41434372fec1d7, []int{8} } func (m *IngressClass) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -219,7 +303,7 @@ var xxx_messageInfo_IngressClass proto.InternalMessageInfo func (m *IngressClassList) Reset() { *m = IngressClassList{} } func (*IngressClassList) ProtoMessage() {} func (*IngressClassList) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{6} + return fileDescriptor_2c41434372fec1d7, []int{9} } func (m *IngressClassList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -247,7 +331,7 @@ var xxx_messageInfo_IngressClassList proto.InternalMessageInfo func (m *IngressClassParametersReference) Reset() { *m = IngressClassParametersReference{} } func (*IngressClassParametersReference) ProtoMessage() {} func (*IngressClassParametersReference) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{7} + return fileDescriptor_2c41434372fec1d7, []int{10} } func (m *IngressClassParametersReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -275,7 +359,7 @@ var xxx_messageInfo_IngressClassParametersReference proto.InternalMessageInfo func (m *IngressClassSpec) Reset() { *m = IngressClassSpec{} } func (*IngressClassSpec) ProtoMessage() {} func (*IngressClassSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{8} + return fileDescriptor_2c41434372fec1d7, []int{11} } func (m *IngressClassSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -303,7 +387,7 @@ var xxx_messageInfo_IngressClassSpec proto.InternalMessageInfo func (m *IngressList) Reset() { *m = IngressList{} } func (*IngressList) ProtoMessage() {} func (*IngressList) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{9} + return fileDescriptor_2c41434372fec1d7, []int{12} } func (m *IngressList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -331,7 +415,7 @@ var xxx_messageInfo_IngressList proto.InternalMessageInfo func (m *IngressLoadBalancerIngress) Reset() { *m = IngressLoadBalancerIngress{} } func (*IngressLoadBalancerIngress) ProtoMessage() {} func (*IngressLoadBalancerIngress) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{10} + return fileDescriptor_2c41434372fec1d7, []int{13} } func (m *IngressLoadBalancerIngress) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -359,7 +443,7 @@ var xxx_messageInfo_IngressLoadBalancerIngress proto.InternalMessageInfo func (m *IngressLoadBalancerStatus) Reset() { *m = IngressLoadBalancerStatus{} } func (*IngressLoadBalancerStatus) ProtoMessage() {} func (*IngressLoadBalancerStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{11} + return fileDescriptor_2c41434372fec1d7, []int{14} } func (m *IngressLoadBalancerStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -387,7 +471,7 @@ var xxx_messageInfo_IngressLoadBalancerStatus proto.InternalMessageInfo func (m *IngressPortStatus) Reset() { *m = IngressPortStatus{} } func (*IngressPortStatus) ProtoMessage() {} func (*IngressPortStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{12} + return fileDescriptor_2c41434372fec1d7, []int{15} } func (m *IngressPortStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -415,7 +499,7 @@ var xxx_messageInfo_IngressPortStatus proto.InternalMessageInfo func (m *IngressRule) Reset() { *m = IngressRule{} } func (*IngressRule) ProtoMessage() {} func (*IngressRule) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{13} + return fileDescriptor_2c41434372fec1d7, []int{16} } func (m *IngressRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -443,7 +527,7 @@ var xxx_messageInfo_IngressRule proto.InternalMessageInfo func (m *IngressRuleValue) Reset() { *m = IngressRuleValue{} } func (*IngressRuleValue) ProtoMessage() {} func (*IngressRuleValue) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{14} + return fileDescriptor_2c41434372fec1d7, []int{17} } func (m *IngressRuleValue) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -471,7 +555,7 @@ var xxx_messageInfo_IngressRuleValue proto.InternalMessageInfo func (m *IngressServiceBackend) Reset() { *m = IngressServiceBackend{} } func (*IngressServiceBackend) ProtoMessage() {} func (*IngressServiceBackend) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{15} + return fileDescriptor_2c41434372fec1d7, []int{18} } func (m *IngressServiceBackend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -499,7 +583,7 @@ var xxx_messageInfo_IngressServiceBackend proto.InternalMessageInfo func (m *IngressSpec) Reset() { *m = IngressSpec{} } func (*IngressSpec) ProtoMessage() {} func (*IngressSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{16} + return fileDescriptor_2c41434372fec1d7, []int{19} } func (m *IngressSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -527,7 +611,7 @@ var xxx_messageInfo_IngressSpec proto.InternalMessageInfo func (m *IngressStatus) Reset() { *m = IngressStatus{} } func (*IngressStatus) ProtoMessage() {} func (*IngressStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{17} + return fileDescriptor_2c41434372fec1d7, []int{20} } func (m *IngressStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -555,7 +639,7 @@ var xxx_messageInfo_IngressStatus proto.InternalMessageInfo func (m *IngressTLS) Reset() { *m = IngressTLS{} } func (*IngressTLS) ProtoMessage() {} func (*IngressTLS) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{18} + return fileDescriptor_2c41434372fec1d7, []int{21} } func (m *IngressTLS) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -583,7 +667,7 @@ var xxx_messageInfo_IngressTLS proto.InternalMessageInfo func (m *NetworkPolicy) Reset() { *m = NetworkPolicy{} } func (*NetworkPolicy) ProtoMessage() {} func (*NetworkPolicy) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{19} + return fileDescriptor_2c41434372fec1d7, []int{22} } func (m *NetworkPolicy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -611,7 +695,7 @@ var xxx_messageInfo_NetworkPolicy proto.InternalMessageInfo func (m *NetworkPolicyEgressRule) Reset() { *m = NetworkPolicyEgressRule{} } func (*NetworkPolicyEgressRule) ProtoMessage() {} func (*NetworkPolicyEgressRule) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{20} + return fileDescriptor_2c41434372fec1d7, []int{23} } func (m *NetworkPolicyEgressRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -639,7 +723,7 @@ var xxx_messageInfo_NetworkPolicyEgressRule proto.InternalMessageInfo func (m *NetworkPolicyIngressRule) Reset() { *m = NetworkPolicyIngressRule{} } func (*NetworkPolicyIngressRule) ProtoMessage() {} func (*NetworkPolicyIngressRule) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{21} + return fileDescriptor_2c41434372fec1d7, []int{24} } func (m *NetworkPolicyIngressRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -667,7 +751,7 @@ var xxx_messageInfo_NetworkPolicyIngressRule proto.InternalMessageInfo func (m *NetworkPolicyList) Reset() { *m = NetworkPolicyList{} } func (*NetworkPolicyList) ProtoMessage() {} func (*NetworkPolicyList) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{22} + return fileDescriptor_2c41434372fec1d7, []int{25} } func (m *NetworkPolicyList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -695,7 +779,7 @@ var xxx_messageInfo_NetworkPolicyList proto.InternalMessageInfo func (m *NetworkPolicyPeer) Reset() { *m = NetworkPolicyPeer{} } func (*NetworkPolicyPeer) ProtoMessage() {} func (*NetworkPolicyPeer) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{23} + return fileDescriptor_2c41434372fec1d7, []int{26} } func (m *NetworkPolicyPeer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -723,7 +807,7 @@ var xxx_messageInfo_NetworkPolicyPeer proto.InternalMessageInfo func (m *NetworkPolicyPort) Reset() { *m = NetworkPolicyPort{} } func (*NetworkPolicyPort) ProtoMessage() {} func (*NetworkPolicyPort) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{24} + return fileDescriptor_2c41434372fec1d7, []int{27} } func (m *NetworkPolicyPort) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -751,7 +835,7 @@ var xxx_messageInfo_NetworkPolicyPort proto.InternalMessageInfo func (m *NetworkPolicySpec) Reset() { *m = NetworkPolicySpec{} } func (*NetworkPolicySpec) ProtoMessage() {} func (*NetworkPolicySpec) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{25} + return fileDescriptor_2c41434372fec1d7, []int{28} } func (m *NetworkPolicySpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -776,10 +860,38 @@ func (m *NetworkPolicySpec) XXX_DiscardUnknown() { var xxx_messageInfo_NetworkPolicySpec proto.InternalMessageInfo +func (m *ParentReference) Reset() { *m = ParentReference{} } +func (*ParentReference) ProtoMessage() {} +func (*ParentReference) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{29} +} +func (m *ParentReference) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ParentReference) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ParentReference) XXX_Merge(src proto.Message) { + xxx_messageInfo_ParentReference.Merge(m, src) +} +func (m *ParentReference) XXX_Size() int { + return m.Size() +} +func (m *ParentReference) XXX_DiscardUnknown() { + xxx_messageInfo_ParentReference.DiscardUnknown(m) +} + +var xxx_messageInfo_ParentReference proto.InternalMessageInfo + func (m *ServiceBackendPort) Reset() { *m = ServiceBackendPort{} } func (*ServiceBackendPort) ProtoMessage() {} func (*ServiceBackendPort) Descriptor() ([]byte, []int) { - return fileDescriptor_2c41434372fec1d7, []int{26} + return fileDescriptor_2c41434372fec1d7, []int{30} } func (m *ServiceBackendPort) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -804,9 +916,124 @@ func (m *ServiceBackendPort) XXX_DiscardUnknown() { var xxx_messageInfo_ServiceBackendPort proto.InternalMessageInfo +func (m *ServiceCIDR) Reset() { *m = ServiceCIDR{} } +func (*ServiceCIDR) ProtoMessage() {} +func (*ServiceCIDR) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{31} +} +func (m *ServiceCIDR) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceCIDR) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ServiceCIDR) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceCIDR.Merge(m, src) +} +func (m *ServiceCIDR) XXX_Size() int { + return m.Size() +} +func (m *ServiceCIDR) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceCIDR.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceCIDR proto.InternalMessageInfo + +func (m *ServiceCIDRList) Reset() { *m = ServiceCIDRList{} } +func (*ServiceCIDRList) ProtoMessage() {} +func (*ServiceCIDRList) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{32} +} +func (m *ServiceCIDRList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceCIDRList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ServiceCIDRList) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceCIDRList.Merge(m, src) +} +func (m *ServiceCIDRList) XXX_Size() int { + return m.Size() +} +func (m *ServiceCIDRList) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceCIDRList.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceCIDRList proto.InternalMessageInfo + +func (m *ServiceCIDRSpec) Reset() { *m = ServiceCIDRSpec{} } +func (*ServiceCIDRSpec) ProtoMessage() {} +func (*ServiceCIDRSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{33} +} +func (m *ServiceCIDRSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceCIDRSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ServiceCIDRSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceCIDRSpec.Merge(m, src) +} +func (m *ServiceCIDRSpec) XXX_Size() int { + return m.Size() +} +func (m *ServiceCIDRSpec) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceCIDRSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceCIDRSpec proto.InternalMessageInfo + +func (m *ServiceCIDRStatus) Reset() { *m = ServiceCIDRStatus{} } +func (*ServiceCIDRStatus) ProtoMessage() {} +func (*ServiceCIDRStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_2c41434372fec1d7, []int{34} +} +func (m *ServiceCIDRStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceCIDRStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *ServiceCIDRStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceCIDRStatus.Merge(m, src) +} +func (m *ServiceCIDRStatus) XXX_Size() int { + return m.Size() +} +func (m *ServiceCIDRStatus) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceCIDRStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceCIDRStatus proto.InternalMessageInfo + func init() { proto.RegisterType((*HTTPIngressPath)(nil), "k8s.io.api.networking.v1.HTTPIngressPath") proto.RegisterType((*HTTPIngressRuleValue)(nil), "k8s.io.api.networking.v1.HTTPIngressRuleValue") + proto.RegisterType((*IPAddress)(nil), "k8s.io.api.networking.v1.IPAddress") + proto.RegisterType((*IPAddressList)(nil), "k8s.io.api.networking.v1.IPAddressList") + proto.RegisterType((*IPAddressSpec)(nil), "k8s.io.api.networking.v1.IPAddressSpec") proto.RegisterType((*IPBlock)(nil), "k8s.io.api.networking.v1.IPBlock") proto.RegisterType((*Ingress)(nil), "k8s.io.api.networking.v1.Ingress") proto.RegisterType((*IngressBackend)(nil), "k8s.io.api.networking.v1.IngressBackend") @@ -831,7 +1058,12 @@ func init() { proto.RegisterType((*NetworkPolicyPeer)(nil), "k8s.io.api.networking.v1.NetworkPolicyPeer") proto.RegisterType((*NetworkPolicyPort)(nil), "k8s.io.api.networking.v1.NetworkPolicyPort") proto.RegisterType((*NetworkPolicySpec)(nil), "k8s.io.api.networking.v1.NetworkPolicySpec") + proto.RegisterType((*ParentReference)(nil), "k8s.io.api.networking.v1.ParentReference") proto.RegisterType((*ServiceBackendPort)(nil), "k8s.io.api.networking.v1.ServiceBackendPort") + proto.RegisterType((*ServiceCIDR)(nil), "k8s.io.api.networking.v1.ServiceCIDR") + proto.RegisterType((*ServiceCIDRList)(nil), "k8s.io.api.networking.v1.ServiceCIDRList") + proto.RegisterType((*ServiceCIDRSpec)(nil), "k8s.io.api.networking.v1.ServiceCIDRSpec") + proto.RegisterType((*ServiceCIDRStatus)(nil), "k8s.io.api.networking.v1.ServiceCIDRStatus") } func init() { @@ -839,111 +1071,125 @@ func init() { } var fileDescriptor_2c41434372fec1d7 = []byte{ - // 1652 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x4b, 0x6f, 0x1b, 0x55, - 0x14, 0xce, 0x38, 0x71, 0xec, 0x1c, 0x27, 0x69, 0x72, 0x69, 0x85, 0x09, 0xc2, 0x0e, 0x23, 0xda, - 0x06, 0xda, 0xda, 0x34, 0xad, 0x10, 0x6c, 0x78, 0x4c, 0x9a, 0xa6, 0xa1, 0xa9, 0x63, 0x5d, 0x5b, - 0x45, 0x20, 0x1e, 0x9d, 0x8c, 0x6f, 0x9c, 0x69, 0xc6, 0x33, 0xa3, 0x3b, 0xd7, 0xa5, 0x95, 0x10, - 0x62, 0xc3, 0x82, 0x1d, 0x7f, 0x01, 0xf1, 0x0b, 0x10, 0x2c, 0x90, 0x10, 0x14, 0x36, 0xa8, 0xcb, - 0x4a, 0x6c, 0xba, 0xc1, 0xa2, 0xe6, 0x5f, 0x64, 0x85, 0xee, 0x63, 0x1e, 0x7e, 0xd5, 0xa6, 0xaa, - 0xb2, 0x4a, 0xee, 0x39, 0xe7, 0x7e, 0xe7, 0x71, 0xcf, 0x6b, 0x0c, 0x6b, 0x87, 0x6f, 0x06, 0x25, - 0xdb, 0x2b, 0x9b, 0xbe, 0x5d, 0x76, 0x09, 0xfb, 0xdc, 0xa3, 0x87, 0xb6, 0xdb, 0x2c, 0xdf, 0xb9, - 0x58, 0x6e, 0x12, 0x97, 0x50, 0x93, 0x91, 0x46, 0xc9, 0xa7, 0x1e, 0xf3, 0x50, 0x5e, 0x4a, 0x96, - 0x4c, 0xdf, 0x2e, 0xc5, 0x92, 0xa5, 0x3b, 0x17, 0x57, 0x2e, 0x34, 0x6d, 0x76, 0xd0, 0xde, 0x2b, - 0x59, 0x5e, 0xab, 0xdc, 0xf4, 0x9a, 0x5e, 0x59, 0x5c, 0xd8, 0x6b, 0xef, 0x8b, 0x93, 0x38, 0x88, - 0xff, 0x24, 0xd0, 0x8a, 0x9e, 0x50, 0x69, 0x79, 0x94, 0x0c, 0x51, 0xb6, 0x72, 0x39, 0x96, 0x69, - 0x99, 0xd6, 0x81, 0xed, 0x12, 0x7a, 0xaf, 0xec, 0x1f, 0x36, 0x39, 0x21, 0x28, 0xb7, 0x08, 0x33, - 0x87, 0xdd, 0x2a, 0x8f, 0xba, 0x45, 0xdb, 0x2e, 0xb3, 0x5b, 0x64, 0xe0, 0xc2, 0x1b, 0xe3, 0x2e, - 0x04, 0xd6, 0x01, 0x69, 0x99, 0x03, 0xf7, 0x2e, 0x8d, 0xba, 0xd7, 0x66, 0xb6, 0x53, 0xb6, 0x5d, - 0x16, 0x30, 0xda, 0x7f, 0x49, 0xff, 0x4d, 0x83, 0x13, 0xd7, 0xea, 0xf5, 0xea, 0xb6, 0xdb, 0xa4, - 0x24, 0x08, 0xaa, 0x26, 0x3b, 0x40, 0xab, 0x30, 0xe3, 0x9b, 0xec, 0x20, 0xaf, 0xad, 0x6a, 0x6b, - 0x73, 0xc6, 0xfc, 0x83, 0x4e, 0x71, 0xaa, 0xdb, 0x29, 0xce, 0x70, 0x1e, 0x16, 0x1c, 0x74, 0x19, - 0xb2, 0xfc, 0x6f, 0xfd, 0x9e, 0x4f, 0xf2, 0xd3, 0x42, 0x2a, 0xdf, 0xed, 0x14, 0xb3, 0x55, 0x45, - 0x3b, 0x4a, 0xfc, 0x8f, 0x23, 0x49, 0x54, 0x83, 0xcc, 0x9e, 0x69, 0x1d, 0x12, 0xb7, 0x91, 0x4f, - 0xad, 0x6a, 0x6b, 0xb9, 0xf5, 0xb5, 0xd2, 0xa8, 0xe7, 0x2b, 0x29, 0x7b, 0x0c, 0x29, 0x6f, 0x9c, - 0x50, 0x46, 0x64, 0x14, 0x01, 0x87, 0x48, 0xfa, 0x3e, 0x9c, 0x4c, 0xd8, 0x8f, 0xdb, 0x0e, 0xb9, - 0x69, 0x3a, 0x6d, 0x82, 0x2a, 0x90, 0xe6, 0x8a, 0x83, 0xbc, 0xb6, 0x3a, 0xbd, 0x96, 0x5b, 0x7f, - 0x75, 0xb4, 0xaa, 0x3e, 0xf7, 0x8d, 0x05, 0xa5, 0x2b, 0xcd, 0x4f, 0x01, 0x96, 0x30, 0xfa, 0x2e, - 0x64, 0xb6, 0xab, 0x86, 0xe3, 0x59, 0x87, 0x3c, 0x3e, 0x96, 0xdd, 0xa0, 0xfd, 0xf1, 0xd9, 0xd8, - 0xbe, 0x82, 0xb1, 0xe0, 0x20, 0x1d, 0x66, 0xc9, 0x5d, 0x8b, 0xf8, 0x2c, 0x9f, 0x5a, 0x9d, 0x5e, - 0x9b, 0x33, 0xa0, 0xdb, 0x29, 0xce, 0x6e, 0x0a, 0x0a, 0x56, 0x1c, 0xfd, 0xeb, 0x14, 0x64, 0x94, - 0x5a, 0x74, 0x0b, 0xb2, 0x3c, 0x7d, 0x1a, 0x26, 0x33, 0x05, 0x6a, 0x6e, 0xfd, 0xf5, 0x84, 0xbd, - 0xd1, 0x6b, 0x96, 0xfc, 0xc3, 0x26, 0x27, 0x04, 0x25, 0x2e, 0xcd, 0x6d, 0xdf, 0xdd, 0xbb, 0x4d, - 0x2c, 0x76, 0x83, 0x30, 0xd3, 0x40, 0xca, 0x0e, 0x88, 0x69, 0x38, 0x42, 0x45, 0x5b, 0x30, 0x13, - 0xf8, 0xc4, 0x52, 0x81, 0x3f, 0x3d, 0x36, 0xf0, 0x35, 0x9f, 0x58, 0xb1, 0x6b, 0xfc, 0x84, 0x05, - 0x00, 0xda, 0x85, 0xd9, 0x80, 0x99, 0xac, 0x1d, 0x88, 0x87, 0xcf, 0xad, 0x9f, 0x1d, 0x0f, 0x25, - 0xc4, 0x8d, 0x45, 0x05, 0x36, 0x2b, 0xcf, 0x58, 0xc1, 0xe8, 0x7f, 0x68, 0xb0, 0xd8, 0xfb, 0xda, - 0xe8, 0x26, 0x64, 0x02, 0x42, 0xef, 0xd8, 0x16, 0xc9, 0xcf, 0x08, 0x25, 0xe5, 0xf1, 0x4a, 0xa4, - 0x7c, 0x98, 0x2f, 0x39, 0x9e, 0x2b, 0x8a, 0x86, 0x43, 0x30, 0xf4, 0x01, 0x64, 0x29, 0x09, 0xbc, - 0x36, 0xb5, 0x88, 0xb2, 0xfe, 0x42, 0x12, 0x98, 0xd7, 0x3d, 0x87, 0xe4, 0xc9, 0xda, 0xd8, 0xf1, - 0x2c, 0xd3, 0x91, 0xa1, 0xc4, 0x64, 0x9f, 0x50, 0xe2, 0x5a, 0xc4, 0x98, 0xe7, 0x59, 0x8e, 0x15, - 0x04, 0x8e, 0xc0, 0x78, 0x15, 0xcd, 0x2b, 0x43, 0x36, 0x1c, 0xf3, 0x58, 0x1e, 0x74, 0xa7, 0xe7, - 0x41, 0x5f, 0x1b, 0x1b, 0x20, 0x61, 0xd7, 0xa8, 0x57, 0xd5, 0x7f, 0xd5, 0x60, 0x29, 0x29, 0xb8, - 0x63, 0x07, 0x0c, 0x7d, 0x3c, 0xe0, 0x44, 0x69, 0x32, 0x27, 0xf8, 0x6d, 0xe1, 0xc2, 0x92, 0x52, - 0x95, 0x0d, 0x29, 0x09, 0x07, 0xae, 0x43, 0xda, 0x66, 0xa4, 0x15, 0x88, 0x12, 0xc9, 0xad, 0x9f, - 0x99, 0xcc, 0x83, 0xb8, 0x3a, 0xb7, 0xf9, 0x65, 0x2c, 0x31, 0xf4, 0xbf, 0x35, 0x28, 0x26, 0xc5, - 0xaa, 0x26, 0x35, 0x5b, 0x84, 0x11, 0x1a, 0x44, 0x8f, 0x87, 0xd6, 0x20, 0x6b, 0x56, 0xb7, 0xb7, - 0xa8, 0xd7, 0xf6, 0xc3, 0xd2, 0xe5, 0xa6, 0xbd, 0xa7, 0x68, 0x38, 0xe2, 0xf2, 0x02, 0x3f, 0xb4, - 0x55, 0x97, 0x4a, 0x14, 0xf8, 0x75, 0xdb, 0x6d, 0x60, 0xc1, 0xe1, 0x12, 0xae, 0xd9, 0x0a, 0x9b, - 0x5f, 0x24, 0x51, 0x31, 0x5b, 0x04, 0x0b, 0x0e, 0x2a, 0x42, 0x3a, 0xb0, 0x3c, 0x5f, 0x66, 0xf0, - 0x9c, 0x31, 0xc7, 0x4d, 0xae, 0x71, 0x02, 0x96, 0x74, 0x74, 0x0e, 0xe6, 0xb8, 0x60, 0xe0, 0x9b, - 0x16, 0xc9, 0xa7, 0x85, 0xd0, 0x42, 0xb7, 0x53, 0x9c, 0xab, 0x84, 0x44, 0x1c, 0xf3, 0xf5, 0x1f, - 0xfa, 0xde, 0x87, 0x3f, 0x1d, 0x5a, 0x07, 0xb0, 0x3c, 0x97, 0x51, 0xcf, 0x71, 0x48, 0xd8, 0x8d, - 0xa2, 0xa4, 0xd9, 0x88, 0x38, 0x38, 0x21, 0x85, 0x6c, 0x00, 0x3f, 0x8a, 0x8d, 0x4a, 0x9e, 0xb7, - 0x26, 0x0b, 0xfd, 0x90, 0x98, 0x1a, 0x8b, 0x5c, 0x55, 0x82, 0x91, 0x00, 0xd7, 0x7f, 0xd4, 0x20, - 0xa7, 0xee, 0x1f, 0x43, 0x3a, 0x5d, 0xed, 0x4d, 0xa7, 0x97, 0xc7, 0x8f, 0x96, 0xe1, 0x99, 0xf4, - 0xb3, 0x06, 0x2b, 0xa1, 0xd5, 0x9e, 0xd9, 0x30, 0x4c, 0xc7, 0x74, 0x2d, 0x42, 0xc3, 0x4e, 0xbd, - 0x02, 0x29, 0x3b, 0x4c, 0x1f, 0x50, 0x00, 0xa9, 0xed, 0x2a, 0x4e, 0xd9, 0x3e, 0x3a, 0x0f, 0xd9, - 0x03, 0x2f, 0x60, 0x22, 0x31, 0x64, 0xea, 0x44, 0x06, 0x5f, 0x53, 0x74, 0x1c, 0x49, 0xa0, 0x2a, - 0xa4, 0x7d, 0x8f, 0xb2, 0x20, 0x3f, 0x23, 0x0c, 0x3e, 0x37, 0xd6, 0xe0, 0xaa, 0x47, 0x99, 0xea, - 0xa5, 0xf1, 0x88, 0xe2, 0x08, 0x58, 0x02, 0xe9, 0x5f, 0xc0, 0x0b, 0x43, 0x2c, 0x97, 0x57, 0xd0, - 0x67, 0x90, 0xb1, 0x25, 0x53, 0x4d, 0xc4, 0xcb, 0x63, 0x15, 0x0e, 0xf1, 0x3f, 0x1e, 0xc4, 0xe1, - 0xc0, 0x0d, 0x51, 0xf5, 0xef, 0x35, 0x58, 0x1e, 0xb0, 0x54, 0xec, 0x12, 0x1e, 0x65, 0x22, 0x62, - 0xe9, 0xc4, 0x2e, 0xe1, 0x51, 0x86, 0x05, 0x07, 0x5d, 0x87, 0xac, 0x58, 0x45, 0x2c, 0xcf, 0x51, - 0x51, 0x2b, 0x87, 0x51, 0xab, 0x2a, 0xfa, 0x51, 0xa7, 0xf8, 0xe2, 0xe0, 0x7e, 0x56, 0x0a, 0xd9, - 0x38, 0x02, 0xe0, 0x55, 0x47, 0x28, 0xf5, 0xa8, 0x2a, 0x4c, 0x51, 0x75, 0x9b, 0x9c, 0x80, 0x25, - 0x5d, 0xff, 0x2e, 0x4e, 0x4a, 0xbe, 0x2b, 0x70, 0xfb, 0xf8, 0x8b, 0xf4, 0xcf, 0x72, 0xfe, 0x5e, - 0x58, 0x70, 0x90, 0x0f, 0x4b, 0x76, 0xdf, 0x72, 0x31, 0x71, 0xd3, 0x8d, 0x6e, 0x18, 0x79, 0x85, - 0xbc, 0xd4, 0xcf, 0xc1, 0x03, 0xe8, 0xfa, 0x2d, 0x18, 0x90, 0xe2, 0xed, 0xfe, 0x80, 0x31, 0x7f, - 0x48, 0xe1, 0x8c, 0xde, 0x66, 0x62, 0xed, 0x59, 0xe1, 0x53, 0xbd, 0x5e, 0xc5, 0x02, 0x45, 0xff, - 0x46, 0x83, 0x53, 0x43, 0x07, 0x67, 0xd4, 0xd8, 0xb4, 0x91, 0x8d, 0xad, 0xa2, 0x5e, 0x54, 0xc6, - 0xe0, 0xfc, 0x68, 0x4b, 0x7a, 0x91, 0xf9, 0x8b, 0x0f, 0x7b, 0x7f, 0xfd, 0xcf, 0x54, 0xf4, 0x22, - 0xa2, 0xab, 0xbd, 0x1b, 0xc5, 0x5b, 0x74, 0x1d, 0xae, 0x59, 0xf5, 0xd0, 0x93, 0x89, 0xf8, 0x45, - 0x3c, 0x3c, 0x20, 0x8d, 0x1a, 0xb0, 0xd8, 0x20, 0xfb, 0x66, 0xdb, 0x61, 0x4a, 0xb7, 0x8a, 0xda, - 0xe4, 0xeb, 0x26, 0xea, 0x76, 0x8a, 0x8b, 0x57, 0x7a, 0x30, 0x70, 0x1f, 0x26, 0xda, 0x80, 0x69, - 0xe6, 0x84, 0xed, 0xe6, 0x95, 0xb1, 0xd0, 0xf5, 0x9d, 0x9a, 0x91, 0x53, 0xee, 0x4f, 0xd7, 0x77, - 0x6a, 0x98, 0xdf, 0x46, 0xef, 0x43, 0x9a, 0xb6, 0x1d, 0xc2, 0x97, 0xa9, 0xe9, 0x89, 0xf6, 0x32, - 0xfe, 0xa6, 0x71, 0xf9, 0xf3, 0x53, 0x80, 0x25, 0x84, 0xfe, 0x25, 0x2c, 0xf4, 0x6c, 0x5c, 0xa8, - 0x05, 0xf3, 0x4e, 0xa2, 0x84, 0x55, 0x14, 0x2e, 0xfd, 0xaf, 0xba, 0x57, 0x0d, 0xe7, 0xa4, 0xd2, - 0x38, 0x9f, 0xe4, 0xe1, 0x1e, 0x78, 0xdd, 0x04, 0x88, 0x7d, 0xe5, 0x95, 0xc8, 0xcb, 0x47, 0x76, - 0x1b, 0x55, 0x89, 0xbc, 0xaa, 0x02, 0x2c, 0xe9, 0x7c, 0x7a, 0x05, 0xc4, 0xa2, 0x84, 0x55, 0xe2, - 0x7e, 0x19, 0x4d, 0xaf, 0x5a, 0xc4, 0xc1, 0x09, 0x29, 0xfd, 0x77, 0x0d, 0x16, 0x2a, 0xd2, 0xe4, - 0xaa, 0xe7, 0xd8, 0xd6, 0xbd, 0x63, 0x58, 0xb4, 0x6e, 0xf4, 0x2c, 0x5a, 0x4f, 0x68, 0xd3, 0x3d, - 0x86, 0x8d, 0xdc, 0xb4, 0x7e, 0xd2, 0xe0, 0xf9, 0x1e, 0xc9, 0xcd, 0xb8, 0x19, 0x45, 0x23, 0x41, - 0x1b, 0x37, 0x12, 0x7a, 0x10, 0x44, 0x69, 0x0d, 0x1d, 0x09, 0x68, 0x0b, 0x52, 0xcc, 0x53, 0x39, - 0x3a, 0x31, 0x1c, 0x21, 0x34, 0x9e, 0x6d, 0x75, 0x0f, 0xa7, 0x98, 0xa7, 0xff, 0xa2, 0x41, 0xbe, - 0x47, 0x2a, 0xd9, 0x44, 0x9f, 0xbd, 0xdd, 0x37, 0x60, 0x66, 0x9f, 0x7a, 0xad, 0xa7, 0xb1, 0x3c, - 0x0a, 0xfa, 0x55, 0xea, 0xb5, 0xb0, 0x80, 0xd1, 0xef, 0x6b, 0xb0, 0xdc, 0x23, 0x79, 0x0c, 0x0b, - 0xc9, 0x4e, 0xef, 0x42, 0x72, 0x76, 0x42, 0x1f, 0x46, 0xac, 0x25, 0xf7, 0x53, 0x7d, 0x1e, 0x70, - 0x5f, 0xd1, 0x3e, 0xe4, 0x7c, 0xaf, 0x51, 0x23, 0x0e, 0xb1, 0x98, 0x37, 0xac, 0xc0, 0x9f, 0xe4, - 0x84, 0xb9, 0x47, 0x9c, 0xf0, 0xaa, 0x71, 0xa2, 0xdb, 0x29, 0xe6, 0xaa, 0x31, 0x16, 0x4e, 0x02, - 0xa3, 0xbb, 0xb0, 0x1c, 0xed, 0xa2, 0x91, 0xb6, 0xd4, 0xd3, 0x6b, 0x3b, 0xd5, 0xed, 0x14, 0x97, - 0x2b, 0xfd, 0x88, 0x78, 0x50, 0x09, 0xba, 0x06, 0x19, 0xdb, 0x17, 0x9f, 0xdd, 0xea, 0x8b, 0xed, - 0x49, 0x8b, 0x9d, 0xfc, 0x3e, 0x97, 0x1f, 0x7f, 0xea, 0x80, 0xc3, 0xeb, 0xfa, 0x5f, 0xfd, 0x39, - 0xc0, 0x13, 0x0e, 0x6d, 0x25, 0xb6, 0x0f, 0x39, 0xf3, 0xce, 0x3d, 0xdd, 0xe6, 0xd1, 0x3b, 0x16, - 0x47, 0x37, 0xa1, 0x36, 0xb3, 0x9d, 0x92, 0xfc, 0x31, 0xa6, 0xb4, 0xed, 0xb2, 0x5d, 0x5a, 0x63, - 0xd4, 0x76, 0x9b, 0x72, 0x44, 0x27, 0xd6, 0xa2, 0xd3, 0x90, 0x51, 0x53, 0x53, 0x38, 0x9e, 0x96, - 0x5e, 0x6d, 0x4a, 0x12, 0x0e, 0x79, 0xfa, 0x51, 0x7f, 0x5e, 0x88, 0x19, 0x7a, 0xfb, 0x99, 0xe5, - 0xc5, 0x73, 0x2a, 0x1b, 0x47, 0xe7, 0xc6, 0x27, 0xf1, 0x62, 0x29, 0x33, 0x7d, 0x7d, 0xc2, 0x4c, - 0x4f, 0x4e, 0xb4, 0x91, 0x6b, 0x25, 0xfa, 0x10, 0x66, 0x89, 0x44, 0x97, 0x23, 0xf2, 0xe2, 0x84, - 0xe8, 0x71, 0x5b, 0x8d, 0x7f, 0x79, 0x50, 0x34, 0x05, 0x88, 0xde, 0xe1, 0x51, 0xe2, 0xb2, 0xfc, - 0x83, 0x5f, 0xee, 0xe1, 0x73, 0xc6, 0x4b, 0xd2, 0xd9, 0x88, 0x7c, 0xc4, 0x3f, 0x70, 0xa2, 0x23, - 0x4e, 0xde, 0xd0, 0x3f, 0x05, 0x34, 0xb8, 0xe4, 0x4c, 0xb0, 0x42, 0x9d, 0x81, 0x59, 0xb7, 0xdd, - 0xda, 0x23, 0xb2, 0x86, 0xd2, 0xb1, 0x81, 0x15, 0x41, 0xc5, 0x8a, 0x6b, 0xbc, 0xfd, 0xe0, 0x71, - 0x61, 0xea, 0xe1, 0xe3, 0xc2, 0xd4, 0xa3, 0xc7, 0x85, 0xa9, 0xaf, 0xba, 0x05, 0xed, 0x41, 0xb7, - 0xa0, 0x3d, 0xec, 0x16, 0xb4, 0x47, 0xdd, 0x82, 0xf6, 0x4f, 0xb7, 0xa0, 0x7d, 0xfb, 0x6f, 0x61, - 0xea, 0xa3, 0xfc, 0xa8, 0x5f, 0x4b, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x24, 0x03, 0xec, 0x04, - 0x48, 0x15, 0x00, 0x00, + // 1884 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x59, 0xcd, 0x8f, 0x1b, 0x49, + 0x15, 0x9f, 0xf6, 0x8c, 0x67, 0xec, 0xe7, 0xf9, 0xc8, 0x14, 0x59, 0x61, 0x06, 0x61, 0x87, 0x5e, + 0xb2, 0x3b, 0x4b, 0x76, 0x6d, 0x32, 0x1b, 0x21, 0xb8, 0x00, 0xdb, 0x93, 0x6c, 0xe2, 0xcd, 0xc4, + 0xb1, 0xca, 0x56, 0x10, 0x88, 0x8f, 0xed, 0x69, 0xd7, 0x78, 0x7a, 0xa7, 0xdd, 0xd5, 0xaa, 0x2e, + 0x87, 0x44, 0x42, 0x88, 0x0b, 0x07, 0x6e, 0xf0, 0x27, 0x20, 0xfe, 0x02, 0x04, 0xd2, 0xae, 0xb4, + 0x82, 0x85, 0x0b, 0xca, 0x71, 0x25, 0x2e, 0x7b, 0xc1, 0x22, 0xe6, 0xbf, 0xc8, 0x09, 0xd5, 0x47, + 0x7f, 0xd9, 0xee, 0xb1, 0x89, 0x22, 0x9f, 0xc6, 0xfd, 0xde, 0xab, 0xdf, 0x7b, 0xf5, 0xea, 0x7d, + 0x55, 0x0d, 0x1c, 0x5e, 0x7c, 0x27, 0x6c, 0xb8, 0xb4, 0x69, 0x07, 0x6e, 0xd3, 0x27, 0xfc, 0x17, + 0x94, 0x5d, 0xb8, 0xfe, 0xa0, 0xf9, 0xf8, 0x66, 0x73, 0x40, 0x7c, 0xc2, 0x6c, 0x4e, 0xfa, 0x8d, + 0x80, 0x51, 0x4e, 0x51, 0x55, 0x49, 0x36, 0xec, 0xc0, 0x6d, 0x24, 0x92, 0x8d, 0xc7, 0x37, 0x0f, + 0xde, 0x19, 0xb8, 0xfc, 0x7c, 0x74, 0xda, 0x70, 0xe8, 0xb0, 0x39, 0xa0, 0x03, 0xda, 0x94, 0x0b, + 0x4e, 0x47, 0x67, 0xf2, 0x4b, 0x7e, 0xc8, 0x5f, 0x0a, 0xe8, 0xc0, 0x4c, 0xa9, 0x74, 0x28, 0x23, + 0x73, 0x94, 0x1d, 0xdc, 0x4a, 0x64, 0x86, 0xb6, 0x73, 0xee, 0xfa, 0x84, 0x3d, 0x6d, 0x06, 0x17, + 0x03, 0x41, 0x08, 0x9b, 0x43, 0xc2, 0xed, 0x79, 0xab, 0x9a, 0x79, 0xab, 0xd8, 0xc8, 0xe7, 0xee, + 0x90, 0xcc, 0x2c, 0xf8, 0xf6, 0xa2, 0x05, 0xa1, 0x73, 0x4e, 0x86, 0xf6, 0xcc, 0xba, 0x77, 0xf3, + 0xd6, 0x8d, 0xb8, 0xeb, 0x35, 0x5d, 0x9f, 0x87, 0x9c, 0x4d, 0x2f, 0x32, 0xff, 0x66, 0xc0, 0xde, + 0xbd, 0x5e, 0xaf, 0xd3, 0xf2, 0x07, 0x8c, 0x84, 0x61, 0xc7, 0xe6, 0xe7, 0xe8, 0x1a, 0x6c, 0x04, + 0x36, 0x3f, 0xaf, 0x1a, 0xd7, 0x8c, 0xc3, 0xb2, 0xb5, 0xfd, 0x6c, 0x5c, 0x5f, 0x9b, 0x8c, 0xeb, + 0x1b, 0x82, 0x87, 0x25, 0x07, 0xdd, 0x82, 0x92, 0xf8, 0xdb, 0x7b, 0x1a, 0x90, 0xea, 0xba, 0x94, + 0xaa, 0x4e, 0xc6, 0xf5, 0x52, 0x47, 0xd3, 0x5e, 0xa4, 0x7e, 0xe3, 0x58, 0x12, 0x75, 0x61, 0xeb, + 0xd4, 0x76, 0x2e, 0x88, 0xdf, 0xaf, 0x16, 0xae, 0x19, 0x87, 0x95, 0xa3, 0xc3, 0x46, 0xde, 0xf1, + 0x35, 0xb4, 0x3d, 0x96, 0x92, 0xb7, 0xf6, 0xb4, 0x11, 0x5b, 0x9a, 0x80, 0x23, 0x24, 0xf3, 0x0c, + 0xae, 0xa6, 0xec, 0xc7, 0x23, 0x8f, 0x3c, 0xb2, 0xbd, 0x11, 0x41, 0x6d, 0x28, 0x0a, 0xc5, 0x61, + 0xd5, 0xb8, 0xb6, 0x7e, 0x58, 0x39, 0x7a, 0x2b, 0x5f, 0xd5, 0xd4, 0xf6, 0xad, 0x1d, 0xad, 0xab, + 0x28, 0xbe, 0x42, 0xac, 0x60, 0xcc, 0x4f, 0x0c, 0x28, 0xb7, 0x3a, 0xef, 0xf5, 0xfb, 0x42, 0x0e, + 0x7d, 0x08, 0x25, 0x71, 0xde, 0x7d, 0x9b, 0xdb, 0xd2, 0x4d, 0x95, 0xa3, 0x6f, 0xa5, 0x14, 0xc4, + 0xee, 0x6f, 0x04, 0x17, 0x03, 0x41, 0x08, 0x1b, 0x42, 0x5a, 0x28, 0x7b, 0x78, 0xfa, 0x11, 0x71, + 0xf8, 0x03, 0xc2, 0x6d, 0x0b, 0x69, 0x3d, 0x90, 0xd0, 0x70, 0x8c, 0x8a, 0x5a, 0xb0, 0x11, 0x06, + 0xc4, 0xd1, 0x9e, 0x7a, 0xf3, 0x12, 0x4f, 0x45, 0x46, 0x75, 0x03, 0xe2, 0x24, 0xa7, 0x25, 0xbe, + 0xb0, 0x84, 0x30, 0x3f, 0x36, 0x60, 0x27, 0x96, 0x3a, 0x71, 0x43, 0x8e, 0x7e, 0x32, 0x63, 0x7e, + 0x63, 0x39, 0xf3, 0xc5, 0x6a, 0x69, 0xfc, 0x15, 0xad, 0xa7, 0x14, 0x51, 0x52, 0xa6, 0xdf, 0x83, + 0xa2, 0xcb, 0xc9, 0x30, 0xac, 0x16, 0xa4, 0xeb, 0x5f, 0x5f, 0xc2, 0xf6, 0xc4, 0xe9, 0x2d, 0xb1, + 0x12, 0x2b, 0x00, 0x73, 0x90, 0x32, 0x5c, 0x6c, 0x08, 0x3d, 0x82, 0x72, 0x60, 0x33, 0xe2, 0x73, + 0x4c, 0xce, 0xb4, 0xe5, 0x97, 0x9c, 0x6c, 0x27, 0x12, 0x25, 0x8c, 0xf8, 0x0e, 0xb1, 0x76, 0x26, + 0xe3, 0x7a, 0x39, 0x26, 0xe2, 0x04, 0xca, 0x7c, 0x08, 0x5b, 0xad, 0x8e, 0xe5, 0x51, 0xe7, 0x42, + 0x44, 0xbf, 0xe3, 0xf6, 0xd9, 0x74, 0xf4, 0x1f, 0xb7, 0x6e, 0x63, 0x2c, 0x39, 0xc8, 0x84, 0x4d, + 0xf2, 0xc4, 0x21, 0x01, 0x97, 0x1b, 0x2c, 0x5b, 0x30, 0x19, 0xd7, 0x37, 0xef, 0x48, 0x0a, 0xd6, + 0x1c, 0xf3, 0x37, 0x05, 0xd8, 0xd2, 0x41, 0xb5, 0x82, 0x60, 0xb9, 0x9b, 0x09, 0x96, 0xeb, 0x0b, + 0xd3, 0x2a, 0x2f, 0x54, 0xd0, 0x43, 0xd8, 0x0c, 0xb9, 0xcd, 0x47, 0xa1, 0x4c, 0xeb, 0xcb, 0xe3, + 0x4e, 0x43, 0x49, 0x71, 0x6b, 0x57, 0x83, 0x6d, 0xaa, 0x6f, 0xac, 0x61, 0xcc, 0x7f, 0x18, 0xb0, + 0x9b, 0xcd, 0x65, 0xf4, 0x08, 0xb6, 0x42, 0xc2, 0x1e, 0xbb, 0x0e, 0xa9, 0x6e, 0x48, 0x25, 0xcd, + 0xc5, 0x4a, 0x94, 0x7c, 0x54, 0x0d, 0x2a, 0xa2, 0x12, 0x68, 0x1a, 0x8e, 0xc0, 0xd0, 0x0f, 0xa1, + 0xc4, 0x48, 0x48, 0x47, 0xcc, 0x21, 0xda, 0xfa, 0x77, 0xd2, 0xc0, 0xa2, 0xaa, 0x0b, 0x48, 0x51, + 0x8a, 0xfa, 0x27, 0xd4, 0xb1, 0x3d, 0xe5, 0xca, 0x24, 0x3c, 0xb6, 0x45, 0x3c, 0x63, 0x0d, 0x81, + 0x63, 0x30, 0x51, 0x23, 0xb7, 0xb5, 0x21, 0xc7, 0x9e, 0xbd, 0x92, 0x03, 0x3d, 0xc9, 0x1c, 0xe8, + 0x37, 0x17, 0x3a, 0x48, 0xda, 0x95, 0x5b, 0x00, 0xfe, 0x6a, 0xc0, 0x95, 0xb4, 0xe0, 0x0a, 0x6a, + 0xc0, 0xfd, 0x6c, 0x0d, 0x78, 0x63, 0xb9, 0x1d, 0xe4, 0x94, 0x81, 0x7f, 0x1b, 0x50, 0x4f, 0x8b, + 0x75, 0x6c, 0x66, 0x0f, 0x09, 0x27, 0x2c, 0x8c, 0x0f, 0x0f, 0x1d, 0x42, 0xc9, 0xee, 0xb4, 0xee, + 0x32, 0x3a, 0x0a, 0xa2, 0xd4, 0x15, 0xa6, 0xbd, 0xa7, 0x69, 0x38, 0xe6, 0x8a, 0x04, 0xbf, 0x70, + 0x75, 0x0f, 0x4a, 0x25, 0xf8, 0x7d, 0xd7, 0xef, 0x63, 0xc9, 0x11, 0x12, 0xbe, 0x3d, 0x8c, 0x5a, + 0x5b, 0x2c, 0xd1, 0xb6, 0x87, 0x04, 0x4b, 0x0e, 0xaa, 0x43, 0x31, 0x74, 0x68, 0xa0, 0x22, 0xb8, + 0x6c, 0x95, 0x85, 0xc9, 0x5d, 0x41, 0xc0, 0x8a, 0x8e, 0x6e, 0x40, 0x59, 0x08, 0x86, 0x81, 0xed, + 0x90, 0x6a, 0x51, 0x0a, 0xc9, 0xea, 0xd3, 0x8e, 0x88, 0x38, 0xe1, 0x9b, 0x7f, 0x9a, 0x3a, 0x1f, + 0x59, 0xea, 0x8e, 0x00, 0x1c, 0xea, 0x73, 0x46, 0x3d, 0x8f, 0x44, 0xd5, 0x28, 0x0e, 0x9a, 0xe3, + 0x98, 0x83, 0x53, 0x52, 0xc8, 0x05, 0x08, 0x62, 0xdf, 0xe8, 0xe0, 0xf9, 0xee, 0x72, 0xae, 0x9f, + 0xe3, 0x53, 0x6b, 0x57, 0xa8, 0x4a, 0x31, 0x52, 0xe0, 0xe6, 0x9f, 0x0d, 0xa8, 0xe8, 0xf5, 0x2b, + 0x08, 0xa7, 0xf7, 0xb3, 0xe1, 0xf4, 0xf5, 0xc5, 0x83, 0xc3, 0xfc, 0x48, 0xfa, 0xc4, 0x80, 0x83, + 0xc8, 0x6a, 0x6a, 0xf7, 0x2d, 0xdb, 0xb3, 0x7d, 0x87, 0xb0, 0xa8, 0x52, 0x1f, 0x40, 0xc1, 0x8d, + 0xc2, 0x07, 0x34, 0x40, 0xa1, 0xd5, 0xc1, 0x05, 0x37, 0x40, 0x6f, 0x43, 0xe9, 0x9c, 0x86, 0x5c, + 0x06, 0x86, 0x0a, 0x9d, 0xd8, 0xe0, 0x7b, 0x9a, 0x8e, 0x63, 0x09, 0xd4, 0x81, 0x62, 0x40, 0x19, + 0x0f, 0xab, 0x1b, 0xd2, 0xe0, 0x1b, 0x0b, 0x0d, 0xee, 0x50, 0xc6, 0x75, 0x2d, 0x4d, 0x06, 0x10, + 0x81, 0x80, 0x15, 0x90, 0xf9, 0x4b, 0xf8, 0xca, 0x1c, 0xcb, 0xd5, 0x12, 0xf4, 0x73, 0xd8, 0x72, + 0x15, 0x53, 0xcf, 0x3b, 0xb7, 0x16, 0x2a, 0x9c, 0xb3, 0xff, 0x64, 0xcc, 0x8a, 0xc6, 0xa9, 0x08, + 0xd5, 0xfc, 0xa3, 0x01, 0xfb, 0x33, 0x96, 0xca, 0x49, 0x91, 0x32, 0x2e, 0x3d, 0x56, 0x4c, 0x4d, + 0x8a, 0x94, 0x71, 0x2c, 0x39, 0xe8, 0x3e, 0x94, 0xe4, 0xa0, 0xe9, 0x50, 0x4f, 0x7b, 0xad, 0x19, + 0x79, 0xad, 0xa3, 0xe9, 0x2f, 0xc6, 0xf5, 0xaf, 0xce, 0x4e, 0xdf, 0x8d, 0x88, 0x8d, 0x63, 0x00, + 0x91, 0x75, 0x84, 0x31, 0xca, 0x74, 0x62, 0xca, 0xac, 0xbb, 0x23, 0x08, 0x58, 0xd1, 0xcd, 0x3f, + 0x24, 0x41, 0x29, 0x26, 0x41, 0x61, 0x9f, 0x38, 0x91, 0xe9, 0x5e, 0x2e, 0xce, 0x0b, 0x4b, 0x0e, + 0x0a, 0xe0, 0x8a, 0x3b, 0x35, 0x3a, 0x2e, 0x5d, 0x74, 0xe3, 0x15, 0x56, 0x55, 0x23, 0x5f, 0x99, + 0xe6, 0xe0, 0x19, 0x74, 0xf3, 0x43, 0x98, 0x91, 0x12, 0xe5, 0xfe, 0x9c, 0xf3, 0x60, 0x4e, 0xe2, + 0xe4, 0xcf, 0xaa, 0x89, 0xf6, 0x92, 0xdc, 0x53, 0xaf, 0xd7, 0xc1, 0x12, 0xc5, 0xfc, 0xad, 0x01, + 0xaf, 0xcd, 0x6d, 0x9c, 0x71, 0x61, 0x33, 0x72, 0x0b, 0x5b, 0x5b, 0x9f, 0xa8, 0xf2, 0xc1, 0xdb, + 0xf9, 0x96, 0x64, 0x91, 0xc5, 0x89, 0xcf, 0x3b, 0x7f, 0xf3, 0x9f, 0x85, 0xf8, 0x44, 0x64, 0x55, + 0xfb, 0x41, 0xec, 0x6f, 0x59, 0x75, 0x84, 0x66, 0x5d, 0x43, 0xaf, 0xa6, 0xfc, 0x17, 0xf3, 0xf0, + 0x8c, 0x34, 0xea, 0xc3, 0x6e, 0x9f, 0x9c, 0xd9, 0x23, 0x8f, 0x6b, 0xdd, 0xda, 0x6b, 0xcb, 0x5f, + 0x26, 0xd0, 0x64, 0x5c, 0xdf, 0xbd, 0x9d, 0xc1, 0xc0, 0x53, 0x98, 0xe8, 0x18, 0xd6, 0xb9, 0x17, + 0x95, 0x9b, 0x6f, 0x2c, 0x84, 0xee, 0x9d, 0x74, 0xad, 0x8a, 0xde, 0xfe, 0x7a, 0xef, 0xa4, 0x8b, + 0xc5, 0x6a, 0xf4, 0x01, 0x14, 0xd9, 0xc8, 0x23, 0x62, 0x98, 0x5a, 0x5f, 0x6a, 0x2e, 0x13, 0x67, + 0x9a, 0xa4, 0xbf, 0xf8, 0x0a, 0xb1, 0x82, 0x30, 0x7f, 0x05, 0x3b, 0x99, 0x89, 0x0b, 0x0d, 0x61, + 0xdb, 0x4b, 0xa5, 0xb0, 0xf6, 0xc2, 0xbb, 0xff, 0x57, 0xde, 0xeb, 0x82, 0x73, 0x55, 0x6b, 0xdc, + 0x4e, 0xf3, 0x70, 0x06, 0xde, 0xb4, 0x01, 0x92, 0xbd, 0x8a, 0x4c, 0x14, 0xe9, 0xa3, 0xaa, 0x8d, + 0xce, 0x44, 0x91, 0x55, 0x21, 0x56, 0x74, 0xd1, 0xbd, 0x42, 0xe2, 0x30, 0xc2, 0xdb, 0x49, 0xbd, + 0x8c, 0xbb, 0x57, 0x37, 0xe6, 0xe0, 0x94, 0x94, 0xf9, 0x77, 0x03, 0x76, 0xda, 0xca, 0xe4, 0x0e, + 0xf5, 0x5c, 0xe7, 0xe9, 0x0a, 0x06, 0xad, 0x07, 0x99, 0x41, 0xeb, 0x92, 0x32, 0x9d, 0x31, 0x2c, + 0x77, 0xd2, 0xfa, 0x8b, 0x01, 0x5f, 0xce, 0x48, 0xde, 0x49, 0x8a, 0x51, 0xdc, 0x12, 0x8c, 0x45, + 0x2d, 0x21, 0x83, 0x20, 0x53, 0x6b, 0x6e, 0x4b, 0x40, 0x77, 0xa1, 0xc0, 0xa9, 0x8e, 0xd1, 0xa5, + 0xe1, 0x08, 0x61, 0x49, 0x6f, 0xeb, 0x51, 0x5c, 0xe0, 0xd4, 0xfc, 0xd4, 0x80, 0x6a, 0x46, 0x2a, + 0x5d, 0x44, 0x5f, 0xbd, 0xdd, 0x0f, 0x60, 0xe3, 0x8c, 0xd1, 0xe1, 0xcb, 0x58, 0x1e, 0x3b, 0xfd, + 0x7d, 0x46, 0x87, 0x58, 0xc2, 0x98, 0x9f, 0x19, 0xb0, 0x9f, 0x91, 0x5c, 0xc1, 0x40, 0x72, 0x92, + 0x1d, 0x48, 0xde, 0x5c, 0x72, 0x0f, 0x39, 0x63, 0xc9, 0x67, 0x85, 0xa9, 0x1d, 0x88, 0xbd, 0xa2, + 0x33, 0xa8, 0x04, 0xb4, 0xdf, 0x25, 0x1e, 0x71, 0x38, 0x9d, 0x97, 0xe0, 0x97, 0x6d, 0xc2, 0x3e, + 0x25, 0x5e, 0xb4, 0xd4, 0xda, 0x9b, 0x8c, 0xeb, 0x95, 0x4e, 0x82, 0x85, 0xd3, 0xc0, 0xe8, 0x09, + 0xec, 0xc7, 0xb3, 0x68, 0xac, 0xad, 0xf0, 0xf2, 0xda, 0x5e, 0x9b, 0x8c, 0xeb, 0xfb, 0xed, 0x69, + 0x44, 0x3c, 0xab, 0x04, 0xdd, 0x83, 0x2d, 0x37, 0x90, 0xd7, 0x6e, 0x7d, 0x63, 0xbb, 0x6c, 0xb0, + 0x53, 0xf7, 0x73, 0x75, 0xf9, 0xd3, 0x1f, 0x38, 0x5a, 0x6e, 0xfe, 0x6b, 0x3a, 0x06, 0x44, 0xc0, + 0xa1, 0xbb, 0xa9, 0xe9, 0x43, 0xf5, 0xbc, 0x1b, 0x2f, 0x37, 0x79, 0x64, 0xdb, 0x62, 0x7e, 0x11, + 0x1a, 0x71, 0xd7, 0x6b, 0xa8, 0xa7, 0xb6, 0x46, 0xcb, 0xe7, 0x0f, 0x59, 0x97, 0x33, 0xd7, 0x1f, + 0xa8, 0x16, 0x9d, 0x1a, 0x8b, 0xae, 0xc3, 0x96, 0xee, 0x9a, 0x72, 0xe3, 0x45, 0xb5, 0xab, 0x3b, + 0x8a, 0x84, 0x23, 0x9e, 0xf9, 0x62, 0x3a, 0x2e, 0x64, 0x0f, 0xfd, 0xe8, 0x95, 0xc5, 0xc5, 0x97, + 0x74, 0x34, 0xe6, 0xc7, 0xc6, 0x4f, 0x93, 0xc1, 0x52, 0x45, 0xfa, 0xd1, 0x92, 0x91, 0x9e, 0xee, + 0x68, 0xb9, 0x63, 0x25, 0xfa, 0x11, 0x6c, 0x12, 0x85, 0xae, 0x5a, 0xe4, 0xcd, 0x25, 0xd1, 0x93, + 0xb2, 0x9a, 0xbc, 0x3c, 0x68, 0x9a, 0x06, 0x44, 0xdf, 0x17, 0x5e, 0x12, 0xb2, 0xe2, 0xc2, 0xaf, + 0xe6, 0xf0, 0xb2, 0xf5, 0x35, 0xb5, 0xd9, 0x98, 0xfc, 0x42, 0x5c, 0x70, 0xe2, 0x4f, 0x9c, 0x5e, + 0x61, 0x7e, 0x6c, 0xc0, 0xde, 0xd4, 0x0b, 0x12, 0x7a, 0x1d, 0x8a, 0x83, 0xd4, 0x15, 0x33, 0xce, + 0x66, 0x75, 0xc7, 0x54, 0x3c, 0x71, 0x53, 0x88, 0x1f, 0x22, 0xa6, 0x6e, 0x0a, 0xb3, 0xaf, 0x0b, + 0xa8, 0x99, 0xbe, 0x29, 0xaa, 0xc1, 0x76, 0x5f, 0x8b, 0xcf, 0xbd, 0x2d, 0xc6, 0x43, 0xdc, 0x46, + 0xde, 0x10, 0x67, 0xfe, 0x0c, 0xd0, 0xec, 0x78, 0xb6, 0xc4, 0xf0, 0xf7, 0x06, 0x6c, 0xfa, 0xa3, + 0xe1, 0x29, 0x51, 0xd9, 0x5f, 0x4c, 0x5c, 0xdb, 0x96, 0x54, 0xac, 0xb9, 0xe6, 0xef, 0x0b, 0x50, + 0xd1, 0x0a, 0x8e, 0x5b, 0xb7, 0xf1, 0x0a, 0xda, 0xf4, 0xfd, 0x4c, 0x9b, 0x7e, 0x6b, 0xe1, 0x58, + 0x2a, 0xcc, 0xca, 0x7d, 0xe4, 0xea, 0x4e, 0x3d, 0x72, 0xdd, 0x58, 0x0e, 0xee, 0xf2, 0x87, 0xae, + 0x4f, 0x0d, 0xd8, 0x4b, 0x49, 0xaf, 0xa0, 0x05, 0x7d, 0x90, 0x6d, 0x41, 0xd7, 0x97, 0xda, 0x45, + 0x4e, 0x03, 0x3a, 0xca, 0x18, 0x2f, 0xab, 0x4c, 0x1d, 0x8a, 0x8e, 0xdb, 0x67, 0x99, 0x11, 0x4f, + 0x30, 0x43, 0xac, 0xe8, 0xe6, 0x13, 0xd8, 0x9f, 0x71, 0x0f, 0x72, 0xe4, 0xab, 0x45, 0xdf, 0xe5, + 0x2e, 0xf5, 0xa3, 0x89, 0xa1, 0xb9, 0xdc, 0xa6, 0x8f, 0xa3, 0x75, 0x99, 0x67, 0x0e, 0x0d, 0x85, + 0x53, 0xb0, 0xd6, 0xf7, 0x9e, 0x3d, 0xaf, 0xad, 0x7d, 0xfe, 0xbc, 0xb6, 0xf6, 0xc5, 0xf3, 0xda, + 0xda, 0xaf, 0x27, 0x35, 0xe3, 0xd9, 0xa4, 0x66, 0x7c, 0x3e, 0xa9, 0x19, 0x5f, 0x4c, 0x6a, 0xc6, + 0x7f, 0x26, 0x35, 0xe3, 0x77, 0xff, 0xad, 0xad, 0xfd, 0xb8, 0x9a, 0xf7, 0x5f, 0xa4, 0xff, 0x05, + 0x00, 0x00, 0xff, 0xff, 0xb5, 0x6b, 0x8c, 0x52, 0x60, 0x1a, 0x00, 0x00, } func (m *HTTPIngressPath) Marshal() (dAtA []byte, err error) { @@ -1028,7 +1274,7 @@ func (m *HTTPIngressRuleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *IPBlock) Marshal() (dAtA []byte, err error) { +func (m *IPAddress) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1038,34 +1284,40 @@ func (m *IPBlock) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *IPBlock) MarshalTo(dAtA []byte) (int, error) { +func (m *IPAddress) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *IPBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *IPAddress) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Except) > 0 { - for iNdEx := len(m.Except) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Except[iNdEx]) - copy(dAtA[i:], m.Except[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Except[iNdEx]))) - i-- - dAtA[i] = 0x12 + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= len(m.CIDR) - copy(dAtA[i:], m.CIDR) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.CIDR))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *Ingress) Marshal() (dAtA []byte, err error) { +func (m *IPAddressList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1075,38 +1327,32 @@ func (m *Ingress) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Ingress) MarshalTo(dAtA []byte) (int, error) { +func (m *IPAddressList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Ingress) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *IPAddressList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i-- - dAtA[i] = 0x12 { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1118,7 +1364,7 @@ func (m *Ingress) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *IngressBackend) Marshal() (dAtA []byte, err error) { +func (m *IPAddressSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1128,19 +1374,19 @@ func (m *IngressBackend) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *IngressBackend) MarshalTo(dAtA []byte) (int, error) { +func (m *IPAddressSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *IngressBackend) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *IPAddressSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Service != nil { + if m.ParentRef != nil { { - size, err := m.Service.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ParentRef.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1148,15 +1394,140 @@ func (m *IngressBackend) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0xa } - if m.Resource != nil { - { - size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size + return len(dAtA) - i, nil +} + +func (m *IPBlock) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IPBlock) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IPBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Except) > 0 { + for iNdEx := len(m.Except) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Except[iNdEx]) + copy(dAtA[i:], m.Except[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Except[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.CIDR) + copy(dAtA[i:], m.CIDR) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CIDR))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Ingress) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Ingress) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Ingress) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *IngressBackend) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IngressBackend) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IngressBackend) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Service != nil { + { + size, err := m.Service.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Resource != nil { + { + size, err := m.Resource.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- @@ -2137,6 +2508,49 @@ func (m *NetworkPolicySpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ParentReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ParentReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ParentReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x22 + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i-- + dAtA[i] = 0x1a + i -= len(m.Resource) + copy(dAtA[i:], m.Resource) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *ServiceBackendPort) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2168,72 +2582,284 @@ func (m *ServiceBackendPort) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *ServiceCIDR) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *HTTPIngressPath) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Path) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Backend.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.PathType != nil { - l = len(*m.PathType) - n += 1 + l + sovGenerated(uint64(l)) - } - return n + +func (m *ServiceCIDR) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *HTTPIngressRuleValue) Size() (n int) { - if m == nil { - return 0 - } +func (m *ServiceCIDR) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Paths) > 0 { - for _, e := range m.Paths { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n -} - -func (m *IPBlock) Size() (n int) { - if m == nil { - return 0 + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - var l int - _ = l - l = len(m.CIDR) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Except) > 0 { - for _, s := range m.Except { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *Ingress) Size() (n int) { - if m == nil { - return 0 +func (m *ServiceCIDRList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l + return dAtA[:n], nil +} + +func (m *ServiceCIDRList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceCIDRList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ServiceCIDRSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServiceCIDRSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceCIDRSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CIDRs) > 0 { + for iNdEx := len(m.CIDRs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CIDRs[iNdEx]) + copy(dAtA[i:], m.CIDRs[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CIDRs[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ServiceCIDRStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServiceCIDRStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceCIDRStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *HTTPIngressPath) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Backend.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.PathType != nil { + l = len(*m.PathType) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *HTTPIngressRuleValue) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Paths) > 0 { + for _, e := range m.Paths { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *IPAddress) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *IPAddressList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *IPAddressSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ParentRef != nil { + l = m.ParentRef.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *IPBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CIDR) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Except) > 0 { + for _, s := range m.Except { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *Ingress) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l l = m.ObjectMeta.Size() n += 1 + l + sovGenerated(uint64(l)) l = m.Spec.Size() @@ -2635,6 +3261,23 @@ func (m *NetworkPolicySpec) Size() (n int) { return n } +func (m *ParentReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Resource) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *ServiceBackendPort) Size() (n int) { if m == nil { return 0 @@ -2647,39 +3290,138 @@ func (m *ServiceBackendPort) Size() (n int) { return n } -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *HTTPIngressPath) String() string { - if this == nil { - return "nil" +func (m *ServiceCIDR) Size() (n int) { + if m == nil { + return 0 } - s := strings.Join([]string{`&HTTPIngressPath{`, - `Path:` + fmt.Sprintf("%v", this.Path) + `,`, - `Backend:` + strings.Replace(strings.Replace(this.Backend.String(), "IngressBackend", "IngressBackend", 1), `&`, ``, 1) + `,`, - `PathType:` + valueToStringGenerated(this.PathType) + `,`, - `}`, - }, "") - return s + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n } -func (this *HTTPIngressRuleValue) String() string { - if this == nil { - return "nil" + +func (m *ServiceCIDRList) Size() (n int) { + if m == nil { + return 0 } - repeatedStringForPaths := "[]HTTPIngressPath{" - for _, f := range this.Paths { - repeatedStringForPaths += strings.Replace(strings.Replace(f.String(), "HTTPIngressPath", "HTTPIngressPath", 1), `&`, ``, 1) + "," + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } } - repeatedStringForPaths += "}" + return n +} + +func (m *ServiceCIDRSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.CIDRs) > 0 { + for _, s := range m.CIDRs { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ServiceCIDRStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *HTTPIngressPath) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&HTTPIngressPath{`, + `Path:` + fmt.Sprintf("%v", this.Path) + `,`, + `Backend:` + strings.Replace(strings.Replace(this.Backend.String(), "IngressBackend", "IngressBackend", 1), `&`, ``, 1) + `,`, + `PathType:` + valueToStringGenerated(this.PathType) + `,`, + `}`, + }, "") + return s +} +func (this *HTTPIngressRuleValue) String() string { + if this == nil { + return "nil" + } + repeatedStringForPaths := "[]HTTPIngressPath{" + for _, f := range this.Paths { + repeatedStringForPaths += strings.Replace(strings.Replace(f.String(), "HTTPIngressPath", "HTTPIngressPath", 1), `&`, ``, 1) + "," + } + repeatedStringForPaths += "}" s := strings.Join([]string{`&HTTPIngressRuleValue{`, `Paths:` + repeatedStringForPaths + `,`, `}`, }, "") return s } +func (this *IPAddress) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&IPAddress{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "IPAddressSpec", "IPAddressSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *IPAddressList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]IPAddress{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "IPAddress", "IPAddress", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&IPAddressList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *IPAddressSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&IPAddressSpec{`, + `ParentRef:` + strings.Replace(this.ParentRef.String(), "ParentReference", "ParentReference", 1) + `,`, + `}`, + }, "") + return s +} func (this *IPBlock) String() string { if this == nil { return "nil" @@ -3018,6 +3760,19 @@ func (this *NetworkPolicySpec) String() string { }, "") return s } +func (this *ParentReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ParentReference{`, + `Group:` + fmt.Sprintf("%v", this.Group) + `,`, + `Resource:` + fmt.Sprintf("%v", this.Resource) + `,`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `}`, + }, "") + return s +} func (this *ServiceBackendPort) String() string { if this == nil { return "nil" @@ -3029,6 +3784,59 @@ func (this *ServiceBackendPort) String() string { }, "") return s } +func (this *ServiceCIDR) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ServiceCIDR{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ServiceCIDRSpec", "ServiceCIDRSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ServiceCIDRStatus", "ServiceCIDRStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ServiceCIDRList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]ServiceCIDR{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "ServiceCIDR", "ServiceCIDR", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&ServiceCIDRList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ServiceCIDRSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ServiceCIDRSpec{`, + `CIDRs:` + fmt.Sprintf("%v", this.CIDRs) + `,`, + `}`, + }, "") + return s +} +func (this *ServiceCIDRStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForConditions := "[]Condition{" + for _, f := range this.Conditions { + repeatedStringForConditions += fmt.Sprintf("%v", f) + "," + } + repeatedStringForConditions += "}" + s := strings.Join([]string{`&ServiceCIDRStatus{`, + `Conditions:` + repeatedStringForConditions + `,`, + `}`, + }, "") + return s +} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -3269,7 +4077,7 @@ func (m *HTTPIngressRuleValue) Unmarshal(dAtA []byte) error { } return nil } -func (m *IPBlock) Unmarshal(dAtA []byte) error { +func (m *IPAddress) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3292,17 +4100,17 @@ func (m *IPBlock) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IPBlock: wiretype end group for non-group") + return fmt.Errorf("proto: IPAddress: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IPBlock: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IPAddress: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CIDR", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3312,29 +4120,30 @@ func (m *IPBlock) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.CIDR = string(dAtA[iNdEx:postIndex]) + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Except", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3344,23 +4153,24 @@ func (m *IPBlock) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Except = append(m.Except, string(dAtA[iNdEx:postIndex])) + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -3383,7 +4193,7 @@ func (m *IPBlock) Unmarshal(dAtA []byte) error { } return nil } -func (m *Ingress) Unmarshal(dAtA []byte) error { +func (m *IPAddressList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3406,15 +4216,15 @@ func (m *Ingress) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Ingress: wiretype end group for non-group") + return fmt.Errorf("proto: IPAddressList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Ingress: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IPAddressList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3441,46 +4251,13 @@ func (m *Ingress) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3507,7 +4284,8 @@ func (m *Ingress) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Items = append(m.Items, IPAddress{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3532,7 +4310,7 @@ func (m *Ingress) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressBackend) Unmarshal(dAtA []byte) error { +func (m *IPAddressSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3555,51 +4333,15 @@ func (m *IngressBackend) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressBackend: wiretype end group for non-group") + return fmt.Errorf("proto: IPAddressSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressBackend: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IPAddressSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Resource == nil { - m.Resource = &v11.TypedLocalObjectReference{} - } - if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Service", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ParentRef", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3626,10 +4368,10 @@ func (m *IngressBackend) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Service == nil { - m.Service = &IngressServiceBackend{} + if m.ParentRef == nil { + m.ParentRef = &ParentReference{} } - if err := m.Service.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ParentRef.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3654,7 +4396,7 @@ func (m *IngressBackend) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressClass) Unmarshal(dAtA []byte) error { +func (m *IPBlock) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3677,17 +4419,17 @@ func (m *IngressClass) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressClass: wiretype end group for non-group") + return fmt.Errorf("proto: IPBlock: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressClass: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IPBlock: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CIDR", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3697,30 +4439,29 @@ func (m *IngressClass) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.CIDR = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Except", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3730,24 +4471,23 @@ func (m *IngressClass) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Except = append(m.Except, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex @@ -3770,7 +4510,7 @@ func (m *IngressClass) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressClassList) Unmarshal(dAtA []byte) error { +func (m *Ingress) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3793,15 +4533,15 @@ func (m *IngressClassList) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressClassList: wiretype end group for non-group") + return fmt.Errorf("proto: Ingress: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressClassList: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Ingress: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3828,13 +4568,13 @@ func (m *IngressClassList) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3861,8 +4601,40 @@ func (m *IngressClassList) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Items = append(m.Items, IngressClass{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3887,7 +4659,7 @@ func (m *IngressClassList) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { +func (m *IngressBackend) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3910,50 +4682,17 @@ func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressClassParametersReference: wiretype end group for non-group") + return fmt.Errorf("proto: IngressBackend: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressClassParametersReference: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressBackend: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field APIGroup", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.APIGroup = &s - iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3963,61 +4702,33 @@ func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Kind = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated + if m.Resource == nil { + m.Resource = &v11.TypedLocalObjectReference{} } - if postIndex > l { - return io.ErrUnexpectedEOF + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Service", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4027,57 +4738,27 @@ func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.Scope = &s - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated + if m.Service == nil { + m.Service = &IngressServiceBackend{} } - if postIndex > l { - return io.ErrUnexpectedEOF + if err := m.Service.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - s := string(dAtA[iNdEx:postIndex]) - m.Namespace = &s iNdEx = postIndex default: iNdEx = preIndex @@ -4100,7 +4781,7 @@ func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { +func (m *IngressClass) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4123,17 +4804,17 @@ func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressClassSpec: wiretype end group for non-group") + return fmt.Errorf("proto: IngressClass: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressClassSpec: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressClass: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Controller", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4143,27 +4824,28 @@ func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Controller = string(dAtA[iNdEx:postIndex]) + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Parameters", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4190,10 +4872,7 @@ func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Parameters == nil { - m.Parameters = &IngressClassParametersReference{} - } - if err := m.Parameters.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4218,7 +4897,7 @@ func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressList) Unmarshal(dAtA []byte) error { +func (m *IngressClassList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4241,10 +4920,10 @@ func (m *IngressList) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressList: wiretype end group for non-group") + return fmt.Errorf("proto: IngressClassList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressList: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressClassList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -4309,7 +4988,7 @@ func (m *IngressList) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Items = append(m.Items, Ingress{}) + m.Items = append(m.Items, IngressClass{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -4335,7 +5014,7 @@ func (m *IngressList) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { +func (m *IngressClassParametersReference) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4358,15 +5037,15 @@ func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressLoadBalancerIngress: wiretype end group for non-group") + return fmt.Errorf("proto: IngressClassParametersReference: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressLoadBalancerIngress: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressClassParametersReference: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field APIGroup", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4394,11 +5073,12 @@ func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.IP = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.APIGroup = &s iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4426,13 +5106,45 @@ func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Hostname = string(dAtA[iNdEx:postIndex]) + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4442,25 +5154,57 @@ func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Ports = append(m.Ports, IngressPortStatus{}) - if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + s := string(dAtA[iNdEx:postIndex]) + m.Scope = &s + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF } + s := string(dAtA[iNdEx:postIndex]) + m.Namespace = &s iNdEx = postIndex default: iNdEx = preIndex @@ -4483,7 +5227,7 @@ func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressLoadBalancerStatus) Unmarshal(dAtA []byte) error { +func (m *IngressClassSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4506,15 +5250,47 @@ func (m *IngressLoadBalancerStatus) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressLoadBalancerStatus: wiretype end group for non-group") + return fmt.Errorf("proto: IngressClassSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressLoadBalancerStatus: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressClassSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ingress", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Controller", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Controller = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Parameters", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4541,8 +5317,10 @@ func (m *IngressLoadBalancerStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Ingress = append(m.Ingress, IngressLoadBalancerIngress{}) - if err := m.Ingress[len(m.Ingress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.Parameters == nil { + m.Parameters = &IngressClassParametersReference{} + } + if err := m.Parameters.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4567,7 +5345,7 @@ func (m *IngressLoadBalancerStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { +func (m *IngressList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4590,36 +5368,17 @@ func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressPortStatus: wiretype end group for non-group") + return fmt.Errorf("proto: IngressList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressPortStatus: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) - } - m.Port = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Port |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Protocol", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4629,29 +5388,30 @@ func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Protocol = k8s_io_api_core_v1.Protocol(dAtA[iNdEx:postIndex]) + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4661,24 +5421,25 @@ func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.Error = &s + m.Items = append(m.Items, Ingress{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -4701,7 +5462,7 @@ func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressRule) Unmarshal(dAtA []byte) error { +func (m *IngressLoadBalancerIngress) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4724,15 +5485,15 @@ func (m *IngressRule) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressRule: wiretype end group for non-group") + return fmt.Errorf("proto: IngressLoadBalancerIngress: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressRule: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressLoadBalancerIngress: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Host", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4760,11 +5521,43 @@ func (m *IngressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Host = string(dAtA[iNdEx:postIndex]) + m.IP = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IngressRuleValue", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hostname = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4791,7 +5584,8 @@ func (m *IngressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.IngressRuleValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Ports = append(m.Ports, IngressPortStatus{}) + if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4816,7 +5610,7 @@ func (m *IngressRule) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressRuleValue) Unmarshal(dAtA []byte) error { +func (m *IngressLoadBalancerStatus) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4839,15 +5633,15 @@ func (m *IngressRuleValue) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressRuleValue: wiretype end group for non-group") + return fmt.Errorf("proto: IngressLoadBalancerStatus: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressRuleValue: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressLoadBalancerStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field HTTP", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Ingress", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4874,10 +5668,8 @@ func (m *IngressRuleValue) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.HTTP == nil { - m.HTTP = &HTTPIngressRuleValue{} - } - if err := m.HTTP.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Ingress = append(m.Ingress, IngressLoadBalancerIngress{}) + if err := m.Ingress[len(m.Ingress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4902,7 +5694,7 @@ func (m *IngressRuleValue) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { +func (m *IngressPortStatus) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4925,15 +5717,34 @@ func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressServiceBackend: wiretype end group for non-group") + return fmt.Errorf("proto: IngressPortStatus: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressServiceBackend: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressPortStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Port |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Protocol", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4961,13 +5772,13 @@ func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.Protocol = k8s_io_api_core_v1.Protocol(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -4977,24 +5788,24 @@ func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Port.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := string(dAtA[iNdEx:postIndex]) + m.Error = &s iNdEx = postIndex default: iNdEx = preIndex @@ -5017,7 +5828,7 @@ func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressSpec) Unmarshal(dAtA []byte) error { +func (m *IngressRule) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5040,17 +5851,17 @@ func (m *IngressSpec) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressSpec: wiretype end group for non-group") + return fmt.Errorf("proto: IngressRule: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressSpec: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressRule: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DefaultBackend", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Host", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5060,65 +5871,27 @@ func (m *IngressSpec) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.DefaultBackend == nil { - m.DefaultBackend = &IngressBackend{} - } - if err := m.DefaultBackend.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Host = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TLS", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.TLS = append(m.TLS, IngressTLS{}) - if err := m.TLS[len(m.TLS)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Rules", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field IngressRuleValue", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5145,44 +5918,10 @@ func (m *IngressSpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Rules = append(m.Rules, IngressRule{}) - if err := m.Rules[len(m.Rules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.IngressRuleValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IngressClassName", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.IngressClassName = &s - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -5204,7 +5943,7 @@ func (m *IngressSpec) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressStatus) Unmarshal(dAtA []byte) error { +func (m *IngressRuleValue) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5227,15 +5966,15 @@ func (m *IngressStatus) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressStatus: wiretype end group for non-group") + return fmt.Errorf("proto: IngressRuleValue: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressStatus: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressRuleValue: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LoadBalancer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field HTTP", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5262,7 +6001,10 @@ func (m *IngressStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.LoadBalancer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.HTTP == nil { + m.HTTP = &HTTPIngressRuleValue{} + } + if err := m.HTTP.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5287,7 +6029,7 @@ func (m *IngressStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *IngressTLS) Unmarshal(dAtA []byte) error { +func (m *IngressServiceBackend) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5310,15 +6052,15 @@ func (m *IngressTLS) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: IngressTLS: wiretype end group for non-group") + return fmt.Errorf("proto: IngressServiceBackend: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: IngressTLS: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressServiceBackend: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5346,13 +6088,13 @@ func (m *IngressTLS) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Hosts = append(m.Hosts, string(dAtA[iNdEx:postIndex])) + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SecretName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5362,23 +6104,24 @@ func (m *IngressTLS) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.SecretName = string(dAtA[iNdEx:postIndex]) + if err := m.Port.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -5401,7 +6144,7 @@ func (m *IngressTLS) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicy) Unmarshal(dAtA []byte) error { +func (m *IngressSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5424,15 +6167,15 @@ func (m *NetworkPolicy) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicy: wiretype end group for non-group") + return fmt.Errorf("proto: IngressSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DefaultBackend", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5459,13 +6202,16 @@ func (m *NetworkPolicy) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.DefaultBackend == nil { + m.DefaultBackend = &IngressBackend{} + } + if err := m.DefaultBackend.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TLS", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5492,63 +6238,14 @@ func (m *NetworkPolicy) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.TLS = append(m.TLS, IngressTLS{}) + if err := m.TLS[len(m.TLS)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *NetworkPolicyEgressRule) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicyEgressRule: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicyEgressRule: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Rules", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5575,16 +6272,99 @@ func (m *NetworkPolicyEgressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Ports = append(m.Ports, NetworkPolicyPort{}) - if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Rules = append(m.Rules, IngressRule{}) + if err := m.Rules[len(m.Rules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 2: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field To", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field IngressClassName", wireType) } - var msglen int + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.IngressClassName = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IngressStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IngressStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IngressStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LoadBalancer", wireType) + } + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5609,8 +6389,7 @@ func (m *NetworkPolicyEgressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.To = append(m.To, NetworkPolicyPeer{}) - if err := m.To[len(m.To)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.LoadBalancer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5635,7 +6414,7 @@ func (m *NetworkPolicyEgressRule) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { +func (m *IngressTLS) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5658,15 +6437,129 @@ func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicyIngressRule: wiretype end group for non-group") + return fmt.Errorf("proto: IngressTLS: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicyIngressRule: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: IngressTLS: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hosts = append(m.Hosts, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SecretName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SecretName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicy) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5693,14 +6586,13 @@ func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Ports = append(m.Ports, NetworkPolicyPort{}) - if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field From", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5727,10 +6619,1020 @@ func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.From = append(m.From, NetworkPolicyPeer{}) - if err := m.From[len(m.From)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { return err } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicyEgressRule) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicyEgressRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicyEgressRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ports = append(m.Ports, NetworkPolicyPort{}) + if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field To", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.To = append(m.To, NetworkPolicyPeer{}) + if err := m.To[len(m.To)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicyIngressRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicyIngressRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ports = append(m.Ports, NetworkPolicyPort{}) + if err := m.Ports[len(m.Ports)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field From", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.From = append(m.From, NetworkPolicyPeer{}) + if err := m.From[len(m.From)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicyList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicyList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, NetworkPolicy{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicyPeer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicyPeer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PodSelector == nil { + m.PodSelector = &v1.LabelSelector{} + } + if err := m.PodSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NamespaceSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NamespaceSelector == nil { + m.NamespaceSelector = &v1.LabelSelector{} + } + if err := m.NamespaceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IPBlock", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IPBlock == nil { + m.IPBlock = &IPBlock{} + } + if err := m.IPBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicyPort: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicyPort: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Protocol", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := k8s_io_api_core_v1.Protocol(dAtA[iNdEx:postIndex]) + m.Protocol = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Port == nil { + m.Port = &intstr.IntOrString{} + } + if err := m.Port.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EndPort", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.EndPort = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NetworkPolicySpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NetworkPolicySpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NetworkPolicySpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PodSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PodSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ingress", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ingress = append(m.Ingress, NetworkPolicyIngressRule{}) + if err := m.Ingress[len(m.Ingress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Egress", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Egress = append(m.Egress, NetworkPolicyEgressRule{}) + if err := m.Egress[len(m.Egress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PolicyTypes", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PolicyTypes = append(m.PolicyTypes, PolicyType(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ParentReference) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ParentReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ParentReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Resource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -5753,7 +7655,7 @@ func (m *NetworkPolicyIngressRule) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { +func (m *ServiceBackendPort) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5776,17 +7678,17 @@ func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicyList: wiretype end group for non-group") + return fmt.Errorf("proto: ServiceBackendPort: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicyList: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ServiceBackendPort: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5796,30 +7698,29 @@ func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Number", wireType) } - var msglen int + m.Number = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5829,26 +7730,11 @@ func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + m.Number |= int32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, NetworkPolicy{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -5870,7 +7756,7 @@ func (m *NetworkPolicyList) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { +func (m *ServiceCIDR) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5893,15 +7779,15 @@ func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicyPeer: wiretype end group for non-group") + return fmt.Errorf("proto: ServiceCIDR: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicyPeer: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ServiceCIDR: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PodSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5928,16 +7814,13 @@ func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.PodSelector == nil { - m.PodSelector = &v1.LabelSelector{} - } - if err := m.PodSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NamespaceSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5964,16 +7847,13 @@ func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.NamespaceSelector == nil { - m.NamespaceSelector = &v1.LabelSelector{} - } - if err := m.NamespaceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IPBlock", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6000,10 +7880,7 @@ func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.IPBlock == nil { - m.IPBlock = &IPBlock{} - } - if err := m.IPBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -6028,7 +7905,7 @@ func (m *NetworkPolicyPeer) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { +func (m *ServiceCIDRList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6051,17 +7928,17 @@ func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicyPort: wiretype end group for non-group") + return fmt.Errorf("proto: ServiceCIDRList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicyPort: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ServiceCIDRList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Protocol", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6071,28 +7948,28 @@ func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := k8s_io_api_core_v1.Protocol(dAtA[iNdEx:postIndex]) - m.Protocol = &s + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6119,33 +7996,11 @@ func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Port == nil { - m.Port = &intstr.IntOrString{} - } - if err := m.Port.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Items = append(m.Items, ServiceCIDR{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field EndPort", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.EndPort = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6167,7 +8022,7 @@ func (m *NetworkPolicyPort) Unmarshal(dAtA []byte) error { } return nil } -func (m *NetworkPolicySpec) Unmarshal(dAtA []byte) error { +func (m *ServiceCIDRSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6190,116 +8045,15 @@ func (m *NetworkPolicySpec) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: NetworkPolicySpec: wiretype end group for non-group") + return fmt.Errorf("proto: ServiceCIDRSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: NetworkPolicySpec: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ServiceCIDRSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PodSelector", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.PodSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Ingress", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Ingress = append(m.Ingress, NetworkPolicyIngressRule{}) - if err := m.Ingress[len(m.Ingress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Egress", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Egress = append(m.Egress, NetworkPolicyEgressRule{}) - if err := m.Egress[len(m.Egress)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PolicyTypes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CIDRs", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6327,7 +8081,7 @@ func (m *NetworkPolicySpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.PolicyTypes = append(m.PolicyTypes, PolicyType(dAtA[iNdEx:postIndex])) + m.CIDRs = append(m.CIDRs, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex @@ -6350,7 +8104,7 @@ func (m *NetworkPolicySpec) Unmarshal(dAtA []byte) error { } return nil } -func (m *ServiceBackendPort) Unmarshal(dAtA []byte) error { +func (m *ServiceCIDRStatus) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6373,17 +8127,17 @@ func (m *ServiceBackendPort) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ServiceBackendPort: wiretype end group for non-group") + return fmt.Errorf("proto: ServiceCIDRStatus: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ServiceBackendPort: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ServiceCIDRStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6393,43 +8147,26 @@ func (m *ServiceBackendPort) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Number", wireType) - } - m.Number = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Number |= int32(b&0x7F) << shift - if b < 0x80 { - break - } + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/networking/v1/generated.proto b/go-controller/vendor/k8s.io/api/networking/v1/generated.proto index c72fdc8f37..e3e3e9215e 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/networking/v1/generated.proto @@ -72,6 +72,44 @@ message HTTPIngressRuleValue { repeated HTTPIngressPath paths = 1; } +// IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs +// that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. +// An IP address can be represented in different formats, to guarantee the uniqueness of the IP, +// the name of the object is the IP address in canonical format, four decimal digits separated +// by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. +// Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 +// Invalid: 10.01.2.3 or 2001:db8:0:0:0::1 +message IPAddress { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec is the desired state of the IPAddress. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional IPAddressSpec spec = 2; +} + +// IPAddressList contains a list of IPAddress. +message IPAddressList { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // items is the list of IPAddresses. + repeated IPAddress items = 2; +} + +// IPAddressSpec describe the attributes in an IP Address. +message IPAddressSpec { + // ParentRef references the resource that an IPAddress is attached to. + // An IPAddress must reference a parent object. + // +required + optional ParentReference parentRef = 1; +} + // IPBlock describes a particular CIDR (Ex. "192.168.1.0/24","2001:db8::/64") that is allowed // to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs // that should not be included within this rule. @@ -540,6 +578,25 @@ message NetworkPolicySpec { repeated string policyTypes = 4; } +// ParentReference describes a reference to a parent object. +message ParentReference { + // Group is the group of the object being referenced. + // +optional + optional string group = 1; + + // Resource is the resource of the object being referenced. + // +required + optional string resource = 2; + + // Namespace is the namespace of the object being referenced. + // +optional + optional string namespace = 3; + + // Name is the name of the object being referenced. + // +required + optional string name = 4; +} + // ServiceBackendPort is the service port being referenced. // +structType=atomic message ServiceBackendPort { @@ -554,3 +611,55 @@ message ServiceBackendPort { optional int32 number = 2; } +// ServiceCIDR defines a range of IP addresses using CIDR format (e.g. 192.168.0.0/24 or 2001:db2::/64). +// This range is used to allocate ClusterIPs to Service objects. +message ServiceCIDR { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // spec is the desired state of the ServiceCIDR. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional ServiceCIDRSpec spec = 2; + + // status represents the current state of the ServiceCIDR. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional ServiceCIDRStatus status = 3; +} + +// ServiceCIDRList contains a list of ServiceCIDR objects. +message ServiceCIDRList { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // items is the list of ServiceCIDRs. + repeated ServiceCIDR items = 2; +} + +// ServiceCIDRSpec define the CIDRs the user wants to use for allocating ClusterIPs for Services. +message ServiceCIDRSpec { + // CIDRs defines the IP blocks in CIDR notation (e.g. "192.168.0.0/24" or "2001:db8::/64") + // from which to assign service cluster IPs. Max of two CIDRs is allowed, one of each IP family. + // This field is immutable. + // +optional + // +listType=atomic + repeated string cidrs = 1; +} + +// ServiceCIDRStatus describes the current state of the ServiceCIDR. +message ServiceCIDRStatus { + // conditions holds an array of metav1.Condition that describe the state of the ServiceCIDR. + // Current service state + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + repeated .k8s.io.apimachinery.pkg.apis.meta.v1.Condition conditions = 1; +} + diff --git a/go-controller/vendor/k8s.io/api/networking/v1/register.go b/go-controller/vendor/k8s.io/api/networking/v1/register.go index a200d54370..b9bdcb78c9 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/register.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/register.go @@ -50,6 +50,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { &IngressClassList{}, &NetworkPolicy{}, &NetworkPolicyList{}, + &IPAddress{}, + &IPAddressList{}, + &ServiceCIDR{}, + &ServiceCIDRList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/go-controller/vendor/k8s.io/api/networking/v1/types.go b/go-controller/vendor/k8s.io/api/networking/v1/types.go index d75e27558d..216647ceeb 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/types.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/types.go @@ -635,3 +635,133 @@ type IngressClassList struct { // items is the list of IngressClasses. Items []IngressClass `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs +// that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. +// An IP address can be represented in different formats, to guarantee the uniqueness of the IP, +// the name of the object is the IP address in canonical format, four decimal digits separated +// by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. +// Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 +// Invalid: 10.01.2.3 or 2001:db8:0:0:0::1 +type IPAddress struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // spec is the desired state of the IPAddress. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec IPAddressSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` +} + +// IPAddressSpec describe the attributes in an IP Address. +type IPAddressSpec struct { + // ParentRef references the resource that an IPAddress is attached to. + // An IPAddress must reference a parent object. + // +required + ParentRef *ParentReference `json:"parentRef,omitempty" protobuf:"bytes,1,opt,name=parentRef"` +} + +// ParentReference describes a reference to a parent object. +type ParentReference struct { + // Group is the group of the object being referenced. + // +optional + Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"` + // Resource is the resource of the object being referenced. + // +required + Resource string `json:"resource,omitempty" protobuf:"bytes,2,opt,name=resource"` + // Namespace is the namespace of the object being referenced. + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` + // Name is the name of the object being referenced. + // +required + Name string `json:"name,omitempty" protobuf:"bytes,4,opt,name=name"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// IPAddressList contains a list of IPAddress. +type IPAddressList struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // items is the list of IPAddresses. + Items []IPAddress `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// ServiceCIDR defines a range of IP addresses using CIDR format (e.g. 192.168.0.0/24 or 2001:db2::/64). +// This range is used to allocate ClusterIPs to Service objects. +type ServiceCIDR struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // spec is the desired state of the ServiceCIDR. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec ServiceCIDRSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + // status represents the current state of the ServiceCIDR. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status ServiceCIDRStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// ServiceCIDRSpec define the CIDRs the user wants to use for allocating ClusterIPs for Services. +type ServiceCIDRSpec struct { + // CIDRs defines the IP blocks in CIDR notation (e.g. "192.168.0.0/24" or "2001:db8::/64") + // from which to assign service cluster IPs. Max of two CIDRs is allowed, one of each IP family. + // This field is immutable. + // +optional + // +listType=atomic + CIDRs []string `json:"cidrs,omitempty" protobuf:"bytes,1,opt,name=cidrs"` +} + +const ( + // ServiceCIDRConditionReady represents status of a ServiceCIDR that is ready to be used by the + // apiserver to allocate ClusterIPs for Services. + ServiceCIDRConditionReady = "Ready" + // ServiceCIDRReasonTerminating represents a reason where a ServiceCIDR is not ready because it is + // being deleted. + ServiceCIDRReasonTerminating = "Terminating" +) + +// ServiceCIDRStatus describes the current state of the ServiceCIDR. +type ServiceCIDRStatus struct { + // conditions holds an array of metav1.Condition that describe the state of the ServiceCIDR. + // Current service state + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// ServiceCIDRList contains a list of ServiceCIDR objects. +type ServiceCIDRList struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + // items is the list of ServiceCIDRs. + Items []ServiceCIDR `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/go-controller/vendor/k8s.io/api/networking/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/networking/v1/types_swagger_doc_generated.go index ff080540d3..0e294848ba 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/types_swagger_doc_generated.go @@ -47,6 +47,35 @@ func (HTTPIngressRuleValue) SwaggerDoc() map[string]string { return map_HTTPIngressRuleValue } +var map_IPAddress = map[string]string{ + "": "IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. An IP address can be represented in different formats, to guarantee the uniqueness of the IP, the name of the object is the IP address in canonical format, four decimal digits separated by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 Invalid: 10.01.2.3 or 2001:db8:0:0:0::1", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", +} + +func (IPAddress) SwaggerDoc() map[string]string { + return map_IPAddress +} + +var map_IPAddressList = map[string]string{ + "": "IPAddressList contains a list of IPAddress.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "items": "items is the list of IPAddresses.", +} + +func (IPAddressList) SwaggerDoc() map[string]string { + return map_IPAddressList +} + +var map_IPAddressSpec = map[string]string{ + "": "IPAddressSpec describe the attributes in an IP Address.", + "parentRef": "ParentRef references the resource that an IPAddress is attached to. An IPAddress must reference a parent object.", +} + +func (IPAddressSpec) SwaggerDoc() map[string]string { + return map_IPAddressSpec +} + var map_IPBlock = map[string]string{ "": "IPBlock describes a particular CIDR (Ex. \"192.168.1.0/24\",\"2001:db8::/64\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.", "cidr": "cidr is a string representing the IPBlock Valid examples are \"192.168.1.0/24\" or \"2001:db8::/64\"", @@ -294,6 +323,18 @@ func (NetworkPolicySpec) SwaggerDoc() map[string]string { return map_NetworkPolicySpec } +var map_ParentReference = map[string]string{ + "": "ParentReference describes a reference to a parent object.", + "group": "Group is the group of the object being referenced.", + "resource": "Resource is the resource of the object being referenced.", + "namespace": "Namespace is the namespace of the object being referenced.", + "name": "Name is the name of the object being referenced.", +} + +func (ParentReference) SwaggerDoc() map[string]string { + return map_ParentReference +} + var map_ServiceBackendPort = map[string]string{ "": "ServiceBackendPort is the service port being referenced.", "name": "name is the name of the port on the Service. This is a mutually exclusive setting with \"Number\".", @@ -304,4 +345,43 @@ func (ServiceBackendPort) SwaggerDoc() map[string]string { return map_ServiceBackendPort } +var map_ServiceCIDR = map[string]string{ + "": "ServiceCIDR defines a range of IP addresses using CIDR format (e.g. 192.168.0.0/24 or 2001:db2::/64). This range is used to allocate ClusterIPs to Service objects.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec is the desired state of the ServiceCIDR. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "status": "status represents the current state of the ServiceCIDR. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", +} + +func (ServiceCIDR) SwaggerDoc() map[string]string { + return map_ServiceCIDR +} + +var map_ServiceCIDRList = map[string]string{ + "": "ServiceCIDRList contains a list of ServiceCIDR objects.", + "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "items": "items is the list of ServiceCIDRs.", +} + +func (ServiceCIDRList) SwaggerDoc() map[string]string { + return map_ServiceCIDRList +} + +var map_ServiceCIDRSpec = map[string]string{ + "": "ServiceCIDRSpec define the CIDRs the user wants to use for allocating ClusterIPs for Services.", + "cidrs": "CIDRs defines the IP blocks in CIDR notation (e.g. \"192.168.0.0/24\" or \"2001:db8::/64\") from which to assign service cluster IPs. Max of two CIDRs is allowed, one of each IP family. This field is immutable.", +} + +func (ServiceCIDRSpec) SwaggerDoc() map[string]string { + return map_ServiceCIDRSpec +} + +var map_ServiceCIDRStatus = map[string]string{ + "": "ServiceCIDRStatus describes the current state of the ServiceCIDR.", + "conditions": "conditions holds an array of metav1.Condition that describe the state of the ServiceCIDR. Current service state", +} + +func (ServiceCIDRStatus) SwaggerDoc() map[string]string { + return map_ServiceCIDRStatus +} + // AUTO-GENERATED FUNCTIONS END HERE diff --git a/go-controller/vendor/k8s.io/api/networking/v1/well_known_labels.go b/go-controller/vendor/k8s.io/api/networking/v1/well_known_labels.go new file mode 100644 index 0000000000..28e2e8f3f6 --- /dev/null +++ b/go-controller/vendor/k8s.io/api/networking/v1/well_known_labels.go @@ -0,0 +1,33 @@ +/* +Copyright 2023 The Kubernetes Authors. + +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 v1 + +const ( + + // TODO: Use IPFamily as field with a field selector,And the value is set based on + // the name at create time and immutable. + // LabelIPAddressFamily is used to indicate the IP family of a Kubernetes IPAddress. + // This label simplify dual-stack client operations allowing to obtain the list of + // IP addresses filtered by family. + LabelIPAddressFamily = "ipaddress.kubernetes.io/ip-family" + // LabelManagedBy is used to indicate the controller or entity that manages + // an IPAddress. This label aims to enable different IPAddress + // objects to be managed by different controllers or entities within the + // same cluster. It is highly recommended to configure this label for all + // IPAddress objects. + LabelManagedBy = "ipaddress.kubernetes.io/managed-by" +) diff --git a/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.deepcopy.go b/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.deepcopy.go index 540873833f..9ce6435a46 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.deepcopy.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.deepcopy.go @@ -73,6 +73,87 @@ func (in *HTTPIngressRuleValue) DeepCopy() *HTTPIngressRuleValue { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAddress) DeepCopyInto(out *IPAddress) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddress. +func (in *IPAddress) DeepCopy() *IPAddress { + if in == nil { + return nil + } + out := new(IPAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAddress) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAddressList) DeepCopyInto(out *IPAddressList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPAddress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressList. +func (in *IPAddressList) DeepCopy() *IPAddressList { + if in == nil { + return nil + } + out := new(IPAddressList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAddressList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) { + *out = *in + if in.ParentRef != nil { + in, out := &in.ParentRef, &out.ParentRef + *out = new(ParentReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec. +func (in *IPAddressSpec) DeepCopy() *IPAddressSpec { + if in == nil { + return nil + } + out := new(IPAddressSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPBlock) DeepCopyInto(out *IPBlock) { *out = *in @@ -711,6 +792,22 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParentReference) DeepCopyInto(out *ParentReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentReference. +func (in *ParentReference) DeepCopy() *ParentReference { + if in == nil { + return nil + } + out := new(ParentReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceBackendPort) DeepCopyInto(out *ServiceBackendPort) { *out = *in @@ -726,3 +823,108 @@ func (in *ServiceBackendPort) DeepCopy() *ServiceBackendPort { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceCIDR) DeepCopyInto(out *ServiceCIDR) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceCIDR. +func (in *ServiceCIDR) DeepCopy() *ServiceCIDR { + if in == nil { + return nil + } + out := new(ServiceCIDR) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceCIDR) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceCIDRList) DeepCopyInto(out *ServiceCIDRList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ServiceCIDR, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceCIDRList. +func (in *ServiceCIDRList) DeepCopy() *ServiceCIDRList { + if in == nil { + return nil + } + out := new(ServiceCIDRList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceCIDRList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceCIDRSpec) DeepCopyInto(out *ServiceCIDRSpec) { + *out = *in + if in.CIDRs != nil { + in, out := &in.CIDRs, &out.CIDRs + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceCIDRSpec. +func (in *ServiceCIDRSpec) DeepCopy() *ServiceCIDRSpec { + if in == nil { + return nil + } + out := new(ServiceCIDRSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceCIDRStatus) DeepCopyInto(out *ServiceCIDRStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceCIDRStatus. +func (in *ServiceCIDRStatus) DeepCopy() *ServiceCIDRStatus { + if in == nil { + return nil + } + out := new(ServiceCIDRStatus) + in.DeepCopyInto(out) + return out +} diff --git a/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.prerelease-lifecycle.go b/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.prerelease-lifecycle.go index 21e8c671a5..6894d8c539 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.prerelease-lifecycle.go +++ b/go-controller/vendor/k8s.io/api/networking/v1/zz_generated.prerelease-lifecycle.go @@ -21,6 +21,18 @@ limitations under the License. package v1 +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *IPAddress) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *IPAddressList) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + // APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. // It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. func (in *Ingress) APILifecycleIntroduced() (major, minor int) { @@ -56,3 +68,15 @@ func (in *NetworkPolicy) APILifecycleIntroduced() (major, minor int) { func (in *NetworkPolicyList) APILifecycleIntroduced() (major, minor int) { return 1, 19 } + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ServiceCIDR) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *ServiceCIDRList) APILifecycleIntroduced() (major, minor int) { + return 1, 33 +} diff --git a/go-controller/vendor/k8s.io/api/networking/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/networking/v1alpha1/doc.go index 3827b0418f..55264ae707 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/networking/v1alpha1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=networking.k8s.io -package v1alpha1 // import "k8s.io/api/networking/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/networking/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/networking/v1beta1/doc.go index fa6d01cea0..c5a03e04e8 100644 --- a/go-controller/vendor/k8s.io/api/networking/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/networking/v1beta1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=networking.k8s.io -package v1beta1 // import "k8s.io/api/networking/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/node/v1/doc.go b/go-controller/vendor/k8s.io/api/node/v1/doc.go index 57ca52445b..3239af7039 100644 --- a/go-controller/vendor/k8s.io/api/node/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/node/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=node.k8s.io -package v1 // import "k8s.io/api/node/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/node/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/node/v1alpha1/doc.go index dfe99540b5..2f3d46ac20 100644 --- a/go-controller/vendor/k8s.io/api/node/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/node/v1alpha1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +groupName=node.k8s.io -package v1alpha1 // import "k8s.io/api/node/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/node/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/node/v1beta1/doc.go index c76ba89c48..7b47c8df66 100644 --- a/go-controller/vendor/k8s.io/api/node/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/node/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=node.k8s.io -package v1beta1 // import "k8s.io/api/node/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/policy/v1/doc.go b/go-controller/vendor/k8s.io/api/policy/v1/doc.go index c51e02685a..ff47e7fd49 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/policy/v1/doc.go @@ -22,4 +22,4 @@ limitations under the License. // Package policy is for any kind of policy object. Suitable examples, even if // they aren't all here, are PodDisruptionBudget, // NetworkPolicy, etc. -package v1 // import "k8s.io/api/policy/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/policy/v1/generated.proto b/go-controller/vendor/k8s.io/api/policy/v1/generated.proto index 57128e8112..9534890723 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1/generated.proto +++ b/go-controller/vendor/k8s.io/api/policy/v1/generated.proto @@ -115,9 +115,6 @@ message PodDisruptionBudgetSpec { // Additional policies may be added in the future. // Clients making eviction decisions should disallow eviction of unhealthy pods // if they encounter an unrecognized policy in this field. - // - // This field is beta-level. The eviction API uses this field when - // the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default). // +optional optional string unhealthyPodEvictionPolicy = 4; } diff --git a/go-controller/vendor/k8s.io/api/policy/v1/types.go b/go-controller/vendor/k8s.io/api/policy/v1/types.go index f05367ebe4..4e74367894 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1/types.go +++ b/go-controller/vendor/k8s.io/api/policy/v1/types.go @@ -70,9 +70,6 @@ type PodDisruptionBudgetSpec struct { // Additional policies may be added in the future. // Clients making eviction decisions should disallow eviction of unhealthy pods // if they encounter an unrecognized policy in this field. - // - // This field is beta-level. The eviction API uses this field when - // the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default). // +optional UnhealthyPodEvictionPolicy *UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty" protobuf:"bytes,4,opt,name=unhealthyPodEvictionPolicy"` } diff --git a/go-controller/vendor/k8s.io/api/policy/v1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/policy/v1/types_swagger_doc_generated.go index 799b0794a9..9b2f5b9450 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/policy/v1/types_swagger_doc_generated.go @@ -63,7 +63,7 @@ var map_PodDisruptionBudgetSpec = map[string]string{ "minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".", "selector": "Label query over pods whose evictions are managed by the disruption budget. A null selector will match no pods, while an empty ({}) selector will select all pods within the namespace.", "maxUnavailable": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".", - "unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is beta-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default).", + "unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.", } func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/policy/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/policy/v1beta1/doc.go index 76da54b4c7..777106c600 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/policy/v1beta1/doc.go @@ -22,4 +22,4 @@ limitations under the License. // Package policy is for any kind of policy object. Suitable examples, even if // they aren't all here, are PodDisruptionBudget, // NetworkPolicy, etc. -package v1beta1 // import "k8s.io/api/policy/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/policy/v1beta1/generated.proto b/go-controller/vendor/k8s.io/api/policy/v1beta1/generated.proto index 91e33f2332..e0cbe00f1c 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1beta1/generated.proto +++ b/go-controller/vendor/k8s.io/api/policy/v1beta1/generated.proto @@ -115,9 +115,6 @@ message PodDisruptionBudgetSpec { // Additional policies may be added in the future. // Clients making eviction decisions should disallow eviction of unhealthy pods // if they encounter an unrecognized policy in this field. - // - // This field is beta-level. The eviction API uses this field when - // the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default). // +optional optional string unhealthyPodEvictionPolicy = 4; } diff --git a/go-controller/vendor/k8s.io/api/policy/v1beta1/types.go b/go-controller/vendor/k8s.io/api/policy/v1beta1/types.go index bc5f970d27..9bba454f94 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1beta1/types.go +++ b/go-controller/vendor/k8s.io/api/policy/v1beta1/types.go @@ -67,9 +67,6 @@ type PodDisruptionBudgetSpec struct { // Additional policies may be added in the future. // Clients making eviction decisions should disallow eviction of unhealthy pods // if they encounter an unrecognized policy in this field. - // - // This field is beta-level. The eviction API uses this field when - // the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default). // +optional UnhealthyPodEvictionPolicy *UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty" protobuf:"bytes,4,opt,name=unhealthyPodEvictionPolicy"` } diff --git a/go-controller/vendor/k8s.io/api/policy/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/policy/v1beta1/types_swagger_doc_generated.go index 4a79d75949..cffc9a548c 100644 --- a/go-controller/vendor/k8s.io/api/policy/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/policy/v1beta1/types_swagger_doc_generated.go @@ -63,7 +63,7 @@ var map_PodDisruptionBudgetSpec = map[string]string{ "minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".", "selector": "Label query over pods whose evictions are managed by the disruption budget. A null selector selects no pods. An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods. In policy/v1, an empty selector will select all pods in the namespace.", "maxUnavailable": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".", - "unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is beta-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default).", + "unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.", } func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string { diff --git a/go-controller/vendor/k8s.io/api/rbac/v1/doc.go b/go-controller/vendor/k8s.io/api/rbac/v1/doc.go index b0e4e5b5b5..408546274b 100644 --- a/go-controller/vendor/k8s.io/api/rbac/v1/doc.go +++ b/go-controller/vendor/k8s.io/api/rbac/v1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +k8s:prerelease-lifecycle-gen=true // +groupName=rbac.authorization.k8s.io -package v1 // import "k8s.io/api/rbac/v1" +package v1 diff --git a/go-controller/vendor/k8s.io/api/rbac/v1alpha1/doc.go b/go-controller/vendor/k8s.io/api/rbac/v1alpha1/doc.go index 918b8a337c..70d3c0e971 100644 --- a/go-controller/vendor/k8s.io/api/rbac/v1alpha1/doc.go +++ b/go-controller/vendor/k8s.io/api/rbac/v1alpha1/doc.go @@ -20,4 +20,4 @@ limitations under the License. // +groupName=rbac.authorization.k8s.io -package v1alpha1 // import "k8s.io/api/rbac/v1alpha1" +package v1alpha1 diff --git a/go-controller/vendor/k8s.io/api/rbac/v1beta1/doc.go b/go-controller/vendor/k8s.io/api/rbac/v1beta1/doc.go index 156f273e69..504a58d8bf 100644 --- a/go-controller/vendor/k8s.io/api/rbac/v1beta1/doc.go +++ b/go-controller/vendor/k8s.io/api/rbac/v1beta1/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=rbac.authorization.k8s.io -package v1beta1 // import "k8s.io/api/rbac/v1beta1" +package v1beta1 diff --git a/go-controller/vendor/k8s.io/api/resource/v1alpha3/doc.go b/go-controller/vendor/k8s.io/api/resource/v1alpha3/doc.go index ffc21307d0..82e64f1d00 100644 --- a/go-controller/vendor/k8s.io/api/resource/v1alpha3/doc.go +++ b/go-controller/vendor/k8s.io/api/resource/v1alpha3/doc.go @@ -21,4 +21,4 @@ limitations under the License. // +groupName=resource.k8s.io // Package v1alpha3 is the v1alpha3 version of the resource API. -package v1alpha3 // import "k8s.io/api/resource/v1alpha3" +package v1alpha3 diff --git a/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.pb.go b/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.pb.go index 540f7b8184..716492fea4 100644 --- a/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.pb.go +++ b/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.pb.go @@ -29,6 +29,7 @@ import ( v11 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" math "math" math_bits "math/bits" @@ -161,10 +162,66 @@ func (m *CELDeviceSelector) XXX_DiscardUnknown() { var xxx_messageInfo_CELDeviceSelector proto.InternalMessageInfo +func (m *Counter) Reset() { *m = Counter{} } +func (*Counter) ProtoMessage() {} +func (*Counter) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{4} +} +func (m *Counter) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Counter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *Counter) XXX_Merge(src proto.Message) { + xxx_messageInfo_Counter.Merge(m, src) +} +func (m *Counter) XXX_Size() int { + return m.Size() +} +func (m *Counter) XXX_DiscardUnknown() { + xxx_messageInfo_Counter.DiscardUnknown(m) +} + +var xxx_messageInfo_Counter proto.InternalMessageInfo + +func (m *CounterSet) Reset() { *m = CounterSet{} } +func (*CounterSet) ProtoMessage() {} +func (*CounterSet) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{5} +} +func (m *CounterSet) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CounterSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *CounterSet) XXX_Merge(src proto.Message) { + xxx_messageInfo_CounterSet.Merge(m, src) +} +func (m *CounterSet) XXX_Size() int { + return m.Size() +} +func (m *CounterSet) XXX_DiscardUnknown() { + xxx_messageInfo_CounterSet.DiscardUnknown(m) +} + +var xxx_messageInfo_CounterSet proto.InternalMessageInfo + func (m *Device) Reset() { *m = Device{} } func (*Device) ProtoMessage() {} func (*Device) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{4} + return fileDescriptor_66649ee9bbcd89d2, []int{6} } func (m *Device) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -192,7 +249,7 @@ var xxx_messageInfo_Device proto.InternalMessageInfo func (m *DeviceAllocationConfiguration) Reset() { *m = DeviceAllocationConfiguration{} } func (*DeviceAllocationConfiguration) ProtoMessage() {} func (*DeviceAllocationConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{5} + return fileDescriptor_66649ee9bbcd89d2, []int{7} } func (m *DeviceAllocationConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -220,7 +277,7 @@ var xxx_messageInfo_DeviceAllocationConfiguration proto.InternalMessageInfo func (m *DeviceAllocationResult) Reset() { *m = DeviceAllocationResult{} } func (*DeviceAllocationResult) ProtoMessage() {} func (*DeviceAllocationResult) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{6} + return fileDescriptor_66649ee9bbcd89d2, []int{8} } func (m *DeviceAllocationResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -248,7 +305,7 @@ var xxx_messageInfo_DeviceAllocationResult proto.InternalMessageInfo func (m *DeviceAttribute) Reset() { *m = DeviceAttribute{} } func (*DeviceAttribute) ProtoMessage() {} func (*DeviceAttribute) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{7} + return fileDescriptor_66649ee9bbcd89d2, []int{9} } func (m *DeviceAttribute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -276,7 +333,7 @@ var xxx_messageInfo_DeviceAttribute proto.InternalMessageInfo func (m *DeviceClaim) Reset() { *m = DeviceClaim{} } func (*DeviceClaim) ProtoMessage() {} func (*DeviceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{8} + return fileDescriptor_66649ee9bbcd89d2, []int{10} } func (m *DeviceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -304,7 +361,7 @@ var xxx_messageInfo_DeviceClaim proto.InternalMessageInfo func (m *DeviceClaimConfiguration) Reset() { *m = DeviceClaimConfiguration{} } func (*DeviceClaimConfiguration) ProtoMessage() {} func (*DeviceClaimConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{9} + return fileDescriptor_66649ee9bbcd89d2, []int{11} } func (m *DeviceClaimConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -332,7 +389,7 @@ var xxx_messageInfo_DeviceClaimConfiguration proto.InternalMessageInfo func (m *DeviceClass) Reset() { *m = DeviceClass{} } func (*DeviceClass) ProtoMessage() {} func (*DeviceClass) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{10} + return fileDescriptor_66649ee9bbcd89d2, []int{12} } func (m *DeviceClass) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -360,7 +417,7 @@ var xxx_messageInfo_DeviceClass proto.InternalMessageInfo func (m *DeviceClassConfiguration) Reset() { *m = DeviceClassConfiguration{} } func (*DeviceClassConfiguration) ProtoMessage() {} func (*DeviceClassConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{11} + return fileDescriptor_66649ee9bbcd89d2, []int{13} } func (m *DeviceClassConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -388,7 +445,7 @@ var xxx_messageInfo_DeviceClassConfiguration proto.InternalMessageInfo func (m *DeviceClassList) Reset() { *m = DeviceClassList{} } func (*DeviceClassList) ProtoMessage() {} func (*DeviceClassList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{12} + return fileDescriptor_66649ee9bbcd89d2, []int{14} } func (m *DeviceClassList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -416,7 +473,7 @@ var xxx_messageInfo_DeviceClassList proto.InternalMessageInfo func (m *DeviceClassSpec) Reset() { *m = DeviceClassSpec{} } func (*DeviceClassSpec) ProtoMessage() {} func (*DeviceClassSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{13} + return fileDescriptor_66649ee9bbcd89d2, []int{15} } func (m *DeviceClassSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -444,7 +501,7 @@ var xxx_messageInfo_DeviceClassSpec proto.InternalMessageInfo func (m *DeviceConfiguration) Reset() { *m = DeviceConfiguration{} } func (*DeviceConfiguration) ProtoMessage() {} func (*DeviceConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{14} + return fileDescriptor_66649ee9bbcd89d2, []int{16} } func (m *DeviceConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -472,7 +529,7 @@ var xxx_messageInfo_DeviceConfiguration proto.InternalMessageInfo func (m *DeviceConstraint) Reset() { *m = DeviceConstraint{} } func (*DeviceConstraint) ProtoMessage() {} func (*DeviceConstraint) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{15} + return fileDescriptor_66649ee9bbcd89d2, []int{17} } func (m *DeviceConstraint) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -497,10 +554,38 @@ func (m *DeviceConstraint) XXX_DiscardUnknown() { var xxx_messageInfo_DeviceConstraint proto.InternalMessageInfo +func (m *DeviceCounterConsumption) Reset() { *m = DeviceCounterConsumption{} } +func (*DeviceCounterConsumption) ProtoMessage() {} +func (*DeviceCounterConsumption) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{18} +} +func (m *DeviceCounterConsumption) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceCounterConsumption) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceCounterConsumption) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceCounterConsumption.Merge(m, src) +} +func (m *DeviceCounterConsumption) XXX_Size() int { + return m.Size() +} +func (m *DeviceCounterConsumption) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceCounterConsumption.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceCounterConsumption proto.InternalMessageInfo + func (m *DeviceRequest) Reset() { *m = DeviceRequest{} } func (*DeviceRequest) ProtoMessage() {} func (*DeviceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{16} + return fileDescriptor_66649ee9bbcd89d2, []int{19} } func (m *DeviceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -528,7 +613,7 @@ var xxx_messageInfo_DeviceRequest proto.InternalMessageInfo func (m *DeviceRequestAllocationResult) Reset() { *m = DeviceRequestAllocationResult{} } func (*DeviceRequestAllocationResult) ProtoMessage() {} func (*DeviceRequestAllocationResult) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{17} + return fileDescriptor_66649ee9bbcd89d2, []int{20} } func (m *DeviceRequestAllocationResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -556,7 +641,7 @@ var xxx_messageInfo_DeviceRequestAllocationResult proto.InternalMessageInfo func (m *DeviceSelector) Reset() { *m = DeviceSelector{} } func (*DeviceSelector) ProtoMessage() {} func (*DeviceSelector) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{18} + return fileDescriptor_66649ee9bbcd89d2, []int{21} } func (m *DeviceSelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -581,10 +666,206 @@ func (m *DeviceSelector) XXX_DiscardUnknown() { var xxx_messageInfo_DeviceSelector proto.InternalMessageInfo +func (m *DeviceSubRequest) Reset() { *m = DeviceSubRequest{} } +func (*DeviceSubRequest) ProtoMessage() {} +func (*DeviceSubRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{22} +} +func (m *DeviceSubRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceSubRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceSubRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceSubRequest.Merge(m, src) +} +func (m *DeviceSubRequest) XXX_Size() int { + return m.Size() +} +func (m *DeviceSubRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceSubRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceSubRequest proto.InternalMessageInfo + +func (m *DeviceTaint) Reset() { *m = DeviceTaint{} } +func (*DeviceTaint) ProtoMessage() {} +func (*DeviceTaint) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{23} +} +func (m *DeviceTaint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceTaint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceTaint) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceTaint.Merge(m, src) +} +func (m *DeviceTaint) XXX_Size() int { + return m.Size() +} +func (m *DeviceTaint) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceTaint.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceTaint proto.InternalMessageInfo + +func (m *DeviceTaintRule) Reset() { *m = DeviceTaintRule{} } +func (*DeviceTaintRule) ProtoMessage() {} +func (*DeviceTaintRule) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{24} +} +func (m *DeviceTaintRule) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceTaintRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceTaintRule) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceTaintRule.Merge(m, src) +} +func (m *DeviceTaintRule) XXX_Size() int { + return m.Size() +} +func (m *DeviceTaintRule) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceTaintRule.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceTaintRule proto.InternalMessageInfo + +func (m *DeviceTaintRuleList) Reset() { *m = DeviceTaintRuleList{} } +func (*DeviceTaintRuleList) ProtoMessage() {} +func (*DeviceTaintRuleList) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{25} +} +func (m *DeviceTaintRuleList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceTaintRuleList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceTaintRuleList) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceTaintRuleList.Merge(m, src) +} +func (m *DeviceTaintRuleList) XXX_Size() int { + return m.Size() +} +func (m *DeviceTaintRuleList) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceTaintRuleList.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceTaintRuleList proto.InternalMessageInfo + +func (m *DeviceTaintRuleSpec) Reset() { *m = DeviceTaintRuleSpec{} } +func (*DeviceTaintRuleSpec) ProtoMessage() {} +func (*DeviceTaintRuleSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{26} +} +func (m *DeviceTaintRuleSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceTaintRuleSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceTaintRuleSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceTaintRuleSpec.Merge(m, src) +} +func (m *DeviceTaintRuleSpec) XXX_Size() int { + return m.Size() +} +func (m *DeviceTaintRuleSpec) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceTaintRuleSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceTaintRuleSpec proto.InternalMessageInfo + +func (m *DeviceTaintSelector) Reset() { *m = DeviceTaintSelector{} } +func (*DeviceTaintSelector) ProtoMessage() {} +func (*DeviceTaintSelector) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{27} +} +func (m *DeviceTaintSelector) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceTaintSelector) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceTaintSelector) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceTaintSelector.Merge(m, src) +} +func (m *DeviceTaintSelector) XXX_Size() int { + return m.Size() +} +func (m *DeviceTaintSelector) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceTaintSelector.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceTaintSelector proto.InternalMessageInfo + +func (m *DeviceToleration) Reset() { *m = DeviceToleration{} } +func (*DeviceToleration) ProtoMessage() {} +func (*DeviceToleration) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{28} +} +func (m *DeviceToleration) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceToleration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceToleration) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceToleration.Merge(m, src) +} +func (m *DeviceToleration) XXX_Size() int { + return m.Size() +} +func (m *DeviceToleration) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceToleration.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceToleration proto.InternalMessageInfo + func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } func (*NetworkDeviceData) ProtoMessage() {} func (*NetworkDeviceData) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{19} + return fileDescriptor_66649ee9bbcd89d2, []int{29} } func (m *NetworkDeviceData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -612,7 +893,7 @@ var xxx_messageInfo_NetworkDeviceData proto.InternalMessageInfo func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (*OpaqueDeviceConfiguration) ProtoMessage() {} func (*OpaqueDeviceConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{20} + return fileDescriptor_66649ee9bbcd89d2, []int{30} } func (m *OpaqueDeviceConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -640,7 +921,7 @@ var xxx_messageInfo_OpaqueDeviceConfiguration proto.InternalMessageInfo func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } func (*ResourceClaim) ProtoMessage() {} func (*ResourceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{21} + return fileDescriptor_66649ee9bbcd89d2, []int{31} } func (m *ResourceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -668,7 +949,7 @@ var xxx_messageInfo_ResourceClaim proto.InternalMessageInfo func (m *ResourceClaimConsumerReference) Reset() { *m = ResourceClaimConsumerReference{} } func (*ResourceClaimConsumerReference) ProtoMessage() {} func (*ResourceClaimConsumerReference) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{22} + return fileDescriptor_66649ee9bbcd89d2, []int{32} } func (m *ResourceClaimConsumerReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -696,7 +977,7 @@ var xxx_messageInfo_ResourceClaimConsumerReference proto.InternalMessageInfo func (m *ResourceClaimList) Reset() { *m = ResourceClaimList{} } func (*ResourceClaimList) ProtoMessage() {} func (*ResourceClaimList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{23} + return fileDescriptor_66649ee9bbcd89d2, []int{33} } func (m *ResourceClaimList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -724,7 +1005,7 @@ var xxx_messageInfo_ResourceClaimList proto.InternalMessageInfo func (m *ResourceClaimSpec) Reset() { *m = ResourceClaimSpec{} } func (*ResourceClaimSpec) ProtoMessage() {} func (*ResourceClaimSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{24} + return fileDescriptor_66649ee9bbcd89d2, []int{34} } func (m *ResourceClaimSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -752,7 +1033,7 @@ var xxx_messageInfo_ResourceClaimSpec proto.InternalMessageInfo func (m *ResourceClaimStatus) Reset() { *m = ResourceClaimStatus{} } func (*ResourceClaimStatus) ProtoMessage() {} func (*ResourceClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{25} + return fileDescriptor_66649ee9bbcd89d2, []int{35} } func (m *ResourceClaimStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -780,7 +1061,7 @@ var xxx_messageInfo_ResourceClaimStatus proto.InternalMessageInfo func (m *ResourceClaimTemplate) Reset() { *m = ResourceClaimTemplate{} } func (*ResourceClaimTemplate) ProtoMessage() {} func (*ResourceClaimTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{26} + return fileDescriptor_66649ee9bbcd89d2, []int{36} } func (m *ResourceClaimTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -808,7 +1089,7 @@ var xxx_messageInfo_ResourceClaimTemplate proto.InternalMessageInfo func (m *ResourceClaimTemplateList) Reset() { *m = ResourceClaimTemplateList{} } func (*ResourceClaimTemplateList) ProtoMessage() {} func (*ResourceClaimTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{27} + return fileDescriptor_66649ee9bbcd89d2, []int{37} } func (m *ResourceClaimTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -836,7 +1117,7 @@ var xxx_messageInfo_ResourceClaimTemplateList proto.InternalMessageInfo func (m *ResourceClaimTemplateSpec) Reset() { *m = ResourceClaimTemplateSpec{} } func (*ResourceClaimTemplateSpec) ProtoMessage() {} func (*ResourceClaimTemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{28} + return fileDescriptor_66649ee9bbcd89d2, []int{38} } func (m *ResourceClaimTemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -864,7 +1145,7 @@ var xxx_messageInfo_ResourceClaimTemplateSpec proto.InternalMessageInfo func (m *ResourcePool) Reset() { *m = ResourcePool{} } func (*ResourcePool) ProtoMessage() {} func (*ResourcePool) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{29} + return fileDescriptor_66649ee9bbcd89d2, []int{39} } func (m *ResourcePool) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -892,7 +1173,7 @@ var xxx_messageInfo_ResourcePool proto.InternalMessageInfo func (m *ResourceSlice) Reset() { *m = ResourceSlice{} } func (*ResourceSlice) ProtoMessage() {} func (*ResourceSlice) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{30} + return fileDescriptor_66649ee9bbcd89d2, []int{40} } func (m *ResourceSlice) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -920,7 +1201,7 @@ var xxx_messageInfo_ResourceSlice proto.InternalMessageInfo func (m *ResourceSliceList) Reset() { *m = ResourceSliceList{} } func (*ResourceSliceList) ProtoMessage() {} func (*ResourceSliceList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{31} + return fileDescriptor_66649ee9bbcd89d2, []int{41} } func (m *ResourceSliceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -948,7 +1229,7 @@ var xxx_messageInfo_ResourceSliceList proto.InternalMessageInfo func (m *ResourceSliceSpec) Reset() { *m = ResourceSliceSpec{} } func (*ResourceSliceSpec) ProtoMessage() {} func (*ResourceSliceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{32} + return fileDescriptor_66649ee9bbcd89d2, []int{42} } func (m *ResourceSliceSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -980,6 +1261,9 @@ func init() { proto.RegisterMapType((map[QualifiedName]DeviceAttribute)(nil), "k8s.io.api.resource.v1alpha3.BasicDevice.AttributesEntry") proto.RegisterMapType((map[QualifiedName]resource.Quantity)(nil), "k8s.io.api.resource.v1alpha3.BasicDevice.CapacityEntry") proto.RegisterType((*CELDeviceSelector)(nil), "k8s.io.api.resource.v1alpha3.CELDeviceSelector") + proto.RegisterType((*Counter)(nil), "k8s.io.api.resource.v1alpha3.Counter") + proto.RegisterType((*CounterSet)(nil), "k8s.io.api.resource.v1alpha3.CounterSet") + proto.RegisterMapType((map[string]Counter)(nil), "k8s.io.api.resource.v1alpha3.CounterSet.CountersEntry") proto.RegisterType((*Device)(nil), "k8s.io.api.resource.v1alpha3.Device") proto.RegisterType((*DeviceAllocationConfiguration)(nil), "k8s.io.api.resource.v1alpha3.DeviceAllocationConfiguration") proto.RegisterType((*DeviceAllocationResult)(nil), "k8s.io.api.resource.v1alpha3.DeviceAllocationResult") @@ -992,9 +1276,18 @@ func init() { proto.RegisterType((*DeviceClassSpec)(nil), "k8s.io.api.resource.v1alpha3.DeviceClassSpec") proto.RegisterType((*DeviceConfiguration)(nil), "k8s.io.api.resource.v1alpha3.DeviceConfiguration") proto.RegisterType((*DeviceConstraint)(nil), "k8s.io.api.resource.v1alpha3.DeviceConstraint") + proto.RegisterType((*DeviceCounterConsumption)(nil), "k8s.io.api.resource.v1alpha3.DeviceCounterConsumption") + proto.RegisterMapType((map[string]Counter)(nil), "k8s.io.api.resource.v1alpha3.DeviceCounterConsumption.CountersEntry") proto.RegisterType((*DeviceRequest)(nil), "k8s.io.api.resource.v1alpha3.DeviceRequest") proto.RegisterType((*DeviceRequestAllocationResult)(nil), "k8s.io.api.resource.v1alpha3.DeviceRequestAllocationResult") proto.RegisterType((*DeviceSelector)(nil), "k8s.io.api.resource.v1alpha3.DeviceSelector") + proto.RegisterType((*DeviceSubRequest)(nil), "k8s.io.api.resource.v1alpha3.DeviceSubRequest") + proto.RegisterType((*DeviceTaint)(nil), "k8s.io.api.resource.v1alpha3.DeviceTaint") + proto.RegisterType((*DeviceTaintRule)(nil), "k8s.io.api.resource.v1alpha3.DeviceTaintRule") + proto.RegisterType((*DeviceTaintRuleList)(nil), "k8s.io.api.resource.v1alpha3.DeviceTaintRuleList") + proto.RegisterType((*DeviceTaintRuleSpec)(nil), "k8s.io.api.resource.v1alpha3.DeviceTaintRuleSpec") + proto.RegisterType((*DeviceTaintSelector)(nil), "k8s.io.api.resource.v1alpha3.DeviceTaintSelector") + proto.RegisterType((*DeviceToleration)(nil), "k8s.io.api.resource.v1alpha3.DeviceToleration") proto.RegisterType((*NetworkDeviceData)(nil), "k8s.io.api.resource.v1alpha3.NetworkDeviceData") proto.RegisterType((*OpaqueDeviceConfiguration)(nil), "k8s.io.api.resource.v1alpha3.OpaqueDeviceConfiguration") proto.RegisterType((*ResourceClaim)(nil), "k8s.io.api.resource.v1alpha3.ResourceClaim") @@ -1016,134 +1309,172 @@ func init() { } var fileDescriptor_66649ee9bbcd89d2 = []byte{ - // 2030 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x19, 0xcd, 0x6f, 0x1c, 0x57, - 0xdd, 0xb3, 0xe3, 0xcf, 0xdf, 0xfa, 0x2b, 0x2f, 0xa4, 0x38, 0xa6, 0xec, 0x3a, 0x53, 0x04, 0x4e, - 0x9b, 0xee, 0x36, 0x4e, 0xd5, 0x16, 0xc2, 0x01, 0x8f, 0xed, 0x06, 0x47, 0x89, 0xe3, 0x3c, 0xb7, - 0x11, 0x81, 0x12, 0x78, 0x9e, 0x7d, 0xb6, 0x07, 0xcf, 0xce, 0x4c, 0xe7, 0xbd, 0x71, 0xea, 0x0b, - 0xaa, 0xe0, 0x1e, 0xf1, 0x0f, 0x20, 0x0e, 0x48, 0x48, 0x5c, 0x80, 0xff, 0x00, 0x24, 0x90, 0x88, - 0xe0, 0x12, 0x09, 0x0e, 0x3d, 0x2d, 0xcd, 0x22, 0xce, 0xdc, 0x73, 0x42, 0xef, 0xcd, 0x9b, 0xcf, - 0xdd, 0x71, 0xc6, 0x55, 0xb1, 0xd2, 0xdb, 0xce, 0xef, 0xfb, 0xfd, 0xbe, 0xdf, 0x5b, 0xb8, 0x72, - 0xf8, 0x0e, 0x6b, 0xd9, 0x5e, 0x9b, 0xf8, 0x76, 0x3b, 0xa0, 0xcc, 0x0b, 0x03, 0x8b, 0xb6, 0x8f, - 0xae, 0x12, 0xc7, 0x3f, 0x20, 0xd7, 0xda, 0xfb, 0xd4, 0xa5, 0x01, 0xe1, 0xb4, 0xd3, 0xf2, 0x03, - 0x8f, 0x7b, 0xe8, 0xe5, 0x88, 0xba, 0x45, 0x7c, 0xbb, 0x15, 0x53, 0xb7, 0x62, 0xea, 0xc5, 0xd7, - 0xf7, 0x6d, 0x7e, 0x10, 0xee, 0xb6, 0x2c, 0xaf, 0xdb, 0xde, 0xf7, 0xf6, 0xbd, 0xb6, 0x64, 0xda, - 0x0d, 0xf7, 0xe4, 0x97, 0xfc, 0x90, 0xbf, 0x22, 0x61, 0x8b, 0x46, 0x46, 0xb5, 0xe5, 0x05, 0x42, - 0x6d, 0x51, 0xe1, 0xe2, 0x9b, 0x29, 0x4d, 0x97, 0x58, 0x07, 0xb6, 0x4b, 0x83, 0xe3, 0xb6, 0x7f, - 0xb8, 0x9f, 0xb7, 0xf7, 0x34, 0x5c, 0xac, 0xdd, 0xa5, 0x9c, 0x0c, 0xd3, 0xd5, 0x2e, 0xe3, 0x0a, - 0x42, 0x97, 0xdb, 0xdd, 0x41, 0x35, 0x6f, 0x3d, 0x8f, 0x81, 0x59, 0x07, 0xb4, 0x4b, 0x8a, 0x7c, - 0xc6, 0xaf, 0x75, 0xb8, 0xb0, 0xea, 0x38, 0x9e, 0x25, 0x60, 0xeb, 0xf4, 0xc8, 0xb6, 0xe8, 0x0e, - 0x27, 0x3c, 0x64, 0xe8, 0xeb, 0x30, 0xde, 0x09, 0xec, 0x23, 0x1a, 0x2c, 0x68, 0x4b, 0xda, 0xf2, - 0x94, 0x39, 0xfb, 0xb8, 0xd7, 0x1c, 0xe9, 0xf7, 0x9a, 0xe3, 0xeb, 0x12, 0x8a, 0x15, 0x16, 0x2d, - 0xc1, 0xa8, 0xef, 0x79, 0xce, 0x42, 0x4d, 0x52, 0x4d, 0x2b, 0xaa, 0xd1, 0x6d, 0xcf, 0x73, 0xb0, - 0xc4, 0x48, 0x49, 0x52, 0xf2, 0x82, 0x5e, 0x90, 0x24, 0xa1, 0x58, 0x61, 0x91, 0x05, 0x60, 0x79, - 0x6e, 0xc7, 0xe6, 0xb6, 0xe7, 0xb2, 0x85, 0xd1, 0x25, 0x7d, 0xb9, 0xbe, 0xd2, 0x6e, 0xa5, 0x61, - 0x4e, 0x0e, 0xd6, 0xf2, 0x0f, 0xf7, 0x05, 0x80, 0xb5, 0x84, 0xff, 0x5a, 0x47, 0x57, 0x5b, 0x6b, - 0x31, 0x9f, 0x89, 0x94, 0x70, 0x48, 0x40, 0x0c, 0x67, 0xc4, 0xa2, 0x3b, 0x30, 0xda, 0x21, 0x9c, - 0x2c, 0x8c, 0x2d, 0x69, 0xcb, 0xf5, 0x95, 0xd7, 0x4b, 0xc5, 0x2b, 0xbf, 0xb5, 0x30, 0x79, 0xb8, - 0xf1, 0x11, 0xa7, 0x2e, 0x13, 0xc2, 0x93, 0xd3, 0xad, 0x13, 0x4e, 0xb0, 0x14, 0x84, 0x76, 0xa1, - 0xee, 0x52, 0xfe, 0xd0, 0x0b, 0x0e, 0x05, 0x70, 0x61, 0x5c, 0xca, 0xcd, 0x9a, 0x3d, 0x98, 0x9d, - 0xad, 0x2d, 0xc5, 0x20, 0xcf, 0x2d, 0xd8, 0xcc, 0xb9, 0x7e, 0xaf, 0x59, 0xdf, 0x4a, 0xe5, 0xe0, - 0xac, 0x50, 0xe3, 0xef, 0x1a, 0xcc, 0xab, 0x28, 0xd9, 0x9e, 0x8b, 0x29, 0x0b, 0x1d, 0x8e, 0x7e, - 0x04, 0x13, 0x91, 0xe3, 0x98, 0x8c, 0x50, 0x7d, 0xe5, 0xcd, 0x93, 0x95, 0x46, 0xda, 0x8a, 0x62, - 0xcc, 0x39, 0x75, 0xa6, 0x89, 0x08, 0xcf, 0x70, 0x2c, 0x15, 0xdd, 0x83, 0x69, 0xd7, 0xeb, 0xd0, - 0x1d, 0xea, 0x50, 0x8b, 0x7b, 0x81, 0x8c, 0x5e, 0x7d, 0x65, 0x29, 0xab, 0x45, 0xd4, 0x8a, 0xf0, - 0xff, 0x56, 0x86, 0xce, 0x9c, 0xef, 0xf7, 0x9a, 0xd3, 0x59, 0x08, 0xce, 0xc9, 0x31, 0x3e, 0xd5, - 0xa1, 0x6e, 0x12, 0x66, 0x5b, 0x91, 0x46, 0xf4, 0x53, 0x00, 0xc2, 0x79, 0x60, 0xef, 0x86, 0x5c, - 0x9e, 0x45, 0xc4, 0xfd, 0x9b, 0x27, 0x9f, 0x25, 0xc3, 0xde, 0x5a, 0x4d, 0x78, 0x37, 0x5c, 0x1e, - 0x1c, 0x9b, 0xaf, 0xc4, 0x19, 0x90, 0x22, 0x7e, 0xf6, 0xaf, 0xe6, 0xcc, 0xdd, 0x90, 0x38, 0xf6, - 0x9e, 0x4d, 0x3b, 0x5b, 0xa4, 0x4b, 0x71, 0x46, 0x23, 0x3a, 0x82, 0x49, 0x8b, 0xf8, 0xc4, 0xb2, - 0xf9, 0xf1, 0x42, 0x4d, 0x6a, 0x7f, 0xbb, 0xba, 0xf6, 0x35, 0xc5, 0x19, 0xe9, 0xbe, 0xa4, 0x74, - 0x4f, 0xc6, 0xe0, 0x41, 0xcd, 0x89, 0xae, 0x45, 0x07, 0xe6, 0x0a, 0xb6, 0xa3, 0x79, 0xd0, 0x0f, - 0xe9, 0x71, 0x54, 0x71, 0x58, 0xfc, 0x44, 0x6b, 0x30, 0x76, 0x44, 0x9c, 0x90, 0xca, 0xfa, 0xca, - 0x27, 0x6c, 0x79, 0x8c, 0x63, 0xa9, 0x38, 0xe2, 0xfd, 0x56, 0xed, 0x1d, 0x6d, 0xf1, 0x10, 0x66, - 0x72, 0xb6, 0x0e, 0xd1, 0xb5, 0x9e, 0xd7, 0xd5, 0x3a, 0xa9, 0xf6, 0x52, 0xe5, 0x77, 0x43, 0xe2, - 0x72, 0x9b, 0x1f, 0x67, 0x94, 0x19, 0x37, 0xe0, 0xdc, 0xda, 0xc6, 0x2d, 0xd5, 0x4f, 0x54, 0xdc, - 0xd1, 0x0a, 0x00, 0xfd, 0xc8, 0x0f, 0x28, 0x13, 0xb5, 0xa4, 0xba, 0x4a, 0x52, 0xae, 0x1b, 0x09, - 0x06, 0x67, 0xa8, 0x8c, 0x23, 0x50, 0x5d, 0x42, 0xf4, 0x19, 0x97, 0x74, 0xa9, 0xe2, 0x4b, 0x2a, - 0x51, 0xfa, 0x54, 0x62, 0xd0, 0x4d, 0x18, 0xdb, 0x15, 0x91, 0x51, 0xe6, 0x5f, 0xae, 0x1c, 0x44, - 0x73, 0xaa, 0xdf, 0x6b, 0x8e, 0x49, 0x00, 0x8e, 0x44, 0x18, 0x8f, 0x6a, 0xf0, 0xd5, 0x62, 0xc1, - 0xac, 0x79, 0xee, 0x9e, 0xbd, 0x1f, 0x06, 0xf2, 0x03, 0x7d, 0x07, 0xc6, 0x23, 0x91, 0xca, 0xa2, - 0xe5, 0xb8, 0xab, 0xed, 0x48, 0xe8, 0xb3, 0x5e, 0xf3, 0xa5, 0x22, 0x6b, 0x84, 0xc1, 0x8a, 0x0f, - 0x2d, 0xc3, 0x64, 0x40, 0x3f, 0x0c, 0x29, 0xe3, 0x4c, 0xe6, 0xdd, 0x94, 0x39, 0x2d, 0x52, 0x07, - 0x2b, 0x18, 0x4e, 0xb0, 0xe8, 0x63, 0x0d, 0xce, 0x47, 0x55, 0x99, 0xb3, 0x41, 0x55, 0xe4, 0xd5, - 0x2a, 0x39, 0x91, 0x63, 0x34, 0xbf, 0xa2, 0x8c, 0x3d, 0x3f, 0x04, 0x89, 0x87, 0xa9, 0x32, 0xfe, - 0xa3, 0xc1, 0x4b, 0xc3, 0x3b, 0x08, 0xda, 0x83, 0x89, 0x40, 0xfe, 0x8a, 0x8b, 0xf7, 0x7a, 0x15, - 0x83, 0xd4, 0x31, 0xcb, 0xfb, 0x51, 0xf4, 0xcd, 0x70, 0x2c, 0x1c, 0x59, 0x30, 0x6e, 0x49, 0x9b, - 0x54, 0x95, 0x5e, 0x3f, 0x5d, 0xbf, 0xcb, 0x7b, 0x20, 0x19, 0x42, 0x11, 0x18, 0x2b, 0xd1, 0xc6, - 0x6f, 0x35, 0x98, 0x2b, 0x54, 0x11, 0x6a, 0x80, 0x6e, 0xbb, 0x5c, 0xa6, 0x95, 0x1e, 0xc5, 0x68, - 0xd3, 0xe5, 0xf7, 0x44, 0xb2, 0x63, 0x81, 0x40, 0x97, 0x60, 0x74, 0x57, 0x8c, 0x40, 0x11, 0x8e, - 0x49, 0x73, 0xa6, 0xdf, 0x6b, 0x4e, 0x99, 0x9e, 0xe7, 0x44, 0x14, 0x12, 0x85, 0xbe, 0x01, 0xe3, - 0x8c, 0x07, 0xb6, 0xbb, 0xbf, 0x30, 0x2a, 0xb3, 0x45, 0xf6, 0xfb, 0x1d, 0x09, 0x89, 0xc8, 0x14, - 0x1a, 0xbd, 0x0a, 0x13, 0x47, 0x34, 0x90, 0x15, 0x32, 0x26, 0x29, 0x65, 0x37, 0xbd, 0x17, 0x81, - 0x22, 0xd2, 0x98, 0xc0, 0xf8, 0x7d, 0x0d, 0xea, 0x2a, 0x80, 0x0e, 0xb1, 0xbb, 0xe8, 0x7e, 0x26, - 0xa1, 0xa2, 0x48, 0xbc, 0x76, 0x8a, 0x48, 0x98, 0xf3, 0x71, 0xf3, 0x1a, 0x92, 0x81, 0x14, 0xea, - 0x96, 0xe7, 0x32, 0x1e, 0x10, 0xdb, 0x55, 0xe9, 0x9a, 0x6f, 0x10, 0x27, 0x25, 0x9e, 0x62, 0x33, - 0xcf, 0x2b, 0x05, 0xf5, 0x14, 0xc6, 0x70, 0x56, 0x2e, 0x7a, 0x90, 0x84, 0x58, 0x97, 0x1a, 0xde, - 0xaa, 0xa4, 0x41, 0x1c, 0xbe, 0x5a, 0x74, 0xff, 0xaa, 0xc1, 0x42, 0x19, 0x53, 0xae, 0x1e, 0xb5, - 0xcf, 0x54, 0x8f, 0xb5, 0xb3, 0xab, 0xc7, 0x3f, 0x69, 0x99, 0xd8, 0x33, 0x86, 0x7e, 0x0c, 0x93, - 0x62, 0x19, 0x92, 0xbb, 0x4d, 0xb4, 0x0e, 0xbc, 0x51, 0x6d, 0x75, 0xba, 0xb3, 0xfb, 0x13, 0x6a, - 0xf1, 0xdb, 0x94, 0x93, 0xb4, 0x19, 0xa7, 0x30, 0x9c, 0x48, 0x15, 0x9b, 0x13, 0xf3, 0xa9, 0x75, - 0x9a, 0x41, 0x24, 0x4d, 0xdb, 0xf1, 0xa9, 0x95, 0xf6, 0x6b, 0xf1, 0x85, 0xa5, 0x20, 0xe3, 0x97, - 0xd9, 0x60, 0x30, 0x96, 0x0f, 0x46, 0x99, 0x8b, 0xb5, 0xb3, 0x73, 0xf1, 0x1f, 0x93, 0x56, 0x20, - 0xed, 0xbb, 0x65, 0x33, 0x8e, 0x3e, 0x18, 0x70, 0x73, 0xab, 0x9a, 0x9b, 0x05, 0xb7, 0x74, 0x72, - 0x52, 0x65, 0x31, 0x24, 0xe3, 0xe2, 0x2d, 0x18, 0xb3, 0x39, 0xed, 0xc6, 0xf5, 0x75, 0xb9, 0xb2, - 0x8f, 0xcd, 0x19, 0x25, 0x75, 0x6c, 0x53, 0xf0, 0xe3, 0x48, 0x8c, 0xf1, 0x24, 0x7f, 0x02, 0xe1, - 0x7b, 0xf4, 0x43, 0x98, 0x62, 0x6a, 0x22, 0xc7, 0x5d, 0xe2, 0x4a, 0x15, 0x3d, 0xc9, 0x7a, 0x77, - 0x4e, 0xa9, 0x9a, 0x8a, 0x21, 0x0c, 0xa7, 0x12, 0x33, 0x15, 0x5c, 0x3b, 0x55, 0x05, 0x17, 0xe2, - 0x5f, 0x5a, 0xc1, 0x01, 0x0c, 0x0b, 0x20, 0xfa, 0x01, 0x8c, 0x7b, 0x3e, 0xf9, 0x30, 0xa4, 0x2a, - 0x2a, 0xcf, 0xd9, 0xe0, 0xee, 0x48, 0xda, 0x61, 0x69, 0x02, 0x42, 0x67, 0x84, 0xc6, 0x4a, 0xa4, - 0xf1, 0x48, 0x83, 0xf9, 0x62, 0x33, 0x3b, 0x45, 0xb7, 0xd8, 0x86, 0xd9, 0x2e, 0xe1, 0xd6, 0x41, - 0x32, 0x50, 0xd4, 0x5d, 0x69, 0xb9, 0xdf, 0x6b, 0xce, 0xde, 0xce, 0x61, 0x9e, 0xf5, 0x9a, 0xe8, - 0xdd, 0xd0, 0x71, 0x8e, 0xf3, 0x3b, 0x63, 0x81, 0xdf, 0xf8, 0xb9, 0x0e, 0x33, 0xb9, 0xde, 0x5d, - 0x61, 0x3b, 0x5a, 0x85, 0xb9, 0x4e, 0xea, 0x6c, 0x81, 0x50, 0x66, 0x7c, 0x59, 0x11, 0x67, 0x33, - 0x45, 0xf2, 0x15, 0xe9, 0xf3, 0xa9, 0xa3, 0x7f, 0xee, 0xa9, 0x73, 0x0f, 0x66, 0x49, 0x32, 0xad, - 0x6f, 0x7b, 0x1d, 0xaa, 0x66, 0x65, 0x4b, 0x71, 0xcd, 0xae, 0xe6, 0xb0, 0xcf, 0x7a, 0xcd, 0x2f, - 0x15, 0x67, 0xbc, 0x80, 0xe3, 0x82, 0x14, 0xf4, 0x0a, 0x8c, 0x59, 0x5e, 0xe8, 0x72, 0x39, 0x50, - 0xf5, 0xb4, 0x54, 0xd6, 0x04, 0x10, 0x47, 0x38, 0x74, 0x15, 0xea, 0xa4, 0xd3, 0xb5, 0xdd, 0x55, - 0xcb, 0xa2, 0x8c, 0xc9, 0x6b, 0xdc, 0x64, 0x34, 0xa5, 0x57, 0x53, 0x30, 0xce, 0xd2, 0x18, 0xff, - 0xd5, 0xe2, 0x1d, 0xb1, 0x64, 0x97, 0x41, 0x97, 0xc5, 0x66, 0x24, 0x51, 0x2a, 0x30, 0x99, 0xe5, - 0x46, 0x82, 0x71, 0x8c, 0xcf, 0x5c, 0xb7, 0x6b, 0x95, 0xae, 0xdb, 0x7a, 0x85, 0xeb, 0xf6, 0xe8, - 0x89, 0xd7, 0xed, 0xc2, 0x89, 0xc7, 0x2a, 0x9c, 0xf8, 0x03, 0x98, 0x2d, 0xec, 0xf4, 0x37, 0x41, - 0xb7, 0xa8, 0xa3, 0x8a, 0xee, 0x39, 0xb7, 0xde, 0x81, 0x1b, 0x81, 0x39, 0xd1, 0xef, 0x35, 0xf5, - 0xb5, 0x8d, 0x5b, 0x58, 0x08, 0x31, 0x7e, 0xa7, 0xc1, 0xb9, 0x81, 0x9b, 0x31, 0xba, 0x0e, 0x33, - 0xb6, 0xcb, 0x69, 0xb0, 0x47, 0x2c, 0xba, 0x95, 0xa6, 0xf8, 0x05, 0x75, 0xaa, 0x99, 0xcd, 0x2c, - 0x12, 0xe7, 0x69, 0xd1, 0x45, 0xd0, 0x6d, 0x3f, 0xde, 0xae, 0xa5, 0xb6, 0xcd, 0x6d, 0x86, 0x05, - 0x4c, 0xd4, 0xc3, 0x01, 0x09, 0x3a, 0x0f, 0x49, 0x40, 0x57, 0x3b, 0x1d, 0x71, 0xdf, 0x50, 0x3e, - 0x4d, 0xea, 0xe1, 0xbb, 0x79, 0x34, 0x2e, 0xd2, 0x1b, 0xbf, 0xd1, 0xe0, 0x62, 0x69, 0x27, 0xa9, - 0xfc, 0x80, 0x42, 0x00, 0x7c, 0x12, 0x90, 0x2e, 0xe5, 0x34, 0x60, 0x43, 0xa6, 0x6b, 0x85, 0x77, - 0x89, 0x64, 0x70, 0x6f, 0x27, 0x82, 0x70, 0x46, 0xa8, 0xf1, 0xab, 0x1a, 0xcc, 0x60, 0x15, 0x8f, - 0x68, 0x55, 0xfc, 0xff, 0xaf, 0x0b, 0x77, 0x73, 0xeb, 0xc2, 0x73, 0x52, 0x23, 0x67, 0x5c, 0xd9, - 0xc2, 0x80, 0xee, 0x8b, 0x25, 0x9a, 0xf0, 0x90, 0x55, 0xbb, 0xf8, 0xe4, 0x85, 0x4a, 0xc6, 0x34, - 0x08, 0xd1, 0x37, 0x56, 0x02, 0x8d, 0xbe, 0x06, 0x8d, 0x1c, 0xbd, 0xe8, 0xf4, 0x61, 0x97, 0x06, - 0x98, 0xee, 0xd1, 0x80, 0xba, 0x16, 0x45, 0x57, 0x60, 0x92, 0xf8, 0xf6, 0x8d, 0xc0, 0x0b, 0x7d, - 0x15, 0xd1, 0x64, 0x94, 0xaf, 0x6e, 0x6f, 0x4a, 0x38, 0x4e, 0x28, 0x04, 0x75, 0x6c, 0x91, 0xca, - 0xab, 0xcc, 0x7a, 0x1d, 0xc1, 0x71, 0x42, 0x91, 0xb4, 0xef, 0xd1, 0xd2, 0xf6, 0x6d, 0x82, 0x1e, - 0xda, 0x1d, 0x75, 0x27, 0x78, 0x43, 0x11, 0xe8, 0xef, 0x6f, 0xae, 0x3f, 0xeb, 0x35, 0x2f, 0x95, - 0x3d, 0xfe, 0xf1, 0x63, 0x9f, 0xb2, 0xd6, 0xfb, 0x9b, 0xeb, 0x58, 0x30, 0x1b, 0x7f, 0xd6, 0xe0, - 0x5c, 0xee, 0x90, 0x67, 0xb0, 0xd2, 0x6c, 0xe7, 0x57, 0x9a, 0xd7, 0x4e, 0x11, 0xb2, 0x92, 0xa5, - 0xc6, 0x2e, 0x1c, 0x42, 0x6e, 0x35, 0xef, 0x15, 0x1f, 0xc3, 0x2e, 0x57, 0xbe, 0x39, 0x94, 0xbf, - 0x80, 0x19, 0x7f, 0xab, 0xc1, 0xf9, 0x21, 0x59, 0x84, 0x1e, 0x00, 0xa4, 0x33, 0x66, 0x88, 0xd3, - 0x86, 0x28, 0x1c, 0xb8, 0xe7, 0xce, 0xca, 0x27, 0xaa, 0x14, 0x9a, 0x91, 0x88, 0x18, 0xd4, 0x03, - 0xca, 0x68, 0x70, 0x44, 0x3b, 0xef, 0x7a, 0x81, 0x72, 0xdd, 0xb7, 0x4f, 0xe1, 0xba, 0x81, 0xec, - 0x4d, 0xef, 0x5e, 0x38, 0x15, 0x8c, 0xb3, 0x5a, 0xd0, 0x83, 0xd4, 0x85, 0xd1, 0xdb, 0xeb, 0xb5, - 0x4a, 0x27, 0xca, 0x3f, 0x1b, 0x9f, 0xe0, 0xcc, 0x7f, 0x6a, 0x70, 0x21, 0x67, 0xe4, 0x7b, 0xb4, - 0xeb, 0x3b, 0x84, 0xd3, 0x33, 0x68, 0x46, 0xf7, 0x73, 0xcd, 0xe8, 0xed, 0x53, 0x78, 0x32, 0x36, - 0xb2, 0xf4, 0x16, 0xf3, 0x0f, 0x0d, 0x2e, 0x0e, 0xe5, 0x38, 0x83, 0xe2, 0xfa, 0x5e, 0xbe, 0xb8, - 0xae, 0x7d, 0x86, 0x73, 0x95, 0xdf, 0x1c, 0x2e, 0x96, 0xfa, 0xe1, 0x0b, 0x39, 0x3d, 0x8c, 0x3f, - 0x68, 0x30, 0x1d, 0x53, 0x8a, 0x75, 0xa9, 0xc2, 0xce, 0xbc, 0x02, 0xa0, 0xfe, 0x30, 0x89, 0x6f, - 0xf7, 0x7a, 0x6a, 0xf7, 0x8d, 0x04, 0x83, 0x33, 0x54, 0xe8, 0x26, 0xa0, 0xd8, 0xc2, 0x1d, 0x47, - 0x2e, 0x05, 0x62, 0xf5, 0xd4, 0x25, 0xef, 0xa2, 0xe2, 0x45, 0x78, 0x80, 0x02, 0x0f, 0xe1, 0x32, - 0xfe, 0xa2, 0xa5, 0x73, 0x5b, 0x82, 0x5f, 0x54, 0xcf, 0x4b, 0xe3, 0x4a, 0x3d, 0x9f, 0x9d, 0x3b, - 0x92, 0xf2, 0x85, 0x9d, 0x3b, 0xd2, 0xba, 0x92, 0x92, 0x78, 0xa4, 0x17, 0x4e, 0x21, 0x4b, 0xa1, - 0xea, 0x96, 0x77, 0x2b, 0xf3, 0x37, 0x59, 0x7d, 0xe5, 0xd5, 0x6a, 0xe6, 0x88, 0x34, 0x1d, 0xba, - 0xe3, 0x5f, 0x81, 0x49, 0xd7, 0xeb, 0x44, 0xfb, 0x70, 0x61, 0xbb, 0xd8, 0x52, 0x70, 0x9c, 0x50, - 0x0c, 0xfc, 0x91, 0x33, 0xfa, 0xf9, 0xfc, 0x91, 0x23, 0x37, 0x22, 0xc7, 0x11, 0x04, 0xf1, 0xf5, - 0x21, 0xdd, 0x88, 0x14, 0x1c, 0x27, 0x14, 0xe8, 0x4e, 0x3a, 0x5f, 0xc6, 0x65, 0x4c, 0xbe, 0x56, - 0x65, 0x44, 0x97, 0x0f, 0x14, 0xd3, 0x7c, 0xfc, 0xb4, 0x31, 0xf2, 0xe4, 0x69, 0x63, 0xe4, 0x93, - 0xa7, 0x8d, 0x91, 0x8f, 0xfb, 0x0d, 0xed, 0x71, 0xbf, 0xa1, 0x3d, 0xe9, 0x37, 0xb4, 0x4f, 0xfa, - 0x0d, 0xed, 0xd3, 0x7e, 0x43, 0xfb, 0xc5, 0xbf, 0x1b, 0x23, 0xdf, 0x7f, 0xf9, 0xa4, 0x7f, 0x95, - 0xff, 0x17, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x60, 0x85, 0x64, 0x74, 0x1e, 0x00, 0x00, + // 2635 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x1a, 0x5b, 0x6f, 0x1c, 0x57, + 0x39, 0xb3, 0xbb, 0x5e, 0xaf, 0xbf, 0x8d, 0x1d, 0xfb, 0x84, 0x84, 0x8d, 0x49, 0x77, 0x93, 0x09, + 0x17, 0xa7, 0x75, 0xd6, 0x8d, 0x53, 0xb5, 0x85, 0x80, 0x84, 0xd7, 0x76, 0x52, 0xa7, 0x89, 0xe3, + 0x9c, 0x75, 0x03, 0x81, 0x12, 0x18, 0xcf, 0x1e, 0xdb, 0x83, 0x67, 0x67, 0xa6, 0x73, 0x66, 0x9d, + 0x5a, 0x42, 0xa8, 0xe2, 0x07, 0x54, 0xbc, 0xf2, 0x80, 0x2a, 0xf1, 0x50, 0x89, 0x17, 0xe0, 0x99, + 0x17, 0x90, 0x40, 0x6a, 0x04, 0x3c, 0x44, 0xa2, 0x42, 0x15, 0x12, 0x0b, 0x59, 0x84, 0xf8, 0x0b, + 0xc8, 0x4f, 0xe8, 0x5c, 0xe6, 0xba, 0x3b, 0xce, 0xac, 0x49, 0xac, 0x20, 0xf5, 0x6d, 0xf7, 0x3b, + 0xdf, 0xed, 0x7c, 0xf7, 0x73, 0xe6, 0xc0, 0xec, 0xce, 0xeb, 0xb4, 0x6e, 0xd8, 0x73, 0x9a, 0x63, + 0xcc, 0xb9, 0x84, 0xda, 0x1d, 0x57, 0x27, 0x73, 0xbb, 0x97, 0x35, 0xd3, 0xd9, 0xd6, 0xae, 0xcc, + 0x6d, 0x11, 0x8b, 0xb8, 0x9a, 0x47, 0x5a, 0x75, 0xc7, 0xb5, 0x3d, 0x1b, 0x9d, 0x15, 0xd8, 0x75, + 0xcd, 0x31, 0xea, 0x3e, 0x76, 0xdd, 0xc7, 0x9e, 0xbe, 0xb4, 0x65, 0x78, 0xdb, 0x9d, 0x8d, 0xba, + 0x6e, 0xb7, 0xe7, 0xb6, 0xec, 0x2d, 0x7b, 0x8e, 0x13, 0x6d, 0x74, 0x36, 0xf9, 0x3f, 0xfe, 0x87, + 0xff, 0x12, 0xcc, 0xa6, 0xd5, 0x88, 0x68, 0xdd, 0x76, 0x99, 0xd8, 0xa4, 0xc0, 0xe9, 0x57, 0x42, + 0x9c, 0xb6, 0xa6, 0x6f, 0x1b, 0x16, 0x71, 0xf7, 0xe6, 0x9c, 0x9d, 0xad, 0xb8, 0xbe, 0xc3, 0x50, + 0xd1, 0xb9, 0x36, 0xf1, 0xb4, 0x41, 0xb2, 0xe6, 0xd2, 0xa8, 0xdc, 0x8e, 0xe5, 0x19, 0xed, 0x7e, + 0x31, 0xaf, 0x3e, 0x89, 0x80, 0xea, 0xdb, 0xa4, 0xad, 0x25, 0xe9, 0xd4, 0x0f, 0xf2, 0x70, 0x6a, + 0xc1, 0x34, 0x6d, 0x9d, 0xc1, 0x96, 0xc8, 0xae, 0xa1, 0x93, 0xa6, 0xa7, 0x79, 0x1d, 0x8a, 0xbe, + 0x08, 0xc5, 0x96, 0x6b, 0xec, 0x12, 0xb7, 0xa2, 0x9c, 0x53, 0x66, 0xc6, 0x1a, 0x13, 0x0f, 0xbb, + 0xb5, 0x63, 0xbd, 0x6e, 0xad, 0xb8, 0xc4, 0xa1, 0x58, 0xae, 0xa2, 0x73, 0x50, 0x70, 0x6c, 0xdb, + 0xac, 0xe4, 0x38, 0xd6, 0x71, 0x89, 0x55, 0x58, 0xb3, 0x6d, 0x13, 0xf3, 0x15, 0xce, 0x89, 0x73, + 0xae, 0xe4, 0x13, 0x9c, 0x38, 0x14, 0xcb, 0x55, 0xa4, 0x03, 0xe8, 0xb6, 0xd5, 0x32, 0x3c, 0xc3, + 0xb6, 0x68, 0xa5, 0x70, 0x2e, 0x3f, 0x53, 0x9e, 0x9f, 0xab, 0x87, 0x6e, 0x0e, 0x36, 0x56, 0x77, + 0x76, 0xb6, 0x18, 0x80, 0xd6, 0x99, 0xfd, 0xea, 0xbb, 0x97, 0xeb, 0x8b, 0x3e, 0x5d, 0x03, 0x49, + 0xe6, 0x10, 0x80, 0x28, 0x8e, 0xb0, 0x45, 0x6f, 0x42, 0xa1, 0xa5, 0x79, 0x5a, 0x65, 0xe4, 0x9c, + 0x32, 0x53, 0x9e, 0xbf, 0x94, 0xca, 0x5e, 0xda, 0xad, 0x8e, 0xb5, 0x07, 0xcb, 0xef, 0x7a, 0xc4, + 0xa2, 0x8c, 0x79, 0x89, 0xed, 0x6c, 0x49, 0xf3, 0x34, 0xcc, 0x99, 0xa0, 0x0d, 0x28, 0x5b, 0xc4, + 0x7b, 0x60, 0xbb, 0x3b, 0x0c, 0x58, 0x29, 0x72, 0x9e, 0x51, 0x95, 0xfb, 0x23, 0xb3, 0xbe, 0x2a, + 0x09, 0xf8, 0x9e, 0x19, 0x59, 0xe3, 0x44, 0xaf, 0x5b, 0x2b, 0xaf, 0x86, 0x7c, 0x70, 0x94, 0xa9, + 0xfa, 0x47, 0x05, 0x26, 0xa5, 0x87, 0x0c, 0xdb, 0xc2, 0x84, 0x76, 0x4c, 0x0f, 0x7d, 0x17, 0x46, + 0x85, 0xd1, 0x28, 0xf7, 0x4e, 0x79, 0xfe, 0x95, 0x83, 0x85, 0x0a, 0x69, 0x49, 0x36, 0x8d, 0x13, + 0xd2, 0x58, 0xa3, 0x62, 0x9d, 0x62, 0x9f, 0x2b, 0xba, 0x0b, 0xc7, 0x2d, 0xbb, 0x45, 0x9a, 0xc4, + 0x24, 0xba, 0x67, 0xbb, 0xdc, 0x73, 0xe5, 0xf9, 0x73, 0x51, 0x29, 0x2c, 0x4f, 0x98, 0xed, 0x57, + 0x23, 0x78, 0x8d, 0xc9, 0x5e, 0xb7, 0x76, 0x3c, 0x0a, 0xc1, 0x31, 0x3e, 0xea, 0xdf, 0x8a, 0x50, + 0x6e, 0x68, 0xd4, 0xd0, 0x85, 0x44, 0xf4, 0x43, 0x00, 0xcd, 0xf3, 0x5c, 0x63, 0xa3, 0xe3, 0xf1, + 0xbd, 0x30, 0x9f, 0x7f, 0xf9, 0xe0, 0xbd, 0x44, 0xc8, 0xeb, 0x0b, 0x01, 0xed, 0xb2, 0xe5, 0xb9, + 0x7b, 0x8d, 0x0b, 0xbe, 0xf7, 0xc3, 0x85, 0x1f, 0xfd, 0xbd, 0x36, 0x7e, 0xa7, 0xa3, 0x99, 0xc6, + 0xa6, 0x41, 0x5a, 0xab, 0x5a, 0x9b, 0xe0, 0x88, 0x44, 0xb4, 0x0b, 0x25, 0x5d, 0x73, 0x34, 0xdd, + 0xf0, 0xf6, 0x2a, 0x39, 0x2e, 0xfd, 0xb5, 0xec, 0xd2, 0x17, 0x25, 0xa5, 0x90, 0x7d, 0x5e, 0xca, + 0x2e, 0xf9, 0xe0, 0x7e, 0xc9, 0x81, 0x2c, 0xf4, 0x03, 0x98, 0xd4, 0x6d, 0x8b, 0x76, 0xda, 0x84, + 0x2e, 0xda, 0x1d, 0xcb, 0x23, 0x2e, 0xad, 0xe4, 0xb9, 0xfc, 0x57, 0xb3, 0x78, 0x52, 0xd2, 0x2c, + 0x72, 0x16, 0x0e, 0x0f, 0xfc, 0x8a, 0x14, 0x3f, 0xb9, 0x98, 0xe0, 0x8b, 0xfb, 0x24, 0xa1, 0x19, + 0x28, 0x31, 0xaf, 0x30, 0x9d, 0x2a, 0x05, 0x91, 0xb7, 0x4c, 0xf1, 0x55, 0x09, 0xc3, 0xc1, 0x6a, + 0x5f, 0x1c, 0x8c, 0x3c, 0x9d, 0x38, 0x60, 0x1a, 0x68, 0xa6, 0xc9, 0x10, 0x28, 0x4f, 0x9b, 0x92, + 0xd0, 0x60, 0x41, 0xc2, 0x70, 0xb0, 0x8a, 0xee, 0x40, 0xd1, 0xd3, 0x0c, 0xcb, 0xa3, 0x95, 0x51, + 0x6e, 0x9f, 0x8b, 0x59, 0xec, 0xb3, 0xce, 0x28, 0xc2, 0x42, 0xc3, 0xff, 0x52, 0x2c, 0x19, 0x4d, + 0x9b, 0x70, 0x22, 0x11, 0x38, 0x68, 0x12, 0xf2, 0x3b, 0x64, 0x4f, 0x94, 0x3a, 0xcc, 0x7e, 0xa2, + 0x45, 0x18, 0xd9, 0xd5, 0xcc, 0x0e, 0xe1, 0x85, 0x2d, 0x5e, 0x29, 0xd2, 0x13, 0xcc, 0xe7, 0x8a, + 0x05, 0xed, 0x57, 0x72, 0xaf, 0x2b, 0xd3, 0x3b, 0x30, 0x1e, 0x0b, 0x94, 0x01, 0xb2, 0x96, 0xe2, + 0xb2, 0xea, 0x07, 0x15, 0xbd, 0x50, 0xf8, 0x9d, 0x8e, 0x66, 0x79, 0x86, 0xb7, 0x17, 0x11, 0xa6, + 0x5e, 0x87, 0xa9, 0xc5, 0xe5, 0x9b, 0xb2, 0x90, 0xfb, 0xc6, 0x9e, 0x07, 0x20, 0xef, 0x3a, 0x2e, + 0xa1, 0xac, 0x88, 0xc9, 0x72, 0x1e, 0xd4, 0xc9, 0xe5, 0x60, 0x05, 0x47, 0xb0, 0xd4, 0xfb, 0x30, + 0x2a, 0xc3, 0x05, 0x35, 0x7d, 0xed, 0x94, 0xc3, 0x68, 0xd7, 0x18, 0x97, 0x92, 0x46, 0xee, 0x32, + 0x26, 0x52, 0x59, 0xf5, 0x3f, 0x0a, 0x80, 0x14, 0xd0, 0x24, 0x1e, 0xeb, 0x22, 0x16, 0x8b, 0x46, + 0x25, 0xde, 0x45, 0x78, 0x34, 0xf2, 0x15, 0xd4, 0x82, 0x92, 0xee, 0x67, 0x4a, 0x2e, 0x4b, 0xa6, + 0x84, 0xdc, 0xfd, 0x9f, 0xb2, 0x48, 0x4c, 0x06, 0x89, 0xea, 0x67, 0x48, 0xc0, 0x79, 0x7a, 0x03, + 0xc6, 0x63, 0xc8, 0x03, 0x9c, 0x75, 0x35, 0xee, 0xac, 0x2f, 0x64, 0xd2, 0x22, 0xea, 0xa3, 0x5d, + 0x90, 0x9d, 0x2f, 0xc3, 0xae, 0x6f, 0xc0, 0xc8, 0x06, 0xab, 0x38, 0x52, 0xd8, 0xc5, 0xcc, 0xc5, + 0xa9, 0x31, 0xc6, 0x4c, 0xce, 0x01, 0x58, 0xb0, 0x50, 0xdf, 0xcf, 0xc1, 0x0b, 0xc9, 0x46, 0xb0, + 0x68, 0x5b, 0x9b, 0xc6, 0x56, 0xc7, 0xe5, 0x7f, 0xd0, 0xd7, 0xa1, 0x28, 0x58, 0x4a, 0x8d, 0x66, + 0xfc, 0x04, 0x6a, 0x72, 0xe8, 0x7e, 0xb7, 0x76, 0x3a, 0x49, 0x2a, 0x56, 0xb0, 0xa4, 0x63, 0x79, + 0xed, 0x92, 0x77, 0x3a, 0x84, 0x7a, 0xc2, 0x4b, 0xb2, 0xb2, 0x60, 0x09, 0xc3, 0xc1, 0x2a, 0x7a, + 0x4f, 0x81, 0x93, 0x2d, 0x59, 0xcc, 0x22, 0x3a, 0xc8, 0x4e, 0x73, 0x39, 0x5b, 0x15, 0x8c, 0x10, + 0x36, 0x3e, 0x27, 0x95, 0x3d, 0x39, 0x60, 0x11, 0x0f, 0x12, 0xa5, 0xfe, 0x4b, 0x81, 0xd3, 0x83, + 0x3b, 0x23, 0xda, 0x84, 0x51, 0x97, 0xff, 0xf2, 0x9b, 0xd2, 0xd5, 0x2c, 0x0a, 0xc9, 0x6d, 0xa6, + 0xf7, 0x59, 0xf1, 0x9f, 0x62, 0x9f, 0x39, 0xd2, 0xa1, 0xa8, 0x73, 0x9d, 0x64, 0x4c, 0x5f, 0x1d, + 0xae, 0x8f, 0xc7, 0x2d, 0x10, 0xd4, 0x3b, 0x01, 0xc6, 0x92, 0xb5, 0xfa, 0x73, 0x05, 0x4e, 0x24, + 0x0a, 0x14, 0xaa, 0x42, 0xde, 0xb0, 0x3c, 0x1e, 0x56, 0x79, 0xe1, 0xa3, 0x15, 0xcb, 0x13, 0x19, + 0xca, 0x16, 0xd0, 0x79, 0x28, 0x6c, 0xb0, 0xb1, 0x2e, 0xcf, 0x8b, 0xf3, 0x78, 0xaf, 0x5b, 0x1b, + 0x6b, 0xd8, 0xb6, 0x29, 0x30, 0xf8, 0x12, 0xfa, 0x12, 0x14, 0xa9, 0xe7, 0x1a, 0xd6, 0x96, 0xec, + 0x21, 0x7c, 0x8e, 0x69, 0x72, 0x88, 0x40, 0x93, 0xcb, 0xe8, 0x45, 0x18, 0xdd, 0x25, 0x2e, 0x2f, + 0x3e, 0x23, 0x1c, 0x93, 0x77, 0x87, 0xbb, 0x02, 0x24, 0x50, 0x7d, 0x04, 0xf5, 0x97, 0x39, 0x28, + 0x4b, 0x07, 0x9a, 0x9a, 0xd1, 0x46, 0xf7, 0x22, 0x01, 0x25, 0x3c, 0xf1, 0xd2, 0x10, 0x9e, 0x08, + 0x73, 0x7d, 0x40, 0x04, 0x12, 0x28, 0xb3, 0xce, 0xe8, 0xb9, 0xa2, 0xbd, 0x08, 0x07, 0xd4, 0x33, + 0x06, 0x9e, 0x24, 0x6b, 0x9c, 0x94, 0x02, 0xca, 0x21, 0x8c, 0xe2, 0x28, 0x5f, 0x74, 0x3f, 0x70, + 0xf1, 0x30, 0x0d, 0x9e, 0x6d, 0x3e, 0x9b, 0x77, 0x3f, 0x52, 0xa0, 0x92, 0x46, 0x14, 0xcb, 0x47, + 0xe5, 0x50, 0xf9, 0x98, 0x3b, 0xba, 0x7c, 0xfc, 0xad, 0x12, 0xf1, 0x3d, 0xa5, 0xe8, 0x7b, 0x50, + 0x62, 0x03, 0x3e, 0x9f, 0xd7, 0x45, 0xef, 0x79, 0x39, 0xdb, 0x71, 0xe0, 0xf6, 0xc6, 0xf7, 0x89, + 0xee, 0xdd, 0x22, 0x9e, 0x16, 0xf6, 0xb9, 0x10, 0x86, 0x03, 0xae, 0xe8, 0x36, 0x14, 0xa8, 0x43, + 0xf4, 0x61, 0x7a, 0x3c, 0x57, 0xad, 0xe9, 0x10, 0x3d, 0xac, 0xd7, 0xec, 0x1f, 0xe6, 0x8c, 0xd4, + 0x9f, 0x46, 0x9d, 0x41, 0x69, 0xdc, 0x19, 0x69, 0x26, 0x56, 0x8e, 0xce, 0xc4, 0xbf, 0x09, 0x4a, + 0x01, 0xd7, 0xef, 0xa6, 0x41, 0x3d, 0xf4, 0x76, 0x9f, 0x99, 0xeb, 0xd9, 0xcc, 0xcc, 0xa8, 0xb9, + 0x91, 0x83, 0x2c, 0xf3, 0x21, 0x11, 0x13, 0xaf, 0xc2, 0x88, 0xe1, 0x91, 0xb6, 0x9f, 0x5f, 0x17, + 0x33, 0xdb, 0x38, 0x1c, 0x1c, 0x56, 0x18, 0x3d, 0x16, 0x6c, 0xd4, 0x47, 0xf1, 0x1d, 0x30, 0xdb, + 0xa3, 0xef, 0xc0, 0x18, 0x95, 0xc3, 0x8e, 0x5f, 0x25, 0x66, 0xb3, 0xc8, 0x09, 0xc6, 0xd5, 0x29, + 0x29, 0x6a, 0xcc, 0x87, 0x50, 0x1c, 0x72, 0x8c, 0x64, 0x70, 0x6e, 0xa8, 0x0c, 0x4e, 0xf8, 0x3f, + 0x35, 0x83, 0x5d, 0x18, 0xe4, 0x40, 0xf4, 0x6d, 0x28, 0xda, 0x8e, 0xf6, 0x4e, 0x30, 0x78, 0x3d, + 0xe1, 0x64, 0x72, 0x9b, 0xe3, 0x0e, 0x0a, 0x13, 0x60, 0x32, 0xc5, 0x32, 0x96, 0x2c, 0xd5, 0xf7, + 0x15, 0x98, 0x4c, 0x16, 0xb3, 0x21, 0xaa, 0xc5, 0x1a, 0x4c, 0xb4, 0x35, 0x4f, 0xdf, 0x0e, 0x1a, + 0x8a, 0x3c, 0xff, 0xcf, 0xf4, 0xba, 0xb5, 0x89, 0x5b, 0xb1, 0x95, 0xfd, 0x6e, 0x0d, 0x5d, 0xeb, + 0x98, 0xe6, 0x5e, 0xfc, 0x2c, 0x94, 0xa0, 0x57, 0x3f, 0xcc, 0x05, 0x99, 0xd3, 0x77, 0xb8, 0x61, + 0x13, 0xac, 0x1e, 0x8c, 0x73, 0xc9, 0x09, 0x36, 0x1c, 0xf4, 0x70, 0x04, 0x0b, 0xb9, 0x7d, 0x03, + 0xe3, 0xd2, 0xe1, 0x8e, 0x56, 0xcf, 0xd9, 0xf8, 0xf8, 0xd7, 0x02, 0x8c, 0xc7, 0x9a, 0x5c, 0x86, + 0x31, 0x72, 0x01, 0x4e, 0xb4, 0xc2, 0xa8, 0xe4, 0xe7, 0x3e, 0xe1, 0xaf, 0xcf, 0x4a, 0xe4, 0x68, + 0x4a, 0x71, 0xba, 0x24, 0x7e, 0x3c, 0xc7, 0xf2, 0x4f, 0x3d, 0xc7, 0xee, 0xc2, 0x84, 0x16, 0x8c, + 0x35, 0xb7, 0xec, 0x96, 0x7f, 0x30, 0xad, 0x4b, 0xaa, 0x89, 0x85, 0xd8, 0xea, 0x7e, 0xb7, 0xf6, + 0x99, 0xe4, 0x30, 0xc4, 0xe0, 0x38, 0xc1, 0x05, 0x5d, 0x80, 0x11, 0xee, 0x1d, 0x3e, 0x79, 0xe4, + 0xc3, 0x9a, 0xc2, 0x0d, 0x8b, 0xc5, 0x1a, 0xba, 0x0c, 0x65, 0xad, 0xd5, 0x36, 0xac, 0x05, 0x5d, + 0x27, 0xd4, 0x3f, 0x90, 0xf2, 0x71, 0x66, 0x21, 0x04, 0xe3, 0x28, 0x0e, 0xb2, 0x60, 0x62, 0xd3, + 0x70, 0xa9, 0xb7, 0xb0, 0xab, 0x19, 0xa6, 0xb6, 0x61, 0x12, 0x79, 0x3c, 0xcd, 0x34, 0x3f, 0x34, + 0x3b, 0x1b, 0xfe, 0x80, 0x72, 0xda, 0xdf, 0xdf, 0xb5, 0x18, 0x37, 0x9c, 0xe0, 0xce, 0x86, 0x15, + 0xcf, 0x36, 0x89, 0xc8, 0x68, 0x5a, 0x29, 0x65, 0x17, 0xb6, 0x1e, 0x90, 0x85, 0xc3, 0x4a, 0x08, + 0xa3, 0x38, 0xca, 0x57, 0xfd, 0x4b, 0x70, 0x46, 0x48, 0x99, 0x65, 0xd1, 0x45, 0x36, 0x19, 0xf3, + 0x25, 0x19, 0x6f, 0x91, 0xe1, 0x96, 0x83, 0xb1, 0xbf, 0x1e, 0xb9, 0x42, 0xcc, 0x65, 0xba, 0x42, + 0xcc, 0x67, 0xb8, 0x42, 0x2c, 0x1c, 0x78, 0x85, 0x98, 0x70, 0xe4, 0x48, 0x06, 0x47, 0x26, 0x0c, + 0x5b, 0x7c, 0x46, 0x86, 0x7d, 0x1b, 0x26, 0x12, 0xa7, 0xf2, 0x1b, 0x90, 0xd7, 0x89, 0x29, 0x6b, + 0xfb, 0x13, 0x2e, 0x0d, 0xfb, 0xce, 0xf4, 0x8d, 0xd1, 0x5e, 0xb7, 0x96, 0x5f, 0x5c, 0xbe, 0x89, + 0x19, 0x13, 0xf5, 0xd7, 0x79, 0xbf, 0x9a, 0x87, 0xa1, 0xf5, 0x69, 0x59, 0xf8, 0x5f, 0xcb, 0x42, + 0x22, 0x34, 0x46, 0x9f, 0x51, 0x68, 0xfc, 0x3b, 0x18, 0x7b, 0xf9, 0x3d, 0x15, 0x7a, 0x21, 0xd2, + 0x33, 0x1a, 0x65, 0x49, 0x9e, 0x7f, 0x93, 0xec, 0x89, 0x06, 0x72, 0x21, 0xda, 0x40, 0xc6, 0x06, + 0x5f, 0xaf, 0xa0, 0xab, 0x50, 0x24, 0x9b, 0x9b, 0x44, 0xf7, 0x64, 0x52, 0xf9, 0x17, 0xa3, 0xc5, + 0x65, 0x0e, 0xdd, 0xef, 0xd6, 0xa6, 0x22, 0x22, 0x05, 0x10, 0x4b, 0x12, 0xf4, 0x0d, 0x18, 0xf3, + 0x8c, 0x36, 0x59, 0x68, 0xb5, 0x48, 0x8b, 0xdb, 0xbb, 0x3c, 0xff, 0x62, 0xb6, 0x89, 0x70, 0xdd, + 0x68, 0x13, 0x71, 0x58, 0x5c, 0xf7, 0x19, 0xe0, 0x90, 0x97, 0xfa, 0x30, 0x98, 0xdd, 0xb8, 0x58, + 0xdc, 0x31, 0xc9, 0x11, 0x0c, 0xf9, 0xcd, 0xd8, 0x90, 0x7f, 0x39, 0xf3, 0xfd, 0x21, 0x53, 0x2f, + 0x75, 0xd0, 0xff, 0x48, 0xf1, 0x87, 0xb6, 0x00, 0xf7, 0x08, 0x86, 0x69, 0x1c, 0x1f, 0xa6, 0x2f, + 0x0d, 0xb5, 0x97, 0x94, 0x81, 0xfa, 0xe3, 0xfe, 0x9d, 0xf0, 0xa1, 0xba, 0x0d, 0x13, 0xad, 0x58, + 0xaa, 0x0e, 0x73, 0x4e, 0xe1, 0xac, 0x82, 0x1c, 0x47, 0x2c, 0x53, 0xe3, 0x79, 0x8f, 0x13, 0xcc, + 0xd9, 0x39, 0x81, 0x5f, 0xcf, 0x66, 0xbb, 0xe9, 0x8a, 0x5e, 0xf3, 0x06, 0xdb, 0x12, 0xfa, 0x0b, + 0x36, 0xea, 0x4f, 0x72, 0xb1, 0x6d, 0x05, 0x72, 0xbe, 0xd6, 0x5f, 0xf3, 0x44, 0xa6, 0x9d, 0xcc, + 0x54, 0xef, 0xd4, 0x44, 0x4f, 0x83, 0x01, 0xfd, 0xec, 0x6c, 0xac, 0x9f, 0x95, 0x12, 0xbd, 0x4c, + 0x4d, 0xf4, 0x32, 0x18, 0xd0, 0xc7, 0x62, 0x55, 0x75, 0xe4, 0x69, 0x57, 0x55, 0xf5, 0x67, 0x39, + 0xbf, 0x5d, 0x84, 0x45, 0xe9, 0x49, 0x65, 0xe7, 0x0d, 0x28, 0xd9, 0x0e, 0xc3, 0xb5, 0xfd, 0xad, + 0xcf, 0xfa, 0x81, 0x7a, 0x5b, 0xc2, 0xf7, 0xbb, 0xb5, 0x4a, 0x92, 0xad, 0xbf, 0x86, 0x03, 0xea, + 0xb0, 0x80, 0xe5, 0x33, 0x15, 0xb0, 0xc2, 0xf0, 0x05, 0x6c, 0x11, 0xa6, 0xc2, 0x02, 0xdb, 0x24, + 0xba, 0x6d, 0xb5, 0xa8, 0xac, 0xf4, 0xa7, 0x7a, 0xdd, 0xda, 0xd4, 0x7a, 0x72, 0x11, 0xf7, 0xe3, + 0xab, 0xbf, 0x50, 0x60, 0xaa, 0xef, 0x63, 0x1d, 0xba, 0x0a, 0xe3, 0x06, 0x9b, 0xc8, 0x37, 0x35, + 0x9d, 0x44, 0x82, 0xe7, 0x94, 0x54, 0x6f, 0x7c, 0x25, 0xba, 0x88, 0xe3, 0xb8, 0xe8, 0x0c, 0xe4, + 0x0d, 0xc7, 0xbf, 0x18, 0xe5, 0x1d, 0x7c, 0x65, 0x8d, 0x62, 0x06, 0x63, 0xad, 0x78, 0x5b, 0x73, + 0x5b, 0x0f, 0x34, 0x97, 0xd5, 0x4a, 0x97, 0x4d, 0x2f, 0xf9, 0x78, 0x2b, 0x7e, 0x23, 0xbe, 0x8c, + 0x93, 0xf8, 0xea, 0x87, 0x0a, 0x9c, 0x49, 0x3d, 0x04, 0x66, 0xfe, 0x9e, 0xab, 0x01, 0x38, 0x9a, + 0xab, 0xb5, 0x89, 0x3c, 0x38, 0x1d, 0xe2, 0x33, 0x69, 0x50, 0x8e, 0xd7, 0x02, 0x46, 0x38, 0xc2, + 0x54, 0xfd, 0x20, 0x07, 0xe3, 0x58, 0x46, 0xb0, 0xb8, 0xe5, 0x7b, 0xf6, 0x4d, 0xe0, 0x4e, 0xac, + 0x09, 0x3c, 0x61, 0xdc, 0x8a, 0x29, 0x97, 0xd6, 0x02, 0xd0, 0x3d, 0x28, 0x52, 0xfe, 0xad, 0x3c, + 0xdb, 0x9d, 0x75, 0x9c, 0x29, 0x27, 0x0c, 0x9d, 0x20, 0xfe, 0x63, 0xc9, 0x50, 0xed, 0x29, 0x50, + 0x8d, 0xe1, 0xcb, 0x8f, 0x7a, 0x2e, 0x26, 0x9b, 0xc4, 0x25, 0x96, 0x4e, 0xd0, 0x2c, 0x94, 0x34, + 0xc7, 0xb8, 0xee, 0xda, 0x1d, 0x47, 0x7a, 0x34, 0x68, 0x1c, 0x0b, 0x6b, 0x2b, 0x1c, 0x8e, 0x03, + 0x0c, 0x86, 0xed, 0x6b, 0x24, 0xe3, 0x2a, 0x72, 0x33, 0x2a, 0xe0, 0x38, 0xc0, 0x08, 0x26, 0xc7, + 0x42, 0xea, 0xe4, 0xd8, 0x80, 0x7c, 0xc7, 0x68, 0xc9, 0xeb, 0xdc, 0x97, 0xfd, 0x62, 0xf1, 0xd6, + 0xca, 0xd2, 0x7e, 0xb7, 0x76, 0x3e, 0xed, 0x2d, 0x82, 0xb7, 0xe7, 0x10, 0x5a, 0x7f, 0x6b, 0x65, + 0x09, 0x33, 0x62, 0xf5, 0x77, 0x0a, 0x4c, 0xc5, 0x36, 0x79, 0x04, 0x0d, 0x74, 0x2d, 0xde, 0x40, + 0x5f, 0x1a, 0xc2, 0x65, 0x29, 0xed, 0xd3, 0x48, 0x6c, 0x82, 0xf7, 0xce, 0xf5, 0xe4, 0xf7, 0xf9, + 0x8b, 0x99, 0x2f, 0x7d, 0xd3, 0x3f, 0xca, 0xab, 0x7f, 0xc8, 0xc1, 0xc9, 0x01, 0x51, 0x84, 0xee, + 0x03, 0x84, 0xe3, 0xed, 0x00, 0xa3, 0x0d, 0x10, 0xd8, 0xf7, 0x89, 0x62, 0x82, 0x7f, 0x35, 0x0f, + 0xa1, 0x11, 0x8e, 0x88, 0x42, 0xd9, 0x25, 0x94, 0xb8, 0xbb, 0xa4, 0x75, 0x8d, 0x57, 0x7f, 0x66, + 0xba, 0xaf, 0x0e, 0x61, 0xba, 0xbe, 0xe8, 0x0d, 0xa7, 0x62, 0x1c, 0x32, 0xc6, 0x51, 0x29, 0xe8, + 0x7e, 0x68, 0x42, 0xf1, 0x14, 0xe4, 0x4a, 0xa6, 0x1d, 0xc5, 0x5f, 0xb1, 0x1c, 0x60, 0xcc, 0x8f, + 0x15, 0x38, 0x15, 0x53, 0x72, 0x9d, 0xb4, 0x1d, 0x53, 0xf3, 0x8e, 0x62, 0x22, 0xbd, 0x17, 0x2b, + 0x46, 0xaf, 0x0d, 0x61, 0x49, 0x5f, 0xc9, 0xd4, 0xb9, 0xf4, 0xcf, 0x0a, 0x9c, 0x19, 0x48, 0x71, + 0x04, 0xc9, 0xf5, 0xcd, 0x78, 0x72, 0x5d, 0x39, 0xc4, 0xbe, 0xd2, 0x2f, 0x7d, 0xcf, 0xa4, 0xda, + 0xe1, 0xff, 0xb2, 0x7b, 0xa8, 0xbf, 0x52, 0xe0, 0xb8, 0x8f, 0xc9, 0xa6, 0xc3, 0x0c, 0xc7, 0xf5, + 0x79, 0x00, 0xf9, 0x7e, 0xcb, 0xff, 0x30, 0x93, 0x0f, 0xf5, 0xbe, 0x1e, 0xac, 0xe0, 0x08, 0x16, + 0xba, 0x01, 0xc8, 0xd7, 0xb0, 0x69, 0xfa, 0xd7, 0x9b, 0xbc, 0x05, 0xe4, 0x1b, 0xd3, 0x92, 0x16, + 0xe1, 0x3e, 0x0c, 0x3c, 0x80, 0x4a, 0xfd, 0xbd, 0x12, 0xf6, 0x6d, 0x0e, 0x7e, 0x5e, 0x2d, 0xcf, + 0x95, 0x4b, 0xb5, 0x7c, 0xb4, 0xef, 0x70, 0xcc, 0xe7, 0xb6, 0xef, 0x70, 0xed, 0x52, 0x52, 0xe2, + 0x4f, 0x85, 0xc4, 0x2e, 0x78, 0x2a, 0x64, 0x9d, 0xf2, 0x6e, 0x46, 0x5e, 0xed, 0xc5, 0x4f, 0xf7, + 0x07, 0xa8, 0xc3, 0xc2, 0x74, 0xe0, 0xf5, 0xdc, 0x6c, 0xe4, 0x3d, 0x51, 0x62, 0xba, 0xc8, 0xf0, + 0xa6, 0xa8, 0xf0, 0x94, 0xde, 0x14, 0xcd, 0x46, 0xde, 0x14, 0x89, 0x9b, 0xbf, 0x70, 0x22, 0xea, + 0x7f, 0x57, 0x74, 0x3b, 0xec, 0x2f, 0xe2, 0xce, 0xef, 0xf3, 0x59, 0x5a, 0xf4, 0x01, 0x4f, 0xe6, + 0x30, 0x9c, 0x76, 0x88, 0x2b, 0xc0, 0xa1, 0x96, 0x2c, 0x53, 0x47, 0xb9, 0x32, 0xd3, 0xbd, 0x6e, + 0xed, 0xf4, 0xda, 0x40, 0x0c, 0x9c, 0x42, 0x89, 0xb6, 0x61, 0x82, 0x6e, 0x6b, 0x2e, 0x69, 0x05, + 0x8f, 0xc4, 0xc4, 0xc5, 0xef, 0x4c, 0xd6, 0xa7, 0x2f, 0xe1, 0xfd, 0x72, 0x33, 0xc6, 0x07, 0x27, + 0xf8, 0x36, 0x1a, 0x0f, 0x1f, 0x57, 0x8f, 0x3d, 0x7a, 0x5c, 0x3d, 0xf6, 0xc9, 0xe3, 0xea, 0xb1, + 0xf7, 0x7a, 0x55, 0xe5, 0x61, 0xaf, 0xaa, 0x3c, 0xea, 0x55, 0x95, 0x4f, 0x7a, 0x55, 0xe5, 0x1f, + 0xbd, 0xaa, 0xf2, 0xe3, 0x7f, 0x56, 0x8f, 0x7d, 0xeb, 0xec, 0x41, 0x4f, 0x74, 0xff, 0x1b, 0x00, + 0x00, 0xff, 0xff, 0xa5, 0x57, 0x37, 0xad, 0xc1, 0x2b, 0x00, 0x00, } func (m *AllocatedDeviceStatus) Marshal() (dAtA []byte, err error) { @@ -1178,16 +1509,18 @@ func (m *AllocatedDeviceStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - { - size, err := m.Data.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.Data != nil { + { + size, err := m.Data.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a } - i-- - dAtA[i] = 0x2a if len(m.Conditions) > 0 { for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { { @@ -1285,17 +1618,10 @@ func (m *BasicDevice) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Capacity) > 0 { - keysForCapacity := make([]string, 0, len(m.Capacity)) - for k := range m.Capacity { - keysForCapacity = append(keysForCapacity, string(k)) - } - github_com_gogo_protobuf_sortkeys.Strings(keysForCapacity) - for iNdEx := len(keysForCapacity) - 1; iNdEx >= 0; iNdEx-- { - v := m.Capacity[QualifiedName(keysForCapacity[iNdEx])] - baseI := i + if len(m.Taints) > 0 { + for iNdEx := len(m.Taints) - 1; iNdEx >= 0; iNdEx-- { { - size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Taints[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1303,21 +1629,85 @@ func (m *BasicDevice) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - i -= len(keysForCapacity[iNdEx]) - copy(dAtA[i:], keysForCapacity[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(keysForCapacity[iNdEx]))) - i-- - dAtA[i] = 0xa - i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) - i-- - dAtA[i] = 0x12 + dAtA[i] = 0x3a } } - if len(m.Attributes) > 0 { - keysForAttributes := make([]string, 0, len(m.Attributes)) - for k := range m.Attributes { - keysForAttributes = append(keysForAttributes, string(k)) + if m.AllNodes != nil { + i-- + if *m.AllNodes { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if m.NodeSelector != nil { + { + size, err := m.NodeSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.NodeName != nil { + i -= len(*m.NodeName) + copy(dAtA[i:], *m.NodeName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.NodeName))) + i-- + dAtA[i] = 0x22 + } + if len(m.ConsumesCounters) > 0 { + for iNdEx := len(m.ConsumesCounters) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ConsumesCounters[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Capacity) > 0 { + keysForCapacity := make([]string, 0, len(m.Capacity)) + for k := range m.Capacity { + keysForCapacity = append(keysForCapacity, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForCapacity) + for iNdEx := len(keysForCapacity) - 1; iNdEx >= 0; iNdEx-- { + v := m.Capacity[QualifiedName(keysForCapacity[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForCapacity[iNdEx]) + copy(dAtA[i:], keysForCapacity[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForCapacity[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Attributes) > 0 { + keysForAttributes := make([]string, 0, len(m.Attributes)) + for k := range m.Attributes { + keysForAttributes = append(keysForAttributes, string(k)) } github_com_gogo_protobuf_sortkeys.Strings(keysForAttributes) for iNdEx := len(keysForAttributes) - 1; iNdEx >= 0; iNdEx-- { @@ -1374,6 +1764,96 @@ func (m *CELDeviceSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Counter) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Counter) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Counter) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Value.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *CounterSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CounterSet) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CounterSet) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Counters) > 0 { + keysForCounters := make([]string, 0, len(m.Counters)) + for k := range m.Counters { + keysForCounters = append(keysForCounters, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForCounters) + for iNdEx := len(keysForCounters) - 1; iNdEx >= 0; iNdEx-- { + v := m.Counters[string(keysForCounters[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForCounters[iNdEx]) + copy(dAtA[i:], keysForCounters[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForCounters[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *Device) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1919,6 +2399,63 @@ func (m *DeviceConstraint) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *DeviceCounterConsumption) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceCounterConsumption) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceCounterConsumption) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Counters) > 0 { + keysForCounters := make([]string, 0, len(m.Counters)) + for k := range m.Counters { + keysForCounters = append(keysForCounters, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForCounters) + for iNdEx := len(keysForCounters) - 1; iNdEx >= 0; iNdEx-- { + v := m.Counters[string(keysForCounters[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForCounters[iNdEx]) + copy(dAtA[i:], keysForCounters[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForCounters[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.CounterSet) + copy(dAtA[i:], m.CounterSet) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CounterSet))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *DeviceRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1939,6 +2476,34 @@ func (m *DeviceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Tolerations) > 0 { + for iNdEx := len(m.Tolerations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Tolerations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if len(m.FirstAvailable) > 0 { + for iNdEx := len(m.FirstAvailable) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FirstAvailable[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } if m.AdminAccess != nil { i-- if *m.AdminAccess { @@ -2004,6 +2569,20 @@ func (m *DeviceRequestAllocationResult) MarshalToSizedBuffer(dAtA []byte) (int, _ = i var l int _ = l + if len(m.Tolerations) > 0 { + for iNdEx := len(m.Tolerations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Tolerations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } if m.AdminAccess != nil { i-- if *m.AdminAccess { @@ -2072,7 +2651,7 @@ func (m *DeviceSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *NetworkDeviceData) Marshal() (dAtA []byte, err error) { +func (m *DeviceSubRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2082,77 +2661,66 @@ func (m *NetworkDeviceData) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *NetworkDeviceData) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceSubRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *NetworkDeviceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceSubRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.HardwareAddress) - copy(dAtA[i:], m.HardwareAddress) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.HardwareAddress))) - i-- - dAtA[i] = 0x1a - if len(m.IPs) > 0 { - for iNdEx := len(m.IPs) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.IPs[iNdEx]) - copy(dAtA[i:], m.IPs[iNdEx]) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.IPs[iNdEx]))) + if len(m.Tolerations) > 0 { + for iNdEx := len(m.Tolerations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Tolerations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x12 + dAtA[i] = 0x3a } } - i -= len(m.InterfaceName) - copy(dAtA[i:], m.InterfaceName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.InterfaceName))) + i = encodeVarintGenerated(dAtA, i, uint64(m.Count)) i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *OpaqueDeviceConfiguration) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *OpaqueDeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Parameters.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + dAtA[i] = 0x28 + i -= len(m.AllocationMode) + copy(dAtA[i:], m.AllocationMode) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AllocationMode))) + i-- + dAtA[i] = 0x22 + if len(m.Selectors) > 0 { + for iNdEx := len(m.Selectors) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Selectors[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) } + i -= len(m.DeviceClassName) + copy(dAtA[i:], m.DeviceClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DeviceClassName))) i-- dAtA[i] = 0x12 - i -= len(m.Driver) - copy(dAtA[i:], m.Driver) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceClaim) Marshal() (dAtA []byte, err error) { +func (m *DeviceTaint) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2162,50 +2730,47 @@ func (m *ResourceClaim) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaim) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceTaint) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaim) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceTaint) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.TimeAdded != nil { + { + size, err := m.TimeAdded.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 } + i -= len(m.Effect) + copy(dAtA[i:], m.Effect) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Effect))) i-- dAtA[i] = 0x1a - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Value))) i-- dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceClaimConsumerReference) Marshal() (dAtA []byte, err error) { +func (m *DeviceTaintRule) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2215,40 +2780,40 @@ func (m *ResourceClaimConsumerReference) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimConsumerReference) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceTaintRule) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimConsumerReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceTaintRule) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.UID) - copy(dAtA[i:], m.UID) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) - i-- - dAtA[i] = 0x2a - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x22 - i -= len(m.Resource) - copy(dAtA[i:], m.Resource) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x1a - i -= len(m.APIGroup) - copy(dAtA[i:], m.APIGroup) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroup))) + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceClaimList) Marshal() (dAtA []byte, err error) { +func (m *DeviceTaintRuleList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2258,12 +2823,12 @@ func (m *ResourceClaimList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimList) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceTaintRuleList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceTaintRuleList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2295,7 +2860,7 @@ func (m *ResourceClaimList) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ResourceClaimSpec) Marshal() (dAtA []byte, err error) { +func (m *DeviceTaintRuleSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2305,18 +2870,18 @@ func (m *ResourceClaimSpec) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimSpec) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceTaintRuleSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceTaintRuleSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size, err := m.Devices.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Taint.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2324,11 +2889,23 @@ func (m *ResourceClaimSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 + if m.DeviceSelector != nil { + { + size, err := m.DeviceSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } return len(dAtA) - i, nil } -func (m *ResourceClaimStatus) Marshal() (dAtA []byte, err error) { +func (m *DeviceTaintSelector) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2338,20 +2915,20 @@ func (m *ResourceClaimStatus) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimStatus) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceTaintSelector) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceTaintSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Devices) > 0 { - for iNdEx := len(m.Devices) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Selectors) > 0 { + for iNdEx := len(m.Selectors) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.Devices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Selectors[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2359,39 +2936,41 @@ func (m *ResourceClaimStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a } } - if len(m.ReservedFor) > 0 { - for iNdEx := len(m.ReservedFor) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.ReservedFor[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } + if m.Device != nil { + i -= len(*m.Device) + copy(dAtA[i:], *m.Device) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Device))) + i-- + dAtA[i] = 0x22 } - if m.Allocation != nil { - { - size, err := m.Allocation.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + if m.Pool != nil { + i -= len(*m.Pool) + copy(dAtA[i:], *m.Pool) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Pool))) + i-- + dAtA[i] = 0x1a + } + if m.Driver != nil { + i -= len(*m.Driver) + copy(dAtA[i:], *m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.Driver))) + i-- + dAtA[i] = 0x12 + } + if m.DeviceClassName != nil { + i -= len(*m.DeviceClassName) + copy(dAtA[i:], *m.DeviceClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.DeviceClassName))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *ResourceClaimTemplate) Marshal() (dAtA []byte, err error) { +func (m *DeviceToleration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2401,40 +2980,45 @@ func (m *ResourceClaimTemplate) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimTemplate) MarshalTo(dAtA []byte) (int, error) { +func (m *DeviceToleration) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimTemplate) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *DeviceToleration) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + if m.TolerationSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TolerationSeconds)) + i-- + dAtA[i] = 0x28 } + i -= len(m.Effect) + copy(dAtA[i:], m.Effect) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Effect))) + i-- + dAtA[i] = 0x22 + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x1a + i -= len(m.Operator) + copy(dAtA[i:], m.Operator) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operator))) i-- dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceClaimTemplateList) Marshal() (dAtA []byte, err error) { +func (m *NetworkDeviceData) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2444,44 +3028,39 @@ func (m *ResourceClaimTemplateList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimTemplateList) MarshalTo(dAtA []byte) (int, error) { +func (m *NetworkDeviceData) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimTemplateList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *NetworkDeviceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Items) > 0 { - for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.HardwareAddress) + copy(dAtA[i:], m.HardwareAddress) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.HardwareAddress))) + i-- + dAtA[i] = 0x1a + if len(m.IPs) > 0 { + for iNdEx := len(m.IPs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.IPs[iNdEx]) + copy(dAtA[i:], m.IPs[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.IPs[iNdEx]))) i-- dAtA[i] = 0x12 } } - { - size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.InterfaceName) + copy(dAtA[i:], m.InterfaceName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.InterfaceName))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceClaimTemplateSpec) Marshal() (dAtA []byte, err error) { +func (m *OpaqueDeviceConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2491,18 +3070,18 @@ func (m *ResourceClaimTemplateSpec) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceClaimTemplateSpec) MarshalTo(dAtA []byte) (int, error) { +func (m *OpaqueDeviceConfiguration) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceClaimTemplateSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *OpaqueDeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Parameters.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2511,20 +3090,15 @@ func (m *ResourceClaimTemplateSpec) MarshalToSizedBuffer(dAtA []byte) (int, erro } i-- dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.Driver) + copy(dAtA[i:], m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourcePool) Marshal() (dAtA []byte, err error) { +func (m *ResourceClaim) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2534,31 +3108,50 @@ func (m *ResourcePool) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourcePool) MarshalTo(dAtA []byte) (int, error) { +func (m *ResourceClaim) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourcePool) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ResourceClaim) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i = encodeVarintGenerated(dAtA, i, uint64(m.ResourceSliceCount)) - i-- - dAtA[i] = 0x18 - i = encodeVarintGenerated(dAtA, i, uint64(m.Generation)) - i-- - dAtA[i] = 0x10 - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceSlice) Marshal() (dAtA []byte, err error) { +func (m *ResourceClaimConsumerReference) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2568,40 +3161,40 @@ func (m *ResourceSlice) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceSlice) MarshalTo(dAtA []byte) (int, error) { +func (m *ResourceClaimConsumerReference) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceSlice) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ResourceClaimConsumerReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + i -= len(m.UID) + copy(dAtA[i:], m.UID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) i-- - dAtA[i] = 0x12 - { - size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) - } + dAtA[i] = 0x2a + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x22 + i -= len(m.Resource) + copy(dAtA[i:], m.Resource) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) + i-- + dAtA[i] = 0x1a + i -= len(m.APIGroup) + copy(dAtA[i:], m.APIGroup) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIGroup))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func (m *ResourceSliceList) Marshal() (dAtA []byte, err error) { +func (m *ResourceClaimList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2611,12 +3204,12 @@ func (m *ResourceSliceList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceSliceList) MarshalTo(dAtA []byte) (int, error) { +func (m *ResourceClaimList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ResourceClaimList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2648,7 +3241,7 @@ func (m *ResourceSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ResourceSliceSpec) Marshal() (dAtA []byte, err error) { +func (m *ResourceClaimSpec) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2658,12 +3251,45 @@ func (m *ResourceSliceSpec) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ResourceSliceSpec) MarshalTo(dAtA []byte) (int, error) { +func (m *ResourceClaimSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ResourceSliceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ResourceClaimSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Devices.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ResourceClaimStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourceClaimStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceClaimStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2679,20 +3305,26 @@ func (m *ResourceSliceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 + dAtA[i] = 0x22 } } - i-- - if m.AllNodes { - dAtA[i] = 1 - } else { - dAtA[i] = 0 + if len(m.ReservedFor) > 0 { + for iNdEx := len(m.ReservedFor) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ReservedFor[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } } - i-- - dAtA[i] = 0x28 - if m.NodeSelector != nil { + if m.Allocation != nil { { - size, err := m.NodeSelector.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Allocation.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2700,15 +3332,33 @@ func (m *ResourceSliceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0xa } - i -= len(m.NodeName) - copy(dAtA[i:], m.NodeName) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.NodeName))) - i-- - dAtA[i] = 0x1a + return len(dAtA) - i, nil +} + +func (m *ResourceClaimTemplate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResourceClaimTemplate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceClaimTemplate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l { - size, err := m.Pool.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2717,282 +3367,432 @@ func (m *ResourceSliceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x12 - i -= len(m.Driver) - copy(dAtA[i:], m.Driver) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } -func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { - offset -= sovGenerated(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *ResourceClaimTemplateList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *AllocatedDeviceStatus) Size() (n int) { - if m == nil { - return 0 - } + +func (m *ResourceClaimTemplateList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceClaimTemplateList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Driver) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Pool) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Device) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Conditions) > 0 { - for _, e := range m.Conditions { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } } - l = m.Data.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.NetworkData != nil { - l = m.NetworkData.Size() - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *AllocationResult) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Devices.Size() - n += 1 + l + sovGenerated(uint64(l)) - if m.NodeSelector != nil { - l = m.NodeSelector.Size() - n += 1 + l + sovGenerated(uint64(l)) +func (m *ResourceClaimTemplateSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - return n + return dAtA[:n], nil } -func (m *BasicDevice) Size() (n int) { - if m == nil { - return 0 - } +func (m *ResourceClaimTemplateSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceClaimTemplateSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Attributes) > 0 { - for k, v := range m.Attributes { - _ = k - _ = v - l = v.Size() - mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) - n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - if len(m.Capacity) > 0 { - for k, v := range m.Capacity { - _ = k - _ = v - l = v.Size() - mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) - n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *CELDeviceSelector) Size() (n int) { - if m == nil { - return 0 +func (m *ResourcePool) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - l = len(m.Expression) - n += 1 + l + sovGenerated(uint64(l)) - return n + return dAtA[:n], nil } -func (m *Device) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - if m.Basic != nil { - l = m.Basic.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - return n +func (m *ResourcePool) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *DeviceAllocationConfiguration) Size() (n int) { - if m == nil { - return 0 - } +func (m *ResourcePool) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Source) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Requests) > 0 { - for _, s := range m.Requests { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - l = m.DeviceConfiguration.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n + i = encodeVarintGenerated(dAtA, i, uint64(m.ResourceSliceCount)) + i-- + dAtA[i] = 0x18 + i = encodeVarintGenerated(dAtA, i, uint64(m.Generation)) + i-- + dAtA[i] = 0x10 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *DeviceAllocationResult) Size() (n int) { - if m == nil { - return 0 +func (m *ResourceSlice) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } + return dAtA[:n], nil +} + +func (m *ResourceSlice) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceSlice) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Results) > 0 { - for _, e := range m.Results { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - if len(m.Config) > 0 { - for _, e := range m.Config { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *DeviceAttribute) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.IntValue != nil { - n += 1 + sovGenerated(uint64(*m.IntValue)) - } - if m.BoolValue != nil { - n += 2 - } - if m.StringValue != nil { - l = len(*m.StringValue) - n += 1 + l + sovGenerated(uint64(l)) - } - if m.VersionValue != nil { - l = len(*m.VersionValue) - n += 1 + l + sovGenerated(uint64(l)) +func (m *ResourceSliceList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - return n + return dAtA[:n], nil } -func (m *DeviceClaim) Size() (n int) { - if m == nil { - return 0 - } +func (m *ResourceSliceList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceSliceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Requests) > 0 { - for _, e := range m.Requests { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - if len(m.Constraints) > 0 { - for _, e := range m.Constraints { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } } - if len(m.Config) > 0 { - for _, e := range m.Config { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *DeviceClaimConfiguration) Size() (n int) { - if m == nil { - return 0 +func (m *ResourceSliceSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - if len(m.Requests) > 0 { - for _, s := range m.Requests { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) + return dAtA[:n], nil +} + +func (m *ResourceSliceSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResourceSliceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.SharedCounters) > 0 { + for iNdEx := len(m.SharedCounters) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SharedCounters[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 } } - l = m.DeviceConfiguration.Size() - n += 1 + l + sovGenerated(uint64(l)) - return n + if m.PerDeviceNodeSelection != nil { + i-- + if *m.PerDeviceNodeSelection { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + if len(m.Devices) > 0 { + for iNdEx := len(m.Devices) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Devices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + i-- + if m.AllNodes { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + if m.NodeSelector != nil { + { + size, err := m.NodeSelector.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + i -= len(m.NodeName) + copy(dAtA[i:], m.NodeName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.NodeName))) + i-- + dAtA[i] = 0x1a + { + size, err := m.Pool.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.Driver) + copy(dAtA[i:], m.Driver) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Driver))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *DeviceClass) Size() (n int) { +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *AllocatedDeviceStatus) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.ObjectMeta.Size() + l = len(m.Driver) n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() + l = len(m.Pool) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Device) n += 1 + l + sovGenerated(uint64(l)) + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.Data != nil { + l = m.Data.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.NetworkData != nil { + l = m.NetworkData.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } -func (m *DeviceClassConfiguration) Size() (n int) { +func (m *AllocationResult) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.DeviceConfiguration.Size() + l = m.Devices.Size() n += 1 + l + sovGenerated(uint64(l)) + if m.NodeSelector != nil { + l = m.NodeSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } -func (m *DeviceClassList) Size() (n int) { +func (m *BasicDevice) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Attributes) > 0 { + for k, v := range m.Attributes { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } - return n -} - -func (m *DeviceClassSpec) Size() (n int) { - if m == nil { - return 0 + if len(m.Capacity) > 0 { + for k, v := range m.Capacity { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } } - var l int - _ = l - if len(m.Selectors) > 0 { - for _, e := range m.Selectors { + if len(m.ConsumesCounters) > 0 { + for _, e := range m.ConsumesCounters { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } - if len(m.Config) > 0 { - for _, e := range m.Config { + if m.NodeName != nil { + l = len(*m.NodeName) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.NodeSelector != nil { + l = m.NodeSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.AllNodes != nil { + n += 2 + } + if len(m.Taints) > 0 { + for _, e := range m.Taints { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } @@ -3000,39 +3800,29 @@ func (m *DeviceClassSpec) Size() (n int) { return n } -func (m *DeviceConfiguration) Size() (n int) { +func (m *CELDeviceSelector) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.Opaque != nil { - l = m.Opaque.Size() - n += 1 + l + sovGenerated(uint64(l)) - } + l = len(m.Expression) + n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *DeviceConstraint) Size() (n int) { +func (m *Counter) Size() (n int) { if m == nil { return 0 } var l int _ = l - if len(m.Requests) > 0 { - for _, s := range m.Requests { - l = len(s) - n += 1 + l + sovGenerated(uint64(l)) - } - } - if m.MatchAttribute != nil { - l = len(*m.MatchAttribute) - n += 1 + l + sovGenerated(uint64(l)) - } + l = m.Value.Size() + n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *DeviceRequest) Size() (n int) { +func (m *CounterSet) Size() (n int) { if m == nil { return 0 } @@ -3040,89 +3830,141 @@ func (m *DeviceRequest) Size() (n int) { _ = l l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) - l = len(m.DeviceClassName) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Selectors) > 0 { - for _, e := range m.Selectors { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Counters) > 0 { + for k, v := range m.Counters { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } - l = len(m.AllocationMode) - n += 1 + l + sovGenerated(uint64(l)) - n += 1 + sovGenerated(uint64(m.Count)) - if m.AdminAccess != nil { - n += 2 - } return n } -func (m *DeviceRequestAllocationResult) Size() (n int) { +func (m *Device) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Request) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Driver) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Pool) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Device) + l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) - if m.AdminAccess != nil { - n += 2 + if m.Basic != nil { + l = m.Basic.Size() + n += 1 + l + sovGenerated(uint64(l)) } return n } -func (m *DeviceSelector) Size() (n int) { +func (m *DeviceAllocationConfiguration) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.CEL != nil { - l = m.CEL.Size() - n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Source) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Requests) > 0 { + for _, s := range m.Requests { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } } + l = m.DeviceConfiguration.Size() + n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *NetworkDeviceData) Size() (n int) { +func (m *DeviceAllocationResult) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.InterfaceName) - n += 1 + l + sovGenerated(uint64(l)) - if len(m.IPs) > 0 { - for _, s := range m.IPs { - l = len(s) + if len(m.Results) > 0 { + for _, e := range m.Results { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Config) > 0 { + for _, e := range m.Config { + l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } - l = len(m.HardwareAddress) - n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *OpaqueDeviceConfiguration) Size() (n int) { +func (m *DeviceAttribute) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Driver) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Parameters.Size() + if m.IntValue != nil { + n += 1 + sovGenerated(uint64(*m.IntValue)) + } + if m.BoolValue != nil { + n += 2 + } + if m.StringValue != nil { + l = len(*m.StringValue) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.VersionValue != nil { + l = len(*m.VersionValue) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *DeviceClaim) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Requests) > 0 { + for _, e := range m.Requests { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Constraints) > 0 { + for _, e := range m.Constraints { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Config) > 0 { + for _, e := range m.Config { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *DeviceClaimConfiguration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Requests) > 0 { + for _, s := range m.Requests { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = m.DeviceConfiguration.Size() n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ResourceClaim) Size() (n int) { +func (m *DeviceClass) Size() (n int) { if m == nil { return 0 } @@ -3132,29 +3974,21 @@ func (m *ResourceClaim) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = m.Spec.Size() n += 1 + l + sovGenerated(uint64(l)) - l = m.Status.Size() - n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ResourceClaimConsumerReference) Size() (n int) { +func (m *DeviceClassConfiguration) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.APIGroup) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Resource) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.Name) - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.UID) + l = m.DeviceConfiguration.Size() n += 1 + l + sovGenerated(uint64(l)) return n } -func (m *ResourceClaimList) Size() (n int) { +func (m *DeviceClassList) Size() (n int) { if m == nil { return 0 } @@ -3171,65 +4005,135 @@ func (m *ResourceClaimList) Size() (n int) { return n } -func (m *ResourceClaimSpec) Size() (n int) { +func (m *DeviceClassSpec) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.Devices.Size() - n += 1 + l + sovGenerated(uint64(l)) + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Config) > 0 { + for _, e := range m.Config { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } -func (m *ResourceClaimStatus) Size() (n int) { +func (m *DeviceConfiguration) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.Allocation != nil { - l = m.Allocation.Size() + if m.Opaque != nil { + l = m.Opaque.Size() n += 1 + l + sovGenerated(uint64(l)) } - if len(m.ReservedFor) > 0 { - for _, e := range m.ReservedFor { - l = e.Size() + return n +} + +func (m *DeviceConstraint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Requests) > 0 { + for _, s := range m.Requests { + l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } - if len(m.Devices) > 0 { - for _, e := range m.Devices { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) + if m.MatchAttribute != nil { + l = len(*m.MatchAttribute) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *DeviceCounterConsumption) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CounterSet) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Counters) > 0 { + for k, v := range m.Counters { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } return n } -func (m *ResourceClaimTemplate) Size() (n int) { +func (m *DeviceRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.ObjectMeta.Size() + l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() + l = len(m.DeviceClassName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.AllocationMode) n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Count)) + if m.AdminAccess != nil { + n += 2 + } + if len(m.FirstAvailable) > 0 { + for _, e := range m.FirstAvailable { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Tolerations) > 0 { + for _, e := range m.Tolerations { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } -func (m *ResourceClaimTemplateList) Size() (n int) { +func (m *DeviceRequestAllocationResult) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.ListMeta.Size() + l = len(m.Request) n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Pool) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Device) + n += 1 + l + sovGenerated(uint64(l)) + if m.AdminAccess != nil { + n += 2 + } + if len(m.Tolerations) > 0 { + for _, e := range m.Tolerations { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } @@ -3237,33 +4141,67 @@ func (m *ResourceClaimTemplateList) Size() (n int) { return n } -func (m *ResourceClaimTemplateSpec) Size() (n int) { +func (m *DeviceSelector) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = m.ObjectMeta.Size() + if m.CEL != nil { + l = m.CEL.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *DeviceSubRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) - l = m.Spec.Size() + l = len(m.DeviceClassName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.AllocationMode) n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Count)) + if len(m.Tolerations) > 0 { + for _, e := range m.Tolerations { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } -func (m *ResourcePool) Size() (n int) { +func (m *DeviceTaint) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Name) + l = len(m.Key) n += 1 + l + sovGenerated(uint64(l)) - n += 1 + sovGenerated(uint64(m.Generation)) - n += 1 + sovGenerated(uint64(m.ResourceSliceCount)) + l = len(m.Value) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Effect) + n += 1 + l + sovGenerated(uint64(l)) + if m.TimeAdded != nil { + l = m.TimeAdded.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } -func (m *ResourceSlice) Size() (n int) { +func (m *DeviceTaintRule) Size() (n int) { if m == nil { return 0 } @@ -3276,7 +4214,7 @@ func (m *ResourceSlice) Size() (n int) { return n } -func (m *ResourceSliceList) Size() (n int) { +func (m *DeviceTaintRuleList) Size() (n int) { if m == nil { return 0 } @@ -3293,25 +4231,45 @@ func (m *ResourceSliceList) Size() (n int) { return n } -func (m *ResourceSliceSpec) Size() (n int) { +func (m *DeviceTaintRuleSpec) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Driver) - n += 1 + l + sovGenerated(uint64(l)) - l = m.Pool.Size() - n += 1 + l + sovGenerated(uint64(l)) - l = len(m.NodeName) + if m.DeviceSelector != nil { + l = m.DeviceSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = m.Taint.Size() n += 1 + l + sovGenerated(uint64(l)) - if m.NodeSelector != nil { - l = m.NodeSelector.Size() + return n +} + +func (m *DeviceTaintSelector) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DeviceClassName != nil { + l = len(*m.DeviceClassName) n += 1 + l + sovGenerated(uint64(l)) } - n += 2 - if len(m.Devices) > 0 { - for _, e := range m.Devices { + if m.Driver != nil { + l = len(*m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Pool != nil { + l = len(*m.Pool) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Device != nil { + l = len(*m.Device) + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } @@ -3319,15 +4277,273 @@ func (m *ResourceSliceSpec) Size() (n int) { return n } -func sovGenerated(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozGenerated(x uint64) (n int) { - return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (this *AllocatedDeviceStatus) String() string { - if this == nil { - return "nil" +func (m *DeviceToleration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operator) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Value) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Effect) + n += 1 + l + sovGenerated(uint64(l)) + if m.TolerationSeconds != nil { + n += 1 + sovGenerated(uint64(*m.TolerationSeconds)) + } + return n +} + +func (m *NetworkDeviceData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.InterfaceName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.IPs) > 0 { + for _, s := range m.IPs { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.HardwareAddress) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *OpaqueDeviceConfiguration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Parameters.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceClaim) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceClaimConsumerReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.APIGroup) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Resource) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceClaimList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ResourceClaimSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Devices.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceClaimStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Allocation != nil { + l = m.Allocation.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.ReservedFor) > 0 { + for _, e := range m.ReservedFor { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ResourceClaimTemplate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceClaimTemplateList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ResourceClaimTemplateSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourcePool) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Generation)) + n += 1 + sovGenerated(uint64(m.ResourceSliceCount)) + return n +} + +func (m *ResourceSlice) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ResourceSliceList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ResourceSliceSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Driver) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Pool.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.NodeName) + n += 1 + l + sovGenerated(uint64(l)) + if m.NodeSelector != nil { + l = m.NodeSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 2 + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.PerDeviceNodeSelection != nil { + n += 2 + } + if len(m.SharedCounters) > 0 { + for _, e := range m.SharedCounters { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *AllocatedDeviceStatus) String() string { + if this == nil { + return "nil" } repeatedStringForConditions := "[]Condition{" for _, f := range this.Conditions { @@ -3339,7 +4555,7 @@ func (this *AllocatedDeviceStatus) String() string { `Pool:` + fmt.Sprintf("%v", this.Pool) + `,`, `Device:` + fmt.Sprintf("%v", this.Device) + `,`, `Conditions:` + repeatedStringForConditions + `,`, - `Data:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Data), "RawExtension", "runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `Data:` + strings.Replace(fmt.Sprintf("%v", this.Data), "RawExtension", "runtime.RawExtension", 1) + `,`, `NetworkData:` + strings.Replace(this.NetworkData.String(), "NetworkDeviceData", "NetworkDeviceData", 1) + `,`, `}`, }, "") @@ -3360,7 +4576,17 @@ func (this *BasicDevice) String() string { if this == nil { return "nil" } - keysForAttributes := make([]string, 0, len(this.Attributes)) + repeatedStringForConsumesCounters := "[]DeviceCounterConsumption{" + for _, f := range this.ConsumesCounters { + repeatedStringForConsumesCounters += strings.Replace(strings.Replace(f.String(), "DeviceCounterConsumption", "DeviceCounterConsumption", 1), `&`, ``, 1) + "," + } + repeatedStringForConsumesCounters += "}" + repeatedStringForTaints := "[]DeviceTaint{" + for _, f := range this.Taints { + repeatedStringForTaints += strings.Replace(strings.Replace(f.String(), "DeviceTaint", "DeviceTaint", 1), `&`, ``, 1) + "," + } + repeatedStringForTaints += "}" + keysForAttributes := make([]string, 0, len(this.Attributes)) for k := range this.Attributes { keysForAttributes = append(keysForAttributes, string(k)) } @@ -3383,6 +4609,11 @@ func (this *BasicDevice) String() string { s := strings.Join([]string{`&BasicDevice{`, `Attributes:` + mapStringForAttributes + `,`, `Capacity:` + mapStringForCapacity + `,`, + `ConsumesCounters:` + repeatedStringForConsumesCounters + `,`, + `NodeName:` + valueToStringGenerated(this.NodeName) + `,`, + `NodeSelector:` + strings.Replace(fmt.Sprintf("%v", this.NodeSelector), "NodeSelector", "v11.NodeSelector", 1) + `,`, + `AllNodes:` + valueToStringGenerated(this.AllNodes) + `,`, + `Taints:` + repeatedStringForTaints + `,`, `}`, }, "") return s @@ -3397,6 +4628,37 @@ func (this *CELDeviceSelector) String() string { }, "") return s } +func (this *Counter) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Counter{`, + `Value:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Value), "Quantity", "resource.Quantity", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *CounterSet) String() string { + if this == nil { + return "nil" + } + keysForCounters := make([]string, 0, len(this.Counters)) + for k := range this.Counters { + keysForCounters = append(keysForCounters, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForCounters) + mapStringForCounters := "map[string]Counter{" + for _, k := range keysForCounters { + mapStringForCounters += fmt.Sprintf("%v: %v,", k, this.Counters[k]) + } + mapStringForCounters += "}" + s := strings.Join([]string{`&CounterSet{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Counters:` + mapStringForCounters + `,`, + `}`, + }, "") + return s +} func (this *Device) String() string { if this == nil { return "nil" @@ -3571,6 +4833,27 @@ func (this *DeviceConstraint) String() string { }, "") return s } +func (this *DeviceCounterConsumption) String() string { + if this == nil { + return "nil" + } + keysForCounters := make([]string, 0, len(this.Counters)) + for k := range this.Counters { + keysForCounters = append(keysForCounters, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForCounters) + mapStringForCounters := "map[string]Counter{" + for _, k := range keysForCounters { + mapStringForCounters += fmt.Sprintf("%v: %v,", k, this.Counters[k]) + } + mapStringForCounters += "}" + s := strings.Join([]string{`&DeviceCounterConsumption{`, + `CounterSet:` + fmt.Sprintf("%v", this.CounterSet) + `,`, + `Counters:` + mapStringForCounters + `,`, + `}`, + }, "") + return s +} func (this *DeviceRequest) String() string { if this == nil { return "nil" @@ -3580,6 +4863,16 @@ func (this *DeviceRequest) String() string { repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," } repeatedStringForSelectors += "}" + repeatedStringForFirstAvailable := "[]DeviceSubRequest{" + for _, f := range this.FirstAvailable { + repeatedStringForFirstAvailable += strings.Replace(strings.Replace(f.String(), "DeviceSubRequest", "DeviceSubRequest", 1), `&`, ``, 1) + "," + } + repeatedStringForFirstAvailable += "}" + repeatedStringForTolerations := "[]DeviceToleration{" + for _, f := range this.Tolerations { + repeatedStringForTolerations += strings.Replace(strings.Replace(f.String(), "DeviceToleration", "DeviceToleration", 1), `&`, ``, 1) + "," + } + repeatedStringForTolerations += "}" s := strings.Join([]string{`&DeviceRequest{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, @@ -3587,6 +4880,8 @@ func (this *DeviceRequest) String() string { `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, `Count:` + fmt.Sprintf("%v", this.Count) + `,`, `AdminAccess:` + valueToStringGenerated(this.AdminAccess) + `,`, + `FirstAvailable:` + repeatedStringForFirstAvailable + `,`, + `Tolerations:` + repeatedStringForTolerations + `,`, `}`, }, "") return s @@ -3595,12 +4890,18 @@ func (this *DeviceRequestAllocationResult) String() string { if this == nil { return "nil" } + repeatedStringForTolerations := "[]DeviceToleration{" + for _, f := range this.Tolerations { + repeatedStringForTolerations += strings.Replace(strings.Replace(f.String(), "DeviceToleration", "DeviceToleration", 1), `&`, ``, 1) + "," + } + repeatedStringForTolerations += "}" s := strings.Join([]string{`&DeviceRequestAllocationResult{`, `Request:` + fmt.Sprintf("%v", this.Request) + `,`, `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, `Pool:` + fmt.Sprintf("%v", this.Pool) + `,`, `Device:` + fmt.Sprintf("%v", this.Device) + `,`, `AdminAccess:` + valueToStringGenerated(this.AdminAccess) + `,`, + `Tolerations:` + repeatedStringForTolerations + `,`, `}`, }, "") return s @@ -3615,6 +4916,115 @@ func (this *DeviceSelector) String() string { }, "") return s } +func (this *DeviceSubRequest) String() string { + if this == nil { + return "nil" + } + repeatedStringForSelectors := "[]DeviceSelector{" + for _, f := range this.Selectors { + repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," + } + repeatedStringForSelectors += "}" + repeatedStringForTolerations := "[]DeviceToleration{" + for _, f := range this.Tolerations { + repeatedStringForTolerations += strings.Replace(strings.Replace(f.String(), "DeviceToleration", "DeviceToleration", 1), `&`, ``, 1) + "," + } + repeatedStringForTolerations += "}" + s := strings.Join([]string{`&DeviceSubRequest{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, + `Selectors:` + repeatedStringForSelectors + `,`, + `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, + `Count:` + fmt.Sprintf("%v", this.Count) + `,`, + `Tolerations:` + repeatedStringForTolerations + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaint) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaint{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `Value:` + fmt.Sprintf("%v", this.Value) + `,`, + `Effect:` + fmt.Sprintf("%v", this.Effect) + `,`, + `TimeAdded:` + strings.Replace(fmt.Sprintf("%v", this.TimeAdded), "Time", "v1.Time", 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRule) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaintRule{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "DeviceTaintRuleSpec", "DeviceTaintRuleSpec", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRuleList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]DeviceTaintRule{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "DeviceTaintRule", "DeviceTaintRule", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&DeviceTaintRuleList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintRuleSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceTaintRuleSpec{`, + `DeviceSelector:` + strings.Replace(this.DeviceSelector.String(), "DeviceTaintSelector", "DeviceTaintSelector", 1) + `,`, + `Taint:` + strings.Replace(strings.Replace(this.Taint.String(), "DeviceTaint", "DeviceTaint", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeviceTaintSelector) String() string { + if this == nil { + return "nil" + } + repeatedStringForSelectors := "[]DeviceSelector{" + for _, f := range this.Selectors { + repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," + } + repeatedStringForSelectors += "}" + s := strings.Join([]string{`&DeviceTaintSelector{`, + `DeviceClassName:` + valueToStringGenerated(this.DeviceClassName) + `,`, + `Driver:` + valueToStringGenerated(this.Driver) + `,`, + `Pool:` + valueToStringGenerated(this.Pool) + `,`, + `Device:` + valueToStringGenerated(this.Device) + `,`, + `Selectors:` + repeatedStringForSelectors + `,`, + `}`, + }, "") + return s +} +func (this *DeviceToleration) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeviceToleration{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `Operator:` + fmt.Sprintf("%v", this.Operator) + `,`, + `Value:` + fmt.Sprintf("%v", this.Value) + `,`, + `Effect:` + fmt.Sprintf("%v", this.Effect) + `,`, + `TolerationSeconds:` + valueToStringGenerated(this.TolerationSeconds) + `,`, + `}`, + }, "") + return s +} func (this *NetworkDeviceData) String() string { if this == nil { return "nil" @@ -3797,6 +5207,11 @@ func (this *ResourceSliceSpec) String() string { repeatedStringForDevices += strings.Replace(strings.Replace(f.String(), "Device", "Device", 1), `&`, ``, 1) + "," } repeatedStringForDevices += "}" + repeatedStringForSharedCounters := "[]CounterSet{" + for _, f := range this.SharedCounters { + repeatedStringForSharedCounters += strings.Replace(strings.Replace(f.String(), "CounterSet", "CounterSet", 1), `&`, ``, 1) + "," + } + repeatedStringForSharedCounters += "}" s := strings.Join([]string{`&ResourceSliceSpec{`, `Driver:` + fmt.Sprintf("%v", this.Driver) + `,`, `Pool:` + strings.Replace(strings.Replace(this.Pool.String(), "ResourcePool", "ResourcePool", 1), `&`, ``, 1) + `,`, @@ -3804,6 +5219,8 @@ func (this *ResourceSliceSpec) String() string { `NodeSelector:` + strings.Replace(fmt.Sprintf("%v", this.NodeSelector), "NodeSelector", "v11.NodeSelector", 1) + `,`, `AllNodes:` + fmt.Sprintf("%v", this.AllNodes) + `,`, `Devices:` + repeatedStringForDevices + `,`, + `PerDeviceNodeSelection:` + valueToStringGenerated(this.PerDeviceNodeSelection) + `,`, + `SharedCounters:` + repeatedStringForSharedCounters + `,`, `}`, }, "") return s @@ -3813,10 +5230,1915 @@ func valueToStringGenerated(v interface{}) string { if rv.IsNil() { return "nil" } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AllocatedDeviceStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AllocatedDeviceStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Driver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pool = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Device = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, v1.Condition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Data == nil { + m.Data = &runtime.RawExtension{} + } + if err := m.Data.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetworkData", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NetworkData == nil { + m.NetworkData = &NetworkDeviceData{} + } + if err := m.NetworkData.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AllocationResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AllocationResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Devices.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NodeSelector == nil { + m.NodeSelector = &v11.NodeSelector{} + } + if err := m.NodeSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BasicDevice) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BasicDevice: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BasicDevice: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Attributes == nil { + m.Attributes = make(map[QualifiedName]DeviceAttribute) + } + var mapkey QualifiedName + mapvalue := &DeviceAttribute{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = QualifiedName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &DeviceAttribute{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Attributes[QualifiedName(mapkey)] = *mapvalue + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Capacity", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Capacity == nil { + m.Capacity = make(map[QualifiedName]resource.Quantity) + } + var mapkey QualifiedName + mapvalue := &resource.Quantity{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = QualifiedName(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &resource.Quantity{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Capacity[QualifiedName(mapkey)] = *mapvalue + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsumesCounters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsumesCounters = append(m.ConsumesCounters, DeviceCounterConsumption{}) + if err := m.ConsumesCounters[len(m.ConsumesCounters)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.NodeName = &s + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NodeSelector == nil { + m.NodeSelector = &v11.NodeSelector{} + } + if err := m.NodeSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllNodes", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.AllNodes = &b + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Taints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Taints = append(m.Taints, DeviceTaint{}) + if err := m.Taints[len(m.Taints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CELDeviceSelector) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CELDeviceSelector: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CELDeviceSelector: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Expression = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Counter) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Counter: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Counter: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Value.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CounterSet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CounterSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CounterSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Counters == nil { + m.Counters = make(map[string]Counter) + } + var mapkey string + mapvalue := &Counter{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &Counter{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Counters[mapkey] = *mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Basic == nil { + m.Basic = &BasicDevice{} + } + if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceAllocationConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceAllocationConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Source = AllocationConfigSource(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceAllocationResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceAllocationResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceAllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Results = append(m.Results, DeviceRequestAllocationResult{}) + if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Config = append(m.Config, DeviceAllocationConfiguration{}) + if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceAttribute: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceAttribute: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IntValue", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IntValue = &v + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BoolValue", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.BoolValue = &b + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StringValue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.StringValue = &s + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionValue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.VersionValue = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceClaim) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceClaim: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceClaim: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Requests = append(m.Requests, DeviceRequest{}) + if err := m.Requests[len(m.Requests)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Constraints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Constraints = append(m.Constraints, DeviceConstraint{}) + if err := m.Constraints[len(m.Constraints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Config = append(m.Config, DeviceClaimConfiguration{}) + if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil } -func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { +func (m *DeviceClaimConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3839,15 +7161,15 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: AllocatedDeviceStatus: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceClaimConfiguration: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: AllocatedDeviceStatus: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceClaimConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3875,13 +7197,13 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Driver = string(dAtA[iNdEx:postIndex]) + m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3891,29 +7213,80 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Pool = string(dAtA[iNdEx:postIndex]) + if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 3: + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceClass) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceClass: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceClass: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -3923,27 +7296,28 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Device = string(dAtA[iNdEx:postIndex]) + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 4: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3970,14 +7344,63 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Conditions = append(m.Conditions, v1.Condition{}) - if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 5: + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceClassConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceClassConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceClassConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4004,13 +7427,63 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Data.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 6: + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceClassList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceClassList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceClassList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetworkData", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4037,10 +7510,41 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.NetworkData == nil { - m.NetworkData = &NetworkDeviceData{} + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - if err := m.NetworkData.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, DeviceClass{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4065,7 +7569,7 @@ func (m *AllocatedDeviceStatus) Unmarshal(dAtA []byte) error { } return nil } -func (m *AllocationResult) Unmarshal(dAtA []byte) error { +func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4088,15 +7592,15 @@ func (m *AllocationResult) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: AllocationResult: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceClassSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: AllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceClassSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4123,13 +7627,14 @@ func (m *AllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Devices.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NodeSelector", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4156,10 +7661,8 @@ func (m *AllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.NodeSelector == nil { - m.NodeSelector = &v11.NodeSelector{} - } - if err := m.NodeSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Config = append(m.Config, DeviceClassConfiguration{}) + if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4184,7 +7687,7 @@ func (m *AllocationResult) Unmarshal(dAtA []byte) error { } return nil } -func (m *BasicDevice) Unmarshal(dAtA []byte) error { +func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4207,144 +7710,15 @@ func (m *BasicDevice) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BasicDevice: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceConfiguration: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BasicDevice: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Attributes == nil { - m.Attributes = make(map[QualifiedName]DeviceAttribute) - } - var mapkey QualifiedName - mapvalue := &DeviceAttribute{} - for iNdEx < postIndex { - entryPreIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - if fieldNum == 1 { - var stringLenmapkey uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLenmapkey |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLenmapkey := int(stringLenmapkey) - if intStringLenmapkey < 0 { - return ErrInvalidLengthGenerated - } - postStringIndexmapkey := iNdEx + intStringLenmapkey - if postStringIndexmapkey < 0 { - return ErrInvalidLengthGenerated - } - if postStringIndexmapkey > l { - return io.ErrUnexpectedEOF - } - mapkey = QualifiedName(dAtA[iNdEx:postStringIndexmapkey]) - iNdEx = postStringIndexmapkey - } else if fieldNum == 2 { - var mapmsglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - mapmsglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if mapmsglen < 0 { - return ErrInvalidLengthGenerated - } - postmsgIndex := iNdEx + mapmsglen - if postmsgIndex < 0 { - return ErrInvalidLengthGenerated - } - if postmsgIndex > l { - return io.ErrUnexpectedEOF - } - mapvalue = &DeviceAttribute{} - if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { - return err - } - iNdEx = postmsgIndex - } else { - iNdEx = entryPreIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > postIndex { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - m.Attributes[QualifiedName(mapkey)] = *mapvalue - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Capacity", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Opaque", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4371,105 +7745,12 @@ func (m *BasicDevice) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Capacity == nil { - m.Capacity = make(map[QualifiedName]resource.Quantity) + if m.Opaque == nil { + m.Opaque = &OpaqueDeviceConfiguration{} } - var mapkey QualifiedName - mapvalue := &resource.Quantity{} - for iNdEx < postIndex { - entryPreIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - if fieldNum == 1 { - var stringLenmapkey uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLenmapkey |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLenmapkey := int(stringLenmapkey) - if intStringLenmapkey < 0 { - return ErrInvalidLengthGenerated - } - postStringIndexmapkey := iNdEx + intStringLenmapkey - if postStringIndexmapkey < 0 { - return ErrInvalidLengthGenerated - } - if postStringIndexmapkey > l { - return io.ErrUnexpectedEOF - } - mapkey = QualifiedName(dAtA[iNdEx:postStringIndexmapkey]) - iNdEx = postStringIndexmapkey - } else if fieldNum == 2 { - var mapmsglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - mapmsglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if mapmsglen < 0 { - return ErrInvalidLengthGenerated - } - postmsgIndex := iNdEx + mapmsglen - if postmsgIndex < 0 { - return ErrInvalidLengthGenerated - } - if postmsgIndex > l { - return io.ErrUnexpectedEOF - } - mapvalue = &resource.Quantity{} - if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { - return err - } - iNdEx = postmsgIndex - } else { - iNdEx = entryPreIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > postIndex { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } + if err := m.Opaque.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - m.Capacity[QualifiedName(mapkey)] = *mapvalue iNdEx = postIndex default: iNdEx = preIndex @@ -4492,7 +7773,7 @@ func (m *BasicDevice) Unmarshal(dAtA []byte) error { } return nil } -func (m *CELDeviceSelector) Unmarshal(dAtA []byte) error { +func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4515,15 +7796,15 @@ func (m *CELDeviceSelector) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: CELDeviceSelector: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceConstraint: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: CELDeviceSelector: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceConstraint: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4551,7 +7832,40 @@ func (m *CELDeviceSelector) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Expression = string(dAtA[iNdEx:postIndex]) + m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchAttribute", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := FullyQualifiedName(dAtA[iNdEx:postIndex]) + m.MatchAttribute = &s iNdEx = postIndex default: iNdEx = preIndex @@ -4574,7 +7888,7 @@ func (m *CELDeviceSelector) Unmarshal(dAtA []byte) error { } return nil } -func (m *Device) Unmarshal(dAtA []byte) error { +func (m *DeviceCounterConsumption) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4597,15 +7911,15 @@ func (m *Device) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Device: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceCounterConsumption: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceCounterConsumption: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CounterSet", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4633,11 +7947,11 @@ func (m *Device) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.CounterSet = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Counters", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4664,12 +7978,105 @@ func (m *Device) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Basic == nil { - m.Basic = &BasicDevice{} + if m.Counters == nil { + m.Counters = make(map[string]Counter) } - if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + var mapkey string + mapvalue := &Counter{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &Counter{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } } + m.Counters[mapkey] = *mapvalue iNdEx = postIndex default: iNdEx = preIndex @@ -4692,7 +8099,7 @@ func (m *Device) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { +func (m *DeviceRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4715,15 +8122,15 @@ func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceAllocationConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceAllocationConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4751,11 +8158,11 @@ func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Source = AllocationConfigSource(dAtA[iNdEx:postIndex]) + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4783,11 +8190,11 @@ func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) + m.DeviceClassName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4814,63 +8221,86 @@ func (m *DeviceAllocationConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMode", wireType) } - if (skippy < 0) || (iNdEx+skippy) < 0 { + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - if (iNdEx + skippy) > l { + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { return io.ErrUnexpectedEOF } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceAllocationResult) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated + m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) } - if iNdEx >= l { - return io.ErrUnexpectedEOF + m.Count = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Count |= int64(b&0x7F) << shift + if b < 0x80 { + break + } } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdminAccess", wireType) } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceAllocationResult: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceAllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.AdminAccess = &b + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FirstAvailable", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4897,14 +8327,14 @@ func (m *DeviceAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Results = append(m.Results, DeviceRequestAllocationResult{}) - if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FirstAvailable = append(m.FirstAvailable, DeviceSubRequest{}) + if err := m.FirstAvailable[len(m.FirstAvailable)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 2: + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Tolerations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4931,8 +8361,8 @@ func (m *DeviceAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Config = append(m.Config, DeviceAllocationConfiguration{}) - if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Tolerations = append(m.Tolerations, DeviceToleration{}) + if err := m.Tolerations[len(m.Tolerations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4957,7 +8387,7 @@ func (m *DeviceAllocationResult) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { +func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4980,17 +8410,17 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceAttribute: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceRequestAllocationResult: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceAttribute: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceRequestAllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IntValue", wireType) + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Request", wireType) } - var v int64 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5000,17 +8430,29 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int64(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - m.IntValue = &v - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BoolValue", wireType) + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated } - var v int + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Request = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + } + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5020,16 +8462,27 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - b := bool(v != 0) - m.BoolValue = &b - case 4: + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Driver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StringValue", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5057,12 +8510,11 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.StringValue = &s + m.Pool = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VersionValue", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5090,8 +8542,62 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - s := string(dAtA[iNdEx:postIndex]) - m.VersionValue = &s + m.Device = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdminAccess", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.AdminAccess = &b + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tolerations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tolerations = append(m.Tolerations, DeviceToleration{}) + if err := m.Tolerations[len(m.Tolerations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -5114,7 +8620,7 @@ func (m *DeviceAttribute) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceClaim) Unmarshal(dAtA []byte) error { +func (m *DeviceSelector) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5137,49 +8643,15 @@ func (m *DeviceClaim) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceClaim: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceSelector: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClaim: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceSelector: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Requests = append(m.Requests, DeviceRequest{}) - if err := m.Requests[len(m.Requests)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Constraints", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CEL", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5206,42 +8678,10 @@ func (m *DeviceClaim) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Constraints = append(m.Constraints, DeviceConstraint{}) - if err := m.Constraints[len(m.Constraints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF + if m.CEL == nil { + m.CEL = &CELDeviceSelector{} } - m.Config = append(m.Config, DeviceClaimConfiguration{}) - if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.CEL.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5266,7 +8706,7 @@ func (m *DeviceClaim) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceClaimConfiguration) Unmarshal(dAtA []byte) error { +func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5289,15 +8729,47 @@ func (m *DeviceClaimConfiguration) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceClaimConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceSubRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClaimConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceSubRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5325,11 +8797,11 @@ func (m *DeviceClaimConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) + m.DeviceClassName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5356,65 +8828,16 @@ func (m *DeviceClaimConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceClass) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceClass: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClass: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMode", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5424,28 +8847,46 @@ func (m *DeviceClass) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Count |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Tolerations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5472,7 +8913,8 @@ func (m *DeviceClass) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Tolerations = append(m.Tolerations, DeviceToleration{}) + if err := m.Tolerations[len(m.Tolerations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5497,7 +8939,7 @@ func (m *DeviceClass) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceClassConfiguration) Unmarshal(dAtA []byte) error { +func (m *DeviceTaint) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5520,17 +8962,17 @@ func (m *DeviceClassConfiguration) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceClassConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaint: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClassConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaint: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceConfiguration", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5540,80 +8982,61 @@ func (m *DeviceClassConfiguration) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DeviceConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Key = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceClassList) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated } - if iNdEx >= l { + if postIndex > l { return io.ErrUnexpectedEOF } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceClassList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClassList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + m.Value = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5623,28 +9046,27 @@ func (m *DeviceClassList) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TimeAdded", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5671,8 +9093,10 @@ func (m *DeviceClassList) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Items = append(m.Items, DeviceClass{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.TimeAdded == nil { + m.TimeAdded = &v1.Time{} + } + if err := m.TimeAdded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5697,7 +9121,7 @@ func (m *DeviceClassList) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { +func (m *DeviceTaintRule) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5720,15 +9144,15 @@ func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceClassSpec: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaintRule: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceClassSpec: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaintRule: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5755,14 +9179,13 @@ func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Selectors = append(m.Selectors, DeviceSelector{}) - if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5789,8 +9212,7 @@ func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Config = append(m.Config, DeviceClassConfiguration{}) - if err := m.Config[len(m.Config)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5815,7 +9237,7 @@ func (m *DeviceClassSpec) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error { +func (m *DeviceTaintRuleList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5838,15 +9260,15 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaintRuleList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaintRuleList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Opaque", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5873,10 +9295,41 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Opaque == nil { - m.Opaque = &OpaqueDeviceConfiguration{} + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF } - if err := m.Opaque.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Items = append(m.Items, DeviceTaintRule{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -5901,7 +9354,7 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { +func (m *DeviceTaintRuleSpec) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5924,17 +9377,17 @@ func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceConstraint: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaintRuleSpec: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceConstraint: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaintRuleSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceSelector", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5944,29 +9397,33 @@ func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Requests = append(m.Requests, string(dAtA[iNdEx:postIndex])) + if m.DeviceSelector == nil { + m.DeviceSelector = &DeviceTaintSelector{} + } + if err := m.DeviceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MatchAttribute", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Taint", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -5976,24 +9433,24 @@ func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - s := FullyQualifiedName(dAtA[iNdEx:postIndex]) - m.MatchAttribute = &s + if err := m.Taint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -6016,7 +9473,7 @@ func (m *DeviceConstraint) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceRequest) Unmarshal(dAtA []byte) error { +func (m *DeviceTaintSelector) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6039,15 +9496,15 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceRequest: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceTaintSelector: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceTaintSelector: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6075,11 +9532,12 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.DeviceClassName = &s iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6107,13 +9565,14 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DeviceClassName = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.Driver = &s iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6123,29 +9582,28 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - m.Selectors = append(m.Selectors, DeviceSelector{}) - if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + s := string(dAtA[iNdEx:postIndex]) + m.Pool = &s iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AllocationMode", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6173,13 +9631,14 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.Device = &s iNdEx = postIndex case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) } - m.Count = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6189,32 +9648,26 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Count |= int64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AdminAccess", wireType) + if msglen < 0 { + return ErrInvalidLengthGenerated } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated } - b := bool(v != 0) - m.AdminAccess = &b + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6236,7 +9689,7 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { +func (m *DeviceToleration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6259,15 +9712,15 @@ func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DeviceRequestAllocationResult: wiretype end group for non-group") + return fmt.Errorf("proto: DeviceToleration: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceRequestAllocationResult: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DeviceToleration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Request", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6295,11 +9748,11 @@ func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Request = string(dAtA[iNdEx:postIndex]) + m.Key = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Operator", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6327,11 +9780,11 @@ func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Driver = string(dAtA[iNdEx:postIndex]) + m.Operator = DeviceTolerationOperator(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pool", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6359,11 +9812,11 @@ func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Pool = string(dAtA[iNdEx:postIndex]) + m.Value = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Effect", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6391,84 +9844,13 @@ func (m *DeviceRequestAllocationResult) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Device = string(dAtA[iNdEx:postIndex]) + m.Effect = DeviceTaintEffect(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AdminAccess", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - b := bool(v != 0) - m.AdminAccess = &b - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeviceSelector) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeviceSelector: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeviceSelector: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CEL", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TolerationSeconds", wireType) } - var msglen int + var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -6478,28 +9860,12 @@ func (m *DeviceSelector) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= int64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return ErrInvalidLengthGenerated - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenerated - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.CEL == nil { - m.CEL = &CELDeviceSelector{} - } - if err := m.CEL.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex + m.TolerationSeconds = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -8381,6 +11747,61 @@ func (m *ResourceSliceSpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PerDeviceNodeSelection", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.PerDeviceNodeSelection = &b + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SharedCounters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SharedCounters = append(m.SharedCounters, CounterSet{}) + if err := m.SharedCounters[len(m.SharedCounters)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.proto b/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.proto index e802a01439..103cafc6ad 100644 --- a/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.proto +++ b/go-controller/vendor/k8s.io/api/resource/v1alpha3/generated.proto @@ -62,6 +62,8 @@ message AllocatedDeviceStatus { // If the device has been configured according to the class and claim // config references, the `Ready` condition should be True. // + // Must not contain more than 8 entries. + // // +optional // +listType=map // +listMapKey=type @@ -111,6 +113,64 @@ message BasicDevice { // // +optional map capacity = 2; + + // ConsumesCounters defines a list of references to sharedCounters + // and the set of counters that the device will + // consume from those counter sets. + // + // There can only be a single entry per counterSet. + // + // The total number of device counter consumption entries + // must be <= 32. In addition, the total number in the + // entire ResourceSlice must be <= 1024 (for example, + // 64 devices with 16 counters each). + // + // +optional + // +listType=atomic + // +featureGate=DRAPartitionableDevices + repeated DeviceCounterConsumption consumesCounters = 3; + + // NodeName identifies the node where the device is available. + // + // Must only be set if Spec.PerDeviceNodeSelection is set to true. + // At most one of NodeName, NodeSelector and AllNodes can be set. + // + // +optional + // +oneOf=DeviceNodeSelection + // +featureGate=DRAPartitionableDevices + optional string nodeName = 4; + + // NodeSelector defines the nodes where the device is available. + // + // Must only be set if Spec.PerDeviceNodeSelection is set to true. + // At most one of NodeName, NodeSelector and AllNodes can be set. + // + // +optional + // +oneOf=DeviceNodeSelection + // +featureGate=DRAPartitionableDevices + optional .k8s.io.api.core.v1.NodeSelector nodeSelector = 5; + + // AllNodes indicates that all nodes have access to the device. + // + // Must only be set if Spec.PerDeviceNodeSelection is set to true. + // At most one of NodeName, NodeSelector and AllNodes can be set. + // + // +optional + // +oneOf=DeviceNodeSelection + // +featureGate=DRAPartitionableDevices + optional bool allNodes = 6; + + // If specified, these are the driver-defined taints. + // + // The maximum number of taints is 4. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceTaint taints = 7; } // CELDeviceSelector contains a CEL expression for selecting a device. @@ -170,6 +230,42 @@ message CELDeviceSelector { optional string expression = 1; } +// Counter describes a quantity associated with a device. +message Counter { + // Value defines how much of a certain device counter is available. + // + // +required + optional .k8s.io.apimachinery.pkg.api.resource.Quantity value = 1; +} + +// CounterSet defines a named set of counters +// that are available to be used by devices defined in the +// ResourceSlice. +// +// The counters are not allocatable by themselves, but +// can be referenced by devices. When a device is allocated, +// the portion of counters it uses will no longer be available for use +// by other devices. +message CounterSet { + // CounterSet is the name of the set from which the + // counters defined will be consumed. + // + // +required + optional string name = 1; + + // Counters defines the counters that will be consumed by the device. + // The name of each counter must be unique in that set and must be a DNS label. + // + // To ensure this uniqueness, capacities defined by the vendor + // must be listed without the driver name as domain prefix in + // their name. All others must be listed with their domain prefix. + // + // The maximum number of counters is 32. + // + // +required + map counters = 2; +} + // Device represents one individual hardware instance that can be selected based // on its attributes. Besides the name, exactly one field must be set. message Device { @@ -198,6 +294,10 @@ message DeviceAllocationConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format

[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 2; @@ -284,6 +384,10 @@ message DeviceClaimConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -368,6 +472,10 @@ message DeviceConstraint { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -390,14 +498,30 @@ message DeviceConstraint { optional string matchAttribute = 2; } +// DeviceCounterConsumption defines a set of counters that +// a device will consume from a CounterSet. +message DeviceCounterConsumption { + // CounterSet defines the set from which the + // counters defined will be consumed. + // + // +required + optional string counterSet = 1; + + // Counters defines the Counter that will be consumed by + // the device. + // + // The maximum number counters in a device is 32. + // In addition, the maximum number of all counters + // in all devices is 1024 (for example, 64 devices with + // 16 counters each). + // + // +required + map counters = 2; +} + // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. message DeviceRequest { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. @@ -411,7 +535,10 @@ message DeviceRequest { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -419,7 +546,8 @@ message DeviceRequest { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -427,6 +555,9 @@ message DeviceRequest { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic repeated DeviceSelector selectors = 3; @@ -439,13 +570,17 @@ message DeviceRequest { // count field. // // - All: This request is for all of the matching devices in a pool. + // At least one device must exist on the node for the allocation to succeed. // Allocation will fail if some devices are already allocated, // unless adminAccess is requested. // - // If AlloctionMode is not specified, the default mode is ExactCount. If + // If AllocationMode is not specified, the default mode is ExactCount. If // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -455,6 +590,9 @@ message DeviceRequest { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode optional int64 count = 5; @@ -465,6 +603,9 @@ message DeviceRequest { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -472,13 +613,65 @@ message DeviceRequest { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 6; + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + repeated DeviceSubRequest firstAvailable = 7; + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 8; } // DeviceRequestAllocationResult contains the allocation result for one request. message DeviceRequestAllocationResult { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required optional string request = 1; @@ -519,6 +712,19 @@ message DeviceRequestAllocationResult { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 5; + + // A copy of all tolerations specified in the request at the time + // when the device got allocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 6; } // DeviceSelector must have exactly one field set. @@ -530,6 +736,262 @@ message DeviceSelector { optional CELDeviceSelector cel = 1; } +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +message DeviceSubRequest { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + optional string deviceClassName = 2; + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // request. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 3; + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this request. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This request is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AllocationMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other requests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + optional string allocationMode = 4; + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + optional int64 count = 5; + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 7; +} + +// The device this taint is attached to has the "effect" on +// any claim which does not tolerate the taint and, through the claim, +// to pods using the claim. +message DeviceTaint { + // The taint key to be applied to a device. + // Must be a label name. + // + // +required + optional string key = 1; + + // The taint value corresponding to the taint key. + // Must be a label value. + // + // +optional + optional string value = 2; + + // The effect of the taint on claims that do not tolerate the taint + // and through such claims on the pods using them. + // Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for + // nodes is not valid here. + // + // +required + optional string effect = 3; + + // TimeAdded represents the time at which the taint was added. + // Added automatically during create or update if not set. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; +} + +// DeviceTaintRule adds one taint to all devices which match the selector. +// This has the same effect as if the taint was specified directly +// in the ResourceSlice by the DRA driver. +message DeviceTaintRule { + // Standard object metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec specifies the selector and one taint. + // + // Changing the spec automatically increments the metadata.generation number. + optional DeviceTaintRuleSpec spec = 2; +} + +// DeviceTaintRuleList is a collection of DeviceTaintRules. +message DeviceTaintRuleList { + // Standard list metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of DeviceTaintRules. + repeated DeviceTaintRule items = 2; +} + +// DeviceTaintRuleSpec specifies the selector and one taint. +message DeviceTaintRuleSpec { + // DeviceSelector defines which device(s) the taint is applied to. + // All selector criteria must be satified for a device to + // match. The empty selector matches all devices. Without + // a selector, no devices are matches. + // + // +optional + optional DeviceTaintSelector deviceSelector = 1; + + // The taint that gets applied to matching devices. + // + // +required + optional DeviceTaint taint = 2; +} + +// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. +// The empty selector matches all devices. Without a selector, no devices +// are matched. +message DeviceTaintSelector { + // If DeviceClassName is set, the selectors defined there must be + // satisfied by a device to be selected. This field corresponds + // to class.metadata.name. + // + // +optional + optional string deviceClassName = 1; + + // If driver is set, only devices from that driver are selected. + // This fields corresponds to slice.spec.driver. + // + // +optional + optional string driver = 2; + + // If pool is set, only devices in that pool are selected. + // + // Also setting the driver name may be useful to avoid + // ambiguity when different drivers use the same pool name, + // but this is not required because selecting pools from + // different drivers may also be useful, for example when + // drivers with node-local devices use the node name as + // their pool name. + // + // +optional + optional string pool = 3; + + // If device is set, only devices with that name are selected. + // This field corresponds to slice.spec.devices[].name. + // + // Setting also driver and pool may be required to avoid ambiguity, + // but is not required. + // + // +optional + optional string device = 4; + + // Selectors contains the same selection criteria as a ResourceClaim. + // Currently, CEL expressions are supported. All of these selectors + // must be satisfied. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 5; +} + +// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches +// the triple using the matching operator . +message DeviceToleration { + // Key is the taint key that the toleration applies to. Empty means match all taint keys. + // If the key is empty, operator must be Exists; this combination means to match all values and all keys. + // Must be a label name. + // + // +optional + optional string key = 1; + + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a ResourceClaim can + // tolerate all taints of a particular category. + // + // +optional + // +default="Equal" + optional string operator = 2; + + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value must be empty, otherwise just a regular string. + // Must be a label value. + // + // +optional + optional string value = 3; + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and NoExecute. + // + // +optional + optional string effect = 4; + + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // If larger than zero, the time when the pod needs to be evicted is calculated as
/. + // + // Must be a DNS label. + // + // +required + Name string `json:"name" protobuf:"bytes,1,name=name"` + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // request. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this request. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This request is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AllocationMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other requests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,7,opt,name=tolerations"` } const ( - DeviceSelectorsMaxSize = 32 + DeviceSelectorsMaxSize = 32 + FirstAvailableDeviceRequestMaxSize = 8 + DeviceTolerationsMaxLength = 16 ) type DeviceAllocationMode string @@ -581,6 +952,10 @@ type DeviceConstraint struct { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -618,6 +993,10 @@ type DeviceClaimConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -666,6 +1045,59 @@ type OpaqueDeviceConfiguration struct { // [OpaqueDeviceConfiguration.Parameters] field. const OpaqueParametersMaxLength = 10 * 1024 +// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches +// the triple using the matching operator . +type DeviceToleration struct { + // Key is the taint key that the toleration applies to. Empty means match all taint keys. + // If the key is empty, operator must be Exists; this combination means to match all values and all keys. + // Must be a label name. + // + // +optional + Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"` + + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a ResourceClaim can + // tolerate all taints of a particular category. + // + // +optional + // +default="Equal" + Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"` + + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value must be empty, otherwise just a regular string. + // Must be a label value. + // + // +optional + Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"` + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and NoExecute. + // + // +optional + Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"` + + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // If larger than zero, the time when the pod needs to be evicted is calculated as
/. + // + // Multiple devices may have been allocated per request. // // +required Request string `json:"request" protobuf:"bytes,1,name=request"` @@ -832,6 +1268,19 @@ type DeviceRequestAllocationResult struct { // +optional // +featureGate=DRAAdminAccess AdminAccess *bool `json:"adminAccess" protobuf:"bytes,5,name=adminAccess"` + + // A copy of all tolerations specified in the request at the time + // when the device got allocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` } // DeviceAllocationConfiguration gets embedded in an AllocationResult. @@ -846,6 +1295,10 @@ type DeviceAllocationConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` @@ -1003,6 +1456,24 @@ type ResourceClaimTemplateList struct { Items []ResourceClaimTemplate `json:"items" protobuf:"bytes,2,rep,name=items"` } +const ( + // AllocatedDeviceStatusMaxConditions represents the maximum number of + // conditions in a device status. + AllocatedDeviceStatusMaxConditions int = 8 + // AllocatedDeviceStatusDataMaxLength represents the maximum length of the + // raw data in the Data field in a device status. + AllocatedDeviceStatusDataMaxLength int = 10 * 1024 + // NetworkDeviceDataMaxIPs represents the maximum number of IPs in the networkData + // field in a device status. + NetworkDeviceDataMaxIPs int = 16 + // NetworkDeviceDataInterfaceNameMaxLength represents the maximum number of characters + // for the networkData.interfaceName field in a device status. + NetworkDeviceDataInterfaceNameMaxLength int = 256 + // NetworkDeviceDataHardwareAddressMaxLength represents the maximum number of characters + // for the networkData.hardwareAddress field in a device status. + NetworkDeviceDataHardwareAddressMaxLength int = 128 +) + // AllocatedDeviceStatus contains the status of an allocated device, if the // driver chooses to report it. This may include driver-specific information. type AllocatedDeviceStatus struct { @@ -1035,6 +1506,8 @@ type AllocatedDeviceStatus struct { // If the device has been configured according to the class and claim // config references, the `Ready` condition should be True. // + // Must not contain more than 8 entries. + // // +optional // +listType=map // +listMapKey=type @@ -1045,7 +1518,7 @@ type AllocatedDeviceStatus struct { // The length of the raw data must be smaller or equal to 10 Ki. // // +optional - Data runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,5,opt,name=data"` + Data *runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,5,opt,name=data"` // NetworkData contains network-related information specific to the device. // @@ -1072,6 +1545,8 @@ type NetworkDeviceData struct { // associated subnet mask. // e.g.: "192.0.2.5/24" for IPv4 and "2001:db8::5/64" for IPv6. // + // Must not contain more than 16 entries. + // // +optional // +listType=atomic IPs []string `json:"ips,omitempty" protobuf:"bytes,2,opt,name=ips"` @@ -1083,3 +1558,107 @@ type NetworkDeviceData struct { // +optional HardwareAddress string `json:"hardwareAddress,omitempty" protobuf:"bytes,3,opt,name=hardwareAddress"` } + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// DeviceTaintRule adds one taint to all devices which match the selector. +// This has the same effect as if the taint was specified directly +// in the ResourceSlice by the DRA driver. +type DeviceTaintRule struct { + metav1.TypeMeta `json:",inline"` + // Standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec specifies the selector and one taint. + // + // Changing the spec automatically increments the metadata.generation number. + Spec DeviceTaintRuleSpec `json:"spec" protobuf:"bytes,2,name=spec"` + + // ^^^ + // A spec gets added because adding a status seems likely. + // Such a status could provide feedback on applying the + // eviction and/or statistics (number of matching devices, + // affected allocated claims, pods remaining to be evicted, + // etc.). +} + +// DeviceTaintRuleSpec specifies the selector and one taint. +type DeviceTaintRuleSpec struct { + // DeviceSelector defines which device(s) the taint is applied to. + // All selector criteria must be satified for a device to + // match. The empty selector matches all devices. Without + // a selector, no devices are matches. + // + // +optional + DeviceSelector *DeviceTaintSelector `json:"deviceSelector,omitempty" protobuf:"bytes,1,opt,name=deviceSelector"` + + // The taint that gets applied to matching devices. + // + // +required + Taint DeviceTaint `json:"taint,omitempty" protobuf:"bytes,2,rep,name=taint"` +} + +// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. +// The empty selector matches all devices. Without a selector, no devices +// are matched. +type DeviceTaintSelector struct { + // If DeviceClassName is set, the selectors defined there must be + // satisfied by a device to be selected. This field corresponds + // to class.metadata.name. + // + // +optional + DeviceClassName *string `json:"deviceClassName,omitempty" protobuf:"bytes,1,opt,name=deviceClassName"` + + // If driver is set, only devices from that driver are selected. + // This fields corresponds to slice.spec.driver. + // + // +optional + Driver *string `json:"driver,omitempty" protobuf:"bytes,2,opt,name=driver"` + + // If pool is set, only devices in that pool are selected. + // + // Also setting the driver name may be useful to avoid + // ambiguity when different drivers use the same pool name, + // but this is not required because selecting pools from + // different drivers may also be useful, for example when + // drivers with node-local devices use the node name as + // their pool name. + // + // +optional + Pool *string `json:"pool,omitempty" protobuf:"bytes,3,opt,name=pool"` + + // If device is set, only devices with that name are selected. + // This field corresponds to slice.spec.devices[].name. + // + // Setting also driver and pool may be required to avoid ambiguity, + // but is not required. + // + // +optional + Device *string `json:"device,omitempty" protobuf:"bytes,4,opt,name=device"` + + // Selectors contains the same selection criteria as a ResourceClaim. + // Currently, CEL expressions are supported. All of these selectors + // must be satisfied. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,5,rep,name=selectors"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.33 + +// DeviceTaintRuleList is a collection of DeviceTaintRules. +type DeviceTaintRuleList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of DeviceTaintRules. + Items []DeviceTaintRule `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/go-controller/vendor/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go index b41609d118..291cce7eba 100644 --- a/go-controller/vendor/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go @@ -32,7 +32,7 @@ var map_AllocatedDeviceStatus = map[string]string{ "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", - "conditions": "Conditions contains the latest observation of the device's state. If the device has been configured according to the class and claim config references, the `Ready` condition should be True.", + "conditions": "Conditions contains the latest observation of the device's state. If the device has been configured according to the class and claim config references, the `Ready` condition should be True.\n\nMust not contain more than 8 entries.", "data": "Data contains arbitrary driver-specific data.\n\nThe length of the raw data must be smaller or equal to 10 Ki.", "networkData": "NetworkData contains network-related information specific to the device.", } @@ -52,9 +52,14 @@ func (AllocationResult) SwaggerDoc() map[string]string { } var map_BasicDevice = map[string]string{ - "": "BasicDevice defines one device instance.", - "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "": "BasicDevice defines one device instance.", + "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe total number of device counter consumption entries must be <= 32. In addition, the total number in the entire ResourceSlice must be <= 1024 (for example, 64 devices with 16 counters each).", + "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 4.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (BasicDevice) SwaggerDoc() map[string]string { @@ -70,6 +75,25 @@ func (CELDeviceSelector) SwaggerDoc() map[string]string { return map_CELDeviceSelector } +var map_Counter = map[string]string{ + "": "Counter describes a quantity associated with a device.", + "value": "Value defines how much of a certain device counter is available.", +} + +func (Counter) SwaggerDoc() map[string]string { + return map_Counter +} + +var map_CounterSet = map[string]string{ + "": "CounterSet defines a named set of counters that are available to be used by devices defined in the ResourceSlice.\n\nThe counters are not allocatable by themselves, but can be referenced by devices. When a device is allocated, the portion of counters it uses will no longer be available for use by other devices.", + "name": "CounterSet is the name of the set from which the counters defined will be consumed.", + "counters": "Counters defines the counters that will be consumed by the device. The name of each counter must be unique in that set and must be a DNS label.\n\nTo ensure this uniqueness, capacities defined by the vendor must be listed without the driver name as domain prefix in their name. All others must be listed with their domain prefix.\n\nThe maximum number of counters is 32.", +} + +func (CounterSet) SwaggerDoc() map[string]string { + return map_CounterSet +} + var map_Device = map[string]string{ "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", @@ -83,7 +107,7 @@ func (Device) SwaggerDoc() map[string]string { var map_DeviceAllocationConfiguration = map[string]string{ "": "DeviceAllocationConfiguration gets embedded in an AllocationResult.", "source": "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceAllocationConfiguration) SwaggerDoc() map[string]string { @@ -125,7 +149,7 @@ func (DeviceClaim) SwaggerDoc() map[string]string { var map_DeviceClaimConfiguration = map[string]string{ "": "DeviceClaimConfiguration is used for configuration parameters in DeviceClaim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceClaimConfiguration) SwaggerDoc() map[string]string { @@ -181,7 +205,7 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", - "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", } @@ -189,14 +213,26 @@ func (DeviceConstraint) SwaggerDoc() map[string]string { return map_DeviceConstraint } +var map_DeviceCounterConsumption = map[string]string{ + "": "DeviceCounterConsumption defines a set of counters that a device will consume from a CounterSet.", + "counterSet": "CounterSet defines the set from which the counters defined will be consumed.", + "counters": "Counters defines the Counter that will be consumed by the device.\n\nThe maximum number counters in a device is 32. In addition, the maximum number of all counters in all devices is 1024 (for example, 64 devices with 16 counters each).", +} + +func (DeviceCounterConsumption) SwaggerDoc() map[string]string { + return map_DeviceCounterConsumption +} + var map_DeviceRequest = map[string]string{ - "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", - "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", - "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", - "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", - "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "firstAvailable": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (DeviceRequest) SwaggerDoc() map[string]string { @@ -205,11 +241,12 @@ func (DeviceRequest) SwaggerDoc() map[string]string { var map_DeviceRequestAllocationResult = map[string]string{ "": "DeviceRequestAllocationResult contains the allocation result for one request.", - "request": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "request": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (DeviceRequestAllocationResult) SwaggerDoc() map[string]string { @@ -225,10 +262,92 @@ func (DeviceSelector) SwaggerDoc() map[string]string { return map_DeviceSelector } +var map_DeviceSubRequest = map[string]string{ + "": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "name": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", +} + +func (DeviceSubRequest) SwaggerDoc() map[string]string { + return map_DeviceSubRequest +} + +var map_DeviceTaint = map[string]string{ + "": "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.", + "key": "The taint key to be applied to a device. Must be a label name.", + "value": "The taint value corresponding to the taint key. Must be a label value.", + "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.", + "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", +} + +func (DeviceTaint) SwaggerDoc() map[string]string { + return map_DeviceTaint +} + +var map_DeviceTaintRule = map[string]string{ + "": "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.", + "metadata": "Standard object metadata", + "spec": "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number.", +} + +func (DeviceTaintRule) SwaggerDoc() map[string]string { + return map_DeviceTaintRule +} + +var map_DeviceTaintRuleList = map[string]string{ + "": "DeviceTaintRuleList is a collection of DeviceTaintRules.", + "metadata": "Standard list metadata", + "items": "Items is the list of DeviceTaintRules.", +} + +func (DeviceTaintRuleList) SwaggerDoc() map[string]string { + return map_DeviceTaintRuleList +} + +var map_DeviceTaintRuleSpec = map[string]string{ + "": "DeviceTaintRuleSpec specifies the selector and one taint.", + "deviceSelector": "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satified for a device to match. The empty selector matches all devices. Without a selector, no devices are matches.", + "taint": "The taint that gets applied to matching devices.", +} + +func (DeviceTaintRuleSpec) SwaggerDoc() map[string]string { + return map_DeviceTaintRuleSpec +} + +var map_DeviceTaintSelector = map[string]string{ + "": "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.", + "deviceClassName": "If DeviceClassName is set, the selectors defined there must be satisfied by a device to be selected. This field corresponds to class.metadata.name.", + "driver": "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.", + "pool": "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.", + "device": "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.", + "selectors": "Selectors contains the same selection criteria as a ResourceClaim. Currently, CEL expressions are supported. All of these selectors must be satisfied.", +} + +func (DeviceTaintSelector) SwaggerDoc() map[string]string { + return map_DeviceTaintSelector +} + +var map_DeviceToleration = map[string]string{ + "": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple using the matching operator .", + "key": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.", + "operator": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.", + "value": "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.", + "effect": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.", + "tolerationSeconds": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 2; @@ -292,6 +389,10 @@ message DeviceClaimConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -376,6 +477,10 @@ message DeviceConstraint { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -398,19 +503,35 @@ message DeviceConstraint { optional string matchAttribute = 2; } +// DeviceCounterConsumption defines a set of counters that +// a device will consume from a CounterSet. +message DeviceCounterConsumption { + // CounterSet is the name of the set from which the + // counters defined will be consumed. + // + // +required + optional string counterSet = 1; + + // Counters defines the counters that will be consumed by the device. + // + // The maximum number counters in a device is 32. + // In addition, the maximum number of all counters + // in all devices is 1024 (for example, 64 devices with + // 16 counters each). + // + // +required + map counters = 2; +} + // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. message DeviceRequest { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. // - // Must be a DNS label. + // Must be a DNS label and unique among all DeviceRequests in a + // ResourceClaim. // // +required optional string name = 1; @@ -419,7 +540,10 @@ message DeviceRequest { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -427,7 +551,8 @@ message DeviceRequest { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -435,6 +560,9 @@ message DeviceRequest { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic repeated DeviceSelector selectors = 3; @@ -447,13 +575,17 @@ message DeviceRequest { // count field. // // - All: This request is for all of the matching devices in a pool. + // At least one device must exist on the node for the allocation to succeed. // Allocation will fail if some devices are already allocated, // unless adminAccess is requested. // - // If AlloctionMode is not specified, the default mode is ExactCount. If + // If AllocationMode is not specified, the default mode is ExactCount. If // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -463,6 +595,9 @@ message DeviceRequest { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode optional int64 count = 5; @@ -473,6 +608,9 @@ message DeviceRequest { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -480,13 +618,65 @@ message DeviceRequest { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 6; + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + repeated DeviceSubRequest firstAvailable = 7; + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 8; } // DeviceRequestAllocationResult contains the allocation result for one request. message DeviceRequestAllocationResult { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required optional string request = 1; @@ -527,6 +717,19 @@ message DeviceRequestAllocationResult { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 5; + + // A copy of all tolerations specified in the request at the time + // when the device got allocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 6; } // DeviceSelector must have exactly one field set. @@ -538,6 +741,177 @@ message DeviceSelector { optional CELDeviceSelector cel = 1; } +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +message DeviceSubRequest { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + optional string deviceClassName = 2; + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 3; + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AllocationMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + optional string allocationMode = 4; + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + optional int64 count = 5; + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 7; +} + +// The device this taint is attached to has the "effect" on +// any claim which does not tolerate the taint and, through the claim, +// to pods using the claim. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message DeviceTaint { + // The taint key to be applied to a device. + // Must be a label name. + // + // +required + optional string key = 1; + + // The taint value corresponding to the taint key. + // Must be a label value. + // + // +optional + optional string value = 2; + + // The effect of the taint on claims that do not tolerate the taint + // and through such claims on the pods using them. + // Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for + // nodes is not valid here. + // + // +required + optional string effect = 3; + + // TimeAdded represents the time at which the taint was added. + // Added automatically during create or update if not set. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; +} + +// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches +// the triple using the matching operator . +message DeviceToleration { + // Key is the taint key that the toleration applies to. Empty means match all taint keys. + // If the key is empty, operator must be Exists; this combination means to match all values and all keys. + // Must be a label name. + // + // +optional + optional string key = 1; + + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a ResourceClaim can + // tolerate all taints of a particular category. + // + // +optional + // +default="Equal" + optional string operator = 2; + + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value must be empty, otherwise just a regular string. + // Must be a label value. + // + // +optional + optional string value = 3; + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and NoExecute. + // + // +optional + optional string effect = 4; + + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // If larger than zero, the time when the pod needs to be evicted is calculated as
/. + // + // Must be a DNS label. + // + // +required + Name string `json:"name" protobuf:"bytes,1,name=name"` + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AllocationMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,7,opt,name=tolerations"` } const ( - DeviceSelectorsMaxSize = 32 + DeviceSelectorsMaxSize = 32 + FirstAvailableDeviceRequestMaxSize = 8 + DeviceTolerationsMaxLength = 16 ) type DeviceAllocationMode string @@ -589,6 +959,10 @@ type DeviceConstraint struct { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -626,6 +1000,10 @@ type DeviceClaimConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -674,6 +1052,59 @@ type OpaqueDeviceConfiguration struct { // [OpaqueDeviceConfiguration.Parameters] field. const OpaqueParametersMaxLength = 10 * 1024 +// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches +// the triple using the matching operator . +type DeviceToleration struct { + // Key is the taint key that the toleration applies to. Empty means match all taint keys. + // If the key is empty, operator must be Exists; this combination means to match all values and all keys. + // Must be a label name. + // + // +optional + Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"` + + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a ResourceClaim can + // tolerate all taints of a particular category. + // + // +optional + // +default="Equal" + Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"` + + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value must be empty, otherwise just a regular string. + // Must be a label value. + // + // +optional + Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"` + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and NoExecute. + // + // +optional + Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"` + + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // If larger than zero, the time when the pod needs to be evicted is calculated as
/. + // + // Multiple devices may have been allocated per request. // // +required Request string `json:"request" protobuf:"bytes,1,name=request"` @@ -840,6 +1275,19 @@ type DeviceRequestAllocationResult struct { // +optional // +featureGate=DRAAdminAccess AdminAccess *bool `json:"adminAccess" protobuf:"bytes,5,name=adminAccess"` + + // A copy of all tolerations specified in the request at the time + // when the device got allocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"` } // DeviceAllocationConfiguration gets embedded in an AllocationResult. @@ -854,6 +1302,10 @@ type DeviceAllocationConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` @@ -1006,6 +1458,24 @@ type ResourceClaimTemplateList struct { Items []ResourceClaimTemplate `json:"items" protobuf:"bytes,2,rep,name=items"` } +const ( + // AllocatedDeviceStatusMaxConditions represents the maximum number of + // conditions in a device status. + AllocatedDeviceStatusMaxConditions int = 8 + // AllocatedDeviceStatusDataMaxLength represents the maximum length of the + // raw data in the Data field in a device status. + AllocatedDeviceStatusDataMaxLength int = 10 * 1024 + // NetworkDeviceDataMaxIPs represents the maximum number of IPs in the networkData + // field in a device status. + NetworkDeviceDataMaxIPs int = 16 + // NetworkDeviceDataInterfaceNameMaxLength represents the maximum number of characters + // for the networkData.interfaceName field in a device status. + NetworkDeviceDataInterfaceNameMaxLength int = 256 + // NetworkDeviceDataHardwareAddressMaxLength represents the maximum number of characters + // for the networkData.hardwareAddress field in a device status. + NetworkDeviceDataHardwareAddressMaxLength int = 128 +) + // AllocatedDeviceStatus contains the status of an allocated device, if the // driver chooses to report it. This may include driver-specific information. type AllocatedDeviceStatus struct { @@ -1038,6 +1508,8 @@ type AllocatedDeviceStatus struct { // If the device has been configured according to the class and claim // config references, the `Ready` condition should be True. // + // Must not contain more than 8 entries. + // // +optional // +listType=map // +listMapKey=type @@ -1048,7 +1520,7 @@ type AllocatedDeviceStatus struct { // The length of the raw data must be smaller or equal to 10 Ki. // // +optional - Data runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,5,opt,name=data"` + Data *runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,5,opt,name=data"` // NetworkData contains network-related information specific to the device. // @@ -1075,6 +1547,8 @@ type NetworkDeviceData struct { // associated subnet mask. // e.g.: "192.0.2.5/24" for IPv4 and "2001:db8::5/64" for IPv6. // + // Must not contain more than 16 entries. + // // +optional // +listType=atomic IPs []string `json:"ips,omitempty" protobuf:"bytes,2,opt,name=ips"` diff --git a/go-controller/vendor/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go b/go-controller/vendor/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go index 4ecc35d08a..cd8b15fe3a 100644 --- a/go-controller/vendor/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go +++ b/go-controller/vendor/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go @@ -32,7 +32,7 @@ var map_AllocatedDeviceStatus = map[string]string{ "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", - "conditions": "Conditions contains the latest observation of the device's state. If the device has been configured according to the class and claim config references, the `Ready` condition should be True.", + "conditions": "Conditions contains the latest observation of the device's state. If the device has been configured according to the class and claim config references, the `Ready` condition should be True.\n\nMust not contain more than 8 entries.", "data": "Data contains arbitrary driver-specific data.\n\nThe length of the raw data must be smaller or equal to 10 Ki.", "networkData": "NetworkData contains network-related information specific to the device.", } @@ -52,9 +52,14 @@ func (AllocationResult) SwaggerDoc() map[string]string { } var map_BasicDevice = map[string]string{ - "": "BasicDevice defines one device instance.", - "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", - "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "": "BasicDevice defines one device instance.", + "attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.", + "consumesCounters": "ConsumesCounters defines a list of references to sharedCounters and the set of counters that the device will consume from those counter sets.\n\nThere can only be a single entry per counterSet.\n\nThe total number of device counter consumption entries must be <= 32. In addition, the total number in the entire ResourceSlice must be <= 1024 (for example, 64 devices with 16 counters each).", + "nodeName": "NodeName identifies the node where the device is available.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "nodeSelector": "NodeSelector defines the nodes where the device is available.\n\nMust use exactly one term.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "allNodes": "AllNodes indicates that all nodes have access to the device.\n\nMust only be set if Spec.PerDeviceNodeSelection is set to true. At most one of NodeName, NodeSelector and AllNodes can be set.", + "taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 4.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (BasicDevice) SwaggerDoc() map[string]string { @@ -70,6 +75,25 @@ func (CELDeviceSelector) SwaggerDoc() map[string]string { return map_CELDeviceSelector } +var map_Counter = map[string]string{ + "": "Counter describes a quantity associated with a device.", + "value": "Value defines how much of a certain device counter is available.", +} + +func (Counter) SwaggerDoc() map[string]string { + return map_Counter +} + +var map_CounterSet = map[string]string{ + "": "CounterSet defines a named set of counters that are available to be used by devices defined in the ResourceSlice.\n\nThe counters are not allocatable by themselves, but can be referenced by devices. When a device is allocated, the portion of counters it uses will no longer be available for use by other devices.", + "name": "Name defines the name of the counter set. It must be a DNS label.", + "counters": "Counters defines the set of counters for this CounterSet The name of each counter must be unique in that set and must be a DNS label.\n\nThe maximum number of counters is 32.", +} + +func (CounterSet) SwaggerDoc() map[string]string { + return map_CounterSet +} + var map_Device = map[string]string{ "": "Device represents one individual hardware instance that can be selected based on its attributes. Besides the name, exactly one field must be set.", "name": "Name is unique identifier among all devices managed by the driver in the pool. It must be a DNS label.", @@ -83,7 +107,7 @@ func (Device) SwaggerDoc() map[string]string { var map_DeviceAllocationConfiguration = map[string]string{ "": "DeviceAllocationConfiguration gets embedded in an AllocationResult.", "source": "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceAllocationConfiguration) SwaggerDoc() map[string]string { @@ -134,7 +158,7 @@ func (DeviceClaim) SwaggerDoc() map[string]string { var map_DeviceClaimConfiguration = map[string]string{ "": "DeviceClaimConfiguration is used for configuration parameters in DeviceClaim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceClaimConfiguration) SwaggerDoc() map[string]string { @@ -190,7 +214,7 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", - "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", } @@ -198,14 +222,26 @@ func (DeviceConstraint) SwaggerDoc() map[string]string { return map_DeviceConstraint } +var map_DeviceCounterConsumption = map[string]string{ + "": "DeviceCounterConsumption defines a set of counters that a device will consume from a CounterSet.", + "counterSet": "CounterSet is the name of the set from which the counters defined will be consumed.", + "counters": "Counters defines the counters that will be consumed by the device.\n\nThe maximum number counters in a device is 32. In addition, the maximum number of all counters in all devices is 1024 (for example, 64 devices with 16 counters each).", +} + +func (DeviceCounterConsumption) SwaggerDoc() map[string]string { + return map_DeviceCounterConsumption +} + var map_DeviceRequest = map[string]string{ - "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", - "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", - "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", - "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", - "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", - "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", + "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label and unique among all DeviceRequests in a ResourceClaim.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "firstAvailable": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (DeviceRequest) SwaggerDoc() map[string]string { @@ -214,11 +250,12 @@ func (DeviceRequest) SwaggerDoc() map[string]string { var map_DeviceRequestAllocationResult = map[string]string{ "": "DeviceRequestAllocationResult contains the allocation result for one request.", - "request": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "request": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", "adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", } func (DeviceRequestAllocationResult) SwaggerDoc() map[string]string { @@ -234,10 +271,49 @@ func (DeviceSelector) SwaggerDoc() map[string]string { return map_DeviceSelector } +var map_DeviceSubRequest = map[string]string{ + "": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "name": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.", +} + +func (DeviceSubRequest) SwaggerDoc() map[string]string { + return map_DeviceSubRequest +} + +var map_DeviceTaint = map[string]string{ + "": "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.", + "key": "The taint key to be applied to a device. Must be a label name.", + "value": "The taint value corresponding to the taint key. Must be a label value.", + "effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.", + "timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.", +} + +func (DeviceTaint) SwaggerDoc() map[string]string { + return map_DeviceTaint +} + +var map_DeviceToleration = map[string]string{ + "": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple using the matching operator .", + "key": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.", + "operator": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.", + "value": "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.", + "effect": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.", + "tolerationSeconds": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // + // +optional + // +listType=atomic + repeated string requests = 2; + + optional DeviceConfiguration deviceConfiguration = 3; +} + +// DeviceAllocationResult is the result of allocating devices. +message DeviceAllocationResult { + // Results lists all allocated devices. + // + // +optional + // +listType=atomic + repeated DeviceRequestAllocationResult results = 1; + + // This field is a combination of all the claim and class configuration parameters. + // Drivers can distinguish between those based on a flag. + // + // This includes configuration parameters for drivers which have no allocated + // devices in the result because it is up to the drivers which configuration + // parameters they support. They can silently ignore unknown configuration + // parameters. + // + // +optional + // +listType=atomic + repeated DeviceAllocationConfiguration config = 2; +} + +// DeviceAttribute must have exactly one field set. +message DeviceAttribute { + // IntValue is a number. + // + // +optional + // +oneOf=ValueType + optional int64 int = 2; + + // BoolValue is a true/false value. + // + // +optional + // +oneOf=ValueType + optional bool bool = 3; + + // StringValue is a string. Must not be longer than 64 characters. + // + // +optional + // +oneOf=ValueType + optional string string = 4; + + // VersionValue is a semantic version according to semver.org spec 2.0.0. + // Must not be longer than 64 characters. + // + // +optional + // +oneOf=ValueType + optional string version = 5; +} + +// DeviceCapacity describes a quantity associated with a device. +message DeviceCapacity { + // Value defines how much of a certain device capacity is available. + // + // +required + optional .k8s.io.apimachinery.pkg.api.resource.Quantity value = 1; +} + +// DeviceClaim defines how to request devices with a ResourceClaim. +message DeviceClaim { + // Requests represent individual requests for distinct devices which + // must all be satisfied. If empty, nothing needs to be allocated. + // + // +optional + // +listType=atomic + repeated DeviceRequest requests = 1; + + // These constraints must be satisfied by the set of devices that get + // allocated for the claim. + // + // +optional + // +listType=atomic + repeated DeviceConstraint constraints = 2; + + // This field holds configuration for multiple potential drivers which + // could satisfy requests in this claim. It is ignored while allocating + // the claim. + // + // +optional + // +listType=atomic + repeated DeviceClaimConfiguration config = 3; +} + +// DeviceClaimConfiguration is used for configuration parameters in DeviceClaim. +message DeviceClaimConfiguration { + // Requests lists the names of requests where the configuration applies. + // If empty, it applies to all requests. + // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // + // +optional + // +listType=atomic + repeated string requests = 1; + + optional DeviceConfiguration deviceConfiguration = 2; +} + +// DeviceClass is a vendor- or admin-provided resource that contains +// device configuration and selectors. It can be referenced in +// the device requests of a claim to apply these presets. +// Cluster scoped. +// +// This is an alpha type and requires enabling the DynamicResourceAllocation +// feature gate. +message DeviceClass { + // Standard object metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines what can be allocated and how to configure it. + // + // This is mutable. Consumers have to be prepared for classes changing + // at any time, either because they get updated or replaced. Claim + // allocations are done once based on whatever was set in classes at + // the time of allocation. + // + // Changing the spec automatically increments the metadata.generation number. + optional DeviceClassSpec spec = 2; +} + +// DeviceClassConfiguration is used in DeviceClass. +message DeviceClassConfiguration { + optional DeviceConfiguration deviceConfiguration = 1; +} + +// DeviceClassList is a collection of classes. +message DeviceClassList { + // Standard list metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // Items is the list of resource classes. + repeated DeviceClass items = 2; +} + +// DeviceClassSpec is used in a [DeviceClass] to define what can be allocated +// and how to configure it. +message DeviceClassSpec { + // Each selector must be satisfied by a device which is claimed via this class. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 1; + + // Config defines configuration parameters that apply to each device that is claimed via this class. + // Some classses may potentially be satisfied by multiple drivers, so each instance of a vendor + // configuration applies to exactly one driver. + // + // They are passed to the driver, but are not considered while allocating the claim. + // + // +optional + // +listType=atomic + repeated DeviceClassConfiguration config = 2; +} + +// DeviceConfiguration must have exactly one field set. It gets embedded +// inline in some other structs which have other fields, so field names must +// not conflict with those. +message DeviceConfiguration { + // Opaque provides driver-specific configuration parameters. + // + // +optional + // +oneOf=ConfigurationType + optional OpaqueDeviceConfiguration opaque = 1; +} + +// DeviceConstraint must have exactly one field set besides Requests. +message DeviceConstraint { + // Requests is a list of the one or more requests in this claim which + // must co-satisfy this constraint. If a request is fulfilled by + // multiple devices, then all of the devices must satisfy the + // constraint. If this is not specified, this constraint applies to all + // requests in this claim. + // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // + // +optional + // +listType=atomic + repeated string requests = 1; + + // MatchAttribute requires that all devices in question have this + // attribute and that its type and value are the same across those + // devices. + // + // For example, if you specified "dra.example.com/numa" (a hypothetical example!), + // then only devices in the same NUMA node will be chosen. A device which + // does not have that attribute will not be chosen. All devices should + // use a value of the same type for this attribute because that is part of + // its specification, but if one device doesn't, then it also will not be + // chosen. + // + // Must include the domain qualifier. + // + // +optional + // +oneOf=ConstraintType + optional string matchAttribute = 2; +} + +// DeviceCounterConsumption defines a set of counters that +// a device will consume from a CounterSet. +message DeviceCounterConsumption { + // CounterSet is the name of the set from which the + // counters defined will be consumed. + // + // +required + optional string counterSet = 1; + + // Counters defines the counters that will be consumed by the device. + // + // The maximum number counters in a device is 32. + // In addition, the maximum number of all counters + // in all devices is 1024 (for example, 64 devices with + // 16 counters each). + // + // +required + map counters = 2; +} + +// DeviceRequest is a request for devices required for a claim. +// This is typically a request for a single resource like a device, but can +// also ask for several identical devices. With FirstAvailable it is also +// possible to provide a prioritized list of requests. +message DeviceRequest { + // Name can be used to reference this request in a pod.spec.containers[].resources.claims + // entry and in a constraint of the claim. + // + // References using the name in the DeviceRequest will uniquely + // identify a request when the Exactly field is set. When the + // FirstAvailable field is set, a reference to the name of the + // DeviceRequest will match whatever subrequest is chosen by the + // scheduler. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // Exactly specifies the details for a single request that must + // be met exactly for the request to be satisfied. + // + // One of Exactly or FirstAvailable must be set. + // + // +optional + // +oneOf=deviceRequestType + optional ExactDeviceRequest exactly = 2; + + // FirstAvailable contains subrequests, of which exactly one will be + // selected by the scheduler. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one can not be used. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + repeated DeviceSubRequest firstAvailable = 3; +} + +// DeviceRequestAllocationResult contains the allocation result for one request. +message DeviceRequestAllocationResult { + // Request is the name of the request in the claim which caused this + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. + // + // +required + optional string request = 1; + + // Driver specifies the name of the DRA driver whose kubelet + // plugin should be invoked to process the allocation once the claim is + // needed on a node. + // + // Must be a DNS subdomain and should end with a DNS domain owned by the + // vendor of the driver. + // + // +required + optional string driver = 2; + + // This name together with the driver name and the device name field + // identify which device was allocated (`//`). + // + // Must not be longer than 253 characters and may contain one or more + // DNS sub-domains separated by slashes. + // + // +required + optional string pool = 3; + + // Device references one device instance via its name in the driver's + // resource pool. It must be a DNS label. + // + // +required + optional string device = 4; + + // AdminAccess indicates that this device was allocated for + // administrative access. See the corresponding request field + // for a definition of mode. + // + // This is an alpha field and requires enabling the DRAAdminAccess + // feature gate. Admin access is disabled if this field is unset or + // set to false, otherwise it is enabled. + // + // +optional + // +featureGate=DRAAdminAccess + optional bool adminAccess = 5; + + // A copy of all tolerations specified in the request at the time + // when the device got allocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 6; +} + +// DeviceSelector must have exactly one field set. +message DeviceSelector { + // CEL contains a CEL expression for selecting a device. + // + // +optional + // +oneOf=SelectorType + optional CELDeviceSelector cel = 1; +} + +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to ExactDeviceRequest, but doesn't expose the +// AdminAccess field as that one is only supported when requesting a +// specific device. +message DeviceSubRequest { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + optional string deviceClassName = 2; + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 3; + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AllocationMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + optional string allocationMode = 4; + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + optional int64 count = 5; + + // If specified, the request's tolerations. + // + // Tolerations for NoSchedule are required to allocate a + // device which has a taint with that effect. The same applies + // to NoExecute. + // + // In addition, should any of the allocated devices get tainted + // with NoExecute after allocation and that effect is not tolerated, + // then all pods consuming the ResourceClaim get deleted to evict + // them. The scheduler will not let new pods reserve the claim while + // it has these tainted devices. Once all pods are evicted, the + // claim will get deallocated. + // + // The maximum number of tolerations is 16. + // + // This is an alpha field and requires enabling the DRADeviceTaints + // feature gate. + // + // +optional + // +listType=atomic + // +featureGate=DRADeviceTaints + repeated DeviceToleration tolerations = 6; +} + +// The device this taint is attached to has the "effect" on +// any claim which does not tolerate the taint and, through the claim, +// to pods using the claim. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message DeviceTaint { + // The taint key to be applied to a device. + // Must be a label name. + // + // +required + optional string key = 1; + + // The taint value corresponding to the taint key. + // Must be a label value. + // + // +optional + optional string value = 2; + + // The effect of the taint on claims that do not tolerate the taint + // and through such claims on the pods using them. + // Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for + // nodes is not valid here. + // + // +required + optional string effect = 3; + + // TimeAdded represents the time at which the taint was added. + // Added automatically during create or update if not set. + // + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4; +} + +// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches +// the triple using the matching operator . +message DeviceToleration { + // Key is the taint key that the toleration applies to. Empty means match all taint keys. + // If the key is empty, operator must be Exists; this combination means to match all values and all keys. + // Must be a label name. + // + // +optional + optional string key = 1; + + // Operator represents a key's relationship to the value. + // Valid operators are Exists and Equal. Defaults to Equal. + // Exists is equivalent to wildcard for value, so that a ResourceClaim can + // tolerate all taints of a particular category. + // + // +optional + // +default="Equal" + optional string operator = 2; + + // Value is the taint value the toleration matches to. + // If the operator is Exists, the value must be empty, otherwise just a regular string. + // Must be a label value. + // + // +optional + optional string value = 3; + + // Effect indicates the taint effect to match. Empty means match all taint effects. + // When specified, allowed values are NoSchedule and NoExecute. + // + // +optional + optional string effect = 4; + + // TolerationSeconds represents the period of time the toleration (which must be + // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + // it is not set, which means tolerate the taint forever (do not evict). Zero and + // negative values will be treated as 0 (evict immediately) by the system. + // If larger than zero, the time when the pod needs to be evicted is calculated as