Changeset 57b93b22 in trunk


Ignore:
Timestamp:
2023-05-01T16:54:51Z (2 years ago)
Author:
Itamar Turner-Trauring <itamar@…>
Branches:
master
Children:
f1f3c12
Parents:
2a4dcb7 (diff), 4edf98b (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge remote-tracking branch 'origin/master' into 4015-more-linting

Files:
4 added
10 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified .circleci/create-virtualenv.sh

    r2a4dcb7 r57b93b22  
    4848# explicitly ask for one.
    4949"${PIP}" install --upgrade setuptools==44.0.0 wheel
     50
     51# Just about every user of this image wants to use tox from the bootstrap
     52# virtualenv so go ahead and install it now.
     53"${PIP}" install "tox~=3.0"
  • TabularUnified .circleci/populate-wheelhouse.sh

    r2a4dcb7 r57b93b22  
    33# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
    44set -euxo pipefail
    5 
    6 # Basic Python packages that you just need to have around to do anything,
    7 # practically speaking.
    8 BASIC_DEPS="pip wheel"
    9 
    10 # Python packages we need to support the test infrastructure.  *Not* packages
    11 # Tahoe-LAFS itself (implementation or test suite) need.
    12 TEST_DEPS="tox~=3.0"
    13 
    14 # Python packages we need to generate test reports for CI infrastructure.
    15 # *Not* packages Tahoe-LAFS itself (implement or test suite) need.
    16 REPORTING_DEPS="python-subunit junitxml subunitreporter"
    175
    186# The filesystem location of the wheelhouse which we'll populate with wheels
     
    4230    wheel \
    4331    --wheel-dir "${WHEELHOUSE_PATH}" \
    44     "${PROJECT_ROOT}"[test] \
    45     ${BASIC_DEPS} \
    46     ${TEST_DEPS} \
    47     ${REPORTING_DEPS}
    48 
    49 # Not strictly wheelhouse population but ... Note we omit basic deps here.
    50 # They're in the wheelhouse if Tahoe-LAFS wants to drag them in but it will
    51 # have to ask.
    52 "${PIP}" \
    53     install \
    54     ${TEST_DEPS} \
    55     ${REPORTING_DEPS}
     32    "${PROJECT_ROOT}"[testenv] \
     33    "${PROJECT_ROOT}"[test]
  • TabularUnified .circleci/run-tests.sh

    r2a4dcb7 r57b93b22  
    8080fi
    8181
     82WORKDIR=/tmp/tahoe-lafs.tox
    8283${TIMEOUT} ${BOOTSTRAP_VENV}/bin/tox \
    8384    -c ${PROJECT_ROOT}/tox.ini \
    84     --workdir /tmp/tahoe-lafs.tox \
     85    --workdir "${WORKDIR}" \
    8586    -e "${TAHOE_LAFS_TOX_ENVIRONMENT}" \
    8687    ${TAHOE_LAFS_TOX_ARGS} || "${alternative}"
     
    9495    # Create a junitxml results area.
    9596    mkdir -p "$(dirname "${JUNITXML}")"
    96     "${BOOTSTRAP_VENV}"/bin/subunit2junitxml < "${SUBUNIT2}" > "${JUNITXML}" || "${alternative}"
     97
     98    "${WORKDIR}/${TAHOE_LAFS_TOX_ENVIRONMENT}/bin/subunit2junitxml" < "${SUBUNIT2}" > "${JUNITXML}" || "${alternative}"
    9799fi
  • TabularUnified .circleci/setup-virtualenv.sh

    r2a4dcb7 r57b93b22  
    2727# Tell pip where it can find any existing wheels.
    2828export PIP_FIND_LINKS="file://${WHEELHOUSE_PATH}"
    29 
    30 # It is tempting to also set PIP_NO_INDEX=1 but (a) that will cause problems
    31 # between the time dependencies change and the images are re-built and (b) the
    32 # upcoming-deprecations job wants to install some dependencies from github and
    33 # it's awkward to get that done any earlier than the tox run.  So, we don't
    34 # set it.
     29export PIP_NO_INDEX="1"
    3530
    3631# Get everything else installed in it, too.
  • TabularUnified integration/conftest.py

    r2a4dcb7 r57b93b22  
    55from __future__ import annotations
    66
     7import os
    78import sys
    89import shutil
     
    4849    block_with_timeout,
    4950)
     51
     52
     53# No reason for HTTP requests to take longer than two minutes in the
     54# integration tests. See allmydata/scripts/common_http.py for usage.
     55os.environ["__TAHOE_CLI_HTTP_TIMEOUT"] = "120"
    5056
    5157
  • TabularUnified setup.py

    r2a4dcb7 r57b93b22  
    142142    # HTTP server and client
    143143    "klein",
     144
    144145    # 2.2.0 has a bug: https://github.com/pallets/werkzeug/issues/2465
    145     "werkzeug != 2.2.0",
     146    # 2.3.x has an incompatibility with Klein: https://github.com/twisted/klein/pull/575
     147    "werkzeug != 2.2.0, < 2.3",
    146148    "treq",
    147149    "cbor2",
     
    399401              "gpg",
    400402          ],
     403
     404          # Here are the dependencies required to set up a reproducible test
     405          # environment.  This could be for CI or local development.  These
     406          # are *not* library dependencies of the test suite itself.  They are
     407          # the tools we use to run the test suite at all.
     408          "testenv": [
     409              # Pin all of these versions for the same reason you ever want to
     410              # pin anything: to prevent new releases with regressions from
     411              # introducing spurious failures into CI runs for whatever
     412              # development work is happening at the time.  The versions
     413              # selected here are just the current versions at the time.
     414              # Bumping them to keep up with future releases is fine as long
     415              # as those releases are known to actually work.
     416              "pip==22.0.3",
     417              "wheel==0.37.1",
     418              "setuptools==60.9.1",
     419              "subunitreporter==22.2.0",
     420              "python-subunit==1.4.2",
     421              "junitxml==0.7",
     422              "coverage ~= 5.0",
     423          ],
     424
     425          # Here are the library dependencies of the test suite.
    401426          "test": [
    402               # Pin a specific pyflakes so we don't have different folks
    403               # disagreeing on what is or is not a lint issue.  We can bump
    404               # this version from time to time, but we will do it
    405               # intentionally.
    406               "ruff==0.0.261",
    407               "coverage ~= 5.0",
    408427              "mock",
    409               "tox ~= 3.0",
    410428              "pytest",
    411429              "pytest-twisted",
     
    416434              "beautifulsoup4",
    417435              "html5lib",
    418               "junitxml",
    419436              # Pin old version until
    420437              # https://github.com/paramiko/paramiko/issues/1961 is fixed.
  • TabularUnified src/allmydata/scripts/common_http.py

    r2a4dcb7 r57b93b22  
    11"""
    2 Ported to Python 3.
     2Blocking HTTP client APIs.
    33"""
    4 from __future__ import unicode_literals
    5 from __future__ import absolute_import
    6 from __future__ import division
    7 from __future__ import print_function
    8 
    9 from future.utils import PY2
    10 if PY2:
    11     from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min  # noqa: F401
    124
    135import os
    146from io import BytesIO
    15 from six.moves import urllib, http_client
    16 import six
     7from http import client as http_client
     8import urllib
    179import allmydata # for __full_version__
    1810
     
    5244    if isinstance(body, bytes):
    5345        body = BytesIO(body)
    54     elif isinstance(body, six.text_type):
     46    elif isinstance(body, str):
    5547        raise TypeError("do_http body must be a bytestring, not unicode")
    5648    else:
     
    6254        assert body.read
    6355    scheme, host, port, path = parse_url(url)
     56
     57    # For testing purposes, allow setting a timeout on HTTP requests. If this
     58    # ever become a user-facing feature, this should probably be a CLI option?
     59    timeout = os.environ.get("__TAHOE_CLI_HTTP_TIMEOUT", None)
     60    if timeout is not None:
     61        timeout = float(timeout)
     62
    6463    if scheme == "http":
    65         c = http_client.HTTPConnection(host, port)
     64        c = http_client.HTTPConnection(host, port, timeout=timeout, blocksize=65536)
    6665    elif scheme == "https":
    67         c = http_client.HTTPSConnection(host, port)
     66        c = http_client.HTTPSConnection(host, port, timeout=timeout, blocksize=65536)
    6867    else:
    6968        raise ValueError("unknown scheme '%s', need http or https" % scheme)
     
    8685
    8786    while True:
    88         data = body.read(8192)
     87        data = body.read(65536)
    8988        if not data:
    9089            break
     
    9594
    9695def format_http_success(resp):
    97     # ensure_text() shouldn't be necessary when Python 2 is dropped.
    9896    return quote_output(
    99         "%s %s" % (resp.status, six.ensure_text(resp.reason)),
     97        "%s %s" % (resp.status, resp.reason),
    10098        quotemarks=False)
    10199
    102100def format_http_error(msg, resp):
    103     # ensure_text() shouldn't be necessary when Python 2 is dropped.
    104101    return quote_output(
    105         "%s: %s %s\n%s" % (msg, resp.status, six.ensure_text(resp.reason),
    106                            six.ensure_text(resp.read())),
     102        "%s: %s %s\n%r" % (msg, resp.status, resp.reason,
     103                           resp.read()),
    107104        quotemarks=False)
    108105
  • TabularUnified src/allmydata/storage/http_client.py

    r2a4dcb7 r57b93b22  
    66
    77from eliot import start_action, register_exception_extractor
    8 from typing import Union, Optional, Sequence, Mapping, BinaryIO
     8from typing import Union, Optional, Sequence, Mapping, BinaryIO, cast, TypedDict, Set
    99from base64 import b64encode
    1010from io import BytesIO
     
    2121from twisted.web import http
    2222from twisted.web.iweb import IPolicyForHTTPS, IResponse
    23 from twisted.internet.defer import inlineCallbacks, returnValue, fail, Deferred, succeed
     23from twisted.internet.defer import inlineCallbacks, Deferred, succeed
    2424from twisted.internet.interfaces import (
    2525    IOpenSSLClientConnectionCreator,
     
    448448        )
    449449
    450     def decode_cbor(self, response, schema: Schema):
     450    async def decode_cbor(self, response, schema: Schema) -> object:
    451451        """Given HTTP response, return decoded CBOR body."""
    452 
    453         def got_content(f: BinaryIO):
    454             data = f.read()
    455             schema.validate_cbor(data)
    456             return loads(data)
    457 
    458         if response.code > 199 and response.code < 300:
    459             content_type = get_content_type(response.headers)
    460             if content_type == CBOR_MIME_TYPE:
    461                 return limited_content(response, self._clock).addCallback(got_content)
     452        with start_action(action_type="allmydata:storage:http-client:decode-cbor"):
     453            if response.code > 199 and response.code < 300:
     454                content_type = get_content_type(response.headers)
     455                if content_type == CBOR_MIME_TYPE:
     456                    f = await limited_content(response, self._clock)
     457                    data = f.read()
     458                    schema.validate_cbor(data)
     459                    return loads(data)
     460                else:
     461                    raise ClientException(
     462                        -1,
     463                        "Server didn't send CBOR, content type is {}".format(
     464                            content_type
     465                        ),
     466                    )
    462467            else:
    463                 raise ClientException(-1, "Server didn't send CBOR")
    464         else:
    465             return treq.content(response).addCallback(
    466                 lambda data: fail(ClientException(response.code, response.phrase, data))
    467             )
     468                data = (
     469                    await limited_content(response, self._clock, max_length=10_000)
     470                ).read()
     471                raise ClientException(response.code, response.phrase, data)
    468472
    469473
     
    476480    _client: StorageClient
    477481
    478     @inlineCallbacks
    479     def get_version(self):
     482    @async_to_deferred
     483    async def get_version(self):
    480484        """
    481485        Return the version metadata for the server.
    482486        """
    483487        url = self._client.relative_url("/storage/v1/version")
    484         response = yield self._client.request("GET", url)
    485         decoded_response = yield self._client.decode_cbor(
    486             response, _SCHEMAS["get_version"]
     488        response = await self._client.request("GET", url)
     489        decoded_response = cast(
     490            Mapping[bytes, object],
     491            await self._client.decode_cbor(response, _SCHEMAS["get_version"]),
    487492        )
    488493        # Add some features we know are true because the HTTP API
    489494        # specification requires them and because other parts of the storage
    490495        # client implementation assumes they will be present.
    491         decoded_response[b"http://allmydata.org/tahoe/protocols/storage/v1"].update(
     496        cast(
     497            Mapping[bytes, object],
     498            decoded_response[b"http://allmydata.org/tahoe/protocols/storage/v1"],
     499        ).update(
    492500            {
    493501                b"tolerates-immutable-read-overrun": True,
     
    497505            }
    498506        )
    499         returnValue(decoded_response)
     507        return decoded_response
    500508
    501509    @inlineCallbacks
     
    648656    _client: StorageClient
    649657
    650     @inlineCallbacks
    651     def create(
     658    @async_to_deferred
     659    async def create(
    652660        self,
    653         storage_index,
    654         share_numbers,
    655         allocated_size,
    656         upload_secret,
    657         lease_renew_secret,
    658         lease_cancel_secret,
    659     ):  # type: (bytes, set[int], int, bytes, bytes, bytes) -> Deferred[ImmutableCreateResult]
     661        storage_index: bytes,
     662        share_numbers: set[int],
     663        allocated_size: int,
     664        upload_secret: bytes,
     665        lease_renew_secret: bytes,
     666        lease_cancel_secret: bytes,
     667    ) -> ImmutableCreateResult:
    660668        """
    661669        Create a new storage index for an immutable.
     
    676684        message = {"share-numbers": share_numbers, "allocated-size": allocated_size}
    677685
    678         response = yield self._client.request(
     686        response = await self._client.request(
    679687            "POST",
    680688            url,
     
    684692            message_to_serialize=message,
    685693        )
    686         decoded_response = yield self._client.decode_cbor(
    687             response, _SCHEMAS["allocate_buckets"]
    688         )
    689         returnValue(
    690             ImmutableCreateResult(
    691                 already_have=decoded_response["already-have"],
    692                 allocated=decoded_response["allocated"],
    693             )
     694        decoded_response = cast(
     695            Mapping[str, Set[int]],
     696            await self._client.decode_cbor(response, _SCHEMAS["allocate_buckets"]),
     697        )
     698        return ImmutableCreateResult(
     699            already_have=decoded_response["already-have"],
     700            allocated=decoded_response["allocated"],
    694701        )
    695702
     
    717724            )
    718725
    719     @inlineCallbacks
    720     def write_share_chunk(
    721         self, storage_index, share_number, upload_secret, offset, data
    722     ):  # type: (bytes, int, bytes, int, bytes) -> Deferred[UploadProgress]
     726    @async_to_deferred
     727    async def write_share_chunk(
     728        self,
     729        storage_index: bytes,
     730        share_number: int,
     731        upload_secret: bytes,
     732        offset: int,
     733        data: bytes,
     734    ) -> UploadProgress:
    723735        """
    724736        Upload a chunk of data for a specific share.
     
    738750            )
    739751        )
    740         response = yield self._client.request(
     752        response = await self._client.request(
    741753            "PATCH",
    742754            url,
     
    762774                response.code,
    763775            )
    764         body = yield self._client.decode_cbor(
    765             response, _SCHEMAS["immutable_write_share_chunk"]
     776        body = cast(
     777            Mapping[str, Sequence[Mapping[str, int]]],
     778            await self._client.decode_cbor(
     779                response, _SCHEMAS["immutable_write_share_chunk"]
     780            ),
    766781        )
    767782        remaining = RangeMap()
    768783        for chunk in body["required"]:
    769784            remaining.set(True, chunk["begin"], chunk["end"])
    770         returnValue(UploadProgress(finished=finished, required=remaining))
     785        return UploadProgress(finished=finished, required=remaining)
    771786
    772787    def read_share_chunk(
     
    780795        )
    781796
    782     @inlineCallbacks
    783     def list_shares(self, storage_index: bytes) -> Deferred[set[int]]:
     797    @async_to_deferred
     798    async def list_shares(self, storage_index: bytes) -> Set[int]:
    784799        """
    785800        Return the set of shares for a given storage index.
     
    788803            "/storage/v1/immutable/{}/shares".format(_encode_si(storage_index))
    789804        )
    790         response = yield self._client.request(
     805        response = await self._client.request(
    791806            "GET",
    792807            url,
    793808        )
    794809        if response.code == http.OK:
    795             body = yield self._client.decode_cbor(response, _SCHEMAS["list_shares"])
    796             returnValue(set(body))
     810            return cast(
     811                Set[int],
     812                await self._client.decode_cbor(response, _SCHEMAS["list_shares"]),
     813            )
    797814        else:
    798815            raise ClientException(response.code)
     
    862879    # ReadVectors:
    863880    reads: Mapping[int, Sequence[bytes]]
     881
     882
     883# Result type for mutable read/test/write HTTP response. Can't just use
     884# dict[int,list[bytes]] because on Python 3.8 that will error out.
     885MUTABLE_RTW = TypedDict(
     886    "MUTABLE_RTW", {"success": bool, "data": Mapping[int, Sequence[bytes]]}
     887)
    864888
    865889
     
    910934        )
    911935        if response.code == http.OK:
    912             result = await self._client.decode_cbor(
    913                 response, _SCHEMAS["mutable_read_test_write"]
     936            result = cast(
     937                MUTABLE_RTW,
     938                await self._client.decode_cbor(
     939                    response, _SCHEMAS["mutable_read_test_write"]
     940                ),
    914941            )
    915942            return ReadTestWriteResult(success=result["success"], reads=result["data"])
     
    932959
    933960    @async_to_deferred
    934     async def list_shares(self, storage_index: bytes) -> set[int]:
     961    async def list_shares(self, storage_index: bytes) -> Set[int]:
    935962        """
    936963        List the share numbers for a given storage index.
     
    941968        response = await self._client.request("GET", url)
    942969        if response.code == http.OK:
    943             return await self._client.decode_cbor(
    944                 response, _SCHEMAS["mutable_list_shares"]
     970            return cast(
     971                Set[int],
     972                await self._client.decode_cbor(
     973                    response, _SCHEMAS["mutable_list_shares"]
     974                ),
    945975            )
    946976        else:
  • TabularUnified src/allmydata/test/test_storage_http.py

    r2a4dcb7 r57b93b22  
    3535from twisted.internet.task import Clock, Cooperator
    3636from twisted.internet.interfaces import IReactorTime, IReactorFromThreads
    37 from twisted.internet.defer import CancelledError, Deferred
     37from twisted.internet.defer import CancelledError, Deferred, ensureDeferred
    3838from twisted.web import http
    3939from twisted.web.http_headers import Headers
     
    521521        infrastructure necessary to support asynchronous HTTP server endpoints.
    522522        """
     523        d = ensureDeferred(d)
    523524        result = []
    524525        error = []
  • TabularUnified tox.ini

    r2a4dcb7 r57b93b22  
    2424[testenv]
    2525passenv = TAHOE_LAFS_* PIP_* SUBUNITREPORTER_* USERPROFILE HOMEDRIVE HOMEPATH
    26 # Get "certifi" to avoid bug #2913. Basically if a `setup_requires=...` causes
    27 # a package to be installed (with setuptools) then it'll fail on certain
    28 # platforms (travis's OX-X 10.12, Slackware 14.2) because PyPI's TLS
    29 # requirements (TLS >= 1.2) are incompatible with the old TLS clients
    30 # available to those systems.  Installing it ahead of time (with pip) avoids
    31 # this problem.
    32 deps =
    33      # Pin all of these versions for the same reason you ever want to pin
    34      # anything: to prevent new releases with regressions from introducing
    35      # spurious failures into CI runs for whatever development work is
    36      # happening at the time.  The versions selected here are just the current
    37      # versions at the time.  Bumping them to keep up with future releases is
    38      # fine as long as those releases are known to actually work.
    39      pip==22.0.3
    40      setuptools==60.9.1
    41      wheel==0.37.1
    42      subunitreporter==22.2.0
    43      # As an exception, we don't pin certifi because it contains CA
    44      # certificates which necessarily change over time.  Pinning this is
    45      # guaranteed to cause things to break eventually as old certificates
    46      # expire and as new ones are used in the wild that aren't present in
    47      # whatever version we pin.  Hopefully there won't be functionality
    48      # regressions in new releases of this package that cause us the kind of
    49      # suffering we're trying to avoid with the above pins.
    50      certifi
     26deps =
     27    # We pull in certify *here* to avoid bug #2913. Basically if a
     28    # `setup_requires=...` causes a package to be installed (with setuptools)
     29    # then it'll fail on certain platforms (travis's OX-X 10.12, Slackware
     30    # 14.2) because PyPI's TLS requirements (TLS >= 1.2) are incompatible with
     31    # the old TLS clients available to those systems.  Installing it ahead of
     32    # time (with pip) avoids this problem.
     33    #
     34    # We don't pin an exact version of it because it contains CA certificates
     35    # which necessarily change over time.  Pinning this is guaranteed to cause
     36    # things to break eventually as old certificates expire and as new ones
     37    # are used in the wild that aren't present in whatever version we pin.
     38    # Hopefully there won't be functionality regressions in new releases of
     39    # this package that cause us the kind of suffering we're trying to avoid
     40    # with the above pins.
     41    certifi
    5142
    5243# We add usedevelop=False because testing against a true installation gives
    5344# more useful results.
    5445usedevelop = False
    55 # We use extras=test to get things like "mock" that are required for our unit
    56 # tests.
    57 extras = test
     46
     47extras =
     48       # Get general testing environment dependencies so we can run the tests
     49       # how we like.
     50       testenv
     51
     52       # And get all of the test suite's actual direct Python dependencies.
     53       test
    5854
    5955setenv =
     
    10298skip_install = true
    10399deps =
    104      ruff
     100     # Pin a specific version so we get consistent outcomes; update this
     101     # occasionally:
     102     ruff == 0.0.263
    105103     towncrier
    106104# On macOS, git inside of towncrier needs $HOME.
Note: See TracChangeset for help on using the changeset viewer.