mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-22 18:22:40 +00:00
Merge remote-tracking branch 'origin/master' into 3880-http-storage-logging
This commit is contained in:
commit
c9e36411ac
@ -18,15 +18,11 @@ RUN apt-get --quiet update && \
|
|||||||
libffi-dev \
|
libffi-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libyaml-dev \
|
libyaml-dev \
|
||||||
virtualenv
|
virtualenv \
|
||||||
|
tor
|
||||||
|
|
||||||
# Get the project source. This is better than it seems. CircleCI will
|
# Get the project source. This is better than it seems. CircleCI will
|
||||||
# *update* this checkout on each job run, saving us more time per-job.
|
# *update* this checkout on each job run, saving us more time per-job.
|
||||||
COPY . ${BUILD_SRC_ROOT}
|
COPY . ${BUILD_SRC_ROOT}
|
||||||
|
|
||||||
RUN "${BUILD_SRC_ROOT}"/.circleci/prepare-image.sh "${WHEELHOUSE_PATH}" "${VIRTUALENV_PATH}" "${BUILD_SRC_ROOT}" "python${PYTHON_VERSION}"
|
RUN "${BUILD_SRC_ROOT}"/.circleci/prepare-image.sh "${WHEELHOUSE_PATH}" "${VIRTUALENV_PATH}" "${BUILD_SRC_ROOT}" "python${PYTHON_VERSION}"
|
||||||
|
|
||||||
# Only the integration tests currently need this but it doesn't hurt to always
|
|
||||||
# have it present and it's simpler than building a whole extra image just for
|
|
||||||
# the integration tests.
|
|
||||||
RUN ${BUILD_SRC_ROOT}/integration/install-tor.sh
|
|
||||||
|
@ -18,8 +18,7 @@ workflows:
|
|||||||
- "debian-10":
|
- "debian-10":
|
||||||
{}
|
{}
|
||||||
- "debian-11":
|
- "debian-11":
|
||||||
requires:
|
{}
|
||||||
- "debian-10"
|
|
||||||
|
|
||||||
- "ubuntu-20-04":
|
- "ubuntu-20-04":
|
||||||
{}
|
{}
|
||||||
@ -58,7 +57,7 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
# If the unit test suite doesn't pass, don't bother running the
|
# If the unit test suite doesn't pass, don't bother running the
|
||||||
# integration tests.
|
# integration tests.
|
||||||
- "debian-10"
|
- "debian-11"
|
||||||
|
|
||||||
- "typechecks":
|
- "typechecks":
|
||||||
{}
|
{}
|
||||||
@ -297,6 +296,10 @@ jobs:
|
|||||||
|
|
||||||
integration:
|
integration:
|
||||||
<<: *DEBIAN
|
<<: *DEBIAN
|
||||||
|
docker:
|
||||||
|
- <<: *DOCKERHUB_AUTH
|
||||||
|
image: "tahoelafsci/debian:11-py3.9"
|
||||||
|
user: "nobody"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
<<: *UTF_8_ENVIRONMENT
|
<<: *UTF_8_ENVIRONMENT
|
||||||
|
@ -63,7 +63,7 @@ release = u'1.x'
|
|||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = None
|
language = "en"
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
|
@ -350,8 +350,10 @@ Because of the simple types used throughout
|
|||||||
and the equivalence described in `RFC 7049`_
|
and the equivalence described in `RFC 7049`_
|
||||||
these examples should be representative regardless of which of these two encodings is chosen.
|
these examples should be representative regardless of which of these two encodings is chosen.
|
||||||
|
|
||||||
|
The one exception is sets.
|
||||||
For CBOR messages, any sequence that is semantically a set (i.e. no repeated values allowed, order doesn't matter, and elements are hashable in Python) should be sent as a set.
|
For CBOR messages, any sequence that is semantically a set (i.e. no repeated values allowed, order doesn't matter, and elements are hashable in Python) should be sent as a set.
|
||||||
Tag 6.258 is used to indicate sets in CBOR; see `the CBOR registry <https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml>`_ for more details.
|
Tag 6.258 is used to indicate sets in CBOR; see `the CBOR registry <https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml>`_ for more details.
|
||||||
|
Sets will be represented as JSON lists in examples because JSON doesn't support sets.
|
||||||
|
|
||||||
HTTP Design
|
HTTP Design
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
@ -652,6 +654,11 @@ The ``Range`` header may be used to request exactly one ``bytes`` range, in whic
|
|||||||
Interpretation and response behavior is as specified in RFC 7233 § 4.1.
|
Interpretation and response behavior is as specified in RFC 7233 § 4.1.
|
||||||
Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported.
|
Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported.
|
||||||
|
|
||||||
|
If the response reads beyond the end of the data, the response may be shorter than the requested range.
|
||||||
|
The resulting ``Content-Range`` header will be consistent with the returned data.
|
||||||
|
|
||||||
|
If the response to a query is an empty range, the ``NO CONTENT`` (204) response code will be used.
|
||||||
|
|
||||||
Discussion
|
Discussion
|
||||||
``````````
|
``````````
|
||||||
|
|
||||||
@ -738,8 +745,8 @@ Reading
|
|||||||
``GET /v1/mutable/:storage_index/shares``
|
``GET /v1/mutable/:storage_index/shares``
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
Retrieve a list indicating all shares available for the indicated storage index.
|
Retrieve a set indicating all shares available for the indicated storage index.
|
||||||
For example::
|
For example (this is shown as list, since it will be list for JSON, but will be set for CBOR)::
|
||||||
|
|
||||||
[1, 5]
|
[1, 5]
|
||||||
|
|
||||||
@ -752,6 +759,11 @@ The ``Range`` header may be used to request exactly one ``bytes`` range, in whic
|
|||||||
Interpretation and response behavior is as specified in RFC 7233 § 4.1.
|
Interpretation and response behavior is as specified in RFC 7233 § 4.1.
|
||||||
Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported.
|
Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported.
|
||||||
|
|
||||||
|
If the response reads beyond the end of the data, the response may be shorter than the requested range.
|
||||||
|
The resulting ``Content-Range`` header will be consistent with the returned data.
|
||||||
|
|
||||||
|
If the response to a query is an empty range, the ``NO CONTENT`` (204) response code will be used.
|
||||||
|
|
||||||
|
|
||||||
``POST /v1/mutable/:storage_index/:share_number/corrupt``
|
``POST /v1/mutable/:storage_index/:share_number/corrupt``
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
@ -1,794 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
CODENAME=$(lsb_release --short --codename)
|
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ]; then
|
|
||||||
SUDO="sudo"
|
|
||||||
else
|
|
||||||
SUDO=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Script to install Tor
|
|
||||||
echo "deb http://deb.torproject.org/torproject.org ${CODENAME} main" | ${SUDO} tee -a /etc/apt/sources.list
|
|
||||||
echo "deb-src http://deb.torproject.org/torproject.org ${CODENAME} main" | ${SUDO} tee -a /etc/apt/sources.list
|
|
||||||
|
|
||||||
# # Install Tor repo signing key
|
|
||||||
${SUDO} apt-key add - <<EOF
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mQENBEqg7GsBCACsef8koRT8UyZxiv1Irke5nVpte54TDtTl1za1tOKfthmHbs2I
|
|
||||||
4DHWG3qrwGayw+6yb5mMFe0h9Ap9IbilA5a1IdRsdDgViyQQ3kvdfoavFHRxvGON
|
|
||||||
tknIyk5Goa36GMBl84gQceRs/4Zx3kxqCV+JYXE9CmdkpkVrh2K3j5+ysDWfD/kO
|
|
||||||
dTzwu3WHaAwL8d5MJAGQn2i6bTw4UHytrYemS1DdG/0EThCCyAnPmmb8iBkZlSW8
|
|
||||||
6MzVqTrN37yvYWTXk6MwKH50twaX5hzZAlSh9eqRjZLq51DDomO7EumXP90rS5mT
|
|
||||||
QrS+wiYfGQttoZfbh3wl5ZjejgEjx+qrnOH7ABEBAAG0JmRlYi50b3Jwcm9qZWN0
|
|
||||||
Lm9yZyBhcmNoaXZlIHNpZ25pbmcga2V5iQE8BBMBAgAmBQJKoOxrAhsDBQkJZgGA
|
|
||||||
BgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ7oy8noht3YmVUAgApMyyFaBxvie1
|
|
||||||
/jAMoQ3uZLjnrP/SWK9Sv9TIiiJxig4PLSNn+dlu1EZicFoZaGx+wLMhOOuCoLKA
|
|
||||||
Vfo3RSF2WgvBePkxqN03hILPAVuT2kus+7f7y926lkRy2mF+eWVd5CZDoHERABFt
|
|
||||||
gX0Zf24TBz90Cza1tu+1OWiYgD7zi24AIlFwcU4Up9+ejZWGSG4J3yOZj5xkEAxg
|
|
||||||
5RDKfkbsRVV+ZnqaxcDqe+Gpu4BFEiNv1r/OyZIA8FbWEjn0rnXDA4ynOsown9pa
|
|
||||||
QE0NrMIHrh6fR9+CUyeFzn+xFhPaNho7k8GAzC02WctTGX5lZRBaLt7MDC1i6eaj
|
|
||||||
VcC1eXgtPYhMBBMRAgAMBQJKoO50BYMJZf93AAoJEN56r26UwJx/hiQAoMT5EmxK
|
|
||||||
flkAi2UywT99PuQGp3ckAJ4jJubPJNnHFeCNZ6/TtKmHoziU4okBPAQTAQIAJgIb
|
|
||||||
AwYLCQgHAwIEFQIIAwQWAgMBAh4BAheABQJQPjNuBQkNIhUAAAoJEO6MvJ6Ibd2J
|
|
||||||
GbAH/2fjtebQ7xsC8zUTnjIk8jmeH8kNZcp1KTkt31CZd6jN9KFj5dbSuaXQGYMJ
|
|
||||||
Xi9AqPHdux79eM6QjsMCN4bYJe3bA/CEueuL9bBxsfl9any8yJ8BcSJVcc61W4VD
|
|
||||||
Xi0iogSeqsHGagCHqXkti7/pd5RCzr42x0OG8eQ6qFWZ9LlKpLIdz5MjfQ7uJWdl
|
|
||||||
hok5taSFg8WPJCSIMaQxRC93uYv3CEMusLH3hNjcNk9KqMZ/rFkr8AVIo7X6tCuN
|
|
||||||
cOI6RLJ5o4mUNJflU8HKBpRf6ELhJAFfhV0Ai8Numtmj1F4s7bZTyDSfCYjc5evI
|
|
||||||
/BWjJ6pGhQMyX32zPA9VDmVXZp2IRgQQEQIABgUCSqqiMgAKCRDrWolqKJiL9aY6
|
|
||||||
AJ9PJ/c0nvAdMFyTAB4TgxK3lm1dWwCfRcOrw9ZaeTicrpOV6+or9WhYi0WIRgQQ
|
|
||||||
EQIABgUCSqxgNQAKCRA7nQk/MbCXS+gnAJwJKiSIlI1j7IivecE838smV1vF6QCb
|
|
||||||
B9TrQZ5pYXDPuGrBUUvbfF5OnKeIRgQQEQIABgUCS32d2AAKCRBiFZZPWxcqsjlM
|
|
||||||
AJ9wE9uxo8DUBRVVdc+/Qp5YViBVogCgyvePB3U1hUPpN7cP7ImEbPMIPo+IRgQQ
|
|
||||||
EQIABgUCS36WLwAKCRBOUwAZoaG8BTgXAJ9fcfgaCb/HTIgC+a3gJbwA/0XkPwCg
|
|
||||||
pqm7BuOwadxPdR00WIeaKcBqrW2IRgQQEQIABgUCTLqaOwAKCRCF9yYxJ6HImkv6
|
|
||||||
AKCDGzgLmj47OeTtaYWs9DeVud8MogCeLinbRpG7DHpBYYfyGiWPNNKabkWIXgQQ
|
|
||||||
EQgABgUCTMEPxgAKCRBrN4EsW1TWjEMIAP4pRDudJEmpk5jQIjqcAPu1qT0rsmWT
|
|
||||||
Q5ElxPeLpkPIPwD/fdoFfMzDSSdNN0noO595BgFMwr4I1cz6GSsd8GCA8NeIXgQQ
|
|
||||||
EQgABgUCTgyF3gAKCRCDojkL/aKKGg2vAQDM6swxNsKGjw6wb+0PGCeXBj3H+QEi
|
|
||||||
oJ8J0outkIyT1QD+L5gYFAIeDUxpnNmt9tJ6gTv+rJk5gNjOrvz7QTXpYtmInAQQ
|
|
||||||
AQIABgUCTNR85QAKCRDjsV6KbxD8QmnaA/90V7ITTZGfdbvbe7/usuyzr26e59gt
|
|
||||||
HmsRdSxJn7zG3vng+tMjjDTapwY4vTk/5s7BshlGFT2Vw1kl61VhC0vf+wFUAgGh
|
|
||||||
lV7cH7DQyJNaBFdxJ0nz0XJ+gbKjDN2gA7tK5VbAD8j8M/sJG6m8cLmFml59+v+e
|
|
||||||
Yo4VA/Xfl5qRYIkBHAQQAQIABgUCTJFqpgAKCRBjkJvie11mayZaCADGDPzdfisD
|
|
||||||
nVPK68hnJ7vx1uCgdkMKyAJmNXca0twIiYl1oKmZ961h1Y5qUJOj02AjtxKgeI+b
|
|
||||||
1hRwGAxQ4uS8bHtYj6Pn/mXK0Q3G1lw0Q6M+q4mDdj3zLAeHR/WoyCQTFWX2gmgd
|
|
||||||
46C0XQkqpfY9mmfPZxpKoilMxlhX4z6TxxYRiwbxZOB/jwhZMCNMoXx5SYDC3Aco
|
|
||||||
RqXWd3wCwwy5lsv8XC33cQd/c+XbJIC4hzu4lTj3ndDlptpJp9SPNSUiNe8YD0sw
|
|
||||||
SITX+R1uzO4l6LLavw02j/MAhfVi3dEpf8lt4ZKaRVUHB/XPTsUmxYx7jOtDyr+w
|
|
||||||
/lPnZFMhAECDiQEcBBMBAgAGBQJM4UTLAAoJEE7GByMpYG5327oIAMDOuVYbMiL9
|
|
||||||
anx0+sRuEEQZbY1otCoTCIf8rDEBAw0RBPYuXOfcMkHWNPzfoohW6qAjeEK831AS
|
|
||||||
PVg3cta5Ctmn/mM2ehO3Y+XCEtenTZJP8ZtHg3pZEt4PtQaOBtrWxqX1h633KEIa
|
|
||||||
0a7dASaU4KOZg/SyKoChcSr2pY+jtzDacsZ8q/et+zz2gktdvcDSkJurkPjlORx9
|
|
||||||
CcWFhOd7PFP4ZWn0A0AkufMpbLXhlVJCmSykyyG0Don3C9i7sG045303KNy6CA+l
|
|
||||||
jvcm/EBeeMWvLMdjr51XmkGFjaAs4Lyw0CfKj9uNZdriOtSVtH2kcMmNSvcUln2B
|
|
||||||
FZTBo2NeRKGJAhwEEAECAAYFAktpE+EACgkQxel8K2OfamZhpg/+P9NPk88rqRnE
|
|
||||||
uDVDHodlkA5hG0d0Yi5vkV9rw07yjYut474aUd3FjJFqNEoiW+6dFbNy6YqqYPhr
|
|
||||||
XLtnfJl5LAUJUzMA2aSLtbuX+cq18DCv5ZmU4DW6kZOWi5vX7QkQCTTLP03VlcD3
|
|
||||||
Gu6HyofseBMgE4zoEXdmZSZmPnOygakFLzC9w+D1XfK2gcaTKjAJJdW80aY56eUe
|
|
||||||
zFDKLhOw+YzIK1/ZeeOTS4LeITtTq5J6/hnwHrJdjApX80v2WJzVVoy7lQbxAPsl
|
|
||||||
JHZdYVFCBy2Tyk7kYdddVxYCcdYr0e8A+GfG/tQJGxvZ3O4nOrezSv0XmlhLZ5rj
|
|
||||||
Cn8M6fg/NKUXsPtXiac+DQJbr5RwQ5Sc7bnPVsCywqetOeA+xv3L2wi94rg4u97Q
|
|
||||||
iwqhDW0SE9zZuQL5vaXl/GFpaRXs+mVGATS9h+0lDBQPi21oPkdN/BKKzr//2GCl
|
|
||||||
5VFb+rkOY65HthCuiIrT8jFGArJIF4nXku/4BPpNrganC89iTsd5+UUNFIlta+WY
|
|
||||||
kENQ9tC2mwj96BaK0KyRQZP9AAzTo5wG8aouczptpwSH0aECJNy8kd/UR8IAkZkx
|
|
||||||
jY4+zyfQDlb4aNDsVGvempgjFcNo0rciKrPQl5GyRLQj2azuv46gaGcYzqsobejS
|
|
||||||
/2jqJLMnkTeExaCryrWuXo/raWBWQLOJAhwEEAECAAYFAkybgq4ACgkQ2HRyfjOa
|
|
||||||
f6huKQ//Yfey5BJXqZqIt9i6tyw2VqzMtZ1gAqFdEKeuSmz30xty9g6KknIjpeZo
|
|
||||||
+POb3rQFUKGZ/q4AjWKdD9C5WUvLcXd0RCWeDG7dmD78h35OWwqhc+8FXO1vU0nG
|
|
||||||
yFdEx89cNiO42M/z+eYeoysgVL3ixbCjJlrN4MHrilqshxH5MvG7JfIfoPwucQyt
|
|
||||||
NcwSa8T9kTlmC9uSl1rwEllKlDNabxMpsf+9T0kZtI+KQrvMBg8A4RRJhpP13Bt6
|
|
||||||
y949FbR4zva7kqV24h+5c/bKsgY4PXXM+AnIuXy+Dq1aRVgRLhWypJqc73UnpD/M
|
|
||||||
DDOPKX8nkF3F0mjcfEso6KtvNsniPCr5GKcnvoGu38qlQ7ILm2Pv0tjBHNIYQNG9
|
|
||||||
xPn2TMH74D6f88NahHj33Ha7PG8Jn/dZMuKg7qEeHit7+lJDn18cTT8xIMMUpl9A
|
|
||||||
pmjLuWwo5eTXysai7PQQU/ezEbOgYqznBKEFK+CXH6KINnGH13d/r9L71AZj/KZs
|
|
||||||
I+c7E0imLwUStvJEZr2M9nR+ybA4SN6/kwcF5n2kx+lBJjqBn72hb0wyaXXtTYFG
|
|
||||||
deruYIGsxEx8imbIBDtX6rWOMIrZAHlPBS5NTj4Hye14XcChR/AodmXrgJD/z+8+
|
|
||||||
sDGGZpHAc291wknHO++j22vF47Q2VSt8T+WM6Tx8vq0+Wsnui/iJARwEEAECAAYF
|
|
||||||
Ak6DrGQACgkQ/YT8uPW0MEdizQf+LRGpkyYcVnEXiFUUuJiMZlWSoTeFsFlTLdBV
|
|
||||||
jxAlcTanW5PUZ1O+fzxhSTjtAgEZm1UJUv3RaJxGlMeOVV+1o6F7xzsaTOFajjAK
|
|
||||||
DwrfP9WdvRyiC5IrvdfuJB6THCkgu5l0yoMxANyBXi9lEPHFPllOk6sTjfEk9LlJ
|
|
||||||
Tn1Quy3c5qb9GJgiSbA+7sS6AO7woE52TxdAJjxB+PM1dt/FZGG4hjeH3WmjUtfa
|
|
||||||
hm1UlBtWLEVleOz4EFXwTQErNpHfBaReJecOfJZ/30OGEJNWkNkmrg+ed1uLsE+K
|
|
||||||
2DxEHTFCZd83OPQGHpi+qYcv9SDDMYxzzdlynkOn5DoR0z87N4kBnAQRAQoABgUC
|
|
||||||
TqmiPwAKCRCg8hPxRutYH4lKC/9YYwjHjABrogdB2sb49JIiM2Dqe+G++GizVTZs
|
|
||||||
mV26PJXWQLKr2zKZDMLk3l/b9YLVkuFeG2K035HPFCtpWIlxkxpbarI5i9F0NjMm
|
|
||||||
gaIyqvh14xNhDS6NHgioDdNKvdNI5LYtWXGREjYJVCBIwdxWZHi5JsQgV2E0vfIZ
|
|
||||||
GDKWFfMIF2xrt6x0uvhWZnD94ecU0Dd8sFz7TKJoCdzfdYpoj5ROenLGJ7OcDMUL
|
|
||||||
knSA4NEVIEY0BVyQCb3TCjfboCRxRdXs+6yz4YEqTCzPNvQqIKKO6MA/X3ytmUok
|
|
||||||
RZIVmU8es4iZxYUXrHKeMzrvYVpbwwHwpziGwBr+SOkrS5iv5c1V1Nb+pSajtzAm
|
|
||||||
4tQnNoyjvB2YsEOvTLUNgaScY5O7Xu/FGhI6E9Y8KbD7nb2t9XdtEFgHiq1ST15t
|
|
||||||
iew6YNCatVA/GW3r97ediBjqAX35hqFSZ05yaNDlCgfKxrRiv2SHu+hutAX7cVLT
|
|
||||||
Aetm2mrJBb0ip7hQKrmUOpziT7iIXgQQEQoABgUCUVVRWQAKCRCHWDJ6EJ8lkdti
|
|
||||||
AQCDqrwsq6QrE1puqjai8cGvIUdY5UWiBVj6IjrTmvAdlAD/WEqresRrwQdoPJ6x
|
|
||||||
4VKJyJByQPCuJvlfl6nzpnBg2LyJARwEEAECAAYFAlEuf78ACgkQdxZ3RMno5CjA
|
|
||||||
8Qf+LM8nZhjvJyGdngan05EKqwc5HAppi34pctNpSreJvNxSBXQ4vydVckvdAJNI
|
|
||||||
ttGeWjVDr6Z61w6+h9rMoUwZkKMLU5wii5qJkvwGtPw5JZVe6ecEKJrr/p9tkMjI
|
|
||||||
jTHeneYrm+zGJAx/F8eCy+CzWwGacLw1w68IHHH6zsJZRhyNlSBc9ZJANRzXRPWc
|
|
||||||
0tzHfT7HtiN2dQK2OlFLRr+4t9KLFae0MsNRr4M6nBtOX+CBP4OdKTbeASyXnK8G
|
|
||||||
bpnpEjn0b4isr6eoMcJbNwVBX4XnI5RG/Ugur4es9ktOQkUFxy8Zpp8/vk/+hyWH
|
|
||||||
unr1G2ema2dak8zHIa7G2T8Bb4kCGwQQAQIABgUCUVSNVAAKCRB+fTNcWi1ewX4x
|
|
||||||
D/d0R2OHFLo42KJPsIc9Wz3AMO7mfpbCmSXcxoM+Cyd9/GT2qgAt9hgItv3iqg9d
|
|
||||||
j+AbjPNUKfpGG4Q4D/x/tb018C3F4U1PLC/PQ2lYX0csvuv3Gp5MuNpCuHS5bW4k
|
|
||||||
LyOpRZh1JrqniL8K1Mp8cdBhMf6H+ZckQuXShGHwOhGyBMu3X7biXikSvdgQmbDQ
|
|
||||||
MtaDbxuYZ+JGXF0uacPVnlAUwW1F55IIhmUHIV7t+poYo/8M0HJ/lB9y5auamrJT
|
|
||||||
4acsPWS+fYHAjfGfpSE7T7QWuiIKJ2EmpVa5hpGhzII9ahF0wtHTKkF7d7RYV1p1
|
|
||||||
UUA5nu8QFTope8fyERJDZg88ICt+TpXJ7+PJ9THcXgNI+papKy2wKHPfly6B+071
|
|
||||||
BA4n0UX0tV7zqWk9axoN+nyUL97/k572kLTbxahrBEYXphdNeqqXHa/udWpTYaKw
|
|
||||||
SGYmIohTSIqBZh7Xa/rhLsx2UfgR5B0WW34E8cTzuiZziYalIC/9694vjOtPaSTp
|
|
||||||
iPyK2Bn/gOF6zXEqtUYPTdVfYADyhD00uNAxAsmgmju+KkoYl6j4oG3a71LZWcdQ
|
|
||||||
+hx3n+TgpNx51hXlqdv8g1HmkGM5KJW31ZgxfPmqgO6JfUiWucRaGHNjA2AdinU+
|
|
||||||
pFq9rlIaHWaxG+xw+tFNtdTDxmmzaj2pCsYUz/qTAN31iQIcBBABAgAGBQJNGJ3w
|
|
||||||
AAoJEIO1uBYaG9UOMXcP/0kA1SRdYd24ORdRdkVyhI8QqBE49+seV3iElKsk6e54
|
|
||||||
auaQDhpSFXfCLbSY2tmEnxD2AWDVwUDHtBPuKXREr8ytB44MKVm5Ar7M1o/ner+R
|
|
||||||
JsMdYR1bxLxF4j5MuPgTLaZKEszxmI5C+eo8wvf5heFwtIq23HxO+7DtYO2XKWLj
|
|
||||||
/k7Q3K760YvLtO72awqfMXr+MxX57/L6qyWdiMNfNiT1uGv9BpixRGB6xbDN18un
|
|
||||||
pVKk3sLPcE3oc44UdkSuxVrqHXVMzUIxpQGqOf+KYk9s5Z0KijllK09uoZI3WyKO
|
|
||||||
R2I5iGJDuBBzbuMGP23Gr3IMRTmVNAEWmjpxgLC2j1t80ocaAkguejTAKTjjXH1M
|
|
||||||
WJHoESsBXKdbk2xuAvnvqQqZ7weZfLCBS4XoSGdg3teeGa/ZQOHDknrLurqaa2ah
|
|
||||||
FGxcG4lOrf0OBZWMaI9Kj3HnrcThmEOwIozL4SDmUvvQxyK5s3uZjphFAyxRhQx1
|
|
||||||
fCKhnyA+D8oVtnTZ9uxtUWstIKK5RlOCxWJH3obvEGmGi+6E+zgDsK+ivqM8gFjj
|
|
||||||
3XmMpO6dh3/yZ6B8b8kanj4cYlCHhpeJ7v16G+FvGh/aMBlCopXAvoTprxQgXa12
|
|
||||||
MgYzYGRyuviOV+PWo+RTTPRyYmJ9RLADKSdHwA8VUvHp+nxZucES1M9PxVq92hhW
|
|
||||||
iQIcBBABAgAGBQJQezFyAAoJEFOcQ2uC5Av326UQALBzrx914us/lT+hEnfz5aRD
|
|
||||||
E7TwOhrt2ymPVzLvreRcaXOnbvG9eVz3FYwSQtl4UbprP6wjdi9bourU9ljNBEuy
|
|
||||||
OAwoM0MwMwHnFHeDrmVFbgop3SkKzn8JHGzaEM+Tq6WKHYTXY3/KrCBdOy1sQPNe
|
|
||||||
ZoF7/rq4Z20CcrQaKdd0T7nAEy7TLQIXEnKCQKa2j+E55i584dIshxVWvNuwsfeZ
|
|
||||||
649f2FTGM3hEg527BZ4eLQhZQLHkjIY+0w0EB9f4AhViZfutakQf5uqV9oRlgmHm
|
|
||||||
QsN5vMKryC1G15HO9HPSMJf9mvtJm7U+ySNE354wt2Q2CwX1NdDLa8UUzlpGgR6c
|
|
||||||
d4PmAyVrykEWdtk/4ADic+tu4pTJVx92ssgiBAQoi/GMp61KPcxXU9O4flg0HDYj
|
|
||||||
erGuCau/5iUKWaLL9VBe3YdznoQBCzwquTs3TT1toXHjiujGFo5arl5elPv4eNfU
|
|
||||||
/S0Yf3aguYbwj2vVrDbp3JxYjJouxklxQ2J4jOXD1cehjZ+xFRfdnyUDV2o9FzvW
|
|
||||||
Cc3N04var7Wx8+0mtok0N0xTkJunN8rkxvVUuh32zJlFlvZX4u61ZY4wI3hPz072
|
|
||||||
AFBdqv+B645Hrk04Hbu93iZ5ZgcICNZppyd6xZeBvqaEZXS+Zv92HCbxIBS9P7zB
|
|
||||||
3sXmQT57jusVSUdQtfJwiQIcBBABAgAGBQJRcGlBAAoJELlvIwCtEcvuoWwP/ReL
|
|
||||||
zhFKWlc/F35MvNyO1usz+qvs+SrlAtwaNcv3Dd9ih0mw+bH+U+PVVgXlk1g0NY9h
|
|
||||||
NNRLxt2mUc+mg9ttN+ha0RkqUYsYjg1Wj9bDuR0a+3DhtuS9hhEjWrBBT3UbTcWT
|
|
||||||
5lxKkUgy4Sj+Dh0N78spHo2orUN3qRw3VkHY4hWcxAvlXreuEv6J7Ik4uZ+8MMgJ
|
|
||||||
Fld4oVhMmnWOrMwt10D58URvZsGypI+dK0p2JSue5yfBWkSMpFsJ8z2cCOBMAPQq
|
|
||||||
9S63mhXZiORrxJS4pzJ87wcYG/H3R1pqF6I/49tWBlyZwiwOYs0fFEJc9idF/hSz
|
|
||||||
en/qDDQpvy4gNF48if7SGEtOBu1vEGqWKvNsataNcjYgj4BZhDlMHgAxWn0G7VNR
|
|
||||||
Vsx1D6nzOzEAlFa/PQgQfCXScJXRV72uKoMk2uuOk8yb2+toOW5LoS/0UbsnUi77
|
|
||||||
VvknpZPbQPQ5svsGBCU1BQpDeFsQk4IMW5Flv1VVSEtxnfLi89An4HPMN92+qNUD
|
|
||||||
RM3E/eLkFnrPdiB3yMkjAgDbao5Gh+CTszQ118xkhmRC+pNCI75AS/X4V1WrcAJU
|
|
||||||
niTbFgBRZr4t2tWfLMgx44XMtVrKraROj7QH4rEODSInBBEWT2hiJeWm4QS1g5Rf
|
|
||||||
oym4ur02xxqhwXAsCXFGFKZirXDoTMHDds6dI0QXiQEcBBABAgAGBQJQSx6AAAoJ
|
|
||||||
EH+pHtoamZ2Ehb0IAJzD7va1uonOpQiUuIRmUpoyYQ0EXOa+jlWpO8DQ/RPORPM1
|
|
||||||
IEGIsDZ3kTx6UJ+Zha1TAisQJzuLqAeNRaRUo0Tt3elIUgI+oDNKRWGEpc4Z8/Rv
|
|
||||||
4s6zBnPBkDwCEslAeFj3fnbLSR+9fHF0eD/u1Pj7uPyM23kiwWSnG4KQCyZhHPKR
|
|
||||||
jhmBg1UhEA25fOr8p9yHuMqTjadMbp3+S8lBI3MZBXOKl2JUPRIZFe6rXqx+SVJj
|
|
||||||
RW6cXMGHhe6QQGISzQBeBobqQnSim08sr18jvhleKqegGZVs1YhadZQzmQBNJXNT
|
|
||||||
/YmVX9cyrpktkHAPGRQ8NyjRSPwkRZAqaBnB71CJAfAEEAEKAAYFAlKGBO0ACgkQ
|
|
||||||
N4Uj/AufSZbFOQ6fbHEEerx0zf6FtLG2/EyK00q95yQY363WfM6fXvEbEHe8RThP
|
|
||||||
oZswxLAn96yfTNWXLhDS64muDntsPPpenk86siNzp9Br8qN1fKkZY2tBjyUtvGz9
|
|
||||||
i+paQWowXPfFeV5WutjqRY3cn6xY4SXWNWyffr3XTYqublnWs4s+yJuHQeb3XiWX
|
|
||||||
4o8p9csmTuC5sJgmZpkvppRgzRpHAd8VCzzC/cMEVeV2+cbFon4sHw5NJVAXbaRo
|
|
||||||
Z/P4SoA6S2Tz0SB1FWNa1v9TEu57/f7l8XYdI6nL4y6imnJ/RZqgpG7gJUqJSwS/
|
|
||||||
iu80JJqnZJ030hWrRZHHp2k+ZWr/kZgKGCxHbRCcQNpJCmPmSuJccVABWIkoKjgV
|
|
||||||
R4jXDbh+saGYLn2eUUzxkZmd7xaDSNUBhP2qdtKlGFc8ESL0qZkwixLhmpgUgFsf
|
|
||||||
7D/bGGJyVkhOji4rJDZx9I0K5s0JrDrEqO0nzYod08s7aaOcQrgMYcQA7x/Z3BlS
|
|
||||||
uRRo6KK61dOO42SzSbFSEW5Z8IEfSoUYHoyN81kbfC+j/q1dpwg+Bhw9PTqSWfLi
|
|
||||||
XI6H15X7H/Ig6NDK0U9v9s+gqmqG0AtQhEnCEqKNZFV1K8rnY+B+lNXMA0PIgxA0
|
|
||||||
iQHwBBABCgAGBQJSjUjjAAoJEMQJSn+pq5SBKV4On0Gzb3r2SAx4CM9zAhGoQw81
|
|
||||||
yM34WUHrkDESj2TrKw0sLYLMzM3wriEzFT+88buowSBT8h3ONNDijbj8NdjYQCfY
|
|
||||||
90bqgAROZ+W9/dmV2C9dJxmv5kWJQ/5D2ksuVpu1LUyK6AWXEkV1KpIcRHCP+Kb8
|
|
||||||
EWaMEjPPQbNJ1KrFzAFfIUeFTbBL5kMmJK5aYVUiHWnLZq0SK5OlWGqBihuRLI7O
|
|
||||||
IoBOjlcoXvFoEgSkgUKpapE6C9VkErW60WCK91sMhaa8CY9pVDPaanMG2o73BfS3
|
|
||||||
jGPylm4H2+8jlJ1+l5ietvoyiqOST1iIfOsbi30mxuVJ4JBvKtmapqpBwT6eNvCi
|
|
||||||
PKsMyjB5oWI5IVbK8MDIaYQM9TL+nyMGhl19GzcUMP8tZRlCifM9b/zmMMt1sgVY
|
|
||||||
0koF8AZfh3Ho9KLyXqNMUtXAFSQrAcTbN5SmzjlJtl+hz6uhiHH9kAeSX4MFRXX6
|
|
||||||
JDfZxyAw72JqJkZaPEAKQCpodkNwNG9b2dedIBsTaD9IoEkryDtR17qV2ePwlCey
|
|
||||||
muwNnGVVaJ8hLbI7ZATbIaSn7XNvMGM8hX0N/ram5nTvrR2laG1o1ss5oxtg7PfT
|
|
||||||
rhMyCTrzTcxc8VskAgtbJjoyi4kCHAQQAQIABgUCUHsxcgAKCRBTnENrguQL99ul
|
|
||||||
EACwc68fdeLrP5U/oRJ38+WkQxO08Doa7dspj1cy763kXGlzp27xvXlc9xWMEkLZ
|
|
||||||
eFG6az+sI3YvW6Lq1PZYzQRLsjgMKDNDMDMB5xR3g65lRW4KKd0pCs5/CRxs2hDP
|
|
||||||
k6ulih2E12N/yqwgXTstbEDzXmaBe/66uGdtAnK0GinXdE+5wBMu0y0CFxJygkCm
|
|
||||||
to/hOeYufOHSLIcVVrzbsLH3meuPX9hUxjN4RIOduwWeHi0IWUCx5IyGPtMNBAfX
|
|
||||||
+AIVYmX7rWpEH+bqlfaEZYJh5kLDebzCq8gtRteRzvRz0jCX/Zr7SZu1PskjRN+e
|
|
||||||
MLdkNgsF9TXQy2vFFM5aRoEenHeD5gMla8pBFnbZP+AA4nPrbuKUyVcfdrLIIgQE
|
|
||||||
KIvxjKetSj3MV1PTuH5YNBw2I3qxrgmrv+YlClmiy/VQXt3/////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
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
|
|
||||||
13UOzECiPRIbMhHmLHQic9MeJp0bzJiTo1icQVRnim5ZovcpXW2piJQaWqx/TUXG
|
|
||||||
aRdCjYrJJJZObIi6qnSB7SjdxwJUq6GuTEb/BJElQFnjsxySvTu24YkCGwQQAQIA
|
|
||||||
BgUCUVSNVAAKCRB+fTNcWi1ewX4xD/d0R2OHFLo42KJPsIc9Wz3AMO7mfpbCmSXc
|
|
||||||
xoM+Cyd9/GT2qgAt9hgItv3iqg9dj+AbjPNUKfpGG4Q4D/x/tb018C3F4U1PLC/P
|
|
||||||
Q2lYX0csvuv3Gp5MuNpCuHS5bW4kLyOpRZh1JrqniL8K1Mp8cdBhMf6H+ZckQuXS
|
|
||||||
hGHwOhGyBMu3X7biXikSvdgQmbDQMtaDbxuYZ+JGXF0uacPVnlAUwW1F55IIhmUH
|
|
||||||
IV7t+poYo/8M0HJ/lB9y5auamrJT4acsPWS+fYHAjfGfpSE7T7QWuiIKJ2EmpVa5
|
|
||||||
hpGhzII9ahF0wtHTKkF7d7RYV1p1UUA5nu8QFTope8fyERJDZg88ICt+TpXJ7+PJ
|
|
||||||
9THcXgNI+papKy2wKHPfly6B+071BA4n0UX0tV7zqWk9axoN+nyUL97/k572kLTb
|
|
||||||
xahrBEYXphdNeqqXHa/udWpTYaKwSGYmIohTSIqBZh7Xa/rhLsx2UfgR5B0WW34E
|
|
||||||
8cTzuiZz////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////iQIcBBABAgAGBQJW5/QxAAoJEPvqMRCoU3iU3SkP+wRdT8z3EczONAcv
|
|
||||||
Jsu7ZHgh1ggzsmozTciSuaAZRfvFmUyB9h63cKNTS86CIrqHmMZrtHRu9llkNNiE
|
|
||||||
4Nj8JAAsMPSR4YaKHfHxc3bOH0iWtcPxtIiQEwYs/7oP0/YzFAxcUmZBDeLvy7aK
|
|
||||||
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
|
|
||||||
keSPmmDrjl8cySCNsMo=
|
|
||||||
=R0uO
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
EOF
|
|
||||||
|
|
||||||
${SUDO} apt-get --quiet update
|
|
||||||
${SUDO} apt-get --quiet --yes install tor deb.torproject.org-keyring
|
|
@ -40,8 +40,11 @@ if PY2:
|
|||||||
|
|
||||||
@pytest_twisted.inlineCallbacks
|
@pytest_twisted.inlineCallbacks
|
||||||
def test_onion_service_storage(reactor, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl):
|
def test_onion_service_storage(reactor, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl):
|
||||||
yield _create_anonymous_node(reactor, 'carol', 8008, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl)
|
carol = yield _create_anonymous_node(reactor, 'carol', 8008, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl)
|
||||||
yield _create_anonymous_node(reactor, 'dave', 8009, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl)
|
dave = yield _create_anonymous_node(reactor, 'dave', 8009, request, temp_dir, flog_gatherer, tor_network, tor_introducer_furl)
|
||||||
|
util.await_client_ready(carol, minimum_number_of_servers=2)
|
||||||
|
util.await_client_ready(dave, minimum_number_of_servers=2)
|
||||||
|
|
||||||
# ensure both nodes are connected to "a grid" by uploading
|
# ensure both nodes are connected to "a grid" by uploading
|
||||||
# something via carol, and retrieve it using dave.
|
# something via carol, and retrieve it using dave.
|
||||||
gold_path = join(temp_dir, "gold")
|
gold_path = join(temp_dir, "gold")
|
||||||
@ -143,5 +146,6 @@ shares.total = 2
|
|||||||
f.write(node_config)
|
f.write(node_config)
|
||||||
|
|
||||||
print("running")
|
print("running")
|
||||||
yield util._run_node(reactor, node_dir.path, request, None)
|
result = yield util._run_node(reactor, node_dir.path, request, None)
|
||||||
print("okay, launched")
|
print("okay, launched")
|
||||||
|
return result
|
||||||
|
@ -482,14 +482,15 @@ def web_post(tahoe, uri_fragment, **kwargs):
|
|||||||
return resp.content
|
return resp.content
|
||||||
|
|
||||||
|
|
||||||
def await_client_ready(tahoe, timeout=10, liveness=60*2):
|
def await_client_ready(tahoe, timeout=10, liveness=60*2, minimum_number_of_servers=1):
|
||||||
"""
|
"""
|
||||||
Uses the status API to wait for a client-type node (in `tahoe`, a
|
Uses the status API to wait for a client-type node (in `tahoe`, a
|
||||||
`TahoeProcess` instance usually from a fixture e.g. `alice`) to be
|
`TahoeProcess` instance usually from a fixture e.g. `alice`) to be
|
||||||
'ready'. A client is deemed ready if:
|
'ready'. A client is deemed ready if:
|
||||||
|
|
||||||
- it answers `http://<node_url>/statistics/?t=json/`
|
- it answers `http://<node_url>/statistics/?t=json/`
|
||||||
- there is at least one storage-server connected
|
- there is at least one storage-server connected (configurable via
|
||||||
|
``minimum_number_of_servers``)
|
||||||
- every storage-server has a "last_received_data" and it is
|
- every storage-server has a "last_received_data" and it is
|
||||||
within the last `liveness` seconds
|
within the last `liveness` seconds
|
||||||
|
|
||||||
@ -506,8 +507,8 @@ def await_client_ready(tahoe, timeout=10, liveness=60*2):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(js['servers']) == 0:
|
if len(js['servers']) < minimum_number_of_servers:
|
||||||
print("waiting because no servers at all")
|
print("waiting because insufficient servers")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
server_times = [
|
server_times = [
|
||||||
|
0
newsfragments/3709.minor
Normal file
0
newsfragments/3709.minor
Normal file
0
newsfragments/3872.minor
Normal file
0
newsfragments/3872.minor
Normal file
0
newsfragments/3891.minor
Normal file
0
newsfragments/3891.minor
Normal file
0
newsfragments/3893.minor
Normal file
0
newsfragments/3893.minor
Normal file
0
newsfragments/3895.minor
Normal file
0
newsfragments/3895.minor
Normal file
0
newsfragments/3896.minor
Normal file
0
newsfragments/3896.minor
Normal file
0
newsfragments/3898.minor
Normal file
0
newsfragments/3898.minor
Normal file
0
newsfragments/3900.minor
Normal file
0
newsfragments/3900.minor
Normal file
2
setup.py
2
setup.py
@ -114,7 +114,7 @@ install_requires = [
|
|||||||
"attrs >= 18.2.0",
|
"attrs >= 18.2.0",
|
||||||
|
|
||||||
# WebSocket library for twisted and asyncio
|
# WebSocket library for twisted and asyncio
|
||||||
"autobahn >= 19.5.2",
|
"autobahn < 22.4.1", # remove this when 22.4.3 is released
|
||||||
|
|
||||||
# Support for Python 3 transition
|
# Support for Python 3 transition
|
||||||
"future >= 0.18.2",
|
"future >= 0.18.2",
|
||||||
|
@ -5,10 +5,12 @@ HTTP client that talks to the HTTP storage server.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from eliot import start_action, register_exception_extractor
|
from eliot import start_action, register_exception_extractor
|
||||||
from typing import Union, Optional, Sequence, Mapping
|
from typing import Union, Optional, Sequence, Mapping, BinaryIO
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from io import BytesIO
|
||||||
|
from os import SEEK_END
|
||||||
|
|
||||||
from attrs import define, asdict, frozen
|
from attrs import define, asdict, frozen, field
|
||||||
|
|
||||||
# TODO Make sure to import Python version?
|
# TODO Make sure to import Python version?
|
||||||
from cbor2 import loads, dumps
|
from cbor2 import loads, dumps
|
||||||
@ -18,7 +20,7 @@ from werkzeug.datastructures import Range, ContentRange
|
|||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web import http
|
from twisted.web import http
|
||||||
from twisted.web.iweb import IPolicyForHTTPS
|
from twisted.web.iweb import IPolicyForHTTPS
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue, fail, Deferred
|
from twisted.internet.defer import inlineCallbacks, returnValue, fail, Deferred, succeed
|
||||||
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
||||||
from twisted.internet.ssl import CertificateOptions
|
from twisted.internet.ssl import CertificateOptions
|
||||||
from twisted.web.client import Agent, HTTPConnectionPool
|
from twisted.web.client import Agent, HTTPConnectionPool
|
||||||
@ -29,6 +31,7 @@ from treq.client import HTTPClient
|
|||||||
from treq.testing import StubTreq
|
from treq.testing import StubTreq
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
from cryptography.hazmat.bindings.openssl.binding import Binding
|
from cryptography.hazmat.bindings.openssl.binding import Binding
|
||||||
|
from werkzeug.http import parse_content_range_header
|
||||||
|
|
||||||
from .http_common import (
|
from .http_common import (
|
||||||
swissnum_auth_header,
|
swissnum_auth_header,
|
||||||
@ -56,6 +59,7 @@ class ClientException(Exception):
|
|||||||
Exception.__init__(self, code, *additional_args)
|
Exception.__init__(self, code, *additional_args)
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
register_exception_extractor(ClientException, lambda e: {"response_code": e.code})
|
register_exception_extractor(ClientException, lambda e: {"response_code": e.code})
|
||||||
|
|
||||||
|
|
||||||
@ -109,22 +113,66 @@ _SCHEMAS = {
|
|||||||
share_number = uint
|
share_number = uint
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
|
"mutable_list_shares": Schema(
|
||||||
|
"""
|
||||||
|
response = #6.258([* uint])
|
||||||
|
"""
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class _LengthLimitedCollector:
|
||||||
|
"""
|
||||||
|
Collect data using ``treq.collect()``, with limited length.
|
||||||
|
"""
|
||||||
|
|
||||||
|
remaining_length: int
|
||||||
|
f: BytesIO = field(factory=BytesIO)
|
||||||
|
|
||||||
|
def __call__(self, data: bytes):
|
||||||
|
self.remaining_length -= len(data)
|
||||||
|
if self.remaining_length < 0:
|
||||||
|
raise ValueError("Response length was too long")
|
||||||
|
self.f.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
def limited_content(response, max_length: int = 30 * 1024 * 1024) -> Deferred[BinaryIO]:
|
||||||
|
"""
|
||||||
|
Like ``treq.content()``, but limit data read from the response to a set
|
||||||
|
length. If the response is longer than the max allowed length, the result
|
||||||
|
fails with a ``ValueError``.
|
||||||
|
|
||||||
|
A potentially useful future improvement would be using a temporary file to
|
||||||
|
store the content; since filesystem buffering means that would use memory
|
||||||
|
for small responses and disk for large responses.
|
||||||
|
"""
|
||||||
|
collector = _LengthLimitedCollector(max_length)
|
||||||
|
# Make really sure everything gets called in Deferred context, treq might
|
||||||
|
# call collector directly...
|
||||||
|
d = succeed(None)
|
||||||
|
d.addCallback(lambda _: treq.collect(response, collector))
|
||||||
|
|
||||||
|
def done(_):
|
||||||
|
collector.f.seek(0)
|
||||||
|
return collector.f
|
||||||
|
|
||||||
|
d.addCallback(done)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _decode_cbor(response, schema: Schema):
|
def _decode_cbor(response, schema: Schema):
|
||||||
"""Given HTTP response, return decoded CBOR body."""
|
"""Given HTTP response, return decoded CBOR body."""
|
||||||
|
|
||||||
def got_content(data):
|
def got_content(f: BinaryIO):
|
||||||
|
data = f.read()
|
||||||
schema.validate_cbor(data)
|
schema.validate_cbor(data)
|
||||||
return loads(data)
|
return loads(data)
|
||||||
|
|
||||||
if response.code > 199 and response.code < 300:
|
if response.code > 199 and response.code < 300:
|
||||||
content_type = get_content_type(response.headers)
|
content_type = get_content_type(response.headers)
|
||||||
if content_type == CBOR_MIME_TYPE:
|
if content_type == CBOR_MIME_TYPE:
|
||||||
# TODO limit memory usage
|
return limited_content(response).addCallback(got_content)
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
|
||||||
return treq.content(response).addCallback(got_content)
|
|
||||||
else:
|
else:
|
||||||
raise ClientException(-1, "Server didn't send CBOR")
|
raise ClientException(-1, "Server didn't send CBOR")
|
||||||
else:
|
else:
|
||||||
@ -294,7 +342,7 @@ class StorageClient(object):
|
|||||||
write_enabler_secret=None,
|
write_enabler_secret=None,
|
||||||
headers=None,
|
headers=None,
|
||||||
message_to_serialize=None,
|
message_to_serialize=None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Like ``treq.request()``, but with optional secrets that get translated
|
Like ``treq.request()``, but with optional secrets that get translated
|
||||||
@ -303,7 +351,11 @@ class StorageClient(object):
|
|||||||
If ``message_to_serialize`` is set, it will be serialized (by default
|
If ``message_to_serialize`` is set, it will be serialized (by default
|
||||||
with CBOR) and set as the request body.
|
with CBOR) and set as the request body.
|
||||||
"""
|
"""
|
||||||
with start_action(action_type="allmydata:storage:http-client:request", method=method, url=str(url)) as ctx:
|
with start_action(
|
||||||
|
action_type="allmydata:storage:http-client:request",
|
||||||
|
method=method,
|
||||||
|
url=str(url),
|
||||||
|
) as ctx:
|
||||||
headers = self._get_headers(headers)
|
headers = self._get_headers(headers)
|
||||||
|
|
||||||
# Add secrets:
|
# Add secrets:
|
||||||
@ -357,6 +409,31 @@ class StorageClientGeneral(object):
|
|||||||
decoded_response = yield _decode_cbor(response, _SCHEMAS["get_version"])
|
decoded_response = yield _decode_cbor(response, _SCHEMAS["get_version"])
|
||||||
returnValue(decoded_response)
|
returnValue(decoded_response)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def add_or_renew_lease(
|
||||||
|
self, storage_index: bytes, renew_secret: bytes, cancel_secret: bytes
|
||||||
|
) -> Deferred[None]:
|
||||||
|
"""
|
||||||
|
Add or renew a lease.
|
||||||
|
|
||||||
|
If the renewal secret matches an existing lease, it is renewed.
|
||||||
|
Otherwise a new lease is added.
|
||||||
|
"""
|
||||||
|
url = self._client.relative_url(
|
||||||
|
"/v1/lease/{}".format(_encode_si(storage_index))
|
||||||
|
)
|
||||||
|
response = yield self._client.request(
|
||||||
|
"PUT",
|
||||||
|
url,
|
||||||
|
lease_renew_secret=renew_secret,
|
||||||
|
lease_cancel_secret=cancel_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.code == http.NO_CONTENT:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise ClientException(response.code)
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
class UploadProgress(object):
|
class UploadProgress(object):
|
||||||
@ -382,16 +459,14 @@ def read_share_chunk(
|
|||||||
"""
|
"""
|
||||||
Download a chunk of data from a share.
|
Download a chunk of data from a share.
|
||||||
|
|
||||||
TODO https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3857 Failed
|
TODO https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3857 Failed downloads
|
||||||
downloads should be transparently retried and redownloaded by the
|
should be transparently retried and redownloaded by the implementation a
|
||||||
implementation a few times so that if a failure percolates up, the
|
few times so that if a failure percolates up, the caller can assume the
|
||||||
caller can assume the failure isn't a short-term blip.
|
failure isn't a short-term blip.
|
||||||
|
|
||||||
NOTE: the underlying HTTP protocol is much more flexible than this API,
|
NOTE: the underlying HTTP protocol is somewhat more flexible than this API,
|
||||||
so a future refactor may expand this in order to simplify the calling
|
insofar as it doesn't always require a range. In practice a range is
|
||||||
code and perhaps download data more efficiently. But then again maybe
|
always provided by the current callers.
|
||||||
the HTTP protocol will be simplified, see
|
|
||||||
https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3777
|
|
||||||
"""
|
"""
|
||||||
url = client.relative_url(
|
url = client.relative_url(
|
||||||
"/v1/{}/{}/{}".format(share_type, _encode_si(storage_index), share_number)
|
"/v1/{}/{}/{}".format(share_type, _encode_si(storage_index), share_number)
|
||||||
@ -400,16 +475,75 @@ def read_share_chunk(
|
|||||||
"GET",
|
"GET",
|
||||||
url,
|
url,
|
||||||
headers=Headers(
|
headers=Headers(
|
||||||
|
# Ranges in HTTP are _inclusive_, Python's convention is exclusive,
|
||||||
|
# but Range constructor does that the conversion for us.
|
||||||
{"range": [Range("bytes", [(offset, offset + length)]).to_header()]}
|
{"range": [Range("bytes", [(offset, offset + length)]).to_header()]}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if response.code == http.NO_CONTENT:
|
||||||
|
return b""
|
||||||
|
|
||||||
if response.code == http.PARTIAL_CONTENT:
|
if response.code == http.PARTIAL_CONTENT:
|
||||||
body = yield response.content()
|
content_range = parse_content_range_header(
|
||||||
returnValue(body)
|
response.headers.getRawHeaders("content-range")[0] or ""
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
content_range is None
|
||||||
|
or content_range.stop is None
|
||||||
|
or content_range.start is None
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
"Content-Range was missing, invalid, or in format we don't support"
|
||||||
|
)
|
||||||
|
supposed_length = content_range.stop - content_range.start
|
||||||
|
if supposed_length > length:
|
||||||
|
raise ValueError("Server sent more than we asked for?!")
|
||||||
|
# It might also send less than we asked for. That's (probably) OK, e.g.
|
||||||
|
# if we went past the end of the file.
|
||||||
|
body = yield limited_content(response, supposed_length)
|
||||||
|
body.seek(0, SEEK_END)
|
||||||
|
actual_length = body.tell()
|
||||||
|
if actual_length != supposed_length:
|
||||||
|
# Most likely a mutable that got changed out from under us, but
|
||||||
|
# conceivably could be a bug...
|
||||||
|
raise ValueError(
|
||||||
|
f"Length of response sent from server ({actual_length}) "
|
||||||
|
+ f"didn't match Content-Range header ({supposed_length})"
|
||||||
|
)
|
||||||
|
body.seek(0)
|
||||||
|
return body.read()
|
||||||
else:
|
else:
|
||||||
|
# Technically HTTP allows sending an OK with full body under these
|
||||||
|
# circumstances, but the server is not designed to do that so we ignore
|
||||||
|
# that possibility for now...
|
||||||
raise ClientException(response.code)
|
raise ClientException(response.code)
|
||||||
|
|
||||||
|
|
||||||
|
@async_to_deferred
|
||||||
|
async def advise_corrupt_share(
|
||||||
|
client: StorageClient,
|
||||||
|
share_type: str,
|
||||||
|
storage_index: bytes,
|
||||||
|
share_number: int,
|
||||||
|
reason: str,
|
||||||
|
):
|
||||||
|
assert isinstance(reason, str)
|
||||||
|
url = client.relative_url(
|
||||||
|
"/v1/{}/{}/{}/corrupt".format(
|
||||||
|
share_type, _encode_si(storage_index), share_number
|
||||||
|
)
|
||||||
|
)
|
||||||
|
message = {"reason": reason}
|
||||||
|
response = await client.request("POST", url, message_to_serialize=message)
|
||||||
|
if response.code == http.OK:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise ClientException(
|
||||||
|
response.code,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
class StorageClientImmutables(object):
|
class StorageClientImmutables(object):
|
||||||
"""
|
"""
|
||||||
@ -541,11 +675,14 @@ class StorageClientImmutables(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def list_shares(self, storage_index): # type: (bytes,) -> Deferred[set[int]]
|
def list_shares(self, storage_index: bytes) -> Deferred[set[int]]:
|
||||||
"""
|
"""
|
||||||
Return the set of shares for a given storage index.
|
Return the set of shares for a given storage index.
|
||||||
"""
|
"""
|
||||||
with start_action(action_type="allmydata:storage:http-client:immutable:list-shares", storage_index=storage_index) as ctx:
|
with start_action(
|
||||||
|
action_type="allmydata:storage:http-client:immutable:list-shares",
|
||||||
|
storage_index=storage_index,
|
||||||
|
) as ctx:
|
||||||
url = self._client.relative_url(
|
url = self._client.relative_url(
|
||||||
"/v1/immutable/{}/shares".format(_encode_si(storage_index))
|
"/v1/immutable/{}/shares".format(_encode_si(storage_index))
|
||||||
)
|
)
|
||||||
@ -559,32 +696,6 @@ class StorageClientImmutables(object):
|
|||||||
else:
|
else:
|
||||||
raise ClientException(response.code)
|
raise ClientException(response.code)
|
||||||
|
|
||||||
@inlineCallbacks
|
|
||||||
def add_or_renew_lease(
|
|
||||||
self, storage_index: bytes, renew_secret: bytes, cancel_secret: bytes
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Add or renew a lease.
|
|
||||||
|
|
||||||
If the renewal secret matches an existing lease, it is renewed.
|
|
||||||
Otherwise a new lease is added.
|
|
||||||
"""
|
|
||||||
url = self._client.relative_url(
|
|
||||||
"/v1/lease/{}".format(_encode_si(storage_index))
|
|
||||||
)
|
|
||||||
response = yield self._client.request(
|
|
||||||
"PUT",
|
|
||||||
url,
|
|
||||||
lease_renew_secret=renew_secret,
|
|
||||||
lease_cancel_secret=cancel_secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.code == http.NO_CONTENT:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ClientException(response.code)
|
|
||||||
|
|
||||||
@inlineCallbacks
|
|
||||||
def advise_corrupt_share(
|
def advise_corrupt_share(
|
||||||
self,
|
self,
|
||||||
storage_index: bytes,
|
storage_index: bytes,
|
||||||
@ -592,19 +703,8 @@ class StorageClientImmutables(object):
|
|||||||
reason: str,
|
reason: str,
|
||||||
):
|
):
|
||||||
"""Indicate a share has been corrupted, with a human-readable message."""
|
"""Indicate a share has been corrupted, with a human-readable message."""
|
||||||
assert isinstance(reason, str)
|
return advise_corrupt_share(
|
||||||
url = self._client.relative_url(
|
self._client, "immutable", storage_index, share_number, reason
|
||||||
"/v1/immutable/{}/{}/corrupt".format(
|
|
||||||
_encode_si(storage_index), share_number
|
|
||||||
)
|
|
||||||
)
|
|
||||||
message = {"reason": reason}
|
|
||||||
response = yield self._client.request("POST", url, message_to_serialize=message)
|
|
||||||
if response.code == http.OK:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ClientException(
|
|
||||||
response.code,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -639,8 +739,8 @@ class ReadVector:
|
|||||||
class TestWriteVectors:
|
class TestWriteVectors:
|
||||||
"""Test and write vectors for a specific share."""
|
"""Test and write vectors for a specific share."""
|
||||||
|
|
||||||
test_vectors: Sequence[TestVector]
|
test_vectors: Sequence[TestVector] = field(factory=list)
|
||||||
write_vectors: Sequence[WriteVector]
|
write_vectors: Sequence[WriteVector] = field(factory=list)
|
||||||
new_length: Optional[int] = None
|
new_length: Optional[int] = None
|
||||||
|
|
||||||
def asdict(self) -> dict:
|
def asdict(self) -> dict:
|
||||||
@ -689,7 +789,6 @@ class StorageClientMutables:
|
|||||||
Given a mapping between share numbers and test/write vectors, the tests
|
Given a mapping between share numbers and test/write vectors, the tests
|
||||||
are done and if they are valid the writes are done.
|
are done and if they are valid the writes are done.
|
||||||
"""
|
"""
|
||||||
# TODO unit test all the things
|
|
||||||
url = self._client.relative_url(
|
url = self._client.relative_url(
|
||||||
"/v1/mutable/{}/read-test-write".format(_encode_si(storage_index))
|
"/v1/mutable/{}/read-test-write".format(_encode_si(storage_index))
|
||||||
)
|
)
|
||||||
@ -720,11 +819,35 @@ class StorageClientMutables:
|
|||||||
share_number: int,
|
share_number: int,
|
||||||
offset: int,
|
offset: int,
|
||||||
length: int,
|
length: int,
|
||||||
) -> bytes:
|
) -> Deferred[bytes]:
|
||||||
"""
|
"""
|
||||||
Download a chunk of data from a share.
|
Download a chunk of data from a share.
|
||||||
"""
|
"""
|
||||||
# TODO unit test all the things
|
|
||||||
return read_share_chunk(
|
return read_share_chunk(
|
||||||
self._client, "mutable", storage_index, share_number, offset, length
|
self._client, "mutable", storage_index, share_number, offset, length
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@async_to_deferred
|
||||||
|
async def list_shares(self, storage_index: bytes) -> set[int]:
|
||||||
|
"""
|
||||||
|
List the share numbers for a given storage index.
|
||||||
|
"""
|
||||||
|
url = self._client.relative_url(
|
||||||
|
"/v1/mutable/{}/shares".format(_encode_si(storage_index))
|
||||||
|
)
|
||||||
|
response = await self._client.request("GET", url)
|
||||||
|
if response.code == http.OK:
|
||||||
|
return await _decode_cbor(response, _SCHEMAS["mutable_list_shares"])
|
||||||
|
else:
|
||||||
|
raise ClientException(response.code)
|
||||||
|
|
||||||
|
def advise_corrupt_share(
|
||||||
|
self,
|
||||||
|
storage_index: bytes,
|
||||||
|
share_number: int,
|
||||||
|
reason: str,
|
||||||
|
):
|
||||||
|
"""Indicate a share has been corrupted, with a human-readable message."""
|
||||||
|
return advise_corrupt_share(
|
||||||
|
self._client, "mutable", storage_index, share_number, reason
|
||||||
|
)
|
||||||
|
@ -2,24 +2,30 @@
|
|||||||
HTTP server for storage.
|
HTTP server for storage.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, List, Set, Tuple, Any
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict, List, Set, Tuple, Any, Callable, Union
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
import binascii
|
import binascii
|
||||||
|
from tempfile import TemporaryFile
|
||||||
|
|
||||||
from eliot import start_action
|
from eliot import start_action
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from klein import Klein
|
from klein import Klein
|
||||||
from twisted.web import http
|
from twisted.web import http
|
||||||
from twisted.internet.interfaces import IListeningPort, IStreamServerEndpoint
|
from twisted.internet.interfaces import (
|
||||||
|
IListeningPort,
|
||||||
|
IStreamServerEndpoint,
|
||||||
|
IPullProducer,
|
||||||
|
)
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
from twisted.internet.ssl import CertificateOptions, Certificate, PrivateCertificate
|
from twisted.internet.ssl import CertificateOptions, Certificate, PrivateCertificate
|
||||||
from twisted.web.server import Site
|
from twisted.web.server import Site, Request
|
||||||
from twisted.protocols.tls import TLSMemoryBIOFactory
|
from twisted.protocols.tls import TLSMemoryBIOFactory
|
||||||
from twisted.python.filepath import FilePath
|
from twisted.python.filepath import FilePath
|
||||||
|
|
||||||
import attr
|
from attrs import define, field, Factory
|
||||||
from werkzeug.http import (
|
from werkzeug.http import (
|
||||||
parse_range_header,
|
parse_range_header,
|
||||||
parse_content_range_header,
|
parse_content_range_header,
|
||||||
@ -32,7 +38,7 @@ from cryptography.x509 import load_pem_x509_certificate
|
|||||||
|
|
||||||
|
|
||||||
# TODO Make sure to use pure Python versions?
|
# TODO Make sure to use pure Python versions?
|
||||||
from cbor2 import dumps, loads
|
from cbor2 import dump, loads
|
||||||
from pycddl import Schema, ValidationError as CDDLValidationError
|
from pycddl import Schema, ValidationError as CDDLValidationError
|
||||||
from .server import StorageServer
|
from .server import StorageServer
|
||||||
from .http_common import (
|
from .http_common import (
|
||||||
@ -47,6 +53,7 @@ from .common import si_a2b
|
|||||||
from .immutable import BucketWriter, ConflictingWriteError
|
from .immutable import BucketWriter, ConflictingWriteError
|
||||||
from ..util.hashutil import timing_safe_compare
|
from ..util.hashutil import timing_safe_compare
|
||||||
from ..util.base32 import rfc3548_alphabet
|
from ..util.base32 import rfc3548_alphabet
|
||||||
|
from allmydata.interfaces import BadWriteEnablerError
|
||||||
|
|
||||||
|
|
||||||
class ClientSecretsException(Exception):
|
class ClientSecretsException(Exception):
|
||||||
@ -152,31 +159,31 @@ def _authorized_route(app, required_secrets, *route_args, **route_kwargs):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@define
|
||||||
class StorageIndexUploads(object):
|
class StorageIndexUploads(object):
|
||||||
"""
|
"""
|
||||||
In-progress upload to storage index.
|
In-progress upload to storage index.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Map share number to BucketWriter
|
# Map share number to BucketWriter
|
||||||
shares = attr.ib(factory=dict) # type: Dict[int,BucketWriter]
|
shares: dict[int, BucketWriter] = Factory(dict)
|
||||||
|
|
||||||
# Map share number to the upload secret (different shares might have
|
# Map share number to the upload secret (different shares might have
|
||||||
# different upload secrets).
|
# different upload secrets).
|
||||||
upload_secrets = attr.ib(factory=dict) # type: Dict[int,bytes]
|
upload_secrets: dict[int, bytes] = Factory(dict)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@define
|
||||||
class UploadsInProgress(object):
|
class UploadsInProgress(object):
|
||||||
"""
|
"""
|
||||||
Keep track of uploads for storage indexes.
|
Keep track of uploads for storage indexes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Map storage index to corresponding uploads-in-progress
|
# Map storage index to corresponding uploads-in-progress
|
||||||
_uploads = attr.ib(type=Dict[bytes, StorageIndexUploads], factory=dict)
|
_uploads: dict[bytes, StorageIndexUploads] = Factory(dict)
|
||||||
|
|
||||||
# Map BucketWriter to (storage index, share number)
|
# Map BucketWriter to (storage index, share number)
|
||||||
_bucketwriters = attr.ib(type=Dict[BucketWriter, Tuple[bytes, int]], factory=dict)
|
_bucketwriters: dict[BucketWriter, Tuple[bytes, int]] = Factory(dict)
|
||||||
|
|
||||||
def add_write_bucket(
|
def add_write_bucket(
|
||||||
self,
|
self,
|
||||||
@ -255,11 +262,15 @@ class _HTTPError(Exception):
|
|||||||
# Tags are of the form #6.nnn, where the number is documented at
|
# Tags are of the form #6.nnn, where the number is documented at
|
||||||
# https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. Notably, #6.258
|
# https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. Notably, #6.258
|
||||||
# indicates a set.
|
# indicates a set.
|
||||||
|
#
|
||||||
|
# Somewhat arbitrary limits are set to reduce e.g. number of shares, number of
|
||||||
|
# vectors, etc.. These may need to be iterated on in future revisions of the
|
||||||
|
# code.
|
||||||
_SCHEMAS = {
|
_SCHEMAS = {
|
||||||
"allocate_buckets": Schema(
|
"allocate_buckets": Schema(
|
||||||
"""
|
"""
|
||||||
request = {
|
request = {
|
||||||
share-numbers: #6.258([* uint])
|
share-numbers: #6.258([*256 uint])
|
||||||
allocated-size: uint
|
allocated-size: uint
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@ -275,13 +286,15 @@ _SCHEMAS = {
|
|||||||
"""
|
"""
|
||||||
request = {
|
request = {
|
||||||
"test-write-vectors": {
|
"test-write-vectors": {
|
||||||
* share_number: {
|
; TODO Add length limit here, after
|
||||||
"test": [* {"offset": uint, "size": uint, "specimen": bstr}]
|
; https://github.com/anweiss/cddl/issues/128 is fixed
|
||||||
"write": [* {"offset": uint, "data": bstr}]
|
* share_number => {
|
||||||
"new-length": uint // null
|
"test": [*30 {"offset": uint, "size": uint, "specimen": bstr}]
|
||||||
|
"write": [*30 {"offset": uint, "data": bstr}]
|
||||||
|
"new-length": uint / null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"read-vector": [* {"offset": uint, "size": uint}]
|
"read-vector": [*30 {"offset": uint, "size": uint}]
|
||||||
}
|
}
|
||||||
share_number = uint
|
share_number = uint
|
||||||
"""
|
"""
|
||||||
@ -289,6 +302,179 @@ _SCHEMAS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Callable that takes offset and length, returns the data at that range.
|
||||||
|
ReadData = Callable[[int, int], bytes]
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IPullProducer)
|
||||||
|
@define
|
||||||
|
class _ReadAllProducer:
|
||||||
|
"""
|
||||||
|
Producer that calls a read function repeatedly to read all the data, and
|
||||||
|
writes to a request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
request: Request
|
||||||
|
read_data: ReadData
|
||||||
|
result: Deferred = Factory(Deferred)
|
||||||
|
start: int = field(default=0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def produce_to(cls, request: Request, read_data: ReadData) -> Deferred:
|
||||||
|
"""
|
||||||
|
Create and register the producer, returning ``Deferred`` that should be
|
||||||
|
returned from a HTTP server endpoint.
|
||||||
|
"""
|
||||||
|
producer = cls(request, read_data)
|
||||||
|
request.registerProducer(producer, False)
|
||||||
|
return producer.result
|
||||||
|
|
||||||
|
def resumeProducing(self):
|
||||||
|
data = self.read_data(self.start, 65536)
|
||||||
|
if not data:
|
||||||
|
self.request.unregisterProducer()
|
||||||
|
d = self.result
|
||||||
|
del self.result
|
||||||
|
d.callback(b"")
|
||||||
|
return
|
||||||
|
self.request.write(data)
|
||||||
|
self.start += len(data)
|
||||||
|
|
||||||
|
def pauseProducing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stopProducing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IPullProducer)
|
||||||
|
@define
|
||||||
|
class _ReadRangeProducer:
|
||||||
|
"""
|
||||||
|
Producer that calls a read function to read a range of data, and writes to
|
||||||
|
a request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
request: Request
|
||||||
|
read_data: ReadData
|
||||||
|
result: Deferred
|
||||||
|
start: int
|
||||||
|
remaining: int
|
||||||
|
|
||||||
|
def resumeProducing(self):
|
||||||
|
to_read = min(self.remaining, 65536)
|
||||||
|
data = self.read_data(self.start, to_read)
|
||||||
|
assert len(data) <= to_read
|
||||||
|
|
||||||
|
if not data and self.remaining > 0:
|
||||||
|
d, self.result = self.result, None
|
||||||
|
d.errback(
|
||||||
|
ValueError(
|
||||||
|
f"Should be {self.remaining} bytes left, but we got an empty read"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.stopProducing()
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(data) > self.remaining:
|
||||||
|
d, self.result = self.result, None
|
||||||
|
d.errback(
|
||||||
|
ValueError(
|
||||||
|
f"Should be {self.remaining} bytes left, but we got more than that ({len(data)})!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.stopProducing()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.start += len(data)
|
||||||
|
self.remaining -= len(data)
|
||||||
|
assert self.remaining >= 0
|
||||||
|
|
||||||
|
self.request.write(data)
|
||||||
|
|
||||||
|
if self.remaining == 0:
|
||||||
|
self.stopProducing()
|
||||||
|
|
||||||
|
def pauseProducing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stopProducing(self):
|
||||||
|
if self.request is not None:
|
||||||
|
self.request.unregisterProducer()
|
||||||
|
self.request = None
|
||||||
|
if self.result is not None:
|
||||||
|
d = self.result
|
||||||
|
self.result = None
|
||||||
|
d.callback(b"")
|
||||||
|
|
||||||
|
|
||||||
|
def read_range(
|
||||||
|
request: Request, read_data: ReadData, share_length: int
|
||||||
|
) -> Union[Deferred, bytes]:
|
||||||
|
"""
|
||||||
|
Read an optional ``Range`` header, reads data appropriately via the given
|
||||||
|
callable, writes the data to the request.
|
||||||
|
|
||||||
|
Only parses a subset of ``Range`` headers that we support: must be set,
|
||||||
|
bytes only, only a single range, the end must be explicitly specified.
|
||||||
|
Raises a ``_HTTPError(http.REQUESTED_RANGE_NOT_SATISFIABLE)`` if parsing is
|
||||||
|
not possible or the header isn't set.
|
||||||
|
|
||||||
|
Takes a function that will do the actual reading given the start offset and
|
||||||
|
a length to read.
|
||||||
|
|
||||||
|
The resulting data is written to the request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def read_data_with_error_handling(offset: int, length: int) -> bytes:
|
||||||
|
try:
|
||||||
|
return read_data(offset, length)
|
||||||
|
except _HTTPError as e:
|
||||||
|
request.setResponseCode(e.code)
|
||||||
|
# Empty read means we're done.
|
||||||
|
return b""
|
||||||
|
|
||||||
|
if request.getHeader("range") is None:
|
||||||
|
return _ReadAllProducer.produce_to(request, read_data_with_error_handling)
|
||||||
|
|
||||||
|
range_header = parse_range_header(request.getHeader("range"))
|
||||||
|
if (
|
||||||
|
range_header is None # failed to parse
|
||||||
|
or range_header.units != "bytes"
|
||||||
|
or len(range_header.ranges) > 1 # more than one range
|
||||||
|
or range_header.ranges[0][1] is None # range without end
|
||||||
|
):
|
||||||
|
raise _HTTPError(http.REQUESTED_RANGE_NOT_SATISFIABLE)
|
||||||
|
|
||||||
|
offset, end = range_header.ranges[0]
|
||||||
|
# If we're being ask to read beyond the length of the share, just read
|
||||||
|
# less:
|
||||||
|
end = min(end, share_length)
|
||||||
|
if offset >= end:
|
||||||
|
# Basically we'd need to return an empty body. However, the
|
||||||
|
# Content-Range header can't actually represent empty lengths... so
|
||||||
|
# (mis)use 204 response code to indicate that.
|
||||||
|
raise _HTTPError(http.NO_CONTENT)
|
||||||
|
|
||||||
|
request.setResponseCode(http.PARTIAL_CONTENT)
|
||||||
|
|
||||||
|
# Actual conversion from Python's exclusive ranges to inclusive ranges is
|
||||||
|
# handled by werkzeug.
|
||||||
|
request.setHeader(
|
||||||
|
"content-range",
|
||||||
|
ContentRange("bytes", offset, end).to_header(),
|
||||||
|
)
|
||||||
|
|
||||||
|
d = Deferred()
|
||||||
|
request.registerProducer(
|
||||||
|
_ReadRangeProducer(
|
||||||
|
request, read_data_with_error_handling, d, offset, end - offset
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class HTTPServer(object):
|
class HTTPServer(object):
|
||||||
"""
|
"""
|
||||||
A HTTP interface to the storage server.
|
A HTTP interface to the storage server.
|
||||||
@ -340,9 +526,14 @@ class HTTPServer(object):
|
|||||||
accept = parse_accept_header(accept_headers[0])
|
accept = parse_accept_header(accept_headers[0])
|
||||||
if accept.best == CBOR_MIME_TYPE:
|
if accept.best == CBOR_MIME_TYPE:
|
||||||
request.setHeader("Content-Type", CBOR_MIME_TYPE)
|
request.setHeader("Content-Type", CBOR_MIME_TYPE)
|
||||||
# TODO if data is big, maybe want to use a temporary file eventually...
|
f = TemporaryFile()
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
dump(data, f)
|
||||||
return dumps(data)
|
|
||||||
|
def read_data(offset: int, length: int) -> bytes:
|
||||||
|
f.seek(offset)
|
||||||
|
return f.read(length)
|
||||||
|
|
||||||
|
return _ReadAllProducer.produce_to(request, read_data)
|
||||||
else:
|
else:
|
||||||
# TODO Might want to optionally send JSON someday:
|
# TODO Might want to optionally send JSON someday:
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3861
|
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3861
|
||||||
@ -351,12 +542,18 @@ class HTTPServer(object):
|
|||||||
def _read_encoded(self, request, schema: Schema) -> Any:
|
def _read_encoded(self, request, schema: Schema) -> Any:
|
||||||
"""
|
"""
|
||||||
Read encoded request body data, decoding it with CBOR by default.
|
Read encoded request body data, decoding it with CBOR by default.
|
||||||
|
|
||||||
|
Somewhat arbitrarily, limit body size to 1MB; this may be too low, we
|
||||||
|
may want to customize per query type, but this is the starting point
|
||||||
|
for now.
|
||||||
"""
|
"""
|
||||||
content_type = get_content_type(request.requestHeaders)
|
content_type = get_content_type(request.requestHeaders)
|
||||||
if content_type == CBOR_MIME_TYPE:
|
if content_type == CBOR_MIME_TYPE:
|
||||||
# TODO limit memory usage, client could send arbitrarily large data...
|
# Read 1 byte more than 1MB. We expect length to be 1MB or
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
# less; if it's more assume it's not a legitimate message.
|
||||||
message = request.content.read()
|
message = request.content.read(1024 * 1024 + 1)
|
||||||
|
if len(message) > 1024 * 1024:
|
||||||
|
raise _HTTPError(http.REQUEST_ENTITY_TOO_LARGE)
|
||||||
schema.validate_cbor(message)
|
schema.validate_cbor(message)
|
||||||
result = loads(message)
|
result = loads(message)
|
||||||
return result
|
return result
|
||||||
@ -405,10 +602,7 @@ class HTTPServer(object):
|
|||||||
|
|
||||||
return self._send_encoded(
|
return self._send_encoded(
|
||||||
request,
|
request,
|
||||||
{
|
{"already-have": set(already_got), "allocated": set(sharenum_to_bucket)},
|
||||||
"already-have": set(already_got),
|
|
||||||
"allocated": set(sharenum_to_bucket),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@_authorized_route(
|
@_authorized_route(
|
||||||
@ -455,20 +649,24 @@ class HTTPServer(object):
|
|||||||
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
|
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
offset = content_range.start
|
|
||||||
|
|
||||||
# TODO limit memory usage
|
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
|
||||||
data = request.content.read(content_range.stop - content_range.start + 1)
|
|
||||||
bucket = self._uploads.get_write_bucket(
|
bucket = self._uploads.get_write_bucket(
|
||||||
storage_index, share_number, authorization[Secrets.UPLOAD]
|
storage_index, share_number, authorization[Secrets.UPLOAD]
|
||||||
)
|
)
|
||||||
|
offset = content_range.start
|
||||||
|
remaining = content_range.stop - content_range.start
|
||||||
|
finished = False
|
||||||
|
|
||||||
|
while remaining > 0:
|
||||||
|
data = request.content.read(min(remaining, 65536))
|
||||||
|
assert data, "uploaded data length doesn't match range"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
finished = bucket.write(offset, data)
|
finished = bucket.write(offset, data)
|
||||||
except ConflictingWriteError:
|
except ConflictingWriteError:
|
||||||
request.setResponseCode(http.CONFLICT)
|
request.setResponseCode(http.CONFLICT)
|
||||||
return b""
|
return b""
|
||||||
|
remaining -= len(data)
|
||||||
|
offset += len(data)
|
||||||
|
|
||||||
if finished:
|
if finished:
|
||||||
bucket.close()
|
bucket.close()
|
||||||
@ -508,44 +706,7 @@ class HTTPServer(object):
|
|||||||
request.setResponseCode(http.NOT_FOUND)
|
request.setResponseCode(http.NOT_FOUND)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
if request.getHeader("range") is None:
|
return read_range(request, bucket.read, bucket.get_length())
|
||||||
# Return the whole thing.
|
|
||||||
start = 0
|
|
||||||
while True:
|
|
||||||
# TODO should probably yield to event loop occasionally...
|
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
|
||||||
data = bucket.read(start, start + 65536)
|
|
||||||
if not data:
|
|
||||||
request.finish()
|
|
||||||
return
|
|
||||||
request.write(data)
|
|
||||||
start += len(data)
|
|
||||||
|
|
||||||
range_header = parse_range_header(request.getHeader("range"))
|
|
||||||
if (
|
|
||||||
range_header is None
|
|
||||||
or range_header.units != "bytes"
|
|
||||||
or len(range_header.ranges) > 1 # more than one range
|
|
||||||
or range_header.ranges[0][1] is None # range without end
|
|
||||||
):
|
|
||||||
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
|
|
||||||
return b""
|
|
||||||
|
|
||||||
offset, end = range_header.ranges[0]
|
|
||||||
|
|
||||||
# TODO limit memory usage
|
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
|
||||||
data = bucket.read(offset, end - offset)
|
|
||||||
|
|
||||||
request.setResponseCode(http.PARTIAL_CONTENT)
|
|
||||||
if len(data):
|
|
||||||
# For empty bodies the content-range header makes no sense since
|
|
||||||
# the end of the range is inclusive.
|
|
||||||
request.setHeader(
|
|
||||||
"content-range",
|
|
||||||
ContentRange("bytes", offset, offset + len(data)).to_header(),
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@_authorized_route(
|
@_authorized_route(
|
||||||
_app,
|
_app,
|
||||||
@ -554,8 +715,8 @@ class HTTPServer(object):
|
|||||||
methods=["PUT"],
|
methods=["PUT"],
|
||||||
)
|
)
|
||||||
def add_or_renew_lease(self, request, authorization, storage_index):
|
def add_or_renew_lease(self, request, authorization, storage_index):
|
||||||
"""Update the lease for an immutable share."""
|
"""Update the lease for an immutable or mutable share."""
|
||||||
if not self._storage_server.get_buckets(storage_index):
|
if not list(self._storage_server.get_shares(storage_index)):
|
||||||
raise _HTTPError(http.NOT_FOUND)
|
raise _HTTPError(http.NOT_FOUND)
|
||||||
|
|
||||||
# Checking of the renewal secret is done by the backend.
|
# Checking of the renewal secret is done by the backend.
|
||||||
@ -597,19 +758,22 @@ class HTTPServer(object):
|
|||||||
)
|
)
|
||||||
def mutable_read_test_write(self, request, authorization, storage_index):
|
def mutable_read_test_write(self, request, authorization, storage_index):
|
||||||
"""Read/test/write combined operation for mutables."""
|
"""Read/test/write combined operation for mutables."""
|
||||||
# TODO unit tests
|
|
||||||
rtw_request = self._read_encoded(request, _SCHEMAS["mutable_read_test_write"])
|
rtw_request = self._read_encoded(request, _SCHEMAS["mutable_read_test_write"])
|
||||||
secrets = (
|
secrets = (
|
||||||
authorization[Secrets.WRITE_ENABLER],
|
authorization[Secrets.WRITE_ENABLER],
|
||||||
authorization[Secrets.LEASE_RENEW],
|
authorization[Secrets.LEASE_RENEW],
|
||||||
authorization[Secrets.LEASE_CANCEL],
|
authorization[Secrets.LEASE_CANCEL],
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
success, read_data = self._storage_server.slot_testv_and_readv_and_writev(
|
success, read_data = self._storage_server.slot_testv_and_readv_and_writev(
|
||||||
storage_index,
|
storage_index,
|
||||||
secrets,
|
secrets,
|
||||||
{
|
{
|
||||||
k: (
|
k: (
|
||||||
[(d["offset"], d["size"], b"eq", d["specimen"]) for d in v["test"]],
|
[
|
||||||
|
(d["offset"], d["size"], b"eq", d["specimen"])
|
||||||
|
for d in v["test"]
|
||||||
|
],
|
||||||
[(d["offset"], d["data"]) for d in v["write"]],
|
[(d["offset"], d["data"]) for d in v["write"]],
|
||||||
v["new-length"],
|
v["new-length"],
|
||||||
)
|
)
|
||||||
@ -617,6 +781,8 @@ class HTTPServer(object):
|
|||||||
},
|
},
|
||||||
[(d["offset"], d["size"]) for d in rtw_request["read-vector"]],
|
[(d["offset"], d["size"]) for d in rtw_request["read-vector"]],
|
||||||
)
|
)
|
||||||
|
except BadWriteEnablerError:
|
||||||
|
raise _HTTPError(http.UNAUTHORIZED)
|
||||||
return self._send_encoded(request, {"success": success, "data": read_data})
|
return self._send_encoded(request, {"success": success, "data": read_data})
|
||||||
|
|
||||||
@_authorized_route(
|
@_authorized_route(
|
||||||
@ -627,44 +793,56 @@ class HTTPServer(object):
|
|||||||
)
|
)
|
||||||
def read_mutable_chunk(self, request, authorization, storage_index, share_number):
|
def read_mutable_chunk(self, request, authorization, storage_index, share_number):
|
||||||
"""Read a chunk from a mutable."""
|
"""Read a chunk from a mutable."""
|
||||||
if request.getHeader("range") is None:
|
|
||||||
# TODO in follow-up ticket
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
# TODO reduce duplication with immutable reads?
|
try:
|
||||||
# TODO unit tests, perhaps shared if possible
|
share_length = self._storage_server.get_mutable_share_length(
|
||||||
range_header = parse_range_header(request.getHeader("range"))
|
storage_index, share_number
|
||||||
if (
|
|
||||||
range_header is None
|
|
||||||
or range_header.units != "bytes"
|
|
||||||
or len(range_header.ranges) > 1 # more than one range
|
|
||||||
or range_header.ranges[0][1] is None # range without end
|
|
||||||
):
|
|
||||||
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
|
|
||||||
return b""
|
|
||||||
|
|
||||||
offset, end = range_header.ranges[0]
|
|
||||||
|
|
||||||
# TODO limit memory usage
|
|
||||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
|
|
||||||
data = self._storage_server.slot_readv(
|
|
||||||
storage_index, [share_number], [(offset, end - offset)]
|
|
||||||
)[share_number][0]
|
|
||||||
|
|
||||||
# TODO reduce duplication?
|
|
||||||
request.setResponseCode(http.PARTIAL_CONTENT)
|
|
||||||
if len(data):
|
|
||||||
# For empty bodies the content-range header makes no sense since
|
|
||||||
# the end of the range is inclusive.
|
|
||||||
request.setHeader(
|
|
||||||
"content-range",
|
|
||||||
ContentRange("bytes", offset, offset + len(data)).to_header(),
|
|
||||||
)
|
)
|
||||||
return data
|
except KeyError:
|
||||||
|
raise _HTTPError(http.NOT_FOUND)
|
||||||
|
|
||||||
|
def read_data(offset, length):
|
||||||
|
try:
|
||||||
|
return self._storage_server.slot_readv(
|
||||||
|
storage_index, [share_number], [(offset, length)]
|
||||||
|
)[share_number][0]
|
||||||
|
except KeyError:
|
||||||
|
raise _HTTPError(http.NOT_FOUND)
|
||||||
|
|
||||||
|
return read_range(request, read_data, share_length)
|
||||||
|
|
||||||
|
@_authorized_route(
|
||||||
|
_app, set(), "/v1/mutable/<storage_index:storage_index>/shares", methods=["GET"]
|
||||||
|
)
|
||||||
|
def enumerate_mutable_shares(self, request, authorization, storage_index):
|
||||||
|
"""List mutable shares for a storage index."""
|
||||||
|
shares = self._storage_server.enumerate_mutable_shares(storage_index)
|
||||||
|
return self._send_encoded(request, shares)
|
||||||
|
|
||||||
|
@_authorized_route(
|
||||||
|
_app,
|
||||||
|
set(),
|
||||||
|
"/v1/mutable/<storage_index:storage_index>/<int(signed=False):share_number>/corrupt",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
def advise_corrupt_share_mutable(
|
||||||
|
self, request, authorization, storage_index, share_number
|
||||||
|
):
|
||||||
|
"""Indicate that given share is corrupt, with a text reason."""
|
||||||
|
if share_number not in {
|
||||||
|
shnum for (shnum, _) in self._storage_server.get_shares(storage_index)
|
||||||
|
}:
|
||||||
|
raise _HTTPError(http.NOT_FOUND)
|
||||||
|
|
||||||
|
info = self._read_encoded(request, _SCHEMAS["advise_corrupt_share"])
|
||||||
|
self._storage_server.advise_corrupt_share(
|
||||||
|
b"mutable", storage_index, share_number, info["reason"].encode("utf-8")
|
||||||
|
)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
|
||||||
@implementer(IStreamServerEndpoint)
|
@implementer(IStreamServerEndpoint)
|
||||||
@attr.s
|
@define
|
||||||
class _TLSEndpointWrapper(object):
|
class _TLSEndpointWrapper(object):
|
||||||
"""
|
"""
|
||||||
Wrap an existing endpoint with the server-side storage TLS policy. This is
|
Wrap an existing endpoint with the server-side storage TLS policy. This is
|
||||||
@ -672,8 +850,8 @@ class _TLSEndpointWrapper(object):
|
|||||||
example there's Tor and i2p.
|
example there's Tor and i2p.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
endpoint = attr.ib(type=IStreamServerEndpoint)
|
endpoint: IStreamServerEndpoint
|
||||||
context_factory = attr.ib(type=CertificateOptions)
|
context_factory: CertificateOptions
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_paths(
|
def from_paths(
|
||||||
|
@ -199,8 +199,16 @@ class ShareFile(object):
|
|||||||
raise UnknownImmutableContainerVersionError(filename, version)
|
raise UnknownImmutableContainerVersionError(filename, version)
|
||||||
self._num_leases = num_leases
|
self._num_leases = num_leases
|
||||||
self._lease_offset = filesize - (num_leases * self.LEASE_SIZE)
|
self._lease_offset = filesize - (num_leases * self.LEASE_SIZE)
|
||||||
|
self._length = filesize - 0xc - (num_leases * self.LEASE_SIZE)
|
||||||
|
|
||||||
self._data_offset = 0xc
|
self._data_offset = 0xc
|
||||||
|
|
||||||
|
def get_length(self):
|
||||||
|
"""
|
||||||
|
Return the length of the data in the share, if we're reading.
|
||||||
|
"""
|
||||||
|
return self._length
|
||||||
|
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
os.unlink(self.home)
|
os.unlink(self.home)
|
||||||
|
|
||||||
@ -544,6 +552,12 @@ class BucketReader(object):
|
|||||||
self.shnum,
|
self.shnum,
|
||||||
reason)
|
reason)
|
||||||
|
|
||||||
|
def get_length(self):
|
||||||
|
"""
|
||||||
|
Return the length of the data in the share.
|
||||||
|
"""
|
||||||
|
return self._share_file.get_length()
|
||||||
|
|
||||||
|
|
||||||
@implementer(RIBucketReader)
|
@implementer(RIBucketReader)
|
||||||
class FoolscapBucketReader(Referenceable): # type: ignore # warner/foolscap#78
|
class FoolscapBucketReader(Referenceable): # type: ignore # warner/foolscap#78
|
||||||
|
@ -412,11 +412,14 @@ class MutableShareFile(object):
|
|||||||
datav.append(self._read_share_data(f, offset, length))
|
datav.append(self._read_share_data(f, offset, length))
|
||||||
return datav
|
return datav
|
||||||
|
|
||||||
# def remote_get_length(self):
|
def get_length(self):
|
||||||
# f = open(self.home, 'rb')
|
"""
|
||||||
# data_length = self._read_data_length(f)
|
Return the length of the data in the share.
|
||||||
# f.close()
|
"""
|
||||||
# return data_length
|
f = open(self.home, 'rb')
|
||||||
|
data_length = self._read_data_length(f)
|
||||||
|
f.close()
|
||||||
|
return data_length
|
||||||
|
|
||||||
def check_write_enabler(self, write_enabler, si_s):
|
def check_write_enabler(self, write_enabler, si_s):
|
||||||
with open(self.home, 'rb+') as f:
|
with open(self.home, 'rb+') as f:
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Ported to Python 3.
|
Ported to Python 3.
|
||||||
"""
|
"""
|
||||||
from __future__ import division
|
from __future__ import annotations
|
||||||
from __future__ import absolute_import
|
from future.utils import bytes_to_native_str
|
||||||
from __future__ import print_function
|
from typing import Dict, Tuple, Iterable
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from future.utils import bytes_to_native_str, PY2
|
|
||||||
if PY2:
|
|
||||||
# Omit open() to get native behavior where open("w") always accepts native
|
|
||||||
# strings. Omit bytes so we don't leak future's custom bytes.
|
|
||||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, pow, round, super, dict, list, object, range, str, max, min # noqa: F401
|
|
||||||
else:
|
|
||||||
from typing import Dict, Tuple
|
|
||||||
|
|
||||||
import os, re
|
import os, re
|
||||||
|
|
||||||
@ -330,7 +321,7 @@ class StorageServer(service.MultiService):
|
|||||||
# they asked about: this will save them a lot of work. Add or update
|
# they asked about: this will save them a lot of work. Add or update
|
||||||
# leases for all of them: if they want us to hold shares for this
|
# leases for all of them: if they want us to hold shares for this
|
||||||
# file, they'll want us to hold leases for this file.
|
# file, they'll want us to hold leases for this file.
|
||||||
for (shnum, fn) in self._get_bucket_shares(storage_index):
|
for (shnum, fn) in self.get_shares(storage_index):
|
||||||
alreadygot[shnum] = ShareFile(fn)
|
alreadygot[shnum] = ShareFile(fn)
|
||||||
if renew_leases:
|
if renew_leases:
|
||||||
self._add_or_renew_leases(alreadygot.values(), lease_info)
|
self._add_or_renew_leases(alreadygot.values(), lease_info)
|
||||||
@ -372,7 +363,7 @@ class StorageServer(service.MultiService):
|
|||||||
return set(alreadygot), bucketwriters
|
return set(alreadygot), bucketwriters
|
||||||
|
|
||||||
def _iter_share_files(self, storage_index):
|
def _iter_share_files(self, storage_index):
|
||||||
for shnum, filename in self._get_bucket_shares(storage_index):
|
for shnum, filename in self.get_shares(storage_index):
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
header = f.read(32)
|
header = f.read(32)
|
||||||
if MutableShareFile.is_valid_header(header):
|
if MutableShareFile.is_valid_header(header):
|
||||||
@ -425,10 +416,12 @@ class StorageServer(service.MultiService):
|
|||||||
"""
|
"""
|
||||||
self._call_on_bucket_writer_close.append(handler)
|
self._call_on_bucket_writer_close.append(handler)
|
||||||
|
|
||||||
def _get_bucket_shares(self, storage_index):
|
def get_shares(self, storage_index) -> Iterable[tuple[int, str]]:
|
||||||
"""Return a list of (shnum, pathname) tuples for files that hold
|
"""
|
||||||
|
Return an iterable of (shnum, pathname) tuples for files that hold
|
||||||
shares for this storage_index. In each tuple, 'shnum' will always be
|
shares for this storage_index. In each tuple, 'shnum' will always be
|
||||||
the integer form of the last component of 'pathname'."""
|
the integer form of the last component of 'pathname'.
|
||||||
|
"""
|
||||||
storagedir = os.path.join(self.sharedir, storage_index_to_dir(storage_index))
|
storagedir = os.path.join(self.sharedir, storage_index_to_dir(storage_index))
|
||||||
try:
|
try:
|
||||||
for f in os.listdir(storagedir):
|
for f in os.listdir(storagedir):
|
||||||
@ -440,12 +433,15 @@ class StorageServer(service.MultiService):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_buckets(self, storage_index):
|
def get_buckets(self, storage_index):
|
||||||
|
"""
|
||||||
|
Get ``BucketReaders`` for an immutable.
|
||||||
|
"""
|
||||||
start = self._clock.seconds()
|
start = self._clock.seconds()
|
||||||
self.count("get")
|
self.count("get")
|
||||||
si_s = si_b2a(storage_index)
|
si_s = si_b2a(storage_index)
|
||||||
log.msg("storage: get_buckets %r" % si_s)
|
log.msg("storage: get_buckets %r" % si_s)
|
||||||
bucketreaders = {} # k: sharenum, v: BucketReader
|
bucketreaders = {} # k: sharenum, v: BucketReader
|
||||||
for shnum, filename in self._get_bucket_shares(storage_index):
|
for shnum, filename in self.get_shares(storage_index):
|
||||||
bucketreaders[shnum] = BucketReader(self, filename,
|
bucketreaders[shnum] = BucketReader(self, filename,
|
||||||
storage_index, shnum)
|
storage_index, shnum)
|
||||||
self.add_latency("get", self._clock.seconds() - start)
|
self.add_latency("get", self._clock.seconds() - start)
|
||||||
@ -462,7 +458,7 @@ class StorageServer(service.MultiService):
|
|||||||
# since all shares get the same lease data, we just grab the leases
|
# since all shares get the same lease data, we just grab the leases
|
||||||
# from the first share
|
# from the first share
|
||||||
try:
|
try:
|
||||||
shnum, filename = next(self._get_bucket_shares(storage_index))
|
shnum, filename = next(self.get_shares(storage_index))
|
||||||
sf = ShareFile(filename)
|
sf = ShareFile(filename)
|
||||||
return sf.get_leases()
|
return sf.get_leases()
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
@ -476,7 +472,7 @@ class StorageServer(service.MultiService):
|
|||||||
|
|
||||||
:return: An iterable of the leases attached to this slot.
|
:return: An iterable of the leases attached to this slot.
|
||||||
"""
|
"""
|
||||||
for _, share_filename in self._get_bucket_shares(storage_index):
|
for _, share_filename in self.get_shares(storage_index):
|
||||||
share = MutableShareFile(share_filename)
|
share = MutableShareFile(share_filename)
|
||||||
return share.get_leases()
|
return share.get_leases()
|
||||||
return []
|
return []
|
||||||
@ -699,6 +695,21 @@ class StorageServer(service.MultiService):
|
|||||||
self)
|
self)
|
||||||
return share
|
return share
|
||||||
|
|
||||||
|
def enumerate_mutable_shares(self, storage_index: bytes) -> set[int]:
|
||||||
|
"""Return all share numbers for the given mutable."""
|
||||||
|
si_dir = storage_index_to_dir(storage_index)
|
||||||
|
# shares exist if there is a file for them
|
||||||
|
bucketdir = os.path.join(self.sharedir, si_dir)
|
||||||
|
if not os.path.isdir(bucketdir):
|
||||||
|
return set()
|
||||||
|
result = set()
|
||||||
|
for sharenum_s in os.listdir(bucketdir):
|
||||||
|
try:
|
||||||
|
result.add(int(sharenum_s))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return result
|
||||||
|
|
||||||
def slot_readv(self, storage_index, shares, readv):
|
def slot_readv(self, storage_index, shares, readv):
|
||||||
start = self._clock.seconds()
|
start = self._clock.seconds()
|
||||||
self.count("readv")
|
self.count("readv")
|
||||||
@ -736,7 +747,7 @@ class StorageServer(service.MultiService):
|
|||||||
:return bool: ``True`` if a share with the given number exists at the
|
:return bool: ``True`` if a share with the given number exists at the
|
||||||
given storage index, ``False`` otherwise.
|
given storage index, ``False`` otherwise.
|
||||||
"""
|
"""
|
||||||
for existing_sharenum, ignored in self._get_bucket_shares(storage_index):
|
for existing_sharenum, ignored in self.get_shares(storage_index):
|
||||||
if existing_sharenum == shnum:
|
if existing_sharenum == shnum:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -783,6 +794,20 @@ class StorageServer(service.MultiService):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_immutable_share_length(self, storage_index: bytes, share_number: int) -> int:
|
||||||
|
"""Returns the length (in bytes) of an immutable."""
|
||||||
|
si_dir = storage_index_to_dir(storage_index)
|
||||||
|
path = os.path.join(self.sharedir, si_dir, str(share_number))
|
||||||
|
return ShareFile(path).get_length()
|
||||||
|
|
||||||
|
def get_mutable_share_length(self, storage_index: bytes, share_number: int) -> int:
|
||||||
|
"""Returns the length (in bytes) of a mutable."""
|
||||||
|
si_dir = storage_index_to_dir(storage_index)
|
||||||
|
path = os.path.join(self.sharedir, si_dir, str(share_number))
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise KeyError("No such storage index or share number")
|
||||||
|
return MutableShareFile(path).get_length()
|
||||||
|
|
||||||
|
|
||||||
@implementer(RIStorageServer)
|
@implementer(RIStorageServer)
|
||||||
class FoolscapStorageServer(Referenceable): # type: ignore # warner/foolscap#78
|
class FoolscapStorageServer(Referenceable): # type: ignore # warner/foolscap#78
|
||||||
|
@ -5,10 +5,6 @@ the foolscap-based server implemented in src/allmydata/storage/*.py .
|
|||||||
|
|
||||||
Ported to Python 3.
|
Ported to Python 3.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
# roadmap:
|
# roadmap:
|
||||||
#
|
#
|
||||||
@ -34,14 +30,10 @@ from __future__ import unicode_literals
|
|||||||
#
|
#
|
||||||
# 6: implement other sorts of IStorageClient classes: S3, etc
|
# 6: implement other sorts of IStorageClient classes: S3, etc
|
||||||
|
|
||||||
from future.utils import PY2
|
|
||||||
if PY2:
|
|
||||||
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
|
|
||||||
from six import ensure_text
|
from six import ensure_text
|
||||||
|
from typing import Union
|
||||||
import re, time, hashlib
|
import re, time, hashlib
|
||||||
from os import urandom
|
from os import urandom
|
||||||
# On Python 2 this will be the backport.
|
|
||||||
from configparser import NoSectionError
|
from configparser import NoSectionError
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
@ -50,6 +42,7 @@ from zope.interface import (
|
|||||||
Interface,
|
Interface,
|
||||||
implementer,
|
implementer,
|
||||||
)
|
)
|
||||||
|
from twisted.web import http
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
from twisted.plugin import (
|
from twisted.plugin import (
|
||||||
@ -75,10 +68,11 @@ from allmydata.util.observer import ObserverList
|
|||||||
from allmydata.util.rrefutil import add_version_to_remote_reference
|
from allmydata.util.rrefutil import add_version_to_remote_reference
|
||||||
from allmydata.util.hashutil import permute_server_hash
|
from allmydata.util.hashutil import permute_server_hash
|
||||||
from allmydata.util.dictutil import BytesKeyDict, UnicodeKeyDict
|
from allmydata.util.dictutil import BytesKeyDict, UnicodeKeyDict
|
||||||
|
from allmydata.util.deferredutil import async_to_deferred
|
||||||
from allmydata.storage.http_client import (
|
from allmydata.storage.http_client import (
|
||||||
StorageClient, StorageClientImmutables, StorageClientGeneral,
|
StorageClient, StorageClientImmutables, StorageClientGeneral,
|
||||||
ClientException as HTTPClientException, StorageClientMutables,
|
ClientException as HTTPClientException, StorageClientMutables,
|
||||||
ReadVector, TestWriteVectors, WriteVector, TestVector
|
ReadVector, TestWriteVectors, WriteVector, TestVector, ClientException
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1165,16 +1159,23 @@ class _HTTPStorageServer(object):
|
|||||||
for share_num in share_numbers
|
for share_num in share_numbers
|
||||||
})
|
})
|
||||||
|
|
||||||
def add_lease(
|
@async_to_deferred
|
||||||
|
async def add_lease(
|
||||||
self,
|
self,
|
||||||
storage_index,
|
storage_index,
|
||||||
renew_secret,
|
renew_secret,
|
||||||
cancel_secret
|
cancel_secret
|
||||||
):
|
):
|
||||||
immutable_client = StorageClientImmutables(self._http_client)
|
client = StorageClientGeneral(self._http_client)
|
||||||
return immutable_client.add_or_renew_lease(
|
try:
|
||||||
|
await client.add_or_renew_lease(
|
||||||
storage_index, renew_secret, cancel_secret
|
storage_index, renew_secret, cancel_secret
|
||||||
)
|
)
|
||||||
|
except ClientException as e:
|
||||||
|
if e.code == http.NOT_FOUND:
|
||||||
|
# Silently do nothing, as is the case for the Foolscap client
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
def advise_corrupt_share(
|
def advise_corrupt_share(
|
||||||
self,
|
self,
|
||||||
@ -1184,21 +1185,24 @@ class _HTTPStorageServer(object):
|
|||||||
reason: bytes
|
reason: bytes
|
||||||
):
|
):
|
||||||
if share_type == b"immutable":
|
if share_type == b"immutable":
|
||||||
imm_client = StorageClientImmutables(self._http_client)
|
client : Union[StorageClientImmutables, StorageClientMutables] = StorageClientImmutables(self._http_client)
|
||||||
return imm_client.advise_corrupt_share(
|
elif share_type == b"mutable":
|
||||||
|
client = StorageClientMutables(self._http_client)
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown share type")
|
||||||
|
return client.advise_corrupt_share(
|
||||||
storage_index, shnum, str(reason, "utf-8", errors="backslashreplace")
|
storage_index, shnum, str(reason, "utf-8", errors="backslashreplace")
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
raise NotImplementedError() # future tickets
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def slot_readv(self, storage_index, shares, readv):
|
def slot_readv(self, storage_index, shares, readv):
|
||||||
mutable_client = StorageClientMutables(self._http_client)
|
mutable_client = StorageClientMutables(self._http_client)
|
||||||
pending_reads = {}
|
pending_reads = {}
|
||||||
reads = {}
|
reads = {}
|
||||||
# TODO if shares list is empty, that means list all shares, so we need
|
# If shares list is empty, that means list all shares, so we need
|
||||||
# to do a query to get that.
|
# to do a query to get that.
|
||||||
assert shares # TODO replace with call to list shares if and only if it's empty
|
if not shares:
|
||||||
|
shares = yield mutable_client.list_shares(storage_index)
|
||||||
|
|
||||||
# Start all the queries in parallel:
|
# Start all the queries in parallel:
|
||||||
for share_number in shares:
|
for share_number in shares:
|
||||||
@ -1246,8 +1250,13 @@ class _HTTPStorageServer(object):
|
|||||||
ReadVector(offset=offset, size=size)
|
ReadVector(offset=offset, size=size)
|
||||||
for (offset, size) in r_vector
|
for (offset, size) in r_vector
|
||||||
]
|
]
|
||||||
|
try:
|
||||||
client_result = yield mutable_client.read_test_write_chunks(
|
client_result = yield mutable_client.read_test_write_chunks(
|
||||||
storage_index, we_secret, lr_secret, lc_secret, client_tw_vectors,
|
storage_index, we_secret, lr_secret, lc_secret, client_tw_vectors,
|
||||||
client_read_vectors,
|
client_read_vectors,
|
||||||
)
|
)
|
||||||
|
except ClientException as e:
|
||||||
|
if e.code == http.UNAUTHORIZED:
|
||||||
|
raise RemoteException("Unauthorized write, possibly you passed the wrong write enabler?")
|
||||||
|
raise
|
||||||
return (client_result.success, client_result.reads)
|
return (client_result.success, client_result.reads)
|
||||||
|
@ -459,6 +459,21 @@ class IStorageServerImmutableAPIsTestsMixin(object):
|
|||||||
lease.get_expiration_time() - self.fake_time() > (31 * 24 * 60 * 60 - 10)
|
lease.get_expiration_time() - self.fake_time() > (31 * 24 * 60 * 60 - 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_add_lease_non_existent(self):
|
||||||
|
"""
|
||||||
|
If the storage index doesn't exist, adding the lease silently does nothing.
|
||||||
|
"""
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
self.assertEqual(list(self.server.get_leases(storage_index)), [])
|
||||||
|
|
||||||
|
renew_secret = new_secret()
|
||||||
|
cancel_secret = new_secret()
|
||||||
|
|
||||||
|
# Add a lease:
|
||||||
|
yield self.storage_client.add_lease(storage_index, renew_secret, cancel_secret)
|
||||||
|
self.assertEqual(list(self.server.get_leases(storage_index)), [])
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def test_add_lease_renewal(self):
|
def test_add_lease_renewal(self):
|
||||||
"""
|
"""
|
||||||
@ -854,6 +869,23 @@ class IStorageServerMutableAPIsTestsMixin(object):
|
|||||||
{0: [b"abcdefg"], 1: [b"0123456"], 2: [b"9876543"]},
|
{0: [b"abcdefg"], 1: [b"0123456"], 2: [b"9876543"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_slot_readv_unknown_storage_index(self):
|
||||||
|
"""
|
||||||
|
With unknown storage index, ``IStorageServer.slot_readv()`` returns
|
||||||
|
empty dict.
|
||||||
|
"""
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
reads = yield self.storage_client.slot_readv(
|
||||||
|
storage_index,
|
||||||
|
shares=[],
|
||||||
|
readv=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
reads,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def create_slot(self):
|
def create_slot(self):
|
||||||
"""Create a slot with sharenum 0."""
|
"""Create a slot with sharenum 0."""
|
||||||
@ -1147,12 +1179,3 @@ class HTTPMutableAPIsTests(
|
|||||||
_HTTPMixin, IStorageServerMutableAPIsTestsMixin, AsyncTestCase
|
_HTTPMixin, IStorageServerMutableAPIsTestsMixin, AsyncTestCase
|
||||||
):
|
):
|
||||||
"""HTTP-specific tests for mutable ``IStorageServer`` APIs."""
|
"""HTTP-specific tests for mutable ``IStorageServer`` APIs."""
|
||||||
|
|
||||||
# TODO will be implemented in later tickets
|
|
||||||
SKIP_TESTS = {
|
|
||||||
"test_STARAW_write_enabler_must_match",
|
|
||||||
"test_add_lease_renewal",
|
|
||||||
"test_add_new_lease",
|
|
||||||
"test_advise_corrupt_share",
|
|
||||||
"test_slot_readv_no_shares",
|
|
||||||
}
|
|
||||||
|
@ -717,7 +717,7 @@ class Repairer(GridTestMixin, unittest.TestCase, RepairTestMixin,
|
|||||||
ss = self.g.servers_by_number[0]
|
ss = self.g.servers_by_number[0]
|
||||||
# we want to delete the share corresponding to the server
|
# we want to delete the share corresponding to the server
|
||||||
# we're making not-respond
|
# we're making not-respond
|
||||||
share = next(ss._get_bucket_shares(self.c0_filenode.get_storage_index()))[0]
|
share = next(ss.get_shares(self.c0_filenode.get_storage_index()))[0]
|
||||||
self.delete_shares_numbered(self.uri, [share])
|
self.delete_shares_numbered(self.uri, [share])
|
||||||
return self.c0_filenode.check_and_repair(Monitor())
|
return self.c0_filenode.check_and_repair(Monitor())
|
||||||
d.addCallback(_then)
|
d.addCallback(_then)
|
||||||
|
@ -688,6 +688,19 @@ class Server(unittest.TestCase):
|
|||||||
writer.abort()
|
writer.abort()
|
||||||
self.failUnlessEqual(ss.allocated_size(), 0)
|
self.failUnlessEqual(ss.allocated_size(), 0)
|
||||||
|
|
||||||
|
def test_immutable_length(self):
|
||||||
|
"""
|
||||||
|
``get_immutable_share_length()`` returns the length of an immutable
|
||||||
|
share, as does ``BucketWriter.get_length()``..
|
||||||
|
"""
|
||||||
|
ss = self.create("test_immutable_length")
|
||||||
|
_, writers = self.allocate(ss, b"allocate", [22], 75)
|
||||||
|
bucket = writers[22]
|
||||||
|
bucket.write(0, b"X" * 75)
|
||||||
|
bucket.close()
|
||||||
|
self.assertEqual(ss.get_immutable_share_length(b"allocate", 22), 75)
|
||||||
|
self.assertEqual(ss.get_buckets(b"allocate")[22].get_length(), 75)
|
||||||
|
|
||||||
def test_allocate(self):
|
def test_allocate(self):
|
||||||
ss = self.create("test_allocate")
|
ss = self.create("test_allocate")
|
||||||
|
|
||||||
@ -766,7 +779,7 @@ class Server(unittest.TestCase):
|
|||||||
writer.close()
|
writer.close()
|
||||||
|
|
||||||
# It should have a lease granted at the current time.
|
# It should have a lease granted at the current time.
|
||||||
shares = dict(ss._get_bucket_shares(storage_index))
|
shares = dict(ss.get_shares(storage_index))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[first_lease],
|
[first_lease],
|
||||||
list(
|
list(
|
||||||
@ -789,7 +802,7 @@ class Server(unittest.TestCase):
|
|||||||
writer.close()
|
writer.close()
|
||||||
|
|
||||||
# The first share's lease expiration time is unchanged.
|
# The first share's lease expiration time is unchanged.
|
||||||
shares = dict(ss._get_bucket_shares(storage_index))
|
shares = dict(ss.get_shares(storage_index))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[first_lease],
|
[first_lease],
|
||||||
list(
|
list(
|
||||||
@ -1315,6 +1328,64 @@ class MutableServer(unittest.TestCase):
|
|||||||
self.failUnless(isinstance(readv_data, dict))
|
self.failUnless(isinstance(readv_data, dict))
|
||||||
self.failUnlessEqual(len(readv_data), 0)
|
self.failUnlessEqual(len(readv_data), 0)
|
||||||
|
|
||||||
|
def test_enumerate_mutable_shares(self):
|
||||||
|
"""
|
||||||
|
``StorageServer.enumerate_mutable_shares()`` returns a set of share
|
||||||
|
numbers for the given storage index, or an empty set if it does not
|
||||||
|
exist at all.
|
||||||
|
"""
|
||||||
|
ss = self.create("test_enumerate_mutable_shares")
|
||||||
|
|
||||||
|
# Initially, nothing exists:
|
||||||
|
empty = ss.enumerate_mutable_shares(b"si1")
|
||||||
|
|
||||||
|
self.allocate(ss, b"si1", b"we1", b"le1", [0, 1, 4, 2], 12)
|
||||||
|
shares0_1_2_4 = ss.enumerate_mutable_shares(b"si1")
|
||||||
|
|
||||||
|
# Remove share 2, by setting size to 0:
|
||||||
|
secrets = (self.write_enabler(b"we1"),
|
||||||
|
self.renew_secret(b"le1"),
|
||||||
|
self.cancel_secret(b"le1"))
|
||||||
|
ss.slot_testv_and_readv_and_writev(b"si1", secrets, {2: ([], [], 0)}, [])
|
||||||
|
shares0_1_4 = ss.enumerate_mutable_shares(b"si1")
|
||||||
|
self.assertEqual(
|
||||||
|
(empty, shares0_1_2_4, shares0_1_4),
|
||||||
|
(set(), {0, 1, 2, 4}, {0, 1, 4})
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mutable_share_length(self):
|
||||||
|
"""``get_mutable_share_length()`` returns the length of the share."""
|
||||||
|
ss = self.create("test_mutable_share_length")
|
||||||
|
self.allocate(ss, b"si1", b"we1", b"le1", [16], 23)
|
||||||
|
ss.slot_testv_and_readv_and_writev(
|
||||||
|
b"si1", (self.write_enabler(b"we1"),
|
||||||
|
self.renew_secret(b"le1"),
|
||||||
|
self.cancel_secret(b"le1")),
|
||||||
|
{16: ([], [(0, b"x" * 23)], None)},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
self.assertEqual(ss.get_mutable_share_length(b"si1", 16), 23)
|
||||||
|
|
||||||
|
def test_mutable_share_length_unknown(self):
|
||||||
|
"""
|
||||||
|
``get_mutable_share_length()`` raises a ``KeyError`` on unknown shares.
|
||||||
|
"""
|
||||||
|
ss = self.create("test_mutable_share_length_unknown")
|
||||||
|
self.allocate(ss, b"si1", b"we1", b"le1", [16], 23)
|
||||||
|
ss.slot_testv_and_readv_and_writev(
|
||||||
|
b"si1", (self.write_enabler(b"we1"),
|
||||||
|
self.renew_secret(b"le1"),
|
||||||
|
self.cancel_secret(b"le1")),
|
||||||
|
{16: ([], [(0, b"x" * 23)], None)},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
# Wrong share number.
|
||||||
|
ss.get_mutable_share_length(b"si1", 17)
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
# Wrong storage index
|
||||||
|
ss.get_mutable_share_length(b"unknown", 16)
|
||||||
|
|
||||||
def test_bad_magic(self):
|
def test_bad_magic(self):
|
||||||
ss = self.create("test_bad_magic")
|
ss = self.create("test_bad_magic")
|
||||||
self.allocate(ss, b"si1", b"we1", next(self._lease_secret), set([0]), 10)
|
self.allocate(ss, b"si1", b"we1", next(self._lease_secret), set([0]), 10)
|
||||||
|
File diff suppressed because it is too large
Load Diff
2
tox.ini
2
tox.ini
@ -97,7 +97,7 @@ setenv =
|
|||||||
COVERAGE_PROCESS_START=.coveragerc
|
COVERAGE_PROCESS_START=.coveragerc
|
||||||
commands =
|
commands =
|
||||||
# NOTE: 'run with "py.test --keep-tempdir -s -v integration/" to debug failures'
|
# NOTE: 'run with "py.test --keep-tempdir -s -v integration/" to debug failures'
|
||||||
py.test --timeout=1800 --coverage -v {posargs:integration}
|
py.test --timeout=1800 --coverage -s -v {posargs:integration}
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage report
|
coverage report
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user