Changeset 57b93b22 in trunk
- Timestamp:
- 2023-05-01T16:54:51Z (2 years ago)
- 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. - Files:
-
- 4 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
TabularUnified .circleci/create-virtualenv.sh ¶
r2a4dcb7 r57b93b22 48 48 # explicitly ask for one. 49 49 "${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 3 3 # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ 4 4 set -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* packages11 # 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"17 5 18 6 # The filesystem location of the wheelhouse which we'll populate with wheels … … 42 30 wheel \ 43 31 --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 80 80 fi 81 81 82 WORKDIR=/tmp/tahoe-lafs.tox 82 83 ${TIMEOUT} ${BOOTSTRAP_VENV}/bin/tox \ 83 84 -c ${PROJECT_ROOT}/tox.ini \ 84 --workdir /tmp/tahoe-lafs.tox\85 --workdir "${WORKDIR}" \ 85 86 -e "${TAHOE_LAFS_TOX_ENVIRONMENT}" \ 86 87 ${TAHOE_LAFS_TOX_ARGS} || "${alternative}" … … 94 95 # Create a junitxml results area. 95 96 mkdir -p "$(dirname "${JUNITXML}")" 96 "${BOOTSTRAP_VENV}"/bin/subunit2junitxml < "${SUBUNIT2}" > "${JUNITXML}" || "${alternative}" 97 98 "${WORKDIR}/${TAHOE_LAFS_TOX_ENVIRONMENT}/bin/subunit2junitxml" < "${SUBUNIT2}" > "${JUNITXML}" || "${alternative}" 97 99 fi -
TabularUnified .circleci/setup-virtualenv.sh ¶
r2a4dcb7 r57b93b22 27 27 # Tell pip where it can find any existing wheels. 28 28 export 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. 29 export PIP_NO_INDEX="1" 35 30 36 31 # Get everything else installed in it, too. -
TabularUnified integration/conftest.py ¶
r2a4dcb7 r57b93b22 5 5 from __future__ import annotations 6 6 7 import os 7 8 import sys 8 9 import shutil … … 48 49 block_with_timeout, 49 50 ) 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. 55 os.environ["__TAHOE_CLI_HTTP_TIMEOUT"] = "120" 50 56 51 57 -
TabularUnified setup.py ¶
r2a4dcb7 r57b93b22 142 142 # HTTP server and client 143 143 "klein", 144 144 145 # 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", 146 148 "treq", 147 149 "cbor2", … … 399 401 "gpg", 400 402 ], 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. 401 426 "test": [ 402 # Pin a specific pyflakes so we don't have different folks403 # disagreeing on what is or is not a lint issue. We can bump404 # this version from time to time, but we will do it405 # intentionally.406 "ruff==0.0.261",407 "coverage ~= 5.0",408 427 "mock", 409 "tox ~= 3.0",410 428 "pytest", 411 429 "pytest-twisted", … … 416 434 "beautifulsoup4", 417 435 "html5lib", 418 "junitxml",419 436 # Pin old version until 420 437 # https://github.com/paramiko/paramiko/issues/1961 is fixed. -
TabularUnified src/allmydata/scripts/common_http.py ¶
r2a4dcb7 r57b93b22 1 1 """ 2 Ported to Python 3.2 Blocking HTTP client APIs. 3 3 """ 4 from __future__ import unicode_literals5 from __future__ import absolute_import6 from __future__ import division7 from __future__ import print_function8 9 from future.utils import PY210 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: F40112 4 13 5 import os 14 6 from io import BytesIO 15 from six.moves import urllib,http_client16 import six7 from http import client as http_client 8 import urllib 17 9 import allmydata # for __full_version__ 18 10 … … 52 44 if isinstance(body, bytes): 53 45 body = BytesIO(body) 54 elif isinstance(body, s ix.text_type):46 elif isinstance(body, str): 55 47 raise TypeError("do_http body must be a bytestring, not unicode") 56 48 else: … … 62 54 assert body.read 63 55 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 64 63 if scheme == "http": 65 c = http_client.HTTPConnection(host, port )64 c = http_client.HTTPConnection(host, port, timeout=timeout, blocksize=65536) 66 65 elif scheme == "https": 67 c = http_client.HTTPSConnection(host, port )66 c = http_client.HTTPSConnection(host, port, timeout=timeout, blocksize=65536) 68 67 else: 69 68 raise ValueError("unknown scheme '%s', need http or https" % scheme) … … 86 85 87 86 while True: 88 data = body.read( 8192)87 data = body.read(65536) 89 88 if not data: 90 89 break … … 95 94 96 95 def format_http_success(resp): 97 # ensure_text() shouldn't be necessary when Python 2 is dropped.98 96 return quote_output( 99 "%s %s" % (resp.status, six.ensure_text(resp.reason)),97 "%s %s" % (resp.status, resp.reason), 100 98 quotemarks=False) 101 99 102 100 def format_http_error(msg, resp): 103 # ensure_text() shouldn't be necessary when Python 2 is dropped.104 101 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()), 107 104 quotemarks=False) 108 105 -
TabularUnified src/allmydata/storage/http_client.py ¶
r2a4dcb7 r57b93b22 6 6 7 7 from eliot import start_action, register_exception_extractor 8 from typing import Union, Optional, Sequence, Mapping, BinaryIO 8 from typing import Union, Optional, Sequence, Mapping, BinaryIO, cast, TypedDict, Set 9 9 from base64 import b64encode 10 10 from io import BytesIO … … 21 21 from twisted.web import http 22 22 from twisted.web.iweb import IPolicyForHTTPS, IResponse 23 from twisted.internet.defer import inlineCallbacks, returnValue, fail,Deferred, succeed23 from twisted.internet.defer import inlineCallbacks, Deferred, succeed 24 24 from twisted.internet.interfaces import ( 25 25 IOpenSSLClientConnectionCreator, … … 448 448 ) 449 449 450 def decode_cbor(self, response, schema: Schema):450 async def decode_cbor(self, response, schema: Schema) -> object: 451 451 """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 ) 462 467 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) 468 472 469 473 … … 476 480 _client: StorageClient 477 481 478 @ inlineCallbacks479 def get_version(self):482 @async_to_deferred 483 async def get_version(self): 480 484 """ 481 485 Return the version metadata for the server. 482 486 """ 483 487 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"]), 487 492 ) 488 493 # Add some features we know are true because the HTTP API 489 494 # specification requires them and because other parts of the storage 490 495 # 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( 492 500 { 493 501 b"tolerates-immutable-read-overrun": True, … … 497 505 } 498 506 ) 499 return Value(decoded_response)507 return decoded_response 500 508 501 509 @inlineCallbacks … … 648 656 _client: StorageClient 649 657 650 @ inlineCallbacks651 def create(658 @async_to_deferred 659 async def create( 652 660 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: 660 668 """ 661 669 Create a new storage index for an immutable. … … 676 684 message = {"share-numbers": share_numbers, "allocated-size": allocated_size} 677 685 678 response = yieldself._client.request(686 response = await self._client.request( 679 687 "POST", 680 688 url, … … 684 692 message_to_serialize=message, 685 693 ) 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"], 694 701 ) 695 702 … … 717 724 ) 718 725 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: 723 735 """ 724 736 Upload a chunk of data for a specific share. … … 738 750 ) 739 751 ) 740 response = yieldself._client.request(752 response = await self._client.request( 741 753 "PATCH", 742 754 url, … … 762 774 response.code, 763 775 ) 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 ), 766 781 ) 767 782 remaining = RangeMap() 768 783 for chunk in body["required"]: 769 784 remaining.set(True, chunk["begin"], chunk["end"]) 770 return Value(UploadProgress(finished=finished, required=remaining))785 return UploadProgress(finished=finished, required=remaining) 771 786 772 787 def read_share_chunk( … … 780 795 ) 781 796 782 @ inlineCallbacks783 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]: 784 799 """ 785 800 Return the set of shares for a given storage index. … … 788 803 "/storage/v1/immutable/{}/shares".format(_encode_si(storage_index)) 789 804 ) 790 response = yieldself._client.request(805 response = await self._client.request( 791 806 "GET", 792 807 url, 793 808 ) 794 809 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 ) 797 814 else: 798 815 raise ClientException(response.code) … … 862 879 # ReadVectors: 863 880 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. 885 MUTABLE_RTW = TypedDict( 886 "MUTABLE_RTW", {"success": bool, "data": Mapping[int, Sequence[bytes]]} 887 ) 864 888 865 889 … … 910 934 ) 911 935 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 ), 914 941 ) 915 942 return ReadTestWriteResult(success=result["success"], reads=result["data"]) … … 932 959 933 960 @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]: 935 962 """ 936 963 List the share numbers for a given storage index. … … 941 968 response = await self._client.request("GET", url) 942 969 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 ), 945 975 ) 946 976 else: -
TabularUnified src/allmydata/test/test_storage_http.py ¶
r2a4dcb7 r57b93b22 35 35 from twisted.internet.task import Clock, Cooperator 36 36 from twisted.internet.interfaces import IReactorTime, IReactorFromThreads 37 from twisted.internet.defer import CancelledError, Deferred 37 from twisted.internet.defer import CancelledError, Deferred, ensureDeferred 38 38 from twisted.web import http 39 39 from twisted.web.http_headers import Headers … … 521 521 infrastructure necessary to support asynchronous HTTP server endpoints. 522 522 """ 523 d = ensureDeferred(d) 523 524 result = [] 524 525 error = [] -
TabularUnified tox.ini ¶
r2a4dcb7 r57b93b22 24 24 [testenv] 25 25 passenv = 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 26 deps = 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 51 42 52 43 # We add usedevelop=False because testing against a true installation gives 53 44 # more useful results. 54 45 usedevelop = False 55 # We use extras=test to get things like "mock" that are required for our unit 56 # tests. 57 extras = test 46 47 extras = 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 58 54 59 55 setenv = … … 102 98 skip_install = true 103 99 deps = 104 ruff 100 # Pin a specific version so we get consistent outcomes; update this 101 # occasionally: 102 ruff == 0.0.263 105 103 towncrier 106 104 # On macOS, git inside of towncrier needs $HOME.
Note: See TracChangeset
for help on using the changeset viewer.