[ovs-dev] [RFC PATCH v1 1/3] datapath-windows: Introduce HNS OVS calls

Sairam Venugopal vsairam at vmware.com
Fri Oct 19 21:53:41 UTC 2018


Hi Alin,

Since the hns.psm1 isn't officially supported by Microsoft, is it safe to use the script as part of the OVS repo?  Also, "Invoke-HNSRequest" isn't officially documented at this time. I understand that this is an RFC, but do you know if Microsoft plans to finalize this api? If not, I would suggest keeping hns.psm1 out of the OVS repo and instead assume that the end-user has access to it. Otherwise, OVS will need to keep parity with the script in Microsoft's project.

Thanks,
Sairam


On 10/8/18, 4:26 PM, "ovs-dev-bounces at openvswitch.org on behalf of Alin Gabriel Serdean" <ovs-dev-bounces at openvswitch.org on behalf of aserdean at ovn.org> wrote:

    We introduce a new powershell module for interacting with HNS (host network service):
    https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FMicrosoft%2FSDN%2Fblob%2Fmaster%2FKubernetes%2Fwindows%2Fhns.psm1&amp;data=02%7C01%7Cvsairam%40vmware.com%7Cd3d913f61b40483be5c808d62d757f34%7Cb39138ca3cee4b4aa4d6cd83d9dd62f0%7C1%7C0%7C636746380042797946&amp;sdata=rh%2FrirPPKSOUGvrHUhV36M2f83JUFX%2FkbGSCy%2FBvb%2BM%3D&amp;reserved=0
    
    In our repository this shall be named HNSHelper.psm1.
    
    We also add five new commandlets in our OVS powershell module:
    Disable-OVSOnHNSNetwork - disable OVS extension on a given HNS network.
    Enable-OVSOnHNSNetwork - enable OVS extension on a given HNS network.
    Get-OVSEnabledHNSNetworks - return the HNS network on which OVS is enabled.
    Add-OVSHNSInternalPort - will add a new internal port with he name: `name`, on
                             the HNS network, which has OVS enabled. This port
                             shall use the host compartment.
    Delete-OVSHNSInternalPort - will delete an internal port with name: `name` on a
                                HNS network which has OVS enabled.
    
    Signed-off-by: Alin Gabriel Serdean <aserdean at ovn.org>
    ---
     datapath-windows/automake.mk         |   1 +
     datapath-windows/misc/HNSHelper.psm1 | 499 +++++++++++++++++++++++++++++++++++
     datapath-windows/misc/OVS.psm1       |  79 +++++-
     3 files changed, 578 insertions(+), 1 deletion(-)
     create mode 100644 datapath-windows/misc/HNSHelper.psm1
    
    diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk
    index 3820041f6..d98ab3c60 100644
    --- a/datapath-windows/automake.mk
    +++ b/datapath-windows/automake.mk
    @@ -3,6 +3,7 @@ EXTRA_DIST += \
     	datapath-windows/Package/package.VcxProj.user \
     	datapath-windows/include/OvsDpInterfaceExt.h \
     	datapath-windows/include/OvsDpInterfaceCtExt.h \
    +	datapath-windows/misc/HNSHelper.psm1 \
     	datapath-windows/misc/OVS.psm1 \
     	datapath-windows/misc/install.cmd \
     	datapath-windows/misc/uninstall.cmd \
    diff --git a/datapath-windows/misc/HNSHelper.psm1 b/datapath-windows/misc/HNSHelper.psm1
    new file mode 100644
    index 000000000..ac99889cf
    --- /dev/null
    +++ b/datapath-windows/misc/HNSHelper.psm1
    @@ -0,0 +1,499 @@
    +# SDN Sample Scripts  v.1.0
    +#
    +# Copyright (c) Microsoft Corporation
    +#
    +# All rights reserved. 
    +#
    +# MIT License
    +#
    +# 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.
    +#
    +#########################################################################
    +# Global Initialize
    +function Get-VmComputeNativeMethods()
    +{
    +        $signature = @'
    +                     [DllImport("vmcompute.dll")]
    +                     public static extern void HNSCall([MarshalAs(UnmanagedType.LPWStr)] string method, [MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] string request, [MarshalAs(UnmanagedType.LPWStr)] out string response);
    +'@
    +
    +    # Compile into runtime type
    +    try { return [VmCompute.PrivatePInvoke.NativeMethods] }
    +    catch { return (Add-Type -MemberDefinition $signature -Namespace VmCompute.PrivatePInvoke -Name NativeMethods -PassThru) }
    +}
    +
    +#########################################################################
    +# Configuration
    +#########################################################################
    +function Get-HnsSwitchExtensions
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $NetworkId
    +    )
    +
    +    return (Get-HNSNetwork $NetworkId).Extensions
    +}
    +
    +function Set-HnsSwitchExtension
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $NetworkId,
    +        [parameter(Mandatory=$true)] [string] $ExtensionId,
    +        [parameter(Mandatory=$true)] [bool]   $state
    +    )
    +
    +    # { "Extensions": [ { "Id": "...", "IsEnabled": true|false } ] }
    +    $req = @{
    +        "Extensions"=@(@{
    +            "Id"=$ExtensionId;
    +            "IsEnabled"=$state;
    +        };)
    +    }
    +    Invoke-HNSRequest -Method POST -Type networks -Id $NetworkId -Data (ConvertTo-Json $req)
    +}
    +
    +#########################################################################
    +# Activities
    +#########################################################################
    +function Get-HNSActivities
    +{
    +    [cmdletbinding()]Param()
    +    return Invoke-HNSRequest -Type activities -Method GET
    +}
    +
    +#########################################################################
    +# PolicyLists
    +#########################################################################
    +function Get-HNSPolicyList {
    +    [cmdletbinding()]Param()
    +    return Invoke-HNSRequest -Type policylists -Method GET
    +}
    +
    +function Remove-HnsPolicyList
    +{
    +    [CmdletBinding()]
    +    param
    +    (
    +        [parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
    +        [Object[]] $InputObjects
    +    )
    +    begin {$Objects = @()}
    +    process {$Objects += $InputObjects; }
    +    end {
    +        $Objects | foreach {  Invoke-HNSRequest -Method DELETE -Type  policylists -Id $_.Id }
    +    }
    +}
    +
    +function New-HnsRoute {
    +    param
    +    (
    +        [parameter(Mandatory = $false)] [Guid[]] $Endpoints = $null,
    +        [parameter(Mandatory = $true)] [string] $DestinationPrefix,
    +        [parameter(Mandatory = $false)] [switch] $EncapEnabled
    +    )
    +
    +    $policyLists = @{
    +        References = @(
    +            get-endpointReferences $Endpoints;
    +        );
    +        Policies   = @(
    +            @{
    +                Type = "ROUTE";
    +                DestinationPrefix = $DestinationPrefix;
    +                NeedEncap = $EncapEnabled.IsPresent;
    +            }
    +        );
    +    }
    +
    +    Invoke-HNSRequest -Method POST -Type policylists -Data (ConvertTo-Json  $policyLists -Depth 10)
    +}
    +
    +function New-HnsLoadBalancer {
    +    param
    +    (
    +        [parameter(Mandatory = $false)] [Guid[]] $Endpoints = $null,
    +        [parameter(Mandatory = $true)] [int] $InternalPort,
    +        [parameter(Mandatory = $true)] [int] $ExternalPort,
    +        [parameter(Mandatory = $false)] [string] $Vip
    +    )
    +
    +    $policyLists = @{
    +        References = @(
    +            get-endpointReferences $Endpoints;
    +        );
    +        Policies   = @(
    +            @{
    +                Type = "ELB";
    +                InternalPort = $InternalPort;
    +                ExternalPort = $ExternalPort;
    +                VIPs = @($Vip);
    +            }
    +        );
    +    }
    +
    +    Invoke-HNSRequest -Method POST -Type policylists -Data ( ConvertTo-Json  $policyLists -Depth 10)
    +}
    +
    +
    +function get-endpointReferences {
    +    param
    +    (
    +        [parameter(Mandatory = $true)] [Guid[]] $Endpoints = $null
    +    )
    +    if ($Endpoints ) {
    +        $endpointReference = @()
    +        foreach ($endpoint in $Endpoints)
    +        {
    +            $endpointReference += "/endpoints/$endpoint"
    +        }
    +        return $endpointReference
    +    }
    +    return @()
    +}
    +
    +#########################################################################
    +# Networks
    +#########################################################################
    +function New-HnsNetwork
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$false, Position=0)]
    +        [string] $JsonString,
    +        [ValidateSet('ICS', 'Internal', 'Transparent', 'NAT', 'Overlay', 'L2Bridge', 'L2Tunnel', 'Layered', 'Private')]
    +        [parameter(Mandatory = $false, Position = 0)]
    +        [string] $Type,
    +        [parameter(Mandatory = $false)] [string] $Name,
    +        [parameter(Mandatory = $false)] [string] $AddressPrefix,
    +        [parameter(Mandatory = $false)] [string] $Gateway,
    +        [parameter(Mandatory = $false)] [string] $DNSServer,
    +        [parameter(Mandatory = $false)] [string] $AdapterName
    +    )
    +
    +    Begin {
    +        if (!$JsonString) {
    +            $netobj = @{
    +                Type          = $Type;
    +            };
    +
    +            if ($Name) {
    +                $netobj += @{
    +                    Name = $Name;
    +                }
    +            }
    +
    +            if ($AddressPrefix -and  $Gateway) {
    +                $netobj += @{
    +                    Subnets = @(
    +                        @{
    +                            AddressPrefix  = $AddressPrefix;
    +                            GatewayAddress = $Gateway;
    +                        }
    +                    );
    +                }
    +            }
    +
    +            if ($DNSServerName) {
    +                $netobj += @{
    +                    DNSServerList = $DNSServer
    +                }
    +            }
    +
    +            if ($AdapterName) {
    +                $netobj += @{
    +                    NetworkAdapterName = $AdapterName;
    +                }
    +            }
    +
    +            $JsonString = ConvertTo-Json $netobj -Depth 10
    +        }
    +
    +    }
    +    Process{
    +        return Invoke-HNSRequest -Method POST -Type networks -Data $JsonString
    +    }
    +}
    +
    +#########################################################################
    +# Endpoints
    +#########################################################################
    +function New-HnsEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$false, Position = 0)] [string] $JsonString = $null,
    +        [parameter(Mandatory = $false, Position = 0)] [Guid] $NetworkId,
    +        [parameter(Mandatory = $false)] [string] $Name,
    +        [parameter(Mandatory = $false)] [string] $IPAddress,
    +        [parameter(Mandatory = $false)] [string] $Gateway,
    +        [parameter(Mandatory = $false)] [string] $MacAddress,
    +        [parameter(Mandatory = $false)] [switch] $EnableOutboundNat
    +    )
    +
    +    begin
    +    {
    +        if ($JsonString)
    +        {
    +            $EndpointData = $JsonString | ConvertTo-Json | ConvertFrom-Json
    +        }
    +        else
    +        {
    +            $endpoint = @{
    +                VirtualNetwork = $NetworkId;
    +                Policies       = @();
    +            }
    +
    +            if ($Name) {
    +                $endpoint += @{
    +                    Name = $Name;
    +                }
    +            }
    +
    +            if ($MacAddress) {
    +                $endpoint += @{
    +                    MacAddress     = $MacAddress;
    +                }
    +            }
    +
    +            if ($IPAddress) {
    +                $endpoint += @{
    +                    IPAddress      = $IPAddress;
    +                }
    +            }
    +
    +            if ($Gateway) {
    +                $endpoint += @{
    +                    GatewayAddress = $Gateway;
    +                }
    +            }
    +
    +            if ($EnableOutboundNat) {
    +                $endpoint.Policies += @{
    +                    Type = "OutBoundNAT";
    +                }
    +
    +            }
    +            # Try to Generate the data
    +            $EndpointData = convertto-json $endpoint
    +        }
    +    }
    +
    +    Process
    +    {
    +        return Invoke-HNSRequest -Method POST -Type endpoints -Data $EndpointData
    +    }
    +}
    +
    +
    +function New-HnsRemoteEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory = $true)] [Guid] $NetworkId,
    +        [parameter(Mandatory = $false)] [string] $IPAddress,
    +        [parameter(Mandatory = $false)] [string] $MacAddress
    +    )
    +
    +    $remoteEndpoint = @{
    +        ID = [Guid]::NewGuid();
    +        VirtualNetwork = $NetworkId;
    +        IPAddress = $IPAddress;
    +        MacAddress = $MacAddress;
    +        IsRemoteEndpoint = $true;
    +    }
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $remoteEndpoint  -Depth 10)
    +
    +}
    +
    +
    +function Attach-HnsHostEndpoint
    +{
    +    param
    +    (
    +     [parameter(Mandatory=$true)] [Guid] $EndpointID,
    +     [parameter(Mandatory=$true)] [int] $CompartmentID
    +     )
    +    $request = @{
    +        SystemType    = "Host";
    +        CompartmentId = $CompartmentID;
    +    };
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request) -Action "attach" -Id $EndpointID
    +}
    +
    +function Attach-HNSVMEndpoint
    +{
    +    param
    +    (
    +     [parameter(Mandatory=$true)] [Guid] $EndpointID,
    +     [parameter(Mandatory=$true)] [string] $VMNetworkAdapterName
    +     )
    +
    +    $request = @{
    +        VirtualNicName   = $VMNetworkAdapterName;
    +        SystemType    = "VirtualMachine";
    +    };
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request ) -Action "attach" -Id $EndpointID
    +
    +}
    +
    +function Attach-HNSEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [Guid] $EndpointID,
    +        [parameter(Mandatory=$true)] [int] $CompartmentID,
    +        [parameter(Mandatory=$true)] [string] $ContainerID
    +    )
    +     $request = @{
    +        ContainerId = $ContainerID;
    +        SystemType="Container";
    +        CompartmentId = $CompartmentID;
    +    };
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request) -Action "attach" -Id $EndpointID
    +}
    +
    +function Detach-HNSVMEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [Guid] $EndpointID
    +    )
    +    $request = @{
    +        SystemType  = "VirtualMachine";
    +    };
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request ) -Action "detach" -Id $EndpointID
    +}
    +
    +function Detach-HNSHostEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [Guid] $EndpointID
    +    )
    +    $request = @{
    +        SystemType  = "Host";
    +    };
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request ) -Action "detach" -Id $EndpointID
    +}
    +
    +function Detach-HNSEndpoint
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [Guid] $EndpointID,
    +        [parameter(Mandatory=$true)] [string] $ContainerID
    +    )
    +
    +    $request = @{
    +        ContainerId = $ContainerID;
    +        SystemType="Container";
    +    };
    +
    +    return Invoke-HNSRequest -Method POST -Type endpoints -Data (ConvertTo-Json $request ) -Action "detach" -Id $EndpointID
    +}
    +#########################################################################
    +
    +function Invoke-HNSRequest
    +{
    +    param
    +    (
    +        [ValidateSet('GET', 'POST', 'DELETE')]
    +        [parameter(Mandatory=$true)] [string] $Method,
    +        [ValidateSet('networks', 'endpoints', 'activities', 'policylists', 'endpointstats', 'plugins')]
    +        [parameter(Mandatory=$true)] [string] $Type,
    +        [parameter(Mandatory=$false)] [string] $Action = $null,
    +        [parameter(Mandatory=$false)] [string] $Data = $null,
    +        [parameter(Mandatory=$false)] [Guid] $Id = [Guid]::Empty
    +    )
    +
    +    $hnsPath = "/$Type"
    +
    +    if ($id -ne [Guid]::Empty)
    +    {
    +        $hnsPath += "/$id";
    +    }
    +
    +    if ($Action)
    +    {
    +        $hnsPath += "/$Action";
    +    }
    +
    +    $request = "";
    +    if ($Data)
    +    {
    +        $request = $Data
    +    }
    +
    +    $output = "";
    +    $response = "";
    +    Write-Verbose "Invoke-HNSRequest Method[$Method] Path[$hnsPath] Data[$request]"
    +
    +    $hnsApi = Get-VmComputeNativeMethods
    +    $hnsApi::HNSCall($Method, $hnsPath, "$request", [ref] $response);
    +
    +    Write-Verbose "Result : $response"
    +    if ($response)
    +    {
    +        try {
    +            $output = ($response | ConvertFrom-Json);
    +        } catch {
    +            Write-Error $_.Exception.Message
    +            return ""
    +        }
    +        if ($output.Error)
    +        {
    +             Write-Error $output;
    +        }
    +        $output = $output.Output;
    +    }
    +
    +    return $output;
    +}
    +
    +#########################################################################
    +
    +Export-ModuleMember -Function Get-HNSActivities
    +Export-ModuleMember -Function Get-HnsSwitchExtensions
    +Export-ModuleMember -Function Set-HnsSwitchExtension
    +
    +Export-ModuleMember -Function New-HNSNetwork
    +
    +Export-ModuleMember -Function New-HNSEndpoint
    +Export-ModuleMember -Function New-HnsRemoteEndpoint
    +
    +Export-ModuleMember -Function Attach-HNSHostEndpoint
    +Export-ModuleMember -Function Attach-HNSVMEndpoint
    +Export-ModuleMember -Function Attach-HNSEndpoint
    +Export-ModuleMember -Function Detach-HNSHostEndpoint
    +Export-ModuleMember -Function Detach-HNSVMEndpoint
    +Export-ModuleMember -Function Detach-HNSEndpoint
    +
    +Export-ModuleMember -Function Get-HNSPolicyList
    +Export-ModuleMember -Function Remove-HnsPolicyList
    +Export-ModuleMember -Function New-HnsRoute
    +Export-ModuleMember -Function New-HnsLoadBalancer
    +
    +Export-ModuleMember -Function Invoke-HNSRequest
    diff --git a/datapath-windows/misc/OVS.psm1 b/datapath-windows/misc/OVS.psm1
    index a8ffcaefd..1cc347bc6 100644
    --- a/datapath-windows/misc/OVS.psm1
    +++ b/datapath-windows/misc/OVS.psm1
    @@ -207,4 +207,81 @@ function Set-VMNetworkAdapterOVSPortDirect
         }
     }
     
    -Export-ModuleMember -function Set-*, Get-*
    +function Get-OVSEnabledHNSNetworks
    +{
    +    return (Get-HNSNetwork) | Where-Object {($_.Extensions.Id -eq '583cc151-73ec-4a6a-8b47-578297ad7623') -and ($_.Extensions.IsEnabled -eq 'True')}
    +}
    +
    +function Add-OVSHNSInternalPort
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $PortName
    +    )
    +    $test = [array](Get-NetAdapter -IncludeHidden -InterfaceAlias "$PortName" -ErrorAction SilentlyContinue)
    +    if (!($test -eq $null))
    +    {
    +        return
    +    }
    +    $test = [array]((Get-HnsEndpoint) | Where-Object {($_.Name -eq "$PortName")})
    +    if (!($test -eq $null))
    +    {
    +        return
    +    }
    +    $a = [array](Get-OVSEnabledHNSNetworks).Id
    +    if ($a.count -eq 0)
    +    {
    +        # If we did not find any OVS enabled switches try to find the ID via
    +        # environment variable
    +        $a = [array]$env:OVS_ENABLED_NETWORK_ID
    +        if ($a.count -eq 0)
    +        {
    +            # Bypass 1803 listing bug and assumed that the first transparent
    +            # network is OVS enabled
    +            $a = [array](Get-HNSNetwork | where {$_.type -eq "transparent"}).ID
    +            if ($a.count -eq 0)
    +            {
    +                return
    +            }
    +        }
    +    }
    +    $temp = New-HnsEndpoint -NetworkId $a[0] -Name "$PortName"
    +    Attach-HnsHostEndpoint $temp.Id 1
    +    Rename-NetAdapter -IncludeHidden -InterfaceAlias "vEthernet ($PortName)" -NewName "$PortName" -ErrorAction SilentlyContinue
    +    Remove-NetRoute -InterfaceAlias "$PortName" -Confirm:$false -ErrorAction SilentlyContinue
    +    Disable-NetAdapter -Confirm:$false -IncludeHidden -InterfaceAlias "$PortName" -ErrorAction SilentlyContinue
    +}
    +
    +function Delete-OVSHNSInternalPort
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $PortName
    +    )
    +    $a = [array]((Get-HnsEndpoint) | Where-Object {($_.Name -eq "$PortName")})
    +    $request = @{
    +        SystemType  = "Host";
    +    };
    +
    +    return Invoke-HNSRequest -Method DELETE -Type endpoints -Data (ConvertTo-Json $request ) -Id $a[0].ID
    +}
    +
    +function Enable-OVSOnHNSNetwork
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $NetworkId
    +    )
    +    return Set-HnsSwitchExtension -NetworkId $NetworkId -ExtensionId 583cc151-73ec-4a6a-8b47-578297ad7623 -state $True
    +}
    +
    +function Disable-OVSOnHNSNetwork
    +{
    +    param
    +    (
    +        [parameter(Mandatory=$true)] [string] $NetworkId
    +    )
    +    return Set-HnsSwitchExtension -NetworkId $NetworkId -ExtensionId 583cc151-73ec-4a6a-8b47-578297ad7623 -state $False
    +}
    +
    +Export-ModuleMember -function Set-*, Get-*, Enable-*, Disable-*, Add-*, Delete-*
    -- 
    2.16.1.windows.1
    
    _______________________________________________
    dev mailing list
    dev at openvswitch.org
    https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmail.openvswitch.org%2Fmailman%2Flistinfo%2Fovs-dev&amp;data=02%7C01%7Cvsairam%40vmware.com%7Cd3d913f61b40483be5c808d62d757f34%7Cb39138ca3cee4b4aa4d6cd83d9dd62f0%7C1%7C0%7C636746380042797946&amp;sdata=psUPrew%2FaWKLte7V9Kb361XUWouYCGB3ufcYo24m0FU%3D&amp;reserved=0
    



More information about the dev mailing list