Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use standard value type for k8s.v1.cni.cncf.io/networks annotation #4146

Conversation

antoninbas
Copy link
Contributor

For SecondaryNetwork support, we switch to using the standard value type
for the k8s.v1.cni.cncf.io/networks annotation:
https://pkg.go.dev/github.com/k8snetworkplumbingwg/[email protected]/pkg/apis/k8s.cni.cncf.io/v1#NetworkSelectionElement

By switching to the standard type:

  • we avoid user confusion
  • we ensure compatibility with other solutions such as Multus
  • we support "edge" cases that were not supported before: 1) using a
    NetworkAttachmentDefinition CR defined in a different Namespace than
    the Pod, 2) providing the NetworkAttachmentDefinition CR name as
    [namespace/]name[@interface]
  • we can use the standard ParseNetworkAnnotation function and avoid
    potential bugs or inconsistencies.

This means that it is no longer possible to provide an "interface type"
as part of the annotation value. However, this is not really a standard
concept, as this information is typically provided in the
NetworkAttachmentDefinition CR. For example, "bridge" mode for
"macvlan". And in our case we use "sriov" mode (for the "antrea" network
type).

In addition to this change, we add a good amount of unit tests, in the
context of #4142.

Signed-off-by: Antonin Bas [email protected]

@antoninbas
Copy link
Contributor Author

@arunvelayutham I cannot add you as a reviewer at the moment. But I invited you as an antrea-io member (you should have received an invitation from Github), which will enable me to do that.

@codecov
Copy link

codecov bot commented Aug 23, 2022

Codecov Report

Merging #4146 (560558a) into main (3696543) will increase coverage by 0.55%.
The diff coverage is 51.85%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4146      +/-   ##
==========================================
+ Coverage   65.92%   66.48%   +0.55%     
==========================================
  Files         304      304              
  Lines       46626    46614      -12     
==========================================
+ Hits        30737    30989     +252     
+ Misses      13487    13213     -274     
- Partials     2402     2412      +10     
Flag Coverage Δ
e2e-tests 39.34% <3.70%> (?)
integration-tests 34.90% <33.33%> (-0.05%) ⬇️
kind-e2e-tests 49.63% <14.81%> (+0.58%) ⬆️
unit-tests 45.08% <51.31%> (+0.11%) ⬆️
Impacted Files Coverage Δ
pkg/agent/secondarynetwork/ipam/ipam_delegator.go 3.70% <50.00%> (+3.70%) ⬆️
pkg/agent/secondarynetwork/podwatch/controller.go 63.20% <50.00%> (+24.18%) ⬆️
pkg/agent/cniserver/server.go 77.75% <100.00%> (+3.80%) ⬆️
pkg/agent/secondarynetwork/cnipodcache/cache.go 77.55% <100.00%> (ø)
.../agent/flowexporter/priorityqueue/priorityqueue.go 65.55% <0.00%> (-27.78%) ⬇️
pkg/agent/flowexporter/connections/connections.go 60.60% <0.00%> (-15.16%) ⬇️
pkg/agent/controller/networkpolicy/reject.go 74.50% <0.00%> (-6.50%) ⬇️
pkg/agent/memberlist/cluster.go 72.29% <0.00%> (-2.87%) ⬇️
pkg/agent/controller/networkpolicy/packetin.go 69.59% <0.00%> (-2.71%) ⬇️
pkg/agent/controller/trafficcontrol/controller.go 81.08% <0.00%> (-2.42%) ⬇️
... and 36 more

@antoninbas antoninbas force-pushed the secondarynetwork-switch-to-standard-annotation branch 2 times, most recently from 536fc6b to 5fef5a1 Compare August 23, 2022 20:23
@arunvelayutham
Copy link
Contributor

@arunvelayutham I cannot add you as a reviewer at the moment. But I invited you as an antrea-io member (you should have received an invitation from Github), which will enable me to do that.

Thanks @antoninbas . I just got to see the invite and accepted it. Please add me to this PR review.

@antoninbas antoninbas force-pushed the secondarynetwork-switch-to-standard-annotation branch from 5fef5a1 to aad9225 Compare August 23, 2022 20:43
// Set type to "antrea"
Type string `json:"type,omitempty"`
// Set mode to "sriov"
Mode string `json:"mode,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think NetworkType works better? I am not certain. Mode works for me too.

Copy link
Contributor Author

@antoninbas antoninbas Aug 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that works for me. I was inspired by macvlan, but I agree that the macvlan mode is a totally different concept :) Let's see if @arunvelayutham likes networkType too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NetworkType may be confusing as we are referring to the physical/logical "interface" as Mode (SRIOV or Veth?).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not about interface type (SRIOV or veth), but network type - SRIOV, Overlay, MACVLAN, IPVLAN, etc.

@arunvelayutham
Copy link
Contributor

arunvelayutham commented Aug 24, 2022

@antoninbas To clarify on the interface type at Annotation, The reason we kept interface type as part of Pod Annotation is to make it flexible at the Pod/CNF level to decide which interface they wanted to use based on the traffic needs/usage (for example, CNF with higher throughput needs can use SRIOV VFs).
As per the PR change, we moved it to the CR instance. Which means, any CNF pod which is getting configured to be part of this network will use SRIOV. for example, if the CNF gets scheduled in a server with no SRIOV NIC support (though its unlikely scenario in DC env?) secondary network config will fail (in current implementation, if the interface type is not provided, it will fall back to veth pair - Note: veth pair code is not merged yet).

So, if we choose "mode" option on CR to decide what type of network interface to use, we may need to have additional logic to handle Pod Scheduling (on a server with SRIOV capability) and a fallback option if there is an issue to get the VF assigned?.

// See the License for the specific language governing permissions and
// limitations under the License.

package podwatch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for getting the controller_test added!!.

}
if netinfo.InterfaceType == sriovInterfaceType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antoninbas To clarify on the interface type at Annotation, The reason we kept interface type as part of Pod Annotation is to make it flexible at the Pod/CNF level to decide which interface they wanted to use based on the traffic needs/usage (for example, CNF with higher throughput needs can use SRIOV VFs).
As per the PR change, we moved it to the CR instance. Which means, any CNF pod which is getting configured to be part of this network will use SRIOV. for example, if the CNF gets scheduled in a server with no SRIOV NIC support (though its unlikely scenario in DC env?) secondary network config will fail (in current implementation, if the interface type is not provided, it will fall back to veth pair - Note: veth pair code is not merged yet).

So, if we choose "mode" option on CR to decide what type of network interface to use, we may need to have additional logic to handle Pod Scheduling (on a server with SRIOV capability) and a fallback option if there is an issue to get the VF assigned?.

@antoninbas
Copy link
Contributor Author

@arunvelayutham you're correct, but in the current version (the one in main) it looks like we are using the standard upstream annotation, when in practice we use the standard annotation name but with a custom annotation type which is confusing. I also initially believed that there was value in choosing the "interface type" on a per interface basis, but this is not the upstream approach / the Multus approach. Yet, Multus is a standard choice for enterprise users. So is there really an interest in being able to make that choice on a per-interface basis, as opposed to defining 2 networks / NetworkAttachmentDefinition (one with SRIOV and one without)? Curious to hear what @jianjuns thinks here.

I don't think the question is about having an automatic fallback from SRIOV to veth. When using SRIOV, the Pod needs to request a VF and this is taken into account during scheduling. It's more about having 2 NetworkAttachmentDefinition CRs (one for SRIOV, one for veth) with separate subnets, or being able to share the same NetworkAttachmentDefinition CR.

@arunvelayutham
Copy link
Contributor

Sure @antoninbas got it. I'm good either ways. separate subnet/net-attach-def per mode (sriov and veth) vs Pod specific interface choice. I Just wanted to ensure that the use case is clear from the edge/cnf perspective.

@jianjuns please share your inputs as well.

b/w, are these changes tested?. If not, I can help with that (just need time till Friday COB, if that is ok). I call pull in your changes and do a local build and test it at my cluster env.

@jianjuns
Copy link
Contributor

jianjuns commented Aug 24, 2022

I am confused on the sriov or veth "interface type" or "mode" discussion. There should be only network concept.

SR-IOV is a network type. "veth" is not a network type, but just an interface type, and itself defines nothing. We need not expose veth to user configuration at all in my mind, which is just an implementation choice of some types of network (e.g. overlay with OVS, Linux bridge, MACVLAN, etc.).

And I do not feel on one network (NetworkAttachmentDefinition) we should have both SR-IOV and veth interfaces - how can that work? Neither SR-IOV should fallback to veth (I do not understand what does that mean - veth connects to what? What network?).

Back to the original topic, yes, I also feel we should stick to the upstream annotation/CRD behaviors.

@antoninbas
Copy link
Contributor Author

@jianjuns I agree that the SRIOV vs veth discussion was confusing.
let me rephrase, at least from my point of view: logically, for the same flat L2 network, I could have Pods connected to the network through SRIOV interfaces, and other Pods connected to the network through MACVLAN interfaces. Traffic between all of these Pods would be switched by a physical network. Once could imagine having a single network definition, and request SRIOV or MACVLAN connectivity on a per-Pod basis, when specifying the secondary network interface. I think that would be possible, but that doesn't match the upstream approach.

@jianjuns
Copy link
Contributor

jianjuns commented Aug 24, 2022

@antoninbas : sure, you can have that case. But 1) it is not supported by any other plugin I know; 2) it is not very useful in my mind, as you be able to achieve the same with two network definitions; 3) if we want to support that, we need to introduce more parameters (for MACVLAN, or other ways to connect veth) than just a "veth" mode. 4) Probably we should introduce another annotation, not the existing one.

And I personally feel no need to support that case, at least not for now.

@antoninbas antoninbas force-pushed the secondarynetwork-switch-to-standard-annotation branch from aad9225 to a8a4391 Compare August 25, 2022 00:26
@antoninbas
Copy link
Contributor Author

@jianjuns I agree with you. I updated the PR to replace 'mode' with 'networkType' as discussed above.

@antoninbas
Copy link
Contributor Author

@arunvelayutham

b/w, are these changes tested?. If not, I can help with that (just need time till Friday COB, if that is ok). I call pull in your changes and do a local build and test it at my cluster env.

I would appreciate your help with that, since we don't have the e2e test CI job running for this feature yet. That's also why I tried to add a comprehensive suite of unit tests.

@antoninbas antoninbas added the action/release-note Indicates a PR that should be included in release notes. label Aug 25, 2022
@antoninbas antoninbas added this to the Antrea v1.9 release milestone Aug 25, 2022
@arunvelayutham
Copy link
Contributor

@arunvelayutham

b/w, are these changes tested?. If not, I can help with that (just need time till Friday COB, if that is ok). I call pull in your changes and do a local build and test it at my cluster env.

I would appreciate your help with that, since we don't have the e2e test CI job running for this feature yet. That's also why I tried to add a comprehensive suite of unit tests.

Sure, I will do it today and get back @antoninbas

@arunvelayutham
Copy link
Contributor

arunvelayutham commented Aug 26, 2022

@arunvelayutham

b/w, are these changes tested?. If not, I can help with that (just need time till Friday COB, if that is ok). I call pull in your changes and do a local build and test it at my cluster env.

I would appreciate your help with that, since we don't have the e2e test CI job running for this feature yet. That's also why I tried to add a comprehensive suite of unit tests.

@antoninbas
Did a local build with you changes and sanity tested. Secondary network gets created as expected.

CRD Instance:
cat virtual-network-instance-antonin.yml
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: virtual-test-network
namespace: kube-system
spec:
config: '{
"cniVersion": "0.3.0",
"type": "antrea",
"mode": "sriov",
"ipam": {
"type": "whereabouts",
"datastore": "kubernetes",
"kubernetes": {
"kubeconfig": "/host/etc/cni/net.d/whereabouts.d/whereabouts.kubeconfig"
},
"range": "148.14.24.0/24"
}
}'

kubectl get network-attachment-definitions.k8s.cni.cncf.io -n kube-system
NAME AGE
virtual-test-network 10m

Pod Spec with Annotation defined:
cat sanity1_antonin.yml
apiVersion: v1
kind: Pod
metadata:
annotations:
k8s.v1.cni.cncf.io/networks:
'[{"Name": "virtual-test-network", "InterfaceRequest": "eth2"}]'
labels:
app: testsecpod
name: testpodsec150
spec:
containers:
- image: busybox
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- infinity
name: busyboxpod
resources:
requests:
intel.com/intel_sriov_netdevice: "1"
limits:
intel.com/intel_sriov_netdevice: "1"
restartPolicy: OnFailure

kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system antrea-agent-9cngk 2/2 Running 0 64m
kube-system antrea-controller-5467dd5df6-jxm4j 1/1 Running 0 64m
kube-system coredns-64897985d-wgnrf 1/1 Running 2 (23h ago) 28d
kube-system coredns-64897985d-zmg9d 1/1 Running 2 (23h ago) 28d
kube-system etcd-vcesd2 1/1 Running 38 (23h ago) 28d
kube-system kube-apiserver-vcesd2 1/1 Running 12 (23h ago) 28d
kube-system kube-controller-manager-vcesd2 1/1 Running 14 (5h34m ago) 28d
kube-system kube-multus-ds-bdbgd 1/1 Running 2 (23h ago) 28d
kube-system kube-proxy-pgd25 1/1 Running 2 (23h ago) 28d
kube-system kube-scheduler-vcesd2 1/1 Running 14 (5h34m ago) 28d
kube-system kube-sriov-device-plugin-amd64-dsw2q 1/1 Running 0 144m
kube-system testpodsec120 1/1 Running 0 21h
kube-system testpodsec15 1/1 Running 0 21h
kube-system testpodsec150 1/1 Running 0 69s
root@vcesd2:/tmp/secondary_network# kubectl exec -it testpodsec150 -n kube-system -- ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
3: eth0@if447: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
link/ether 4a:a2:04:a1:bf:a6 brd ff:ff:ff:ff:ff:ff
inet 172.25.0.41/24 brd 172.25.0.255 scope global eth0
valid_lft forever preferred_lft forever
428: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc mq qlen 1000
link/ether 5e:5b:35:4e:e0:01 brd ff:ff:ff:ff:ff:ff
inet 148.14.24.23/24 brd 148.14.24.255 scope global eth1
valid_lft forever preferred_lft forever

Antrea-agent logs:
I0825 23:08:30.615232 1 server.go:425] Received CmdAdd request cni_args:{container_id:"084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b" netns:"/proc/1152724/ns/net" ifname:"eth0" args:"IgnoreUnknown=1;K8S_POD_NAMESPACE=kube-system;K8S_POD_NAME=testpodsec150;K8S_POD_INFRA_CONTAINER_ID=084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b" path:"/opt/cni/bin" network_configuration:"{"cniVersion":"0.3.0","ipam":{"type":"host-local"},"name":"antrea","type":"antrea"}"}
I0825 23:08:30.621860 1 server.go:495] "Allocated IP addresses" container="084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b" result=&{Result:{CNIVersion:0.4.0 Interfaces:[] IPs:[{Version:4 Interface: Address:{IP:172.25.0.41 Mask:ffffff00} Gateway:172.25.0.1}] Routes:[] DNS:{Nameservers:[] Domain: Search:[] Options:[]}} VLANID:0}
I0825 23:08:30.700979 1 pod_configuration.go:265] Configured interfaces for container 084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b
I0825 23:08:30.701018 1 server.go:522] CmdAdd for container 084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b succeeded
I0825 23:08:31.658123 1 controller.go:346] "Secondary Network attached to Pod" network=&{Name:virtual-test-network Namespace:kube-system IPRequest:[] MacRequest: InfinibandGUIDRequest: InterfaceRequest: PortMappingsRequest:[] BandwidthRequest: CNIArgs: GatewayRequest:[]} Pod="kube-system/testpodsec150"
2022-08-25T23:08:31Z [debug] ADD - IPAM configuration successfully read: {Name:virtual-test-network Type:whereabouts Routes:[] Datastore:kubernetes Addresses:[] OmitRanges:[] DNS:{Nameservers:[] Domain: Search:[] Options:[]} Range:148.14.24.0/24 RangeStart:148.14.24.0 RangeEnd: GatewayStr: EtcdHost: EtcdUsername: EtcdPassword:********* EtcdKeyFile: EtcdCertFile: EtcdCACertFile: LeaderLeaseDuration:1500 LeaderRenewDeadline:1000 LeaderRetryPeriod:500 LogFile: LogLevel: OverlappingRanges:true SleepForRace:0 Gateway: Kubernetes:{KubeConfigPath:/host/etc/cni/net.d/whereabouts.d/whereabouts.kubeconfig K8sAPIRoot:} ConfigurationPath: PodName:testpodsec150 PodNamespace:kube-system}
2022-08-25T23:08:31Z [debug] Beginning IPAM for ContainerID: 084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b
2022-08-25T23:08:31Z [debug] Started leader election
I0825 23:08:31.740207 4137 leaderelection.go:248] attempting to acquire leader lease kube-system/whereabouts...
I0825 23:08:31.750008 4137 leaderelection.go:258] successfully acquired lease kube-system/whereabouts
2022-08-25T23:08:31Z [debug] OnStartedLeading() called
2022-08-25T23:08:31Z [debug] Elected as leader, do processing
2022-08-25T23:08:31Z [debug] IPManagement -- mode: 0 / containerID: 084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b / podRef: kube-system/testpodsec150
2022-08-25T23:08:31Z [debug] IterateForAssignment input >> ip: 148.14.24.0 | ipnet: {148.14.24.0 ffffff00} | first IP: 148.14.24.1 | last IP: 148.14.24.254
2022-08-25T23:08:31Z [debug] Reserving IP: |148.14.24.23 084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b|
2022-08-25T23:08:31Z [debug] OverlappingRangewide allocation check for IP: 148.14.24.23
2022-08-25T23:08:31Z [debug] K8s UpdateOverlappingRangeAllocation success on allocate: &{TypeMeta:{Kind: APIVersion:} ObjectMeta:{Name:148.14.24.23 GenerateName: Namespace:kube-system SelfLink: UID:64e435af-b649-4629-92a4-6fdf2429ab66 ResourceVersion:3346782 Generation:1 CreationTimestamp:2022-08-25 23:08:31 +0000 UTC DeletionTimestamp: DeletionGracePeriodSeconds: Labels:map[] Annotations:map[] OwnerReferences:[] Finalizers:[] ClusterName: ManagedFields:[{Manager:whereabouts Operation:Update APIVersion:whereabouts.cni.cncf.io/v1alpha1 Time:2022-08-25 23:08:31 +0000 UTC FieldsType:FieldsV1 FieldsV1:{"f:spec":{".":{},"f:containerid":{},"f:podref":{}}} Subresource:}]} Spec:{ContainerID:084f45479e97876a5942b6223eb2b865917817688cc19e03eac12bb226c17e9b PodRef:kube-system/testpodsec150}}
2022-08-25T23:08:31Z [debug] OnStoppedLeading() called
2022-08-25T23:08:31Z [debug] Finished leader election
2022-08-25T23:08:31Z [debug] IPManagement: {148.14.24.23 ffffff00},

For SecondaryNetwork support, we switch to using the standard value type
for the k8s.v1.cni.cncf.io/networks annotation:
https://pkg.go.dev/github.com/k8snetworkplumbingwg/[email protected]/pkg/apis/k8s.cni.cncf.io/v1#NetworkSelectionElement

By switching to the standard type:
* we avoid user confusion
* we ensure compatibility with other solutions such as Multus
* we support "edge" cases that were not supported before: 1) using a
  NetworkAttachmentDefinition CR defined in a different Namespace than
  the Pod, 2) providing the NetworkAttachmentDefinition CR name as
  `[namespace/]name[@interface]`
* we can use the standard ParseNetworkAnnotation function and avoid
  potential bugs or inconsistencies.

This means that it is no longer possible to provide an "interface type"
as part of the annotation value. However, this is not really a standard
concept, as this information is typically provided in the
NetworkAttachmentDefinition CR. For example, "bridge" mode for
"macvlan". And in our case we use "sriov" mode (for the "antrea" network
type).

In addition to this change, we add a good amount of unit tests, in the
context of antrea-io#4142.

Signed-off-by: Antonin Bas <[email protected]>
Signed-off-by: Antonin Bas <[email protected]>
@antoninbas antoninbas force-pushed the secondarynetwork-switch-to-standard-annotation branch from a8a4391 to 560558a Compare August 26, 2022 22:43
@antoninbas
Copy link
Contributor Author

@arunvelayutham thanks for testing and confirming that it works fine

@antoninbas
Copy link
Contributor Author

@jianjuns could you give this another review?

@antoninbas
Copy link
Contributor Author

/test-all

Copy link
Contributor

@jianjuns jianjuns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@arunvelayutham
Copy link
Contributor

LGTM

@antoninbas antoninbas merged commit 222c2b6 into antrea-io:main Aug 29, 2022
@antoninbas antoninbas deleted the secondarynetwork-switch-to-standard-annotation branch August 29, 2022 17:14
@antoninbas antoninbas mentioned this pull request Sep 2, 2022
37 tasks
heanlan pushed a commit to heanlan/antrea that referenced this pull request Mar 29, 2023
…ntrea-io#4146)

For SecondaryNetwork support, we switch to using the standard value type
for the k8s.v1.cni.cncf.io/networks annotation:
https://pkg.go.dev/github.com/k8snetworkplumbingwg/[email protected]/pkg/apis/k8s.cni.cncf.io/v1#NetworkSelectionElement

By switching to the standard type:
* we avoid user confusion
* we ensure compatibility with other solutions such as Multus
* we support "edge" cases that were not supported before: 1) using a
  NetworkAttachmentDefinition CR defined in a different Namespace than
  the Pod, 2) providing the NetworkAttachmentDefinition CR name as
  `[namespace/]name[@interface]`
* we can use the standard ParseNetworkAnnotation function and avoid
  potential bugs or inconsistencies.

This means that it is no longer possible to provide an "interface type"
as part of the annotation value. However, this is not really a standard
concept, as this information is typically provided in the
NetworkAttachmentDefinition CR. For example, "bridge" mode for
"macvlan". And in our case we use "sriov" networkType.

In addition to this change, we add a good amount of unit tests, in the
context of antrea-io#4142.

Signed-off-by: Antonin Bas <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
action/release-note Indicates a PR that should be included in release notes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants