On 6/30/23 14:23, Adrian Moreno wrote:
> 
> 
> On 6/30/23 13:40, Ilya Maximets wrote:
>> On 6/30/23 12:39, Adrian Moreno wrote:
>>>
>>>
>>> On 6/14/23 23:07, Terry Wilson wrote:
>>>> This adds a Python version of the async DNS support added in:
>>>>
>>>> 771680d96 DNS: Add basic support for asynchronous DNS resolving
>>>>
>>>> The above version uses the unbound C library, and this
>>>> implimentation uses the SWIG-wrapped Python version of that.
>>>>
>>>> In the event that the Python unbound library is not available,
>>>> a warning will be logged and the resolve() method will just
>>>> return None. For the case where inet_parse_active() is passed
>>>> an IP address, it will not try to resolve it, so existing
>>>> behavior should be preserved in the case that the unbound
>>>> library is unavailable.
>>>>
>>>> Intentional differences from the C version are as follows:
>>>>
>>>>     OVS_HOSTS_FILE environment variable can bet set to override
>>>>     the system 'hosts' file. This is primarily to allow testing to
>>>>     be done without requiring network connectivity.
>>>>
>>>>     Since resolution can still be done via hosts file lookup, DNS
>>>>     lookups are not disabled when resolv.conf cannot be loaded.
>>>>
>>>>     The Python socket_util module has fallen behind its C equivalent.
>>>>     The bare minimum change was done to inet_parse_active() to support
>>>>     sync/async dns, as there is no equivalent to
>>>>     parse_sockaddr_components(), inet_parse_passive(), etc. A TODO
>>>>     was added to bring socket_util.py up to equivalency to the C
>>>>     version.
>>>>
>>>> Signed-off-by: Terry Wilson <twil...@redhat.com>
>>>> ---
>>>>    .github/workflows/build-and-test.yml    |   4 +-
>>>>    Documentation/intro/install/general.rst |   4 +-
>>>>    Documentation/intro/install/rhel.rst    |   2 +-
>>>>    Documentation/intro/install/windows.rst |   2 +-
>>>>    NEWS                                    |   4 +-
>>>>    debian/control.in                       |   1 +
>>>>    m4/openvswitch.m4                       |   8 +-
>>>>    python/TODO.rst                         |   7 +
>>>>    python/automake.mk                      |   2 +
>>>>    python/ovs/dns_resolve.py               | 272 +++++++++++++++++++++++
>>>>    python/ovs/socket_util.py               |  21 +-
>>>>    python/ovs/stream.py                    |   2 +-
>>>>    python/ovs/tests/test_dns_resolve.py    | 280 ++++++++++++++++++++++++
>>>>    python/setup.py                         |   6 +-
>>>>    rhel/openvswitch-fedora.spec.in         |   2 +-
>>>>    tests/vlog.at                           |   2 +
>>>>    16 files changed, 601 insertions(+), 18 deletions(-)
>>>>    create mode 100644 python/ovs/dns_resolve.py
>>>>    create mode 100644 python/ovs/tests/test_dns_resolve.py
>>>>
>>>> diff --git a/.github/workflows/build-and-test.yml 
>>>> b/.github/workflows/build-and-test.yml
>>>> index f66ab43b0..47d239f10 100644
>>>> --- a/.github/workflows/build-and-test.yml
>>>> +++ b/.github/workflows/build-and-test.yml
>>>> @@ -183,10 +183,10 @@ jobs:
>>>>          run:  sudo apt update || true
>>>>        - name: install common dependencies
>>>>          run:  sudo apt install -y ${{ env.dependencies }}
>>>> -    - name: install libunbound libunwind
>>>> +    - name: install libunbound libunwind python3-unbound
>>>>          # GitHub Actions doesn't have 32-bit versions of these libraries.
>>>>          if:   matrix.m32 == ''
>>>> -      run:  sudo apt install -y libunbound-dev libunwind-dev
>>>> +      run:  sudo apt install -y libunbound-dev libunwind-dev 
>>>> python3-unbound
>>>>        - name: install 32-bit libraries
>>>>          if:   matrix.m32 != ''
>>>>          run:  sudo apt install -y gcc-multilib
>>>> diff --git a/Documentation/intro/install/general.rst 
>>>> b/Documentation/intro/install/general.rst
>>>> index 42b5682fd..19e360d47 100644
>>>> --- a/Documentation/intro/install/general.rst
>>>> +++ b/Documentation/intro/install/general.rst
>>>> @@ -90,7 +90,7 @@ need the following software:
>>>>      If libcap-ng is installed, then Open vSwitch will automatically build 
>>>> with
>>>>      support for it.
>>>>    
>>>> -- Python 3.4 or later.
>>>> +- Python 3.6 or later.
>>>>    
>>>>    - Unbound library, from http://www.unbound.net, is optional but 
>>>> recommended if
>>>>      you want to enable ovs-vswitchd and other utilities to use DNS names 
>>>> when
>>>> @@ -208,7 +208,7 @@ simply install and run Open vSwitch you require the 
>>>> following software:
>>>>      from iproute2 (part of all major distributions and available at
>>>>      https://wiki.linuxfoundation.org/networking/iproute2).
>>>>    
>>>> -- Python 3.4 or later.
>>>> +- Python 3.6 or later.
>>>>    
>>>>    On Linux you should ensure that ``/dev/urandom`` exists. To support TAP
>>>>    devices, you must also ensure that ``/dev/net/tun`` exists.
>>>> diff --git a/Documentation/intro/install/rhel.rst 
>>>> b/Documentation/intro/install/rhel.rst
>>>> index d1fc42021..f2151d890 100644
>>>> --- a/Documentation/intro/install/rhel.rst
>>>> +++ b/Documentation/intro/install/rhel.rst
>>>> @@ -92,7 +92,7 @@ Once that is completed, remove the file 
>>>> ``/tmp/ovs.spec``.
>>>>    If python3-sphinx package is not available in your version of RHEL, you 
>>>> can
>>>>    install it via pip with 'pip install sphinx'.
>>>>    
>>>> -Open vSwitch requires python 3.4 or newer which is not available in older
>>>> +Open vSwitch requires python 3.6 or newer which is not available in older
>>>>    distributions. In the case of RHEL 6.x and its derivatives, one option 
>>>> is
>>>>    to install python34 from `EPEL`_.
>>>>    
>>>> diff --git a/Documentation/intro/install/windows.rst 
>>>> b/Documentation/intro/install/windows.rst
>>>> index 78f60f35a..fce099d5d 100644
>>>> --- a/Documentation/intro/install/windows.rst
>>>> +++ b/Documentation/intro/install/windows.rst
>>>> @@ -56,7 +56,7 @@ The following explains the steps in some detail.
>>>>    
>>>>          'C:/MinGW /mingw'.
>>>>    
>>>> -- Python 3.4 or later.
>>>> +- Python 3.6 or later.
>>>>    
>>>>      Install the latest Python 3.x from python.org and verify that its 
>>>> path is
>>>>      part of Windows' PATH environment variable.
>>>> diff --git a/NEWS b/NEWS
>>>> index cfd466663..24c694b8f 100644
>>>> --- a/NEWS
>>>> +++ b/NEWS
>>>> @@ -36,7 +36,9 @@ Post-v3.1.0
>>>>           process extra privileges when mapping physical interconnect 
>>>> memory.
>>>>       - SRv6 Tunnel Protocol
>>>>         * Added support for userspace datapath (only).
>>>> -
>>>> +   - Python
>>>> +     * Added async DNS support
>>>> +     * Dropped support for Python < 3.6
>>>>    
>>>>    v3.1.0 - 16 Feb 2023
>>>>    --------------------
>>>> diff --git a/debian/control.in b/debian/control.in
>>>> index 19f590d06..64b0a4ce0 100644
>>>> --- a/debian/control.in
>>>> +++ b/debian/control.in
>>>> @@ -287,6 +287,7 @@ Depends:
>>>>    Suggests:
>>>>     python3-netaddr,
>>>>     python3-pyparsing,
>>>> + python3-unbound,
>>>>    Description: Python 3 bindings for Open vSwitch
>>>>     Open vSwitch is a production quality, multilayer, software-based,
>>>>     Ethernet virtual switch. It is designed to enable massive network
>>>> diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
>>>> index 14d9249b8..373a7e413 100644
>>>> --- a/m4/openvswitch.m4
>>>> +++ b/m4/openvswitch.m4
>>>> @@ -371,16 +371,16 @@ dnl Checks for valgrind/valgrind.h.
>>>>    AC_DEFUN([OVS_CHECK_VALGRIND],
>>>>      [AC_CHECK_HEADERS([valgrind/valgrind.h])])
>>>>    
>>>> -dnl Checks for Python 3.4 or later.
>>>> +dnl Checks for Python 3.6 or later.
>>>>    AC_DEFUN([OVS_CHECK_PYTHON3],
>>>>      [AC_CACHE_CHECK(
>>>> -     [for Python 3 (version 3.4 or later)],
>>>> +     [for Python 3 (version 3.6 or later)],
>>>>         [ovs_cv_python3],
>>>>         [if test -n "$PYTHON3"; then
>>>>            ovs_cv_python3=$PYTHON3
>>>>          else
>>>>            ovs_cv_python3=no
>>>> -        for binary in python3 python3.4 python3.5 python3.6 python3.7; do
>>>> +        for binary in python3{,.{6..12}}; do
>>>>              ovs_save_IFS=$IFS; IFS=$PATH_SEPARATOR
>>>>              for dir in $PATH; do
>>>>                IFS=$ovs_save_IFS
>>>> @@ -397,7 +397,7 @@ else:
>>>>            done
>>>>          fi])
>>>>       if test "$ovs_cv_python3" = no; then
>>>> -     AC_MSG_ERROR([Python 3.4 or later is required but not found in 
>>>> $PATH, please install it or set $PYTHON3 to point to it])
>>>> +     AC_MSG_ERROR([Python 3.6 or later is required but not found in 
>>>> $PATH, please install it or set $PYTHON3 to point to it])
>>>>       fi
>>>>       AC_ARG_VAR([PYTHON3])
>>>>       PYTHON3=$ovs_cv_python3])
>>>> diff --git a/python/TODO.rst b/python/TODO.rst
>>>> index 3a53489f1..acc5461e2 100644
>>>> --- a/python/TODO.rst
>>>> +++ b/python/TODO.rst
>>>> @@ -32,3 +32,10 @@ Python Bindings To-do List
>>>>    
>>>>      * Support write-only-changed monitor mode (equivalent of
>>>>        OVSDB_IDL_WRITE_CHANGED_ONLY).
>>>> +
>>>> +* socket_util:
>>>> +
>>>> +  * Add equivalent fuctions to inet_parse_passive, 
>>>> parse_sockaddr_components,
>>>> +    et al. to better support using async dns. The reconnect code will
>>>> +    currently log a warning when inet_parse_active() returns w/o yet 
>>>> having
>>>> +    resolved an address, but will continue to connect and eventually 
>>>> succeed.
>>>> diff --git a/python/automake.mk b/python/automake.mk
>>>> index d00911828..82a508787 100644
>>>> --- a/python/automake.mk
>>>> +++ b/python/automake.mk
>>>> @@ -16,6 +16,7 @@ ovs_pyfiles = \
>>>>            python/ovs/compat/sortedcontainers/sorteddict.py \
>>>>            python/ovs/compat/sortedcontainers/sortedset.py \
>>>>            python/ovs/daemon.py \
>>>> +  python/ovs/dns_resolve.py \
>>>>            python/ovs/db/__init__.py \
>>>>            python/ovs/db/custom_index.py \
>>>>            python/ovs/db/data.py \
>>>> @@ -55,6 +56,7 @@ ovs_pyfiles = \
>>>>    
>>>>    ovs_pytests = \
>>>>            python/ovs/tests/test_decoders.py \
>>>> +  python/ovs/tests/test_dns_resolve.py \
>>>>            python/ovs/tests/test_filter.py \
>>>>            python/ovs/tests/test_kv.py \
>>>>            python/ovs/tests/test_list.py \
>>>> diff --git a/python/ovs/dns_resolve.py b/python/ovs/dns_resolve.py
>>>> new file mode 100644
>>>> index 000000000..7b0f6c266
>>>> --- /dev/null
>>>> +++ b/python/ovs/dns_resolve.py
>>>> @@ -0,0 +1,272 @@
>>>> +# Copyright (c) 2023 Red Hat, Inc.
>>>> +#
>>>> +# 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.
>>>> +
>>>> +import collections
>>>> +import enum
>>>> +import functools
>>>> +import ipaddress
>>>> +import os
>>>> +import time
>>>> +import typing
>>>> +
>>>> +try:
>>>> +    import unbound  # type: ignore
>>>> +except ImportError:
>>>> +    pass
>>>> +
>>>> +import ovs.vlog
>>>> +
>>>> +vlog = ovs.vlog.Vlog("dns_resolve")
>>>> +
>>>> +
>>>> +class ReqState(enum.Enum):
>>>> +    INVALID = 0
>>>> +    PENDING = 1
>>>> +    GOOD = 2
>>>> +    ERROR = 3
>>>> +
>>>> +
>>>> +class DNSRequest:
>>>> +
>>>
>>> nit: extra space
>>>
>>>> +    def __init__(self, name: str):
>>>> +        self.name: str = name
>>>> +        self.state: ReqState = ReqState.INVALID
>>>> +        self.time: typing.Optional[float] = None
>>>> +        # set by DNSResolver._callback
>>>> +        self.result: typing.Optional[str] = None
>>>> +        self.ttl: typing.Optional[float] = None
>>>> +
>>>> +    @property
>>>> +    def expired(self):
>>>> +        return time.time() > self.time + self.ttl
>>>> +
>>>> +    @property
>>>> +    def is_valid(self):
>>>> +        return self.state == ReqState.GOOD and not self.expired
>>>> +
>>>> +    def __str__(self):
>>>> +        return (f"DNSRequest(name={self.name}, state={self.state}, "
>>>> +                f"time={self.time}, result={self.result})")
>>>> +
>>>> +
>>>> +class DefaultReqDict(collections.defaultdict):
>>>> +    def __init__(self):
>>>> +        super().__init__(DNSRequest)
>>>> +
>>>> +    def __missing__(self, key):
>>>> +        ret = self.default_factory(key)
>>>> +        self[key] = ret
>>>> +        return ret
>>>> +
>>>> +
>>>> +class UnboundException(Exception):
>>>> +    def __init__(self, message, errno):
>>>> +        try:
>>>> +            msg = f"{message}: {unbound.ub_strerror(errno)}"
>>>> +        except NameError:
>>>> +            msg = message
>>>> +        super().__init__(msg)
>>>> +
>>>> +
>>>> +def dns_enabled(func):
>>>> +    @functools.wraps(func)
>>>> +    def wrapper(self, *args, **kwargs):
>>>> +        if self.dns_enabled:
>>>> +            return func(self, *args, **kwargs)
>>>> +        vlog.err("DNS support requires the python unbound library")
>>>> +    return wrapper
>>>> +
>>>
>>>
>>>> +
>>>> +class singleton:
>>>> +    def __init__(self, klass):
>>>> +        self._klass = klass
>>>> +        self._instance = None
>>>> +
>>>> +    def __call__(self, *args, **kwargs):
>>>> +        if self._instance is None:
>>>> +            self._instance = self._klass(*args, **kwargs)
>>>> +        return self._instance
>>>> +
>>>
>>> Having a singleton class that accepts a parameter in the constructor could 
>>> be a
>>> bit confusing. For instance: "d = DNSResolver(is_daemon=True)"
>>> might yield a resolver where "is_daemon" is actually "False" if some other 
>>> part
>>> of the code created it before.
>>>
>>> Woudn't it be cleaner if it just exposed "resove_sync" and "resolve_async" 
>>> and
>>> let users choose how to resolve?
>>
>> I suppose, the problem is that most of the resolving is happening
>> from the inside of OVS libraries.  So, it will not be a choice of
>> an application.
>>
> 
> Not sure I understand. Currently if a user does:
> 'DNSResolver(is_daemon=False).resolve("openvswitch.org")', it'll resolve 
> synchronously and so will all the rest of the resolutions of that program.
> 
> The internal variable "_is_daemon" is only used to select the way to resolve. 
> The same behavior would be achieved if, for instance, "is_daemon" is a 
> default 
> argument to "resolve()" with the extra benefit of the first resolution not 
> influencing all the rest.

My point is that inet_parse_active() is calling:

  host_name = dns_resolve.resolve(host_name)

And we will have to add an argument to this call in order to give user
a choice to perform sync or async resolution.  As well as add this
argument to all the functions that may call inet_parse_active() and all
the functions that may call these functions.

To avoid that we have a global "is_daemon" state.

Does that make sense?

Maybe we could remove the class paramater and add a function like
dns_resolve.enable_async() instead, so the global state change will
be explicit?

> 
>> Maybe a warning that the parameter is different will be enough?
>>> Best regards, Ilya Maximets.
>>
> 

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to